From d042487dc118e494db2e2c1382310255c90ff544 Mon Sep 17 00:00:00 2001 From: Roshan Kumar Date: Tue, 28 Jul 2026 10:56:08 +0530 Subject: [PATCH 0001/1317] xfrm: iptfs: fix stack OOB read in iptfs_skb_reset_frag_walk() iptfs_skb_reset_frag_walk() advances to the fragment containing @offset with an unbounded loop: while (offset >= walk->past + walk->frags[walk->fragi].len) walk->past += walk->frags[walk->fragi++].len; walk->fragi is advanced and walk->frags[walk->fragi] is dereferenced without ever checking fragi against walk->nr_frags. When the requested offset is at or beyond the total length spanned by the walk's fragments, fragi runs past nr_frags and off the end of the fixed-size on-stack frags[MAX_SKB_FRAGS + 1] array, reading out-of-bounds stack memory. The two callers behave differently: iptfs_skb_add_frags() already guards against this with if (!walk->nr_frags || offset >= walk->total + walk->initial_offset) return len; but iptfs_skb_can_add_frags() has no such guard and calls iptfs_skb_reset_frag_walk() unconditionally, so it performs the out-of-range walk. Its own "fragi < walk->nr_frags" bound check runs only afterwards, too late to prevent the read. This is reachable from the receive path: a crafted IP-TFS (AGGFRAG) payload delivered to an IPTFS SA drives iptfs_reassem_cont() -> iptfs_skb_can_add_frags() with an offset past the fragment total, e.g.: BUG: KASAN: stack-out-of-bounds in iptfs_skb_reset_frag_walk+0x235/0x250 Read of size 4 at addr ffff888008ad7210 by task repro/345 iptfs_skb_reset_frag_walk+0x235/0x250 net/xfrm/xfrm_iptfs.c:392 iptfs_skb_can_add_frags+0x155/0x310 net/xfrm/xfrm_iptfs.c:420 iptfs_reassem_cont+0xcf8/0x1140 net/xfrm/xfrm_iptfs.c:902 iptfs_input_ordered+0x552/0x670 net/xfrm/xfrm_iptfs.c:1280 iptfs_input+0x3d6/0xde0 net/xfrm/xfrm_iptfs.c:1741 xfrm_input+0x282f/0x6140 net/xfrm/xfrm_input.c:700 xfrm4_esp_rcv+0x93/0x120 net/ipv4/xfrm4_protocol.c:104 ip_rcv+0x278/0x2d0 net/ipv4/ip_input.c:612 Give iptfs_skb_can_add_frags() the same up-front guard that iptfs_skb_add_frags() already has, so the walk is never entered with an out-of-range offset. When it triggers, the caller falls back to the existing linearize-and-copy path, which is safe. Fixes: 5f2b6a909574 ("xfrm: iptfs: add skb-fragment sharing code") Reported-by: Roshan Kumar Signed-off-by: Roshan Kumar Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_iptfs.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c index 597aedeac26e..2ce15c472cc4 100644 --- a/net/xfrm/xfrm_iptfs.c +++ b/net/xfrm/xfrm_iptfs.c @@ -416,6 +416,14 @@ static bool iptfs_skb_can_add_frags(const struct sk_buff *skb, if (skb_has_frag_list(skb) || skb->pp_recycle != walk->pp_recycle) return false; + /* Reject an @offset that is at or beyond the end of the walk's data + * before calling iptfs_skb_reset_frag_walk(), whose fragment-advance + * loop is otherwise unbounded and would index past walk->frags[]. + * This mirrors the guard already present in iptfs_skb_add_frags(). + */ + if (!walk->nr_frags || offset >= walk->total + walk->initial_offset) + return false; + /* Make offset relative to current frag after setting that */ offset = iptfs_skb_reset_frag_walk(walk, offset); From 89fefad9f971bc637fb22373078144f2563c4be9 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 30 Jul 2026 18:35:43 +0800 Subject: [PATCH 0002/1317] xfrm: serialize state GC with device state flush The deferred-device pass in xfrm_dev_state_flush() finds states under xfrm_state_dev_gc_lock, but drops the lock before calling xfrm_dev_state_free() because the driver callback may sleep. The device GC list does not hold an xfrm_state reference, so the state GC worker can destroy the same state concurrently. The race can proceed as follows: CPU 0 CPU 1 find x on the device GC list drop xfrm_state_dev_gc_lock read x->xso.dev xfrm_state_gc_destroy(x) xfrm_dev_state_free(x) xfrm_state_free(x) continue xfrm_dev_state_free(x) Both paths can invoke the driver callback and drop the device reference. CPU 0 can also access the xfrm_state after CPU 1 has freed it. KASAN reported: BUG: KASAN: slab-use-after-free in xfrm_dev_state_free+0x24c/0x2a0 Read of size 8 at addr ffff88810bbaa960 by task poc/102 Call Trace: xfrm_dev_state_free+0x24c/0x2a0 xfrm_dev_state_flush+0x353/0x400 xfrm_dev_event+0x26d/0x3a0 notifier_call_chain+0xc0/0x280 __dev_notify_flags+0x169/0x250 netif_change_flags+0xe7/0x160 dev_change_flags+0x96/0x220 devinet_ioctl+0x7f4/0x1880 Allocated by task 87: xfrm_state_alloc+0x1e/0x5c0 xfrm_add_sa+0xe7f/0x5820 xfrm_user_rcv_msg+0x4f3/0x940 Freed by task 57: kmem_cache_free+0xcb/0x3d0 xfrm_state_gc_task+0x4a8/0x650 process_one_work+0x63a/0x1070 Serialize xfrm_state destruction against the deferred-device pass with a mutex. Keep xfrm_state_dev_gc_lock limited to list operations and retain the existing callback and device-reference release ordering. Fixes: 07b87f9eea0c ("xfrm: Fix unregister netdevice hang on hardware offload.") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_state.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 36a4f6793ede..de097bba803b 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -226,6 +226,7 @@ static struct xfrm_state_afinfo __rcu *xfrm_state_afinfo[NPROTO]; static DEFINE_SPINLOCK(xfrm_state_gc_lock); static DEFINE_SPINLOCK(xfrm_state_dev_gc_lock); +static DEFINE_MUTEX(xfrm_state_gc_mutex); int __xfrm_state_delete(struct xfrm_state *x); @@ -632,8 +633,10 @@ static void xfrm_state_gc_task(struct work_struct *work) synchronize_rcu(); + mutex_lock(&xfrm_state_gc_mutex); hlist_for_each_entry_safe(x, tmp, &gc_list, gclist) xfrm_state_gc_destroy(x); + mutex_unlock(&xfrm_state_gc_mutex); } static enum hrtimer_restart xfrm_timer_handler(struct hrtimer *me) @@ -1000,6 +1003,7 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali out: spin_unlock_bh(&net->xfrm.xfrm_state_lock); + mutex_lock(&xfrm_state_gc_mutex); spin_lock_bh(&xfrm_state_dev_gc_lock); restart_gc: hlist_for_each_entry_safe(x, tmp, &xfrm_state_dev_gc_list, dev_gclist) { @@ -1014,6 +1018,7 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali } spin_unlock_bh(&xfrm_state_dev_gc_lock); + mutex_unlock(&xfrm_state_gc_mutex); xfrm_flush_gc(); From 42d100f5232f39b8ea7b00a7c2482325c7f032a4 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 31 Jul 2026 10:06:20 +0000 Subject: [PATCH 0003/1317] xfrm: add missing RCU read lock in xfrm_send_migrate_state() xfrm_nlmsg_multicast() requires the RCU read lock to be held because it safely dereferences the net->xfrm.nlsk pointer using rcu_dereference(). When it is called from xfrm_send_migrate_state(), the RCU read lock is not held, which triggers a suspicious RCU usage warning: WARNING: suspicious RCU usage net/xfrm/xfrm_user.c:1630 suspicious rcu_dereference_check() usage! Call Trace: lockdep_rcu_suspicious+0x13f/0x1d0 kernel/locking/lockdep.c:6876 xfrm_nlmsg_multicast+0x1d8/0x1f0 net/xfrm/xfrm_user.c:1630 xfrm_send_migrate_state+0x870/0xae0 net/xfrm/xfrm_user.c:3340 xfrm_do_migrate_state+0x1749/0x1e90 net/xfrm/xfrm_user.c:3507 xfrm_user_rcv_msg+0x7a8/0xf30 net/xfrm/xfrm_user.c:3907 Fix this by wrapping the xfrm_nlmsg_multicast() call in xfrm_send_migrate_state() with rcu_read_lock() and rcu_read_unlock(). Fixes: a9d155ea9b44 ("xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+c0e99a1aa85a286d7a3b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c0e99a1aa85a286d7a3b Link: https://syzkaller.appspot.com/ai_job?id=8977f559-3a7e-4bb5-b4d6-1196956260b6 Signed-off-by: Aleksandr Nogikh Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_user.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 6266a92cf302..980dbeb5a57d 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -3337,7 +3337,11 @@ static int xfrm_send_migrate_state(struct net *net, return err; } - return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE); + rcu_read_lock(); + err = xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE); + rcu_read_unlock(); + + return err; } static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh, From dc33262be1fe43d0eb0b84fb58c6ed42e2f64a8c Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Mon, 3 Aug 2026 12:01:54 +0800 Subject: [PATCH 0004/1317] xfrm: iptfs: fix runt reassembly panic from short inner tot_len When the start of an inner packet is split across two outer packets such that fewer than 4 bytes land at the end of the first one, __input_process_payload() saves those bytes as a runt and skips the iplen/iphlen validation performed for in-place packets. When the continuation packet arrives, iptfs_reassem_cont() only requires the declared inner length to be >= sizeof(ra_runt) (6) before allocating the reassembly skb with that attacker-controlled length. However, __iptfs_iphlen() always returns the fixed minimum IP header size (20 for IPv4, 40 for IPv6), so for an inner IPv4 tot_len in [6, 19] the header-completion copy writes past the declared packet length, and the subsequent "ipremain -= copylen" underflows to ~4GB, leaving the payload copy length bounded only by blkoff (up to 64KB). At runtime the skb_put() tailroom check turns this into skb_over_panic(), i.e. an unprivileged kernel panic (DoS), reachable locally via userns+netns IPTFS SAs and remotely against IPTFS VPN gateways when the decrypted outer skb is linear (e.g. AF_PACKET taps, tun/tap delivery). Align the runt path with the normal path by requiring the declared inner length to cover at least the IP header size. This also subsumes the previous >= sizeof(ra_runt) check, since the minimum IP header is always larger than the runt buffer. This issue was found by the autokbug dynamic kernel fuzzer at Tencent Yunding Lab. Fixes: 075694765446 ("xfrm: iptfs: handle received fragmented inner packets") Reported-by: Henry Martin Signed-off-by: Henry Martin Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_iptfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c index 2ce15c472cc4..6920940a35b4 100644 --- a/net/xfrm/xfrm_iptfs.c +++ b/net/xfrm/xfrm_iptfs.c @@ -828,8 +828,8 @@ static u32 iptfs_reassem_cont(struct xfrm_iptfs_data *xtfs, u64 seq, * allocate an in progress skb */ ipremain = __iptfs_iplen(xtfs->ra_runt); - if (ipremain < sizeof(xtfs->ra_runt)) { - /* length has to be at least runtsize large */ + if (ipremain < __iptfs_iphlen(xtfs->ra_runt)) { + /* length has to be at least the IP header size */ XFRM_INC_STATS(xs_net(xtfs->x), LINUX_MIB_XFRMINIPTFSERROR); goto abandon; From 6973a21ee73c5567f883813c8ef414774b45892f Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 21:28:58 +0800 Subject: [PATCH 0005/1317] ipv6: xfrm: use full sockets in local error paths xfrm6_local_rxpmtu() and xfrm6_local_error() dereference skb->sk as if it always pointed at a full IPv6 socket. That is not guaranteed. TCP SYN-ACK skbs can be owned by a TCP_NEW_SYN_RECV request_sock while the output path itself is driven by the full listener. If rerouting selects an IPv6 XFRM tunnel route with a lower MTU, the local PMTU/error handling path can reach these callbacks with that mini-socket still attached to the skb. The callbacks then miscast the request socket as a full inet/IPv6 socket and can read beyond the request_sock allocation when they access inet_sock or ipv6_pinfo state. Resolve the owner with skb_to_full_sk() in both callbacks and bail out when no full socket is attached. This matches the surrounding XFRM IPv6 PMTU/error logic, which already reasons about full sockets with skb_to_full_sk(). Fixes: dd767856a36e ("xfrm6: Don't call icmpv6_send on local error") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Steffen Klassert --- net/ipv6/xfrm6_output.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/ipv6/xfrm6_output.c b/net/ipv6/xfrm6_output.c index 512bdaf13699..44b221a09a0c 100644 --- a/net/ipv6/xfrm6_output.c +++ b/net/ipv6/xfrm6_output.c @@ -19,7 +19,10 @@ void xfrm6_local_rxpmtu(struct sk_buff *skb, u32 mtu) { struct flowi6 fl6; - struct sock *sk = skb->sk; + struct sock *sk = skb_to_full_sk(skb); + + if (!sk) + return; fl6.flowi6_oif = sk->sk_bound_dev_if; fl6.daddr = ipv6_hdr(skb)->daddr; @@ -31,7 +34,10 @@ void xfrm6_local_error(struct sk_buff *skb, u32 mtu) { struct flowi6 fl6; const struct ipv6hdr *hdr; - struct sock *sk = skb->sk; + struct sock *sk = skb_to_full_sk(skb); + + if (!sk) + return; hdr = skb->encapsulation ? inner_ipv6_hdr(skb) : ipv6_hdr(skb); fl6.fl6_dport = inet_sk(sk)->inet_dport; From d1ebd9081879fd9ae9c8fb7e8928f19cc88ae320 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Tue, 4 Aug 2026 06:10:37 +0000 Subject: [PATCH 0006/1317] xfrm: fix compat ALLOCSPI request use-after-free xfrm_state_netlink() builds the ALLOCSPI response with dump_one_state(), which already calls alloc_compat() with the response skb and header. xfrm_alloc_userspi() then calls alloc_compat() again, but passes the original request skb and its header. For a compat request, the translator therefore interprets the 228-byte compat xfrm_userspi_info as the 232-byte native layout and reads four bytes past the declared payload. It also publishes the translated child through the request's frag_list. A multicast clone of the request shares skb_shared_info and can observe that child. xfrm_user_rcv_msg() frees it after the request handler returns, racing a compat receiver which may still be copying from it and resulting in a use-after-free. Remove the redundant conversion. The response keeps its correct compat translation from dump_one_state(), and no child is attached to the inbound request. Fixes: 5f3eea6b7e8f ("xfrm/compat: Attach xfrm dumps to 64=>32 bit translator") Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_user.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 980dbeb5a57d..a2587c7e796b 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -1877,7 +1877,6 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, struct net *net = sock_net(skb->sk); struct xfrm_state *x; struct xfrm_userspi_info *p; - struct xfrm_translator *xtr; struct sk_buff *resp_skb; xfrm_address_t *daddr; int family; @@ -1943,17 +1942,6 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, goto out; } - xtr = xfrm_get_translator(); - if (xtr) { - err = xtr->alloc_compat(skb, nlmsg_hdr(skb)); - - xfrm_put_translator(xtr); - if (err) { - kfree_skb(resp_skb); - goto out; - } - } - err = nlmsg_unicast(xfrm_net_nlsk(net, skb), resp_skb, NETLINK_CB(skb).portid); out: From d2f5082f9e84653fa1a9e8aebaaff23e688f5e19 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 7 Aug 2026 17:15:33 +0000 Subject: [PATCH 0007/1317] xfrm: add missing rcu_read_lock(), skb_dst_force() and dev_hold() for xfrm_trans_reinject() syzbot reported a suspicious RCU usage warning in ip6_pkt_drop(): WARNING: suspicious RCU usage in ip6_pkt_drop include/net/addrconf.h:389 suspicious rcu_dereference_check() usage! Call Trace: __in6_dev_get_safely include/net/addrconf.h:389 [inline] ip6_pkt_drop+0x596/0x610 net/ipv6/route.c:4620 ip6_pkt_discard+0x1c/0x30 net/ipv6/route.c:4651 xfrm_trans_reinject+0x324/0x630 net/xfrm/xfrm_input.c:806 process_one_work kernel/workqueue.c:3322 [inline] process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486 When commit 4f4920669d21 ("xfrm: Reinject transport-mode packets through workqueue") converted xfrm_trans_reinject from a tasklet to a workqueue, the reinjection loop ceased running in softirq context. Workqueue workers run in process context where local_bh_disable() does not enter an RCU read-side critical section under CONFIG_PREEMPT_RCU. Because finish callbacks (such as ip6_rcv_finish) expect to run under an RCU read lock (performing route lookups, l3mdev lookups, and accessing RCU-protected data structures), invoking them in workqueue context without rcu_read_lock() triggers RCU lockdep warnings. Furthermore, packets queued to the workqueue via xfrm_trans_queue_net() may carry non-refcounted (noref) dst entries (e.g. from ip_route_input_noref). Additionally, on netdevice unregistration, dst_dev_put() replaces dst->dev with blackhole_netdev, so dst entries do not keep skb->dev alive while queued in the workqueue. Fix these issues by: 1. Calling skb_dst_force(skb) in xfrm_trans_queue_net() while still in the caller's RCU section to ensure dst is reference-counted before queuing. 2. Holding a reference on skb->dev via dev_hold()/dev_put() across workqueue deferral so skb->dev remains valid during finish() callback processing. 3. Acquiring rcu_read_lock() around the finish callback invocation loop in xfrm_trans_reinject(). Fixes: 4f4920669d21 ("xfrm: Reinject transport-mode packets through workqueue") Reported-by: syzbot Signed-off-by: Eric Dumazet Cc: Steffen Klassert Cc: Liu Jian Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_input.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c index eecab337bd0a..8f6109eada7e 100644 --- a/net/xfrm/xfrm_input.c +++ b/net/xfrm/xfrm_input.c @@ -800,12 +800,17 @@ static void xfrm_trans_reinject(struct work_struct *work) spin_unlock_bh(&trans->queue_lock); local_bh_disable(); + rcu_read_lock(); while ((skb = __skb_dequeue(&queue))) { struct net *net = XFRM_TRANS_SKB_CB(skb)->net; + struct net_device *dev = skb->dev; XFRM_TRANS_SKB_CB(skb)->finish(net, NULL, skb); + if (dev) + dev_put(dev); put_net(net); } + rcu_read_unlock(); local_bh_enable(); } @@ -821,12 +826,18 @@ int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb, if (skb_queue_len(&trans->queue) >= READ_ONCE(net_hotdata.max_backlog)) return -ENOBUFS; + if (skb_dst(skb) && !skb_dst_force(skb)) + return -EHOSTUNREACH; + BUILD_BUG_ON(sizeof(struct xfrm_trans_cb) > sizeof(skb->cb)); hold_net = maybe_get_net(net); if (!hold_net) return -ENODEV; + if (skb->dev) + dev_hold(skb->dev); + XFRM_TRANS_SKB_CB(skb)->finish = finish; XFRM_TRANS_SKB_CB(skb)->net = hold_net; spin_lock_bh(&trans->queue_lock); From 0ecd56573c1f272c72298154a3854380876dbb7c Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 4 Aug 2026 12:41:13 +0200 Subject: [PATCH 0008/1317] ext4: Avoid entering writeback paths during fastcommit replay Fastcommit replay effectively happens in nojournal mode. This results in ext4_mark_iloc_dirty() setting I_METADATA_WRITEBACK flag and as a result we end up entering filesystem writeback functions. However during fastcommit replay s_writepages_rwsem isn't initialized yet and hence we crash. Fix the problem by avoiding setting I_METADATA_WRITEBACK during fastcommit replay. Journal replay flushes the whole block device after replay anyway so all metadata is properly persisted and replay is faster this way as a bonus. Fixes: c26339e1df33 ("ext4: Fix data integrity writeout issues in nojournal mode") Reported-by: Venkat Rao Bagalkote Reported-by: Ojaswin Mujoo Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260804104112.93202-2-jack@suse.cz Tested-by: Venkat Rao Bagalkote Reviewed-by: Ojaswin Mujoo Signed-off-by: Christian Brauner (Amutable) --- fs/ext4/inode.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index bd4b778df9eb..26f0f9714f03 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -6456,9 +6456,10 @@ int ext4_chunk_trans_blocks(struct inode *inode, int nrblocks) int ext4_mark_iloc_dirty(handle_t *handle, struct inode *inode, struct ext4_iloc *iloc) { + struct super_block *sb = inode->i_sb; int err = 0; - err = ext4_emergency_state(inode->i_sb); + err = ext4_emergency_state(sb); if (unlikely(err)) { put_bh(iloc->bh); return err; @@ -6473,9 +6474,13 @@ int ext4_mark_iloc_dirty(handle_t *handle, put_bh(iloc->bh); /* * Mark that there's metadata writeout pending for the inode so that it - * gets properly flushed on fsync(2) and similar. + * gets properly flushed on fsync(2) and similar. We don't bother for + * fastcommit replay as that flushes the whole bdev afterwards anyway. + * It is faster this way and we avoid entering fs writeback paths which + * aren't fully initialized yet. */ - if (!EXT4_SB(inode->i_sb)->s_journal) { + if (!ext4_handle_valid(handle) && + !(EXT4_SB(sb)->s_mount_state & EXT4_FC_REPLAY)) { /* * Inode didn't need to go through dirtying, make sure it is * attached to wb so that writeback can handle it. From 82e9343260dfc6dda6349f285d9a5eac3e0738d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Fri, 14 Aug 2026 10:20:05 +0000 Subject: [PATCH 0009/1317] nsfs: keep namespace tree fields stable until after RCU grace period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct ns_common overlays struct ns_tree with the rcu_head used by kfree_rcu(). That lets the deferred-free machinery overwrite ns_id and __ns_ref_active as soon as a namespace is queued for freeing, even though nsfs tree walkers may still read those fields under RCU after ns_tree_remove(). KASAN reports slab UAF. Keep the tree state and deferred-free callback storage separate. Namespace tree readers can then continue to validate and take references until the grace period has elapsed. Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260814102005.1939777-1-Jeremy.Jean@oss.cyber.gouv.fr Signed-off-by: Christian Brauner (Amutable) --- include/linux/ns/ns_common_types.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/linux/ns/ns_common_types.h b/include/linux/ns/ns_common_types.h index ea45c54e4435..6ed6b497831c 100644 --- a/include/linux/ns/ns_common_types.h +++ b/include/linux/ns/ns_common_types.h @@ -116,10 +116,8 @@ struct ns_common { struct dentry *stashed; const struct proc_ns_operations *ops; unsigned int inum; - union { - struct ns_tree; - struct rcu_head ns_rcu; - }; + struct ns_tree; + struct rcu_head ns_rcu; }; #define to_ns_common(__ns) \ From 3c0ebc4c07ff1147724d8f370203e62390ae7ee7 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Fri, 21 Aug 2026 10:01:22 +0200 Subject: [PATCH 0010/1317] vt: hide cursor prior to font changes to avoid out-of-bound reads KASAN reports slab-out-of-bounds errors: BUG: KASAN: slab-out-of-bounds in soft_cursor+0x3eb/0xb70 drivers/video/fbdev/core/softcursor.c:70 When changing the size of a sceen font, the amount of columns and rows on a screen may change and thus the current position of the cursor and the selection may suddenly lay outside of the current screen limits. Clear the selection and hide the cursor before any font changes to avoid such possible out of bounds accesses. Reported-by: Jaeyoung Chung Signed-off-by: Helge Deller Link: https://lore.kernel.org/all/20260819163440.3702924-1-jjy600901@snu.ac.kr/ --- drivers/tty/vt/vt.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 8f467b22b799..57edf37495a8 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -4986,8 +4986,8 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op) if (!vc->vc_sw->con_font_set) return -ENOSYS; - if (vc_is_sel(vc)) - clear_selection(); + /* hide selection and cursor prior font changes */ + hide_cursor(vc); return vc->vc_sw->con_font_set(vc, &font, vpitch, op->flags); } @@ -5011,8 +5011,9 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op) if (!vc->vc_sw->con_font_default) return -ENOSYS; - if (vc_is_sel(vc)) - clear_selection(); + /* hide selection and cursor prior font changes */ + hide_cursor(vc); + int ret = vc->vc_sw->con_font_default(vc, &font, s); if (ret) return ret; From 2afb8dc1f4390f164db8352f8e685e126e9db566 Mon Sep 17 00:00:00 2001 From: Siwei Zhang Date: Thu, 30 Jul 2026 19:40:08 +0800 Subject: [PATCH 0011/1317] xfrm: use hlist_del_init_rcu for state_cache and state_cache_input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 14acf9652e56 ("xfrm: defensively unhash xfrm_state lists in __xfrm_state_delete") converted bydst/bysrc/byseq/byspi from hlist_del_rcu() to hlist_del_init_rcu() so that a second __xfrm_state_delete() on the same object becomes a no-op rather than a write through LIST_POISON pprev. It missed state_cache and state_cache_input, which kept hlist_del_rcu(): - hlist_del_rcu() leaves pprev = LIST_POISON2 (non-NULL), so hlist_unhashed() returns false. - hlist_del_init_rcu() leaves pprev = NULL, so hlist_unhashed() returns true. A second __xfrm_state_delete() therefore enters __hlist_del() on the already-deleted state_cache/state_cache_input nodes and does WRITE_ONCE(*pprev, next) through LIST_POISON2 — a write use-after-free once the slab is reused. The corruption can in turn cause a subsequent hlist_for_each_entry_rcu traversal to follow a dangling next pointer, producing the read use-after-free reported in xfrm_input_state_lookup(). Switch state_cache and state_cache_input to hlist_del_init_rcu() to match the other four lists, closing the write use-after-free and, with it, the read use-after-free it spawns. Assisted-by: CodeBuddy:GLM-5.2 Fixes: 0045e3d80613 ("xfrm: Cache used outbound xfrm states at the policy.") Fixes: 81a331a0e72d ("xfrm: Add an inbound percpu state cache.") Cc: stable@vger.kernel.org Signed-off-by: Siwei Zhang Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index de097bba803b..e45aa1ed5b96 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -826,9 +826,9 @@ int __xfrm_state_delete(struct xfrm_state *x) if (!hlist_unhashed(&x->byseq)) hlist_del_init_rcu(&x->byseq); if (!hlist_unhashed(&x->state_cache)) - hlist_del_rcu(&x->state_cache); + hlist_del_init_rcu(&x->state_cache); if (!hlist_unhashed(&x->state_cache_input)) - hlist_del_rcu(&x->state_cache_input); + hlist_del_init_rcu(&x->state_cache_input); if (!hlist_unhashed(&x->byspi)) hlist_del_init_rcu(&x->byspi); From f89416eb3db151170a6f3c6dfc5239d26cdce4d2 Mon Sep 17 00:00:00 2001 From: Maher Azzouzi Date: Mon, 17 Aug 2026 14:37:52 +0100 Subject: [PATCH 0012/1317] esp: downgrade zerocopy managed frags before mutating skb frags On the out-of-place output path (esp->inplace == false) ESP rewrites the skb frag array: esp_output_head() appends a trailer frag and esp_output_tail() replaces the frags with a destination page, both referenced with get_page(). When the skb carries zerocopy managed frags (SKBFL_MANAGED_FRAG_REFS) the payload frags are owned by the ubuf and must not be referenced or unreferenced individually, but ESP mutates the frag array without ever downgrading the skb. This breaks the managed-frag invariant two ways: - esp_ssg_unref() walks the source scatterlist and drops a page reference for every frag, including the ubuf-owned payload frags, pushing their refcount below the GUP pin bias while the pages are still pinned, i.e. a use-after-free of the zerocopy pages; - esp_output_tail() installs its destination page as frag 0 with get_page() but leaves SKBFL_MANAGED_FRAG_REFS set, so skb_release_data() takes the skip_unref branch and never drops that reference, leaking the x->xfrag page at packet rate. Fix this the way every other frag-mutating site does (__ip_append_data(), __ip6_append_data(), tcp_sendmsg_locked()) and call skb_zcopy_downgrade_managed() before ESP touches the frag array: it takes a real reference on each existing frag and clears SKBFL_MANAGED_FRAG_REFS, so the per-frag unref in esp_ssg_unref() and the frag release in skb_release_data() are both balanced and no mixed-ownership frag array is left behind. Fixes: 753f1ca4e1e5 ("net: introduce managed frags infrastructure") Signed-off-by: Maher Azzouzi Signed-off-by: Steffen Klassert --- net/ipv4/esp4.c | 6 ++++++ net/ipv6/esp6.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index dfc81ee969ae..faa48f5b9739 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -441,6 +441,12 @@ int esp_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info * esp->inplace = false; + /* Take real page refs and clear SKBFL_MANAGED_FRAG_REFS before + * we mutate the frag array, so the per-frag unref stays balanced + * for zerocopy managed frags (see __ip_append_data()). + */ + skb_zcopy_downgrade_managed(skb); + allocsize = ALIGN(tailen, L1_CACHE_BYTES); spin_lock_bh(&x->lock); diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 296b57926abb..a3a3857eed98 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -470,6 +470,12 @@ int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info esp->inplace = false; + /* Take real page refs and clear SKBFL_MANAGED_FRAG_REFS before + * we mutate the frag array, so the per-frag unref stays balanced + * for zerocopy managed frags (see __ip_append_data()). + */ + skb_zcopy_downgrade_managed(skb); + allocsize = ALIGN(tailen, L1_CACHE_BYTES); spin_lock_bh(&x->lock); From 72e91bba1190c91c76ef2f81476399eb66b87ed2 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 22 Aug 2026 11:43:08 +0200 Subject: [PATCH 0013/1317] fbdev: omapfb: Fix __be32 sparse warning in panel_enabled() This commit resolves a sparse warning in panel_enabled() by explicitly reading the display status into a __be32 variable. It then converts this value to CPU endianness using __be32_to_cpu() before checking the bits. This should fix this sparse warning: ../omapfb/displays/panel-sony-acx565akm.c:218:23: sparse: sparse: cast to restricted __be32 Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608211811.lps93xao-lkp@intel.com/ Signed-off-by: Helge Deller --- .../video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c b/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c index 8f430d9e8054..0202ca8cbfc2 100644 --- a/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c +++ b/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c @@ -210,12 +210,13 @@ static void set_display_state(struct panel_drv_data *ddata, int enabled) static int panel_enabled(struct panel_drv_data *ddata) { + __be32 disp_status_be; u32 disp_status; int enabled; acx565akm_read(ddata, MIPID_CMD_READ_DISP_STATUS, - (u8 *)&disp_status, 4); - disp_status = __be32_to_cpu(disp_status); + (u8 *)&disp_status_be, 4); + disp_status = __be32_to_cpu(disp_status_be); enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10)); dev_dbg(&ddata->spi->dev, "LCD panel %senabled by bootloader (status 0x%04x)\n", From 94e6a058b16820e02f25e1221a4c4e713ba23550 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 22 Aug 2026 12:03:15 +0200 Subject: [PATCH 0014/1317] fbcon: Fix KASAN slab-out-of-bounds Read in fbcon_prepare_logo Ensure the logo will not exceed the screen size, which then should fix a reported KASAN: slab-out-of-bounds Read in fbcon_prepare_logo. Reported-by: syzbot+0c815b25cdb3678e7083@syzkaller.appspotmail.com Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbcon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index 23b3c536d53d..01715873ea49 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -660,6 +660,13 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info, erase &= ~0x400; logo_height = fb_prepare_logo(info, par->rotate); logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height); + logo_lines = min(logo_lines, rows); + logo_lines = min(logo_lines, new_rows - 1); + if (logo_lines <= 0) { + logo_lines = 0; + logo_shown = FBCON_LOGO_DONTSHOW; + return; + } q = (unsigned short *) (vc->vc_origin + vc->vc_size_row * rows); step = logo_lines * cols; From 72dd0ec09e7cc98ed58ddeac26575e5d1ab8a93d Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 4 Aug 2026 22:52:19 +0900 Subject: [PATCH 0015/1317] printk: Don't WARN on kthread_run failure. Since __kthread_create_on_node() returns -EINTR upon SIGKILL, we should not use WARN_ON() in order to catch kthread_run() failure. Reported-by: syzbot+1ebbc20f223b99446034@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1ebbc20f223b99446034 Fixes: 5f53ca3ff83b ("printk: Implement legacy printer kthread for PREEMPT_RT") Fixes: 76f258bf3f2a ("printk: nbcon: Introduce printer kthreads") Signed-off-by: Tetsuo Handa Reviewed-by: John Ogness Reviewed-by: Petr Mladek Link: https://patch.msgid.link/76bb4c1c-5d85-4635-b3bb-fc06f292c59e@I-love.SAKURA.ne.jp Signed-off-by: Petr Mladek --- kernel/printk/nbcon.c | 2 +- kernel/printk/printk.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 4b03b019cd5e..a5921a84a80e 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1382,7 +1382,7 @@ bool nbcon_kthread_create(struct console *con) return true; kt = kthread_run(nbcon_kthread_func, con, "pr/%s%d", con->name, con->index); - if (WARN_ON(IS_ERR(kt))) { + if (IS_ERR(kt)) { con_printk(KERN_ERR, con, "failed to start printing thread\n"); return false; } diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 3fcdf4b4e2e5..6d3d18a50da7 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -3732,7 +3732,7 @@ static bool legacy_kthread_create(void) lockdep_assert_console_list_lock_held(); kt = kthread_run(legacy_kthread_func, NULL, "pr/legacy"); - if (WARN_ON(IS_ERR(kt))) { + if (IS_ERR(kt)) { pr_err("failed to start legacy printing thread\n"); return false; } From ffe0486b139e45cd9c9ca2584f04a1910fe4f8a6 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 19 Aug 2026 15:38:53 +0200 Subject: [PATCH 0016/1317] console: fix /dev/kmsg reference in flags kernel doc Fix typo in the CON_EXTENDED flag kernel doc which is supposed to refer to '/dev/kmsg'. Fixes: 717a5651b109 ("console: Use BIT() macros for @flags values") Signed-off-by: Johan Hovold Reviewed-by: Petr Mladek Link: https://patch.msgid.link/20260819133853.286658-1-johan@kernel.org Signed-off-by: Petr Mladek --- include/linux/console.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/console.h b/include/linux/console.h index d624200cfc17..502d1abe3f50 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -173,7 +173,7 @@ static inline void con_debug_leave(void) { } * @CON_BRL: Indicates a braille device which is exempt from * receiving the printk spam for obvious reasons. * @CON_EXTENDED: The console supports the extended output format of - * /dev/kmesg which requires a larger output buffer. + * /dev/kmsg which requires a larger output buffer. * @CON_SUSPENDED: Indicates if a console is suspended. If true, the * printing callbacks must not be called. * @CON_NBCON: Console can operate outside of the legacy style console_lock From 55a4c98abb9694b067c6a031d11501f06b6b523c Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Sat, 1 Aug 2026 10:12:57 +0300 Subject: [PATCH 0017/1317] ufs: create the root dentry after loading cylinder metadata ufs_fill_super() installed sb->s_root before it loaded the cylinder group structures for a writable mount: sb->s_root = d_make_root(inode); ... if (!sb_rdonly(sb)) if (!ufs_read_cylinder_structures(sb)) goto failed; When ufs_read_cylinder_structures() failed, the error path freed the in-core superblock information and set sb->s_fs_info to NULL while sb->s_root stayed installed. get_tree_bdev() then reached deactivate_locked_super(), and because s_root was present, generic_shutdown_super() called sync_filesystem() and the put_super operation. Both dereference UFS_SB(sb), which is now NULL, so a mount that fails only while reading the cylinder groups oopses during teardown. A crafted image whose first cylinder group cannot be read reaches this path. Load the cylinder group metadata first and create the root dentry last, so the superblock is published to the VFS only once it is fully set up. ufs_setup_cstotal() and ufs_read_cylinder_structures() take only the super_block and do not use the root inode, so the reordering is safe. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260801071306.59484-2-ali@iusegentoo.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ufs/super.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fs/ufs/super.c b/fs/ufs/super.c index 6dcf6d048cce..3569ac92b065 100644 --- a/fs/ufs/super.c +++ b/fs/ufs/super.c @@ -1199,6 +1199,15 @@ static int ufs_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_maxbytes = ufs_max_bytes(sb); sb->s_max_links = UFS_LINK_MAX; + ufs_setup_cstotal(sb); + /* + * Read cylinder group structures + */ + if (!sb_rdonly(sb)) + if (!ufs_read_cylinder_structures(sb)) + goto failed; + + /* create the root dentry last, once UFS_SB(sb) is fully set up */ inode = ufs_iget(sb, UFS_ROOTINO); if (IS_ERR(inode)) { ret = PTR_ERR(inode); @@ -1210,14 +1219,6 @@ static int ufs_fill_super(struct super_block *sb, struct fs_context *fc) goto failed; } - ufs_setup_cstotal(sb); - /* - * Read cylinder group structures - */ - if (!sb_rdonly(sb)) - if (!ufs_read_cylinder_structures(sb)) - goto failed; - UFSD("EXIT\n"); return 0; From c9d263be26806d388129fab8c6904bed197fc6af Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Sat, 1 Aug 2026 10:12:58 +0300 Subject: [PATCH 0018/1317] ufs: validate cylinder group metadata before caching it ufs_read_cylinder() copies the cylinder group index and the rotor positions straight from the on-disk group and caches them without any check: ucpi->c_cgx = fs32_to_cpu(sb, ucg->cg_cgx); ucpi->c_rotor = fs32_to_cpu(sb, ucg->cg_rotor); ucpi->c_frotor = fs32_to_cpu(sb, ucg->cg_frotor); ucpi->c_irotor = fs32_to_cpu(sb, ucg->cg_irotor); They are then used as indices during allocation and free: - c_cgx indexes the cylinder summary array as UFS_SB(sb)->fs_cs(ucpi->c_cgx), so a value past s_ncg writes a 32 bit count outside the s_csp allocation. - c_frotor becomes a bitmap scan start, start = c_frotor >> 3, and then length = ((s_fpg + 7) >> 3) - start. A start beyond the block bitmap wraps the unsigned length to a huge value, so ubh_scanc() walks far past the cylinder group buffers. c_irotor drives the inode bitmap the same way. A crafted image can set any of these freely, turning an ordinary allocation into an out of bounds access. Reject a cylinder group whose recorded index does not match the group being read, or whose rotors fall outside the group, before the metadata is cached. Valid filesystems keep cg_cgx equal to the group number and the rotors within the group, so only malformed images are rejected. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260801071306.59484-3-ali@iusegentoo.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ufs/cylinder.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/ufs/cylinder.c b/fs/ufs/cylinder.c index a2813270c303..b930ee1cf853 100644 --- a/fs/ufs/cylinder.c +++ b/fs/ufs/cylinder.c @@ -68,6 +68,16 @@ static bool ufs_read_cylinder(struct super_block *sb, ucpi->c_clustersumoff = fs32_to_cpu(sb, ucg->cg_u.cg_44.cg_clustersumoff); ucpi->c_clusteroff = fs32_to_cpu(sb, ucg->cg_u.cg_44.cg_clusteroff); ucpi->c_nclusterblks = fs32_to_cpu(sb, ucg->cg_u.cg_44.cg_nclusterblks); + + /* these on-disk values become array and bitmap indices */ + if (ucpi->c_cgx != cgno || + ucpi->c_rotor >= uspi->s_fpg || + ucpi->c_frotor >= uspi->s_fpg || + ucpi->c_irotor >= uspi->s_ipg) { + ufs_error(sb, __func__, + "inconsistent metadata in cylinder group %u\n", cgno); + goto failed; + } UFSD("EXIT\n"); return true; From 08edfb34ee9ca54383970c65ed3a6013e84f5e16 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Sat, 1 Aug 2026 04:39:32 +0300 Subject: [PATCH 0019/1317] ufs: do not treat unreadable directory blocks as empty ufs_empty_dir() scans every directory block to decide whether a directory is empty before rmdir() removes it. When ufs_get_folio() cannot read or validate a block it returns an error pointer, and the loop currently skips that block with continue and keeps scanning the remaining blocks. If none of the readable blocks hold an entry, the function returns 1 and the caller unlinks the directory. A directory whose contents live in a block that cannot be read, for example because of an I/O error or corrupted directory metadata, is therefore seen as empty and removed, losing the entries it still holds. Follow the ext2 behaviour and treat an unreadable block as a reason to consider the directory not empty, so rmdir() fails instead of discarding data that could not be verified. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260801013942.279992-1-ali@iusegentoo.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ufs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ufs/dir.c b/fs/ufs/dir.c index e62fe5667671..ce43cf20b07c 100644 --- a/fs/ufs/dir.c +++ b/fs/ufs/dir.c @@ -590,7 +590,7 @@ int ufs_empty_dir(struct inode * inode) kaddr = ufs_get_folio(inode, i, &folio); if (IS_ERR(kaddr)) - continue; + return 0; de = (struct ufs_dir_entry *)kaddr; kaddr += ufs_last_byte(inode, i) - UFS_DIR_REC_LEN(1); From fe967191e5851ea79818c5fe4e781c3882139218 Mon Sep 17 00:00:00 2001 From: Moritz Tanner Date: Fri, 21 Aug 2026 10:54:51 +0200 Subject: [PATCH 0020/1317] fs: don't return -EINVAL for successful nested thaw Commit 7366f8b6fc6a ("fs: handle freezing from multiple devices") replaced the freeze_holders bitmask with per-holder counters to allow nested freezes. In the bitmask version, a thaw that released a shared hold while another holder remained returned 0. Since the rework, thaw_super_locked() drops the freeze reference via freeze_dec() but then returns -EINVAL when other freezers remain, misinforming the caller: the thaw did succeed, the superblock just stays frozen for the remaining holders. This breaks bdev-initiated freezing. When a filesystem is frozen with FIFREEZE and additionally frozen via bdev_freeze() -- which nests by design, see fs_bdev_freeze() -- the subsequent bdev_thaw() receives -EINVAL from the holder op although its freeze reference was dropped, and therefore keeps bd_fsfreeze_count elevated. Then device-mapper's unlock_fs() ignores bdev_thaw()'s return value, so nothing rebalances the count. After the user's FITHAW and umount, the block device can never be mounted again: dm-1: Can't mount, blockdev is frozen There is no way for userspace to drop the leaked count; only destroying the block device (or a reboot) recovers the device. Reproducer (any kernel since v6.8): dmsetup create dut --table "0 $(blockdev --getsz "$DEV") linear $DEV 0" mkfs.ext4 /dev/mapper/dut mount /dev/mapper/dut /mnt fsfreeze --freeze /mnt # freeze_ucount == 1 dmsetup suspend dut # bd_fsfreeze_count == 1, ucount == 2 dmsetup resume dut # ucount 2 -> 1, but thaw_super() # returns -EINVAL, so bdev_thaw() # keeps bd_fsfreeze_count at 1 fsfreeze --unfreeze /mnt # filesystem thaws fine umount /mnt mount /dev/mapper/dut /mnt # EBUSY, forever The same happens with fsfreeze held across an LVM snapshot of the origin volume. fs_bdev_thaw()'s documentation already describes the intended semantics: "If this function returns zero it doesn't mean that the filesystem is unfrozen as it may have been frozen multiple times". Restore them by returning 0 when a nested thaw drops its hold while other freezers remain. Thawing without holding a freeze still fails with -EINVAL as may_unfreeze() rejects that case before the reference count is touched. Fixes: 7366f8b6fc6a ("fs: handle freezing from multiple devices") Cc: stable@vger.kernel.org # needs adjustments for < 6.17 (no may_unfreeze()) Signed-off-by: Moritz Tanner Link: https://patch.msgid.link/20260821085451.65206-1-moritz.tanner@linbit.com Tested-by: Lars Ellenberg Reviewed-by: Lars Ellenberg Signed-off-by: Christian Brauner (Amutable) --- fs/super.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/super.c b/fs/super.c index 05e443173038..01db6124e409 100644 --- a/fs/super.c +++ b/fs/super.c @@ -2369,11 +2369,14 @@ static int thaw_super_locked(struct super_block *sb, enum freeze_holder who, goto out_unlock; /* - * All freezers share a single active reference. - * So just unlock in case there are any left. + * All freezers share a single active reference. If other freezers + * remain, drop our hold and report success; the superblock stays + * frozen until the last holder thaws it. */ - if (freeze_dec(sb, who)) + if (freeze_dec(sb, who)) { + error = 0; goto out_unlock; + } if (sb_rdonly(sb)) { sb->s_writers.frozen = SB_UNFROZEN; From 21efadc62272cabee9bec27777ae75d84a9ca8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Tue, 18 Aug 2026 18:00:02 +0200 Subject: [PATCH 0021/1317] Input: adp5588-keys - cache GPIO state before registering the gpiochip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So as not to clobber any pre-programmed GPIO state in the execution of its gpiochip ops, the driver caches things during probe time. However, since those ops can be called both during and immediately after the call to devm_gpiochip_add_data(), it is imperative that things are cached before that. That's not the case right now, so reorder the two steps to prevent any clobbering. In the concrete example which motivated this change, a bootloader was preconfiguring an important GPIO output to HIGH before booting the kernel. Linux would then inadvertently set that output to LOW while configuring a GPIO hog on a discrete GPIO line within the same 8-bit bank (because the cached value was 0=LOW). Fixes: ba9f507a1bea ("Input: adp5588-keys - export unused GPIO pins") Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260818-adp5588-gpio-cache-v1-1-650a2674fc0d@analog.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/adp5588-keys.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c index 40371f5bd9ba..4f0ddff5baba 100644 --- a/drivers/input/keyboard/adp5588-keys.c +++ b/drivers/input/keyboard/adp5588-keys.c @@ -446,12 +446,6 @@ static int adp5588_gpio_add(struct adp5588_kpad *kpad) mutex_init(&kpad->gpio_lock); - error = devm_gpiochip_add_data(dev, &kpad->gc, kpad); - if (error) { - dev_err(dev, "gpiochip_add failed: %d\n", error); - return error; - } - for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) { kpad->dat_out[i] = adp5588_read(kpad->client, GPIO_DAT_OUT1 + i); @@ -459,6 +453,12 @@ static int adp5588_gpio_add(struct adp5588_kpad *kpad) kpad->pull_dis[i] = adp5588_read(kpad->client, GPIO_PULL1 + i); } + error = devm_gpiochip_add_data(dev, &kpad->gc, kpad); + if (error) { + dev_err(dev, "gpiochip_add failed: %d\n", error); + return error; + } + return 0; } From 4d7fa28e151a6a6f24098d9e60f558f8ea61ddaa Mon Sep 17 00:00:00 2001 From: Roman Vivchar Date: Fri, 14 Aug 2026 16:13:52 +0300 Subject: [PATCH 0022/1317] dt-bindings: input: mediatek,mt6779-keypad: add mt6572 Add a compatible string for the mt6572 keypad, that is compatible with mt6779. Signed-off-by: Roman Vivchar Reviewed-by: Mattijs Korpershoek Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260814-6572-dt-keypad-v1-1-2ceff2373211@protonmail.com Signed-off-by: Dmitry Torokhov --- .../devicetree/bindings/input/mediatek,mt6779-keypad.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml b/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml index 914dd3283df3..20fa41f691ed 100644 --- a/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml +++ b/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml @@ -26,6 +26,7 @@ properties: - const: mediatek,mt6779-keypad - items: - enum: + - mediatek,mt6572-keypad - mediatek,mt6873-keypad - mediatek,mt8183-keypad - mediatek,mt8365-keypad From 16b10f64c63f78220c3b4035f1ed6cd3bdcb0b02 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Thu, 13 Aug 2026 20:36:50 +0300 Subject: [PATCH 0023/1317] gpiolib: Put fwnode reference on failure We get a reference to the fwnode handle which we pass to gpio_shared_make_ref. In case it fails we do not put the reference. Fix by putting the reference in the failure case Fixes: 49416483a953 ("gpio: shared: allow sharing a reset-gpios pin between reset-gpio and gpiolib") Cc: stable@vger.kernel.org Signed-off-by: Michail Tatas Link: https://patch.msgid.link/an4Asr4tx3D2QvLD@michalis-linux Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index 495bd3d0ddf0..5f9623e40b0f 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -261,10 +261,13 @@ static int gpio_shared_of_traverse(struct device_node *curr) con_id[con_id_len - suffix_len] = '\0'; } - ref = gpio_shared_make_ref(fwnode_handle_get(of_fwnode_handle(curr)), - con_id, args.args[1]); - if (!ref) + struct fwnode_handle *curr_fwnode = + fwnode_handle_get(of_fwnode_handle(curr)); + ref = gpio_shared_make_ref(curr_fwnode, con_id, args.args[1]); + if (!ref) { + fwnode_handle_put(curr_fwnode); return -ENOMEM; + } if (!list_empty(&entry->refs)) pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n", From aefbda23eeba234c3ff0f135dc5be6e294bd25a6 Mon Sep 17 00:00:00 2001 From: Alexei Turtanov <9alexei9@gmail.com> Date: Fri, 28 Aug 2026 14:22:39 +0300 Subject: [PATCH 0024/1317] Input: atkbd - skip deactivate for Xiaomi Redmi Book Pro 16 2026 The internal keyboard of the Xiaomi Redmi Book Pro 16 2026 (board TM2425) does not work: atkbd_probe() succeeds and every command is ACKed, but no scancodes ever arrive afterwards. Testing on the hardware through serio_raw shows that ATKBD_CMD_RESET_DIS (0xF5) is the culprit. After 0xF5 the embedded controller keeps ACKing commands but stops delivering scancodes, and neither ATKBD_CMD_ENABLE (0xF4) nor ATKBD_CMD_RESET_BAT (0xFF) bring them back. Only re-enabling the keyboard interface at the controller level (i8042 command 0xAE, or rewriting the command byte as i8042_port_close() does) revives it. Running the init sequence without 0xF5 (0xED 0x00, 0xF3 0x00, 0xF4) keeps the keyboard working. 'i8042.dumbkbd=1' also works around this, but then the driver never writes to the keyboard and the LEDs cannot be controlled. Use the existing atkbd_deactivate_fixup quirk instead, as done for the sibling TM2424 by commit 3a046db33bb9 ("Input: atkbd - skip deactivate for Xiaomi Book Pro 14's internal keyboard"). Tested on v7.2: keyboard, Caps Lock LED and s2idle suspend/resume all work. DMI: XIAOMI REDMI Book Pro 16 2026/TM2425, BIOS RMAPT6B0P0909 05/22/2026 Fixes: 9cf6e24c9fbf ("Input: atkbd - do not skip atkbd_deactivate() when skipping ATKBD_CMD_GETID") Cc: stable@vger.kernel.org Signed-off-by: Alexei Turtanov <9alexei9@gmail.com> Link: https://patch.msgid.link/20260828112239.18081-1-9alexei9@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/atkbd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 5736f4bc5a50..2627434099ff 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -1946,6 +1946,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { }, .callback = atkbd_deactivate_fixup, }, + { + /* Xiaomi Redmi Book Pro 16 2026 (TM2425) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"), + DMI_MATCH(DMI_PRODUCT_NAME, "REDMI Book Pro 16 2026"), + }, + .callback = atkbd_deactivate_fixup, + }, { } }; From aab55360fa11a2c054798a484ac67ad606f563e4 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Thu, 27 Aug 2026 22:42:31 -0600 Subject: [PATCH 0025/1317] watchdog: sunxi_wdt: preserve boot-enabled watchdog sunxi_wdt_probe() unconditionally stops the watchdog even when firmware left it running. This opens an unprotected interval during boot and prevents CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED from taking over the active watchdog. Detect an enabled watchdog and decode its programmed interval. Preserve representable timeouts, and round the 0.5-second interval up to the minimum representable one-second timeout. Use the configured timeout for reserved interval encodings. Set the Linux reset mode and ping the watchdog without clearing its enable bit, then mark it hardware-running before registration so the watchdog core services it until userspace takes control. Leave disabled watchdogs untouched. Fixes: d00680ed0026 ("watchdog: sunxi: New watchdog driver for Allwinner A10/A13") Cc: stable@vger.kernel.org Signed-off-by: James Hilliard Link: https://patch.msgid.link/20260827-submit-sunxi-wdt-boot-enabled-v1-v2-1-610d37dccc97@gmail.com Signed-off-by: Guenter Roeck --- drivers/watchdog/sunxi_wdt.c | 45 +++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c index b6c761acc3de..3db34524ed13 100644 --- a/drivers/watchdog/sunxi_wdt.c +++ b/drivers/watchdog/sunxi_wdt.c @@ -128,6 +128,38 @@ static int sunxi_wdt_ping(struct watchdog_device *wdt_dev) return 0; } +static bool sunxi_wdt_is_running(struct watchdog_device *wdt_dev) +{ + struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); + const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; + + return readl(sunxi_wdt->wdt_base + regs->wdt_mode) & WDT_MODE_EN; +} + +static unsigned int sunxi_wdt_get_timeout(struct watchdog_device *wdt_dev) +{ + struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); + const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; + unsigned int timeout; + u32 interval; + + interval = readl(sunxi_wdt->wdt_base + regs->wdt_mode); + interval >>= regs->wdt_timeout_shift; + interval &= WDT_TIMEOUT_MASK; + /* Round the 0.5-second interval up to the minimum representable timeout. */ + if (!interval) + return WDT_MIN_TIMEOUT; + + for (timeout = WDT_MIN_TIMEOUT; + timeout < ARRAY_SIZE(wdt_timeout_map); timeout++) { + if (wdt_timeout_map[timeout] == interval) + return timeout; + } + + /* Reserved interval encoding. */ + return 0; +} + static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev, unsigned int timeout) { @@ -259,6 +291,7 @@ static int sunxi_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct sunxi_wdt_dev *sunxi_wdt; + unsigned int running_timeout; int err; sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL); @@ -286,7 +319,17 @@ static int sunxi_wdt_probe(struct platform_device *pdev) watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt); - sunxi_wdt_stop(&sunxi_wdt->wdt_dev); + if (sunxi_wdt_is_running(&sunxi_wdt->wdt_dev)) { + running_timeout = sunxi_wdt_get_timeout(&sunxi_wdt->wdt_dev); + if (running_timeout) + sunxi_wdt->wdt_dev.timeout = running_timeout; + + err = sunxi_wdt_start(&sunxi_wdt->wdt_dev); + if (err) + return err; + + set_bit(WDOG_HW_RUNNING, &sunxi_wdt->wdt_dev.status); + } watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev); err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev); From e3eceb76515910746e6268c4e4ac1c07516ebd7b Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Thu, 27 Aug 2026 04:46:59 +0000 Subject: [PATCH 0026/1317] watchdog: msc313e: Fix NULL pointer dereference in PM callbacks msc313e_wdt_probe() doesn't set the driver data for the platform device. As a result, dev_get_drvdata() in msc313e_wdt_suspend() and msc313e_wdt_resume() will return NULL, leading to a NULL pointer dereference afterward. Set the platform device driver data in msc313e_wdt_probe(). Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260827044700.554333-2-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index d962589e2c55..f69d66971c41 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -124,6 +124,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) set_bit(WDOG_HW_RUNNING, &priv->wdev.status); watchdog_set_drvdata(&priv->wdev, priv); + platform_set_drvdata(pdev, priv); watchdog_init_timeout(&priv->wdev, timeout, dev); watchdog_stop_on_reboot(&priv->wdev); From 51cfe54f815ae175c7d1126b983d4d7c89715004 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 4 Aug 2026 22:08:54 -0700 Subject: [PATCH 0027/1317] Input: rmi_smbus - fix out-of-bounds read in rmi_smb_write_block() When chunking writes into SMBus blocks in rmi_smb_write_block(), the loop calculates block_len using the original total length (len) instead of the remaining length (cur_len). If len is greater than 32 bytes (SMB_MAX_COUNT), block_len remains 32 for every iteration, even on the final partial chunk where fewer than 32 bytes remain. This causes smb_block_write() to read 32 bytes from the advanced data buffer pointer, reading past the end of the input buffer. Fix this by calculating block_len using cur_len and advancing the buffer and address pointers by block_len. Fixes: 82264d0cf7ae ("Input: synaptics-rmi4 - add SMBus support") Cc: stable@vger.kernel.org Reported-by: sashiko-bot@kernel.org Assisted-by: LLM Link: https://patch.msgid.link/anLFSMKSoKyyZ272@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_smbus.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c index 3160714a514a..8b61c2382297 100644 --- a/drivers/input/rmi4/rmi_smbus.c +++ b/drivers/input/rmi4/rmi_smbus.c @@ -140,7 +140,7 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, u8 commandcode; struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); - int cur_len = (int)len; + size_t cur_len = len; mutex_lock(&rmi_smb->page_mutex); @@ -148,7 +148,7 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, /* * break into 32 bytes chunks to write get command code */ - int block_len = min_t(int, len, SMB_MAX_COUNT); + int block_len = min_t(size_t, cur_len, SMB_MAX_COUNT); retval = rmi_smb_get_command_code(xport, rmiaddr, block_len, false, &commandcode); @@ -161,9 +161,9 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, goto exit; /* prepare to write next block of bytes */ - cur_len -= SMB_MAX_COUNT; - databuff += SMB_MAX_COUNT; - rmiaddr += SMB_MAX_COUNT; + cur_len -= block_len; + databuff += block_len; + rmiaddr += block_len; } exit: mutex_unlock(&rmi_smb->page_mutex); From fe10579b6dc3f0dac61e51e1797cacbba5039ac2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 5 Aug 2026 22:44:23 -0700 Subject: [PATCH 0028/1317] Input: synaptics-rmi4 - fix GPF in suspend and resume when unbound Transport drivers (such as rmi_i2c and rmi_spi) invoke rmi_driver_suspend() and rmi_driver_resume() on their child rmi_dev device during system power management events. However, transport drivers are fully registered and operational even if the physical RMI driver failed to bind or probe the rmi_dev device. When rmi_driver_suspend() or rmi_driver_resume() is called on an unbound rmi_dev, dev_get_drvdata() returns NULL. Calling rmi_disable_irq() or rmi_enable_irq() without driver data attached causes a NULL pointer dereference and General Protection Fault when attempting to lock data->enabled_mutex. Fix this by checking if driver data is attached to rmi_dev in rmi_driver_suspend() and rmi_driver_resume(), exiting early if no driver data is present. Fixes: 2b6a321da9a2 ("Input: synaptics-rmi4 - add support for Synaptics RMI4 devices") Reported-by: syzbot+09103639e39c989e3ed3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=09103639e39c989e3ed3 Cc: stable@vger.kernel.org Assisted-by: LLM Link: https://patch.msgid.link/anQe8UiyUR4x0flD@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_driver.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index 5d49a9021c7d..a349dfd17519 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -991,6 +991,15 @@ int rmi_driver_suspend(struct rmi_device *rmi_dev, bool enable_wake) { int retval; + /* + * Transport driver will try to suspend RMI device even if physical + * driver did not bind to the RMI device, because transport device + * (I2C, SPI) is fully registered and operational. Exit early if + * there is no driver data attached to the RMI device. + */ + if (!dev_get_drvdata(&rmi_dev->dev)) + return 0; + retval = rmi_suspend_functions(rmi_dev); if (retval) dev_warn(&rmi_dev->dev, "Failed to suspend functions: %d\n", @@ -1005,6 +1014,10 @@ int rmi_driver_resume(struct rmi_device *rmi_dev, bool clear_wake) { int retval; + /* Skip if not fully bound to RMI driver */ + if (!dev_get_drvdata(&rmi_dev->dev)) + return 0; + rmi_enable_irq(rmi_dev, clear_wake); retval = rmi_resume_functions(rmi_dev); From 173cb3147ba26449e6023df56f9bcd34ce6d1c3b Mon Sep 17 00:00:00 2001 From: Antoine Monnet Date: Sat, 15 Aug 2026 14:50:00 +0300 Subject: [PATCH 0029/1317] ASoC: tas2783-sdw: split a stereo stream across the two mono amps A board with two TAS2783 aggregated on one link renders mono: the two amplifiers are mono parts that each carry one channel of the stereo stream, but snd_sdw_params_to_config() hands every codec the full channel mask for playback. The pair stays in mirror mode, both amps render the same channel, and the other channel is never reproduced. Claim a single channel per amplifier instead. The index comes from the machine-assigned component name prefix rather than the SoundWire unique_id, which is board-specific: soc_sdw_ti_amp.c names the amplifiers tas2783-1..4. The bit that is set does not choose the side. sdw_compute_slave_ports() advances the payload offset by hweight32(ch_mask) and never looks at which bit it is, so a one-channel mask fixes mono by defeating mirror mode, and left and right then follow the amplifier's position in the codec order of the DAI link. That was measured: inverting the two masks between the amplifiers does not move the audio. On the boards this has been run on the codec order matches the prefix numbering, so the sides come out as the machine driver names them, but the mapping is not an ABI promise the bus allocator could honour. Reported-by: Robin Everaars Closes: https://lore.kernel.org/all/20260805183517.8665-1-robineveraars@pm.me/ Suggested-by: Robin Everaars Signed-off-by: Antoine Monnet Tested-by: Andrey Golovko Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260815113000.4488-1-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index eaebb0ebbe0d..7fcf8214bf34 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -1001,6 +1001,31 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + + /* + * The two mono amps each render one channel of the stereo stream: + * snd_sdw_params_to_config() hands every codec the full mask for + * playback, which leaves the pair in mirror mode and one channel + * unreproduced. Claim a single channel instead, keyed off the + * machine-assigned component prefix rather than the SoundWire + * address, which is board-specific: soc_sdw_ti_amp.c names the amps + * tas2783-1..4. + * + * Which side an amp then renders does not follow from the bit that + * is set - sdw_compute_slave_ports() advances the payload offset by + * the popcount of ch_mask and never looks at which bit it is - but + * from the amp's position in the codec order of the DAI link, which + * on these boards matches the prefix numbering. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + params_channels(params) == 2 && component->name_prefix) { + const char *idx_str = strrchr(component->name_prefix, '-'); + unsigned long idx; + + if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) + port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); + } + /* port 1 for playback */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port_config.num = 1; From 72bd92bd8190d7869ecb462649ca40f297822a33 Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Tue, 25 Aug 2026 17:48:02 -0400 Subject: [PATCH 0030/1317] x86/amd_node: Avoid divide by zero on virtualized systems On a virtualized system, the number of nodes does not have a relationship to the number of roots. A Xen PVH dom0 can calculate roots_per_node as 0, which crashes with a divide by zero in: if (count++ % roots_per_node) because the underlying topology code on Xen ends up making num_nodes 2 and num_roots 1 and the integer division result is 0. The issue is seen with Xen, but it could affect other systems. Set roots_per_node to 1 in this case. Print a firmware bug when this is performed for non-virtualized systems. [ bp: Massage commit message. ] Fixes: 0a4b61d9c2e4 ("x86/amd_node: Fix AMD root device caching") Suggested-by: Borislav Petkov Signed-off-by: Jason Andryuk Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Yazen Ghannam Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260825214805.39148-2-jason.andryuk@amd.com --- arch/x86/kernel/amd_node.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c index 0be01725a2a4..408b9fd48349 100644 --- a/arch/x86/kernel/amd_node.c +++ b/arch/x86/kernel/amd_node.c @@ -287,6 +287,11 @@ static int __init amd_smn_init(void) return -ENOMEM; roots_per_node = num_roots / num_nodes; + if (!roots_per_node) { + if (!cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) + pr_warn(FW_BUG "Error detecting roots per node.\n"); + roots_per_node = 1; + } count = 0; node = 0; From ae9464c65e9d1ad4df4fed516cee9bca3bc614cc Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 25 Aug 2026 09:23:45 +0300 Subject: [PATCH 0031/1317] perf symbol: Do not use debug file as the binary type dso__load() sets the binary type of a DSO to the type of the first symbol source found. For a DSO with a separate debug file linked via .gnu-debuglink, that is DSO_BINARY_TYPE__DEBUGLINK, which makes dso__get_filename() return the name of the debug file instead of the file that was actually executed. Consumers that need to read instruction bytes, such as Intel PT decoding in 'perf script', then read from the debug file and produce wrong instructions. Prefer DSO_BINARY_TYPE__BUILD_ID_CACHE, and otherwise DSO_BINARY_TYPE__SYSTEM_PATH_DSO, over debug-only types, which restores the behaviour of using a file that contains the executed instructions. This is a workaround. Properly separating the binary file used for instructions from the file used for debug symbols is left for later. Example: Create a shared object with a separate .gnu_debuglink debug file. Note that 'objcopy --only-keep-debug' leaves .text as NOBITS, so instructions read from the debug file are zeros: # cat > foo.c << EOF unsigned long foo_work(unsigned long n) { unsigned long s = 0; for (unsigned long i = 0; i < n; i++) s = s * 31 + i; return s; } EOF # cat > main.c << EOF #include unsigned long foo_work(unsigned long n); int main(void) { printf("%lu\n", foo_work(1000)); return 0; } EOF # gcc -g -O2 -shared -fPIC -o libfoo.so foo.c # gcc -g -O2 -o main main.c -L. -lfoo -Wl,-rpath,'$ORIGIN' # objcopy --only-keep-debug libfoo.so libfoo.so.debug # objcopy --strip-debug libfoo.so # objcopy --add-gnu-debuglink=libfoo.so.debug libfoo.so # perf record -e intel_pt//u ./main Note that branch samples must be requested, because it is the resolving of the branch target symbol that causes dso__load() to be called, and hence the binary type to be set, before the decoder walks the code. With '--itrace=e' alone, nothing loads symbols for libfoo.so, the binary type is left as DSO_BINARY_TYPE__NOT_FOUND, the correct file is read anyway, and no errors are reported either way. Before: # perf.before script --itrace=be 2>&1 | grep "instruction trace error" instruction trace error type 1 time 2350.467489498 cpu 9 pid 75634 tid 75634 ip 0x77d48480718f code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467489832 cpu 9 pid 75634 tid 75634 ip 0x77d484807341 code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467496412 cpu 9 pid 75634 tid 75634 ip 0x5b4de37a8074 code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467593393 cpu 9 pid 75634 tid 75634 ip 0x77d4848070d0 code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467593954 cpu 9 pid 75634 tid 75634 ip 0x77d4848075a8 code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467595728 cpu 9 pid 75634 tid 75634 ip 0x77d4848324de code 6: Trace doesn't match instruction 6 instruction trace errors After: # perf script --itrace=be 2>&1 | grep "instruction trace error" # Fixes: 5363c306787c8 ("perf symbol: Set binary_type of dso when loading") Reported-by: Todd Lipcon Closes: https://lore.kernel.org/all/CAGH6UiG=RJLqBU3kLu9XJciPyPO1HZkbAPERguVUMRuWQgqf=A@mail.gmail.com/ Signed-off-by: Adrian Hunter Signed-off-by: Namhyung Kim --- tools/perf/util/symbol.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 94f9c8faedda..3587ad243159 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -1947,7 +1947,16 @@ int dso__load(struct dso *dso, struct map *map) if (next_slot) { ss_pos++; - if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND) + /* + * The binary type is used to find the file containing + * the executed instructions, so prefer the types that + * refer to the actual object over debug-only files such + * as DSO_BINARY_TYPE__DEBUGLINK. + */ + if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND || + symtab_type == DSO_BINARY_TYPE__BUILD_ID_CACHE || + (symtab_type == DSO_BINARY_TYPE__SYSTEM_PATH_DSO && + dso__binary_type(dso) != DSO_BINARY_TYPE__BUILD_ID_CACHE)) dso__set_binary_type(dso, symtab_type); if (syms_ss && runtime_ss) From e11811a552252740bd396ec38378e9570ee16578 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 26 Aug 2026 14:19:57 +0100 Subject: [PATCH 0032/1317] OPP: of: Fix potential multiplication overflow when calculating freq The multiplication be32_to_cpup(val++) * 1000 is performed using 32 bit unsigned integers and hence uses a 32 bit multiplication; this will overflow if be32_to_cpup(val++) is greater than 4294967 (which is very unlikely at present). The result is assigned to an unsigned long (which is a 64 bit value on 64 bit systems), so fix this potential overflow by casting the first operand of the multiplication to an unsigned int. Fixes: b496dfbc94ab ("PM / OPP: Initialize OPP table from device tree") Signed-off-by: Colin Ian King Signed-off-by: Viresh Kumar --- drivers/opp/of.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/opp/of.c b/drivers/opp/of.c index c02e20632fa6..9c4fd1f0e944 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -1039,7 +1039,7 @@ static int _of_add_opp_table_v1(struct device *dev, struct opp_table *opp_table) val = prop->value; while (nr) { - unsigned long freq = be32_to_cpup(val++) * 1000; + unsigned long freq = (unsigned long)be32_to_cpup(val++) * 1000; unsigned long volt = be32_to_cpup(val++); struct dev_pm_opp_data data = { .freq = freq, From a5096d4927d1eb607d51a7342a7e7591a3838c19 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sat, 29 Aug 2026 19:19:24 +0530 Subject: [PATCH 0033/1317] opp: Use %pe to print symbolic error name Replace PTR_ERR() and %ld with %pe and pass the original pointer directly to dev_dbg(), dev_warn(), dev_err() or pr_err(). The %pe format specifier prints a symbolic error name (e.g. -ENOMEM) when CONFIG_SYMBOLIC_ERRNAME is enabled, otherwise it falls back gracefully and prints the raw integer value. This makes error messages more readable without any functional change. Signed-off-by: Sumeet Pawnikar Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 24 ++++++++++++------------ drivers/opp/of.c | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index cd0e82dae776..1e3b80a1f88e 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -453,8 +453,8 @@ int dev_pm_opp_get_opp_count(struct device *dev) _find_opp_table(dev); if (IS_ERR(opp_table)) { - dev_dbg(dev, "%s: OPP table not found (%ld)\n", - __func__, PTR_ERR(opp_table)); + dev_dbg(dev, "%s: OPP table not found (%pe)\n", + __func__, opp_table); return PTR_ERR(opp_table); } @@ -611,8 +611,8 @@ _find_key(struct device *dev, unsigned long *key, int index, bool available, _find_opp_table(dev); if (IS_ERR(opp_table)) { - dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, - PTR_ERR(opp_table)); + dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, + opp_table); return ERR_CAST(opp_table); } @@ -722,8 +722,8 @@ struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev, struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev); if (IS_ERR(opp_table)) { - dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, - PTR_ERR(opp_table)); + dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, + opp_table); return ERR_CAST(opp_table); } @@ -1036,8 +1036,8 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg, /* Regulator not available for device */ if (IS_ERR(reg)) { - dev_dbg(dev, "%s: regulator not available: %ld\n", __func__, - PTR_ERR(reg)); + dev_dbg(dev, "%s: regulator not available: %pe\n", __func__, + reg); return 0; } @@ -1448,8 +1448,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) temp_freq = freq; opp = _find_freq_ceil(opp_table, &temp_freq); if (IS_ERR(opp)) { - dev_err(dev, "%s: failed to find OPP for freq %lu (%ld)\n", - __func__, freq, PTR_ERR(opp)); + dev_err(dev, "%s: failed to find OPP for freq %lu (%pe)\n", + __func__, freq, opp); return PTR_ERR(opp); } @@ -2869,8 +2869,8 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; if (IS_ERR(opp_table)) { - dev_warn(dev, "%s: Device OPP not found (%ld)\n", __func__, - PTR_ERR(opp_table)); + dev_warn(dev, "%s: Device OPP not found (%pe)\n", __func__, + opp_table); return PTR_ERR(opp_table); } diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 9c4fd1f0e944..2f3bbde9a9e1 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -1345,8 +1345,8 @@ int of_get_required_opp_performance_state(struct device_node *np, int index) _find_table_of_opp_np(required_np); if (IS_ERR(opp_table)) { - pr_err("%s: Failed to find required OPP table %pOF: %ld\n", - __func__, np, PTR_ERR(opp_table)); + pr_err("%s: Failed to find required OPP table %pOF: %pe\n", + __func__, np, opp_table); return PTR_ERR(opp_table); } From aadea57f532882d8bab444646863c7ef8a778ff1 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Tue, 25 Aug 2026 17:47:33 +0800 Subject: [PATCH 0034/1317] perf powerpc-vpadtl: Fix raw_size of DTL samples In powerpc_vpadtl_sample(), raw_data of the synthetic sample points to a struct powerpc_vpadtl_entry (48 bytes), but raw_size is set to sizeof(record). record is a struct powerpc_vpadtl_entry pointer, so sizeof(record) is the size of the pointer (8 bytes on 64-bit) rather than the size of the record itself. As a result, consumers that bound their access to raw_data by raw_size only see or copy the first 8 bytes of each DTL entry instead of the full record. Use sizeof(*record) so that raw_size reflects the actual length of the raw data. Fixes: 8644834a482a ("perf powerpc: Process the DTL entries in queue and deliver samples") Signed-off-by: Wang Yan Reviewed-by: Athira Rajeev Reviewed-by: Arnaldo Carvalho de Melo Signed-off-by: Namhyung Kim --- tools/perf/util/powerpc-vpadtl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/util/powerpc-vpadtl.c b/tools/perf/util/powerpc-vpadtl.c index 710f3093f3f9..af6783cfdb53 100644 --- a/tools/perf/util/powerpc-vpadtl.c +++ b/tools/perf/util/powerpc-vpadtl.c @@ -196,7 +196,7 @@ static int powerpc_vpadtl_sample(struct powerpc_vpadtl_entry *record, sample.cpumode = PERF_RECORD_MISC_KERNEL; sample.time = save; sample.raw_data = record; - sample.raw_size = sizeof(record); + sample.raw_size = sizeof(*record); event.sample.header.type = PERF_RECORD_SAMPLE; event.sample.header.misc = sample.cpumode; event.sample.header.size = sizeof(struct perf_event_header); From a1530ef451f53b63dcf4a2805a5524b590a146ad Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 18 Aug 2026 10:09:33 +0800 Subject: [PATCH 0035/1317] media: rppx1: describe the MAIN_POST white balance gains block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rppx1_ext_params_blocks_info[] is indexed by block type and is built with designated initialisers, so a block type without an entry is left as a zero-sized hole. RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST is the only such hole: a block reporting that type and a size of zero passes v4l2_isp_params_validate_buffer(), which then has nothing to advance the walk with. Describe the block, so the array covers all block types the uAPI defines. The MAIN_POST white balance gains module is probed and started with the rest of the POST pipe, and RPPX1_PARAMS_MAX_SIZE already reserves room for all three white balance gains blocks. The block is not dispatched by rppx1_params(), in line with the other described blocks that have no users yet. Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Signed-off-by: Linmao Li Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/platform/dreamchip/rppx1/rpp_params.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index a75a27a8afd0..5e6727d58946 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -25,6 +25,7 @@ rppx1_ext_params_blocks_info[] = { RPPX1_PARAMS_BLOCK_INFO(LSC_PRE2, lsc), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg), + RPPX1_PARAMS_BLOCK_INFO(AWBG_POST, awbg), RPPX1_PARAMS_BLOCK_INFO(CCOR_POST, ccor), RPPX1_PARAMS_BLOCK_INFO(HIST_PRE1, hist), RPPX1_PARAMS_BLOCK_INFO(HIST_PRE2, hist), From e04ffff543db06308a8100f6aa3a65aebcd9834c Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 18 Aug 2026 17:59:29 +0800 Subject: [PATCH 0036/1317] media: rppx1: bls: read the raw pattern from the PRE2 acquisition module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rppx1_bls_swap_regs() gets the Bayer pattern from the acquisition module. The PRE1 path uses pre1.acq, but the PRE2 path mistakenly uses pre2.bls. The BLS module does not store a Bayer pattern, so PRE2 would read zero (RGGB) and map fixed black levels to the wrong colour registers. PRE2 is not started or dispatched yet, so the bug is currently latent. Read the pattern from pre2.acq, as the PRE1 path does, so that enabling PRE2 does not start out with the wrong register mapping. Signed-off-by: Linmao Li Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/platform/dreamchip/rppx1/rppx1_bls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c b/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c index 01a61db279bf..71c5561457d5 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c @@ -70,7 +70,7 @@ rppx1_bls_swap_regs(struct rpp_module *mod, const u32 input[4], u32 output[4]) /* Swap to pattern used in our path, PRE1 or PRE2. */ struct rpp_module *acq = mod == &mod->rpp->pre1.bls ? - &mod->rpp->pre1.acq : &mod->rpp->pre2.bls; + &mod->rpp->pre1.acq : &mod->rpp->pre2.acq; enum rpp_raw_pattern pattern = acq->info.acq.raw_pattern; for (unsigned int i = 0; i < 4; ++i) From b12e20c6ac156a307acdf0545432eb3b6cb41f8f Mon Sep 17 00:00:00 2001 From: Ahmet Eray Karadag Date: Mon, 15 Dec 2025 06:14:34 +0300 Subject: [PATCH 0037/1317] adfs: fix memory leak in sb->s_fs_info Syzbot reported a memory leak in adfs during the mount process. The issue arises because the ownership of the allocated (struct adfs_sb_info) is transferred from the filesystem context to the superblock via sget_fc(). This function sets fc->s_fs_info to NULL after the transfer. The ADFS filesystem previously used the default kill_block_super for superblock destruction. This helper performs generic cleanup but does not free the private sb->s_fs_info data. Since fc->s_fs_info is set to NULL during the transfer, the standard context cleanup (adfs_free_fc) also skips freeing this memory. As a result, if the superblock is destroyed, the allocated struct adfs_sb_info is leaked. Fix this by implementing a custom .kill_sb callback (adfs_kill_sb) that explicitly frees sb->s_fs_info before invoking the generic kill_block_super. Reported-by: syzbot+1c70732df5fd4f0e4fbb@syzkaller.appspotmail.com Signed-off-by: Ahmet Eray Karadag Link: https://patch.msgid.link/20251215031433.182205-2-eraykrdg1@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/adfs/super.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index a4cd0a5159dd..888aa81a6b39 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -92,10 +92,7 @@ static int adfs_checkdiscrecord(struct adfs_discrecord *dr) static void adfs_put_super(struct super_block *sb) { - struct adfs_sb_info *asb = ADFS_SB(sb); - adfs_free_map(sb); - kfree_rcu(asb, rcu); } static int adfs_show_options(struct seq_file *seq, struct dentry *root) @@ -365,7 +362,7 @@ static int adfs_fill_super(struct super_block *sb, struct fs_context *fc) ret = -EINVAL; } if (ret) - goto error; + return ret; /* set up enough so that we can read an inode */ sb->s_op = &adfs_sops; @@ -406,15 +403,9 @@ static int adfs_fill_super(struct super_block *sb, struct fs_context *fc) if (!sb->s_root) { adfs_free_map(sb); adfs_error(sb, "get root inode failed\n"); - ret = -EIO; - goto error; + return -EIO; } return 0; - -error: - sb->s_fs_info = NULL; - kfree(asb); - return ret; } static int adfs_get_tree(struct fs_context *fc) @@ -465,10 +456,19 @@ static int adfs_init_fs_context(struct fs_context *fc) return 0; } +static void adfs_kill_sb(struct super_block *sb) +{ + struct adfs_sb_info *asb = ADFS_SB(sb); + + kill_block_super(sb); + + kfree_rcu(asb, rcu); +} + static struct file_system_type adfs_fs_type = { .owner = THIS_MODULE, .name = "adfs", - .kill_sb = kill_block_super, + .kill_sb = adfs_kill_sb, .fs_flags = FS_REQUIRES_DEV, .init_fs_context = adfs_init_fs_context, .parameters = adfs_param_spec, From f18e8774f4d3137fa0a5fb8ffa83d59a719666d8 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 27 Aug 2026 14:42:54 +0100 Subject: [PATCH 0038/1317] netfs: Fix uninitialized return value in netfs_unbuffered_write() If preparation of the first subrequest fails, netfs_unbuffered_write() exits its loop before ret is initialized. The empty-iterator check can do the same. For synchronous writes, netfs_unbuffered_write_iter_locked() may then return an unrelated error instead of wreq->error. This is reachable through CIFS if cifs_prepare_write() fails to reopen the file or obtain credits. Initialize ret to 0 so the caller returns wreq->error if no data was written, or the number of bytes already written otherwise. Found with Clang's -Wconditional-uninitialized. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Cc: stable@vger.kernel.org Signed-off-by: Karl Mehltretter Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-2-dhowells@redhat.com Acked-by: Paulo Alcantara Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index c16fbad286a1..b04019097ab8 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -95,7 +95,7 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq) { struct netfs_io_subrequest *subreq = NULL; struct netfs_io_stream *stream = &wreq->io_streams[0]; - int ret; + int ret = 0; _enter("%llx", wreq->len); From c753a33664e4e86246f7491a93d9a77c1a673b5d Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:42:55 +0100 Subject: [PATCH 0039/1317] netfs: Fix unbuffered/DIO write partial transfer error return Fix unbuffered/DIO write to return the amount of data transferred in preference to an error if a partial transfer has been achieved, and to prefer an error stashed in the request over the one returned by netfs_unbuffered_write() (likely -EINTR or -ERESTARTSYS). Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-3-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index b04019097ab8..544a4243fc59 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -139,13 +139,11 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq) if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { retry = true; } else if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) { - ret = subreq->error; - wreq->error = ret; + wreq->error = subreq->error; netfs_see_subrequest(subreq, netfs_sreq_trace_see_failed); subreq = NULL; break; } - ret = 0; if (!retry) { netfs_unbuffered_write_collect(wreq, stream, subreq); @@ -288,11 +286,11 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter * ret = -EIOCBQUEUED; } else { ret = netfs_unbuffered_write(wreq); - if (ret < 0) { - _debug("begin = %zd", ret); - } else { + if (wreq->transferred) { iocb->ki_pos += wreq->transferred; - ret = wreq->transferred ?: wreq->error; + ret = wreq->transferred; + } else if (wreq->error) { + ret = wreq->error; } netfs_put_request(wreq, netfs_rreq_trace_put_complete); From 0bfe2571a6af653611860d0e24c4e4c83bae7a54 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:42:56 +0100 Subject: [PATCH 0040/1317] netfs: Fix error vs transferred passed to ->ki_complete() Fix netfs_unbuffered_write_done() to pass the amount written to ->ki_complete() rather than the error in the event of a partially complete transfer. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-4-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index 544a4243fc59..f7d7e1b54653 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -51,7 +51,7 @@ static void netfs_unbuffered_write_done(struct netfs_io_request *wreq) wreq->iocb->ki_pos += written; if (wreq->iocb->ki_complete) { trace_netfs_rreq(wreq, netfs_rreq_trace_ki_complete); - wreq->iocb->ki_complete(wreq->iocb, wreq->error ?: written); + wreq->iocb->ki_complete(wreq->iocb, written ?: wreq->error); } wreq->iocb = VFS_PTR_POISON; } From 741416a8003b77e636dafade408f808d96ac3f47 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:42:57 +0100 Subject: [PATCH 0041/1317] netfs: Fix i_size update for partial transfer Fix netfs_unbuffered_write_done() to pass the amount written to netfs_update_i_size() in the event of a partial transfer that ends in an error. That said, it might be better for the filesystem to mark the inode data as invalid and recheck it in case something like a network error occurred that prevented the reply from the server from being received. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-5-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index f7d7e1b54653..f33ccddaa826 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -21,7 +21,7 @@ static void netfs_unbuffered_write_done(struct netfs_io_request *wreq) /* Okay, declare that all I/O is complete. */ trace_netfs_rreq(wreq, netfs_rreq_trace_write_done); - if (!wreq->error) + if (wreq->transferred) netfs_update_i_size(ictx, &ictx->inode, wreq->start, wreq->transferred); if (wreq->origin == NETFS_DIO_WRITE && From 3c30087e27598d9d359763e8be9bd3017fe08348 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:42:58 +0100 Subject: [PATCH 0042/1317] netfs: Fix subreq ref leak Fix a subrequest ref leak in netfs_unbuffered_write() in the event that subreq->io_iter ends up zero length during preparation. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-6-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index f33ccddaa826..fbcfadb232ee 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -121,8 +121,14 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq) } iov_iter_truncate(&subreq->io_iter, wreq->len - wreq->transferred); - if (!iov_iter_count(&subreq->io_iter)) + if (!iov_iter_count(&subreq->io_iter)) { + pr_warn("netfs: Unexpected zero-length iterator R=%08x\n", + wreq->debug_id); + __set_bit(NETFS_SREQ_FAILED, &subreq->flags); + netfs_write_subrequest_terminated(subreq, -EIO); + wreq->error = -EIO; break; + } subreq->len = netfs_limit_iter(&subreq->io_iter, 0, stream->sreq_max_len, From 8fb45a934661419c04a44d4cfea1e0df7dcf2805 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Thu, 27 Aug 2026 14:42:59 +0100 Subject: [PATCH 0043/1317] netfs: break unbuffered write when netfs_alloc_subrequest() fails syzbot reported a null-ptr-deref below [1] following a fault injection in netfs_alloc_subrequest(). [0] When netfs_alloc_subrequest() fails, subreq is NULL. Later, netfs_prepare_write() tries to initialize members of subreq(e.g., source), the issue in [1] is triggered. Let's handle the error of netfs_prepare_write() properly. [0] FAULT_INJECTION: forcing a failure. name failslab, interval 1, probability 0, space 0, times 0 Call Trace: netfs_alloc_subrequest+0x116/0x3f0 netfs_prepare_write+0x76/0x7b0 netfs_unbuffered_write+0x75c/0x2020 netfs_unbuffered_write_iter_locked+0x7d6/0xa80 netfs_unbuffered_write_iter+0x442/0x720 v9fs_file_write_iter+0xbf/0x100 vfs_write+0x6ac/0x1050 [1] KASAN: null-ptr-deref in range [0x00000000000000a8-0x00000000000000af] RIP: 0010:netfs_prepare_write+0xbc/0x7b0 fs/netfs/write_issue.c:173 Call Trace: netfs_unbuffered_write+0x75c/0x2020 fs/netfs/direct_write.c:111 netfs_unbuffered_write_iter_locked+0x7d6/0xa80 fs/netfs/direct_write.c:290 netfs_unbuffered_write_iter+0x442/0x720 fs/netfs/direct_write.c:382 v9fs_file_write_iter+0xbf/0x100 fs/9p/vfs_file.c:409 new_sync_write fs/read_write.c:595 [inline] Fixes: 288ace2f57c9 ("netfs: New writeback implementation") Reported-by: syzbot+6a13fc77eb6f0802be2d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6a13fc77eb6f0802be2d Tested-by: syzbot+6a13fc77eb6f0802be2d@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-7-dhowells@redhat.com Acked-by: Paulo Alcantara Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 5 +++++ fs/netfs/write_issue.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index fbcfadb232ee..2361277416c7 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -110,6 +110,11 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq) if (!subreq) { netfs_prepare_write(wreq, stream, wreq->start + wreq->transferred); subreq = stream->construct; + if (!subreq) { + wreq->error = -ENOMEM; + ret = -ENOMEM; + break; + } stream->construct = NULL; } diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 2d9cfcd43658..851f6f93ad45 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -170,6 +170,8 @@ void netfs_prepare_write(struct netfs_io_request *wreq, rolling_buffer_make_space(&wreq->buffer, wreq->gfp); subreq = netfs_alloc_subrequest(wreq); + if (!subreq) + return; subreq->source = stream->source; subreq->start = start; subreq->stream_nr = stream->stream_nr; From fed0b33e6c584986ba70018ec9f9787a98216e64 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:43:00 +0100 Subject: [PATCH 0044/1317] netfs: Fix readahead synchronisation issues by loading all folios upfront There are some synchronisation issues that derive from the app thread adding more folios to the rolling buffer whilst the collector thread is looking at them or trying to clear them, such as determining the setting of front_folio_order when the next folio hasn't been added yet, The reason for the rolling buffer approach is that loading the buffer upfront and then dropping all the refs just acquired is quite a slow operation, and loading progressively allows some of the cost to be deferred until after at least some of the I/O is started. Instead, a better way is to load all the folios into the rolling buffer upfront - and then drop the refs later, once the I/O is in progress. (Even better would be for the refs not to be there at all.) Fix this by changing the rolling buffer loader to load all the folios selected by the VM for readahead upfront into the folio queue. The folio queue is allocated a batch worth at a time as we don't know how many folios are involved (the readahead_control struct, alas, has a page count, not a folio count). The folio refs acquired from readahead are then dropped in bulk once the first subrequest is dispatched as it's quite a slow operation. The collector waits for NETFS_RREQ_NEED_PUT_RA_REFS to be cleared so that it doesn't unlock folios before the xarray has been scanned for them. This simplifies the buffer handling later and isn't noticeably slower as the xarray doesn't need to be modified and the folios are all already pre-locked. Fixes: ee4cdf7ba857 ("netfs: Speed up buffered reading") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-8-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: netfs@lists.linux.dev cc: linux-mm@kvack.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 101 ++++++++++++++++++++------------- fs/netfs/internal.h | 1 + fs/netfs/misc.c | 19 +++++++ fs/netfs/read_collect.c | 7 +++ fs/netfs/read_retry.c | 7 +++ fs/netfs/rolling_buffer.c | 79 +++++++++++++++++--------- include/linux/netfs.h | 1 + include/linux/rolling_buffer.h | 6 +- include/trace/events/netfs.h | 3 + 9 files changed, 153 insertions(+), 71 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 7fdfa4f27e34..303fdce54fba 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -54,6 +54,42 @@ static void netfs_rreq_expand(struct netfs_io_request *rreq, } } +/* + * Drop the folio refs acquired from the readahead API. + */ +static void netfs_bulk_drop_ra_refs(struct netfs_io_request *rreq) +{ + struct folio_batch fbatch; + struct folio *folio; + pgoff_t nr_pages = DIV_ROUND_UP(rreq->len, PAGE_SIZE); + pgoff_t first = rreq->start / PAGE_SIZE; + XA_STATE(xas, &rreq->mapping->i_pages, first); + + folio_batch_init(&fbatch); + + rcu_read_lock(); + + xas_for_each(&xas, folio, first + nr_pages - 1) { + if (xas_retry(&xas, folio)) + continue; + + if (!folio_batch_add(&fbatch, folio)) + folio_batch_release(&fbatch); + } + + rcu_read_unlock(); + folio_batch_release(&fbatch); + trace_netfs_rreq(rreq, netfs_rreq_trace_ra_put_ref); + clear_bit_unlock(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags); + wake_up(&rreq->waitq); +} + +static void netfs_maybe_bulk_drop_ra_refs(struct netfs_io_request *rreq) +{ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_bulk_drop_ra_refs(rreq); +} + /* * Begin an operation, and fetch the stored zero point value from the cookie if * available. @@ -74,12 +110,8 @@ static int netfs_begin_cache_read(struct netfs_io_request *rreq, struct netfs_in * * Returns the limited size if successful and -ENOMEM if insufficient memory * available. - * - * [!] NOTE: This must be run in the same thread as ->issue_read() was called - * in as we access the readahead_control struct. */ -static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq, - struct readahead_control *ractl) +static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq = subreq->rreq; size_t rsize = subreq->len; @@ -87,30 +119,6 @@ static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq, if (subreq->source == NETFS_DOWNLOAD_FROM_SERVER) rsize = umin(rsize, rreq->io_streams[0].sreq_max_len); - if (ractl) { - /* If we don't have sufficient folios in the rolling buffer, - * extract a folioq's worth from the readahead region at a time - * into the buffer. Note that this acquires a ref on each page - * that we will need to release later - but we don't want to do - * that until after we've started the I/O. - */ - struct folio_batch put_batch; - - folio_batch_init(&put_batch); - while (rreq->submitted < subreq->start + rsize) { - ssize_t added; - - added = rolling_buffer_load_from_ra(&rreq->buffer, ractl, - &put_batch); - if (added < 0) { - folio_batch_release(&put_batch); - return added; - } - rreq->submitted += added; - } - folio_batch_release(&put_batch); - } - subreq->len = rsize; if (unlikely(rreq->io_streams[0].sreq_max_segs)) { size_t limit = netfs_limit_iter(&rreq->buffer.iter, 0, rsize, @@ -208,8 +216,7 @@ static void netfs_issue_read(struct netfs_io_request *rreq, * slicing up the region to be read according to available cache blocks and * network rsize. */ -static void netfs_read_to_pagecache(struct netfs_io_request *rreq, - struct readahead_control *ractl) +static void netfs_read_to_pagecache(struct netfs_io_request *rreq) { unsigned long long start = rreq->start; ssize_t size = rreq->len; @@ -288,7 +295,7 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq, break; issue: - slice = netfs_prepare_read_iterator(subreq, ractl); + slice = netfs_prepare_read_iterator(subreq); if (slice < 0) { ret = slice; netfs_cancel_read(subreq, ret); @@ -302,6 +309,7 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq, } netfs_issue_read(rreq, subreq); + netfs_maybe_bulk_drop_ra_refs(rreq); if (test_bit(NETFS_RREQ_PAUSE, &rreq->flags)) netfs_wait_for_paused_read(rreq); @@ -339,7 +347,8 @@ void netfs_readahead(struct readahead_control *ractl) { struct netfs_io_request *rreq; struct netfs_inode *ictx = netfs_inode(ractl->mapping->host); - unsigned long long start = readahead_pos(ractl); + ssize_t added; + uoff_t start = readahead_pos(ractl); size_t size = readahead_length(ractl); int ret; @@ -360,11 +369,23 @@ void netfs_readahead(struct readahead_control *ractl) netfs_rreq_expand(rreq, ractl); - rreq->submitted = rreq->start; - if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->gfp) < 0) + /* Load the folios to be read into a bvecq chain. Note that this + * acquires a ref on each folio that we will need to release later - + * but we don't want to do that until after we've started the I/O. + */ + added = rolling_buffer_bulk_load_from_ra(&rreq->buffer, ractl, + rreq->debug_id, rreq->gfp); + if (added < 0) { + ret = added; goto cleanup_free; - netfs_read_to_pagecache(rreq, ractl); + } + __set_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags); + rreq->submitted = rreq->start + added; + rreq->cleaned_to = rreq->start; + + netfs_read_to_pagecache(rreq); + netfs_maybe_bulk_drop_ra_refs(rreq); return netfs_put_request(rreq, netfs_rreq_trace_put_return); cleanup_free: @@ -457,7 +478,7 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) iov_iter_bvec(&rreq->buffer.iter, ITER_DEST, bvec, i, rreq->len); rreq->submitted = rreq->start + flen; - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret = netfs_wait_for_read(rreq); if (ret >= 0) { @@ -532,7 +553,7 @@ int netfs_read_folio(struct file *file, struct folio *folio) if (ret < 0) goto discard; - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret = netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; @@ -689,7 +710,7 @@ int netfs_write_begin(struct netfs_inode *ctx, if (ret < 0) goto error_put; - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret = netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); if (ret < 0) @@ -754,7 +775,7 @@ int netfs_prefetch_for_write(struct file *file, struct folio *folio, if (ret < 0) goto error_put; - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret = netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 420ee7b26580..bd8b2d633f96 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -79,6 +79,7 @@ ssize_t netfs_wait_for_read(struct netfs_io_request *rreq); ssize_t netfs_wait_for_write(struct netfs_io_request *rreq); void netfs_wait_for_paused_read(struct netfs_io_request *rreq); void netfs_wait_for_paused_write(struct netfs_io_request *rreq); +void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq); /* * objects.c diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index 5d554512ed23..f5c1c463f4ff 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -563,3 +563,22 @@ void netfs_wait_for_paused_write(struct netfs_io_request *rreq) { return netfs_wait_for_pause(rreq, netfs_write_collection); } + +/* + * Wait for the readahead-acquired refs to be put. + */ +void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq) +{ + DEFINE_WAIT(myself); + + for (;;) { + trace_netfs_rreq(rreq, netfs_rreq_trace_wait_put_ra_refs); + prepare_to_wait(&rreq->waitq, &myself, TASK_UNINTERRUPTIBLE); + if (!test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + break; + schedule(); + } + + trace_netfs_rreq(rreq, netfs_rreq_trace_waited_put_ra_refs); + finish_wait(&rreq->waitq, &myself); +} diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 23660a590124..edf7cea7e2f9 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -118,6 +118,13 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, slot = 0; } + /* We have to wait for readahead refs to have been released before we + * can unlock any folios as the ref-dropper walks i_pages and the only + * thing preventing these folios from being removed is the folio lock. + */ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_wait_for_put_ra_refs(rreq); + for (;;) { struct folio *folio; unsigned long long fpos, fend; diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index 2b42758e01ec..dd463a485139 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -292,6 +292,13 @@ void netfs_unlock_abandoned_read_pages(struct netfs_io_request *rreq) { struct folio_queue *p; + /* We have to wait for readahead refs to have been released before we + * can unlock any folios as the ref-dropper walks i_pages and the only + * thing preventing these folios from being removed is the folio lock. + */ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_wait_for_put_ra_refs(rreq); + for (p = rreq->buffer.tail; p; p = p->next) { for (int slot = 0; slot < folioq_count(p); slot++) { struct folio *folio = folioq_folio(p, slot); diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c index 8c0026836f9c..424e77a9a109 100644 --- a/fs/netfs/rolling_buffer.c +++ b/fs/netfs/rolling_buffer.c @@ -115,42 +115,65 @@ int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp) } /* - * Decant the list of folios to read into a rolling buffer. + * Decant the entire list of folios to read into a rolling buffer. */ -ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, - struct readahead_control *ractl, - struct folio_batch *put_batch) +ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll, + struct readahead_control *ractl, + unsigned int rreq_id, gfp_t gfp) { struct folio_queue *fq; - struct page **vec; - int nr, ix, to; - ssize_t size = 0; + ssize_t loaded = 0; - if (rolling_buffer_make_space(roll, GFP_KERNEL) < 0) - return -ENOMEM; + while (ractl->_nr_pages - ractl->_batch_count > 0) { + unsigned int nr; - fq = roll->head; - vec = (struct page **)fq->vec.folios; - nr = __readahead_batch(ractl, vec + folio_batch_count(&fq->vec), - folio_batch_space(&fq->vec)); - ix = fq->vec.nr; - to = ix + nr; - fq->vec.nr = to; - for (; ix < to; ix++) { - struct folio *folio = folioq_folio(fq, ix); - unsigned int order = folio_order(folio); + /* Allocate a folioq to put some folios into and attach it to + * the rolling buffer. + */ + fq = netfs_folioq_alloc(rreq_id, gfp, + netfs_trace_folioq_make_space); + if (!fq) + goto nomem_unlock; + fq->prev = roll->head; + if (!roll->tail) + roll->tail = fq; + else + roll->head->next = fq; + roll->head = fq; - fq->orders[ix] = order; - size += PAGE_SIZE << order; - trace_netfs_folio(folio, netfs_folio_trace_read); - if (!folio_batch_add(put_batch, folio)) - folio_batch_release(put_batch); + /* Get a batch of folios and note their orders. */ + nr = __readahead_batch(ractl, (struct page **)fq->vec.folios, + folioq_nr_slots(fq)); + if (WARN_ON_ONCE(!nr)) + break; + fq->vec.nr = nr; + + for (int slot = 0; slot < nr; slot++) { + struct folio *folio = folioq_folio(fq, slot); + unsigned int order; + + order = folio_order(folio); + fq->orders[slot] = order; + loaded += PAGE_SIZE << order; + trace_netfs_folio(folio, netfs_folio_trace_read); + } } - WRITE_ONCE(roll->iter.count, roll->iter.count + size); - /* Store the counter after setting the slot. */ - smp_store_release(&roll->next_head_slot, to); - return size; + WRITE_ONCE(roll->iter.count, loaded); + iov_iter_folio_queue(&roll->iter, ITER_DEST, roll->tail, 0, 0, loaded); + return loaded; + +nomem_unlock: + for (fq = roll->tail; fq; fq = fq->next) { + for (int slot = 0; slot < folioq_count(fq); slot++) { + folio_unlock(fq->vec.folios[slot]); + folioq_mark(fq, slot); + } + } + rolling_buffer_clear(roll); + roll->head = NULL; + roll->tail = NULL; + return -ENOMEM; } /* diff --git a/include/linux/netfs.h b/include/linux/netfs.h index f837a501008c..5c538d0c5d79 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -278,6 +278,7 @@ struct netfs_io_request { #define NETFS_RREQ_FOLIO_COPY_TO_CACHE 10 /* Copy current folio to cache from read */ #define NETFS_RREQ_UPLOAD_TO_SERVER 11 /* Need to write to the server */ #define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages */ +#define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA gave us */ #define NETFS_RREQ_USE_PGPRIV2 31 /* [DEPRECATED] Use PG_private_2 to mark * write to cache on read */ const struct netfs_request_ops *netfs_ops; diff --git a/include/linux/rolling_buffer.h b/include/linux/rolling_buffer.h index 9e5dad29669c..a97f7cfaacaa 100644 --- a/include/linux/rolling_buffer.h +++ b/include/linux/rolling_buffer.h @@ -45,9 +45,9 @@ struct rolling_buffer_snapshot { int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, unsigned int direction, gfp_t gfp); int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp); -ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, - struct readahead_control *ractl, - struct folio_batch *put_batch); +ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll, + struct readahead_control *ractl, + unsigned int rreq_id, gfp_t gfp); ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio, unsigned int flags, gfp_t gfp); struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *roll); diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 082cb03c6131..9bda9302be90 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -59,6 +59,7 @@ EM(netfs_rreq_trace_free, "FREE ") \ EM(netfs_rreq_trace_intr, "INTR ") \ EM(netfs_rreq_trace_ki_complete, "KI-CMPL") \ + EM(netfs_rreq_trace_ra_put_ref, "RA-PUT ") \ EM(netfs_rreq_trace_recollect, "RECLLCT") \ EM(netfs_rreq_trace_redirty, "REDIRTY") \ EM(netfs_rreq_trace_resubmit, "RESUBMT") \ @@ -70,9 +71,11 @@ EM(netfs_rreq_trace_unpause, "UNPAUSE") \ EM(netfs_rreq_trace_wait_ip, "WAIT-IP") \ EM(netfs_rreq_trace_wait_pause, "--PAUSED--") \ + EM(netfs_rreq_trace_wait_put_ra_refs, "WAIT-P-RA") \ EM(netfs_rreq_trace_wait_quiesce, "WAIT-QUIESCE") \ EM(netfs_rreq_trace_waited_ip, "DONE-IP") \ EM(netfs_rreq_trace_waited_pause, "--UNPAUSED--") \ + EM(netfs_rreq_trace_waited_put_ra_refs, "DONE-P-RA") \ EM(netfs_rreq_trace_waited_quiesce, "DONE-QUIESCE") \ EM(netfs_rreq_trace_wake_ip, "WAKE-IP") \ EM(netfs_rreq_trace_wake_queue, "WAKE-Q ") \ From 533203c4183123dad8ffecd694e7573a0ccd0da0 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:43:01 +0100 Subject: [PATCH 0045/1317] netfs: Mark folios with COPY_TO_CACHE whilst issuing subreqs Mark folios with NETFS_FOLIO_COPY_TO_CACHE whilst issuing subreqs rather than when collecting them. This means that the collector thread doesn't have to try and keep track of which subreqs contribute to which folios - and thus which folios will need to be copied to the cache because at least one byte wasn't in the cache. Instead, this is marked on the folios up front and the collector need only consider the folios. For PG_private_2-using filesystems, PG_private_2 is set instead of NETFS_FOLIO_COPY_TO_CACHE, but otherwise it works the same. The NETFS_RREQ_COPY_TO_CACHE is replaced with NETFS_RREQ_CANCEL_CACHING, which is now set if caching fails somewhere, thereby causing the collection thread to cancel the copy-to-cache marks on the remaining folios. Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-9-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: netfs@lists.linux.dev cc: linux-mm@kvack.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 61 +++++++++++++++++++++++++++++++- fs/netfs/internal.h | 1 + fs/netfs/read_collect.c | 67 ++++++++++++++++++++++-------------- fs/netfs/read_pgpriv2.c | 15 ++++---- fs/netfs/read_retry.c | 6 +++- include/linux/netfs.h | 2 +- include/trace/events/netfs.h | 6 ++-- 7 files changed, 121 insertions(+), 37 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 303fdce54fba..16d4db776f6a 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -211,6 +211,56 @@ static void netfs_issue_read(struct netfs_io_request *rreq, } } +/* + * Mark folios that we want to copy to the cache. For filesystems that use + * netfslib fully, we set folio->private to NETFS_FOLIO_COPY_TO_CACHE; + * otherwise we set the deprecated PG_private_2. + */ +static void netfs_mark_copy_to_cache(struct netfs_io_request *rreq, + struct folio_queue **fq, + unsigned int *offset, + int *slot, + size_t len, + bool copy) +{ + while (len > 0) { + struct folio *folio; + size_t fsize, overlap; + + if (!*fq) + break; + if (*slot >= folioq_count(*fq)) { + *fq = (*fq)->next; + *slot = 0; + *offset = 0; + continue; + } + + /* Determine how much the subreq overlaps the folio, if at all. */ + fsize = folioq_folio_size(*fq, *slot); + overlap = min(len, fsize - *offset); + + if (overlap > 0 && copy) { + folio = folioq_folio(*fq, *slot); + if (unlikely(test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags))) { + if (!folio_test_private_2(folio)) + folio_start_private_2(folio); + } else { + if (!folio_get_private(folio)) + folio_attach_private(folio, NETFS_FOLIO_COPY_TO_CACHE); + } + trace_netfs_folio(folio, netfs_folio_trace_mark_copy); + } + + len -= overlap; + *offset += overlap; + if (*offset >= fsize) { + *slot += 1; + *offset = 0; + } + } +} + /* * Perform a read to the pagecache from a series of sources of different types, * slicing up the region to be read according to available cache blocks and @@ -218,9 +268,11 @@ static void netfs_issue_read(struct netfs_io_request *rreq, */ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) { + struct folio_queue *fq = rreq->buffer.tail; unsigned long long start = rreq->start; + unsigned int offset = 0; ssize_t size = rreq->len; - int ret = 0; + int ret = 0, slot = 0; do { struct netfs_io_subrequest *subreq; @@ -308,6 +360,13 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); } + if (fq) { + /* See if the cache indicated this should be cached. */ + bool copy = test_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags); + + netfs_mark_copy_to_cache(rreq, &fq, &slot, &offset, slice, copy); + } + netfs_issue_read(rreq, subreq); netfs_maybe_bulk_drop_ra_refs(rreq); diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index bd8b2d633f96..dfe7939f35f3 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -110,6 +110,7 @@ static inline void netfs_see_subrequest(struct netfs_io_subrequest *subreq, /* * read_collect.c */ +void netfs_cancel_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio); bool netfs_read_collection(struct netfs_io_request *rreq); void netfs_read_collection_worker(struct work_struct *work); void netfs_cancel_read(struct netfs_io_subrequest *subreq, int error); diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index edf7cea7e2f9..12a786be1ea2 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -19,7 +19,6 @@ #define MADE_PROGRESS 0x04 /* Made progress cleaning up a stream or the folio set */ #define BUFFERED 0x08 /* The pagecache needs cleaning up */ #define NEED_RETRY 0x10 /* A front op requests retrying */ -#define COPY_TO_CACHE 0x40 /* Need to copy subrequest to cache */ #define ABANDON_SREQ 0x80 /* Need to abandon untransferred part of subrequest */ /* @@ -34,6 +33,30 @@ static void netfs_clear_unread(struct netfs_io_subrequest *subreq) __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); } +/* + * Cancel the copy-to-cache mark on a folio. + */ +void netfs_cancel_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio) +{ + if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { + if (folio_get_private(folio) == NETFS_FOLIO_COPY_TO_CACHE) { + folio_detach_private(folio); + trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); + } else if (netfs_folio_group(folio) == NETFS_FOLIO_COPY_TO_CACHE) { + struct netfs_folio *finfo = netfs_folio_info(folio); + + finfo->netfs_group = NULL; + trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); + } + } else { + // TODO: Use of PG_private_2 is deprecated. + if (folio_test_private_2(folio)) { + folio_end_private_2(folio); + trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); + } + } +} + /* * Flush, mark and unlock a folio that's now completely read. If we want to * cache the folio, we set the group to NETFS_FOLIO_COPY_TO_CACHE, mark it @@ -48,37 +71,37 @@ static void netfs_unlock_read_folio(struct netfs_io_request *rreq, if (unlikely(folio_pos(folio) < rreq->abandon_to)) { trace_netfs_folio(folio, netfs_folio_trace_abandon); + netfs_cancel_copy_to_cache(rreq, folio); goto just_unlock; } flush_dcache_folio(folio); folio_mark_uptodate(folio); - if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { - finfo = netfs_folio_info(folio); - if (finfo) { - trace_netfs_folio(folio, netfs_folio_trace_filled_gaps); - if (finfo->netfs_group) - folio_change_private(folio, finfo->netfs_group); - else - folio_detach_private(folio); - kfree(finfo); - } + if (unlikely(test_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags))) + netfs_cancel_copy_to_cache(rreq, folio); - if (test_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags)) { - if (!WARN_ON_ONCE(folio_get_private(folio) != NULL)) { - trace_netfs_folio(folio, netfs_folio_trace_copy_to_cache); - folio_attach_private(folio, NETFS_FOLIO_COPY_TO_CACHE); - folio_mark_dirty(folio); - } + if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { + if (netfs_folio_group(folio) == NETFS_FOLIO_COPY_TO_CACHE) { + trace_netfs_folio(folio, netfs_folio_trace_sched_copy); + folio_mark_dirty(folio); } else { + finfo = netfs_folio_info(folio); + if (finfo) { + trace_netfs_folio(folio, netfs_folio_trace_filled_gaps); + if (finfo->netfs_group) + folio_change_private(folio, finfo->netfs_group); + else + folio_detach_private(folio); + kfree(finfo); + } trace_netfs_folio(folio, netfs_folio_trace_read_done); } folioq_clear(folioq, slot); } else { // TODO: Use of PG_private_2 is deprecated. - if (test_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags)) + if (folio_test_private_2(folio)) netfs_pgpriv2_copy_to_cache(rreq, folio); } @@ -131,9 +154,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, unsigned int order; size_t fsize; - if (*notes & COPY_TO_CACHE) - set_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); - folio = folioq_folio(folioq, slot); if (WARN_ONCE(!folio_test_locked(folio), "R=%08x: folio %lx is not locked\n", @@ -156,8 +176,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, WRITE_ONCE(rreq->cleaned_to, fpos + fsize); *notes |= MADE_PROGRESS; - clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); - /* Clean up the head folioq. If we clear an entire folioq, then * we can get rid of it provided it's not also the tail folioq * being filled by the issuer. @@ -255,9 +273,6 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq) stream->collected_to = front->start + transferred; rreq->collected_to = stream->collected_to; - if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front->flags)) - notes |= COPY_TO_CACHE; - if (test_bit(NETFS_SREQ_FAILED, &front->flags)) { rreq->abandon_to = front->start + front->len; front->transferred = front->len; diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index c31190993b76..a4b7bb88cbdb 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -54,8 +54,8 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio /* Attach the folio to the rolling buffer. */ if (rolling_buffer_append(&creq->buffer, folio, 0, creq->gfp) < 0) { + set_bit(NETFS_RREQ_CANCEL_CACHING, &creq->flags); folio_end_private_2(folio); - clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &creq->flags); return; } @@ -122,13 +122,14 @@ static struct netfs_io_request *netfs_pgpriv2_begin_copy_to_cache( netfs_put_failed_request(creq); cancel: rreq->copy_to_cache = ERR_PTR(-ENOBUFS); - clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); + set_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags); return ERR_PTR(-ENOBUFS); } /* * [DEPRECATED] Mark page as requiring copy-to-cache using PG_private_2 and add - * it to the copy write request. + * it to the copy write request. PG_private_2 should already be set on the + * folio. */ void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio) { @@ -136,11 +137,13 @@ void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct folio *fo if (!creq) creq = netfs_pgpriv2_begin_copy_to_cache(rreq, folio); - if (IS_ERR(creq)) + if (IS_ERR(creq)) { + set_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags); + netfs_cancel_copy_to_cache(rreq, folio); return; + } - trace_netfs_folio(folio, netfs_folio_trace_copy_to_cache); - folio_start_private_2(folio); + trace_netfs_folio(folio, netfs_folio_trace_pgpriv2_copy); netfs_pgpriv2_copy_folio(creq, folio); } diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index dd463a485139..4f6a36c6e214 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -303,7 +303,11 @@ void netfs_unlock_abandoned_read_pages(struct netfs_io_request *rreq) for (int slot = 0; slot < folioq_count(p); slot++) { struct folio *folio = folioq_folio(p, slot); - if (folio && !folioq_is_marked2(p, slot)) { + if (!folio) + continue; + netfs_cancel_copy_to_cache(rreq, folio); + + if (!folioq_is_marked2(p, slot)) { if (folio == rreq->no_unlock_folio && test_bit(NETFS_RREQ_NO_UNLOCK_FOLIO, &rreq->flags)) { diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 5c538d0c5d79..9881f4afdc0c 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -275,7 +275,7 @@ struct netfs_io_request { #define NETFS_RREQ_SHORT_TRANSFER 5 /* Set if we have a short transfer */ #define NETFS_RREQ_OFFLOAD_COLLECTION 8 /* Offload collection to workqueue */ #define NETFS_RREQ_NO_UNLOCK_FOLIO 9 /* Don't unlock no_unlock_folio on completion */ -#define NETFS_RREQ_FOLIO_COPY_TO_CACHE 10 /* Copy current folio to cache from read */ +#define NETFS_RREQ_CANCEL_CACHING 10 /* Set to cancel caching */ #define NETFS_RREQ_UPLOAD_TO_SERVER 11 /* Need to write to the server */ #define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages */ #define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA gave us */ diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 9bda9302be90..a22084813cb5 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -198,7 +198,6 @@ EM(netfs_folio_trace_clear_cc, "clear-cc") \ EM(netfs_folio_trace_clear_g, "clear-g") \ EM(netfs_folio_trace_clear_s, "clear-s") \ - EM(netfs_folio_trace_copy_to_cache, "mark-copy") \ EM(netfs_folio_trace_end_copy, "end-copy") \ EM(netfs_folio_trace_filled_gaps, "filled-gaps") \ EM(netfs_folio_trace_invalidate_all, "inval-all") \ @@ -209,16 +208,19 @@ EM(netfs_folio_trace_kill_cc, "kill-cc") \ EM(netfs_folio_trace_kill_g, "kill-g") \ EM(netfs_folio_trace_kill_s, "kill-s") \ + EM(netfs_folio_trace_mark_copy, "mark-copy") \ EM(netfs_folio_trace_mkwrite, "mkwrite") \ EM(netfs_folio_trace_mkwrite_plus, "mkwrite+") \ - EM(netfs_folio_trace_not_under_wback, "!wback") \ EM(netfs_folio_trace_not_locked, "!locked") \ + EM(netfs_folio_trace_not_under_wback, "!wback") \ + EM(netfs_folio_trace_pgpriv2_copy, "pgpriv2-copy") \ EM(netfs_folio_trace_put, "put") \ EM(netfs_folio_trace_read, "read") \ EM(netfs_folio_trace_read_done, "read-done") \ EM(netfs_folio_trace_read_gaps, "read-gaps") \ EM(netfs_folio_trace_read_unlock, "read-unlock") \ EM(netfs_folio_trace_redirtied, "redirtied") \ + EM(netfs_folio_trace_sched_copy, "sched-copy") \ EM(netfs_folio_trace_store, "store") \ EM(netfs_folio_trace_store_copy, "store-copy") \ EM(netfs_folio_trace_store_plus, "store+") \ From e00827a4d0cfebf8d78dfd0a9a024237f57c9273 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:43:02 +0100 Subject: [PATCH 0046/1317] netfs: Fix read progress reporting For really big read RPC ops that span multiple folios, netfslib allows the filesystem to give progress notifications to wake up the collector thread to do a collection of folios that have now been fetched, even if the RPC is still ongoing, thereby allowing the application to make progress. This works by taking the current rreq->cleaned_to value (which indicates which folios have been unlocked) and adding the stashed size of the next folio to it. cleaned_to, however, is subject to 64-bit tearing on a 32-bit arch. Fix this by stashing the next progress notification point as a size_t (which won't tear) to be added to rreq->start (which won't change), with the collector thread calculating that from cleaned_to plus the next folio size. Further, however, if the folios are small, the collector thread gets constantly woken up - which has a negative performance impact on the system. Fix that too by setting a minimum trigger of 256KiB or the size of the folio at the front of the queue, whichever is larger. Note that this has an issue that different subreqs have different need-to-be-cached properties; this is solved by a preceding patch that marks the property on the folios whilst issuing subreqs rather than when collecting them. Also, make sure rreq->cleaned_to is initialised up front, along with rreq->collected_to and stream->collected_to. Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use one work item") Link: https://sashiko.dev/#/patchset/20260804100224.2748935-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-10-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 2 ++ fs/netfs/internal.h | 1 + fs/netfs/objects.c | 32 +++++++++++++-------- fs/netfs/read_collect.c | 54 ++++++++++++++++++++++++++++-------- fs/netfs/read_single.c | 2 ++ include/linux/netfs.h | 2 +- include/trace/events/netfs.h | 21 ++++++++++++++ 7 files changed, 89 insertions(+), 25 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 16d4db776f6a..424df70a5c30 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -442,6 +442,7 @@ void netfs_readahead(struct readahead_control *ractl) rreq->submitted = rreq->start + added; rreq->cleaned_to = rreq->start; + netfs_read_set_unlock_at(rreq); netfs_read_to_pagecache(rreq); netfs_maybe_bulk_drop_ra_refs(rreq); @@ -467,6 +468,7 @@ static int netfs_create_singular_buffer(struct netfs_io_request *rreq, struct fo if (added < 0) return added; rreq->submitted = rreq->start + added; + rreq->progress_at = added; return 0; } diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index dfe7939f35f3..c79c8e69d60c 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -111,6 +111,7 @@ static inline void netfs_see_subrequest(struct netfs_io_subrequest *subreq, * read_collect.c */ void netfs_cancel_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio); +void netfs_read_set_unlock_at(struct netfs_io_request *rreq); bool netfs_read_collection(struct netfs_io_request *rreq); void netfs_read_collection_worker(struct work_struct *work); void netfs_cancel_read(struct netfs_io_subrequest *subreq, int error); diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 01461a74642d..7f6a3e912602 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -41,24 +41,32 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, memset(rreq, 0, kmem_cache_size(cache)); INIT_WORK(&rreq->cleanup_work, netfs_free_request); - rreq->gfp = gfp; - rreq->start = start; - rreq->len = len; - rreq->origin = origin; - rreq->netfs_ops = ctx->ops; - rreq->mapping = mapping; - rreq->inode = inode; - rreq->i_size = i_size_read(inode); - rreq->debug_id = atomic_inc_return(&debug_ids); - rreq->wsize = INT_MAX; + rreq->gfp = gfp; + rreq->start = start; + rreq->collected_to = start; + rreq->cleaned_to = start; + rreq->len = len; + rreq->progress_at = 0; + rreq->origin = origin; + rreq->netfs_ops = ctx->ops; + rreq->mapping = mapping; + rreq->inode = inode; + rreq->i_size = i_size_read(inode); + rreq->debug_id = atomic_inc_return(&debug_ids); + rreq->wsize = INT_MAX; rreq->io_streams[0].sreq_max_len = ULONG_MAX; rreq->io_streams[0].sreq_max_segs = 0; spin_lock_init(&rreq->lock); - INIT_LIST_HEAD(&rreq->io_streams[0].subrequests); - INIT_LIST_HEAD(&rreq->io_streams[1].subrequests); init_waitqueue_head(&rreq->waitq); refcount_set(&rreq->ref, 2); + for (int s = 0; s < NR_IO_STREAMS; s++) { + struct netfs_io_stream *stream = &rreq->io_streams[s]; + + INIT_LIST_HEAD(&stream->subrequests); + stream->collected_to = rreq->start; + } + if (origin == NETFS_READAHEAD || origin == NETFS_READPAGE || origin == NETFS_READ_GAPS || diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 12a786be1ea2..5cf22087d243 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -117,6 +117,35 @@ static void netfs_unlock_read_folio(struct netfs_io_request *rreq, folioq_clear(folioq, slot); } +/* + * Determine how much to gather before unlocking more folios. + */ +void netfs_read_set_unlock_at(struct netfs_io_request *rreq) +{ + struct folio_queue *folioq = rreq->buffer.tail; + unsigned int slot = rreq->buffer.first_tail_slot; + size_t cleaned_to = rreq->cleaned_to - rreq->start; + size_t progress_at = cleaned_to; + size_t minimum = 256 * 1024; + + while (progress_at < rreq->len) { + if (slot >= folioq_count(folioq)) { + folioq = folioq->next; + if (!folioq) + break; + slot = 0; + } + + progress_at += folioq_folio_size(folioq, slot); + if (progress_at - cleaned_to >= minimum) + break; + slot++; + } + + WRITE_ONCE(rreq->progress_at, progress_at); + trace_netfs_read_progress_at(rreq); +} + /* * Unlock any folios we've finished with. */ @@ -135,7 +164,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, if (slot >= folioq_nr_slots(folioq)) { folioq = rolling_buffer_delete_spent(&rreq->buffer); if (!folioq) { - rreq->front_folio_order = 0; + WRITE_ONCE(rreq->progress_at, rreq->len); return; } slot = 0; @@ -151,7 +180,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, for (;;) { struct folio *folio; unsigned long long fpos, fend; - unsigned int order; size_t fsize; folio = folioq_folio(folioq, slot); @@ -160,9 +188,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, rreq->debug_id, folio->index)) trace_netfs_folio(folio, netfs_folio_trace_not_locked); - order = folioq_folio_order(folioq, slot); - rreq->front_folio_order = order; - fsize = PAGE_SIZE << order; + fsize = folioq_folio_size(folioq, slot); fpos = folio_pos(folio); fend = fpos + fsize; @@ -197,6 +223,8 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, rreq->buffer.tail = folioq; done: rreq->buffer.first_tail_slot = slot; + + netfs_read_set_unlock_at(rreq); } /* @@ -257,7 +285,7 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq) * subreqs. */ if (notes & BUFFERED) { - size_t fsize = PAGE_SIZE << rreq->front_folio_order; + uoff_t unlock_at = rreq->start + rreq->progress_at; /* Clear the tail of a short read. */ if (!(notes & HIT_PENDING) && @@ -279,7 +307,7 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq) transferred = front->len; trace_netfs_rreq(rreq, netfs_rreq_trace_set_abandon); } - if (front->start + transferred >= rreq->cleaned_to + fsize || + if (front->start + transferred >= unlock_at || test_bit(NETFS_SREQ_HIT_EOF, &front->flags)) netfs_read_unlock_folios(rreq, ¬es); } else { @@ -499,20 +527,22 @@ void netfs_read_collection_worker(struct work_struct *work) void netfs_read_subreq_progress(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq = subreq->rreq; - struct netfs_io_stream *stream = &rreq->io_streams[0]; - size_t fsize = PAGE_SIZE << rreq->front_folio_order; - - trace_netfs_sreq(subreq, netfs_sreq_trace_progress); + struct netfs_io_stream *stream = &rreq->io_streams[subreq->stream_nr]; + size_t progress_at = READ_ONCE(rreq->progress_at); + uoff_t update_at = rreq->start + progress_at; + uoff_t transferred_to = subreq->start + subreq->transferred; /* If we are at the head of the queue, wake up the collector, * getting a ref to it if we were the ones to do so. */ - if (subreq->start + subreq->transferred > rreq->cleaned_to + fsize && + if (progress_at < rreq->len && + transferred_to >= update_at && (rreq->origin == NETFS_READAHEAD || rreq->origin == NETFS_READPAGE || rreq->origin == NETFS_READ_FOR_WRITE) && list_is_first(&subreq->rreq_link, &stream->subrequests) ) { + trace_netfs_sreq(subreq, netfs_sreq_trace_progress); __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); netfs_wake_collector(rreq); } diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index 8833550d2eb6..de67ac41548d 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -170,6 +170,8 @@ ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_ite if (IS_ERR(rreq)) return PTR_ERR(rreq); + rreq->progress_at = rreq->len; + ret = netfs_single_begin_cache_read(rreq, ictx); if (ret == -ENOMEM || ret == -EINTR || ret == -ERESTARTSYS) goto cleanup_free; diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 9881f4afdc0c..b4dd32863dd4 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -246,6 +246,7 @@ struct netfs_io_request { unsigned long long submitted; /* Amount submitted for I/O so far */ unsigned long long len; /* Length of the request */ size_t transferred; /* Amount to be indicated as transferred */ + size_t progress_at; /* Report read progress when hit this much read */ long error; /* 0 or error that occurred */ unsigned long long i_size; /* Size of the file */ unsigned long long start; /* Start position */ @@ -262,7 +263,6 @@ struct netfs_io_request { atomic_t subreq_counter; /* Next subreq->debug_index */ unsigned int nr_group_rel; /* Number of refs to release on ->group */ spinlock_t lock; /* Lock for queuing subreqs */ - unsigned char front_folio_order; /* Order (size) of front folio */ enum netfs_io_origin origin; /* Origin of the request */ bool direct_bv_unpin; /* T if direct_bv[] must be unpinned */ refcount_t ref; diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index a22084813cb5..3fec3e8f91c8 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -791,6 +791,27 @@ TRACE_EVENT(netfs_folioq, __print_symbolic(__entry->trace, netfs_folioq_traces)) ); +TRACE_EVENT(netfs_read_progress_at, + TP_PROTO(const struct netfs_io_request *rreq), + + TP_ARGS(rreq), + + TP_STRUCT__entry( + __field(unsigned int, rreq) + __field(size_t, progress_at) + __field(size_t, cleaned_to) + ), + + TP_fast_assign( + __entry->rreq = rreq->debug_id; + __entry->cleaned_to = rreq->cleaned_to - rreq->start; + __entry->progress_at = rreq->progress_at; + ), + + TP_printk("R=%08x cln=%zx prg=%zx", + __entry->rreq, __entry->cleaned_to, __entry->progress_at) + ); + #undef EM #undef E_ #endif /* _TRACE_NETFS_H */ From a67632c8c2688d6e0091529bcefe54bc5ee80e9b Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:43:03 +0100 Subject: [PATCH 0047/1317] cachefiles: Fix potential UAF/KASAN warning Currently, trace_cachefiles_coherency() is being passed a pointer to a __be64 lain over the coherency data in struct cachefiles_xattr so that it can display the first 8 bytes. However, the data is of variable length and could even be 0 bytes. This could lead to a UAF or KASAN warning. Fix this by making sure the buffer has room for at least 8 bytes and that those 8 bytes are pre-cleared. Further, those bytes are not 8-byte aligned, so fix the tracepoint to extract the data as four 2-byte words (they are 2-byte aligned) and reassemble the __be64. The compiler will convert this into a single 8-byte load where the CPU supports it. Fixes: 229105e5cfd9 ("cachefiles: Add auxiliary data trace") Link: https://sashiko.dev/#/patchset/20260810144746.574036-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-11-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/cachefiles/xattr.c | 16 ++++++++-------- include/trace/events/cachefiles.h | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c index f8ae78b3f7b6..c70bf67e52b0 100644 --- a/fs/cachefiles/xattr.c +++ b/fs/cachefiles/xattr.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "internal.h" #define CACHEFILES_COOKIE_TYPE_DATA 1 @@ -50,7 +51,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object) _enter("%x,#%d", object->debug_id, len); - buf = kmalloc(sizeof(struct cachefiles_xattr) + len, GFP_KERNEL); + buf = kmalloc(sizeof(struct cachefiles_xattr) + max(len, sizeof(__be64)), GFP_KERNEL); if (!buf) return -ENOMEM; @@ -60,6 +61,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object) buf->content = object->content_info; if (test_bit(FSCACHE_COOKIE_LOCAL_WRITE, &object->cookie->flags)) buf->content = CACHEFILES_CONTENT_DIRTY; + put_unaligned_be64(0, (__be64 *)buf->data); if (len > 0) memcpy(buf->data, fscache_get_aux(object->cookie), len); @@ -77,8 +79,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object) trace_cachefiles_vfs_error(object, file_inode(file), ret, cachefiles_trace_setxattr_error); trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, + buf->data, buf->content, cachefiles_coherency_set_fail); if (ret != -ENOMEM) cachefiles_io_error_obj( @@ -86,8 +87,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object) "Failed to set xattr with error %d", ret); } else { trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, + buf->data, buf->content, cachefiles_coherency_set_ok); } @@ -110,9 +110,10 @@ int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file int ret = -ESTALE; tlen = sizeof(struct cachefiles_xattr) + len; - buf = kmalloc(tlen, GFP_KERNEL); + buf = kmalloc(sizeof(struct cachefiles_xattr) + max(len, sizeof(__be64)), GFP_KERNEL); if (!buf) return -ENOMEM; + put_unaligned_be64(0, (__be64 *)buf->data); xlen = cachefiles_inject_read_error(); if (xlen == 0) @@ -148,8 +149,7 @@ int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file out: trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, why); + buf->data, buf->content, why); kfree(buf); return ret; } diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h index 9259bc71049e..e3101410e8b2 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -372,7 +372,7 @@ TRACE_EVENT(cachefiles_rename, TRACE_EVENT(cachefiles_coherency, TP_PROTO(struct cachefiles_object *obj, ino_t ino, - u64 disk_aux, + const void *disk_aux, enum cachefiles_content content, enum cachefiles_coherency_trace why), @@ -389,12 +389,27 @@ TRACE_EVENT(cachefiles_coherency, ), TP_fast_assign( + union { + __be16 s[4]; + __be64 ll; + } x; + __entry->obj = obj->debug_id; __entry->why = why; __entry->content = content; __entry->ino = ino; __entry->aux = be64_to_cpup((__be64 *)obj->cookie->inline_aux); - __entry->disk_aux = disk_aux; + + /* cachefiles_xattr::data is 2-byte aligned but not 8-byte aligned. */ + if (disk_aux) { + x.s[0] = ((__be16 *)disk_aux)[0]; + x.s[1] = ((__be16 *)disk_aux)[1]; + x.s[2] = ((__be16 *)disk_aux)[2]; + x.s[3] = ((__be16 *)disk_aux)[3]; + __entry->disk_aux = be64_to_cpu(x.ll); + } else { + __entry->disk_aux = 0; + } ), TP_printk("o=%08x %s B=%llx c=%u aux=%llx dsk=%llx", From 5a88f78df753993469dab4d1831f8fb4256a9468 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Fri, 14 Aug 2026 00:09:44 -0400 Subject: [PATCH 0048/1317] reboot: fix cad_pid use-after-free race cad_pid is a single kernel-wide struct pid pointer. proc_do_cad_pid() reads it and passes it to pid_vnr() without protecting the lifetime of the referenced struct pid. A concurrent writer can replace cad_pid and drop the final reference to the old struct pid after the reader has loaded the pointer but before pid_vnr() has finished dereferencing it, causing a use-after-free. kill_cad_pid() has the same lifetime race when it passes cad_pid to kill_pid(). At the time this issue was reported, an unprivileged user could reach the sysctl through user and PID namespaces because cad_pid was registered in pid_table[]. Moving cad_pid back to the global reboot sysctl table corrected that namespace and permission mismatch, but did not fix the underlying lifetime race. Fix this by treating cad_pid as an RCU-protected pointer at both read sites and by waiting for a grace period before dropping the old reference on the write side. call_rcu(&old_pid->rcu, ...) cannot be used here because free_pid() also queues pid->rcu; queueing the same rcu_head twice can corrupt the RCU callback list. Original KASAN crash stack: kernel/pid.c:545 pid_nr_ns() # reads freed pid->level kernel/pid.c:556 pid_vnr() # calls pid_nr_ns() kernel/pid.c:775 proc_do_cad_pid() # calls pid_vnr(cad_pid) Fixes: 9ec52099e4b8 ("[PATCH] replace cad_pid by a struct pid") Reported-by: AutonomousCodeSecurity@microsoft.com Closes: https://lore.kernel.org/all/20260717210143.4734-1-blbllhy@gmail.com/ Link: https://lore.kernel.org/all/alz5ZYLE4kaq_v2P@redhat.com/ Link: https://lore.kernel.org/all/al4ICz9biJKtdZc4@redhat.com/ Suggested-by: Mateusz Guzik Suggested-by: Bradley Morgan Suggested-by: Oleg Nesterov Suggested-by: Eric W. Biederman Suggested-by: Pavel Tikhomirov Cc: stable@vger.kernel.org Signed-off-by: Cen Zhang (Microsoft) Link: https://patch.msgid.link/20260814040944.16561-1-blbllhy@gmail.com Reviewed-by: Bradley Morgan Reviewed-by: Oleg Nesterov Reviewed-by: Pavel Tikhomirov Signed-off-by: Christian Brauner (Amutable) --- include/linux/sched.h | 2 +- include/linux/sched/signal.h | 5 +---- init/main.c | 2 +- kernel/reboot.c | 19 +++++++++++++++---- kernel/signal.c | 12 ++++++++++++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index 3f100d69b053..fdafd164a42d 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1778,7 +1778,7 @@ static inline bool is_lazy_mmu_mode_active(void) } #endif -extern struct pid *cad_pid; +extern struct pid __rcu *cad_pid; /* * Per process flags diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h index 584ae88b435e..d45a5476b97d 100644 --- a/include/linux/sched/signal.h +++ b/include/linux/sched/signal.h @@ -562,10 +562,7 @@ static inline sigset_t *sigmask_to_save(void) return res; } -static inline int kill_cad_pid(int sig, int priv) -{ - return kill_pid(cad_pid, sig, priv); -} +int kill_cad_pid(int sig, int priv); /* These can be the second arg to send_sig_info/send_group_sig_info. */ #define SEND_SIG_NOINFO ((struct kernel_siginfo *) 0) diff --git a/init/main.c b/init/main.c index 92d34e496a33..f46f3a8b3efd 100644 --- a/init/main.c +++ b/init/main.c @@ -1644,7 +1644,7 @@ static noinline void __init kernel_init_freeable(void) */ set_mems_allowed(node_states[N_MEMORY]); - cad_pid = get_pid(task_pid(current)); + rcu_assign_pointer(cad_pid, get_pid(task_pid(current))); smp_prepare_cpus(setup_max_cpus); diff --git a/kernel/reboot.c b/kernel/reboot.c index f070c5c1103a..d177d89fcc33 100644 --- a/kernel/reboot.c +++ b/kernel/reboot.c @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -24,8 +26,7 @@ */ static int C_A_D = 1; -struct pid *cad_pid; -EXPORT_SYMBOL(cad_pid); +struct pid __rcu *cad_pid; #if defined(CONFIG_ARM) #define DEFAULT_REBOOT_MODE = REBOOT_HARD @@ -1371,10 +1372,14 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe { struct ctl_table tmp_table = *table; struct pid *new_pid; + struct pid *old_pid; pid_t tmp_pid; int r; - tmp_pid = pid_vnr(cad_pid); + rcu_read_lock(); + tmp_pid = pid_vnr(rcu_dereference(cad_pid)); + rcu_read_unlock(); + tmp_table.data = &tmp_pid; r = proc_dointvec(&tmp_table, write, buffer, lenp, ppos); @@ -1385,7 +1390,13 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe if (!new_pid) return -ESRCH; - put_pid(xchg(&cad_pid, new_pid)); + old_pid = unrcu_pointer(xchg(&cad_pid, RCU_INITIALIZER(new_pid))); + /* + * Wait for cad_pid readers before put_pid(). We cannot use + * call_rcu() here because free_pid() already owns pid->rcu. + */ + synchronize_rcu(); + put_pid(old_pid); return 0; } diff --git a/kernel/signal.c b/kernel/signal.c index bbc0fd4cc4d7..2162fad7b940 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1899,6 +1899,18 @@ int kill_pid(struct pid *pid, int sig, int priv) } EXPORT_SYMBOL(kill_pid); +int kill_cad_pid(int sig, int priv) +{ + int ret; + + rcu_read_lock(); + ret = kill_pid(rcu_dereference(cad_pid), sig, priv); + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(kill_cad_pid); + #ifdef CONFIG_POSIX_TIMERS /* * These functions handle POSIX timer signals. POSIX timers use From a518e63c377574784f49653ef5314c70e2463b0c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 25 Aug 2026 17:23:29 +0200 Subject: [PATCH 0049/1317] ovl: return EINVAL instead of EIO in case of mismatched user_ns The EIO was used to signal an internal error (commit 9efb069de4ba ("ovl: add warning on user_ns mismatch")), which is no longer the case. Fixes: 63981fc786da ("ovl: don't warn when the mount is completed from another user namespace") Signed-off-by: Miklos Szeredi Link: https://patch.msgid.link/20260825152330.850645-1-mszeredi@redhat.com Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index e487597337e8..bd0a3f9039d2 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1543,7 +1543,7 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc) struct ovl_fs *ofs = sb->s_fs_info; int err; - err = -EIO; + err = -EINVAL; /* The fscontext fd may have been passed to another user namespace. */ if (fc->user_ns != current_user_ns()) goto out_err; From 04405aeef4f8d7bcac6dcb1947acafdb4420c2c3 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 30 Aug 2026 22:20:26 +0800 Subject: [PATCH 0050/1317] ASoC: sti: initialize IRQ lock before requesting IRQ uni_reader_init() registers the shared IRQ before initializing reader->irq_lock. A pending interrupt can invoke the handler while the lock is still uninitialized. Initialize the lock before registering the IRQ so the interrupt path always sees valid lock state. Fixes: d05d862ead8e ("ASoC: STI: Fix null ptr deference in IRQ handler") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260830142026.2666914-1-runyu.xiao@seu.edu.cn Signed-off-by: Mark Brown --- sound/soc/sti/uniperif_reader.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/sti/uniperif_reader.c b/sound/soc/sti/uniperif_reader.c index 45d7613f595c..5347f9620f25 100644 --- a/sound/soc/sti/uniperif_reader.c +++ b/sound/soc/sti/uniperif_reader.c @@ -416,6 +416,8 @@ int uni_reader_init(struct platform_device *pdev, else reader->hw = &uni_reader_pcm_hw; + spin_lock_init(&reader->irq_lock); + ret = devm_request_irq(&pdev->dev, reader->irq, uni_reader_irq_handler, IRQF_SHARED, dev_name(&pdev->dev), reader); @@ -424,8 +426,6 @@ int uni_reader_init(struct platform_device *pdev, return -EBUSY; } - spin_lock_init(&reader->irq_lock); - return 0; } EXPORT_SYMBOL_GPL(uni_reader_init); From 882de800bf583dd15969836088789edbf035a944 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sun, 23 Aug 2026 12:53:34 +0100 Subject: [PATCH 0051/1317] arm64: dts: amlogic: t7: use the real UART pclk uart_a listed the 24MHz crystal for all three of its clocks because the T7 clock controller driver did not exist when these boards were added. That leaves the real UART bus clock without a user, so the kernel turns it off when it disables unused clocks at the end of boot, and the board hangs. Update the board DTS files to point uart_a's pclk to CLKID_SYS_UART_A instead of the dummy crystal clock. Fixes: 4fef056588f5 ("arm64: dts: amlogic-t7-a311d2-khadas-vim4: add initial device-tree") Fixes: 6f048cc7a635 ("arm64: dts: add board AN400") Signed-off-by: Lucas Tanure Assisted-by: Claude:claude-fable-5 Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260823115335.102219-2-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts | 2 +- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts index cab2ee9ea0d3..ca7536f772ff 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts @@ -33,7 +33,7 @@ xtal: xtal-clk { }; &uart_a { - clocks = <&xtal>, <&xtal>, <&xtal>; + clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_A>, <&xtal>; clock-names = "xtal", "pclk", "baud"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index c41525a34b72..75d81ad6830d 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -266,6 +266,6 @@ &sd_emmc_c { &uart_a { status = "okay"; - clocks = <&xtal>, <&xtal>, <&xtal>; + clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_A>, <&xtal>; clock-names = "xtal", "pclk", "baud"; }; From c793a084ab3088744516aef437b4239d0d98623f Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sun, 23 Aug 2026 12:53:35 +0100 Subject: [PATCH 0052/1317] arm64: dts: amlogic: t7: khadas-vim4: allow the SD card to be power cycled SD cards start at 3.3V and switch to 1.8V to reach UHS-I speeds. Some cards refuse that switch, and the SD specification says the only way to recover is to power the card off and start again. SD_3V3 is marked regulator-always-on, so the supply never goes off, the card stays stuck half way through the switch, and the MMC core retries forever: mmc1: error -95 whilst initialising SD card Drop regulator-always-on. regulator-boot-on still turns the supply on at boot, and a card that refuses the switch now falls back to high speed instead of failing to initialise. Fixes: 8c45bf9ae4ef ("arm64: dts: amlogic: t7: khadas-vim4: Add power regulators") Signed-off-by: Lucas Tanure Assisted-by: Claude:claude-fable-5 Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260823115335.102219-3-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index 75d81ad6830d..c94afaaf8826 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -71,7 +71,6 @@ sd_3v3: regulator-sdcard-3v3 { vin-supply = <&vddao_3v3>; gpio = <&gpio GPIOD_11 GPIO_ACTIVE_LOW>; regulator-boot-on; - regulator-always-on; }; sdio_pwrseq: sdio-pwrseq { From ae7be5c58a819259e5eb98a3abfa60842478f2ae Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:56 +0100 Subject: [PATCH 0053/1317] arm64: dts: amlogic: t7: fix the pin groups of two PWM outputs Two of the PWM outputs can each appear on more than one pin, but the description named a single group that does not exist, so anything using it refused to start. Name the real groups instead, one entry per pin, the same way the other multi-pin PWM output is already described. Fixes: 2a2a7b9701a7 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-3-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 32 ++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index c3dc479b137d..8c4bd683cce0 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -458,9 +458,25 @@ mux { }; }; - pwm_ao_g_pins: pwm-ao-g { + pwm_ao_g_d11_pins: pwm-ao-g-d11 { mux { - groups = "pwm_ao_g"; + groups = "pwm_ao_g_d11"; + function = "pwm_ao_g"; + bias-disable; + }; + }; + + pwm_ao_g_d7_pins: pwm-ao-g-d7 { + mux { + groups = "pwm_ao_g_d7"; + function = "pwm_ao_g"; + bias-disable; + }; + }; + + pwm_ao_g_e_pins: pwm-ao-g-e { + mux { + groups = "pwm_ao_g_e"; function = "pwm_ao_g"; bias-disable; }; @@ -474,9 +490,17 @@ mux { }; }; - pwm_ao_h_pins: pwm-ao-h { + pwm_ao_h_d5_pins: pwm-ao-h-d5 { mux { - groups = "pwm_ao_h"; + groups = "pwm_ao_h_d5"; + function = "pwm_ao_h"; + bias-disable; + }; + }; + + pwm_ao_h_d10_pins: pwm-ao-h-d10 { + mux { + groups = "pwm_ao_h_d10"; function = "pwm_ao_h"; bias-disable; }; From 1e5a53bd16ac501e68463e84023d1bff543cc696 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:57 +0100 Subject: [PATCH 0054/1317] arm64: dts: amlogic: t7: khadas-vim4: add the PWM-driven supplies The board powers its two CPU clusters, the GPU, the NPU, the memory and the always-on domain from regulators steered by PWM outputs. None of them were described, so Linux treated those outputs as unused and switched them off part way through boot. The supplies then drifted away from the levels the bootloader had set, which showed up as random hangs and memory corruption. Describe each supply so it has an owner and is left alone. The voltage ranges are read off the feedback networks on the board schematic. VDDNPU is deliberately not the range in Amlogic's own device tree: this board fits a different feedback resistor, which puts it about 40mV higher at both ends. Fixes: 8c45bf9ae4ef ("arm64: dts: amlogic: t7: khadas-vim4: Add power regulators") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-4-tanure@linux.com Signed-off-by: Neil Armstrong --- .../amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 110 +++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index c94afaaf8826..77abfd555ec5 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -104,6 +104,66 @@ vcc5v0_usb: regulator-vcc-usb { enable-active-high; }; + vdd_ddr: regulator-vddddr { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDDDR"; + regulator-min-microvolt = <690000>; + regulator-max-microvolt = <890000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_gh 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_ee: regulator-vddee { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDEE"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <922000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ab 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_gpu: regulator-vddgpu { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDGPU"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <922000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ef 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_npu: regulator-vddnpu { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDNPU"; + regulator-min-microvolt = <733000>; + regulator-max-microvolt = <933000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ef 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + vddao_1v8: regulator-vddao-1v8 { compatible = "regulator-fixed"; regulator-name = "VDDAO_1V8"; @@ -122,6 +182,36 @@ vddao_3v3: regulator-vddao-3v3 { regulator-always-on; }; + vddcpu_a: regulator-vddcpu-a { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDCPU_A"; + regulator-min-microvolt = <689000>; + regulator-max-microvolt = <1049000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_cd 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vddcpu_b: regulator-vddcpu-b { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDCPU_B"; + regulator-min-microvolt = <689000>; + regulator-max-microvolt = <1049000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ab 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + vddio_1v8: regulator-vddio-1v8 { compatible = "regulator-fixed"; regulator-name = "VDDIO_1V8"; @@ -172,9 +262,27 @@ &pwm_ab { pinctrl-names = "default"; }; +&pwm_ao_ab { + status = "okay"; + pinctrl-0 = <&pwm_ao_a_pins>, <&pwm_ao_b_pins>; + pinctrl-names = "default"; +}; + &pwm_ao_cd { status = "okay"; - pinctrl-0 = <&pwm_ao_c_d_pins>; + pinctrl-0 = <&pwm_ao_c_d_pins>, <&pwm_ao_d_pins>; + pinctrl-names = "default"; +}; + +&pwm_ao_ef { + status = "okay"; + pinctrl-0 = <&pwm_ao_e_pins>, <&pwm_ao_f_pins>; + pinctrl-names = "default"; +}; + +&pwm_ao_gh { + status = "okay"; + pinctrl-0 = <&pwm_ao_g_e_pins>; pinctrl-names = "default"; }; From 406292fd75f95aa3010fec95b5beb5a8b7e3ba3a Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:58 +0100 Subject: [PATCH 0055/1317] arm64: dts: amlogic: t7: fix the pin groups of the vsync PWM The vsync PWM output can appear on either of two pins, but the description named a single group that does not exist, so anything using it would refuse to start. Name the real groups instead, one entry per pin. No board describes this output yet, so nothing changes today. Fixes: 2a2a7b9701a7 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-5-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index 8c4bd683cce0..719e111bc3dd 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -546,9 +546,17 @@ mux { }; }; - pwm_vs_pins: pwm-vs { + pwm_vs_y_pins: pwm-vs-y { mux { - groups = "pwm_vs"; + groups = "pwm_vs_y"; + function = "pwm_vs"; + bias-disable; + }; + }; + + pwm_vs_h_pins: pwm-vs-h { + mux { + groups = "pwm_vs_h"; function = "pwm_vs"; bias-disable; }; From fbf2c660bac863f0ac372b677ee55bf775c94594 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 31 Aug 2026 10:45:06 +0100 Subject: [PATCH 0056/1317] ASoC: cs35l56: Fix pm_runtime imbalance if suspending before first attach Remove the check for init_done in cs35l56_sdw_system_suspend(). Instead, protect the call to cs35l56_mask_soundwire_interrupts() to only be done if the amp is currently enumerated. This fixes a runtime imbalance if cs35l56_sdw_system_suspend() is called before the first SoundWire attach. This would skip the call to pm_runtime_force_suspend() in cs35l56_system_suspend(). But resume unconditionally called pm_runtime_force_resume() leading to an imbalance. cs35l56_system_suspend() doesn't have any dependency on completion of cs35l56_init(), so there is no need for the skip on !init_done in cs35l56_sdw_system_suspend(). Fixes: f9dc6b875ec0a ("ASoC: cs35l56: Add basic system suspend handling") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260831094506.57467-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-sdw.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c index 4fba59e80c37..98bb4542b914 100644 --- a/sound/soc/codecs/cs35l56-sdw.c +++ b/sound/soc/codecs/cs35l56-sdw.c @@ -386,11 +386,8 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) { struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); - if (!cs35l56->base.init_done) - return 0; - - /* runtime_resume unmasks the interrupt */ - cs35l56_mask_soundwire_interrupts(cs35l56); + if (cs35l56->sdw_attached) + cs35l56_mask_soundwire_interrupts(cs35l56); return cs35l56_system_suspend(dev); } From cfc1e9a543e3589ba200795b6e7fd8ef4314efdf Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:22:44 +0800 Subject: [PATCH 0057/1317] ARM: socfpga: select the PL310 erratum 753970 workaround ARCH_INTEL_SOCFPGA selects CACHE_L2X0 and several PL310 erratum workarounds. The 753970 workaround is still conditioned on PL310, but that Kconfig symbol no longer exists, so this one selection is always disabled. Select PL310_ERRATA_753970 directly, consistently with the other PL310 workarounds required by the platform. Fixes: fbc125afdc50 ("ARM: socfpga: Turn on ARM errata for L2 cache") Signed-off-by: Pengpeng Hou Signed-off-by: Dinh Nguyen --- arch/arm/mach-socfpga/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-socfpga/Kconfig b/arch/arm/mach-socfpga/Kconfig index eb72c240c248..528c5c1368c3 100644 --- a/arch/arm/mach-socfpga/Kconfig +++ b/arch/arm/mach-socfpga/Kconfig @@ -16,7 +16,7 @@ menuconfig ARCH_INTEL_SOCFPGA select ARM_ERRATA_775420 select PL310_ERRATA_588369 select PL310_ERRATA_727915 - select PL310_ERRATA_753970 if PL310 + select PL310_ERRATA_753970 select PL310_ERRATA_769419 select RESET_CONTROLLER From 34b8b2d78b6276dc2dc4ebc06625a39956f266e4 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sat, 1 Aug 2026 09:17:31 +0800 Subject: [PATCH 0058/1317] remoteproc: qcom: q6v5_pas: Don't enable handover IRQ on attach qcom_pas_attach() unmasks the handover IRQ and marks handover_issued even though this driver instance never runs qcom_q6v5_prepare() for the boot it is attaching to. This was believed necessary to flush a stale interrupt latched at the interrupt controller while masked, but the handover IRQ is a Qualcomm SMP2P soft IRQ, not a real edge-latched hardware interrupt. The Linux SMP2P driver updates its cached value unconditionally on every notification and only delivers the nested IRQ for bits currently enabled in its own software bitmap, so a transition that happens while masked is simply dropped, never replayed on a later unmask. Since there is nothing to flush, and this driver instance never takes the proxy power-domain/clock/regulator votes that the handover callback would tear down, there is no need to unmask the IRQ in attach() at all. Drop the enable_irq()/disable_irq() pair; setting handover_issued = true is sufficient to keep the flag consistent for the eventual qcom_q6v5_unprepare()/qcom_q6v5_prepare() cycle. It fixes the following unbalanced runtime PM usage and IRQ enable warnings seen on Nord ADSP (probed as attached), after commit bb7c5d6f5b41 ("remoteproc: qcom: q6v5: Make handover IRQ one-shot") comes in place. root@iq10-rrd:~# cat /sys/class/remoteproc/remoteproc0/state attached root@iq10-rrd:~# echo stop > /sys/class/remoteproc/remoteproc0/state [ 40.004874] genpd genpd:0:4c00000.remoteproc: Runtime PM usage count underflow! [ 40.012409] genpd genpd:1:4c00000.remoteproc: Runtime PM usage count underflow! [ 40.050074] remoteproc remoteproc0: stopped remote processor adsp root@iq10-rrd:~# echo start > /sys/class/remoteproc/remoteproc0/state [ 44.350298] remoteproc remoteproc0: powering up adsp [ 44.375769] remoteproc remoteproc0: Booting fw image qcom/nord/adsp.mbn, size 8241816 [ 44.389850] PDM: no support for the platform, userspace daemon might be required. [ 44.397864] ------------[ cut here ]------------ [ 44.402633] Unbalanced enable for IRQ 363 [ 44.406779] WARNING: kernel/irq/manage.c:775 at __enable_irq+0x4c/0x7c, CPU#9: sh/791 ... Suggested-by: Stephan Gerhold Signed-off-by: Shawn Guo Reviewed-by: Abel Vesa Link: https://lore.kernel.org/r/20260801011731.1084591-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index ca8e61254c44..8cff0684fe2f 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -524,7 +524,6 @@ static int qcom_pas_attach(struct rproc *rproc) int ret; pas->q6v5.handover_issued = true; - enable_irq(pas->q6v5.handover_irq); pas->q6v5.running = true; ret = irq_get_irqchip_state(pas->q6v5.fatal_irq, @@ -570,7 +569,6 @@ static int qcom_pas_attach(struct rproc *rproc) pas->rproc->state = RPROC_OFFLINE; ret = -EINVAL; disable_running: - disable_irq(pas->q6v5.handover_irq); pas->q6v5.running = false; return ret; From b853857293584dd1f52183f70a2bdeca692a1e71 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 21 Aug 2026 01:15:40 -0700 Subject: [PATCH 0059/1317] remoteproc: qcom_q6v5_mss: Don't require PAS for memory protection Commit f3b1357673dd ("remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs") changed the probe-time gate for need_mem_protection platforms from qcom_scm_is_available() to qcom_pas_is_available(). Memory protection in this driver is implemented with qcom_scm_assign_mem(), which is a TZ service distinct from PAS. The only PAS call in the driver is qcom_pas_mem_setup(), and it is already guarded by need_pas_mem_setup. No descriptor sets both flags: sc7180, sc7280, sdm660, sdm845, msm8996 and msm8998 set need_mem_protection only, while msm8937, msm8940 and msm8953 set need_pas_mem_setup only. On TrustZone firmware that does not implement PAS - for example SC7180 Chromebooks, where call-availability queries report every QCOM_SCM_SVC_PIL command as unavailable - the modem consequently never probes: platform 4080000.remoteproc: deferred probe pending: (reason unknown) On those machines the modem is also what loads the WLAN firmware, so ath10k never receives QMI and wifi does not come up either. Gate memory protection on SCM availability as it was before, and require PAS only where a PAS call is actually issued. Keeping the SCM check matters: qcom_scm_assign_mem() passes __scm->mempool to qcom_tzmem_alloc() without testing __scm, so dropping the gate entirely would allow a NULL dereference when qcom_scm has not yet probed. Fixes: f3b1357673dd ("remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs") Link: https://lore.kernel.org/r/20260808034716.58888-1-phollinsky@holtechnik.com Signed-off-by: Paul Hollinsky Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260821081540.89137-1-phollinsky@holtechnik.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_mss.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c index bef198b9ee63..2f71ed2feff6 100644 --- a/drivers/remoteproc/qcom_q6v5_mss.c +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -2079,7 +2079,16 @@ static int q6v5_probe(struct platform_device *pdev) if (!desc) return -EINVAL; - if (desc->need_mem_protection && !qcom_pas_is_available()) + /* + * Memory protection is done through qcom_scm_assign_mem(), which needs + * SCM but not PAS. Only the memory setup path issues PAS calls, so + * requiring PAS for every need_mem_protection platform prevents the + * modem from probing at all on TZ firmware that offers no PAS. + */ + if (desc->need_mem_protection && !qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (desc->need_pas_mem_setup && !qcom_pas_is_available()) return -EPROBE_DEFER; mba_image = desc->hexagon_mba_image; From 0d8e2195bce6f08c1c53c5ef4d7347fe46418101 Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Thu, 27 Aug 2026 20:30:55 +0000 Subject: [PATCH 0060/1317] remoteproc: qcom_q6v5_adsp: Fix iommu_unmap() usage During adsp_map_carveout, the IOVA is computed by combining the physical address and the SID: iova = adsp->mem_phys | (sid << 32); However, adsp_unmap_carveout() uses the physical address and not the IOVA in iommu_unmap(), causing the unmap to fail or leak mappings because the address doesn't match the original IOVA. Cache the constructed IOVA within the qcom_adsp device struct during mapping and use it during unmapping. Fixes: f22eedff28af ("remoteproc: qcom: Add support for memory sandbox") Signed-off-by: Mostafa Saleh Link: https://lore.kernel.org/r/20260827203055.640116-1-smostafa@google.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_adsp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c b/drivers/remoteproc/qcom_q6v5_adsp.c index 39654206781d..2e5fb5954fa9 100644 --- a/drivers/remoteproc/qcom_q6v5_adsp.c +++ b/drivers/remoteproc/qcom_q6v5_adsp.c @@ -104,6 +104,7 @@ struct qcom_adsp { struct completion stop_done; phys_addr_t mem_phys; + unsigned long iova; phys_addr_t mem_reloc; void __iomem *mem_region; size_t mem_size; @@ -333,7 +334,7 @@ static void adsp_unmap_carveout(struct rproc *rproc) struct qcom_adsp *adsp = rproc->priv; if (adsp->has_iommu) - iommu_unmap(rproc->domain, adsp->mem_phys, adsp->mem_size); + iommu_unmap(rproc->domain, adsp->iova, adsp->mem_size); } static int adsp_map_carveout(struct rproc *rproc) @@ -341,7 +342,6 @@ static int adsp_map_carveout(struct rproc *rproc) struct qcom_adsp *adsp = rproc->priv; struct of_phandle_args args; long long sid; - unsigned long iova; int ret; if (!adsp->has_iommu) @@ -358,9 +358,9 @@ static int adsp_map_carveout(struct rproc *rproc) of_node_put(args.np); /* Add SID configuration for ADSP Firmware to SMMU */ - iova = adsp->mem_phys | (sid << 32); + adsp->iova = adsp->mem_phys | (sid << 32); - ret = iommu_map(rproc->domain, iova, adsp->mem_phys, + ret = iommu_map(rproc->domain, adsp->iova, adsp->mem_phys, adsp->mem_size, IOMMU_READ | IOMMU_WRITE, GFP_KERNEL); if (ret) { From 9db31edf92dde1f3c98008bd5f88e859d14324cc Mon Sep 17 00:00:00 2001 From: Vignesh Viswanathan Date: Wed, 19 Aug 2026 11:00:30 +0530 Subject: [PATCH 0061/1317] remoteproc: qcom_q6v5_pas: Fix error masking in qcom_pas_stop() In qcom_pas_stop function, return value of qcom_pas_shutdown for pas_id is overwritten by the return value of qcom_pas_shutdown for dtb_pas_id. This causes errors seen on qcom_pas_shutdown failures for pas_id to be masked to the caller. This might lead to issues where the memory regions locked by PAS, as part of qcom_pas_auth_and_reset, are not released for access by linux and rproc_coredump flow will end up accessing the locked memory, leading to an access violation. Fix this by using a separate variable for the dtb_pas_id shutdown call and only overriding the main return value if the pas_id shutdown succeeded but dtb_pas_id shutdown failed. Fixes: 29814986b82e ("remoteproc: qcom_q6v5_pas: add support for dtb co-firmware loading") Signed-off-by: Vignesh Viswanathan Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260819-rproc_dtb_fix-v1-1-cb6be7f93bc9@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 8cff0684fe2f..a005546c265d 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -405,6 +405,7 @@ static int qcom_pas_stop(struct rproc *rproc) { struct qcom_pas *pas = rproc->priv; int handover; + int dtb_ret; int ret; ret = qcom_q6v5_request_stop(&pas->q6v5, pas->sysmon); @@ -419,9 +420,12 @@ static int qcom_pas_stop(struct rproc *rproc) dev_err(pas->dev, "failed to shutdown: %d\n", ret); if (pas->dtb_pas_id) { - ret = qcom_pas_shutdown(pas->dtb_pas_id); - if (ret) - dev_err(pas->dev, "failed to shutdown dtb: %d\n", ret); + dtb_ret = qcom_pas_shutdown(pas->dtb_pas_id); + if (dtb_ret) + dev_err(pas->dev, "failed to shutdown dtb: %d\n", dtb_ret); + + if (!ret && dtb_ret) + ret = dtb_ret; qcom_pas_unmap_carveout(rproc, pas->dtb_mem_phys, pas->dtb_mem_size); } From ee02ed6308fbbd851c4e5c1f642d029617049a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Do=C4=9Fu=20Ari?= Date: Fri, 28 Aug 2026 02:51:39 +0300 Subject: [PATCH 0062/1317] platform/x86: hp-wmi: Fix board_params typo for 8DD6 board MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When adding support for board 8DD6, &omen_v1_no_ec_thermal_params was passed as driver_data instead of &omen_v1_no_ec_board_params. Because active_board_params expects a pointer to struct hp_wmi_board_params, dereferencing active_board_params->thermal_profile results in a type confusion bug and invalid memory access. Update the entry to point to omen_v1_no_ec_board_params. Fixes: a7320d6eb9c42 ("platform/x86: hp-wmi: Add support for OMEN MAX 16-ak0xxx (8DD6)") Cc: stable@vger.kernel.org Signed-off-by: Arda Doğu Ari Reviewed-by: Krishna Chomal Link: https://patch.msgid.link/20260827235139.154462-1-arfeliousheres@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index b2773fc1aca4..615b4cf6fc45 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -337,7 +337,7 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, - .driver_data = (void *)&omen_v1_no_ec_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, From 6bb4fb72c00dc2a9cb663e2d16adce15e4170cdf Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 31 Aug 2026 01:50:58 +0200 Subject: [PATCH 0063/1317] platform/x86: asus-laptop: Fix ACPI event handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The event codes inside asus_keymap[] span a wide range from 0x02 till 0xC5, but using ACPI_DEVICE_NOTIFY prevents us from receiving event codes below 0x80. Fix this by using ACPI_ALL_NOTIFY instead. Fixes: 378500dc1313 ("platform/x86: asus-laptop: Register ACPI notify handler directly") Reported-by: Mo Jun Closes: https://bugs.debian.org/1146124 Tested-by: Mo Jun Signed-off-by: Armin Wolf Reviewed-by: Rafael J. Wysocki Link: https://patch.msgid.link/20260830235058.324140-1-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-laptop.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 449addd1ac7a..79a575d0f5b4 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -1882,7 +1882,7 @@ static int asus_acpi_probe(struct platform_device *pdev) if (result && result != -ENODEV) goto fail_pega_rfkill; - result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + result = acpi_dev_install_notify_handler(device, ACPI_ALL_NOTIFY, asus_acpi_notify, asus); if (result) goto fail_pega_rfkill; @@ -1912,7 +1912,7 @@ static void asus_acpi_remove(struct platform_device *pdev) { struct asus_laptop *asus = platform_get_drvdata(pdev); - acpi_dev_remove_notify_handler(asus->device, ACPI_DEVICE_NOTIFY, + acpi_dev_remove_notify_handler(asus->device, ACPI_ALL_NOTIFY, asus_acpi_notify); asus_backlight_exit(asus); asus_rfkill_exit(asus); From 9ffed84a24d60ec506d8961fe138f0baa92fdbd0 Mon Sep 17 00:00:00 2001 From: Pedro Falcato Date: Mon, 31 Aug 2026 12:43:46 +0100 Subject: [PATCH 0064/1317] platform/x86/amd/pmf: fix build on !CONFIG_AMD_PMF_DEBUG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_pmf_get_ta_custom_bios_inputs() is used by non-debug features. Fix the build on !CONFIG_AMD_PMF_DEBUG by moving amd_pmf_get_ta_custom_bios_inputs() outside the ifdef CONFIG_AMD_PMF_DEBUG. Fixes: 5bda82c797c9 ("platform/x86/amd/pmf: Implement util layer ioctl handler") Reported-by: Oleksandr Natalenko Link: https://lore.kernel.org/all/fS7s9V_xTaedaqEAaxwKnQ@natalenko.name/ Signed-off-by: Pedro Falcato Reviewed-by: Mario Limonciello (AMD) > --- Tested-by: Oleksandr Natalenko Link: https://patch.msgid.link/20260831114346.2041361-1-pfalcato@suse.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/spc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index 94355b435a66..592ba4de4c7f 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -17,7 +17,6 @@ #include #include "pmf.h" -#ifdef CONFIG_AMD_PMF_DEBUG u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) { switch (index) { @@ -31,6 +30,7 @@ u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) } EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); +#ifdef CONFIG_AMD_PMF_DEBUG void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) { int i; From d83b7502bb087fa54daf0fdd419d2910c34bc97d Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Mon, 31 Aug 2026 17:36:46 +0200 Subject: [PATCH 0065/1317] MAINTAINERS: Update URI for watchdog tree The old git repository git://www.linux-watchdog.org/linux-watchdog.git is not accessible anymore, while the development of the watchdog framework is currently handled in git.kernel.org by Guenter's linux-staging repository. Update the URI. Signed-off-by: Antonio Borneo Link: https://patch.msgid.link/20260831153646.396038-1-antonio.borneo@foss.st.com Signed-off-by: Guenter Roeck --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..5e0468e53257 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29357,7 +29357,7 @@ M: Guenter Roeck L: linux-watchdog@vger.kernel.org S: Maintained W: http://www.linux-watchdog.org/ -T: git git://www.linux-watchdog.org/linux-watchdog.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git F: Documentation/devicetree/bindings/watchdog/ F: Documentation/watchdog/ F: drivers/watchdog/ From 8e839bca7793a0b03c005f4b2b0825464290d425 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:06 +0200 Subject: [PATCH 0066/1317] ASoC: ab8500: Reset the audio block before configuring it ResetAudn is active low, but the codec probe only deasserts it. It also clears Clk32kOut2Dis despite claiming to disable that output, and writes codec registers before releasing reset. Pulse ResetAudn before the first audio-bank access and leave the unused 32 kHz output disabled. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-1-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 2cf96cbdd294..714ff0a16f44 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -1638,18 +1638,18 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { static int ab8500_audio_init_audioblock(struct snd_soc_component *component) { int status; + u8 mask = AB8500_STW4500CTRL3_CLK32KOUT2DIS | + AB8500_STW4500CTRL3_RESETAUDN; dev_dbg(component->dev, "%s: Enter.\n", __func__); - /* Reset audio-registers and disable 32kHz-clock output 2 */ - status = ab8500_sysctrl_write(AB8500_STW4500CTRL3, - AB8500_STW4500CTRL3_CLK32KOUT2DIS | - AB8500_STW4500CTRL3_RESETAUDN, - AB8500_STW4500CTRL3_RESETAUDN); + /* Reset the audio registers and disable the unused 32 kHz output. */ + status = ab8500_sysctrl_write(AB8500_STW4500CTRL3, mask, + AB8500_STW4500CTRL3_CLK32KOUT2DIS); if (status < 0) return status; - return 0; + return ab8500_sysctrl_write(AB8500_STW4500CTRL3, mask, mask); } static int ab8500_audio_setup_mics(struct snd_soc_component *component, @@ -2181,6 +2181,13 @@ static int ab8500_codec_probe(struct snd_soc_component *component) ab8500_codec_of_probe(dev, np, &codec_pdata); + status = ab8500_audio_init_audioblock(component); + if (status < 0) { + dev_err(dev, "%s: failed to init audio-block (%d)!\n", + __func__, status); + return status; + } + status = ab8500_audio_setup_mics(component, &codec_pdata.amics); if (status < 0) { pr_err("%s: Failed to setup mics (%d)!\n", __func__, status); @@ -2193,13 +2200,6 @@ static int ab8500_codec_probe(struct snd_soc_component *component) return status; } - status = ab8500_audio_init_audioblock(component); - if (status < 0) { - dev_err(dev, "%s: failed to init audio-block (%d)!\n", - __func__, status); - return status; - } - /* Override HW-defaults */ snd_soc_component_write(component, AB8500_ANACONF5, BIT(AB8500_ANACONF5_HSAUTOEN)); From 103fe1a37f040ef6ac9ed1cf33be786149d2bb15 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:07 +0200 Subject: [PATCH 0067/1317] ASoC: ab8500: Repair the DAPM capture graph The capture stream routes point away from the stream widget. Digital microphone mux routes are unconditional and bypass their enable bits, and several widgets independently own shared AD path enable bits. The dummy ADC and DAC widgets hide the resulting power graph errors. Connect each real AIF widget to the stream and main supply, use the mux item names on digital microphone routes, and model shared AD enables as supplies. Also make the ANC DAPM switch writable. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-2-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 120 +++++++++++++++----------------- 1 file changed, 56 insertions(+), 64 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 714ff0a16f44..5172154f4139 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -259,7 +259,7 @@ static const struct snd_kcontrol_new dapm_anc_in_select[] = { /* ANC - Enable/Disable */ static const struct snd_kcontrol_new dapm_anc_enable[] = { SOC_DAPM_SINGLE("Switch", AB8500_ANCCONF1, - AB8500_ANCCONF1_ENANC, 0, 0), + AB8500_ANCCONF1_ENANC, 1, 0), }; /* ANC to Earpiece - Mute */ @@ -341,12 +341,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { /* DA/AD */ - SND_SOC_DAPM_INPUT("ADC Input"), - SND_SOC_DAPM_ADC("ADC", "ab8500_0c", SND_SOC_NOPM, 0, 0), - - SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_OUTPUT("DAC Output"), - SND_SOC_DAPM_AIF_IN("DA_IN1", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("DA_IN2", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("DA_IN3", NULL, 0, SND_SOC_NOPM, 0, 0), @@ -538,9 +532,8 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_MIXER("AD3 Channel Volume", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("AD3 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD34, 0, - NULL, 0), + SND_SOC_DAPM_SUPPLY("AD34 Enable", AB8500_ADPATHENA, + AB8500_ADPATHENA_ENAD34, 0, NULL, 0), /* Mic 2 */ @@ -599,9 +592,8 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("AD12 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD12, 0, - NULL, 0), + SND_SOC_DAPM_SUPPLY("AD12 Enable", AB8500_ADPATHENA, + AB8500_ADPATHENA_ENAD12, 0, NULL, 0), /* HD Capture path */ @@ -615,12 +607,8 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_MIXER("AD6 Channel Volume", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("AD57 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("AD68 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, - NULL, 0), + SND_SOC_DAPM_SUPPLY("AD5768 Enable", AB8500_ADPATHENA, + AB8500_ADPATHENA_ENAD5768, 0, NULL, 0), /* Digital Microphone path */ @@ -652,10 +640,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_MIXER("AD4 Channel Volume", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("AD4 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD34, - 0, NULL, 0), - /* Acoustical Noise Cancellation path */ SND_SOC_DAPM_INPUT("ANC Configure Input"), @@ -703,24 +687,17 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"Main Supply", NULL, "Audio Power"}, {"Main Supply", NULL, "Audio Analog Power"}, - {"DAC", NULL, "ab8500_0p"}, - {"DAC", NULL, "Main Supply"}, - {"ADC", NULL, "ab8500_0c"}, - {"ADC", NULL, "Main Supply"}, - /* ANC Configure */ {"ANC Configure Input", NULL, "Main Supply"}, {"ANC Configure Output", NULL, "ANC Configure Input"}, - /* AD/DA */ - {"ADC", NULL, "ADC Input"}, - {"DAC Output", NULL, "DAC"}, - /* Powerup charge pump if DA1/2 is in use */ {"DA_IN1", NULL, "ab8500_0p"}, + {"DA_IN1", NULL, "Main Supply"}, {"DA_IN1", NULL, "Charge Pump"}, {"DA_IN2", NULL, "ab8500_0p"}, + {"DA_IN2", NULL, "Main Supply"}, {"DA_IN2", NULL, "Charge Pump"}, /* Headset path */ @@ -755,8 +732,10 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { /* HF or LineOut path */ {"DA_IN3", NULL, "ab8500_0p"}, + {"DA_IN3", NULL, "Main Supply"}, {"DA3 Channel Volume", NULL, "DA_IN3"}, {"DA_IN4", NULL, "ab8500_0p"}, + {"DA_IN4", NULL, "Main Supply"}, {"DA4 Channel Volume", NULL, "DA_IN4"}, {"Speaker Left Source", "Audio Path", "DA3 Channel Volume"}, @@ -814,8 +793,10 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { /* Vibrator path */ {"DA_IN5", NULL, "ab8500_0p"}, + {"DA_IN5", NULL, "Main Supply"}, {"DA5 Channel Volume", NULL, "DA_IN5"}, {"DA_IN6", NULL, "ab8500_0p"}, + {"DA_IN6", NULL, "Main Supply"}, {"DA6 Channel Volume", NULL, "DA_IN6"}, {"VIB1 DAC", NULL, "DA5 Channel Volume"}, @@ -857,13 +838,15 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD1 Channel Volume", NULL, "AD1 Source Select"}, {"AD2 Channel Volume", NULL, "AD2 Source Select"}, - {"AD12 Enable", NULL, "AD1 Channel Volume"}, - {"AD12 Enable", NULL, "AD2 Channel Volume"}, + {"AD1 Channel Volume", NULL, "AD12 Enable"}, + {"AD2 Channel Volume", NULL, "AD12 Enable"}, - {"AD_OUT1", NULL, "ab8500_0c"}, - {"AD_OUT1", NULL, "AD12 Enable"}, - {"AD_OUT2", NULL, "ab8500_0c"}, - {"AD_OUT2", NULL, "AD12 Enable"}, + {"ab8500_0c", NULL, "AD_OUT1"}, + {"AD_OUT1", NULL, "Main Supply"}, + {"AD_OUT1", NULL, "AD1 Channel Volume"}, + {"ab8500_0c", NULL, "AD_OUT2"}, + {"AD_OUT2", NULL, "Main Supply"}, + {"AD_OUT2", NULL, "AD2 Channel Volume"}, /* Mic 1 */ @@ -880,11 +863,11 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD3 Source Select", "Mic 1", "MIC1 ADC"}, {"AD3 Channel Volume", NULL, "AD3 Source Select"}, + {"AD3 Channel Volume", NULL, "AD34 Enable"}, - {"AD3 Enable", NULL, "AD3 Channel Volume"}, - - {"AD_OUT3", NULL, "ab8500_0c"}, - {"AD_OUT3", NULL, "AD3 Enable"}, + {"ab8500_0c", NULL, "AD_OUT3"}, + {"AD_OUT3", NULL, "Main Supply"}, + {"AD_OUT3", NULL, "AD3 Channel Volume"}, /* HD Capture path */ @@ -893,14 +876,15 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD5 Channel Volume", NULL, "AD5 Source Select"}, {"AD6 Channel Volume", NULL, "AD6 Source Select"}, + {"AD5 Channel Volume", NULL, "AD5768 Enable"}, + {"AD6 Channel Volume", NULL, "AD5768 Enable"}, - {"AD57 Enable", NULL, "AD5 Channel Volume"}, - {"AD68 Enable", NULL, "AD6 Channel Volume"}, - - {"AD_OUT57", NULL, "ab8500_0c"}, - {"AD_OUT57", NULL, "AD57 Enable"}, - {"AD_OUT68", NULL, "ab8500_0c"}, - {"AD_OUT68", NULL, "AD68 Enable"}, + {"ab8500_0c", NULL, "AD_OUT57"}, + {"AD_OUT57", NULL, "Main Supply"}, + {"AD_OUT57", NULL, "AD5 Channel Volume"}, + {"ab8500_0c", NULL, "AD_OUT68"}, + {"AD_OUT68", NULL, "Main Supply"}, + {"AD_OUT68", NULL, "AD6 Channel Volume"}, /* Digital Microphone path */ @@ -911,17 +895,25 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"DMic 5", NULL, "V-DMIC"}, {"DMic 6", NULL, "V-DMIC"}, - {"AD1 Source Select", NULL, "DMic 1"}, - {"AD2 Source Select", NULL, "DMic 2"}, - {"AD3 Source Select", NULL, "DMic 3"}, - {"AD5 Source Select", NULL, "DMic 5"}, - {"AD6 Source Select", NULL, "DMic 6"}, + {"DMIC1", NULL, "DMic 1"}, + {"DMIC2", NULL, "DMic 2"}, + {"DMIC3", NULL, "DMic 3"}, + {"DMIC4", NULL, "DMic 4"}, + {"DMIC5", NULL, "DMic 5"}, + {"DMIC6", NULL, "DMic 6"}, - {"AD4 Channel Volume", NULL, "DMic 4"}, - {"AD4 Enable", NULL, "AD4 Channel Volume"}, + {"AD1 Source Select", "DMic 1", "DMIC1"}, + {"AD2 Source Select", "DMic 2", "DMIC2"}, + {"AD3 Source Select", "DMic 3", "DMIC3"}, + {"AD5 Source Select", "DMic 5", "DMIC5"}, + {"AD6 Source Select", "DMic 6", "DMIC6"}, - {"AD_OUT4", NULL, "ab8500_0c"}, - {"AD_OUT4", NULL, "AD4 Enable"}, + {"AD4 Channel Volume", NULL, "DMIC4"}, + {"AD4 Channel Volume", NULL, "AD34 Enable"}, + + {"ab8500_0c", NULL, "AD_OUT4"}, + {"AD_OUT4", NULL, "Main Supply"}, + {"AD_OUT4", NULL, "AD4 Channel Volume"}, /* LineIn Bypass path */ @@ -946,13 +938,13 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { /* Sidetone Filter path */ - {"Sidetone Left Source", "LineIn Left", "AD12 Enable"}, - {"Sidetone Left Source", "LineIn Right", "AD12 Enable"}, - {"Sidetone Left Source", "Mic 1", "AD3 Enable"}, + {"Sidetone Left Source", "LineIn Left", "AD1 Channel Volume"}, + {"Sidetone Left Source", "LineIn Right", "AD2 Channel Volume"}, + {"Sidetone Left Source", "Mic 1", "AD3 Channel Volume"}, {"Sidetone Left Source", "Headset Left", "DA_IN1"}, - {"Sidetone Right Source", "LineIn Right", "AD12 Enable"}, - {"Sidetone Right Source", "Mic 1", "AD3 Enable"}, - {"Sidetone Right Source", "DMic 4", "AD4 Enable"}, + {"Sidetone Right Source", "LineIn Right", "AD2 Channel Volume"}, + {"Sidetone Right Source", "Mic 1", "AD3 Channel Volume"}, + {"Sidetone Right Source", "DMic 4", "AD4 Channel Volume"}, {"Sidetone Right Source", "Headset Right", "DA_IN2"}, {"STFIR1 Control", NULL, "Sidetone Left Source"}, From f98785adf004db6b1c9f4cea9dadae7b800db72f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:08 +0200 Subject: [PATCH 0068/1317] ASoC: ab8500: Correct digital interface format setup The codec programs I2S as an undelayed left-aligned format, although the hardware manual defines delayed left-aligned as I2S compatible. It also enables the master generator when the codec is a clock consumer, changes registers before the complete format has been validated, and discards register I/O errors. Build all three interface register values before writing them, use the required one-bit I2S delay, only run the master generator for a provider configuration, and propagate write failures. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-3-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 175 ++++++++++++-------------------- 1 file changed, 64 insertions(+), 111 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 5172154f4139..4fbe486de74a 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -1737,149 +1737,91 @@ static int ab8500_audio_set_ear_cmv(struct snd_soc_component *component, return 0; } -static int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, - unsigned int delay) -{ - unsigned int mask, val; - struct snd_soc_component *component = dai->component; - - mask = BIT(AB8500_DIGIFCONF2_IF0DEL); - val = 0; - - switch (delay) { - case 0: - break; - case 1: - val |= BIT(AB8500_DIGIFCONF2_IF0DEL); - break; - default: - dev_err(dai->component->dev, - "%s: ERROR: Unsupported bit-delay (0x%x)!\n", - __func__, delay); - return -EINVAL; - } - - dev_dbg(dai->component->dev, "%s: IF0 Bit-delay: %d bits.\n", - __func__, delay); - snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); - - return 0; -} - -/* Gates clocking according format mask */ -static int ab8500_codec_set_dai_clock_gate(struct snd_soc_component *component, - unsigned int fmt) -{ - unsigned int mask; - unsigned int val; - - mask = BIT(AB8500_DIGIFCONF1_ENMASTGEN) | - BIT(AB8500_DIGIFCONF1_ENFSBITCLK0); - - val = BIT(AB8500_DIGIFCONF1_ENMASTGEN); - - switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { - case SND_SOC_DAIFMT_CONT: /* continuous clock */ - dev_dbg(component->dev, "%s: IF0 Clock is continuous.\n", - __func__); - val |= BIT(AB8500_DIGIFCONF1_ENFSBITCLK0); - break; - case SND_SOC_DAIFMT_GATED: /* clock is gated */ - dev_dbg(component->dev, "%s: IF0 Clock is gated.\n", - __func__); - break; - default: - dev_err(component->dev, - "%s: ERROR: Unsupported clock mask (0x%x)!\n", - __func__, fmt & SND_SOC_DAIFMT_CLOCK_MASK); - return -EINVAL; - } - - snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, mask, val); - - return 0; -} - static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { - unsigned int mask; - unsigned int val; struct snd_soc_component *component = dai->component; - int status; + unsigned int conf1_mask, conf1_val = 0; + unsigned int conf2_mask, conf2_val = 0; + unsigned int conf3_mask, conf3_val = 0; + bool provider = false; + int ret; dev_dbg(component->dev, "%s: Enter (fmt = 0x%x)\n", __func__, fmt); - mask = BIT(AB8500_DIGIFCONF3_IF1DATOIF0AD) | + conf3_mask = BIT(AB8500_DIGIFCONF3_IF1DATOIF0AD) | BIT(AB8500_DIGIFCONF3_IF1CLKTOIF0CLK) | BIT(AB8500_DIGIFCONF3_IF0BFIFOEN) | BIT(AB8500_DIGIFCONF3_IF0MASTER); - val = 0; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_CBP_CFP: - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0 Master-mode: AB8500 provider.\n", __func__); - val |= BIT(AB8500_DIGIFCONF3_IF0MASTER); + conf3_val |= BIT(AB8500_DIGIFCONF3_IF0MASTER); + provider = true; break; case SND_SOC_DAIFMT_CBC_CFC: - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0 Master-mode: AB8500 consumer.\n", __func__); break; case SND_SOC_DAIFMT_CBC_CFP: case SND_SOC_DAIFMT_CBP_CFC: - dev_err(dai->component->dev, + dev_err(component->dev, "%s: ERROR: The device is either a provider or a consumer.\n", __func__); fallthrough; default: - dev_err(dai->component->dev, - "%s: ERROR: Unsupporter clocking mask 0x%x\n", + dev_err(component->dev, + "%s: ERROR: Unsupported clocking mask 0x%x\n", __func__, fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK); return -EINVAL; } - snd_soc_component_update_bits(component, AB8500_DIGIFCONF3, mask, val); - - /* Set clock gating */ - status = ab8500_codec_set_dai_clock_gate(component, fmt); - if (status) { - dev_err(dai->component->dev, - "%s: ERROR: Failed to set clock gate (%d).\n", - __func__, status); - return status; + conf1_mask = BIT(AB8500_DIGIFCONF1_ENMASTGEN) | + BIT(AB8500_DIGIFCONF1_ENFSBITCLK0); + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + if (provider) + conf1_val = conf1_mask; + break; + case SND_SOC_DAIFMT_GATED: + if (provider) + conf1_val = BIT(AB8500_DIGIFCONF1_ENMASTGEN); + break; + default: + dev_err(component->dev, "%s: Unsupported clock mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_CLOCK_MASK); + return -EINVAL; } - /* Setting data transfer format */ - - mask = BIT(AB8500_DIGIFCONF2_IF0FORMAT0) | - BIT(AB8500_DIGIFCONF2_IF0FORMAT1) | - BIT(AB8500_DIGIFCONF2_FSYNC0P) | - BIT(AB8500_DIGIFCONF2_BITCLK0P); - val = 0; + conf2_mask = BIT(AB8500_DIGIFCONF2_IF0FORMAT0) | + BIT(AB8500_DIGIFCONF2_IF0FORMAT1) | + BIT(AB8500_DIGIFCONF2_IF0DEL) | + BIT(AB8500_DIGIFCONF2_FSYNC0P) | + BIT(AB8500_DIGIFCONF2_BITCLK0P); switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: /* I2S mode */ - dev_dbg(dai->component->dev, "%s: IF0 Protocol: I2S\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT1); - ab8500_audio_set_bit_delay(dai, 0); + dev_dbg(component->dev, "%s: IF0 Protocol: I2S\n", __func__); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT1) | + BIT(AB8500_DIGIFCONF2_IF0DEL); break; case SND_SOC_DAIFMT_DSP_A: /* L data MSB after FRM LRC */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0 Protocol: DSP A (TDM)\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0); - ab8500_audio_set_bit_delay(dai, 1); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0) | + BIT(AB8500_DIGIFCONF2_IF0DEL); break; case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0 Protocol: DSP B (TDM)\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0); - ab8500_audio_set_bit_delay(dai, 0); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0); break; default: - dev_err(dai->component->dev, + dev_err(component->dev, "%s: ERROR: Unsupported format (0x%x)!\n", __func__, fmt & SND_SOC_DAIFMT_FORMAT_MASK); return -EINVAL; @@ -1887,39 +1829,50 @@ static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0: Normal bit clock, normal frame\n", __func__); break; case SND_SOC_DAIFMT_NB_IF: /* normal BCLK + inv FRM */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0: Normal bit clock, inverted frame\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_FSYNC0P); + conf2_val |= BIT(AB8500_DIGIFCONF2_FSYNC0P); break; case SND_SOC_DAIFMT_IB_NF: /* invert BCLK + nor FRM */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0: Inverted bit clock, normal frame\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_BITCLK0P); + conf2_val |= BIT(AB8500_DIGIFCONF2_BITCLK0P); break; case SND_SOC_DAIFMT_IB_IF: /* invert BCLK + FRM */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0: Inverted bit clock, inverted frame\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_FSYNC0P); - val |= BIT(AB8500_DIGIFCONF2_BITCLK0P); + conf2_val |= BIT(AB8500_DIGIFCONF2_FSYNC0P) | + BIT(AB8500_DIGIFCONF2_BITCLK0P); break; default: - dev_err(dai->component->dev, + dev_err(component->dev, "%s: ERROR: Unsupported INV mask 0x%x\n", __func__, fmt & SND_SOC_DAIFMT_INV_MASK); return -EINVAL; } - snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF3, + conf3_mask, conf3_val); + if (ret < 0) + return ret; - return 0; + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, + conf1_mask, conf1_val); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, + conf2_mask, conf2_val); + + return ret < 0 ? ret : 0; } static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, From 85cef7e2004ef5c1a715feaddb55d3f3d27bac0a Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:09 +0200 Subject: [PATCH 0069/1317] ASoC: ab8500: Validate and program TDM slots correctly The interface clock ratio is selected from the slot count alone, ffs() and fls() produce one-based hardware slot numbers, eight-channel mode does not program any mappings, and all register errors are ignored. Invalid masks can also leave a partially programmed interface. Validate the complete configuration first, derive the supported BCLK ratio from slots times slot width, use zero-based slot indices, program deterministic eight-channel maps, and propagate register failures. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-4-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 222 ++++++++++++++++++-------------- 1 file changed, 126 insertions(+), 96 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 4fbe486de74a..167f021c7ecb 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -1880,23 +1880,27 @@ static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, int slots, int slot_width) { struct snd_soc_component *component = dai->component; - unsigned int val, mask, slot, slots_active; + unsigned int active_mask, clock_ratio, slot, value, ad_out, reg; + unsigned int tx_active, rx_active; + unsigned int conf1_val, conf2_val; + unsigned int mask; + int channel, ret; mask = BIT(AB8500_DIGIFCONF2_IF0WL0) | BIT(AB8500_DIGIFCONF2_IF0WL1); - val = 0; + conf2_val = 0; switch (slot_width) { case 16: break; case 20: - val |= BIT(AB8500_DIGIFCONF2_IF0WL0); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0WL0); break; case 24: - val |= BIT(AB8500_DIGIFCONF2_IF0WL1); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0WL1); break; case 32: - val |= BIT(AB8500_DIGIFCONF2_IF0WL1) | + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0WL1) | BIT(AB8500_DIGIFCONF2_IF0WL0); break; default: @@ -1905,27 +1909,11 @@ static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, return -EINVAL; } - dev_dbg(dai->component->dev, "%s: IF0 slot-width: %d bits.\n", - __func__, slot_width); - snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); - - /* Setup TDM clocking according to slot count */ - dev_dbg(dai->component->dev, "%s: Slots, total: %d\n", __func__, slots); - mask = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | - BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); switch (slots) { case 2: - val = AB8500_MASK_NONE; - break; case 4: - val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0); - break; case 8: - val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); - break; case 16: - val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | - BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); break; default: dev_err(dai->component->dev, @@ -1933,94 +1921,136 @@ static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, __func__, slots); return -EINVAL; } - snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, mask, val); - /* Setup TDM DA according to active tx slots */ - - if (tx_mask & ~0xff) - return -EINVAL; - - mask = AB8500_DASLOTCONFX_SLTODAX_MASK; - tx_mask = tx_mask << AB8500_DA_DATA0_OFFSET; - slots_active = hweight32(tx_mask); - - dev_dbg(dai->component->dev, "%s: Slots, active, TX: %d\n", __func__, - slots_active); - - switch (slots_active) { - case 0: + clock_ratio = slots * slot_width; + switch (clock_ratio) { + case 32: + conf1_val = 0; break; - case 1: - slot = ffs(tx_mask); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF1, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF3, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF2, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF4, mask, slot); + case 64: + conf1_val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0); break; - case 2: - slot = ffs(tx_mask); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF1, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF3, mask, slot); - slot = fls(tx_mask); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF2, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF4, mask, slot); + case 128: + conf1_val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); break; - case 8: - dev_dbg(dai->component->dev, - "%s: In 8-channel mode DA-from-slot mapping is set manually.", - __func__); + case 256: + conf1_val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | + BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); break; default: - dev_err(dai->component->dev, - "%s: Unsupported number of active TX-slots (%d)!\n", - __func__, slots_active); + dev_err(component->dev, "%s: Unsupported BCLK ratio (%u)!\n", + __func__, clock_ratio); return -EINVAL; } - /* Setup TDM AD according to active RX-slots */ - - if (rx_mask & ~0xff) - return -EINVAL; - - rx_mask = rx_mask << AB8500_AD_DATA0_OFFSET; - slots_active = hweight32(rx_mask); - - dev_dbg(dai->component->dev, "%s: Slots, active, RX: %d\n", __func__, - slots_active); - - switch (slots_active) { - case 0: - break; - case 1: - slot = ffs(rx_mask); - snd_soc_component_update_bits(component, AB8500_ADSLOTSEL(slot), - AB8500_MASK_SLOT(slot), - AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, slot)); - break; - case 2: - slot = ffs(rx_mask); - snd_soc_component_update_bits(component, - AB8500_ADSLOTSEL(slot), - AB8500_MASK_SLOT(slot), - AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, slot)); - slot = fls(rx_mask); - snd_soc_component_update_bits(component, - AB8500_ADSLOTSEL(slot), - AB8500_MASK_SLOT(slot), - AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT2, slot)); - break; - case 8: - dev_dbg(dai->component->dev, - "%s: In 8-channel mode AD-to-slot mapping is set manually.", + active_mask = GENMASK(min(slots, 8) - 1, 0); + if ((tx_mask | rx_mask) & ~active_mask) { + dev_err(component->dev, "%s: Slot mask exceeds slot count\n", __func__); - break; - default: - dev_err(dai->component->dev, - "%s: Unsupported number of active RX-slots (%d)!\n", - __func__, slots_active); return -EINVAL; } + tx_active = hweight32(tx_mask); + rx_active = hweight32(rx_mask); + if (tx_active != 0 && tx_active != 1 && tx_active != 2 && + tx_active != 8) { + dev_err(component->dev, "%s: Unsupported active TX slots (%u)!\n", + __func__, tx_active); + return -EINVAL; + } + if (rx_active != 0 && rx_active != 1 && rx_active != 2 && + rx_active != 8) { + dev_err(component->dev, "%s: Unsupported active RX slots (%u)!\n", + __func__, rx_active); + return -EINVAL; + } + + dev_dbg(component->dev, + "%s: %d slots of %d bits, TX active: %u, RX active: %u\n", + __func__, slots, slot_width, tx_active, rx_active); + + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, + mask, conf2_val); + if (ret < 0) + return ret; + + mask = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | + BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, + mask, conf1_val); + if (ret < 0) + return ret; + + mask = AB8500_DASLOTCONFX_SLTODAX_MASK; + if (tx_active == 1 || tx_active == 2) { + slot = __ffs(tx_mask) + AB8500_DA_DATA0_OFFSET; + reg = AB8500_DASLOTCONF1; + ret = snd_soc_component_update_bits(component, reg, mask, slot); + if (ret < 0) + return ret; + reg = AB8500_DASLOTCONF3; + ret = snd_soc_component_update_bits(component, reg, mask, slot); + if (ret < 0) + return ret; + + if (tx_active == 2) + slot = __fls(tx_mask) + AB8500_DA_DATA0_OFFSET; + reg = AB8500_DASLOTCONF2; + ret = snd_soc_component_update_bits(component, reg, mask, slot); + if (ret < 0) + return ret; + reg = AB8500_DASLOTCONF4; + ret = snd_soc_component_update_bits(component, reg, mask, slot); + if (ret < 0) + return ret; + } else if (tx_active == 8) { + channel = 0; + for (slot = 0; slot < 8; slot++) { + if (!(tx_mask & BIT(slot))) + continue; + reg = AB8500_DASLOTCONF1 + channel++; + value = slot + AB8500_DA_DATA0_OFFSET; + ret = snd_soc_component_update_bits(component, reg, mask, value); + if (ret < 0) + return ret; + } + } + + if (rx_active == 1 || rx_active == 2) { + slot = __ffs(rx_mask) + AB8500_AD_DATA0_OFFSET; + value = AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, + slot); + reg = AB8500_ADSLOTSEL(slot); + mask = AB8500_MASK_SLOT(slot); + ret = snd_soc_component_update_bits(component, reg, mask, value); + if (ret < 0) + return ret; + + if (rx_active == 2) { + slot = __fls(rx_mask) + AB8500_AD_DATA0_OFFSET; + value = AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT2, + slot); + reg = AB8500_ADSLOTSEL(slot); + mask = AB8500_MASK_SLOT(slot); + ret = snd_soc_component_update_bits(component, reg, mask, value); + if (ret < 0) + return ret; + } + } else if (rx_active == 8) { + channel = 0; + for (slot = 0; slot < 8; slot++) { + if (!(rx_mask & BIT(slot))) + continue; + ad_out = AB8500_AD_OUT1 + channel++; + value = AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(ad_out, slot); + reg = AB8500_ADSLOTSEL(slot); + mask = AB8500_MASK_SLOT(slot); + ret = snd_soc_component_update_bits(component, reg, mask, value); + if (ret < 0) + return ret; + } + } + return 0; } From ec75e653b70ce26ea68187c2069722baa80efadb Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:10 +0200 Subject: [PATCH 0070/1317] ASoC: ab8500: Remove the nonfunctional sidetone apply control After the coefficient controls were removed, writing "Apply FIR" programs 128 zero coefficients and reports that the sidetone filter is configured. The associated ANC configuration DAPM pins are also now unreachable dead infrastructure. Remove the misleading status/apply control, its private state, and the obsolete configuration-only DAPM pins. Keep the direct sidetone reset and remaining hardware controls. Fixes: e366ce8b22ec ("ASoC: codecs: ab8500: Remove suspicious code") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-5-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 114 -------------------------------- 1 file changed, 114 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 167f021c7ecb..11142d15df45 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -14,17 +14,14 @@ * for ST-Ericsson. */ -#include #include #include #include #include #include #include -#include #include #include -#include #include #include #include @@ -54,32 +51,9 @@ /* Macrocell register definitions */ #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ -/* Nr of FIR/IIR-coeff banks in ANC-block */ -#define AB8500_NR_OF_ANC_COEFF_BANKS 2 - -/* Minimum duration to keep ANC IIR Init bit high or -low before proceeding with the configuration sequence */ -#define AB8500_ANC_SM_DELAY 2000 - -/* Sidetone states */ -static const char * const enum_sid_state[] = { - "Unconfigured", - "Apply FIR", - "FIR is configured", -}; -enum sid_state { - SID_UNCONFIGURED = 0, - SID_APPLY_FIR = 1, - SID_FIR_CONFIGURED = 2, -}; - /* Private data for AB8500 device-driver */ struct ab8500_codec_drvdata { struct regmap *regmap; - struct mutex ctrl_lock; - - /* Sidetone */ - enum sid_state sid_status; }; static inline const char *amic_micbias_str(enum amic_micbias micbias) @@ -642,9 +616,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { NULL, 0), /* Acoustical Noise Cancellation path */ - SND_SOC_DAPM_INPUT("ANC Configure Input"), - SND_SOC_DAPM_OUTPUT("ANC Configure Output"), - SND_SOC_DAPM_MUX("ANC Source", SND_SOC_NOPM, 0, 0, dapm_anc_in_select), @@ -687,10 +658,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"Main Supply", NULL, "Audio Power"}, {"Main Supply", NULL, "Audio Analog Power"}, - /* ANC Configure */ - {"ANC Configure Input", NULL, "Main Supply"}, - {"ANC Configure Output", NULL, "ANC Configure Input"}, - /* Powerup charge pump if DA1/2 is in use */ {"DA_IN1", NULL, "ab8500_0p"}, @@ -972,75 +939,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, }; -/* - * Control-events - */ - -static int sid_status_control_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); - - guard(mutex)(&drvdata->ctrl_lock); - ucontrol->value.enumerated.item[0] = drvdata->sid_status; - - return 0; -} - -/* Write sidetone FIR-coefficients configuration sequence */ -static int sid_status_control_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); - unsigned int param, sidconf; - int status = 1; - - dev_dbg(component->dev, "%s: Enter\n", __func__); - - if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { - dev_err(component->dev, - "%s: ERROR: This control supports '%s' only!\n", - __func__, enum_sid_state[SID_APPLY_FIR]); - return -EIO; - } - - guard(mutex)(&drvdata->ctrl_lock); - - sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); - if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { - if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { - dev_err(component->dev, "%s: Sidetone busy while off!\n", - __func__); - status = -EPERM; - } else { - status = -EBUSY; - } - dev_dbg(component->dev, "%s: Exit\n", __func__); - return status; - } - - snd_soc_component_write(component, AB8500_SIDFIRADR, 0); - - for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { - snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); - snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); - } - - snd_soc_component_update_bits(component, AB8500_SIDFIRADR, - BIT(AB8500_SIDFIRADR_FIRSIDSET), - BIT(AB8500_SIDFIRADR_FIRSIDSET)); - snd_soc_component_update_bits(component, AB8500_SIDFIRADR, - BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); - - drvdata->sid_status = SID_FIR_CONFIGURED; - - dev_dbg(component->dev, "%s: Exit\n", __func__); - - return status; -} - /* * Controls - Non-DAPM ASoC */ @@ -1324,9 +1222,6 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, enum_slavemaster); -/* Sidetone */ -static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); - /* ANC */ static struct snd_kcontrol_new ab8500_ctrls[] = { @@ -1617,8 +1512,6 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), /* Sidetone */ - SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, - sid_status_control_get, sid_status_control_put), SOC_SINGLE_STROBE("Sidetone Reset", AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), }; @@ -2145,10 +2038,8 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, static int ab8500_codec_probe(struct snd_soc_component *component) { - struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct device *dev = component->dev; struct device_node *np = dev->of_node; - struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); struct ab8500_codec_platform_data codec_pdata; int status; @@ -2181,10 +2072,6 @@ static int ab8500_codec_probe(struct snd_soc_component *component) snd_soc_component_write(component, AB8500_SHORTCIRCONF, BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); - snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); - - mutex_init(&drvdata->ctrl_lock); - return status; } @@ -2213,7 +2100,6 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) GFP_KERNEL); if (!drvdata) return -ENOMEM; - drvdata->sid_status = SID_UNCONFIGURED; dev_set_drvdata(&pdev->dev, drvdata); drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, From 711178754287db3fd0f7accff3c2a7575f8873b7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:11 +0200 Subject: [PATCH 0071/1317] ASoC: ab8500: Skip missing DMIC GPIOs on AB8505 GPIO27, GPIO29 and GPIO31 provide the digital microphone clock outputs on AB8500, but these GPIOs do not exist on AB8505. The shared codec driver nevertheless accesses their direction register while setting up every AB8505 codec. Identify the parent MFD device and leave the nonexistent GPIOs untouched on AB8505. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-6-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 11142d15df45..e1a0e35836e6 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -1540,6 +1540,8 @@ static int ab8500_audio_init_audioblock(struct snd_soc_component *component) static int ab8500_audio_setup_mics(struct snd_soc_component *component, struct amic_settings *amics) { + struct device *dev = component->dev; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); u8 value8; unsigned int value; @@ -1548,20 +1550,21 @@ static int ab8500_audio_setup_mics(struct snd_soc_component *component, dev_dbg(component->dev, "%s: Enter.\n", __func__); - /* Set DMic-clocks to outputs */ - status = abx500_get_register_interruptible(component->dev, AB8500_MISC, - AB8500_GPIO_DIR4_REG, - &value8); - if (status < 0) - return status; - value = value8 | GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | - GPIO31_DIR_OUTPUT; - status = abx500_set_register_interruptible(component->dev, - AB8500_MISC, - AB8500_GPIO_DIR4_REG, - value); - if (status < 0) - return status; + /* Set DMic-clocks to outputs; these GPIOs do not exist on AB8505. */ + if (!is_ab8505(ab8500)) { + status = abx500_get_register_interruptible(dev, AB8500_MISC, + AB8500_GPIO_DIR4_REG, + &value8); + if (status < 0) + return status; + value = value8 | GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | + GPIO31_DIR_OUTPUT; + status = abx500_set_register_interruptible(dev, AB8500_MISC, + AB8500_GPIO_DIR4_REG, + value); + if (status < 0) + return status; + } /* Attach regulators to AMic DAPM-paths */ dev_dbg(component->dev, "%s: Mic 1a regulator: %s\n", __func__, From 7a4ce92d150b9e7ecf1a710a34d8cdeb590d3751 Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Mon, 31 Aug 2026 15:13:36 +0000 Subject: [PATCH 0072/1317] ASoC: sprd: validate compress buffer sizes against fixed allocations sprd_platform_compr_open() allocates the stage 0 IRAM buffer (32K data area) and the stage 1 DDR buffer (2M data area) with fixed sizes, but sprd_platform_compr_copy() derives all copy lengths from the user controlled runtime->fragment_size and the write() count, never comparing them against the physical buffer sizes. The compress core only checks fragment_size * fragments for an u32 overflow in snd_compress_check_input(), so a local user can configure a logical buffer of up to ~4GB via SNDRV_COMPRESS_SET_PARAMS, far exceeding the fixed allocations. A fragment_size larger than the 32K IRAM data area makes the stage 0 copy_from_user() overflow past the IRAM allocation, and a buffer_size larger than the 2M DDR buffer makes the wrapping copy at the end of sprd_platform_compr_copy() write fully user controlled data past the buffer. No SNDRV_PCM_TRIGGER_START is needed, a write() in SETUP state reaches the copy callback directly. Reject parameters that do not fit into the fixed buffers in set_params(), and fix the advertised max fragment size: 128K never fitted into the 32K IRAM buffer. The caps values may have been carried over from the qdsp6 driver, which allocates its buffers according to the advertised maxima, unlike this driver. With 32K as max fragment size the advertised limits are self-consistent: 32K * 64 = 2M equals the DDR buffer size. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: cce1396936ef ("ASoC: sprd: Add Spreadtrum audio compress offload support") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/4386bc53631b052c1866a91061715b009d98b04f@linux.dev Signed-off-by: Mark Brown --- sound/soc/sprd/sprd-pcm-compress.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index a7d437b49fbf..e5249924b54d 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -17,7 +17,7 @@ /* Default values if userspace does not set */ #define SPRD_COMPR_MIN_FRAGMENT_SIZE SZ_8K -#define SPRD_COMPR_MAX_FRAGMENT_SIZE SZ_128K +#define SPRD_COMPR_MAX_FRAGMENT_SIZE SZ_32K #define SPRD_COMPR_MIN_NUM_FRAGMENTS 4 #define SPRD_COMPR_MAX_NUM_FRAGMENTS 64 @@ -271,6 +271,19 @@ static int sprd_platform_compr_set_params(struct snd_soc_component *component, struct sprd_compr_params compr_params = { }; int ret; + /* + * The stage 0 IRAM buffer and the stage 1 DDR buffer are allocated + * with fixed sizes at open time, so the requested fragment size and + * fragments must fit into them, otherwise sprd_platform_compr_copy() + * would overflow the buffers. Note the compress core only checks the + * fragment size and fragments against an u32 overflow, not against + * the buffer sizes advertised by get_caps. + */ + if (params->buffer.fragment_size > SPRD_COMPR_IRAM_BUF_SIZE || + (u64)params->buffer.fragment_size * params->buffer.fragments > + SPRD_COMPR_AREA_BUF_SIZE) + return -EINVAL; + /* * Configure the DMA engine 2-stage transfer mode. Channel 1 set as the * destination channel, and channel 0 set as the source channel, that From 93d88ac4a448f200d18d1d71de0074b8e716bbee Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Tue, 25 Aug 2026 10:35:56 +0800 Subject: [PATCH 0073/1317] sched_ext: Pass the initial cpu.idle state in scx_cgroup_init_args scx_cgroup_init_args carries the initial weight and bandwidth control parameters of a cgroup to ops.cgroup_init(), but not its cpu.idle state. A cgroup that was already configured idle before the scheduler was loaded (or before it was onlined under it) is presented as non-idle, and the BPF scheduler only learns about it if cpu.idle is written again later. Add the sched_idle state to scx_cgroup_init_args and fill it in all four places that build the args: scx_tg_online() for cgroups onlined under the scheduler, scx_cgroup_init() for cgroups that already exist when the scheduler is loaded, and the sub-scheduler handover paths scx_cgroup_claim_subtree() and scx_cgroup_return_subtree(). Verified in a VM with a probe scheduler printing the init args: a cgroup configured cpu.idle=1 before loading shows sched_idle=1 in ops.cgroup_init(), the default shows 0, and later cpu.idle writes still come through ops.cgroup_set_idle(). The sub-scheduler paths are compile-tested only. Fixes: 347ed2d566da ("sched/ext: Implement cgroup_set_idle() callback") Signed-off-by: Tao Cui Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 4 +++- kernel/sched/ext/internal.h | 3 +++ kernel/sched/ext/sub.c | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 713aa26b2828..901fb0f8b976 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4766,7 +4766,8 @@ int scx_tg_online(struct task_group *tg) { .weight = tg->scx.weight, .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, - .bw_burst_us = tg->scx.bw_burst_us }; + .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle }; ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, tg->css.cgroup, &args); @@ -5187,6 +5188,7 @@ static int scx_cgroup_init(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, css->cgroup, &args); diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 0967b99a4948..076a351bb3f2 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -259,6 +259,9 @@ struct scx_cgroup_init_args { u64 bw_period_us; u64 bw_quota_us; u64 bw_burst_us; + + /* whether the cgroup is configured SCHED_IDLE via cpu.idle */ + bool sched_idle; }; enum scx_cpu_preempt_reason { diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 0554448835bd..385302d19914 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -1361,6 +1361,7 @@ static s32 scx_cgroup_claim_subtree(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; if (tg->scx.sched != parent || @@ -1464,6 +1465,7 @@ static void scx_cgroup_return_subtree(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; /* the first pass must have transferred everything */ From ca12149896ed040dafef92eacd2af3f903afb177 Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Mon, 24 Aug 2026 02:27:29 +0000 Subject: [PATCH 0074/1317] regulator: dt-bindings: fan53555: add tcs,tcs4526 The driver has accepted tcs,tcs4526 since commit 5eee5eced95f ("regulator: fan53555: add tcs4526"), which added the compatible to both the of_device_id and i2c_device_id tables for the TCS4526, a chip that reports id 0 rather than the TCS4525's id 12. The binding was converted to DT schema afterwards, in commit 6cea468b680e ("regulator: dt-bindings: Convert Fairchild FAN53555 to DT schema"), and only picked up tcs,tcs4525. No in-tree DTS used the 4526 string at the time, so nothing flagged the omission. RK3399Pro boards use the TCS4526 for vdd_gpu and vdd_cpu_b, so a DTS describing them fails dtbs_check today even though the driver binds correctly. Add the missing compatible. Signed-off-by: Rudi Heitbaum Acked-by: Conor Dooley Link: https://patch.msgid.link/aousEdwMni9ScZBn@0d3a7a881997 Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml b/Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml index 9a18891f721e..b35b8f365b0f 100644 --- a/Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml +++ b/Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml @@ -23,6 +23,7 @@ properties: - silergy,syr827 - silergy,syr828 - tcs,tcs4525 + - tcs,tcs4526 - items: - const: rockchip,rk8601 - const: rockchip,rk8600 From 5f8cb07d7a8573753c5d31dc76b51cab8e5e9460 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 28 Aug 2026 10:31:29 +0800 Subject: [PATCH 0075/1317] arm64: dts: socfpga: change access permission from 755 to 644 These files have an incorrect access permission of 755 instead of 644. Change them to the correct access permission of 644. Fixes: 4bc04eb90b7c ("arm64: dts: socfpga: stratix10: Add emmc support") Cc: # v7.1+ Signed-off-by: Dinh Nguyen --- arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi | 0 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi mode change 100755 => 100644 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi b/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi old mode 100755 new mode 100644 diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts b/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts old mode 100755 new mode 100644 From b6b9e6d4abe87b16ab55990b887c6fad8e7a01af Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 28 Aug 2026 16:50:33 +0100 Subject: [PATCH 0076/1317] rust: pin-init: use irrefutable pattern for `stack_pin_init` In Rust 1.100.0, `Infallible` will become an alias of `!`. The let binding in `stack_pin_init` will thus become unreachable and produce an "unreachable expression" warning for the subsequent match, and thus will fail a `-Dwarnings` build. For this macro, all we need to know is that the error type is uninhabited, so replace this with an irrefutable pattern instead. [ The error looks like (dummy reproducer): error: unreachable expression --> rust/kernel/sync.rs:177:5 | 177 | pin_init::stack_pin_init!(let num = 42u32); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | unreachable expression | any code following this expression is unreachable | = note: `-D unreachable-code` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unreachable_code)]` = note: this error originates in the macro `pin_init::stack_pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) - Miguel ] Reported-by: Mohamad Alsadhan Closes: https://github.com/Rust-for-Linux/pin-init/pull/171 Signed-off-by: Gary Guo Cc: stable@vger.kernel.org # Needed in 7.1.y and later (for 6.12.y and 6.18.y a custom one is needed). Link: https://patch.msgid.link/20260828155033.2101924-1-gary@kernel.org [ Reworded for typos. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/pin-init/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index 7600cdbbbf98..f1463be9479d 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -490,13 +490,7 @@ macro_rules! stack_pin_init { (let $var:ident $(: $t:ty)? = $val:expr) => { let val = $val; let mut $var = ::core::pin::pin!($crate::__internal::StackInit$(::<$t>)?::uninit()); - let mut $var = match $crate::__internal::StackInit::init($var, val) { - Ok(res) => res, - Err(x) => { - let x: ::core::convert::Infallible = x; - match x {} - } - }; + let Ok(mut $var) = $crate::__internal::StackInit::init($var, val); }; } From e510334fbaeaa016ac76d80b4c5f47611c5f7860 Mon Sep 17 00:00:00 2001 From: Mehmet Koseoglu Date: Fri, 28 Aug 2026 04:50:20 +0300 Subject: [PATCH 0077/1317] rust: samples: add missing newlines in rust_print_main Calls to `pr_info!` in `arc_print` are missing trailing newlines, which are expected as the `pr_*!` documentation shows. Add the missing `\n` to all four formatting strings. Fixes: f431c5c581fa ("samples: rust: print: Add sample code for Arc printing") Fixes: 47cb6bf7860c ("rust: use derive(CoercePointee) on rustc >= 1.84.0") Suggested-by: Miguel Ojeda Link: https://github.com/Rust-for-Linux/linux/issues/1139 Signed-off-by: Mehmet Koseoglu Link: https://patch.msgid.link/20260828015148.221737-2-mehmet.mkoseoglu@gmail.com [ Reworded to fix the description of the missing-newline behavior. - Miguel ] Signed-off-by: Miguel Ojeda --- samples/rust/rust_print_main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/rust/rust_print_main.rs b/samples/rust/rust_print_main.rs index 682207c81fc2..01729e87d6b5 100644 --- a/samples/rust/rust_print_main.rs +++ b/samples/rust/rust_print_main.rs @@ -23,10 +23,10 @@ fn arc_print() -> Result { let b = UniqueArc::new("hello, world", GFP_KERNEL)?; // Prints the value of data in `a`. - pr_info!("{}", a); + pr_info!("{}\n", a); // Uses ":?" to print debug fmt of `b`. - pr_info!("{:?}", b); + pr_info!("{:?}\n", b); let a: Arc<&str> = b.into(); let c = a.clone(); @@ -42,7 +42,7 @@ fn arc_print() -> Result { use kernel::fmt::Display; fn arc_dyn_print(arc: &Arc) { - pr_info!("Arc says {arc}"); + pr_info!("Arc says {arc}\n"); } let a_i32_display: Arc = Arc::new(42i32, GFP_KERNEL)?; @@ -53,7 +53,7 @@ fn arc_dyn_print(arc: &Arc) { } // Pretty-prints the debug formatting with lower-case hexadecimal integers. - pr_info!("{:#x?}", a); + pr_info!("{:#x?}\n", a); Ok(()) } From 4825ef699cda4c6f2f0586b17a5e225560481da6 Mon Sep 17 00:00:00 2001 From: Yafang Shao Date: Sun, 16 Aug 2026 17:04:41 +0800 Subject: [PATCH 0078/1317] klp-build: Fix wrong index in funcs cleanup error path In the object allocation loop, when kzalloc() for funcs fails, the cleanup loop uses `objs[i].funcs` instead of `objs[j].funcs`. Since `objs[i].funcs` is still NULL at that point, it repeatedly calls kfree(NULL) and leaks all previously allocated funcs arrays. Fixes: 59adee07b568 ("livepatch/klp-build: Add stub init code for livepatch modules") Signed-off-by: Yafang Shao Acked-by: Song Liu Reviewed-by: Petr Mladek Acked-by: Miroslav Benes Link: https://patch.msgid.link/20260816090442.18128-2-laoar.shao@gmail.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c index f14d8c8fb35f..16aff8f736eb 100644 --- a/scripts/livepatch/init.c +++ b/scripts/livepatch/init.c @@ -51,7 +51,7 @@ static int __init livepatch_mod_init(void) if (!funcs) { ret = -ENOMEM; for (int j = 0; j < i; j++) - kfree(objs[i].funcs); + kfree(objs[j].funcs); goto err_free_objs; } From ac323c9467092479dc1e5bc138c9abbe015b0069 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 28 Aug 2026 10:48:50 -0700 Subject: [PATCH 0079/1317] objtool/klp: Fix checksums for constant pool references Adding a line of code to __link_shadow_page() with a literal string causes a false positive changed function with GCC: arch/x86/kvm/kvm.ko.o: changed function: kvm_tdp_mmu_map_private_pfn While the patch only touched __link_shadow_page(), the string addition triggered a rename of .LC64 -> .LC65 in kvm_tdp_mmu_map_private_pfn() even though the underlying referenced constant data didn't change. So for .LC* symbols, the suffix is arbitrary but the data isn't. Add the underlying data to the checksum calculation rather than the symbol name. Clang also uses .LC* symbols, but also uses anonymous data. Both compilers put this data in .rodata.cst sections. Fixes: 0d83da43b1e1 ("objtool/klp: Add --checksum option to generate per-function checksums") Link: https://patch.msgid.link/f3a9e74ceebc6475ce94bcfe985401140857814a.1787939301.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-checksum.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/objtool/klp-checksum.c b/tools/objtool/klp-checksum.c index b8e47f28997e..ebe25f9c5260 100644 --- a/tools/objtool/klp-checksum.c +++ b/tools/objtool/klp-checksum.c @@ -54,6 +54,19 @@ static int checksum_debug_init(struct objtool_file *file) return 0; } +/* + * Detect a reference to anonymous constant pool data which the compiler places + * in .rodata.cst and which either has an .LC symbol associated with + * it or (with Clang) no symbol at all. These are typically initializers for + * local function stack data, so they're considered part of the function rather + * than data per se. + */ +static bool is_anonymous_const_data(struct symbol *sym) +{ + return strstarts(sym->sec->name, ".rodata.cst") && + (is_sec_sym(sym) || strstarts(sym->name, ".LC")); +} + static void checksum_update_insn(struct objtool_file *file, struct symbol *func, struct instruction *insn) { @@ -129,6 +142,14 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, goto alts; } + if (is_anonymous_const_data(sym)) { + void *cst; + + cst = sym->sec->data->d_buf + sym->offset + offset; + __checksum_update_insn(func, insn, cst, sym->sec->sh.sh_entsize); + goto alts; + } + if (is_sec_sym(sym)) { sym = find_symbol_containing(reloc->sym->sec, offset); if (!sym) From bdc46e507b59ac44c8e1dab505121f18c2add1ab Mon Sep 17 00:00:00 2001 From: Xiaofeng Yuan Date: Mon, 31 Aug 2026 19:11:22 -0600 Subject: [PATCH 0080/1317] riscv: mm: make EXECMEM_KPROBES writable without CONFIG_STRICT_MODULE_RWX When CONFIG_STRICT_MODULE_RWX is not set, execmem cannot create temporary writable mappings for read-only executable pages. In this case, the execmem ranges must already have writable permissions. Currently EXECMEM_KPROBES unconditionally uses PAGE_KERNEL_READ_EXEC, which causes kprobe instruction slot writes to trigger page faults on systems where CONFIG_STRICT_MODULE_RWX is not enabled. Fix this by using PAGE_KERNEL_EXEC when CONFIG_STRICT_MODULE_RWX is not available. Signed-off-by: Xiaofeng Yuan Tested-by: Lad Prabhakar Reviewed-by: Nam Cao Link: https://patch.msgid.link/20260814082742.148403-2-xiaofengmian@163.com Signed-off-by: Paul Walmsley --- arch/riscv/mm/init.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c index f8994caefc70..fb37b0b67efe 100644 --- a/arch/riscv/mm/init.c +++ b/arch/riscv/mm/init.c @@ -1465,7 +1465,9 @@ struct execmem_info __init *execmem_arch_setup(void) [EXECMEM_KPROBES] = { .start = VMALLOC_START, .end = VMALLOC_END, - .pgprot = PAGE_KERNEL_READ_EXEC, + .pgprot = IS_ENABLED(CONFIG_STRICT_MODULE_RWX) ? + PAGE_KERNEL_READ_EXEC : + PAGE_KERNEL_EXEC, .alignment = 1, }, [EXECMEM_BPF] = { From 8718e5a3090bbfd759801d088b700aab21e8e989 Mon Sep 17 00:00:00 2001 From: Xiaofeng Yuan Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0081/1317] riscv: patch: skip fixmap mapping when kernel text is already writable patch_map() always creates a temporary writable mapping via fixmap for kernel text addresses, even when CONFIG_STRICT_KERNEL_RWX is disabled and the kernel text is already mapped with _PAGE_WRITE. This is unnecessary overhead at best, and on minimal configurations it can cause page faults. Skip the fixmap path for kernel text when CONFIG_STRICT_KERNEL_RWX is not enabled, since the text pages are already writable in that case. The module text path is already gated on CONFIG_STRICT_MODULE_RWX and is kept unchanged. Reported-by: Klara Modin Closes: https://lore.kernel.org/all/ant_8TaBbov_GS4i@soda.int.kasm.eu/ Reported-by: Lad Prabhakar Closes: https://lore.kernel.org/all/CA+V-a8tQK8rih9SGGTyqrEBGpNkx4H0eX2YccCRrgkVAPr+EBg@mail.gmail.com/ Tested-by: Klara Modin Tested-by: Lad Prabhakar Link: https://patch.msgid.link/20260814082742.148403-3-xiaofengmian@163.com Signed-off-by: Paul Walmsley --- arch/riscv/kernel/patch.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/kernel/patch.c b/arch/riscv/kernel/patch.c index 16b243376f36..2239c28981bc 100644 --- a/arch/riscv/kernel/patch.c +++ b/arch/riscv/kernel/patch.c @@ -45,6 +45,8 @@ static __always_inline void *patch_map(void *addr, const unsigned int fixmap) phys_addr_t phys; if (core_kernel_text(uintaddr) || is_kernel_exittext(uintaddr)) { + if (!IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) + return addr; phys = __pa_symbol(addr); } else if (IS_ENABLED(CONFIG_STRICT_MODULE_RWX)) { struct page *page = vmalloc_to_page(addr); From 2d2184ac90365a4af3274e23c98d469f09f91749 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0082/1317] Revert "riscv: Reset pmm when PR_TAGGED_ADDR_ENABLE is not set" This reverts commit 3033b2b1e3949274f33a140e2a97571b5a307298. The reverted patch is userspace-visible behavior change, not a bug fix. The two variables here (pmm and pmlen) control two independent features: pmm is the _hardware_ pointer masking mode that applies while executing in userspace. pmlen is the shift amount that the _kernel_ uses when untagging addresses; PMLEN_0 means no untagging occurs, so the kernel does not accept tagged addresses in syscall arguments. It is valid (as documented and tested by the self test) to enable pointer masking without enabling the tagged address ABI. This separation is necessary to allow userspace to create an execution environment similar to what the kernel supports on arm64 by default, where TBI is enabled but the tagged address ABI is not. (On arm64, there is no equivalent to PR_PMLEN_MASK because TBI is always enabled.) Signed-off-by: Samuel Holland Link: https://patch.msgid.link/20260820014551.1979772-1-samuel.holland@sifive.com Cc: stable@vger.kernel.org Fixes: 3033b2b1e394 ("riscv: Reset pmm when PR_TAGGED_ADDR_ENABLE is not set") Signed-off-by: Paul Walmsley --- arch/riscv/kernel/process.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c index b2df7f72241a..7cc5a6a5c020 100644 --- a/arch/riscv/kernel/process.c +++ b/arch/riscv/kernel/process.c @@ -349,10 +349,8 @@ long set_tagged_addr_ctrl(struct task_struct *task, unsigned long arg) if (arg & PR_TAGGED_ADDR_ENABLE && (tagged_addr_disabled || !pmlen)) return -EINVAL; - if (!(arg & PR_TAGGED_ADDR_ENABLE)) { + if (!(arg & PR_TAGGED_ADDR_ENABLE)) pmlen = PMLEN_0; - pmm = ENVCFG_PMM_PMLEN_0; - } if (mmap_write_lock_killable(mm)) return -EINTR; From 12381af01024f4a59cd3f673fb3644bbe2ca3aad Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0083/1317] riscv: use string helper in setup_global_riscv_enable() Prefer the convenient string choice 'str_disabled_enabled()' helper over hardcoded strings in 'setup_global_riscv_enable()'. Signed-off-by: Dmitry Antipov Link: https://patch.msgid.link/20260819160546.3219942-1-dmantipov@yandex.ru Signed-off-by: Paul Walmsley --- arch/riscv/kernel/usercfi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/riscv/kernel/usercfi.c b/arch/riscv/kernel/usercfi.c index f027e6e05251..dec0ba5eff5e 100644 --- a/arch/riscv/kernel/usercfi.c +++ b/arch/riscv/kernel/usercfi.c @@ -525,9 +525,8 @@ static int __init setup_global_riscv_enable(char *str) if (riscv_nousercfi) pr_info("RISC-V user CFI disabled via cmdline - shadow stack status : %s, landing pad status : %s\n", - (riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_BCFI) ? "disabled" : - "enabled", (riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_FCFI) ? - "disabled" : "enabled"); + str_disabled_enabled(riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_BCFI), + str_disabled_enabled(riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_FCFI)); return 1; } From d0fc6fab20460add1f27402cd8b945d094a56b21 Mon Sep 17 00:00:00 2001 From: Andy Chiu Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0084/1317] riscv: hwprobe: initialize pair->value in hwprobe_one_pair() The vendor-extension handlers reached from hwprobe_one_pair() (hwprobe_isa_vendor_ext_thead_0() and friends) only OR the present bits into pair->value via VENDOR_EXTENSION_SUPPORTED() and clear their own missing bits; they assume the caller has already zeroed pair->value. That holds for hwprobe_get_values() (it zeroes each pair) and hwprobe_get_cpus() (it re-initializes its scratch pair per key), but not for complete_hwprobe_vdso_data(), which reuses a single pair across all keys without re-zeroing. A vendor key therefore inherits stale bits from the previously probed key, and the wrong value is cached in the vDSO all_cpu_hwprobe_values[] and handed to userspace on the fast patih. Zero pair->value once at the top of hwprobe_one_pair() so every handler starts from a clean value regardless of the caller, and drop the now redundant zeroing in the *_BLOCK_SIZE cases. hwprobe_isa_ext0() keeps its own zeroing because hwprobe_ext0_has() calls it directly, bypassing hwprobe_one_pair(). Fixes: a5ea53da65c5 ("riscv: hwprobe: Add thead vendor extension probing") Signed-off-by: Andy Chiu Reviewed-by: Jesse Taube Link: https://patch.msgid.link/20260725001614.2578617-2-tchiu@tenstorrent.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/kernel/sys_hwprobe.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c index bd6ca7d769da..7818e1d32622 100644 --- a/arch/riscv/kernel/sys_hwprobe.c +++ b/arch/riscv/kernel/sys_hwprobe.c @@ -297,6 +297,8 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus) static void hwprobe_one_pair(struct riscv_hwprobe *pair, const struct cpumask *cpus) { + pair->value = 0; + switch (pair->key) { case RISCV_HWPROBE_KEY_MVENDORID: case RISCV_HWPROBE_KEY_MARCHID: @@ -331,17 +333,14 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, break; case RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOZ)) pair->value = riscv_cboz_block_size; break; case RISCV_HWPROBE_KEY_ZICBOM_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOM)) pair->value = riscv_cbom_block_size; break; case RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOP)) pair->value = riscv_cbop_block_size; break; From ddeaa39406c4cf680643412cd1f75bb98a641f6c Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0085/1317] riscv: bug: Make RV32 use GENERIC_BUG_RELATIVE_POINTERS x86 did this in commit b0a848f4a47a ("x86/bugs: Make i386 use GENERIC_BUG_RELATIVE_POINTERS") powerpc did this in commit 1baa1f70ef77 ("powerpc: Allow relative pointers in bug table entries") Similar as x86 and powerpc does, make RV32 use GENERIC_BUG_RELATIVE_POINTERS for "there is only one code path." and "less #ifdef is more better". Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260221024255.3552-1-jszhang@kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig | 2 +- arch/riscv/include/asm/bug.h | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index f8e26c4bed2b..d6c2dbf8455c 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -326,7 +326,7 @@ config STACKTRACE_SUPPORT config GENERIC_BUG def_bool y depends on BUG - select GENERIC_BUG_RELATIVE_POINTERS if 64BIT + select GENERIC_BUG_RELATIVE_POINTERS config GENERIC_BUG_RELATIVE_POINTERS bool diff --git a/arch/riscv/include/asm/bug.h b/arch/riscv/include/asm/bug.h index 6f581b84d8fc..699c0cf3e4ef 100644 --- a/arch/riscv/include/asm/bug.h +++ b/arch/riscv/include/asm/bug.h @@ -29,13 +29,8 @@ typedef u32 bug_insn_t; -#ifdef CONFIG_GENERIC_BUG_RELATIVE_POINTERS #define __BUG_ENTRY_ADDR RISCV_INT " 1b - ." #define __BUG_ENTRY_FILE(file) RISCV_INT " " file " - ." -#else -#define __BUG_ENTRY_ADDR RISCV_PTR " 1b" -#define __BUG_ENTRY_FILE(file) RISCV_PTR " " file -#endif #ifdef CONFIG_DEBUG_BUGVERBOSE #define __BUG_ENTRY(file, line, flags) \ From 6693171c8c540b3a54e671992f0561613076fc3b Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 18 Aug 2026 17:10:00 +0800 Subject: [PATCH 0086/1317] perf: RISC-V: use BIT_ULL for u64 overflow masks Overflow status and restart masks are u64, but bits were built with BIT(). On RV32 that is an unsigned long shift, so indices >= 32 truncate or wrap and corrupt the mask. Use BIT_ULL() for those u64 bitops. Fixes: a8625217a054 ("drivers/perf: riscv: Implement SBI PMU snapshot function") Assisted-by: DeepSeek:deepseek-v3 Signed-off-by: Xixin Liu Link: https://patch.msgid.link/prpmask01bitul.v2.1786434000.git.liuxixin@kylinos.cn Cc: stable@vger.kernel.org [pjw@kernel.org: updated to apply] Signed-off-by: Paul Walmsley --- drivers/perf/riscv_pmu_sbi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c index 50220f7b46d9..8ea5ae617347 100644 --- a/drivers/perf/riscv_pmu_sbi.c +++ b/drivers/perf/riscv_pmu_sbi.c @@ -1002,7 +1002,7 @@ static inline void pmu_sbi_start_ovf_ctrs_snapshot(struct cpu_hw_events *cpu_hw_ struct riscv_pmu_snapshot_data *sdata = cpu_hw_evt->snapshot_addr; for_each_set_bit(idx, cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS) { - if (ctr_ovf_mask & BIT(idx)) { + if (ctr_ovf_mask & BIT_ULL(idx)) { event = cpu_hw_evt->events[idx]; hwc = &event->hw; max_period = riscv_pmu_ctr_get_width_mask(event); @@ -1109,14 +1109,14 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev) hidx = info->csr - CSR_CYCLE; /* check if the corresponding bit is set in scountovf or overflow mask in shmem */ - if (!(overflow & BIT(hidx))) + if (!(overflow & BIT_ULL(hidx))) continue; /* * Keep a track of overflowed counters so that they can be started * with updated initial value. */ - overflowed_ctrs |= BIT(lidx); + overflowed_ctrs |= BIT_ULL(lidx); hw_evt = &event->hw; /* Update the event states here so that we know the state while reading */ hw_evt->state |= PERF_HES_STOPPED; From 32cd87f54dd1070020e664ccb0312a9f0fea79b4 Mon Sep 17 00:00:00 2001 From: Guoqing Jiang Date: Thu, 27 Aug 2026 20:55:53 +0800 Subject: [PATCH 0087/1317] RDMA/siw: Clear association under lock if siw_qp_modify fails in siw_accept We need to clear cep before release state_lock as siw_qp_llp_close and siw_qp_modify->siw_qp_llp_close did. Otherwise if siw_qp_modify() fails in siw_accept(), the QP's state_lock is released before the error path cleanup. A concurrent ibv_modify_qp() transitioning the QP to ERROR can race in this window: siw_accept() ibv_modify_qp(ERROR) ---------------------- ---------------------- siw_qp_modify() fails up_write(&qp->state_lock) down_write(&qp->state_lock) nextstate_from_idle(): if (qp->cep) siw_cep_put(qp->cep) <- frees cep qp->cep = NULL goto error cep->qp = NULL <- UAF Clear qp->cep and drop the association reference taken by siw_cep_get(), all under the write lock held from the initial down_write(&qp->state_lock). Thread B therefore sees qp->cep == NULL, skips its own put, and cannot free the cep before siw_accept() is done with it. Fixes: 6c52fdc244b5 ("rdma/siw: connection management") Reported-by: Shuangpeng Bai Link: https://lore.kernel.org/linux-rdma/d6fbe475-a5c2-f975-99b0-a0bd6b6d10e8@linux.dev/T/#m5876c1ff2de8686a9a1173b8f1aa0ff5363a785c Signed-off-by: Guoqing Jiang Link: https://patch.msgid.link/20260827125553.12831-1-guoqing.jiang@linux.dev Acked-by: Bernard Metzler Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/siw/siw_cm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/sw/siw/siw_cm.c b/drivers/infiniband/sw/siw/siw_cm.c index 0245b25e7271..ed49818793dd 100644 --- a/drivers/infiniband/sw/siw/siw_cm.c +++ b/drivers/infiniband/sw/siw/siw_cm.c @@ -1719,9 +1719,12 @@ int siw_accept(struct iw_cm_id *id, struct iw_cm_conn_param *params) SIW_QP_ATTR_STATE | SIW_QP_ATTR_LLP_HANDLE | SIW_QP_ATTR_ORD | SIW_QP_ATTR_IRD | SIW_QP_ATTR_MPA); + if (rv) { + qp->cep = NULL; + siw_cep_put(cep); + goto error_unlock; + } up_write(&qp->state_lock); - if (rv) - goto error; siw_dbg_cep(cep, "[QP %u]: send mpa reply, %d byte pdata\n", qp_id(qp), params->private_data_len); From ae36a5b609ae79f4de966328b78d2584be9719a4 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Thu, 27 Aug 2026 19:18:07 +0200 Subject: [PATCH 0088/1317] RDMA/rxe: validate access flags before swapping the MR's PD rxe_rereg_user_mr() reassigns mr->ibmr.pd first and only then validates the IB_MR_REREG_ACCESS argument: if (flags & IB_MR_REREG_PD) { rxe_put(old_pd); rxe_get(pd); mr->ibmr.pd = ibpd; } if (flags & IB_MR_REREG_ACCESS) { if (access & ~RXE_ACCESS_SUPPORTED_MR) return ERR_PTR(-EOPNOTSUPP); mr->access = access; } Both flags pass the entry check because RXE_MR_REREG_SUPPORTED is IB_MR_REREG_PD | IB_MR_REREG_ACCESS, so a caller can reach the access check with mr->ibmr.pd already reassigned. mr->ibmr.pd is owned by the core, which adjusts pd->usecnt only on the success path: ib_uverbs_rereg_mr() jumps to put_new_uobj on a driver error without undoing the reassignment, so mr->pd == new_pd while the usecnts still charge the MR to orig_pd. ib_dereg_mr_user() then decrements new_pd, whose count can reach zero while a memory window still references it; uverbs_free_pd() frees the PD on that count alone and rxe_mw_cleanup() writes to freed memory: BUG: KASAN: slab-use-after-free in __rxe_put+0x31/0xa0 Write of size 4 at addr ffff8881301dd690 by task rxe_poc/591 __rxe_put+0x31/0xa0 rxe_mw_cleanup+0x42/0x200 __rxe_cleanup+0x115/0x370 rxe_dealloc_mw+0x4c/0x80 Allocated by task 591: ib_uverbs_alloc_pd+0x258/0x540 Freed by task 591: ib_dealloc_pd_user+0x174/0x210 uverbs_free_pd+0x8d/0xc0 ib_uverbs_dealloc_pd+0x18e/0x1d0 Validate the access flags before mutating any state so the callback either applies every requested change or none. Fixes: 544c7f62cf32 ("RDMA/rxe: Implement rereg_user_mr") Signed-off-by: Norbert Szetei Link: https://patch.msgid.link/46E1D5C0-24BE-4D01-BDB3-634FE09B22C5@doyensec.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_verbs.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.c b/drivers/infiniband/sw/rxe/rxe_verbs.c index 96c7716057fe..3864284522eb 100644 --- a/drivers/infiniband/sw/rxe/rxe_verbs.c +++ b/drivers/infiniband/sw/rxe/rxe_verbs.c @@ -1331,19 +1331,20 @@ static struct ib_mr *rxe_rereg_user_mr(struct ib_mr *ibmr, int flags, if (err) return ERR_PTR(err); + if ((flags & IB_MR_REREG_ACCESS) && + (access & ~RXE_ACCESS_SUPPORTED_MR)) { + rxe_err_mr(mr, "access = %#x not supported\n", access); + return ERR_PTR(-EOPNOTSUPP); + } + if (flags & IB_MR_REREG_PD) { rxe_put(old_pd); rxe_get(pd); mr->ibmr.pd = ibpd; } - if (flags & IB_MR_REREG_ACCESS) { - if (access & ~RXE_ACCESS_SUPPORTED_MR) { - rxe_err_mr(mr, "access = %#x not supported\n", access); - return ERR_PTR(-EOPNOTSUPP); - } + if (flags & IB_MR_REREG_ACCESS) mr->access = access; - } return NULL; } From d10e2a08799e858d3e71ea4169bcd018f216d444 Mon Sep 17 00:00:00 2001 From: Gang Yan Date: Fri, 14 Aug 2026 17:37:40 +0800 Subject: [PATCH 0089/1317] RDMA/rxe: Fix integer overflow in mr_check_range() leading to OOB access mr_check_range() validates that [iova, iova+length) falls within the registered MR range using wraparound-prone arithmetic: if (iova < mr->ibmr.iova || iova + length > mr->ibmr.iova + mr->ibmr.length) A remote peer can craft an RDMA-Write/Read RETH so that iova + length wraps to 0 (e.g. iova=0xfffffffffffffff8, length=8), bypassing the check. rxe_mr_iova_to_index() then computes a huge index (int idx, only guarded by WARN_ON) and rxe_mr_copy_xarray() dereferences mr->page_info[huge], causing an out-of-bounds read/write and a kernel oops that is triggerable by an unauthenticated remote peer. Rewrite the check in overflow-safe form; the first two clauses guarantee that the subsequent subtractions do not underflow: if (iova < mr->ibmr.iova || length > mr->ibmr.length || iova - mr->ibmr.iova > mr->ibmr.length - length) With the fix, mr_check_range() returns -EINVAL for the crafted iova and the responder reports REMOTE_ACCESS_ERROR instead of triggering the OOB. Fixes: 8700e3e7c485 ("Soft RoCE driver") Signed-off-by: Gang Yan Link: https://patch.msgid.link/20260814093740.292954-1-gang.yan@linux.dev Reviewed-by: Zhu Yanjun Reviewed-by: Shukai Ni Tested-by: Shukai Ni Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_mr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/sw/rxe/rxe_mr.c b/drivers/infiniband/sw/rxe/rxe_mr.c index 875eceb55fdf..71d9ea477289 100644 --- a/drivers/infiniband/sw/rxe/rxe_mr.c +++ b/drivers/infiniband/sw/rxe/rxe_mr.c @@ -33,7 +33,8 @@ int mr_check_range(struct rxe_mr *mr, u64 iova, size_t length) case IB_MR_TYPE_USER: case IB_MR_TYPE_MEM_REG: if (iova < mr->ibmr.iova || - iova + length > mr->ibmr.iova + mr->ibmr.length) { + length > mr->ibmr.length || + iova - mr->ibmr.iova > mr->ibmr.length - length) { rxe_dbg_mr(mr, "iova/length out of range\n"); return -EINVAL; } From 9fa903b24b1f46b4ff5443bcd4aca23e5c57f9c1 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Wed, 26 Aug 2026 16:17:45 -0400 Subject: [PATCH 0090/1317] xfrm: hold net_device reference under RCU in bundle creation xfrm_bundle_create() and xfrm_create_dummy_bundle() read dst->dev into a local pointer without taking a device reference, then pass it to xfrm_fill_dst(). A concurrent RTM_DELLINK replaces dst->dev via dst_dev_put() and frees the old net_device, causing a use-after-free when xfrm6_fill_dst() later dereferences the stale dev pointer. BUG: KASAN: slab-use-after-free in xfrm6_fill_dst+0x82c/0x860 (net/ipv6/xfrm6_policy.c:86 netdev_hold()) Read of size 8 at addr ffff8880142fe588 by task exploit/153 Call Trace: xfrm6_fill_dst+0x82c/0x860 xfrm_resolve_and_create_bundle+0x21d4/0x2bd0 xfrm_lookup_with_ifid+0x485/0x1640 ip6_dst_lookup_flow+0x19b/0x1e0 udpv6_sendmsg+0x1443/0x2dd0 Fix this by reading dst->dev via dst_dev_rcu() and keeping the RCU read-side critical section active until xfrm_fill_dst() has taken the required device references. Fixes: 25ee3286dcbc ("[IPSEC]: Merge common code into xfrm_bundle_create") Fixes: a0073fe18e71 ("xfrm: Add a state resolution packet queue") Suggested-by: Steffen Klassert Reported-by: Xiang Mei (Microsoft) Link: https://lore.kernel.org/all/20260820200245.44312-1-blbllhy@gmail.com/ Cc: AutonomousCodeSecurity@microsoft.com Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_policy.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index 932a313b9460..513c9f228334 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -2770,9 +2770,12 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, xdst0->path = dst; err = -ENODEV; - dev = dst->dev; - if (!dev) + rcu_read_lock(); + dev = dst_dev_rcu(dst); + if (!dev) { + rcu_read_unlock(); goto free_dst; + } xfrm_init_path(xdst0, dst, nfheader_len); xfrm_init_pmtu(bundle, nx); @@ -2780,8 +2783,10 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, for (xdst_prev = xdst0; xdst_prev != (struct xfrm_dst *)dst; xdst_prev = (struct xfrm_dst *) xfrm_dst_child(&xdst_prev->u.dst)) { err = xfrm_fill_dst(xdst_prev, dev, fl); - if (err) + if (err) { + rcu_read_unlock(); goto free_dst; + } xdst_prev->u.dst.header_len = header_len; xdst_prev->u.dst.trailer_len = trailer_len; @@ -2789,6 +2794,7 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, trailer_len -= xdst_prev->u.dst.xfrm->props.trailer_len; } + rcu_read_unlock(); return &xdst0->u.dst; put_states: @@ -3058,11 +3064,15 @@ static struct xfrm_dst *xfrm_create_dummy_bundle(struct net *net, xfrm_init_path((struct xfrm_dst *)dst1, dst, 0); err = -ENODEV; - dev = dst->dev; - if (!dev) + rcu_read_lock(); + dev = dst_dev_rcu(dst); + if (!dev) { + rcu_read_unlock(); goto free_dst; + } err = xfrm_fill_dst(xdst, dev, fl); + rcu_read_unlock(); if (err) goto free_dst; From 3cf5cdecd99c9c186a5ea518d93bbf3045b6e3aa Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 29 Aug 2026 17:24:24 +0800 Subject: [PATCH 0091/1317] xfrm: save input state data before secpath resets xfrm_input() stores the current xfrm_state in the skb secpath while it continues receive-side processing. Some input paths can reset that secpath before xfrm_input() has finished dereferencing the state. Receive callback users such as VTI and XFRM interfaces can reset the secpath. The VTI receive path does so before checking whether the packet crosses network namespaces, while the XFRM interface path does so only for cross-network-namespace packets. The XFRM_MAX_DEPTH error path can also reset the secpath before the final drop callback reports the current state's protocol. If secpath_reset() drops the last state reference while the state is concurrently deleted, xfrm_input() can still dereference the freed state when selecting transport_finish() or reporting the drop callback protocol. Save the state protocol on the stack while the state is still valid, and use the already saved address family for transport_finish(). A larval XFRM_STATE_ACQ state has no type, so retain nexthdr as its protocol. This preserves the existing drop-path fallback while avoiding the post-reset state dereferences without adding an extra state reference to every received packet. Fixes: df3893c176e9 ("vti: Update the ipv4 side to use it's own receive hook.") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_input.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c index 8f6109eada7e..5ed87d51392a 100644 --- a/net/xfrm/xfrm_input.c +++ b/net/xfrm/xfrm_input.c @@ -474,6 +474,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) struct xfrm_state *x = NULL; xfrm_address_t *daddr; u32 mark = skb->mark; + u8 xfrm_proto = nexthdr; unsigned int family = AF_UNSPEC; int decaps = 0; int async = 0; @@ -485,6 +486,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) if (encap_type < 0 || (xo && (xo->flags & XFRM_GRO || encap_type == 0 || encap_type == UDP_ENCAP_ESPINUDP))) { x = xfrm_input_state(skb); + xfrm_proto = x->type ? x->type->proto : nexthdr; if (unlikely(x->km.state != XFRM_STATE_VALID)) { if (x->km.state == XFRM_STATE_ACQ) @@ -592,11 +594,13 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) x = xfrm_input_state_lookup(net, mark, daddr, spi, nexthdr, family); if (x == NULL) { + xfrm_proto = nexthdr; secpath_reset(skb); XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES); xfrm_audit_state_notfound(skb, family, spi, seq); goto drop; } + xfrm_proto = x->type ? x->type->proto : nexthdr; if (unlikely(x->dir && x->dir != XFRM_SA_DIR_IN)) { secpath_reset(skb); @@ -604,6 +608,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) xfrm_audit_state_notfound(skb, family, spi, seq); xfrm_state_put(x); x = NULL; + xfrm_proto = nexthdr; goto drop; } @@ -728,7 +733,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) } while (!err); rcu_read_lock(); - err = xfrm_rcv_cb(skb, family, x->type->proto, 0); + err = xfrm_rcv_cb(skb, family, xfrm_proto, 0); if (err) { rcu_read_unlock(); goto drop; @@ -753,7 +758,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) xfrm_gro = xo->flags & XFRM_GRO; err = -EAFNOSUPPORT; - afinfo = xfrm_state_afinfo_get_rcu(x->props.family); + afinfo = xfrm_state_afinfo_get_rcu(family); if (likely(afinfo)) err = afinfo->transport_finish(skb, xfrm_gro || async); if (xfrm_gro) { @@ -776,7 +781,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) drop: if (async) dev_put(dev); - xfrm_rcv_cb(skb, family, x && x->type ? x->type->proto : nexthdr, -1); + xfrm_rcv_cb(skb, family, xfrm_proto, -1); kfree_skb(skb); return 0; } From 96f01b53c2d05e003b040892256de54a586e8529 Mon Sep 17 00:00:00 2001 From: Wyatt Feng Date: Sat, 29 Aug 2026 23:44:32 +0800 Subject: [PATCH 0092/1317] net: xfrm: reject unrepresentable espintcp transport headers ESP-in-TCP can hand xfrm packets whose transport header offset no longer fits after the stream parser trims the TCP envelope. The plain transport header reset truncates that offset and triggers the skb warning path. Use the careful transport-header helper and drop the skb through the existing XFRM error path when the offset cannot be represented. Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:GPT-5.4 Signed-off-by: Wyatt Feng Signed-off-by: Ren Wei Signed-off-by: Steffen Klassert --- net/xfrm/espintcp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c index 674aedc5af5a..3e72b9f067b9 100644 --- a/net/xfrm/espintcp.c +++ b/net/xfrm/espintcp.c @@ -30,7 +30,11 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk) { struct tcp_skb_cb *tcp_cb = (struct tcp_skb_cb *)skb->cb; - skb_reset_transport_header(skb); + if (!skb_reset_transport_header_careful(skb)) { + XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR); + kfree_skb(skb); + return; + } /* restore IP CB, we need at least IP6CB->nhoff */ memmove(skb->cb, &tcp_cb->header, sizeof(tcp_cb->header)); From 65320b642025c53063372bd34a376bec7bf15598 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0093/1317] firmware: arm_scpi: fix device_node leak in scpi_dev_domain_id of_parse_phandle_with_args() takes a reference on clkspec.np that must be released with of_node_put(). scpi_dev_domain_id() returned clkspec.args[0] without dropping that reference, so every domain lookup leaked a device node. Paths such as scpi_dvfs_info() / cpufreq init call this per CPU, so the leak accumulates over time. Save the domain id, of_node_put(clkspec.np), then return the saved value. Signed-off-by: Xixin Liu Link: https://patch.msgid.link/84fdd490495b.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 2acad5fa5a28..43d6d9bbc7a9 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -661,12 +661,15 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) static int scpi_dev_domain_id(struct device *dev) { struct of_phandle_args clkspec; + int domain; if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells", 0, &clkspec)) return -EINVAL; - return clkspec.args[0]; + domain = clkspec.args[0]; + of_node_put(clkspec.np); + return domain; } static struct scpi_dvfs_info *scpi_dvfs_info(struct device *dev) From 32471d84a487c7fd74532bc96be56f8028cf4a3f Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0094/1317] firmware: arm_scpi: reject DVFS OPP count above MAX_DVFS_OPPS scpi_dvfs_get_info() already rejected a zero opp_count, but still trusted any larger value from the SCP firmware. The shared-memory reply only holds MAX_DVFS_OPPS entries in buf.opps[]; a bigger count over-reads that array and then sizes the allocated OPP table incorrectly (garbage OPPs / OOB). The missing upper bound dates back to the original SCPI DVFS support. Reject zero and out-of-range counts in one check and return -EINVAL. Fixes: 8cb7cf56c9fe ("firmware: add support for ARM System Control and Power Interface(SCPI) protocol") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/022802f0b38f.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 43d6d9bbc7a9..68a730d22037 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -631,8 +631,8 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) if (ret) return ERR_PTR(ret); - if (!buf.opp_count) - return ERR_PTR(-ENOENT); + if (!buf.opp_count || buf.opp_count > MAX_DVFS_OPPS) + return ERR_PTR(-EINVAL); info = kmalloc_obj(*info); if (!info) From 70f4b78d560e592cbf3325b162424737d032fc1d Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0095/1317] clk: scpi: bound-check DVFS index in scpi_dvfs_recalc_rate dvfs_get_idx() may return an out-of-range index if the SCP firmware is buggy or returns a stale value. Only negative indexes were rejected, so a large index walked past info->opps and could treat garbage as a clock rate (KASAN OOB / wrong frequency to consumers). The missing upper bound dates back to the original SCPI clock driver. Treat indexes >= opp count as invalid and return 0, same as idx < 0. Fixes: cd52c2a4b5c4 ("clk: add support for clocks provided by SCP(System Control Processor)") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/04f9ab766e07.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index 2328d2abf6d8..fa4349c8178a 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -73,7 +73,7 @@ static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw, int idx = clk->scpi_ops->dvfs_get_idx(clk->id); const struct scpi_opp *opp; - if (idx < 0) + if (idx < 0 || idx >= clk->info->count) return 0; opp = clk->info->opps + idx; From ab06cf8152dace327cd873188e4a036c4e0b5944 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0096/1317] clk: scpi: register scpi-cpufreq once and clear on failure scpi_clocks_probe() walks clock children and, for each DVFS provider, calls platform_device_register_simple("scpi-cpufreq", -1, ...). Two related bugs: Since all DVFS providers register the fixed scpi-cpufreq device using PLATFORM_DEVID_NONE, a second registration fails with -EEXIST and overwrites the pointer to the successfully registered device. The first device can then no longer be unregistered. Register the virtual device only once. If registration fails, reset the pointer to NULL so a subsequent DVFS provider can retry and the global pointer only represents a successfully registered device. Fixes: 9490f01e2471 ("clk: scpi: add support for cpufreq virtual device") Fixes: 67bcc2c5f1da ("clk: scpi: don't add cpufreq device if the scpi dvfs node is disabled") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/fd1b9199a9c3.v2.1785200642.git.liuxixin@kylinos.cn (sudeep.holla: reworded the commit message to improve readability) Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index fa4349c8178a..f8182175b483 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -272,10 +272,14 @@ static int scpi_clocks_probe(struct platform_device *pdev) if (match->data != &scpi_dvfs_ops) continue; /* Add the virtual cpufreq device if it's DVFS clock provider */ + if (cpufreq_dev) + continue; cpufreq_dev = platform_device_register_simple("scpi-cpufreq", -1, NULL, 0); - if (IS_ERR(cpufreq_dev)) + if (IS_ERR(cpufreq_dev)) { pr_warn("unable to register cpufreq device"); + cpufreq_dev = NULL; + } } return 0; } From 86d923a882f48b047b079a293abb6ebca3eaf23f Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 09:27:31 +0800 Subject: [PATCH 0097/1317] clk: scpi: use PLATFORM_DEVID_NONE for scpi-cpufreq Replace the magic -1 passed to platform_device_register_simple() with PLATFORM_DEVID_NONE. This is a readability cleanup only and does not change behavior. Signed-off-by: Xixin Liu Link: https://patch.msgid.link/fb51cfbfbb41.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index f8182175b483..b2d412ca1d22 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -275,7 +275,8 @@ static int scpi_clocks_probe(struct platform_device *pdev) if (cpufreq_dev) continue; cpufreq_dev = platform_device_register_simple("scpi-cpufreq", - -1, NULL, 0); + PLATFORM_DEVID_NONE, + NULL, 0); if (IS_ERR(cpufreq_dev)) { pr_warn("unable to register cpufreq device"); cpufreq_dev = NULL; From 769001ce838d907ecaa95f1d0a4e8fc86f761f9f Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 26 Jul 2026 19:15:30 +0800 Subject: [PATCH 0098/1317] RDMA/rxe: Restore HMM_PFN_WRITE check in ODP write paths Commit 0b261d7c1cd3 ("RDMA/rxe: Break endless pagefault loop for RO pages") dropped the access permission test from rxe_check_pagefault() and left only HMM_PFN_VALID. A page faulted in read-only, for example a page-cache folio behind a PROT_READ file mapping, then satisfies the check and ODP write operations (RDMA WRITE, RDMA READ response, SEND payload, atomics) modify it through kmap without ever breaking CoW. An unprivileged user can register an ODP MR over such a mapping and have incoming RDMA traffic overwrite the page cache of a file it only holds O_RDONLY, including /etc/passwd or setuid binaries. This is the same primitive class as Dirty COW and CVE-2022-2590. mlx5 has the missing invariant: its ODP path sets the device write bit only for pfns that carry HMM_PFN_WRITE. Restore it in rxe by requiring HMM_PFN_WRITE in rxe_check_pagefault() for every operation except RXE_PAGEFAULT_RDONLY. A write to a non-writable VMA now fails the one fault attempt with -EPERM from hmm_vma_fault() instead of re-faulting forever. For a writable VMA the fault breaks CoW and the write lands in the private page. Keep pmem flushes on the read-only check. arch_wb_cache_pmem() never modifies memory, and the FLUSH access bits do not make the umem writable, so classifying flushes as writes would make every flush against a flush-only MR fail. Fixes: 0b261d7c1cd3 ("RDMA/rxe: Break endless pagefault loop for RO pages") Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260726111533.1037819-1-bestswngs@gmail.com Reviewed-by: Zhu Yanjun Tested-by: Hongqiang Luo Tested-by: Xinyu Ma Tested-by: Zhanbo Ye Reported-by: Weiming Shi Reported-by: Shaomin Chen Reported-by: Rui Ding Reported-by: Miao Zhao Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_odp.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_odp.c b/drivers/infiniband/sw/rxe/rxe_odp.c index e870efa7a0a3..ab21b620e94c 100644 --- a/drivers/infiniband/sw/rxe/rxe_odp.c +++ b/drivers/infiniband/sw/rxe/rxe_odp.c @@ -120,19 +120,23 @@ int rxe_odp_mr_init_user(struct rxe_dev *rxe, u64 start, u64 length, } static inline bool rxe_check_pagefault(struct ib_umem_odp *umem_odp, u64 iova, - int length) + int length, bool write) { bool need_fault = false; + u64 access = HMM_PFN_VALID; u64 addr; int idx; + if (write) + access |= HMM_PFN_WRITE; + addr = iova & (~(BIT(umem_odp->page_shift) - 1)); /* Skim through all pages that are to be accessed. */ while (addr < iova + length) { idx = (addr - ib_umem_start(umem_odp)) >> umem_odp->page_shift; - if (!(umem_odp->map.pfn_list[idx] & HMM_PFN_VALID)) { + if ((umem_odp->map.pfn_list[idx] & access) != access) { need_fault = true; break; } @@ -155,6 +159,7 @@ static unsigned long rxe_odp_iova_to_page_offset(struct ib_umem_odp *umem_odp, u static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u32 flags) { struct ib_umem_odp *umem_odp = to_ib_umem_odp(mr->umem); + bool write = !(flags & RXE_PAGEFAULT_RDONLY); bool need_fault; int err; @@ -163,7 +168,7 @@ static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u mutex_lock(&umem_odp->umem_mutex); - need_fault = rxe_check_pagefault(umem_odp, iova, length); + need_fault = rxe_check_pagefault(umem_odp, iova, length, write); if (need_fault) { mutex_unlock(&umem_odp->umem_mutex); @@ -173,7 +178,7 @@ static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u if (err < 0) return err; - need_fault = rxe_check_pagefault(umem_odp, iova, length); + need_fault = rxe_check_pagefault(umem_odp, iova, length, write); if (need_fault) { mutex_unlock(&umem_odp->umem_mutex); return -EFAULT; @@ -335,8 +340,9 @@ int rxe_odp_flush_pmem_iova(struct rxe_mr *mr, u64 iova, int err; u8 *va; + /* A flush never modifies memory; read-only access suffices. */ err = rxe_odp_map_range_and_lock(mr, iova, length, - RXE_PAGEFAULT_DEFAULT); + RXE_PAGEFAULT_RDONLY); if (err) return err; From d3dbccfe6afa7b9a6a7ed65cfaa76a47fa050a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20Ghe=C8=9Bu?= Date: Sun, 30 Aug 2026 23:51:06 +0300 Subject: [PATCH 0099/1317] ASoC: fsl_micfil: balance mclk enable/disable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hw_params() enables mclk unconditionally and hw_free() disables it unconditionally, but the PCM core does not guarantee 1:1 pairing: hw_free() can run without hw_params(), and hw_params() can be called multiple times from the SETUP state. This triggers an "already disabled" WARN() in the first case and leaks an enable reference in the second, leaving the clock ungateable. Guard both sides with the existing mclk_flag, as fsl_sai.c does with mclk_streams. Fixes: b47024dc624b ("ASoC: fsl_micfil: Add mclk enable flag") Signed-off-by: Ștefan Ghețu Reviewed-by: Chancel Liu Link: https://patch.msgid.link/20260830205106.11267-1-stefanghetu9@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_micfil.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index 60ac8eabab9d..5d8f0f76ab46 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -953,12 +953,17 @@ static int fsl_micfil_reparent_rootclk(struct fsl_micfil *micfil, unsigned int s /* Get root clock */ clk = micfil->mclk; - /* Disable clock first, for it was enabled by pm_runtime */ + /* Reparent root clock to the PLL matching this sample rate */ fsl_asoc_reparent_pll_clocks(dev, clk, micfil->pll8k_clk, micfil->pll11k_clk, ratio); - ret = clk_prepare_enable(clk); - if (ret) - return ret; + + /* Enable only once; hw_params can be called multiple times */ + if (!micfil->mclk_flag) { + ret = clk_prepare_enable(clk); + if (ret) + return ret; + micfil->mclk_flag = true; + } return 0; } @@ -991,8 +996,6 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, if (ret) return ret; - micfil->mclk_flag = true; - /* floor(K * CLKDIV) */ switch (micfil->quality) { case QUALITY_HIGH: @@ -1068,8 +1071,10 @@ static int fsl_micfil_hw_free(struct snd_pcm_substream *substream, { struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); - clk_disable_unprepare(micfil->mclk); - micfil->mclk_flag = false; + if (micfil->mclk_flag) { + clk_disable_unprepare(micfil->mclk); + micfil->mclk_flag = false; + } return 0; } From 1caceeb2d74bbe88223aea55eb8626b4c5f076fd Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:27:28 -0400 Subject: [PATCH 0100/1317] RDMA/rxe: insert mcg into mcg_tree only after rxe_mcast_add() succeeds rxe_get_mcg() publishes a newly allocated multicast group in rxe->mcg_tree before programming the backing Ethernet multicast address with rxe_mcast_add(), which runs outside mcg_lock. A local userspace RDMA client reaches this path with ATTACH_MCAST on a UD QP; if rxe_mcast_add() then returns an error (for example -ENODEV when the backing netdev has been removed, or a propagated dev_mc_add() error), the unwind frees the published group without removing it from the tree. A later lookup of the same MGID dereferences the freed struct rxe_mcg from __rxe_lookup_mcg(). Fix this by keeping the new mcg private until rxe_mcast_add() succeeds. Split the tree publication into __rxe_publish_mcg(), call rxe_mcast_add() before taking the tree reference, and free the still-private mcg on failure. Because the group is never visible in mcg_tree until the multicast address is programmed, no concurrent caller can look it up or attach a QP to a group that is about to be torn down, so the error path needs no conditional unwind. If another caller publishes the same MGID while the address is being programmed, the post-add re-check under mcg_lock finds the winner; this caller then drops its private object and balances its own rxe_mcast_add() with rxe_mcast_del() before returning the winner. Reproduced by forcing the rxe_mcast_add() error return under KASAN: without the change the next attach to the same MGID reports a slab-use-after-free in __rxe_lookup_mcg(); with it the forced failure returns cleanly. A no-injection attach/detach regression, including a two-QP shared join/leave and re-attach, stays KASAN- and leak-clean. Fixes: a926a903b7dc ("RDMA/rxe: Do not call dev_mc_add/del() under a spinlock") Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260617022728.2770116-1-michael.bommarito@gmail.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_mcast.c | 50 +++++++++++++++++++-------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_mcast.c b/drivers/infiniband/sw/rxe/rxe_mcast.c index acd03bd87794..5ca9211ab33b 100644 --- a/drivers/infiniband/sw/rxe/rxe_mcast.c +++ b/drivers/infiniband/sw/rxe/rxe_mcast.c @@ -175,7 +175,9 @@ struct rxe_mcg *rxe_lookup_mcg(struct rxe_dev *rxe, union ib_gid *mgid) * @mgid: multicast address as a gid * @mcg: new mcg object * - * Context: caller should hold rxe->mcg lock + * Initializes the mcg fields. The mcg is private and not yet visible in + * mcg_tree, so this may run without rxe->mcg_lock; __rxe_publish_mcg() + * makes it visible under the lock once it is ready. */ static void __rxe_init_mcg(struct rxe_dev *rxe, union ib_gid *mgid, struct rxe_mcg *mcg) @@ -184,13 +186,22 @@ static void __rxe_init_mcg(struct rxe_dev *rxe, union ib_gid *mgid, memcpy(&mcg->mgid, mgid, sizeof(mcg->mgid)); INIT_LIST_HEAD(&mcg->qp_list); mcg->rxe = rxe; +} - /* caller holds a ref on mcg but that will be - * dropped when mcg goes out of scope. We need to take a ref - * on the pointer that will be saved in the red-black tree - * by __rxe_insert_mcg and used to lookup mcg from mgid later. - * Inserting mcg makes it visible to outside so this should - * be done last after the object is ready. +/** + * __rxe_publish_mcg - make a fully initialized mcg visible in mcg_tree + * @mcg: the mcg object + * + * Context: caller must hold rxe->mcg_lock and a reference on mcg + */ +static void __rxe_publish_mcg(struct rxe_mcg *mcg) +{ + /* caller holds a ref on mcg but that will be dropped when mcg goes + * out of scope. We need to take a ref on the pointer that will be + * saved in the red-black tree by __rxe_insert_mcg and used to lookup + * mcg from mgid later. Inserting mcg makes it visible to outside so + * this is done last after the object is ready and the multicast + * address has been programmed. */ kref_get(&mcg->ref_cnt); __rxe_insert_mcg(mcg); @@ -228,26 +239,37 @@ static struct rxe_mcg *rxe_get_mcg(struct rxe_dev *rxe, union ib_gid *mgid) err = -ENOMEM; goto err_dec; } + __rxe_init_mcg(rxe, mgid, mcg); + + /* program the multicast address while mcg is still private, before + * it is inserted into mcg_tree. dev_mc_add() may sleep so this must + * run outside mcg_lock. On failure mcg was never published, so a + * plain free is correct and the tree is untouched. + */ + err = rxe_mcast_add(rxe, mgid); + if (err) { + kfree(mcg); + goto err_dec; + } spin_lock_bh(&rxe->mcg_lock); - /* re-check to see if someone else just added it */ + /* re-check to see if someone else just added it while we were adding + * the multicast address; if so use theirs and drop ours + */ tmp = __rxe_lookup_mcg(rxe, mgid); if (tmp) { spin_unlock_bh(&rxe->mcg_lock); + rxe_mcast_del(rxe, mgid); atomic_dec(&rxe->mcg_num); kfree(mcg); return tmp; } - __rxe_init_mcg(rxe, mgid, mcg); + __rxe_publish_mcg(mcg); spin_unlock_bh(&rxe->mcg_lock); - /* add mcast address outside of lock */ - err = rxe_mcast_add(rxe, mgid); - if (!err) - return mcg; + return mcg; - kfree(mcg); err_dec: atomic_dec(&rxe->mcg_num); return ERR_PTR(err); From 4ed5bfc41071a48d609a6eaf41d68b2fd09e69d6 Mon Sep 17 00:00:00 2001 From: Jairaj Arava Date: Tue, 1 Sep 2026 09:19:51 +0800 Subject: [PATCH 0101/1317] ASoC: Intel: sof_rt5682: Add support for nvl_max98360a_rt5682 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds the driver data for rt5682 codec on SSP0 and max98360a speaker amplifiers on SSP1 for NVL platform. The existing one is not aligned with PTL. Hence, changed it to align with the working changes in PTL. Signed-off-by: Jairaj Arava Reviewed-by: Péter Ujfalusi Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260901011951.230315-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_rt5682.c | 8 ++++++++ sound/soc/intel/common/soc-acpi-intel-nvl-match.c | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index 7899f7ffd99b..88cf5c0ab2e3 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -908,6 +908,14 @@ static const struct platform_device_id board_ids[] = { SOF_SSP_PORT_BT_OFFLOAD(2) | SOF_BT_OFFLOAD_PRESENT), }, + { + .name = "nvl_rt5682_def", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_SSP_PORT_CODEC(0) | + SOF_SSP_PORT_AMP(1) | + SOF_SSP_PORT_BT_OFFLOAD(2) | + SOF_BT_OFFLOAD_PRESENT), + }, { .name = "ptl_rt5682_c1_h02", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c index 8f6e987c791e..4a67f6b72fa5 100644 --- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c @@ -51,10 +51,10 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { }, { .comp_ids = &nvl_rt5682_rt5682s_hp, - .drv_name = "sof_rt5682", - .sof_tplg_filename = "sof-nvl-rt5682", /* the tplg suffix is added at run time */ - .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | - SND_SOC_ACPI_TPLG_INTEL_SSP_MSB, + .drv_name = "nvl_rt5682_def", + .sof_tplg_filename = "sof-nvl", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_AMP_NAME | + SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME, }, /* place amp/hdmi-in only boards in the end of table */ { From 975396b9e5a4028e649f4b9a6a5ca5dfb76a824b Mon Sep 17 00:00:00 2001 From: Shuhei Takeshita Date: Sun, 9 Aug 2026 12:27:42 +0900 Subject: [PATCH 0102/1317] IB/hfi1: Resolve the credit-return buffer through the send context's node hfi1_file_mmap()'s PIO_CRED case derives this context's credit-return page offset, and the DMA handle for it, from dd->cr_base[uctxt->numa_id]. uctxt->numa_id is the node of whichever CPU the process happened to be running on, but the entry itself lives in the credit-return allocation of the send context's own node: sc->hw_free = &sc->dd->cr_base[sc->node].va[gc].cr[index]; and user send contexts are allocated with sc_alloc(dd, SC_USER, ..., dd->node), the HFI-local node. On a multi-socket host with the process running off that node the two allocations differ, so the subtraction produces an offset into an unrelated buffer and the DMA handle belongs to the wrong allocation. Use the send context's own node for all three references. The continuation lines are reindented at the same time; they mixed spaces and tabs. Fixes: 7724105686e7 ("IB/hfi1: add driver files") Cc: stable@vger.kernel.org Signed-off-by: Shuhei Takeshita Link: https://patch.msgid.link/20260809032743.2671579-2-jyohuku.alterego@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/hfi1/file_ops.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c index dc548e6802e2..72c21d7455fd 100644 --- a/drivers/infiniband/hw/hfi1/file_ops.c +++ b/drivers/infiniband/hw/hfi1/file_ops.c @@ -382,10 +382,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) * of enabled contexts > 64 and 128 respectively). */ cr_page_offset = ((u64)uctxt->sc->hw_free - - (u64)dd->cr_base[uctxt->numa_id].va) & - PAGE_MASK; - memvirt = dd->cr_base[uctxt->numa_id].va + cr_page_offset; - memdma = dd->cr_base[uctxt->numa_id].dma + cr_page_offset; + (u64)dd->cr_base[uctxt->sc->node].va) & + PAGE_MASK; + memvirt = dd->cr_base[uctxt->sc->node].va + cr_page_offset; + memdma = dd->cr_base[uctxt->sc->node].dma + cr_page_offset; memlen = PAGE_SIZE; flags &= ~VM_MAYWRITE; flags |= VM_DONTCOPY | VM_DONTEXPAND; From 62f0f34fbd2b2d5653d33d3b9d42fdcabb1c0101 Mon Sep 17 00:00:00 2001 From: Shuhei Takeshita Date: Sun, 9 Aug 2026 12:27:43 +0900 Subject: [PATCH 0103/1317] IB/hfi1: Fix the PIO_CRED credit-return mmap hfi1_file_mmap()'s PIO_CRED case must hand user space the single credit-return page that holds this context's entry. That page is the second or third page of the per-node credit-return allocation once the hardware send context index reaches 64 or 128, so the failure below is intermittent: when the entry lands on the first page the offset is zero and everything works. Two things are wrong. First, cr_page_offset is a byte offset but .va is a struct credit_return *, so adding it is pointer arithmetic and scales the offset by sizeof(struct credit_return) == 64. memvirt then lands 256 KiB or 512 KiB past a 10240-byte allocation. With an IOMMU translating, that address is inside the vmalloc range but in no vm_area, so dma_mmap_coherent() -> iommu_dma_mmap() finds no pages, vmalloc_to_pfn() returns page_to_pfn(NULL), and remap_pfn_range() installs a frame above MAXPHYADDR. The first user read then takes: psm2_ep_open_pr: Corrupted page table at address 7a14d007e000 PGD 800000013886a067 P4D 800000013886a067 PUD 13886b067 PMD 13886c067 PTE 800049168e911235 Oops: Bad pagetable: 000d [#1] SMP PTI Second, and still wrong once the arithmetic is corrected, dma_mmap_coherent() describes a whole coherent buffer and selects the page within it with vma->vm_pgoff. Offsetting cpu_addr has no effect: for a vmap'd allocation iommu_dma_mmap() uses cpu_addr only to locate the vm_area and then maps pages[vm_pgoff], which hfi1_file_mmap() has just set to 0. User space therefore always receives the first credit-return page, every credit read is for the wrong context, and send PIO stalls forever. Use the DMA API as intended: pass the base of the allocation with its full length and select the page with vm_pgoff. A separate length is needed because memlen must keep describing the VMA for the existing size check. The dma-direct path stays correct as well, since dma_direct_mmap() adds the same vm_pgoff to the base pfn. Tested on a Dell T7610 (Xeon E5-2650 v2, Intel IOMMU in DMA-FQ mode) against a Threadripper PRO 3995WX peer, both Omni-Path 100. Before this change psm2_ep_open() Oopses the kernel; with only the arithmetic corrected psm2_ep_open() succeeds but any transfer that uses send PIO hangs, PSM2_SDMA=2 (send PIO disabled) completing normally while PSM2_SDMA=0 (send PIO only) hangs every time. With this change send PIO, send DMA and the default mixed mode all work. Fixes: 1ec82317a1da ("IB/hfi1: Use dma_mmap_coherent for matching buffers") Cc: stable@vger.kernel.org Signed-off-by: Shuhei Takeshita Link: https://patch.msgid.link/20260809032743.2671579-3-jyohuku.alterego@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/hfi1/file_ops.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c index 72c21d7455fd..1a36f995c4f6 100644 --- a/drivers/infiniband/hw/hfi1/file_ops.c +++ b/drivers/infiniband/hw/hfi1/file_ops.c @@ -326,6 +326,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) void *memvirt = NULL; dma_addr_t memdma = 0; u8 subctxt, mapio = 0, vmf = 0, type; + size_t memdmalen = 0; ssize_t memlen = 0; int ret = 0; u16 ctxt; @@ -371,7 +372,9 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) mapio = 1; break; case PIO_CRED: { + struct credit_return_base *cr = &dd->cr_base[uctxt->sc->node]; u64 cr_page_offset; + if (flags & VM_WRITE) { ret = -EPERM; goto done; @@ -381,11 +384,18 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) * second or third page allocated for credit returns (if number * of enabled contexts > 64 and 128 respectively). */ - cr_page_offset = ((u64)uctxt->sc->hw_free - - (u64)dd->cr_base[uctxt->sc->node].va) & + cr_page_offset = ((u64)uctxt->sc->hw_free - (u64)cr->va) & PAGE_MASK; - memvirt = dd->cr_base[uctxt->sc->node].va + cr_page_offset; - memdma = dd->cr_base[uctxt->sc->node].dma + cr_page_offset; + /* + * dma_mmap_coherent() describes the whole coherent buffer and + * selects the page within it with vma->vm_pgoff, so pass the + * base of the allocation and its length and let vm_pgoff pick + * the page. + */ + vma->vm_pgoff = cr_page_offset >> PAGE_SHIFT; + memvirt = cr->va; + memdma = cr->dma; + memdmalen = TXE_NUM_CONTEXTS * sizeof(struct credit_return); memlen = PAGE_SIZE; flags &= ~VM_MAYWRITE; flags |= VM_DONTCOPY | VM_DONTEXPAND; @@ -567,7 +577,8 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) ret = 0; } else if (memdma) { ret = dma_mmap_coherent(&dd->pcidev->dev, vma, - memvirt, memdma, memlen); + memvirt, memdma, + memdmalen ? memdmalen : memlen); } else if (mapio) { ret = io_remap_pfn_range(vma, vma->vm_start, PFN_DOWN(memaddr), From 2be77295316c2dff0a33c0dc3a65abdab4ccf796 Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Tue, 11 Aug 2026 19:25:57 +0300 Subject: [PATCH 0104/1317] RDMA/mlx5: Remove warn on missing representor in query_port_speed The representor ib_device's phys_port_cnt is set to the total vport count when the uplink vport rep loads. Individual port[i].rep entries are populated only as each VF/SF vport rep registers. A NULL .rep for a given port index is therefore expected while VF reps are still loading or haven't been enabled yet. Tools like ibstat and ibv_devinfo iterate over all ports of all RDMA devices. Some ports may not have an eswitch representor, causing repeated dmesg warnings when these tools run without a device argument. This causes dmesg to be flooded with this message on every ibstat invocation. Remove the warning and return -ENODEV when no representor exists for the queried port. Fixes: aaecff5e13cd ("RDMA/mlx5: Implement query_port_speed callback") Signed-off-by: Or Har-Toov Reviewed-by: Shay Drory Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260811-remove-warn-on-miss-rep-v1-1-eccf399bc6af@nvidia.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mlx5/main.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/mlx5/main.c b/drivers/infiniband/hw/mlx5/main.c index 373ee1f42d4a..a457647edacd 100644 --- a/drivers/infiniband/hw/mlx5/main.c +++ b/drivers/infiniband/hw/mlx5/main.c @@ -1683,11 +1683,8 @@ static int mlx5_ib_query_port_speed_rep(struct mlx5_ib_dev *dev, u32 port_num, struct mlx5_core_dev *mdev; u16 op_mod; - if (!dev->port[port_num - 1].rep) { - mlx5_ib_warn(dev, "Representor doesn't exist for port %u\n", - port_num); - return -EINVAL; - } + if (!dev->port[port_num - 1].rep) + return -ENODEV; rep = dev->port[port_num - 1].rep; mdev = mlx5_eswitch_get_core_dev(rep->esw); From 0c06c4ce0206290c9a934a1e7196aaa86adfe018 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Mon, 24 Aug 2026 14:35:06 +0800 Subject: [PATCH 0105/1317] ASoC: amd: renoir: fix disable_pdm_interrupts() to clear mask bits disable_pdm_interrupts() uses |= ~PDM_DMA_INTR_MASK which sets all bits except the PDM DMA interrupt bit instead of clearing only the PDM DMA interrupt bit. Use &= ~PDM_DMA_INTR_MASK to clear only the target bit. Fixes: f621a3676d3f ("ASoC: amd: add ACP3x PDM platform driver") Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260824063507.483784-1-wangdich9700@163.com Signed-off-by: Mark Brown --- sound/soc/amd/renoir/acp3x-pdm-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index e60e3821703c..3f59b753243d 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -104,7 +104,7 @@ static void disable_pdm_interrupts(void __iomem *acp_base) u32 ext_int_ctrl; ext_int_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); - ext_int_ctrl |= ~PDM_DMA_INTR_MASK; + ext_int_ctrl &= ~PDM_DMA_INTR_MASK; rn_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); } From 1b67e0d3b9691d7b6b74e18960ddd2be24f9dc9d Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Mon, 24 Aug 2026 14:35:07 +0800 Subject: [PATCH 0106/1317] ASoC: amd: yc: fix memory leak in acp6x_pdm_dma_close() acp6x_pdm_dma_close() does not free the runtime->private_data buffer allocated in acp6x_pdm_dma_open(). Add the missing kfree. Fixes: 7610174a5bfe ("ASoC: amd: add acp6x pdm platform driver") Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260824063507.483784-2-wangdich9700@163.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-pdm-dma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c index 710db721ffa4..40c4d833f4ed 100644 --- a/sound/soc/amd/yc/acp6x-pdm-dma.c +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -275,9 +275,11 @@ static int acp6x_pdm_dma_close(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct pdm_dev_data *adata = dev_get_drvdata(component->dev); + struct snd_pcm_runtime *runtime = substream->runtime; acp6x_disable_pdm_interrupts(adata->acp6x_base); adata->capture_stream = NULL; + kfree(runtime->private_data); return 0; } From ef9fbe1b93f3b617b96e86d5cd76b3fa44514cb5 Mon Sep 17 00:00:00 2001 From: Krystian Kaniewski Date: Wed, 12 Aug 2026 10:16:41 +0200 Subject: [PATCH 0107/1317] RDMA/core: Reject unregistering netdevs in ib_get_eth_speed ib_device_get_netdev() intentionally returns a referenced net_device even when it is unregistering, so matching and cleanup callers can still find the association. The reference keeps struct net_device allocated, but does not guarantee that the device remains operational. ib_get_eth_speed() uses the returned device operationally by invoking its ethtool callback. Although that call is made under RTNL, the function does not verify the registration state first. An asynchronous RDMA port query can therefore call into a netdev after NETDEV_UNREGISTER and ndo_uninit have completed. Check for NETREG_REGISTERED while holding RTNL and return -ENODEV for a device which is being unregistered. Keeping RTNL across the check and the ethtool operation prevents unregister from starting between them. Keep the speed fallback and warning under RTNL as well, so the warning can safely read netdev->name. Drop the netdev reference before releasing RTNL once all accesses to the device are complete. Fixes: d41861942fc5 ("IB/core: Add generic function to extract IB speed from netdev") Reported-by: syzbot+5fe14f2ff4ccbace9a26@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=5fe14f2ff4ccbace9a26 Signed-off-by: Krystian Kaniewski Link: https://patch.msgid.link/20260812081708.32468-1-krystianmkaniewski@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/verbs.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/core/verbs.c b/drivers/infiniband/core/verbs.c index 04abc80c1327..c43e25d37266 100644 --- a/drivers/infiniband/core/verbs.c +++ b/drivers/infiniband/core/verbs.c @@ -2058,11 +2058,13 @@ int ib_get_eth_speed(struct ib_device *dev, u32 port_num, u16 *speed, u8 *width) return -ENODEV; rtnl_lock(); + if (READ_ONCE(netdev->reg_state) != NETREG_REGISTERED) { + dev_put(netdev); + rtnl_unlock(); + return -ENODEV; + } + rc = __ethtool_get_link_ksettings(netdev, &lksettings); - rtnl_unlock(); - - dev_put(netdev); - if (!rc && lksettings.base.speed != (u32)SPEED_UNKNOWN) { netdev_speed = lksettings.base.speed; } else { @@ -2071,6 +2073,8 @@ int ib_get_eth_speed(struct ib_device *dev, u32 port_num, u16 *speed, u8 *width) pr_warn("%s speed is unknown, defaulting to %u\n", netdev->name, netdev_speed); } + dev_put(netdev); + rtnl_unlock(); ib_get_width_and_speed(netdev_speed, lksettings.lanes, speed, width); From 2dc65035eb8d9f4b4495a8bc044f10f44974a9c6 Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Tue, 1 Sep 2026 10:11:32 +0800 Subject: [PATCH 0108/1317] ASoC: Intel: sof_sdw: add tac5xx2-sdw family Add Texas Instrument's tac5xx2-sdw family to include support for soundwire codecs tac5572, tac5682 Signed-off-by: Niranjan H Y Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260901021132.231908-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Kconfig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index cddbd2aa424e..b795bcade8e9 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -533,12 +533,14 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH select SND_SOC_CS35L56_SPI select SND_SOC_CS35L56_SDW select SND_SOC_ES9356 + imply SND_SOC_TAC5XX2_SDW select SND_SOC_DMIC select SND_SOC_INTEL_HDA_DSP_COMMON imply SND_SOC_SDW_MOCKUP help Add support for Intel SoundWire-based platforms connected to - MAX98373, RT700, RT711, RT1308 and RT715 + MAX98373, RT700, RT711, RT1308, RT715, TAC5XX2_SDW family (including + TAC5572, TAC5682). If unsure select "N". endif From a44a3f175eaee7e5aeb6a8fed381c4a0d5f49236 Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Tue, 11 Aug 2026 19:19:16 +0300 Subject: [PATCH 0109/1317] RDMA/uverbs: Fix mmap_lock/disassociation_lock circular dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 51976c6cd786 ("RDMA/core: Provide rdma_user_mmap_disassociate() to disassociate mmap pages") introduced disassociation_lock to protect new mmap registrations against uverbs_user_mmap_disassociate(), but created an ABBA deadlock: Thread A (mmap / fork): mmap_lock -> disassociation_lock Thread B (disassociate): disassociation_lock -> mmap_lock Fix by removing disassociation_lock entirely and using the pre-existing hw_destroy_rwsem instead. hw_destroy_rwsem already provides the same protection: rdma_umap_open() and ib_uverbs_mmap() both use down_read_trylock() before registering a new VMA, so holding hw_destroy_rwsem in uverbs_user_mmap_disassociate() is sufficient to block new registrations. trylock is used in both mmap paths (not blocking down_read) because mmap_lock is already held on entry, and uverbs_user_mmap_disassociate() acquires mmap_lock internally — a blocking read would recreate the same deadlock. The only caller that was not taking hw_destroy_rwsem for write was rdma_user_mmap_disassociate(). Fix it to take the rwsem per-ufile while iterating under lists_mutex. This is safe because ib_uverbs_close() releases hw_destroy_rwsem entirely before acquiring lists_mutex, so the two locks are never held simultaneously. lockdep warning: [ 776.654252] ====================================================== [ 776.655214] WARNING: possible circular locking dependency detected [ 776.656167] 6.18.0for-upstream_debug_94e244d9ccab #1 Not tainted [ 776.657114] ------------------------------------------------------ [ 776.658087] devlink/14824 is trying to acquire lock: [ 776.658879] ffff88811170c800 (&mm->mmap_lock){++++}-{4:4}, at: uverbs_user_mmap_disassociate+0x168/0x780 [ib_uverbs] [ 776.660479] [ 776.660479] but task is already holding lock: [ 776.661460] ffff888142d92b08 (&file->disassociation_lock){+.+.}-{4:4}, at: uverbs_user_mmap_disassociate+0x39/0x780 [ib_uverbs] [ 776.663177] [ 776.663177] which lock already depends on the new lock. [ 776.663177] [ 776.664525] [ 776.664525] the existing dependency chain (in reverse order) is: [ 776.665724] [ 776.665724] -> #2 (&file->disassociation_lock){+.+.}-{4:4}: [ 776.666887] __mutex_lock+0x16d/0x2330 [ 776.667633] rdma_umap_open+0x129/0x280 [ib_uverbs] [ 776.668489] dup_mmap+0xa40/0x1790 [ 776.669170] copy_process+0x5dd2/0x6170 [ 776.669933] kernel_clone+0xb6/0x610 [ 776.670636] __do_sys_clone+0xb5/0xf0 [ 776.671354] do_syscall_64+0x70/0x12e0 [ 776.672083] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.672940] [ 776.672940] -> #1 (&mm->mmap_lock/1){+.+.}-{4:4}: [ 776.673985] down_write_nested+0x90/0x1e0 [ 776.674751] dup_mmap+0x201/0x1790 [ 776.675448] copy_process+0x5dd2/0x6170 [ 776.676180] kernel_clone+0xb6/0x610 [ 776.676904] __do_sys_clone+0xb5/0xf0 [ 776.677615] do_syscall_64+0x70/0x12e0 [ 776.678351] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.679239] [ 776.679239] -> #0 (&mm->mmap_lock){++++}-{4:4}: [ 776.680253] __lock_acquire+0x18c6/0x2ec0 [ 776.681018] lock_acquire+0x10e/0x2e0 [ 776.681742] down_read+0x95/0x430 [ 776.682395] uverbs_user_mmap_disassociate+0x168/0x780 [ib_uverbs] [ 776.683436] uverbs_destroy_ufile_hw+0x1ae/0x270 [ib_uverbs] [ 776.684416] ib_uverbs_remove_one+0x22b/0x420 [ib_uverbs] [ 776.685371] remove_client_context+0xa6/0xf0 [ib_core] [ 776.686342] disable_device+0x12b/0x240 [ib_core] [ 776.687249] __ib_unregister_device+0x269/0x460 [ib_core] [ 776.688233] ib_unregister_device+0x21/0x30 [ib_core] [ 776.689140] mlx5r_remove+0xd0/0x170 [mlx5_ib] [ 776.689999] device_release_driver_internal+0x3b2/0x560 [ 776.694876] bus_remove_device+0x1f5/0x3e0 [ 776.695638] device_del+0x3b9/0x990 [ 776.696329] mlx5_detach_device+0x17e/0x350 [mlx5_core] [ 776.697429] mlx5_unload_one_devl_locked+0x3f/0xb0 [mlx5_core] [ 776.698578] mlx5_devlink_reload_down+0x1f9/0x550 [mlx5_core] [ 776.699712] devlink_reload+0x13e/0x680 [ 776.700456] devlink_nl_reload_doit+0xc29/0x1160 [ 776.701293] genl_family_rcv_msg_doit+0x1c9/0x2a0 [ 776.702135] genl_rcv_msg+0x3f0/0x6b0 [ 776.702854] netlink_rcv_skb+0x11d/0x370 [ 776.703605] genl_rcv+0x24/0x40 [ 776.704236] netlink_unicast+0x5b4/0x970 [ 776.704984] netlink_sendmsg+0x730/0xbf0 [ 776.705748] __sock_sendmsg+0xc5/0x190 [ 776.706461] __sys_sendto+0x201/0x2f0 [ 776.707188] __x64_sys_sendto+0xdc/0x1b0 [ 776.707931] do_syscall_64+0x70/0x12e0 [ 776.708643] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.709546] [ 776.709546] other info that might help us debug this: [ 776.709546] [ 776.710910] Chain exists of: [ 776.710910] &mm->mmap_lock --> &mm->mmap_lock/1 --> &file->disassociation_lock [ 776.710910] [ 776.712805] Possible unsafe locking scenario: [ 776.712805] [ 776.713828] CPU0 CPU1 [ 776.714589] ---- ---- [ 776.715347] lock(&file->disassociation_lock); [ 776.716097] lock(&mm->mmap_lock/1); [ 776.717067] lock(&file->disassociation_lock); [ 776.718199] rlock(&mm->mmap_lock); [ 776.718857] [ 776.718857] *** DEADLOCK *** Fixes: 51976c6cd786 ("RDMA/core: Provide rdma_user_mmap_disassociate() to disassociate mmap pages") Signed-off-by: Or Har-Toov Signed-off-by: Leon Romanovsky Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260811-fix-mmap-lockdep-v1-1-1151b41063b4@nvidia.com Acked-by: Junxian Huang Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/rdma_core.c | 1 - drivers/infiniband/core/uverbs_main.c | 25 +++++++++++-------------- include/rdma/uverbs_types.h | 2 -- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/drivers/infiniband/core/rdma_core.c b/drivers/infiniband/core/rdma_core.c index fd5651c003ae..a7cbe643e33c 100644 --- a/drivers/infiniband/core/rdma_core.c +++ b/drivers/infiniband/core/rdma_core.c @@ -69,7 +69,6 @@ void ib_uverbs_release_file(struct kref *ref) if (file->disassociate_page) __free_pages(file->disassociate_page, 0); - mutex_destroy(&file->disassociation_lock); mutex_destroy(&file->umap_lock); mutex_destroy(&file->ucontext_lock); kfree(file); diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c index 0d88b2ee68ff..2a046c888dfa 100644 --- a/drivers/infiniband/core/uverbs_main.c +++ b/drivers/infiniband/core/uverbs_main.c @@ -644,12 +644,15 @@ static int ib_uverbs_mmap(struct file *filp, struct vm_area_struct *vma) goto out; } - mutex_lock(&file->disassociation_lock); + if (!down_read_trylock(&file->hw_destroy_rwsem)) { + ret = -EIO; + goto out; + } vma->vm_ops = &rdma_umap_ops; ret = ucontext->device->ops.mmap(ucontext, vma); - mutex_unlock(&file->disassociation_lock); + up_read(&file->hw_destroy_rwsem); out: srcu_read_unlock(&file->device->disassociate_srcu, srcu_key); return ret; @@ -671,7 +674,6 @@ static void rdma_umap_open(struct vm_area_struct *vma) /* We are racing with disassociation */ if (!down_read_trylock(&ufile->hw_destroy_rwsem)) goto out_zap; - mutex_lock(&ufile->disassociation_lock); /* * Disassociation already completed, the VMA should already be zapped. @@ -684,12 +686,10 @@ static void rdma_umap_open(struct vm_area_struct *vma) goto out_unlock; rdma_umap_priv_init(priv, vma, opriv->entry); - mutex_unlock(&ufile->disassociation_lock); up_read(&ufile->hw_destroy_rwsem); return; out_unlock: - mutex_unlock(&ufile->disassociation_lock); up_read(&ufile->hw_destroy_rwsem); out_zap: /* @@ -773,7 +773,7 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) { struct rdma_umap_priv *priv, *next_priv; - mutex_lock(&ufile->disassociation_lock); + lockdep_assert_held_write(&ufile->hw_destroy_rwsem); while (1) { struct mm_struct *mm = NULL; @@ -799,10 +799,8 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) break; } mutex_unlock(&ufile->umap_lock); - if (!mm) { - mutex_unlock(&ufile->disassociation_lock); + if (!mm) return; - } /* * The umap_lock is nested under mmap_lock since it used within @@ -832,8 +830,6 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) mmap_read_unlock(mm); mmput(mm); } - - mutex_unlock(&ufile->disassociation_lock); } /** @@ -851,8 +847,11 @@ void rdma_user_mmap_disassociate(struct ib_device *device) mutex_lock(&uverbs_dev->lists_mutex); list_for_each_entry(ufile, &uverbs_dev->uverbs_file_list, list) { - if (ufile->ucontext) + if (ufile->ucontext) { + down_write(&ufile->hw_destroy_rwsem); uverbs_user_mmap_disassociate(ufile); + up_write(&ufile->hw_destroy_rwsem); + } } mutex_unlock(&uverbs_dev->lists_mutex); } @@ -927,8 +926,6 @@ static int ib_uverbs_open(struct inode *inode, struct file *filp) mutex_init(&file->umap_lock); INIT_LIST_HEAD(&file->umaps); - mutex_init(&file->disassociation_lock); - filp->private_data = file; list_add_tail(&file->list, &dev->uverbs_file_list); mutex_unlock(&dev->lists_mutex); diff --git a/include/rdma/uverbs_types.h b/include/rdma/uverbs_types.h index 5a07f9a6dcd1..6f3622892c0c 100644 --- a/include/rdma/uverbs_types.h +++ b/include/rdma/uverbs_types.h @@ -180,8 +180,6 @@ struct ib_uverbs_file { struct page *disassociate_page; struct xarray idr; - - struct mutex disassociation_lock; }; extern const struct uverbs_obj_type_class uverbs_idr_class; From 1af874e9f4ce22ccf8b10ab5462f32c70d3be21a Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Sun, 16 Aug 2026 00:45:10 -0400 Subject: [PATCH 0110/1317] IB/mlx4: Fix use-after-free on pkey sysfs registration failure register_pkey_tree() ignores errors from register_one_pkey_tree() and continues registering the remaining slaves. The per-slave error path has already released the pkey parent kobjects, but their pointers remain stored in the device. A later device cleanup therefore passes the stale pointers to kobject_put(), causing a use-after-free. Clear the parent pointers after releasing a failed slave tree and skip unregistered trees during device cleanup. This preserves the existing best-effort registration behavior while preventing a second cleanup of the failed tree. Fixes: c1e7e466120b ("IB/mlx4: Add iov directory in sysfs under the ib device") Cc: stable@vger.kernel.org Signed-off-by: Shuangpeng Bai Link: https://patch.msgid.link/20260816044510.3848996-1-shuangpeng.kernel@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mlx4/sysfs.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/infiniband/hw/mlx4/sysfs.c b/drivers/infiniband/hw/mlx4/sysfs.c index e688ad66a895..5438224bf325 100644 --- a/drivers/infiniband/hw/mlx4/sysfs.c +++ b/drivers/infiniband/hw/mlx4/sysfs.c @@ -751,11 +751,13 @@ static int register_one_pkey_tree(struct mlx4_ib_dev *dev, int slave) kobject_put(p); } kobject_put(dev->dev_ports_parent[slave]); + dev->dev_ports_parent[slave] = NULL; err_ports: kobject_put(dev->pkeys.device_parent[slave]); /* extra put for the device_parent create_and_add */ kobject_put(dev->pkeys.device_parent[slave]); + dev->pkeys.device_parent[slave] = NULL; fail_dev: kobject_put(dev->iov_parent); @@ -785,6 +787,8 @@ static void unregister_pkey_tree(struct mlx4_ib_dev *device) return; for (slave = device->dev->persist->num_vfs; slave >= 0; --slave) { + if (!device->pkeys.device_parent[slave]) + continue; list_for_each_entry_safe(p, t, &device->pkeys.pkey_port_list[slave], entry) { From 8b852965b8eaf910c314dc346967ed82c8d4f235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Tue, 1 Sep 2026 10:06:27 -0300 Subject: [PATCH 0111/1317] Input: evdev - zero absinfo before partial copy in EVIOCSABS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EVIOCSABS handler copies at most the user supplied ioctl size into an uninitialized on-stack struct input_absinfo: if (copy_from_user(&abs, p, min_t(size_t, size, sizeof(struct input_absinfo)))) The size comes from _IOC_SIZE() of the ioctl command and is therefore fully controlled by userspace. A short size leaves the trailing part of the structure holding whatever was on the kernel stack, and the whole structure is then stored into the device: dev->absinfo[t] = abs; EVIOCGABS hands that back to userspace, disclosing the stale stack bytes. Only the resolution field is currently cleared, which covers the legacy struct layout but not an arbitrarily short size. Zero the structure before the copy so any part not supplied by the caller reads back as zero. The existing resolution fixup is kept, since it also handles a size that partially overlaps that field. Fixes: 448cd1664a57 ("Input: evdev - rearrange ioctl handling") Cc: stable@vger.kernel.org Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260901130629.24078-2-ivanrwcm25@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/evdev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 3a718d600006..8bfaaa45e0b9 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -1229,6 +1229,8 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, t = _IOC_NR(cmd) & ABS_MAX; + memset(&abs, 0, sizeof(abs)); + if (copy_from_user(&abs, p, min_t(size_t, size, sizeof(struct input_absinfo)))) return -EFAULT; From f84819ef8d66931ee3998fee3c4f03230f4cb6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Tue, 1 Sep 2026 10:06:28 -0300 Subject: [PATCH 0112/1317] Input: zero ff_effect before compat copy in input_ff_effect_from_user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the compat path input_ff_effect_from_user() aliases the caller's native struct ff_effect with the smaller struct ff_effect_compat and copies only the compat sized prefix: compat_effect = (struct ff_effect_compat *)effect; if (copy_from_user(compat_effect, buffer, sizeof(struct ff_effect_compat))) The tail of the native structure is never written. Callers pass an uninitialized on-stack object, for example evdev_do_ioctl() for EVIOCSFF, so those bytes keep their previous stack contents. input_ff_upload() then stores the full native structure in ff->effects[id], from where a uinput based force feedback daemon can read it back via UI_BEGIN_FF_UPLOAD, disclosing kernel stack memory to userspace. Zero the effect before the compat copy. Fixes: 2d56f3a32c0e ("Input: refactor evdev 32bit compat to be shareable with uinput") Cc: stable@vger.kernel.org Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260901130629.24078-3-ivanrwcm25@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/input-compat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/input/input-compat.c b/drivers/input/input-compat.c index a5043193ead8..8860a0730294 100644 --- a/drivers/input/input-compat.c +++ b/drivers/input/input-compat.c @@ -76,6 +76,8 @@ int input_ff_effect_from_user(const char __user *buffer, size_t size, */ compat_effect = (struct ff_effect_compat *)effect; + memset(effect, 0, sizeof(*effect)); + if (copy_from_user(compat_effect, buffer, sizeof(struct ff_effect_compat))) return -EFAULT; From 85f080fb87ed5cd3e46121be677f52c82f26a0ab Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Tue, 1 Sep 2026 20:26:49 +0800 Subject: [PATCH 0113/1317] Input: cyttsp5 - clamp the HID report size before memcpy The size field comes from the device and is used as the memcpy() length into response_buf, which is CY_MAX_INPUT bytes. Fixes: 5b0c03e24a06 ("Input: Add driver for Cypress Generation 5 touchscreen") Signed-off-by: Linkai Gong Link: https://patch.msgid.link/20260901122649.1173066-1-gonglinkai@kylinos.cn Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/cyttsp5.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/touchscreen/cyttsp5.c b/drivers/input/touchscreen/cyttsp5.c index 9266c07314be..e878a02dc9b7 100644 --- a/drivers/input/touchscreen/cyttsp5.c +++ b/drivers/input/touchscreen/cyttsp5.c @@ -710,6 +710,7 @@ static irqreturn_t cyttsp5_handle_irq(int irq, void *handle) size = 2; } else { report_id = ts->input_buf[2]; + size = min(size, CY_MAX_INPUT); } switch (report_id) { From 1d80a4792f1de236c157bcee2e5400fad4c66c65 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 1 Sep 2026 13:26:44 +0100 Subject: [PATCH 0114/1317] ASoC: cs35l56: Fix probe deadlock waiting for SoundWire enumeration On SoundWire, don't call snd_soc_register_component() from driver probe(). Instead, queue a work item after first SoundWire attach to call snd_soc_register_component(). This prevents a deadlock if snd_soc_register_component() directly calls cs35l56_component_probe(). On SoundWire, the registers are not accessible during driver probe(). Drivers must return from their probe() and wait for the SoundWire core to call their update_status() callback to report an ATTACHED status. The cs35l56 driver handled this by calling snd_soc_register_component() from driver probe() as usual, and cs35l56_component_probe() waited for init_completion to be signalled. A SoundWire attach calls cs35l56_init() which then signals init_completion. This created a deadlock if this was the last component needed to complete a card. In that case, snd_soc_register_component() directly called cs35l56_component_probe() which led to this: driver probe() calls snd_soc_register_component() calls cs35l56_component_probe() waits for init_completion In this case the driver probe() has not returned, so the SoundWire core would not call update_status() and init_completion would not be signalled. Fortunately, snd_soc_register_component() never returns -EPROBE_DEFER, so it doesn't need to be called from a driver probe(). It can be deferred to a work item. This work is queued after the first completed pass through cs35l56_init(), so there is no need for it to wait for init_completion. snd_soc_register_component() isn't called directly from cs35l56_init() because cs35l56_init() runs in the SoundWire bus driver thread, and there would be a risk of nested locking or lock inversion. The work item is queued on a freezable workqueue to prevent a race between the work item and system_suspend of another instance. If the workqueue were not frozen it would be possible for the work item of one driver instance to call snd_soc_register_component() which then calls cs35l56_component_probe() of another instance while that instance is already executing its system suspend functions. The non-SoundWire case still calls snd_soc_register_component() from cs35l56_common_probe() so that it is the last initialization action. There's no need defer the call for I2S/SPI buses so we can also leave it able to return errors during probe. Fixes: 440c2d38950f7 ("ASoC: cs35l56: Wait for init_complete in cs35l56_component_probe()") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260901122644.634494-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56.c | 71 +++++++++++++++++++++++++++++++------- sound/soc/codecs/cs35l56.h | 2 ++ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index b9118ad8fab5..890429ab0dfb 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -1365,12 +1365,6 @@ static int _cs35l56_component_probe(struct snd_soc_component *component) BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values)); - if (!wait_for_completion_timeout(&cs35l56->init_completion, - msecs_to_jiffies(5000))) { - dev_err(cs35l56->base.dev, "%s: init_completion timed out\n", __func__); - return -ENODEV; - } - cs35l56->dsp.part = kasprintf(GFP_KERNEL, "cs35l%02x", cs35l56->base.type); if (!cs35l56->dsp.part) return -ENOMEM; @@ -1939,6 +1933,40 @@ static int cs35l56_try_get_broken_sdca_spkid_gpio(struct cs35l56_private *cs35l5 return ret; } +static int cs35l56_component_register(struct cs35l56_private *cs35l56) +{ + int ret; + + ret = snd_soc_register_component(cs35l56->base.dev, + &soc_component_dev_cs35l56, + cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); + if (ret < 0) { + dev_err(cs35l56->base.dev, "Register codec failed: %d\n", ret); + return ret; + } + + cs35l56->component_registered = true; + + return 0; +} + +static void cs35l56_component_register_work(struct work_struct *work) +{ + struct cs35l56_private *cs35l56 = container_of(work, + struct cs35l56_private, + component_register_work); + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { + dev_err(cs35l56->base.dev, "register_work failed to get pm_runtime: %d\n", ret); + return; + } + + cs35l56_component_register(cs35l56); +} + int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) { int ret; @@ -1947,6 +1975,7 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) mutex_init(&cs35l56->base.irq_lock); cs35l56->base.cal_index = -1; cs35l56->speaker_id = -ENOENT; + INIT_WORK(&cs35l56->component_register_work, cs35l56_component_register_work); dev_set_drvdata(cs35l56->base.dev, cs35l56); @@ -2020,12 +2049,17 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) if (ret) goto err_remove_wm_adsp; - ret = snd_soc_register_component(cs35l56->base.dev, - &soc_component_dev_cs35l56, - cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); - if (ret < 0) { - dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n"); - goto err_free_irq; + /* + * Defer calling snd_soc_register_component() on SoundWire to prevent + * a deadlock where it calls our component_probe(), which requires the + * SoundWire enumeration to complete, but because we are still in probe() + * the SoundWire core will not call the update_status() callback. At time + * of writing snd_soc_register_component() never returns EPROBE_DEFER. + */ + if (!cs35l56->sdw_peripheral) { + ret = cs35l56_component_register(cs35l56); + if (ret < 0) + goto err_free_irq; } return 0; @@ -2055,6 +2089,7 @@ EXPORT_SYMBOL_NS_GPL(cs35l56_common_probe, "SND_SOC_CS35L56_CORE"); int cs35l56_init(struct cs35l56_private *cs35l56) { + bool first_time_init = !cs35l56->base.init_done; int ret; /* @@ -2131,13 +2166,23 @@ int cs35l56_init(struct cs35l56_private *cs35l56) cs35l56->base.init_done = true; complete_all(&cs35l56->init_completion); + if (cs35l56->sdw_peripheral && first_time_init) { + /* + * Hardware now accessible, queue work to call + * snd_soc_register_component(). + */ + queue_work(system_freezable_wq, &cs35l56->component_register_work); + } + return 0; } EXPORT_SYMBOL_NS_GPL(cs35l56_init, "SND_SOC_CS35L56_CORE"); void cs35l56_remove(struct cs35l56_private *cs35l56) { - snd_soc_unregister_component(cs35l56->base.dev); + cancel_work_sync(&cs35l56->component_register_work); + if (cs35l56->component_registered) + snd_soc_unregister_component(cs35l56->base.dev); cs35l56->base.init_done = false; diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 35c02ae17de3..f7cf8aa653e2 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -32,6 +32,7 @@ struct sdw_slave; struct cs35l56_private { struct wm_adsp dsp; /* must be first member */ struct cs35l56_base base; + struct work_struct component_register_work; struct work_struct dsp_work; struct workqueue_struct *dsp_wq; struct snd_soc_component *component; @@ -41,6 +42,7 @@ struct cs35l56_private { const char *fallback_fw_suffix; bool soft_resetting; bool sdw_attached; + bool component_registered; struct completion init_completion; int speaker_id; From 1f1d0812f6a8ab8e6f709c599f137c99646512cc Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Sat, 15 Aug 2026 13:44:19 -0700 Subject: [PATCH 0115/1317] gpiolib: of: don't mark hog nodes OF_POPULATED before a chip is found When a gpio-hog node is attached by a device-tree overlay before its parent GPIO chip has been registered, of_gpio_notify() sets OF_POPULATED on the node via of_node_test_and_set_flag() and only then discovers that there is no gpio_device for the parent, returning NOTIFY_DONE without clearing the flag. Since gpiochip_hog_lines() skips any hog child whose of_node carries OF_POPULATED, the leaked flag makes the hog silently ignored when the chip is registered later. Applying an overlay containing both a GPIO controller node and its hog children - and populating devices only after the overlay apply completes - hits this on every boot; the hog is only applied if the chip driver is unbound (which clears the flag in the remove path) and rebound. Look up the parent gpio_device before claiming the node so that a hog attached ahead of its chip stays unclaimed and is picked up normally by gpiochip_hog_lines() at registration time. Signed-off-by: Abdurrahman Hussain Fixes: a23226b7c1f6 ("gpiolib: handle gpio-hogs only once") Cc: stable@vger.kernel.org Reviewed-by: Daniel Drake Link: https://patch.msgid.link/20260815-gpiolib-of-hog-flag-leak-v1-1-6126aac5f6f3@nexthop.ai Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 940b566946ce..f36e4b171fa7 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -788,13 +788,13 @@ static int of_gpio_notify(struct notifier_block *nb, unsigned long action, if (!of_property_read_bool(rd->dn, "gpio-hog")) return NOTIFY_DONE; /* not for us */ - if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) - return NOTIFY_DONE; - gdev = of_find_gpio_device_by_node(rd->dn->parent); if (!gdev) return NOTIFY_DONE; /* not for us */ + if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) + return NOTIFY_DONE; + ret = gpiochip_add_hog(gpio_device_get_chip(gdev), of_fwnode_handle(rd->dn)); if (ret < 0) { pr_err("%s: failed to add hogs for %pOF\n", __func__, From a20afec40ea1012659861e58374c1bcde2e18a43 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Tue, 1 Sep 2026 17:37:51 +0800 Subject: [PATCH 0116/1317] ASoC: rt721-sdca: Adjust latency control to fix no-sound issue Adjust latency control in speaker preset to fix no-sound issue. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260901093751.2962786-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index 159c35d19dba..b1c3dc060c24 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -206,6 +206,7 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) regmap_write(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); + regmap_write(rt721->regmap, 0x2f5d, 0x1); } static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) From 3bb3e80faf21e432f6e6d89c55fb587c323b8813 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 1 Sep 2026 14:11:12 +0100 Subject: [PATCH 0117/1317] firmware: arm_ffa: Tear down driver during shutdown The platform core invokes a driver's shutdown callback, rather than its remove callback, while preparing devices for a normal kexec. Without a shutdown callback, the FF-A driver leaves notifications, partition devices, and the RX/TX mapping active before the replacement kernel is booted. Use ffa_remove() for shutdown so the existing cleanup runs before a normal kexec and other orderly system shutdowns. Reported-by: Nat Gurumoorthy Closes: https://lore.kernel.org/all/20260729162731.1383875-1-natg@google.com/ Reported-by: Carol L Soto Closes: https://lore.kernel.org/all/20260818224404.3694580-1-csoto@nvidia.com Reported-by: Maxi Saparov Closes: https://lore.kernel.org/all/20260826222337.73480-1-maxi.saparov@gmail.com Link: https://patch.msgid.link/20260901131112.3437516-1-sudeep.holla@kernel.org Tested-by: Carol L Soto Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 8654b3365c9b..28abc808fd32 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -2225,6 +2225,7 @@ static void ffa_remove(struct platform_device *pdev) static struct platform_driver ffa_driver = { .probe = ffa_probe, .remove = ffa_remove, + .shutdown = ffa_remove, .driver = { .name = FFA_PLATFORM_NAME, }, From 00aef6b609bf231b4213704b602250ca53f95897 Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Tue, 1 Sep 2026 15:53:26 +0530 Subject: [PATCH 0118/1317] ASoC: amd: yc: Add DMI entry for HP 255R G10 laptop The HP 255R G10 laptop has an internal DMIC connected to the AMD ACP6x audio coprocessor. Add a DMI quirk entry so the internal microphone is properly detected on this model. Reported-by: eraleexxx@gmail.com Signed-off-by: Syed Saba Kareem Link: https://patch.msgid.link/20260901102338.26403-1-syed.sabakareem@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 21d7ec0e7dbf..c64c727e1034 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -892,6 +892,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "TM2423"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP 255R 15.6 inch G10 Notebook PC"), + } + }, {} }; From 6d49beec658f61801e79019fd73691b17252dee3 Mon Sep 17 00:00:00 2001 From: Ian Luites Date: Mon, 31 Aug 2026 08:25:47 +0000 Subject: [PATCH 0119/1317] soundwire: dmi-quirks: Disable ghost Realtek on Asus GX651AX The Asus ROG Zephyrus Duo GX651AX exposes a Realtek RT722 device in ACPI which does not exist in the physical hardware. The device remains unattached while the CS42L43 and both CS35L56 devices attach successfully. This confuses the function topology machine driver into creating duplicate DAI links named SDW3-Playback-SimpleJack, and the sof_sdw probe fails with error -12. Add a model-specific quirk to remove the ghost RT722 device. Fixes: 45cf24da0a10 ("ASoC: Intel: soc-acpi-intel-ptl-match: Remove unnecessary cs42l43 match") Cc: stable@vger.kernel.org # 7.2.x Assisted-by: LLM Signed-off-by: Ian Luites Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260831082534.224716-1-ian@luites.com Signed-off-by: Vinod Koul --- drivers/soundwire/dmi-quirks.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soundwire/dmi-quirks.c b/drivers/soundwire/dmi-quirks.c index 768255dd12db..62fa64b22412 100644 --- a/drivers/soundwire/dmi-quirks.c +++ b/drivers/soundwire/dmi-quirks.c @@ -200,6 +200,13 @@ static const struct dmi_system_id adr_remap_quirk_table[] = { }, .driver_data = (void *)ghost_realtek, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), + DMI_MATCH(DMI_BOARD_NAME, "GX651AX"), + }, + .driver_data = (void *)ghost_realtek, + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), From 4cb4aead98380fc0b1b0500a1c49d35724854067 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 1 Sep 2026 18:49:46 +0800 Subject: [PATCH 0120/1317] ASoC: codecs: ES8326: issue about capture pop This RFC asks how to resolve the 'capture pop noise' issue. The ES8326's capture pop noise occurs when LRCK is active. While the pop noise can be effectively reduced through codec configuration, it cannot be completely eliminated. So I decided to create a workqueue. unmute the ADC once the LRCK becomes active. Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260901104946.20114-1-zhangyi@everest-semi.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8326.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index c5460589a88b..b6eadc2e9659 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -26,6 +26,7 @@ struct es8326_priv { struct snd_soc_component *component; struct delayed_work jack_detect_work; struct delayed_work button_press_work; + struct delayed_work capture_pop_work; struct snd_soc_jack *jack; int irq; /* The lock protects the situation that an irq is generated @@ -628,6 +629,7 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) regmap_update_bits(es8326->regmap, ES8326_HP_DRIVER_REF, 0x30, 0x00); } else { + cancel_delayed_work_sync(&es8326->capture_pop_work); regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, 0x0F, 0x0F); if (es8326->version > ES8326_VERSION_B) { @@ -666,8 +668,9 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x70); regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x00); } - regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, - 0x0F, 0x00); + + queue_delayed_work(system_dfl_wq, &es8326->capture_pop_work, + msecs_to_jiffies(40)); } } return 0; @@ -773,6 +776,15 @@ static void es8326_disable_micbias(struct snd_soc_component *component) snd_soc_dapm_mutex_unlock(dapm); } +static void es8326_capture_pop_handler(struct work_struct *work) +{ + struct es8326_priv *es8326 = + container_of(work, struct es8326_priv, capture_pop_work.work); + + regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, + 0x0F, 0x00); +} + /* * For button detection, set the following in soundcard * snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); @@ -1140,6 +1152,7 @@ static int es8326_suspend(struct snd_soc_component *component) struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); cancel_delayed_work_sync(&es8326->jack_detect_work); + cancel_delayed_work_sync(&es8326->capture_pop_work); es8326_disable_micbias(component); es8326->calibrated = false; regmap_write(es8326->regmap, ES8326_CLK_MUX, 0x2d); @@ -1291,6 +1304,8 @@ static int es8326_i2c_probe(struct i2c_client *i2c) es8326_jack_detect_handler); INIT_DELAYED_WORK(&es8326->button_press_work, es8326_jack_button_handler); + INIT_DELAYED_WORK(&es8326->capture_pop_work, + es8326_capture_pop_handler); /* ES8316 is level-based while ES8326 is edge-based */ ret = devm_request_threaded_irq(&i2c->dev, es8326->irq, NULL, es8326_irq, IRQF_TRIGGER_RISING | IRQF_ONESHOT, From 6809da6e9c08ccc9a09cb0a48c61471679274aaa Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 18 Aug 2026 17:10:00 +0800 Subject: [PATCH 0121/1317] perf: RISC-V: store available counter mask as bitmap The available-counter mask was a single unsigned long, but iteration uses RISCV_MAX_COUNTERS, which is 64. On RV32 that reads past the object. Filling with an unsigned-long bit at index 32 and above is also wrong. Use DECLARE_BITMAP and set_bit/bitmap helpers. Walk each bitmap word into CFG_MATCH when checking events, when allocating an index, and when stopping all counters. Set the counter base to i times BITS_PER_LONG. Share the CFG_MATCH ecall through a small helper so the 32-bit argument split is not duplicated. On qemu-system-riscv32 the probe bitmap has bits above XLEN set, so the first word alone is not enough. Fixes: e9991434596f ("RISC-V: Add perf platform driver based on SBI PMU extension") Assisted-by: DeepSeek:deepseek-v3 Signed-off-by: Xixin Liu Link: https://patch.msgid.link/prpmask02cmap.v2.1786434000.git.liuxixin@kylinos.cn Cc: stable@kernel.org [pjw@kernel.org: updated to apply; fixed checkpatch.pl issues] Signed-off-by: Paul Walmsley --- drivers/perf/riscv_pmu_legacy.c | 5 +- drivers/perf/riscv_pmu_sbi.c | 88 +++++++++++++++++++++++---------- include/linux/perf/riscv_pmu.h | 2 +- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/drivers/perf/riscv_pmu_legacy.c b/drivers/perf/riscv_pmu_legacy.c index 4d6461d6a74f..1b8e4789cb3c 100644 --- a/drivers/perf/riscv_pmu_legacy.c +++ b/drivers/perf/riscv_pmu_legacy.c @@ -110,8 +110,9 @@ static void pmu_legacy_init(struct riscv_pmu *pmu) { pr_info("Legacy PMU implementation is available\n"); - pmu->cmask = BIT(RISCV_PMU_LEGACY_CYCLE) | - BIT(RISCV_PMU_LEGACY_INSTRET); + bitmap_zero(pmu->cmask, RISCV_MAX_COUNTERS); + set_bit(RISCV_PMU_LEGACY_CYCLE, pmu->cmask); + set_bit(RISCV_PMU_LEGACY_INSTRET, pmu->cmask); pmu->ctr_start = pmu_legacy_ctr_start; pmu->ctr_stop = NULL; pmu->event_map = pmu_legacy_event_map; diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c index 8ea5ae617347..2c50a6ba2a80 100644 --- a/drivers/perf/riscv_pmu_sbi.c +++ b/drivers/perf/riscv_pmu_sbi.c @@ -97,7 +97,7 @@ static unsigned int riscv_pmu_irq_mask; static unsigned int riscv_pmu_irq; /* Cache the available counters in a bitmask */ -static unsigned long cmask; +static DECLARE_BITMAP(cmask, RISCV_MAX_COUNTERS); static int pmu_event_find_cache(u64 config); struct sbi_pmu_event_data { @@ -359,16 +359,38 @@ static int pmu_sbi_check_event_info(void) return result; } +static struct sbiret pmu_sbi_ctr_cfg_match(unsigned long cbase, + unsigned long ctr_mask, + unsigned long cflags, + unsigned long event_idx, + u64 config) +{ +#if defined(CONFIG_32BIT) + return sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, + ctr_mask, cflags, event_idx, config, config >> 32); +#else + return sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, + ctr_mask, cflags, event_idx, config, 0); +#endif +} + static void pmu_sbi_check_event(struct sbi_pmu_event_data *edata) { - struct sbiret ret; + struct sbiret ret = { .error = SBI_ERR_NOT_SUPPORTED }; + int i; - ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, - 0, cmask, 0, edata->event_idx, 0, 0); - if (!ret.error) { - sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, - ret.value, 0x1, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0); - } else if (ret.error == SBI_ERR_NOT_SUPPORTED) { + for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) { + if (!cmask[i]) + continue; + ret = pmu_sbi_ctr_cfg_match(i * BITS_PER_LONG, cmask[i], 0, + edata->event_idx, 0); + if (!ret.error) { + sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, + ret.value, 0x1, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0); + return; + } + } + if (ret.error == SBI_ERR_NOT_SUPPORTED) { /* This event cannot be monitored by any counter */ edata->event_idx = -ENOENT; } @@ -488,10 +510,10 @@ int riscv_pmu_get_hpm_info(u32 *hw_ctr_width, u32 *num_hw_ctr) union sbi_pmu_ctr_info *info; u32 hpm_width = 0, hpm_count = 0; - if (!cmask) + if (bitmap_empty(cmask, RISCV_MAX_COUNTERS)) return -EINVAL; - for_each_set_bit(i, &cmask, RISCV_MAX_COUNTERS) { + for_each_set_bit(i, cmask, RISCV_MAX_COUNTERS) { info = &pmu_ctr_list[i]; if (!info) continue; @@ -540,8 +562,8 @@ static int pmu_sbi_ctr_get_idx(struct perf_event *event) struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu); struct cpu_hw_events *cpuc = this_cpu_ptr(rvpmu->hw_events); struct sbiret ret; - int idx; - uint64_t cbase = 0, cmask = rvpmu->cmask; + int idx, i; + u64 cbase = 0, cmask = 0; unsigned long cflags = 0; cflags = pmu_sbi_get_filter_flags(event); @@ -562,14 +584,21 @@ static int pmu_sbi_ctr_get_idx(struct perf_event *event) } /* retrieve the available counter index */ -#if defined(CONFIG_32BIT) - ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, - cmask, cflags, hwc->event_base, hwc->config, - hwc->config >> 32); -#else - ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, - cmask, cflags, hwc->event_base, hwc->config, 0); -#endif + if (cmask) { + ret = pmu_sbi_ctr_cfg_match(cbase, cmask, cflags, hwc->event_base, + hwc->config); + } else { + ret.error = SBI_ERR_NOT_SUPPORTED; + for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) { + if (!rvpmu->cmask[i]) + continue; + cbase = i * BITS_PER_LONG; + ret = pmu_sbi_ctr_cfg_match(cbase, rvpmu->cmask[i], cflags, + hwc->event_base, hwc->config); + if (!ret.error) + break; + } + } if (ret.error) { pr_debug("Not able to find a counter for event %lx config %llx\n", hwc->event_base, hwc->config); @@ -577,7 +606,7 @@ static int pmu_sbi_ctr_get_idx(struct perf_event *event) } idx = ret.value; - if (!test_bit(idx, &rvpmu->cmask) || !pmu_ctr_list[idx].value) + if (!test_bit(idx, rvpmu->cmask) || !pmu_ctr_list[idx].value) return -ENOENT; /* Additional sanity check for the counter id */ @@ -881,7 +910,7 @@ static int pmu_sbi_get_ctrinfo(int nctr, unsigned long *mask) /* The logical counter ids are not expected to be contiguous */ continue; - *mask |= BIT(i); + set_bit(i, mask); cinfo.value = ret.value; if (cinfo.type == SBI_PMU_CTR_TYPE_FW) @@ -898,12 +927,19 @@ static int pmu_sbi_get_ctrinfo(int nctr, unsigned long *mask) static inline void pmu_sbi_stop_all(struct riscv_pmu *pmu) { + int i; + /* * No need to check the error because we are disabling all the counters * which may include counters that are not enabled yet. */ - sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, - 0, pmu->cmask, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0); + for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) { + if (!pmu->cmask[i]) + continue; + sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, + i * BITS_PER_LONG, pmu->cmask[i], + SBI_PMU_STOP_FLAG_RESET, 0, 0, 0); + } } static inline void pmu_sbi_stop_hw_ctrs(struct riscv_pmu *pmu) @@ -1451,7 +1487,7 @@ static int pmu_sbi_device_probe(struct platform_device *pdev) } /* cache all the information about counters now */ - if (pmu_sbi_get_ctrinfo(num_counters, &cmask)) + if (pmu_sbi_get_ctrinfo(num_counters, cmask)) goto out_free; ret = pmu_sbi_setup_irqs(pmu, pdev); @@ -1464,7 +1500,7 @@ static int pmu_sbi_device_probe(struct platform_device *pdev) pmu->pmu.attr_groups = riscv_pmu_attr_groups; pmu->pmu.parent = &pdev->dev; - pmu->cmask = cmask; + bitmap_copy(pmu->cmask, cmask, RISCV_MAX_COUNTERS); pmu->ctr_start = pmu_sbi_ctr_start; pmu->ctr_stop = pmu_sbi_ctr_stop; pmu->event_map = pmu_sbi_event_map; diff --git a/include/linux/perf/riscv_pmu.h b/include/linux/perf/riscv_pmu.h index f82a28040594..ecaa40370830 100644 --- a/include/linux/perf/riscv_pmu.h +++ b/include/linux/perf/riscv_pmu.h @@ -55,7 +55,7 @@ struct riscv_pmu { irqreturn_t (*handle_irq)(int irq_num, void *dev); - unsigned long cmask; + DECLARE_BITMAP(cmask, RISCV_MAX_COUNTERS); u64 (*ctr_read)(struct perf_event *event); int (*ctr_get_idx)(struct perf_event *event); int (*ctr_get_width)(int idx); From 248dbaf7770c0843702355a9fb724a882c669062 Mon Sep 17 00:00:00 2001 From: JinRui Date: Tue, 11 Aug 2026 08:15:13 +0000 Subject: [PATCH 0122/1317] riscv: report Zfhmin/Zvfhmin when Zfh/Zvfh are present The RISC-V ISA manual specifies that Zfh implies Zfhmin, a normative rule clarified in https://github.com/riscv/riscv-isa-manual/pull/3070. Zvfh likewise implies Zvfhmin, as stated by the vector extension specification. The kernel currently reports ZFH and ZFHMIN (and ZVFH and ZVFHMIN) as independent hwprobe bits derived only from what the device tree declares. Platforms that declare just "zfh" (Zfh being a superset that already contains all Zfhmin instructions) therefore report RISCV_HWPROBE_EXT_ZFHMIN=0, which breaks userspace RVA23 conformance checks (e.g. snapd installing core26 on riscv64). Use the existing superset mechanism to set the implied subset bits: - zfh implies zfhmin - zvfh implies zvfhmin Add a hwprobe selftest asserting the implication holds and update the hwprobe documentation accordingly. This is complementary to the rva23u64 base behavior discussion: the RVA23 conformance query proposed there is derived from the per-extension bits fixed here, so correct EXT_0 reporting is a prerequisite for it to work on harts whose device tree declares only "zfh". Tested on a RISC-V QEMU VM whose device tree only declares "zfh" and "zvfh": with this change both /proc/cpuinfo and the hwprobe RISCV_HWPROBE_KEY_IMA_EXT_0 bitmap report ZFHMIN and ZVFHMIN, and the hwprobe selftest (including the new implication check) passes. Link: https://lore.kernel.org/kvm-riscv/20260206002349.96740-1-andrew.jones@oss.qualcomm.com/ Signed-off-by: JinRui Link: https://patch.msgid.link/7190E4DB338251C3+20260811081513.2849980-1-jinrui@haiwei.tech [pjw@kernel.org: trimmed superfluous blank line in tags] Signed-off-by: Paul Walmsley --- Documentation/arch/riscv/hwprobe.rst | 8 +++++--- arch/riscv/kernel/cpufeature.c | 20 +++++++++++++++++-- .../testing/selftests/riscv/hwprobe/hwprobe.c | 20 ++++++++++++++++++- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst index 893e1a1215d2..bb1e0cbab36f 100644 --- a/Documentation/arch/riscv/hwprobe.rst +++ b/Documentation/arch/riscv/hwprobe.rst @@ -155,7 +155,8 @@ The following keys are defined: defined in version 1.0 of the RISC-V Cryptography Extensions Volume II. * :c:macro:`RISCV_HWPROBE_EXT_ZFH`: The Zfh extension version 1.0 is supported - as defined in the RISC-V ISA manual. + as defined in the RISC-V ISA manual. Zfh is a superset of Zfhmin, so + RISCV_HWPROBE_EXT_ZFHMIN is reported whenever RISCV_HWPROBE_EXT_ZFH is. * :c:macro:`RISCV_HWPROBE_EXT_ZFHMIN`: The Zfhmin extension version 1.0 is supported as defined in the RISC-V ISA manual. @@ -164,8 +165,9 @@ The following keys are defined: is supported as defined in the RISC-V ISA manual. * :c:macro:`RISCV_HWPROBE_EXT_ZVFH`: The Zvfh extension is supported as - defined in the RISC-V Vector manual starting from commit e2ccd0548d6c - ("Remove draft warnings from Zvfh[min]"). + defined in the RISC-V Vector manual starting from commit e2ccd0548d6c + ("Remove draft warnings from Zvfh[min]"). Zvfh is a superset of Zvfhmin, + so RISCV_HWPROBE_EXT_ZVFHMIN is reported whenever RISCV_HWPROBE_EXT_ZVFH is. * :c:macro:`RISCV_HWPROBE_EXT_ZVFHMIN`: The Zvfhmin extension is supported as defined in the RISC-V Vector manual starting from commit e2ccd0548d6c diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c index d2ec96843456..61d21f714830 100644 --- a/arch/riscv/kernel/cpufeature.c +++ b/arch/riscv/kernel/cpufeature.c @@ -412,6 +412,19 @@ static const unsigned int riscv_zvbb_exts[] = { RISCV_ISA_EXT_ZVKB }; +/* + * The RISC-V ISA manual specifies that Zfh implies Zfhmin and Zvfh implies + * Zvfhmin. Report the implied subset extensions whenever the supersets are + * detected (see https://github.com/riscv/riscv-isa-manual/pull/3070). + */ +static const unsigned int riscv_zfh_exts[] = { + RISCV_ISA_EXT_ZFHMIN +}; + +static const unsigned int riscv_zvfh_exts[] = { + RISCV_ISA_EXT_ZVFHMIN +}; + #define RISCV_ISA_EXT_ZVE64F_IMPLY_LIST \ RISCV_ISA_EXT_ZVE64X, \ RISCV_ISA_EXT_ZVE32F, \ @@ -550,7 +563,8 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = { __RISCV_ISA_EXT_DATA(zawrs, RISCV_ISA_EXT_ZAWRS), __RISCV_ISA_EXT_DATA_VALIDATE(zfa, RISCV_ISA_EXT_ZFA, riscv_ext_f_depends), __RISCV_ISA_EXT_DATA_VALIDATE(zfbfmin, RISCV_ISA_EXT_ZFBFMIN, riscv_ext_f_depends), - __RISCV_ISA_EXT_DATA_VALIDATE(zfh, RISCV_ISA_EXT_ZFH, riscv_ext_f_depends), + __RISCV_ISA_EXT_SUPERSET_VALIDATE(zfh, RISCV_ISA_EXT_ZFH, + riscv_zfh_exts, riscv_ext_f_depends), __RISCV_ISA_EXT_DATA_VALIDATE(zfhmin, RISCV_ISA_EXT_ZFHMIN, riscv_ext_f_depends), __RISCV_ISA_EXT_DATA(zca, RISCV_ISA_EXT_ZCA), __RISCV_ISA_EXT_DATA_VALIDATE(zcb, RISCV_ISA_EXT_ZCB, riscv_ext_zca_depends), @@ -586,7 +600,9 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = { __RISCV_ISA_EXT_SUPERSET_VALIDATE(zve64x, RISCV_ISA_EXT_ZVE64X, riscv_zve64x_exts, riscv_ext_vector_x_validate), __RISCV_ISA_EXT_DATA_VALIDATE(zvfbfmin, RISCV_ISA_EXT_ZVFBFMIN, riscv_vector_f_validate), __RISCV_ISA_EXT_DATA_VALIDATE(zvfbfwma, RISCV_ISA_EXT_ZVFBFWMA, riscv_ext_zvfbfwma_validate), - __RISCV_ISA_EXT_DATA(zvfh, RISCV_ISA_EXT_ZVFH), + __RISCV_ISA_EXT_SUPERSET_VALIDATE(zvfh, RISCV_ISA_EXT_ZVFH, + riscv_zvfh_exts, + riscv_ext_vector_float_validate), __RISCV_ISA_EXT_DATA(zvfhmin, RISCV_ISA_EXT_ZVFHMIN), __RISCV_ISA_EXT_DATA_VALIDATE(zvkb, RISCV_ISA_EXT_ZVKB, riscv_ext_vector_crypto_validate), __RISCV_ISA_EXT_DATA_VALIDATE(zvkg, RISCV_ISA_EXT_ZVKG, riscv_ext_vector_crypto_validate), diff --git a/tools/testing/selftests/riscv/hwprobe/hwprobe.c b/tools/testing/selftests/riscv/hwprobe/hwprobe.c index 54c435af9923..eca4441ee77f 100644 --- a/tools/testing/selftests/riscv/hwprobe/hwprobe.c +++ b/tools/testing/selftests/riscv/hwprobe/hwprobe.c @@ -9,7 +9,7 @@ int main(int argc, char **argv) long out; ksft_print_header(); - ksft_set_plan(5); + ksft_set_plan(6); /* Fake the CPU_SET ops. */ cpus = -1; @@ -62,5 +62,23 @@ int main(int argc, char **argv) pairs[1].key == 1 && pairs[1].value != 0xAAAA, "Unknown key overwritten with -1 and doesn't block other elements\n"); + pairs[0].key = RISCV_HWPROBE_KEY_IMA_EXT_0; + out = riscv_hwprobe(pairs, 1, 0, 0, 0); + if (out != 0) + ksft_exit_fail_msg("hwprobe(IMA_EXT_0) failed with %ld\n", out); + + /* + * The RISC-V ISA manual specifies that Zfh implies Zfhmin and Zvfh + * implies Zvfhmin, so hwprobe must report the implied subset + * extensions whenever the supersets are present. + */ + if ((pairs[0].value & RISCV_HWPROBE_EXT_ZFH) && + !(pairs[0].value & RISCV_HWPROBE_EXT_ZFHMIN)) + ksft_exit_fail_msg("Zfh reported without implied Zfhmin\n"); + if ((pairs[0].value & RISCV_HWPROBE_EXT_ZVFH) && + !(pairs[0].value & RISCV_HWPROBE_EXT_ZVFHMIN)) + ksft_exit_fail_msg("Zvfh reported without implied Zvfhmin\n"); + ksft_test_result_pass("Zfh/Zvfh imply Zfhmin/Zvfhmin\n"); + ksft_finished(); } From f643f520c4c6998fa27dce90dd3b3ff6414e0bae Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 11 Aug 2026 11:51:00 +0800 Subject: [PATCH 0123/1317] perf: RISC-V: check cpu_hw_evt before dereference in overflow IRQ The overflow IRQ handler dereferences cpu_hw_evt before the null check. Move the check first. Defensive only; the cookie is valid on the normal path today. Fixes: a8625217a054 ("drivers/perf: riscv: Implement SBI PMU snapshot function") Assisted-by: DeepSeek:deepseek-v3 Signed-off-by: Xixin Liu Link: https://patch.msgid.link/fdd42c791752.v2.1786420235.git.liuxixin@kylinos.cn Signed-off-by: Paul Walmsley --- drivers/perf/riscv_pmu_sbi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c index 2c50a6ba2a80..2991dd92def2 100644 --- a/drivers/perf/riscv_pmu_sbi.c +++ b/drivers/perf/riscv_pmu_sbi.c @@ -1086,11 +1086,13 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev) u64 overflowed_ctrs = 0; struct cpu_hw_events *cpu_hw_evt = dev; u64 start_clock = sched_clock(); - struct riscv_pmu_snapshot_data *sdata = cpu_hw_evt->snapshot_addr; + struct riscv_pmu_snapshot_data *sdata; if (WARN_ON_ONCE(!cpu_hw_evt)) return IRQ_NONE; + sdata = cpu_hw_evt->snapshot_addr; + /* Firmware counter don't support overflow yet */ fidx = find_first_bit(cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS); if (fidx == RISCV_MAX_COUNTERS) { From 93a27367bacdc32e2cdb478597102175db3fb80c Mon Sep 17 00:00:00 2001 From: Ivy Lopez Date: Mon, 31 Aug 2026 19:37:46 -0600 Subject: [PATCH 0124/1317] riscv: hwprobe: simplify has_fpu() to check D extension only The kernel never supports D without F, since D depends on F. The D-extension flag is cleared during devicetree/ACPI parsing whenever F is not present, so has_fpu() checking either extension with '||' never actually produces a different result than checking D alone - F without D cannot occur in practice, and there is no observable impact on RISCV_HWPROBE_IMA_FD or userspace. Simplify has_fpu() to check D only, matching the expectations set elsewhere in the kernel for this dependency, rather than relying on a redundant OR condition. sys_hwprobe.c already calls has_fpu() and needs no changes. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221874 Suggested-by: Conor Dooley Suggested-by: Andreas Schwab Signed-off-by: Ivy Lopez Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20260901013746.19386-1-skunkolee@gmail.com [pjw@kernel.org: updated to apply] Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/switch_to.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/switch_to.h b/arch/riscv/include/asm/switch_to.h index 04f10a949066..123c89b694e8 100644 --- a/arch/riscv/include/asm/switch_to.h +++ b/arch/riscv/include/asm/switch_to.h @@ -61,8 +61,8 @@ static inline void __switch_to_fpu(struct task_struct *prev, static __always_inline bool has_fpu(void) { - return riscv_has_extension_likely(RISCV_ISA_EXT_F) || - riscv_has_extension_likely(RISCV_ISA_EXT_D); + /* D extension depends on F, so checking D alone is sufficient. */ + return riscv_has_extension_likely(RISCV_ISA_EXT_D); } #else static __always_inline bool has_fpu(void) { return false; } From 817ecd588fa5820527ee3affa43b5116276d2520 Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 24 Aug 2026 13:54:14 +0800 Subject: [PATCH 0125/1317] dt-bindings: riscv: cpus: Fix yamllint style issues Wrap an overlong description line and normalize the inline dependency lists to satisfy yamllint. Signed-off-by: Jia Wang Acked-by: Conor Dooley Link: https://patch.msgid.link/20260824-ultrarisc-dts-v1-1-61ab7aebe9e5@ultrarisc.com Signed-off-by: Paul Walmsley --- Documentation/devicetree/bindings/riscv/cpus.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/riscv/cpus.yaml b/Documentation/devicetree/bindings/riscv/cpus.yaml index 5feeb2203050..0da219ae6769 100644 --- a/Documentation/devicetree/bindings/riscv/cpus.yaml +++ b/Documentation/devicetree/bindings/riscv/cpus.yaml @@ -117,8 +117,8 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32 description: VLEN/8, the vector register length in bytes. This property is required on - thead systems where the vector register length is not identical on all harts, or - the vlenb CSR is not available. + thead systems where the vector register length is not identical on all + harts, or the vlenb CSR is not available. # RISC-V has multiple properties for cache op block sizes as the sizes # differ between individual CBO extensions @@ -151,8 +151,8 @@ anyOf: - riscv,isa-base dependencies: - riscv,isa-base: [ "riscv,isa-extensions" ] - riscv,isa-extensions: [ "riscv,isa-base" ] + riscv,isa-base: ["riscv,isa-extensions"] + riscv,isa-extensions: ["riscv,isa-base"] required: - interrupt-controller From 74e26c692c40565448b8a1d1398c69e221a299cf Mon Sep 17 00:00:00 2001 From: Yukai Wu Date: Sat, 29 Aug 2026 10:23:27 +0800 Subject: [PATCH 0126/1317] docs/zh_CN: Update arch/riscv/patch-acceptance.rst translation Update Documentation/arch/riscv/patch-acceptance.rst translation. Update the translation through commit ed843ae947f8 ("docs: move riscv under arch") Signed-off-by: Yukai Wu Reviewed-by: Dongliang Mu Acked-by: Weijie Yuan Link: https://patch.msgid.link/20260829022344.192491-1-xiaoyewuz.Ruster@gmail.com Signed-off-by: Paul Walmsley --- .../zh_CN/arch/riscv/patch-acceptance.rst | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/Documentation/translations/zh_CN/arch/riscv/patch-acceptance.rst b/Documentation/translations/zh_CN/arch/riscv/patch-acceptance.rst index c8eb230ca8ee..20b91d0433a9 100644 --- a/Documentation/translations/zh_CN/arch/riscv/patch-acceptance.rst +++ b/Documentation/translations/zh_CN/arch/riscv/patch-acceptance.rst @@ -15,19 +15,41 @@ arch/riscv 开发者维护指南 概述 ---- -RISC-V指令集体系结构是公开开发的: +RISC-V 指令集体系结构是公开开发的: 正在进行的草案可供所有人查看和测试实现。新模块或者扩展草案可能会在开发过程中发 -生更改---有时以不兼容的方式对以前的草案进行更改。这种灵活性可能会给RISC-V Linux -维护者带来挑战。Linux开发过程更喜欢经过良好检查和测试的代码,而不是试验代码。我 -们希望推广同样的规则到即将被内核合并的RISC-V相关代码。 +生更改 --- 有时以不兼容的方式对以前的草案进行更改。这种灵活性可能会给 RISC-V +Linux 维护者带来挑战。Linux 维护者不赞成频繁的变更,且 Linux 开发过程更喜欢经过 +良好检查和测试的代码,而不是试验代码。我们希望推广同样的规则到即将被内核合并的 +RISC-V 相关代码。 + +Patchwork +--------- + +RISC-V 有一个 patchwork 实例,可以在那里查看补丁的状态: + + https://patchwork.kernel.org/project/linux-riscv/list/ + +如果你的补丁不在默认视图中出现,那么 RISC-V 维护者很有可能已要求修改,或者希望 +将其应用到另一个代码树上。 + +自动化流程会在该 patchwork 实例上运行,在每个补丁到达时立刻对其进行构建/测试。 +自动化流程会根据补丁是否被识别为修复,选用 RISC-V `for-next` 或 `fixes` 分支 +当前的 HEAD;若上述均应用失败,则使用 RISC-V `master` 分支。补丁系列被应用到的具 +体提交将标注在 patchwork 上。任何检查未通过的补丁通常不会被应用,并且在大多数情 +况下将需要重新提交。 附加的提交检查单 ---------------- -我们仅接受相关标准已经被RISC-V基金会标准为“已批准”或“已冻结”的扩展或模块的补丁。 -(开发者当然可以维护自己的Linux内核树,其中包含所需代码扩展草案的代码。) +我们仅接受针对新模块或扩展的补丁,前提是这些模块或扩展的规范被列为未来不太可能发 +生不兼容的变更。对于来自 RISC-V 基金会的规范,这意味着“已冻结”或“已批准”,对于 +UEFI 论坛的规范,这意味着已发布的 ECR。(开发者当然可以维护自己的 Linux 内核树, +其中包含他们所需的任何扩展草案的代码。) -此外,RISC-V规范允许爱好者创建自己的自定义扩展。这些自定义拓展不需要通过RISC-V -基金会的任何审核或批准。为了避免将爱好者一些特别的RISC-V拓展添加进内核代码带来 -的维护复杂性和对性能的潜在影响,我们将只接受RISC-V基金会正式冻结或批准的的扩展 -补丁。(开发者当然可以维护自己的Linux内核树,其中包含他们想要的任何自定义扩展 -的代码。) +此外,RISC-V 规范允许实现者创建自己的自定义扩展。这些自定义扩展不需要通过 RISC-V +基金会的任何审核或批准流程。为了避免因添加实现者特定的 RISC-V 扩展带来的维护复杂 +性和对性能的潜在影响,我们将只考虑符合以下任一条件的扩展补丁: + +- 已由 RISC-V 基金会正式冻结或批准 +- 已按照标准 Linux 惯例,在广泛可用的硬件中实现 + +(实现者当然可以维护自己的 Linux 内核树,其中包含他们所需的任何自定义扩展的代码。) From 2464ac8a6ff8d8a18b50088eb1d1d638f9cee2f1 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 13 Aug 2026 09:53:04 +0200 Subject: [PATCH 0127/1317] kselftest/riscv: Replace __ASSEMBLY__ with __ASSEMBLER__ While the GCC and Clang compilers already define __ASSEMBLER__ automatically when compiling assembly code, __ASSEMBLY__ is a macro that only gets defined by the Makefiles in the kernel. This can be very confusing when switching between userspace and kernelspace coding, or when dealing with uapi headers that rather should use __ASSEMBLER__ instead. So let's standardize now on the __ASSEMBLER__ macro that is provided by the compilers. Signed-off-by: Thomas Huth Reviewed-by: Nick Desaulniers Link: https://patch.msgid.link/20260813075304.75988-1-thuth@redhat.com Signed-off-by: Paul Walmsley --- tools/testing/selftests/riscv/cfi/cfi_rv_test.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/riscv/cfi/cfi_rv_test.h b/tools/testing/selftests/riscv/cfi/cfi_rv_test.h index 1c8043f2b778..184df6903d01 100644 --- a/tools/testing/selftests/riscv/cfi/cfi_rv_test.h +++ b/tools/testing/selftests/riscv/cfi/cfi_rv_test.h @@ -56,7 +56,7 @@ #define CSR_SSP 0x011 -#ifdef __ASSEMBLY__ +#ifdef __ASSEMBLER__ #define __ASM_STR(x) x #else #define __ASM_STR(x) #x From 839f075aabdf5c21048f9801b87c0b841dd3d064 Mon Sep 17 00:00:00 2001 From: Jingbo Xu Date: Wed, 2 Sep 2026 09:50:39 +0800 Subject: [PATCH 0128/1317] erofs: add sysfs feature entry for xattr prefixes Let /sys/fs/erofs/features/xattr_prefixes advertise that this kernel supports the EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES on-disk format. Fixes: 6a318ccd7e08 ("erofs: enable long extended attribute name prefixes") Cc: stable@vger.kernel.org # 6.4+ Reviewed-by: Gao Xiang Signed-off-by: Jingbo Xu Signed-off-by: Gao Xiang --- Documentation/ABI/testing/sysfs-fs-erofs | 2 +- fs/erofs/sysfs.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-fs-erofs b/Documentation/ABI/testing/sysfs-fs-erofs index e4cf6fc6a106..0b8b4354e40b 100644 --- a/Documentation/ABI/testing/sysfs-fs-erofs +++ b/Documentation/ABI/testing/sysfs-fs-erofs @@ -5,7 +5,7 @@ Description: Shows all enabled kernel features. Supported features: compr_cfgs, big_pcluster, chunked_file, device_table, compr_head2, sb_chksum, ztailpacking, dedupe, fragments, - 48bit, metabox. + xattr_prefixes, 48bit, metabox. What: /sys/fs/erofs//sync_decompress Date: November 2021 diff --git a/fs/erofs/sysfs.c b/fs/erofs/sysfs.c index 6734483a440f..dfcec9376cd5 100644 --- a/fs/erofs/sysfs.c +++ b/fs/erofs/sysfs.c @@ -95,6 +95,7 @@ EROFS_ATTR_FEATURE(sb_chksum); EROFS_ATTR_FEATURE(ztailpacking); EROFS_ATTR_FEATURE(fragments); EROFS_ATTR_FEATURE(dedupe); +EROFS_ATTR_FEATURE(xattr_prefixes); EROFS_ATTR_FEATURE(48bit); EROFS_ATTR_FEATURE(metabox); @@ -108,6 +109,7 @@ static struct attribute *erofs_feat_attrs[] = { ATTR_LIST(ztailpacking), ATTR_LIST(fragments), ATTR_LIST(dedupe), + ATTR_LIST(xattr_prefixes), ATTR_LIST(48bit), ATTR_LIST(metabox), NULL, From 617d0d8d199ba1790c94310fd75a22d01c97a8d6 Mon Sep 17 00:00:00 2001 From: Nikhil Gurudasani Date: Sun, 30 Aug 2026 16:11:09 +0530 Subject: [PATCH 0129/1317] erofs: preserve LZMA decoders on resize failure The pool-resize path frees each stream's old decoder before allocating its replacement. If an allocation fails after some streams have already been replaced, the failed stream is put back on the list with state == NULL. z_erofs_lzma_max_dictsize is still advanced as if the whole pool had been resized. An existing LZMA mount can select the broken stream and pass NULL to xz_dec_microlzma_reset(). A retry at the same size also skip another resize attempt. Since the global maximum was advanced, thus, the invalid state is left unrepaired. Allocate each replacement before freeing the old decoder, temporarily retaining one old decoder during allocation. Stop at the first failure and advance z_erofs_lzma_max_dictsize only after all streams satisfy the request. Record each stream's dictionary capacity so retries can skip streams already enlarged before a partial failure. Fixes: 622ceaddb764 ("erofs: lzma compression support") Cc: stable@vger.kernel.org Signed-off-by: Nikhil Gurudasani Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang --- fs/erofs/decompressor_lzma.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/fs/erofs/decompressor_lzma.c b/fs/erofs/decompressor_lzma.c index 6b0cdb446c6a..9d15f94cbee1 100644 --- a/fs/erofs/decompressor_lzma.c +++ b/fs/erofs/decompressor_lzma.c @@ -5,6 +5,7 @@ struct z_erofs_lzma { struct z_erofs_lzma *next; struct xz_dec_microlzma *state; + unsigned int dict_size; u8 bounce[PAGE_SIZE]; }; @@ -128,11 +129,19 @@ static int z_erofs_load_lzma_config(struct super_block *sb, err = 0; /* 2. walk each isolated stream and grow max dict_size if needed */ for (strm = head; strm; strm = strm->next) { + struct xz_dec_microlzma *state; + + if (strm->dict_size >= dict_size) + continue; + state = xz_dec_microlzma_alloc(XZ_PREALLOC, dict_size); + if (!state) { + err = -ENOMEM; + break; + } if (strm->state) xz_dec_microlzma_end(strm->state); - strm->state = xz_dec_microlzma_alloc(XZ_PREALLOC, dict_size); - if (!strm->state) - err = -ENOMEM; + strm->state = state; + strm->dict_size = dict_size; } /* 3. push back all to the global list and update max dict_size */ @@ -142,7 +151,8 @@ static int z_erofs_load_lzma_config(struct super_block *sb, spin_unlock(&z_erofs_lzma_lock); wake_up_all(&z_erofs_lzma_wq); - z_erofs_lzma_max_dictsize = dict_size; + if (!err) + z_erofs_lzma_max_dictsize = dict_size; mutex_unlock(&lzma_resize_mutex); return err; } From 9012da455ab9a05d8205b90d0ad7c8b526f89062 Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Thu, 6 Aug 2026 17:10:45 +0800 Subject: [PATCH 0130/1317] net: 6lowpan: fix mismatched comments Rename @_nexthdrlen to @_hdrlen and drop stale @nhc from lowpan_nhc_do_uncompression docs Signed-off-by: Chenguang Zhao Reviewed-by: Simon Horman Link: https://lore.kernel.org/20260806091045.1701326-1-chenguang.zhao@linux.dev Signed-off-by: Stefan Schmidt --- net/6lowpan/nhc.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/6lowpan/nhc.h b/net/6lowpan/nhc.h index ab7b4977c32b..c995029696d0 100644 --- a/net/6lowpan/nhc.h +++ b/net/6lowpan/nhc.h @@ -15,7 +15,7 @@ * @__nhc: variable name of the lowpan_nhc struct. * @_name: const char * of common header compression name. * @_nexthdr: ipv6 nexthdr field for the header compression. - * @_nexthdrlen: ipv6 nexthdr len for the reserved space. + * @_hdrlen: ipv6 nexthdr len for the reserved space. * @_id: one byte nhc id value. * @_idmask: one byte nhc id mask value. * @_uncompress: callback for uncompression call. @@ -102,7 +102,6 @@ int lowpan_nhc_do_compression(struct sk_buff *skb, const struct ipv6hdr *hdr, /** * lowpan_nhc_do_uncompression - calling uncompress callback for nhc * - * @nhc: 6LoWPAN nhc context, get by lowpan_nhc_by_ functions. * @skb: skb of 6LoWPAN header, skb->data should be pointed to nhc id value. * @dev: netdevice for print logging information. * @hdr: ipv6hdr for setting nexthdr value. From 29871903f3a38e75e896f9a644713859490cb85f Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Tue, 1 Sep 2026 20:43:47 +0800 Subject: [PATCH 0131/1317] sched_ext: Don't deliver duplicate ops.cgroup_set_idle() for same value ops.cgroup_set_idle() is documented to be invoked when a cgroup transitions between idle and non-idle states, and scx_group_set_weight() already skips value-preserving writes. scx_group_set_idle() delivers every write unconditionally, so rewriting an already-correct cpu.idle value feeds the BPF scheduler a transition callback each time, which toggle- or accounting-based schedulers miscount. Mirror the weight guard and only deliver on an actual change. Verified with a probe scheduler printing each callback: rewriting cpu.idle=1 twice on an already-idle cgroup delivered two callbacks before and none after. Fixes: 347ed2d566da ("sched/ext: Implement cgroup_set_idle() callback") Link: https://lore.kernel.org/r/b53c61a1-4d7d-4232-941f-d48b0563d4ed@linux.dev Signed-off-by: Tao Cui Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 901fb0f8b976..5f242ab69cf4 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4933,7 +4933,8 @@ void scx_group_set_idle(struct task_group *tg, bool idle) percpu_down_read(&scx_cgroup_ops_rwsem); sch = scx_tg_knob_sched(tg); - if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle)) + if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle) && + tg->scx.idle != idle) SCX_CALL_OP(sch, cgroup_set_idle, NULL, tg_cgrp(tg), idle); /* Update the task group's idle state */ From ff5891b266a7fc6a062710836be84f1cc19338b5 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 12 Aug 2026 06:17:14 +0000 Subject: [PATCH 0132/1317] ieee802154: cc2520: fix FIFOP work use-after-free The FIFOP interrupt handler queues cc2520_fifop_irqwork. On removal, cc2520_remove() only flushes the work. The devm-managed FIFOP IRQ remains active until after ->remove() returns and can queue the work again after that flush, allowing it to run after the private data is released. Disable the work with disable_work_sync() instead of flushing it, so the handler can no longer queue it once removal begins. Destroy the buffer mutex last, since the worker and the stop callback invoked through ieee802154_unregister_hw() both take it. Found by an in-house static analysis tool. Fixes: 0da6bc8cc341 ("ieee802154: cc2520: adds driver for TI CC2520 radio") Cc: stable@vger.kernel.org # v6.10+ Suggested-by: Miquel Raynal Reviewed-by: Miquel Raynal Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://lore.kernel.org/20260812061714.175966-1-fanwu01@zju.edu.cn Signed-off-by: Stefan Schmidt --- drivers/net/ieee802154/cc2520.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ieee802154/cc2520.c b/drivers/net/ieee802154/cc2520.c index 2b7034193a00..abfcfe07246a 100644 --- a/drivers/net/ieee802154/cc2520.c +++ b/drivers/net/ieee802154/cc2520.c @@ -1156,11 +1156,10 @@ static void cc2520_remove(struct spi_device *spi) { struct cc2520_private *priv = spi_get_drvdata(spi); - mutex_destroy(&priv->buffer_mutex); - flush_work(&priv->fifop_irqwork); - + disable_work_sync(&priv->fifop_irqwork); ieee802154_unregister_hw(priv->hw); ieee802154_free_hw(priv->hw); + mutex_destroy(&priv->buffer_mutex); } static const struct spi_device_id cc2520_ids[] = { From bf79662bc85e820ac3b846e2f347da29fbf6ac95 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 29 Aug 2026 18:07:23 +0800 Subject: [PATCH 0133/1317] ieee802154: 6lowpan: fix NULL dereference in lowpan_newlink TUNSETLINK allows a TUN device to change its link-layer type to ARPHRD_IEEE802154 without initializing ieee802154_ptr. lowpan_newlink() checks only the device type before dereferencing the pointer, so an RTM_NEWLINK request can trigger a NULL pointer dereference. Reject devices without ieee802154_ptr along with devices of the wrong type. Fixes: 51e0e5d8124e ("ieee802154: 6lowpan: remove multiple lowpan per wpan support") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Link: https://lore.kernel.org/0b715da69bd15a86ddc47dad5cf12da648211050.1787997209.git.zhilinz@nebusec.ai Signed-off-by: Stefan Schmidt --- net/ieee802154/6lowpan/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ieee802154/6lowpan/core.c b/net/ieee802154/6lowpan/core.c index 018929563c6b..6a8d6852cb93 100644 --- a/net/ieee802154/6lowpan/core.c +++ b/net/ieee802154/6lowpan/core.c @@ -150,7 +150,7 @@ static int lowpan_newlink(struct net_device *ldev, wdev = dev_get_by_index(dev_net(ldev), nla_get_u32(tb[IFLA_LINK])); if (!wdev) return -ENODEV; - if (wdev->type != ARPHRD_IEEE802154) { + if (wdev->type != ARPHRD_IEEE802154 || !wdev->ieee802154_ptr) { dev_put(wdev); return -EINVAL; } From 979d5b8de8ed4e1f997aef12da5694b99be7b871 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 9 Jul 2026 23:18:58 +0100 Subject: [PATCH 0134/1317] ieee802154: hwsim: serialize pib updates to fix double-free hwsim_update_pib() does an unserialized read-swap-free of phy->pib: pib_old = rtnl_dereference(phy->pib); ... rcu_assign_pointer(phy->pib, pib); kfree_rcu(pib_old, rcu); It assumes the RTNL is held, but ->set_channel is not always called under it: the mac802154 scan worker changes channels via drv_set_channel() without the RTNL. Such an update can race an RTNL-held one on the same phy; both read the same pib_old and both kfree_rcu() it, double-freeing the object. With SLUB percpu sheaves batching kfree_rcu(), this surfaces as a KASAN invalid-free in rcu_free_sheaf(). struct hwsim_phy has no lock for pib. Add one and make the swap atomic with rcu_replace_pointer() under it, dropping the misleading rtnl_dereference(). Reported-by: syzbot+60332fd095f8bb2946ad@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=60332fd095f8bb2946ad Fixes: f25da51fdc38 ("ieee802154: hwsim: add replacement for fakelb") Signed-off-by: David Carlier Cc: stable@vger.kernel.org Link: https://lore.kernel.org/20260709221858.158063-1-devnexen@gmail.com Signed-off-by: Stefan Schmidt --- drivers/net/ieee802154/mac802154_hwsim.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ieee802154/mac802154_hwsim.c b/drivers/net/ieee802154/mac802154_hwsim.c index 6daa0f198b9f..a9bd1555d2dc 100644 --- a/drivers/net/ieee802154/mac802154_hwsim.c +++ b/drivers/net/ieee802154/mac802154_hwsim.c @@ -72,6 +72,8 @@ struct hwsim_phy { struct ieee802154_hw *hw; u32 idx; + /* Serializes phy->pib_updates. */ + spinlock_t pib_lock; struct hwsim_pib __rcu *pib; bool suspended; @@ -102,8 +104,6 @@ static int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel, if (!pib) return -ENOMEM; - pib_old = rtnl_dereference(phy->pib); - pib->page = page; pib->channel = channel; pib->filt.short_addr = filt->short_addr; @@ -112,7 +112,10 @@ static int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel, pib->filt.pan_coord = filt->pan_coord; pib->filt_level = filt_level; - rcu_assign_pointer(phy->pib, pib); + spin_lock_bh(&phy->pib_lock); + pib_old = rcu_replace_pointer(phy->pib, pib, + lockdep_is_held(&phy->pib_lock)); + spin_unlock_bh(&phy->pib_lock); kfree_rcu(pib_old, rcu); return 0; } @@ -952,6 +955,7 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev, goto err_pib; } + spin_lock_init(&phy->pib_lock); pib->channel = 13; pib->filt.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST); pib->filt.pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST); From 0433632bbe8d279e978f3f85212f36281a89946c Mon Sep 17 00:00:00 2001 From: John Ogness Date: Tue, 1 Sep 2026 11:37:42 +0206 Subject: [PATCH 0135/1317] printk/nbcon: Flush nbcon_irq_work in nbcon_free() Ensure any pending nbcon_irq_work is flushed before allowing the console to be recycled. Signed-off-by: John Ogness Reviewed-by: Petr Mladek Link: https://patch.msgid.link/20260901093245.344455-2-john.ogness@linutronix.de Signed-off-by: Petr Mladek --- kernel/printk/nbcon.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 4b03b019cd5e..354f274d8a42 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1837,6 +1837,8 @@ void nbcon_free(struct console *con) /* Synchronize the kthread stop. */ lockdep_assert_console_list_lock_held(); + irq_work_sync(&con->irq_work); + if (printk_kthreads_running) { nbcon_kthread_stop(con); From 560f4deda32785e260056200f8bb911c475c5b88 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Tue, 1 Sep 2026 11:37:43 +0206 Subject: [PATCH 0136/1317] printk/nbcon: Change nbcon_irq_work to IRQ_WORK_LAZY Change the nbcon_irq_work to be IRQ_WORK_LAZY, thus not raising an IRQ upon irq_work queuing. The irq_work is then handled on the next kernel tick. This additional delay is acceptable because nbcon_irq_work is only responsible for non-emergency deferred printing, which is delayed anyway. This has the benefit of not needing to raise an IRQ for each printk() call. On a side note, the Tegra20 and Tegra30 platforms can hang if an irq_work IRQ is raised while entering cpuidle states. This problem was reproducible by calling printk() while entering cpuidle. So this change also provides a workaround for these platforms (as long as they are not running tickless). Link: https://lore.kernel.org/lkml/f3757a75-0ba1-4558-bf57-f19ab7e59a4c@nvidia.com Fixes: 76f258bf3f2a ("printk: nbcon: Introduce printer kthreads") Signed-off-by: John Ogness Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Petr Mladek Tested-by: Jon Hunter Link: https://patch.msgid.link/20260901093245.344455-3-john.ogness@linutronix.de Signed-off-by: Petr Mladek --- kernel/printk/nbcon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 354f274d8a42..c8502fc4f4e5 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1782,7 +1782,7 @@ bool nbcon_alloc(struct console *con) } rcuwait_init(&con->rcuwait); - init_irq_work(&con->irq_work, nbcon_irq_work); + con->irq_work = IRQ_WORK_INIT_LAZY(nbcon_irq_work); atomic_long_set(&ACCESS_PRIVATE(con, nbcon_prev_seq), -1UL); nbcon_state_set(con, &state); From 3b54dbd119805361695cb50ca6a875f4c7518b74 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 26 Aug 2026 13:27:24 +0100 Subject: [PATCH 0137/1317] power: sequencing: Fix build issue with COMPILE_TEST The POWER_SEQUENCING_TH1520_GPU driver depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST. This means when COMPILE_TEST=y and ARCH_THEAD is not set, the driver can still be built even though it requires AUXILIARY_BUS, which may not be selected in that configuration, leading to a build failure. Fix this by dropping AUXILIARY_BUS from the dependency and instead selecting it directly, so the dependency is satisfied regardless of whether COMPILE_TEST or ARCH_THEAD is enabled. Fixes: 1a7312b93ab0 ("power: sequencing: extend build coverage with COMPILE_TEST=y") Signed-off-by: Biju Das Link: https://patch.msgid.link/20260826122742.153643-3-biju.das.jz@bp.renesas.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 1c5f5820f5b7..226c62704d9b 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -29,7 +29,8 @@ config POWER_SEQUENCING_QCOM_WCN config POWER_SEQUENCING_TH1520_GPU tristate "T-HEAD TH1520 GPU power sequencing driver" - depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST + depends on ARCH_THEAD || COMPILE_TEST + select AUXILIARY_BUS help Say Y here to enable the power sequencing driver for the TH1520 SoC GPU. This driver handles the complex clock and reset sequence From 355b6558dd7be049aff4f0d438b0128f91a982eb Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:05 -0700 Subject: [PATCH 0138/1317] platform/x86: x86-android-tablets: fix Arizona GPIO swnode references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standalone arizona_gpiochip_node was created when gpiolib supported matching a software node name against the GPIO chip label ("arizona"). Later, gpiolib replaced name matching with firmware node identity mapping (and eventually dropped the fallback mechanism), causing GPIO lookups on unattached software nodes to fail. In gpio-arizona, the GPIO chip inherits the firmware node of the parent codec device. Fix the lookups by pointing the GPIO property entries directly to the codec device software node (which is attached to the parent device) and dropping the obsolete arizona_gpiochip_node. Fixes: 611fd6cfe139 ("gpio: swnode: remove deprecated lookup mechanism") Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-1-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../platform/x86/x86-android-tablets/lenovo.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index 8d825e0b4661..c34f62bdf8f9 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -61,10 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { .initial_brightness = 128, }; -static const struct software_node arizona_gpiochip_node = { - .name = "arizona", -}; - static const struct software_node crystalcove_gpiochip_node = { .name = "gpio_crystalcove", }; @@ -416,15 +412,17 @@ static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __ini #define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" +static const struct software_node lenovo_yoga_tab2_830_1050_wm5102; + static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = { PROPERTY_ENTRY_GPIO("reset-gpios", &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", - &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), + &lenovo_yoga_tab2_830_1050_wm5102, 2, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", - &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW), + &lenovo_yoga_tab2_830_1050_wm5102, 4, GPIO_ACTIVE_LOW), { } }; @@ -434,7 +432,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { &crystalcove_gpiochip_node, - &arizona_gpiochip_node, &lenovo_yoga_tab2_830_1050_wm5102, &generic_lipo_hv_4v35_battery_node, NULL @@ -985,13 +982,15 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = { }, }; +static const struct software_node lenovo_yt3_wm5102; + static const struct property_entry lenovo_yt3_wm1502_props[] = { PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH), - PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &lenovo_yt3_wm5102, 2, GPIO_ACTIVE_HIGH), { } }; @@ -1001,7 +1000,6 @@ static const struct software_node lenovo_yt3_wm5102 = { }; static const struct software_node *lenovo_yt3_swnodes[] = { - &arizona_gpiochip_node, &lenovo_yt3_wm5102, NULL }; From 144113b0a70fa18033a747ee5db6803308f7688c Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:06 -0700 Subject: [PATCH 0139/1317] platform/x86: x86-android-tablets: hold device reference for secondary fwnode teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In gpio_secondary_fwnode_init(), acpi_bus_find_device_by_name() returns a device reference, but the local dev variable is declared with __free(put_device), dropping the reference at the end of each iteration. Meanwhile, devm_add_action_or_reset() saves the dev pointer for gpio_secondary_unset() without incrementing its reference count, which could lead to a use-after-free during driver teardown if the device is released in the interim. Acquire an explicit device reference with get_device() when registering the devres action, and drop it with put_device() inside gpio_secondary_unset(). Fixes: 1448c2d2ca5c ("platform/x86: x86-android-tablets: enable fwnode matching of GPIO chips") Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-2-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 5db794d65eb5..722c0ae4ecd1 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -367,6 +367,7 @@ static void gpio_secondary_unset(void *data) struct device *dev = data; set_secondary_fwnode(dev, NULL); + put_device(dev); } static void gpio_secondary_unregister_node_group(void *data) @@ -409,7 +410,7 @@ static int gpio_secondary_fwnode_init(struct device *parent) set_secondary_fwnode(dev, fwnode); - ret = devm_add_action_or_reset(parent, gpio_secondary_unset, dev); + ret = devm_add_action_or_reset(parent, gpio_secondary_unset, get_device(dev)); if (ret) return ret; } From aab060ec969c3859b81f80f3444fd5a2edfc3cf5 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:07 -0700 Subject: [PATCH 0140/1317] platform/x86: x86-android-tablets: pass node group to gpio_secondary_fwnode_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently gpio_secondary_fwnode_init() uses a file-scope static gpiochip_node_group variable initialized in probe. Pass the node group directly to gpio_secondary_fwnode_init() as an argument instead of using a global variable. This allows reusing the helper for additional GPIO controllers in subsequent patches. Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-3-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/core.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 722c0ae4ecd1..45673bfc0b7b 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -156,7 +156,6 @@ static struct platform_device **pdevs; static struct serdev_device **serdevs; static const struct software_node **gpio_button_swnodes; static const struct software_node **swnode_group; -static const struct software_node **gpiochip_node_group; static void (*exit_handler)(void); static __init struct i2c_adapter * @@ -377,26 +376,27 @@ static void gpio_secondary_unregister_node_group(void *data) software_node_unregister_node_group(nodes); } -static int gpio_secondary_fwnode_init(struct device *parent) +static int gpio_secondary_fwnode_init(struct device *parent, + const struct software_node * const *node_group) { const struct software_node *const *swnode; struct fwnode_handle *fwnode; int ret; - if (!gpiochip_node_group) + if (!node_group) return 0; - ret = software_node_register_node_group(gpiochip_node_group); + ret = software_node_register_node_group(node_group); if (ret) return ret; ret = devm_add_action_or_reset(parent, gpio_secondary_unregister_node_group, - gpiochip_node_group); + (void *)node_group); if (ret) return ret; - for (swnode = gpiochip_node_group; *swnode; swnode++) { + for (swnode = node_group; *swnode; swnode++) { struct device *dev __free(put_device) = acpi_bus_find_device_by_name((*swnode)->name); if (!dev) @@ -453,6 +453,7 @@ static void x86_android_tablet_remove(struct platform_device *pdev) static __init int x86_android_tablet_probe(struct platform_device *pdev) { + const struct software_node * const *gpiochip_node_group; const struct x86_dev_info *dev_info; const struct dmi_system_id *id; int i, ret = 0; @@ -484,7 +485,7 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) break; } - ret = gpio_secondary_fwnode_init(&pdev->dev); + ret = gpio_secondary_fwnode_init(&pdev->dev, gpiochip_node_group); if (ret) { x86_android_tablet_remove(pdev); return ret; From 7872c625cd83a0247821cedd6c6f63938d4bddbc Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:08 -0700 Subject: [PATCH 0141/1317] platform/x86: x86-android-tablets: add Crystal Cove GPIO swnode support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crystalcove_gpiochip_node was created when gpiolib supported matching a software node name against the GPIO chip label. Later, gpiolib replaced name matching with firmware node identity mapping, and support for dynamically attaching software nodes to ACPI GPIO chips as secondary firmware nodes was added for Baytrail and Cherryview, but Crystal Cove ("INT33FD:00") was omitted. Consequently, lookups on the unattached Crystal Cove software node fail. Add support for attaching crystalcove_gpiochip_node to the INT33FD:00 ACPI device as a secondary firmware node, and enable it on Lenovo Yoga Tab 2 models. Fixes: 611fd6cfe139 ("gpio: swnode: remove deprecated lookup mechanism") Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-4-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/core.c | 17 +++++++++++++++++ .../platform/x86/x86-android-tablets/lenovo.c | 6 ++---- .../x86-android-tablets/x86-android-tablets.h | 2 ++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 45673bfc0b7b..b028af1c9942 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -361,6 +361,15 @@ static const struct software_node *cherryview_gpiochip_node_group[] = { NULL }; +const struct software_node crystalcove_gpiochip_node = { + .name = "INT33FD:00", +}; + +static const struct software_node *crystalcove_gpiochip_node_group[] = { + &crystalcove_gpiochip_node, + NULL +}; + static void gpio_secondary_unset(void *data) { struct device *dev = data; @@ -491,6 +500,14 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) return ret; } + if (dev_info->has_crystalcove) { + ret = gpio_secondary_fwnode_init(&pdev->dev, crystalcove_gpiochip_node_group); + if (ret) { + x86_android_tablet_remove(pdev); + return ret; + } + } + ret = software_node_register_node_group(dev_info->swnode_group); if (ret) { x86_android_tablet_remove(pdev); diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index c34f62bdf8f9..54068a0f4633 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -61,9 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { .initial_brightness = 128, }; -static const struct software_node crystalcove_gpiochip_node = { - .name = "gpio_crystalcove", -}; /* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */ @@ -431,7 +428,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { }; static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { - &crystalcove_gpiochip_node, &lenovo_yoga_tab2_830_1050_wm5102, &generic_lipo_hv_4v35_battery_node, NULL @@ -454,6 +450,7 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, .modules = lenovo_yoga_tab2_modules, + .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, .init = lenovo_yoga_tab2_830_1050_init, .exit = lenovo_yoga_tab2_830_1050_exit, @@ -799,6 +796,7 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, .modules = lenovo_yoga_tab2_modules, + .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, .init = lenovo_yoga_tab2_1380_init, .exit = lenovo_yoga_tab2_830_1050_exit, diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h index c756961ae5fd..6e6534f8fa6c 100644 --- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h @@ -96,6 +96,7 @@ struct x86_dev_info { int (*init)(struct device *dev); void (*exit)(void); bool use_pci; + bool has_crystalcove; enum x86_gpiochip_type gpiochip_type; }; @@ -107,6 +108,7 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data); /* Software nodes representing GPIO chips used by various tablets */ extern const struct software_node baytrail_gpiochip_nodes[]; extern const struct software_node cherryview_gpiochip_nodes[]; +extern const struct software_node crystalcove_gpiochip_node; /* * Extern declarations of x86_dev_info structs so there can be a single From 74884436a53df0bbaf6d92d2922f643a4581b247 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:09 -0700 Subject: [PATCH 0142/1317] platform/x86: x86-android-tablets: drop redundant swnode group on YT3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WM5102 codec software node lenovo_yt3_wm5102 is assigned to the board info swnode pointer in lenovo_yt3_spi_devs. When spi_new_device() instantiates the SPI device, device_add_software_node() automatically registers the software node. Therefore, explicitly registering lenovo_yt3_swnodes via software_node_register_node_group() and listing it in .swnode_group is redundant. Drop the unused node group and registration. Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-5-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/lenovo.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index 54068a0f4633..cd8cee1f4aed 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -997,10 +997,6 @@ static const struct software_node lenovo_yt3_wm5102 = { .name = "wm5102", }; -static const struct software_node *lenovo_yt3_swnodes[] = { - &lenovo_yt3_wm5102, - NULL -}; static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { { @@ -1068,7 +1064,6 @@ const struct x86_dev_info lenovo_yt3_info __initconst = { .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients), .spi_dev_info = lenovo_yt3_spi_devs, .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs), - .swnode_group = lenovo_yt3_swnodes, .modules = lenovo_yt3_modules, .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, .init = lenovo_yt3_init, From 312fd3f3a85b89aa0d4fb5417043d640daa3732c Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:10 -0700 Subject: [PATCH 0143/1317] platform/x86: x86-android-tablets: use shared battery swnode group on Yoga Tab 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WM5102 codec software node lenovo_yoga_tab2_830_1050_wm5102 is registered automatically when attached to the codec device via device_add_software_node() in lenovo_yoga_tab2_830_1050_init_codec(). Including it in lenovo_yoga_tab2_830_1050_swnodes is therefore redundant, leaving generic_lipo_hv_4v35_battery_node as the only node needing registration. Switch lenovo_yoga_tab2_830_1050_info and lenovo_yoga_tab2_1380_info to use the shared generic_lipo_hv_4v35_battery_swnodes group directly and drop the custom lenovo_yoga_tab2_830_1050_swnodes array. Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-6-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/lenovo.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index cd8cee1f4aed..52d96ae89078 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -427,12 +427,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { .properties = lenovo_yoga_tab2_830_1050_wm1502_props, }; -static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { - &lenovo_yoga_tab2_830_1050_wm5102, - &generic_lipo_hv_4v35_battery_node, - NULL -}; - static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); static void lenovo_yoga_tab2_830_1050_exit(void); @@ -448,7 +442,7 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { .pdev_info = lenovo_yoga_tab2_830_1050_pdevs, .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs), .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, - .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, + .swnode_group = generic_lipo_hv_4v35_battery_swnodes, .modules = lenovo_yoga_tab2_modules, .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, @@ -794,7 +788,7 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { .pdev_info = lenovo_yoga_tab2_1380_pdevs, .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, - .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, + .swnode_group = generic_lipo_hv_4v35_battery_swnodes, .modules = lenovo_yoga_tab2_modules, .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, From c37ba8fe00f264eee2fd18b0bff7c5f188136c51 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:51 +0200 Subject: [PATCH 0144/1317] ASoC: ux500: Fix MSP stream lifecycle handling The trigger stop path drops the direction busy flag even though ALSA still owns the stream until shutdown. A later trigger cannot reliably restart it, shutdown may leave the block configured, and a second stream may overwrite shared duplex configuration. Keep configured and running directions as separate state. Program shared settings only for the first direction, require a compatible configuration for the other half of a duplex stream, and enable the frame generator only while a provider stream is running. Also fix the RX-disable direction test and preserve the other direction multichannel setup. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-1-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 8 +- sound/soc/ux500/ux500_msp_i2s.c | 179 ++++++++++++++++++++++++-------- sound/soc/ux500/ux500_msp_i2s.h | 4 + 3 files changed, 147 insertions(+), 44 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 499e826d7120..994422e72512 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -34,8 +34,10 @@ static int setup_pcm_multichan(struct snd_soc_dai *dai, if (drvdata->slots > 1) { msp_config->multichannel_configured = 1; - multi->tx_multichannel_enable = true; - multi->rx_multichannel_enable = true; + multi->tx_multichannel_enable = + msp_config->direction & MSP_DIR_TX; + multi->rx_multichannel_enable = + msp_config->direction & MSP_DIR_RX; multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED; multi->tx_channel_0_enable = drvdata->tx_mask; @@ -192,6 +194,7 @@ static int setup_clocking(struct snd_soc_dai *dai, case SND_SOC_DAIFMT_BC_FC: dev_dbg(dai->dev, "%s: Codec is master.\n", __func__); + msp_config->clock_provider = false; msp_config->iodelay = 0x20; msp_config->rx_fsync_sel = 0; msp_config->tx_fsync_sel = 1 << TFSSEL_SHIFT; @@ -204,6 +207,7 @@ static int setup_clocking(struct snd_soc_dai *dai, case SND_SOC_DAIFMT_BP_FP: dev_dbg(dai->dev, "%s: Codec is slave.\n", __func__); + msp_config->clock_provider = true; msp_config->tx_clk_sel = TX_CLK_SEL_SRG; msp_config->tx_fsync_sel = TX_SYNC_SRG_PROG; msp_config->rx_clk_sel = RX_CLK_SEL_SRG; diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index fbfeefa418ca..ec6f0874294a 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -344,20 +344,27 @@ static int configure_multichannel(struct ux500_msp *msp, return 0; } -static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config) +static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config, + bool first) { - int status = 0; - u32 reg_val_DMACR, reg_val_GCR; + int status; + u32 reg_val_DMACR; /* Configure msp with protocol dependent settings */ - configure_protocol(msp, config); - setup_bitclk(msp, config); + status = configure_protocol(msp, config); + if (status) + return status; + + if (first && config->clock_provider) { + status = setup_bitclk(msp, config); + if (status) + return status; + } + if (config->multichannel_configured == 1) { status = configure_multichannel(msp, config); if (status) - dev_warn(msp->dev, - "%s: WARN: configure_multichannel failed (%d)!\n", - __func__, status); + return status; } reg_val_DMACR = readl(msp->registers + MSP_DMACR); @@ -369,11 +376,7 @@ static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config) writel(config->iodelay, msp->registers + MSP_IODLY); - /* Enable frame generation logic */ - reg_val_GCR = readl(msp->registers + MSP_GCR); - writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR); - - return status; + return 0; } static void flush_fifo_rx(struct ux500_msp *msp) @@ -411,12 +414,36 @@ static void flush_fifo_tx(struct ux500_msp *msp) writel(reg_val_GCR, msp->registers + MSP_GCR); } +static bool ux500_msp_config_compatible(struct ux500_msp *msp, + struct ux500_msp_config *config) +{ + struct ux500_msp_config *active = &msp->config; + + return active->f_inputclk == config->f_inputclk && + active->tx_clk_sel == config->tx_clk_sel && + active->rx_clk_sel == config->rx_clk_sel && + active->srg_clk_sel == config->srg_clk_sel && + active->rx_fsync_pol == config->rx_fsync_pol && + active->tx_fsync_pol == config->tx_fsync_pol && + active->rx_fsync_sel == config->rx_fsync_sel && + active->tx_fsync_sel == config->tx_fsync_sel && + active->default_protdesc == config->default_protdesc && + active->protocol == config->protocol && + active->frame_freq == config->frame_freq && + active->data_size == config->data_size && + active->def_elem_len == config->def_elem_len && + active->clock_provider == config->clock_provider && + !memcmp(&active->protdesc, &config->protdesc, + sizeof(active->protdesc)); +} + int ux500_msp_i2s_open(struct ux500_msp *msp, struct ux500_msp_config *config) { u32 old_reg, new_reg, mask; int res; unsigned int tx_sel, rx_sel, tx_busy, rx_busy; + bool first; if (in_interrupt()) { dev_err(msp->dev, @@ -444,40 +471,66 @@ int ux500_msp_i2s_open(struct ux500_msp *msp, return -EBUSY; } - msp->dir_busy |= (tx_sel ? MSP_DIR_TX : 0) | (rx_sel ? MSP_DIR_RX : 0); + first = !msp->dir_busy; + if (!first && !ux500_msp_config_compatible(msp, config)) { + dev_err(msp->dev, "%s: Incompatible duplex configuration\n", + __func__); + return -EBUSY; + } - /* First do the global config register */ - mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK | - TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | - RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | - LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + if (first) { + /* First do the global config register */ + mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK | + TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | + SRG_CLK_SEL_MASK | LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; - new_reg = (config->tx_clk_sel | config->rx_clk_sel | - config->rx_fsync_pol | config->tx_fsync_pol | - config->rx_fsync_sel | config->tx_fsync_sel | - config->rx_fifo_config | config->tx_fifo_config | - config->srg_clk_sel | config->loopback_enable | - config->tx_data_enable); + new_reg = config->tx_clk_sel | config->rx_clk_sel | + config->rx_fsync_pol | config->tx_fsync_pol | + config->rx_fsync_sel | config->tx_fsync_sel | + config->rx_fifo_config | config->tx_fifo_config | + config->srg_clk_sel | config->loopback_enable | + config->tx_data_enable; - old_reg = readl(msp->registers + MSP_GCR); - old_reg &= ~mask; - new_reg |= old_reg; - writel(new_reg, msp->registers + MSP_GCR); + old_reg = readl(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + writel(new_reg, msp->registers + MSP_GCR); + } - res = enable_msp(msp, config); + res = enable_msp(msp, config, first); if (res < 0) { dev_err(msp->dev, "%s: ERROR: enable_msp failed (%d)!\n", __func__, res); - return -EBUSY; + if (tx_sel) + writel(0, msp->registers + MSP_TCF); + if (rx_sel) + writel(0, msp->registers + MSP_RCF); + if (first) { + writel(0, msp->registers + MSP_GCR); + writel(0, msp->registers + MSP_DMACR); + writel(0, msp->registers + MSP_SRG); + writel(0, msp->registers + MSP_MCR); + } + return res; + } + + msp->dir_busy |= config->direction; + if (first) { + msp->config = *config; + msp->clock_provider = config->clock_provider; } if (config->loopback_enable & 0x80) msp->loopback_enable = 1; /* Flush FIFOs */ - flush_fifo_tx(msp); - flush_fifo_rx(msp); + if (tx_sel) + flush_fifo_tx(msp); + if (rx_sel) + flush_fifo_rx(msp); - msp->msp_state = MSP_STATE_CONFIGURED; + if (!msp->dir_running) + msp->msp_state = MSP_STATE_CONFIGURED; return 0; } @@ -494,7 +547,6 @@ static void disable_msp_rx(struct ux500_msp *msp) ~(RX_SERVICE_INT | RX_OVERRUN_ERROR_INT), msp->registers + MSP_IMSC); - msp->dir_busy &= ~MSP_DIR_RX; } static void disable_msp_tx(struct ux500_msp *msp) @@ -510,7 +562,6 @@ static void disable_msp_tx(struct ux500_msp *msp) ~(TX_SERVICE_INT | TX_UNDERRUN_ERR_INT), msp->registers + MSP_IMSC); - msp->dir_busy &= ~MSP_DIR_TX; } static int disable_msp(struct ux500_msp *msp, unsigned int dir) @@ -520,7 +571,7 @@ static int disable_msp(struct ux500_msp *msp, unsigned int dir) reg_val_GCR = readl(msp->registers + MSP_GCR); disable_tx = dir & MSP_DIR_TX; - disable_rx = dir & MSP_DIR_TX; + disable_rx = dir & MSP_DIR_RX; if (disable_tx && disable_rx) { reg_val_GCR = readl(msp->registers + MSP_GCR); writel(reg_val_GCR | LOOPBACK_MASK, @@ -553,7 +604,15 @@ static int disable_msp(struct ux500_msp *msp, unsigned int dir) int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) { - u32 reg_val_GCR, enable_bit; + u32 reg_val_DMACR, reg_val_GCR, dma_enable_bit, enable_bit; + unsigned int dir; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dir = MSP_DIR_TX; + else if (direction == SNDRV_PCM_STREAM_CAPTURE) + dir = MSP_DIR_RX; + else + return -EINVAL; if (msp->msp_state == MSP_STATE_IDLE) { dev_err(msp->dev, "%s: ERROR: MSP is not configured!\n", @@ -565,21 +624,44 @@ int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (direction == SNDRV_PCM_STREAM_PLAYBACK) + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { enable_bit = TX_ENABLE; - else + dma_enable_bit = TX_DMA_ENABLE; + } else { enable_bit = RX_ENABLE; + dma_enable_bit = RX_DMA_ENABLE; + } + if (!(msp->dir_busy & dir)) + return -EINVAL; + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR | dma_enable_bit, + msp->registers + MSP_DMACR); reg_val_GCR = readl(msp->registers + MSP_GCR); + if (msp->clock_provider) + enable_bit |= FRAME_GEN_ENABLE; writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR); + msp->dir_running |= dir; + msp->msp_state = MSP_STATE_RUNNING; break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (direction == SNDRV_PCM_STREAM_PLAYBACK) + if (!(msp->dir_busy & dir)) + return -EINVAL; + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { disable_msp_tx(msp); - else + msp->dir_running &= ~MSP_DIR_TX; + } else { disable_msp_rx(msp); + msp->dir_running &= ~MSP_DIR_RX; + } + if (!msp->dir_running) { + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~FRAME_GEN_ENABLE, + msp->registers + MSP_GCR); + msp->msp_state = MSP_STATE_CONFIGURED; + } break; default: return -EINVAL; @@ -594,7 +676,18 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) dev_dbg(msp->dev, "%s: Enter (dir = 0x%01x).\n", __func__, dir); + if (!dir || dir & ~(MSP_DIR_TX | MSP_DIR_RX) || + (msp->dir_busy & dir) != dir) + return -EINVAL; + status = disable_msp(msp, dir); + msp->dir_busy &= ~dir; + msp->dir_running &= ~dir; + if (msp->dir_busy && !msp->dir_running) { + writel(readl(msp->registers + MSP_GCR) & ~FRAME_GEN_ENABLE, + msp->registers + MSP_GCR); + msp->msp_state = MSP_STATE_CONFIGURED; + } if (msp->dir_busy == 0) { /* disable sample rate and frame generators */ msp->msp_state = MSP_STATE_IDLE; @@ -618,6 +711,8 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) writel(0, msp->registers + MSP_RCE1); writel(0, msp->registers + MSP_RCE2); writel(0, msp->registers + MSP_RCE3); + memset(&msp->config, 0, sizeof(msp->config)); + msp->clock_provider = false; } return status; diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 69d4ebc409fc..d75a0974369a 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -460,6 +460,7 @@ struct ux500_msp_config { enum msp_data_size data_size; unsigned int def_elem_len; unsigned int iodelay; + bool clock_provider; }; struct ux500_msp { @@ -470,8 +471,11 @@ struct ux500_msp { enum msp_state msp_state; int def_elem_len; unsigned int dir_busy; + unsigned int dir_running; int loopback_enable; unsigned int f_bitclk; + bool clock_provider; + struct ux500_msp_config config; }; int ux500_msp_i2s_init_msp(struct platform_device *pdev, From 3415421a2b0bc4e32bb5a9df24ed7863512d47a7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:52 +0200 Subject: [PATCH 0145/1317] ASoC: ux500: Propagate MSP setup errors The prepare callback continues with a partly initialized configuration when format setup fails. Probe likewise tests the allocated pointer instead of the return value, so an MMIO resource or mapping failure can be ignored after allocation succeeds. Return configuration failures from prepare and test the MSP initialization result directly. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-2-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 994422e72512..29d25a3f6f40 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -468,7 +468,9 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (rate = %d).\n", __func__, dai->id, snd_pcm_stream_str(substream), runtime->rate); - setup_msp_config(substream, dai, &msp_config); + ret = setup_msp_config(substream, dai, &msp_config); + if (ret) + return ret; ret = ux500_msp_i2s_open(drvdata->msp, &msp_config); if (ret < 0) { @@ -764,7 +766,7 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) } ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp); - if (!drvdata->msp) { + if (ret) { dev_err(&pdev->dev, "%s: ERROR: Failed to init MSP-struct (%d)!", __func__, ret); From 94c18cea657c48680e4ee20b635b6c01f3eb352e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:53 +0200 Subject: [PATCH 0146/1317] ASoC: ux500: Correct MSP frame and bit clock setup FRPER plus one is the number of bit clocks in a frame. It must follow the configured slot count and width. The legacy rate-dependent constants produce malformed frames; notably, a 16-slot, 16-bit frame is programmed as 278 rather than 256 clocks. Derive the frame period from the TDM geometry and use the real functional clock rate. Validate that the requested bit clock has an exact, representable divider, program SCKDIV as divider minus one, and report the resulting bit clock using that same divisor. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-3-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 75 +++++++-------------------------- sound/soc/ux500/ux500_msp_dai.h | 11 ----- sound/soc/ux500/ux500_msp_i2s.c | 54 ++++++++++++++---------- sound/soc/ux500/ux500_msp_i2s.h | 2 - 4 files changed, 46 insertions(+), 96 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 29d25a3f6f40..56d5591e2269 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -59,72 +59,21 @@ static int setup_pcm_multichan(struct snd_soc_dai *dai, return 0; } -static int setup_frameper(struct snd_soc_dai *dai, unsigned int rate, - struct msp_protdesc *prot_desc) +static void setup_frameper(struct snd_soc_dai *dai, + struct msp_protdesc *prot_desc) { struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); - switch (drvdata->slots) { - case 1: - switch (rate) { - case 8000: - prot_desc->frame_period = - FRAME_PER_SINGLE_SLOT_8_KHZ; - break; - - case 16000: - prot_desc->frame_period = - FRAME_PER_SINGLE_SLOT_16_KHZ; - break; - - case 44100: - prot_desc->frame_period = - FRAME_PER_SINGLE_SLOT_44_1_KHZ; - break; - - case 48000: - prot_desc->frame_period = - FRAME_PER_SINGLE_SLOT_48_KHZ; - break; - - default: - dev_err(dai->dev, - "%s: Error: Unsupported sample-rate (freq = %d)!\n", - __func__, rate); - return -EINVAL; - } - break; - - case 2: - prot_desc->frame_period = FRAME_PER_2_SLOTS; - break; - - case 8: - prot_desc->frame_period = FRAME_PER_8_SLOTS; - break; - - case 16: - prot_desc->frame_period = FRAME_PER_16_SLOTS; - break; - default: - dev_err(dai->dev, - "%s: Error: Unsupported slot-count (slots = %d)!\n", - __func__, drvdata->slots); - return -EINVAL; - } - - prot_desc->clocks_per_frame = - prot_desc->frame_period+1; + prot_desc->clocks_per_frame = drvdata->slots * drvdata->slot_width; + prot_desc->frame_period = prot_desc->clocks_per_frame - 1; dev_dbg(dai->dev, "%s: Clocks per frame: %u\n", __func__, prot_desc->clocks_per_frame); - - return 0; } -static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate, - struct msp_protdesc *prot_desc) +static int setup_pcm_framing(struct snd_soc_dai *dai, + struct msp_protdesc *prot_desc) { struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); @@ -165,7 +114,9 @@ static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate, prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16; prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16; - return setup_frameper(dai, rate, prot_desc); + setup_frameper(dai, prot_desc); + + return 0; } static int setup_clocking(struct snd_soc_dai *dai, @@ -366,7 +317,7 @@ static int setup_msp_config(struct snd_pcm_substream *substream, if (ret < 0) return ret; - ret = setup_pcm_framing(dai, runtime->rate, prot_desc); + ret = setup_pcm_framing(dai, prot_desc); if (ret < 0) return ret; @@ -735,7 +686,6 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) drvdata->tx_mask = 0x01; drvdata->rx_mask = 0x01; drvdata->slot_width = 16; - drvdata->master_clk = MSP_INPUT_FREQ_APB; drvdata->reg_vape = devm_regulator_get(&pdev->dev, "v-ape"); if (IS_ERR(drvdata->reg_vape)) { @@ -764,6 +714,11 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) __func__, ret); return ret; } + drvdata->master_clk = clk_get_rate(drvdata->clk); + if (!drvdata->master_clk) { + dev_err(&pdev->dev, "MSP clock has no rate\n"); + return -EINVAL; + } ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp); if (ret) { diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h index 30bf70838196..19058c238420 100644 --- a/sound/soc/ux500/ux500_msp_dai.h +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -22,17 +22,6 @@ #define UX500_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) -#define FRAME_PER_SINGLE_SLOT_8_KHZ 31 -#define FRAME_PER_SINGLE_SLOT_16_KHZ 124 -#define FRAME_PER_SINGLE_SLOT_44_1_KHZ 63 -#define FRAME_PER_SINGLE_SLOT_48_KHZ 49 -#define FRAME_PER_2_SLOTS 31 -#define FRAME_PER_8_SLOTS 138 -#define FRAME_PER_16_SLOTS 277 - -#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000 -#define UX500_MSP1_INTERNAL_CLOCK_FREQ UX500_MSP_INTERNAL_CLOCK_FREQ - #define UX500_MSP_MIN_CHANNELS 1 #define UX500_MSP_MAX_CHANNELS 8 diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index ec6f0874294a..ef41de92d8e7 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -212,35 +212,20 @@ static int configure_protocol(struct ux500_msp *msp, static int setup_bitclk(struct ux500_msp *msp, struct ux500_msp_config *config) { + struct msp_protdesc *protdesc; + u64 desired_bitclk; + unsigned int bitclk; u32 reg_val_GCR; - u32 frame_per = 0; - u32 sck_div = 0; - u32 frame_width = 0; - u32 temp_reg = 0; - struct msp_protdesc *protdesc = NULL; + u32 sck_div; + u32 temp_reg; reg_val_GCR = readl(msp->registers + MSP_GCR); writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR); - if (config->default_protdesc) - protdesc = - (struct msp_protdesc *)&prot_descs[config->protocol]; - else - protdesc = (struct msp_protdesc *)&config->protdesc; - switch (config->protocol) { case MSP_PCM_PROTOCOL: case MSP_PCM_COMPAND_PROTOCOL: - frame_width = protdesc->frame_width; - sck_div = config->f_inputclk / (config->frame_freq * - (protdesc->clocks_per_frame)); - frame_per = protdesc->frame_period; - break; case MSP_I2S_PROTOCOL: - frame_width = protdesc->frame_width; - sck_div = config->f_inputclk / (config->frame_freq * - (protdesc->clocks_per_frame)); - frame_per = protdesc->frame_period; break; default: dev_err(msp->dev, "%s: ERROR: Unknown protocol (%d)!\n", @@ -249,12 +234,35 @@ static int setup_bitclk(struct ux500_msp *msp, struct ux500_msp_config *config) return -EINVAL; } + if (config->default_protdesc) + protdesc = (struct msp_protdesc *)&prot_descs[config->protocol]; + else + protdesc = &config->protdesc; + + if (!config->frame_freq || !protdesc->clocks_per_frame) + return -EINVAL; + + desired_bitclk = (u64)config->frame_freq * protdesc->clocks_per_frame; + if (desired_bitclk > config->f_inputclk) + return -EINVAL; + bitclk = desired_bitclk; + if (config->f_inputclk % bitclk) { + dev_err(msp->dev, + "Input clock %u cannot generate bit clock %u\n", + config->f_inputclk, bitclk); + return -EINVAL; + } + + sck_div = config->f_inputclk / bitclk; + if (!sck_div || sck_div > SCK_DIV_MASK + 1) + return -EINVAL; + temp_reg = (sck_div - 1) & SCK_DIV_MASK; - temp_reg |= FRAME_WIDTH_BITS(frame_width); - temp_reg |= FRAME_PERIOD_BITS(frame_per); + temp_reg |= FRAME_WIDTH_BITS(protdesc->frame_width); + temp_reg |= FRAME_PERIOD_BITS(protdesc->frame_period); writel(temp_reg, msp->registers + MSP_SRG); - msp->f_bitclk = (config->f_inputclk)/(sck_div + 1); + msp->f_bitclk = config->f_inputclk / sck_div; /* Enable bit-clock */ udelay(100); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index d75a0974369a..80085dde5079 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -12,8 +12,6 @@ #include -#define MSP_INPUT_FREQ_APB 48000000 - /*** Stereo mode. Used for APB data accesses as 16 bits accesses (mono), * 32 bits accesses (stereo). ***/ From 9ccbacf5a0120964fc1ffacb8151e3347bee9287 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:54 +0200 Subject: [PATCH 0147/1317] ASoC: ux500: Validate MSP DAI configuration Installing channel constraints from hw_params is too late to affect the parameters being committed. The driver consequently accepts channel counts which disagree with the I2S or TDM setup. It also silently truncates out-of-range slot masks and accepts inverted bit clock formats which prepare then rejects. Validate the selected channel count directly, reject invalid masks before changing cached TDM state, and implement all four standard clock and frame inversion combinations. Use the requested format in validation diagnostics. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-4-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 41 ++++++++++++++++++++++----------- sound/soc/ux500/ux500_msp_i2s.c | 7 ++++-- sound/soc/ux500/ux500_msp_i2s.h | 1 + 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 56d5591e2269..78278927cc53 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -130,7 +130,16 @@ static int setup_clocking(struct snd_soc_dai *dai, case SND_SOC_DAIFMT_NB_IF: msp_config->tx_fsync_pol ^= 1 << TFSPOL_SHIFT; msp_config->rx_fsync_pol ^= 1 << RFSPOL_SHIFT; + break; + case SND_SOC_DAIFMT_IB_NF: + msp_config->bclk_inverted = true; + break; + + case SND_SOC_DAIFMT_IB_IF: + msp_config->bclk_inverted = true; + msp_config->tx_fsync_pol ^= 1 << TFSPOL_SHIFT; + msp_config->rx_fsync_pol ^= 1 << RFSPOL_SHIFT; break; default: @@ -453,7 +462,6 @@ static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { unsigned int mask, slots_active; - struct snd_pcm_runtime *runtime = substream->runtime; struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", @@ -461,9 +469,8 @@ static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: - snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, - 1, 2); + if (params_channels(params) < 1 || params_channels(params) > 2) + return -EINVAL; break; case SND_SOC_DAIFMT_DSP_B: @@ -475,9 +482,8 @@ static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, slots_active = hweight32(mask); dev_dbg(dai->dev, "TDM-slots active: %d", slots_active); - snd_pcm_hw_constraint_single(runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, - slots_active); + if (!slots_active || params_channels(params) != slots_active) + return -EINVAL; break; default: @@ -510,20 +516,21 @@ static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai, default: dev_err(dai->dev, "%s: Error: Unsupported protocol/master (fmt = 0x%x)!\n", - __func__, drvdata->fmt); + __func__, fmt); return -EINVAL; } switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_NF: case SND_SOC_DAIFMT_IB_IF: break; default: dev_err(dai->dev, "%s: Error: Unsupported inversion (fmt = 0x%x)!\n", - __func__, drvdata->fmt); + __func__, fmt); return -EINVAL; } @@ -557,17 +564,23 @@ static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai, __func__, slots); return -EINVAL; } - drvdata->slots = slots; - if (!(slot_width == 16)) { + if (slot_width != 16) { dev_err(dai->dev, "%s: Error: Unsupported slot-width (%d)!\n", __func__, slot_width); return -EINVAL; } - drvdata->slot_width = slot_width; - drvdata->tx_mask = tx_mask & cap; - drvdata->rx_mask = rx_mask & cap; + if ((tx_mask | rx_mask) & ~cap) { + dev_err(dai->dev, "%s: Slot mask exceeds %d slots\n", + __func__, slots); + return -EINVAL; + } + + drvdata->slots = slots; + drvdata->slot_width = slot_width; + drvdata->tx_mask = tx_mask; + drvdata->rx_mask = rx_mask; return 0; } diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index ef41de92d8e7..bc77174e0070 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -201,10 +201,12 @@ static int configure_protocol(struct ux500_msp *msp, /* The code below should not be separated. */ temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; - temp_reg |= MSP_TX_CLKPOL_BIT(~protdesc->tx_clk_pol); + temp_reg |= MSP_TX_CLKPOL_BIT(!protdesc->tx_clk_pol ^ + config->bclk_inverted); writel(temp_reg, msp->registers + MSP_GCR); temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; - temp_reg |= MSP_RX_CLKPOL_BIT(protdesc->rx_clk_pol); + temp_reg |= MSP_RX_CLKPOL_BIT(protdesc->rx_clk_pol ^ + config->bclk_inverted); writel(temp_reg, msp->registers + MSP_GCR); return 0; @@ -441,6 +443,7 @@ static bool ux500_msp_config_compatible(struct ux500_msp *msp, active->data_size == config->data_size && active->def_elem_len == config->def_elem_len && active->clock_provider == config->clock_provider && + active->bclk_inverted == config->bclk_inverted && !memcmp(&active->protdesc, &config->protdesc, sizeof(active->protdesc)); } diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 80085dde5079..17b5c37a7e5d 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -459,6 +459,7 @@ struct ux500_msp_config { unsigned int def_elem_len; unsigned int iodelay; bool clock_provider; + bool bclk_inverted; }; struct ux500_msp { From 66ec63e7a90bedc56aa050fbe437964008c3d584 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:55 +0200 Subject: [PATCH 0148/1317] ASoC: ux500: Deassert the MSP reset during probe The devicetree has described each MSP reset line since the PRCC reset controller was added, but the driver never acquires or deasserts it. The block can consequently remain inaccessible when firmware has left it in reset. Acquire the reset exclusively and keep it deasserted for the lifetime of the bound device. Fixes: 95f04048325c ("ARM: dts: ux500: Add reset lines to IP blocks") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-5-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 78278927cc53..b3de115d829a 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -686,6 +687,7 @@ static const struct snd_soc_component_driver ux500_msp_component = { static int ux500_msp_drv_probe(struct platform_device *pdev) { struct ux500_msp_i2s_drvdata *drvdata; + struct reset_control *reset; int ret = 0; drvdata = devm_kzalloc(&pdev->dev, @@ -733,6 +735,11 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) return -EINVAL; } + reset = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(reset), + "Failed to deassert MSP reset\n"); + ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp); if (ret) { dev_err(&pdev->dev, From 4fb67925f33ad789e9e00903a73306ed40f7ae32 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:56 +0200 Subject: [PATCH 0149/1317] ASoC: ux500: Request the MSP MMIO resource A bare devm_ioremap() neither reserves the register range nor preserves the platform resource error. This permits another driver to claim the same range and reports every mapping failure as an allocation failure. Use the managed platform resource helper, retaining the resolved resource only to derive the DMA register address. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-6-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_i2s.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index bc77174e0070..43dc9b3aa4ef 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -733,7 +733,7 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) int ux500_msp_i2s_init_msp(struct platform_device *pdev, struct ux500_msp **msp_p) { - struct resource *res = NULL; + struct resource *res; struct ux500_msp *msp; *msp_p = devm_kzalloc(&pdev->dev, sizeof(struct ux500_msp), GFP_KERNEL); @@ -743,20 +743,10 @@ int ux500_msp_i2s_init_msp(struct platform_device *pdev, msp->dev = &pdev->dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(&pdev->dev, "%s: ERROR: Unable to get resource!\n", - __func__); - return -ENOMEM; - } - + msp->registers = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(msp->registers)) + return PTR_ERR(msp->registers); msp->tx_rx_addr = res->start + MSP_DR; - msp->registers = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); - if (msp->registers == NULL) { - dev_err(&pdev->dev, "%s: ERROR: ioremap failed!\n", __func__); - return -ENOMEM; - } msp->msp_state = MSP_STATE_IDLE; msp->loopback_enable = 0; From 7b819677b503667422b0b7bdb21853e0066f8606 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:57 +0200 Subject: [PATCH 0150/1317] ASoC: ux500: Remove obsolete PRCMU QoS calls The DB8500 PRCMU QoS interface consists of unconditional inline stubs, so the MSP calls and cached constraint state have no effect. Device power and clocks are already represented by the regulator, power-domain and common-clock frameworks. Remove the dead calls and their private state instead of pretending to change the APE operating point. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-7-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 26 -------------------------- sound/soc/ux500/ux500_msp_dai.h | 2 -- 2 files changed, 28 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index b3de115d829a..5b4b3126637e 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -393,12 +392,6 @@ static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, snd_pcm_stream_str(substream)); - if (drvdata->vape_opp_constraint == 1) { - prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, - "ux500_msp_i2s", 50); - drvdata->vape_opp_constraint = 0; - } - if (ux500_msp_i2s_close(drvdata->msp, is_playback ? MSP_DIR_TX : MSP_DIR_RX)) { dev_err(dai->dev, @@ -440,21 +433,6 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, return ret; } - /* Set OPP-level */ - if ((drvdata->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) && - (drvdata->msp->f_bitclk > 19200000)) { - /* If the bit-clock is higher than 19.2MHz, Vape should be - * run in 100% OPP. Only when bit-clock is used (MSP master) - */ - prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, - "ux500-msp-i2s", 100); - drvdata->vape_opp_constraint = 1; - } else { - prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, - "ux500-msp-i2s", 50); - drvdata->vape_opp_constraint = 0; - } - return ret; } @@ -710,8 +688,6 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) __func__, ret); return ret; } - prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50); - drvdata->pclk = devm_clk_get(&pdev->dev, "apb_pclk"); if (IS_ERR(drvdata->pclk)) { ret = PTR_ERR(drvdata->pclk); @@ -780,8 +756,6 @@ static void ux500_msp_drv_remove(struct platform_device *pdev) snd_soc_unregister_component(&pdev->dev); - prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s"); - ux500_msp_i2s_cleanup_msp(pdev, drvdata->msp); } diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h index 19058c238420..ad4ce69bfbf5 100644 --- a/sound/soc/ux500/ux500_msp_dai.h +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -46,8 +46,6 @@ struct ux500_msp_i2s_drvdata { struct clk *clk; struct clk *pclk; - /* Regulators */ - int vape_opp_constraint; }; int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay); From dc1a1b1e22066f01bb86a9b11ee998d4dc72db66 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:58 +0200 Subject: [PATCH 0151/1317] ASoC: ux500: Allow repeated MSP prepare calls ALSA can call the DAI prepare callback again after an XRUN without first shutting down the stream. The MSP open helper rejects the second call with -EBUSY because the direction remains configured. Track successful playback and capture configurations at the DAI layer. Make repeated prepare calls no-ops and only close directions which were successfully prepared. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-8-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 28 +++++++++++++++++++++------- sound/soc/ux500/ux500_msp_dai.h | 1 + 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 5b4b3126637e..37c48cc70394 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -388,15 +388,21 @@ static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, int ret; struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int configured = is_playback ? PLAYBACK_CONFIGURED : + CAPTURE_CONFIGURED; + unsigned int dir = is_playback ? MSP_DIR_TX : MSP_DIR_RX; dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, snd_pcm_stream_str(substream)); - if (ux500_msp_i2s_close(drvdata->msp, - is_playback ? MSP_DIR_TX : MSP_DIR_RX)) { - dev_err(dai->dev, - "%s: Error: MSP %d (%s): Unable to close i2s.\n", - __func__, dai->id, snd_pcm_stream_str(substream)); + if (drvdata->configured & configured) { + if (ux500_msp_i2s_close(drvdata->msp, dir)) { + dev_err(dai->dev, + "%s: Error: MSP %d (%s): Unable to close i2s.\n", + __func__, dai->id, + snd_pcm_stream_str(substream)); + } + drvdata->configured &= ~configured; } /* Disable and unprepare clocks */ @@ -414,14 +420,20 @@ static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - int ret = 0; struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); struct snd_pcm_runtime *runtime = substream->runtime; struct ux500_msp_config msp_config; + bool is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int configured = is_playback ? PLAYBACK_CONFIGURED : + CAPTURE_CONFIGURED; + int ret; dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (rate = %d).\n", __func__, dai->id, snd_pcm_stream_str(substream), runtime->rate); + if (drvdata->configured & configured) + return 0; + ret = setup_msp_config(substream, dai, &msp_config); if (ret) return ret; @@ -433,7 +445,9 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, return ret; } - return ret; + drvdata->configured |= configured; + + return 0; } static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h index ad4ce69bfbf5..aae582030d95 100644 --- a/sound/soc/ux500/ux500_msp_dai.h +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -36,6 +36,7 @@ struct ux500_msp_i2s_drvdata { struct ux500_msp *msp; struct regulator *reg_vape; unsigned int fmt; + unsigned int configured; unsigned int tx_mask; unsigned int rx_mask; int slots; From 2519439b4b5f6ee95879b1a44fc373127291b1e4 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:59 +0200 Subject: [PATCH 0152/1317] ASoC: ux500: Program the MSP FIFO watermarks The DMA engine is configured for four-element bursts, but the MSP driver never programs the FIFO watermark register and instead depends on its previous or reset value. The DB8500 DMA request protocol requires the peripheral watermark to match the DMA packet size. Program four-element receive and transmit watermarks when configuring the first direction, before enabling MSP DMA requests. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-9-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_i2s.c | 2 ++ sound/soc/ux500/ux500_msp_i2s.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index 43dc9b3aa4ef..683b485fb570 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -507,6 +507,8 @@ int ux500_msp_i2s_open(struct ux500_msp *msp, old_reg &= ~mask; new_reg |= old_reg; writel(new_reg, msp->registers + MSP_GCR); + writel(MSP_WMRK_TX_4_ELEMENTS | MSP_WMRK_RX_4_ELEMENTS, + msp->registers + MSP_WMRK); } res = enable_msp(msp, config, first); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 17b5c37a7e5d..2bf2699bdc49 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -62,6 +62,7 @@ enum msp_direction { #define MSP_SRG 0x10 #define MSP_FLR 0x14 #define MSP_DMACR 0x18 +#define MSP_WMRK 0x1c #define MSP_IMSC 0x20 #define MSP_RIS 0x24 @@ -228,6 +229,10 @@ enum msp_direction { #define RDMAE_SHIFT 0 #define TDMAE_SHIFT 1 +/* FIFO watermark register */ +#define MSP_WMRK_RX_4_ELEMENTS BIT(0) +#define MSP_WMRK_TX_4_ELEMENTS BIT(3) + /* Interrupt Register */ #define RX_SERVICE_INT BIT(0) #define RX_OVERRUN_ERROR_INT BIT(1) From 950ae84b5cc944fbe27d81806d0b76af765f779c Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 2 Sep 2026 13:10:19 +0100 Subject: [PATCH 0153/1317] afs: Fix missing kunmap in afs_dir_search_bucket() Fix afs_dir_search_bucket() to kunmap the block it's using in the "bad:" path. Fixes: a5b5beebcf96 ("afs: Use the contained hashtable to search a directory") Closes: https://sashiko.dev/#/patchset/20260716103030.3065561-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260902121024.3328255-2-dhowells@redhat.com cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/dir_search.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/afs/dir_search.c b/fs/afs/dir_search.c index 104411c0692f..4977ad81fa82 100644 --- a/fs/afs/dir_search.c +++ b/fs/afs/dir_search.c @@ -173,12 +173,11 @@ int afs_dir_search_bucket(struct afs_dir_iter *iter, const struct qstr *name, ret = -ENOENT; found: +bad: if (iter->block) { kunmap_local(iter->block); iter->block = NULL; } - -bad: if (ret == -ESTALE) afs_invalidate_dir(iter->dvnode, afs_dir_invalid_iter_stale); _leave(" = %d", ret); From e3cfd3eb7d5be7787cc69530b423f788f14d084f Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 2 Sep 2026 13:10:20 +0100 Subject: [PATCH 0154/1317] afs: Fix double-unmap of directory block Fix afs_edit_dir_remove() to use a cleanup function to unmap the block pointed to by afs_dir_iter::block if it's left pointing to something rather than manually kunmapping the blocks. Manually kunmapping without clearing iter.blocks can result in a double-kunmap if afs_dir_find_block() is called twice in a row (which would be the case if the block being modified is not first in the hash chain). Fixes: a5b5beebcf96 ("afs: Use the contained hashtable to search a directory") Closes: https://sashiko.dev/#/patchset/20260716103030.3065561-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260902121024.3328255-3-dhowells@redhat.com cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/dir_edit.c | 9 ++------- fs/afs/dir_search.c | 10 ++-------- fs/afs/internal.h | 8 ++++++++ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/fs/afs/dir_edit.c b/fs/afs/dir_edit.c index 3ead36a07048..c31303059444 100644 --- a/fs/afs/dir_edit.c +++ b/fs/afs/dir_edit.c @@ -442,7 +442,7 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, /* Check and clear the entry. */ de = &block->dirents[slot]; if (de->u.valid != 1) - goto error_unmap; + goto error; trace_afs_edit_dir(vnode, why, afs_edit_dir_delete, b, slot, ntohl(de->u.vnode), ntohl(de->u.unique), @@ -458,7 +458,6 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, /* Clear the constituent entries. */ next = de->u.hash_next; memset(de, 0, sizeof(*de) * iter.nr_slots); - kunmap_local(block); /* Adjust the hash chain: if iter->prev_entry is 0, the hashtable head * index is previous; otherwise it's slot number of the previous entry. @@ -485,7 +484,6 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, pde = &pblock->dirents[ps]; prev_next = pde->u.hash_next; if (prev_next != htons(entry)) { - kunmap_local(pblock); pr_warn("%llx:%llx:%x: not prev in chain b=%x p=%x,%x e=%x %*s", vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique, iter.bucket, iter.prev_entry, prev_next, entry, @@ -493,7 +491,6 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, goto error; } pde->u.hash_next = next; - kunmap_local(pblock); } netfs_single_mark_inode_dirty(&vnode->netfs.inode); @@ -503,18 +500,16 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, _debug("Remove %s from %u[%u]", name->name, b, slot); out_unmap: + afs_dir_end_iter(&iter); kunmap_local(meta); _leave(""); return; already_invalidated: - kunmap_local(block); trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_inval, 0, 0, 0, 0, name->name); goto out_unmap; -error_unmap: - kunmap_local(block); error: trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_error, 0, 0, 0, 0, name->name); diff --git a/fs/afs/dir_search.c b/fs/afs/dir_search.c index 4977ad81fa82..11ebdfffcb1d 100644 --- a/fs/afs/dir_search.c +++ b/fs/afs/dir_search.c @@ -75,10 +75,7 @@ union afs_xdr_dir_block *afs_dir_find_block(struct afs_dir_iter *iter, size_t bl _enter("%zx,%d", block, slot); - if (iter->block) { - kunmap_local(iter->block); - iter->block = NULL; - } + afs_dir_end_iter(iter); if (dvnode->directory_size < blend) goto fail; @@ -174,10 +171,7 @@ int afs_dir_search_bucket(struct afs_dir_iter *iter, const struct qstr *name, ret = -ENOENT; found: bad: - if (iter->block) { - kunmap_local(iter->block); - iter->block = NULL; - } + afs_dir_end_iter(iter); if (ret == -ESTALE) afs_invalidate_dir(iter->dvnode, afs_dir_invalid_iter_stale); _leave(" = %d", ret); diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 290873bac89b..330654ed16ec 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -1133,6 +1133,14 @@ int afs_dir_search_bucket(struct afs_dir_iter *iter, const struct qstr *name, int afs_dir_search(struct afs_vnode *dvnode, const struct qstr *name, struct afs_fid *_fid, afs_dataversion_t *_dir_version); +static inline void afs_dir_end_iter(struct afs_dir_iter *iter) +{ + if (iter->block) { + kunmap_local(iter->block); + iter->block = NULL; + } +} + /* * dir_silly.c */ From 044d596094af4b769fb8e1173dff0d08bd68db6c Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 2 Sep 2026 13:10:21 +0100 Subject: [PATCH 0155/1317] afs: Fix incorrect free in candidate cleanup in afs_lookup_server() Fix afs_lookup_server() to not free an existing server's endpoint state when cleaning up a candidate server. The candidate record doesn't have an endpoint state yet at this point, so the free for that can just be removed. Fixes: 4882ba78574e ("afs: Fix afs_server ref accounting") Link: https://sashiko.dev/#/patchset/20260729160108.2031453-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260902121024.3328255-4-dhowells@redhat.com cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/server.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/afs/server.c b/fs/afs/server.c index 0fe162ea2a36..189138bd6d71 100644 --- a/fs/afs/server.c +++ b/fs/afs/server.c @@ -242,7 +242,6 @@ struct afs_server *afs_lookup_server(struct afs_cell *cell, struct key *key, out: afs_put_addrlist(alist, afs_alist_trace_put_server_create); if (candidate) { - kfree(rcu_access_pointer(server->endpoint_state)); kfree(candidate); afs_dec_servers_outstanding(cell->net); } From ba0623fc19a424f4745394c499f9f28a8d88d397 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Wed, 2 Sep 2026 13:10:22 +0100 Subject: [PATCH 0156/1317] afs: Clear stale peer app data after address list changes afs_fs_probe_fileserver() fetches the current endpoint state under server->fs_lock, but leaves old_alist as NULL. Consequently, afs_set_peer_appdata() treats every address list replacement as initial setup and only binds the new peers; it never unbinds peers removed from the old list. An address refresh can therefore proceed as follows. CPU 0 replaces server S's list and drops Pold without clearing Pold->app_data. The server destroyer then clears only S's current peers and lets S reach its RCU callback. After the callback frees S, CPU 1 handles a callback through an RxRPC connection that still pins Pold, reads Pold->app_data, and calls afs_use_server() on the freed object. KASAN reported: BUG: KASAN: slab-use-after-free in afs_find_server+0x3c/0xa0 Read of size 4 at addr ffff8881013e1af0 by task krxrpcio/7001/74 Call Trace: afs_find_server+0x3c/0xa0 afs_rx_new_call+0x15c/0x390 rxrpc_new_incoming_call+0x97c/0x1730 rxrpc_input_packet.constprop.0+0xd03/0xec0 rxrpc_io_thread+0x967/0x1640 Allocated by task 93: afs_lookup_server+0x1a7/0x14c0 afs_alloc_server_list+0x43f/0xb60 afs_create_volume+0x923/0x1490 afs_get_tree+0x1c6/0x10a0 Freed by task 0: kfree+0x131/0x3c0 rcu_core+0x50a/0x1850 Last potentially related work creation: __call_rcu_common.constprop.0+0x71/0xa10 afs_put_server+0x213/0x2b0 Preserve old->addresses for the peer app-data update so that removed peers are cleared before the endpoint state is replaced. Also advance both cursors when the old and new lists share a peer; activating the old/new comparison without this would otherwise loop forever on the shared entry. Fixes: 40e8b52fe8c8 ("afs: Use the per-peer app data provided by rxrpc") Signed-off-by: Chengfeng Ye Signed-off-by: Qi Zhang Signed-off-by: David Howells Link: https://patch.msgid.link/20260902121024.3328255-5-dhowells@redhat.com cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/addr_list.c | 5 ++++- fs/afs/fs_probe.c | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/afs/addr_list.c b/fs/afs/addr_list.c index 63bf096b721a..73195d76b481 100644 --- a/fs/afs/addr_list.c +++ b/fs/afs/addr_list.c @@ -394,8 +394,11 @@ void afs_set_peer_appdata(struct afs_server *server, struct rxrpc_peer *pn = new_alist->addrs[n].peer; struct rxrpc_peer *po = old_alist->addrs[o].peer; - if (pn == po) + if (pn == po) { + n++; + o++; continue; + } if (pn < po) { rxrpc_kernel_set_peer_data(pn, data); n++; diff --git a/fs/afs/fs_probe.c b/fs/afs/fs_probe.c index a91ad1938d07..8c62334dbfe7 100644 --- a/fs/afs/fs_probe.c +++ b/fs/afs/fs_probe.c @@ -258,6 +258,7 @@ int afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server, lockdep_is_held(&server->fs_lock)); if (old) { estate->responsive_set = old->responsive_set; + old_alist = old->addresses; if (!new_alist) new_alist = old->addresses; } From 33da68f61d25ef8411489d06514ff627c1f88152 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:49 +0000 Subject: [PATCH 0157/1317] arm64: dts: renesas: r9a09g057: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 050ee38d0002 ("arm64: dts: renesas: r9a09g057: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-2-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g057.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g057.dtsi b/arch/arm64/boot/dts/renesas/r9a09g057.dtsi index 639693d464a7..188ce9f9c7c2 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g057.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g057.dtsi @@ -1715,23 +1715,28 @@ queue3 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -1816,23 +1821,28 @@ queue3 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From 66fcbdbeca0118b8aeac218b33fa18c394513543 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:50 +0000 Subject: [PATCH 0158/1317] arm64: dts: renesas: r9a09g056: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: c8c8a57c5b40 ("arm64: dts: renesas: r9a09g056: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-3-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g056.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g056.dtsi b/arch/arm64/boot/dts/renesas/r9a09g056.dtsi index 76fa34ff3d07..77c2221a9e2a 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g056.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g056.dtsi @@ -1585,23 +1585,28 @@ queue3 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -1686,23 +1691,28 @@ queue3 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From 63016c3a91f2c458ca75869c8c782e899591f22d Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:51 +0000 Subject: [PATCH 0159/1317] arm64: dts: renesas: r9a09g047: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 41ffbb1c42d3 ("arm64: dts: renesas: r9a09g047: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260722085353.136986-4-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g047.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g047.dtsi b/arch/arm64/boot/dts/renesas/r9a09g047.dtsi index 73757e8e2197..060405d4a2dd 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g047.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g047.dtsi @@ -1913,23 +1913,28 @@ queue3 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -2013,23 +2018,28 @@ queue3 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From f9d9a1913c09366bf7b967d39575e06cb80128dc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:52 +0000 Subject: [PATCH 0160/1317] arm64: dts: renesas: r9a09g077: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 394c1e24a4cf ("arm64: dts: renesas: r9a09g077: Add GMAC nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-5-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g077.dtsi | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g077.dtsi b/arch/arm64/boot/dts/renesas/r9a09g077.dtsi index 40494159831d..bac39390ead7 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g077.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g077.dtsi @@ -642,36 +642,45 @@ queue7 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -788,36 +797,45 @@ queue7 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -934,36 +952,45 @@ queue7 { mtl_tx_setup2: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; From 2ac7bad110be6ebe478d6bd57821f7f5259a1f54 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:53 +0000 Subject: [PATCH 0161/1317] arm64: dts: renesas: r9a09g087: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: c4698a34993b ("arm64: dts: renesas: r9a09g087: Add GMAC nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-6-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g087.dtsi | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g087.dtsi b/arch/arm64/boot/dts/renesas/r9a09g087.dtsi index e8d4f76949cc..03b976d93e10 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g087.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g087.dtsi @@ -643,36 +643,45 @@ queue7 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -790,36 +799,45 @@ queue7 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -937,36 +955,45 @@ queue7 { mtl_tx_setup2: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; From 9cdfad5dd5529e1700f51feaed5e8a90044d7e35 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 18 Aug 2026 12:52:29 +0900 Subject: [PATCH 0162/1317] RDMA/srp: Fix srp_remove_target() Remove all logical units before disconnecting the transport because one or more SCSI commands may be submitted while removing logical units. Remove the SCSI host after the transport has been disconnected because the code that disconnects the transport needs resources that are freed by the code that removes the SCSI host (SCSI host tag set). Remove the srp_rport_get() and srp_rport_put() calls because the purpose of these calls was to keep the rport until tl_err_work is cancelled. Reported-by: Yehyeong Lee Closes: https://lore.kernel.org/linux-rdma/20260812190418.200337-1-yhlee@isslab.korea.ac.kr/ Signed-off-by: Bart Van Assche Signed-off-by: Yehyeong Lee Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/srp/ib_srp.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c index 6b429ef63f8f..955f36efeebd 100644 --- a/drivers/infiniband/ulp/srp/ib_srp.c +++ b/drivers/infiniband/ulp/srp/ib_srp.c @@ -1038,15 +1038,20 @@ static void srp_del_scsi_host_attr(struct Scsi_Host *shost) static void srp_remove_target(struct srp_target_port *target) { + struct scsi_device *sdev; struct srp_rdma_ch *ch; int i; WARN_ON_ONCE(target->state != SRP_TARGET_REMOVED); srp_del_scsi_host_attr(target->scsi_host); - srp_rport_get(target->rport); - srp_remove_host(target->scsi_host); - scsi_remove_host(target->scsi_host); + /* + * Remove all logical units. This must happen before the + * srp_disconnect_target() call because scsi_remove_device() may trigger + * submission of SCSI commands. See also sd_shutdown(). + */ + shost_for_each_device(sdev, target->scsi_host) + scsi_remove_device(sdev); srp_stop_rport_timers(target->rport); srp_disconnect_target(target); kobj_ns_drop(KOBJ_NS_TYPE_NET, to_ns_common(target->net)); @@ -1055,7 +1060,8 @@ static void srp_remove_target(struct srp_target_port *target) srp_free_ch_ib(target, ch); } cancel_work_sync(&target->tl_err_work); - srp_rport_put(target->rport); + srp_remove_host(target->scsi_host); + scsi_remove_host(target->scsi_host); kfree(target->ch); target->ch = NULL; From d85f0f0a7c85756fc992c70d869706f19dac9259 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Wed, 19 Aug 2026 10:08:04 +0900 Subject: [PATCH 0163/1317] IB/iser: reject a remote invalidation of an unregistered direction A write command whose data is sent entirely as immediate data is not registered. iser_reg_mem_fastreg() takes the DMA key path and leaves rdma_reg[ISER_DIR_OUT].desc at NULL, while iser_dma_map_task_data() has already set dir[ISER_DIR_OUT]. iser_check_remote_inv() looks at dir[] alone and hands the descriptor to iser_inv_desc(), which reads desc->sig_protected. A target that answers such a command with IB_WR_SEND_WITH_INV faults the initiator. Leaving those commands unregistered is deliberate. The same function already terminates the connection when a target sends a remote invalidation the initiator did not ask for. A target that invalidates a direction that was never registered is in the same class, so give it the same answer. Oops: general protection fault, probably for non-canonical address 0xdffffc0000000004: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027] CPU: 0 UID: 0 PID: 40 Comm: kworker/u8:2 Not tainted 7.2.0-rc5-ISERHOST-gf5098b6bae76-dirty #3 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: rxe_wq do_work RIP: 0010:iser_task_rsp+0x6d6/0xec0 Code: 48 c1 ea 03 80 3c 02 00 0f 85 ba 06 00 00 48 8b 9b 78 01 00 00 48 b8 00 00 00 00 00 fc ff df 48 8d 7b 20 48 89 fa 48 c1 ea 03 <0f> b6 04 02 84 c0 74 06 0f 8e 76 06 00 00 80 7b 20 00 0f 84 3d 04 RSP: 0018:ffff88811b008db8 EFLAGS: 00010202 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000001848 RDX: 0000000000000004 RSI: 1ffff11021587b12 RDI: 0000000000000020 RBP: ffff88810adc1ae4 R08: ffff888109b7f860 R09: ffffffff90a922c0 R10: ffff88810adc1a1c R11: 000000000000003c R12: ffff888109b7f800 R13: ffff88810adc1acc R14: ffff888109b7f820 R15: 0000000000000000 FS: 0000000000000000(0000) GS:ffff88818a676000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000005afe2b CR3: 000000010af23005 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: __ib_process_cq+0xe1/0x390 ib_poll_handler+0x6e/0x200 irq_poll_softirq+0x1df/0x480 ? clockevents_program_event+0x2ba/0x860 ? __pfx_irq_poll_softirq+0x10/0x10 handle_softirqs+0x18e/0x590 ? __pfx_handle_softirqs+0x10/0x10 ? __hrtimer_rearm_deferred+0x156/0x450 do_softirq+0x3b/0x60 __local_bh_enable_ip+0x61/0x70 __alloc_skb+0x732/0x890 ? _raw_spin_lock_irqsave+0x85/0xe0 ? __pfx___alloc_skb+0x10/0x10 ? _raw_read_unlock_irqrestore+0x16/0x50 rxe_init_packet+0x16b/0x4f0 prepare_ack_packet+0xb8/0x830 rxe_receiver+0x499/0x9980 ? __pfx_rxe_receiver+0x10/0x10 ? rxe_completer+0x29e5/0x38c0 ? hrtimer_start_range_ns_common+0x75f/0x1730 ? hrtimer_start_range_ns+0xa6/0x2c0 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 ? __pfx_rxe_receiver+0x10/0x10 do_work+0x144/0x470 process_one_work+0x633/0x1030 ? assign_work+0x11d/0x370 worker_thread+0x45b/0xd10 ? __pfx_worker_thread+0x10/0x10 kthread+0x2c6/0x3b0 ? recalc_sigpending+0x15c/0x1e0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x36e/0x5a0 ? __pfx_ret_from_fork+0x10/0x10 ? __switch_to+0x572/0xdd0 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Modules linked in: ---[ end trace 0000000000000000 ]--- Fixes: 59caaed7a72a ("IB/iser: Support the remote invalidation exception") Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260819010804.641772-1-yhlee@isslab.korea.ac.kr Reviewed-by: Max Gurtovoy Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/iser/iser_initiator.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/ulp/iser/iser_initiator.c b/drivers/infiniband/ulp/iser/iser_initiator.c index 12a2d12fef07..7ea6888b479c 100644 --- a/drivers/infiniband/ulp/iser/iser_initiator.c +++ b/drivers/infiniband/ulp/iser/iser_initiator.c @@ -598,11 +598,8 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, iser_dbg("conn %p: remote invalidation for rkey %#x\n", iser_conn, rkey); - if (unlikely(!iser_conn->snd_w_inv)) { - iser_err("conn %p: unexpected remote invalidation, terminating connection\n", - iser_conn); - return -EPROTO; - } + if (unlikely(!iser_conn->snd_w_inv)) + goto bad_inv; task = iscsi_itt_to_ctask(iser_conn->iscsi_conn, hdr->itt); if (likely(task)) { @@ -611,12 +608,16 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, if (iser_task->dir[ISER_DIR_IN]) { desc = iser_task->rdma_reg[ISER_DIR_IN].desc; + if (unlikely(!desc)) + goto bad_inv; if (unlikely(iser_inv_desc(desc, rkey))) return -EINVAL; } if (iser_task->dir[ISER_DIR_OUT]) { desc = iser_task->rdma_reg[ISER_DIR_OUT].desc; + if (unlikely(!desc)) + goto bad_inv; if (unlikely(iser_inv_desc(desc, rkey))) return -EINVAL; } @@ -627,6 +628,11 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, } return 0; + +bad_inv: + iser_err("conn %p: unexpected remote invalidation, terminating connection\n", + iser_conn); + return -EPROTO; } From a8fe3dfce8c0d8a76dc3d8486a5bff5feebe156f Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Fri, 21 Aug 2026 17:06:20 +0900 Subject: [PATCH 0164/1317] IB/isert: wait for deferred control PDU completions before releasing the connection isert_send_done() hands ISTATE_SEND_TASKMGTRSP, ISTATE_SEND_REJECT and ISTATE_SEND_TEXTRSP completions off to isert_comp_wq and returns. The work item then runs isert_completion_put() -> isert_put_cmd(), which reads isert_conn->conn and takes conn->cmd_lock. Nothing orders that work item against teardown. isert_wait_conn() queues isert_release_work, which frees isert_conn, and iscsit_close_connection() frees the iscsit_conn right after it returns, so the queued work can run against freed memory. Count the deferred control PDU completions per connection and let isert_wait_conn() wait for them before the release work is queued. ISTATE_SEND_LOGOUTRSP is deliberately not counted: that branch runs iscsit_logout_post_handler(), which ends up waiting for conn->conn_wait_comp, and that completion is only sent by iscsit_close_connection() after it has called iscsit_wait_conn(). Waiting for it here would deadlock. Its wait stays the existing isert_wait4logout(). The splat below is from a kernel with tracing printk()s and an msleep(200) injected into isert_do_control_comp() to widen the window: BUG: KASAN: slab-use-after-free in isert_put_cmd+0x53d/0x620 Read of size 8 at addr ffff8881054f1038 by task kworker/u17:1/182 CPU: 0 UID: 0 PID: 182 Comm: kworker/u17:1 Tainted: G B 7.2.0-rc5-TWIDE-gb8babf08acc7 #1 PREEMPT(lazy) Tainted: [B]=BAD_PAGE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: isert_comp_wq isert_do_control_comp Call Trace: dump_stack_lvl+0x53/0x70 print_report+0xd0/0x630 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x3e/0x70 ? isert_put_cmd+0x53d/0x620 kasan_report+0xce/0x100 ? isert_put_cmd+0x53d/0x620 isert_put_cmd+0x53d/0x620 ? isert_completion_put+0x305/0x330 ? isert_do_control_comp+0x2ef/0x310 process_one_work+0x633/0x1030 ? assign_work+0x11d/0x370 worker_thread+0x45b/0xd10 ? __pfx_worker_thread+0x10/0x10 ? __pfx_worker_thread+0x10/0x10 kthread+0x2c6/0x3b0 ? recalc_sigpending+0x15c/0x1e0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x36e/0x5a0 ? __pfx_ret_from_fork+0x10/0x10 ? __switch_to+0x572/0xdd0 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Allocated by task 48: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0x8f/0xa0 __kmalloc_cache_noprof+0x158/0x370 isert_cma_handler+0x1e3/0x2ae0 cma_cm_event_handler+0x3e/0x240 cma_ib_req_handler+0x17d9/0x4490 cm_process_work+0x41/0x330 cm_work_handler+0x5727/0xc160 process_one_work+0x633/0x1030 worker_thread+0x45b/0xd10 kthread+0x2c6/0x3b0 ret_from_fork+0x36e/0x5a0 ret_from_fork_asm+0x1a/0x30 Freed by task 184: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x43/0x70 kfree+0x121/0x380 iscsit_close_connection+0x7cf/0x1e60 iscsit_take_action_for_connection_exit+0x1b6/0x360 iscsi_target_tx_thread+0x472/0x690 kthread+0x2c6/0x3b0 ret_from_fork+0x36e/0x5a0 ret_from_fork_asm+0x1a/0x30 Fixes: b8d26b3be8b3 ("iser-target: Add iSCSI Extensions for RDMA (iSER) target driver") Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260821080620.1694119-1-yhlee@isslab.korea.ac.kr Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/isert/ib_isert.c | 22 ++++++++++++++++++++++ drivers/infiniband/ulp/isert/ib_isert.h | 2 ++ 2 files changed, 24 insertions(+) diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c index 5087ea983071..e69db43370ff 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.c +++ b/drivers/infiniband/ulp/isert/ib_isert.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "ib_isert.h" @@ -310,6 +311,7 @@ isert_init_conn(struct isert_conn *isert_conn) init_completion(&isert_conn->login_req_comp); init_waitqueue_head(&isert_conn->rem_wait); kref_init(&isert_conn->kref); + atomic_set(&isert_conn->ctrl_comp_cnt, 0); mutex_init(&isert_conn->mutex); INIT_WORK(&isert_conn->release_work, isert_release_work); } @@ -1694,6 +1696,8 @@ isert_do_control_comp(struct work_struct *work) struct isert_conn *isert_conn = isert_cmd->conn; struct ib_device *ib_dev = isert_conn->cm_id->device; struct iscsit_cmd *cmd = isert_cmd->iscsit_cmd; + /* The switch below may free isert_cmd. */ + bool counted = isert_cmd->ctrl_counted; isert_dbg("Cmd %p i_state %d\n", isert_cmd, cmd->i_state); @@ -1715,6 +1719,14 @@ isert_do_control_comp(struct work_struct *work) dump_stack(); break; } + + /* + * The count is what keeps isert_conn alive, so drop it last. The wait + * queue lives in the global hash table, not in isert_conn, so this is + * safe even if the waiter has already freed the connection. + */ + if (counted && atomic_dec_and_test(&isert_conn->ctrl_comp_cnt)) + wake_up_var(&isert_conn->ctrl_comp_cnt); } static void @@ -1758,6 +1770,12 @@ isert_send_done(struct ib_cq *cq, struct ib_wc *wc) case ISTATE_SEND_TEXTRSP: isert_unmap_tx_desc(tx_desc, ib_dev); + /* Paired with the wait in isert_wait_conn(). */ + isert_cmd->ctrl_counted = + isert_cmd->iscsit_cmd->i_state != ISTATE_SEND_LOGOUTRSP; + if (isert_cmd->ctrl_counted) + atomic_inc(&isert_conn->ctrl_comp_cnt); + INIT_WORK(&isert_cmd->comp_work, isert_do_control_comp); queue_work(isert_comp_wq, &isert_cmd->comp_work); return; @@ -2602,6 +2620,10 @@ static void isert_wait_conn(struct iscsit_conn *conn) isert_wait4cmds(conn); isert_wait4logout(isert_conn); + /* Paired with the count taken in isert_send_done(). */ + wait_var_event(&isert_conn->ctrl_comp_cnt, + !atomic_read(&isert_conn->ctrl_comp_cnt)); + queue_work(isert_release_wq, &isert_conn->release_work); } diff --git a/drivers/infiniband/ulp/isert/ib_isert.h b/drivers/infiniband/ulp/isert/ib_isert.h index 0bac5aa66c80..519b17e54bd3 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.h +++ b/drivers/infiniband/ulp/isert/ib_isert.h @@ -153,6 +153,7 @@ struct isert_cmd { struct work_struct comp_work; struct scatterlist sg; bool ctx_init_done; + bool ctrl_counted; }; static inline struct isert_cmd *tx_desc_to_cmd(struct iser_tx_desc *desc) @@ -187,6 +188,7 @@ struct isert_conn { struct mutex mutex; struct kref kref; struct work_struct release_work; + atomic_t ctrl_comp_cnt; bool logout_posted; bool snd_w_inv; wait_queue_head_t rem_wait; From 6c368f7baaea63c1c7c28c6df271511f2a1562c9 Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Fri, 21 Aug 2026 17:09:08 +0800 Subject: [PATCH 0165/1317] RDMA/bnxt_re: check create_singlethread_workqueue() in DCB setup bnxt_re_init_dcb_wq() ignores a failed allocation. The async DCB handler later calls queue_work() on the NULL pointer. Fixes: 51dc5312dcd9 ("RDMA/bnxt_re: Add support to handle DCB_CONFIG_CHANGE event") Signed-off-by: Linkai Gong Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/bnxt_re/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/main.c b/drivers/infiniband/hw/bnxt_re/main.c index ce72db1b4bc3..17654a9e23fe 100644 --- a/drivers/infiniband/hw/bnxt_re/main.c +++ b/drivers/infiniband/hw/bnxt_re/main.c @@ -356,9 +356,13 @@ static int bnxt_re_update_qp1_tos_dscp(struct bnxt_re_dev *rdev) return bnxt_qplib_modify_qp(&rdev->qplib_res, &qp->qplib_qp); } -static void bnxt_re_init_dcb_wq(struct bnxt_re_dev *rdev) +static int bnxt_re_init_dcb_wq(struct bnxt_re_dev *rdev) { rdev->dcb_wq = create_singlethread_workqueue("bnxt_re_dcb_wq"); + if (!rdev->dcb_wq) + return -ENOMEM; + + return 0; } static void bnxt_re_uninit_dcb_wq(struct bnxt_re_dev *rdev) @@ -2339,7 +2343,9 @@ static int bnxt_re_dev_init(struct bnxt_re_dev *rdev, u8 op_type) } bnxt_re_debugfs_add_pdev(rdev); - bnxt_re_init_dcb_wq(rdev); + rc = bnxt_re_init_dcb_wq(rdev); + if (rc) + goto fail; bnxt_re_net_register_async_event(rdev); if (!rdev->is_virtfn) From 99c24a8968ebef0573825b5cb89d5985d51635b9 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sat, 22 Aug 2026 23:33:43 -0400 Subject: [PATCH 0166/1317] RDMA/rtrs: guard against null kobj name In the client, if `init_path()` errors, the callee tries to clean up with `rtrs_clt_close_conns()`. However, this can lead to calling the event tracing code with `clt_path->kobj->name` being `NULL` and thus causing a null pointer dereference when trying to copy from it. This just adds a guard to check that the name is not `NULL` before copying from it. The server appears to have a similar pattern. Fixes: 5a93929d9f9a1 ("RDMA/rtrs-clt: Add event tracing support") Fixes: c16762b7bf54d ("RDMA/rtrs-srv: Add event tracing support") Reported-by: syzbot+1695193198994f4e7fed@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1695193198994f4e7fed Signed-off-by: Ryan Mehri Link: https://patch.msgid.link/20260823034303.163403-1-ryan.mehri1@gmail.com Reviewed-by: Jack Wang Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h | 2 +- drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h b/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h index 7738e2676855..29e23404bb7b 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h @@ -55,7 +55,7 @@ DECLARE_EVENT_CLASS(rtrs_clt_conn_class, __entry->max_reconnect_attempts = clt->max_reconnect_attempts; __entry->fail_cnt = clt_path->stats->reconnects.fail_cnt; __entry->success_cnt = clt_path->stats->reconnects.successful_cnt; - memcpy(__entry->sessname, kobject_name(&clt_path->kobj), NAME_MAX); + strscpy(__entry->sessname, kobject_name(&clt_path->kobj) ?: "", NAME_MAX); ), TP_printk("RTRS-CLT: sess='%s' state=%s attempts='%d' max-attempts='%d' fail='%d' success='%d'", diff --git a/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h b/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h index 587d3e033081..a7d7b971e6c8 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h @@ -61,7 +61,7 @@ TRACE_EVENT(send_io_resp_imm, __entry->msg_id = id->msg_id; __entry->wr_cnt = atomic_read(&con->c.wr_cnt); __entry->signal_interval = s->signal_interval; - memcpy(__entry->sessname, kobject_name(&srv_path->kobj), NAME_MAX); + strscpy(__entry->sessname, kobject_name(&srv_path->kobj) ?: "", NAME_MAX); ), TP_printk("sess='%s' state='%s' dir=%s err='%d' inval='%d' glob-inval='%d' msgid='%u' wrcnt='%d' sig-interval='%u'", From 23d7e03a52ece66b992620aa9b8fa5f164077c0f Mon Sep 17 00:00:00 2001 From: Sriharsha Basavapatna Date: Mon, 24 Aug 2026 22:54:43 +0530 Subject: [PATCH 0167/1317] RDMA/bnxt_re: Avoid exposing umdbr to userspace The umdbr field in struct bnxt_re_db_region returns the raw unmapped PCI BAR address of the doorbell region. Avoid sharing this field to the userspace. Change this to a reserved field and stop populating it, keeping the ABI layout and size unchanged for existing binaries. Fixes: 1234a9d8aebb ("RDMA/bnxt_re: Support doorbell extensions") Signed-off-by: Sriharsha Basavapatna Link: https://patch.msgid.link/20260824172443.33943-1-sriharsha.basavapatna@broadcom.com Reviewed-by: Selvin Xavier Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/bnxt_re/uapi.c | 6 ++---- include/uapi/rdma/bnxt_re-abi.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/uapi.c b/drivers/infiniband/hw/bnxt_re/uapi.c index feaf98631fc5..a407c6bc469b 100644 --- a/drivers/infiniband/hw/bnxt_re/uapi.c +++ b/drivers/infiniband/hw/bnxt_re/uapi.c @@ -462,7 +462,6 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_DBR_ALLOC)(struct uverbs_attr_bundle *a uobj->object = obj; uverbs_finalize_uobj_create(attrs, BNXT_RE_ALLOC_DBR_HANDLE); - dbr.umdbr = dpi->umdbr; dbr.dpi = dpi->dpi; ret = uverbs_copy_to_struct_or_zero(attrs, BNXT_RE_ALLOC_DBR_ATTR, &dbr, sizeof(dbr)); @@ -525,7 +524,6 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_GET_DEFAULT_DBR)(struct uverbs_attr_bun return PTR_ERR(ib_uctx); uctx = container_of(ib_uctx, struct bnxt_re_ucontext, ib_uctx); - dpi.umdbr = uctx->dpi.umdbr; dpi.dpi = uctx->dpi.dpi; ret = uverbs_copy_to_struct_or_zero(attrs, BNXT_RE_DEFAULT_DBR_ATTR, @@ -543,7 +541,7 @@ DECLARE_UVERBS_NAMED_METHOD(BNXT_RE_METHOD_DBR_ALLOC, UA_MANDATORY), UVERBS_ATTR_PTR_OUT(BNXT_RE_ALLOC_DBR_ATTR, UVERBS_ATTR_STRUCT(struct bnxt_re_db_region, - umdbr), + reserved2), UA_MANDATORY), UVERBS_ATTR_PTR_OUT(BNXT_RE_ALLOC_DBR_OFFSET, UVERBS_ATTR_TYPE(u64), @@ -563,7 +561,7 @@ DECLARE_UVERBS_NAMED_OBJECT(BNXT_RE_OBJECT_DBR, DECLARE_UVERBS_NAMED_METHOD(BNXT_RE_METHOD_GET_DEFAULT_DBR, UVERBS_ATTR_PTR_OUT(BNXT_RE_DEFAULT_DBR_ATTR, UVERBS_ATTR_STRUCT(struct bnxt_re_db_region, - umdbr), + reserved2), UA_MANDATORY)); DECLARE_UVERBS_GLOBAL_METHODS(BNXT_RE_OBJECT_DEFAULT_DBR, diff --git a/include/uapi/rdma/bnxt_re-abi.h b/include/uapi/rdma/bnxt_re-abi.h index 856a1b3036e9..15ed2a51f8f1 100644 --- a/include/uapi/rdma/bnxt_re-abi.h +++ b/include/uapi/rdma/bnxt_re-abi.h @@ -250,7 +250,7 @@ struct bnxt_re_query_device_ex_resp { struct bnxt_re_db_region { __u32 dpi; __u32 reserved; - __aligned_u64 umdbr; + __aligned_u64 reserved2; }; enum bnxt_re_obj_dbr_alloc_attrs { From 90af7fde083e1b22c349c3a8b1626728e44e474c Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Tue, 4 Aug 2026 21:34:56 +0000 Subject: [PATCH 0168/1317] memstick: ms_block: destroy io_queue workqueue on removal msb_init_disk() creates the per-card ordered workqueue msb->io_queue with alloc_ordered_workqueue(). It is torn down with destroy_workqueue() only on the init error path; msb_remove() never destroys it. msb_stop() merely flushes the queue, and neither msb_data_clear() nor put_disk() free it. As a result every card insert/remove cycle leaks the workqueue and its kworker, exhausting kernel memory over repeated cycles. Destroy the workqueue in msb_remove() after the disk has been removed and the queue drained. Fixes: 0ab30494bc4f ("memstick: add support for legacy memorysticks") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yifei Gao Signed-off-by: Ulf Hansson --- drivers/memstick/core/ms_block.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/memstick/core/ms_block.c b/drivers/memstick/core/ms_block.c index ce33907bfc24..65154e569a9e 100644 --- a/drivers/memstick/core/ms_block.c +++ b/drivers/memstick/core/ms_block.c @@ -2204,6 +2204,8 @@ static void msb_remove(struct memstick_dev *card) msb_data_clear(msb); mutex_unlock(&msb_disk_lock); + destroy_workqueue(msb->io_queue); + put_disk(msb->disk); memstick_set_drvdata(card, NULL); } From 08d4d9802d58bf032099091e6acf719f3298f28e Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Wed, 26 Aug 2026 15:31:46 +0800 Subject: [PATCH 0169/1317] RDMA/uverbs: Fix potential leak of resources->collection in flow_resources_alloc() The two array allocations are done unconditionally and only checked afterwards, so if the counters allocation fails while the collection allocation succeeds, the error path frees counters and the containing struct but never frees resources->collection, losing the only pointer to it. Fixes: de7498147d00 ("RDMA/uverbs: Refactor flow_resources_alloc() function") Signed-off-by: Li RongQing Link: https://patch.msgid.link/20260826073146.2203-1-lirongqing@baidu.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/uverbs_flow.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/infiniband/core/uverbs_flow.c b/drivers/infiniband/core/uverbs_flow.c index 1528a294f7f8..de5a2769f084 100644 --- a/drivers/infiniband/core/uverbs_flow.c +++ b/drivers/infiniband/core/uverbs_flow.c @@ -26,6 +26,7 @@ struct ib_uflow_resources *flow_resources_alloc(size_t num_specs) return resources; err: + kfree(resources->collection); kfree(resources->counters); kfree(resources); From 3476c28c9addfa253f505e6bd87f1f5598b961d0 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Wed, 26 Aug 2026 15:32:16 +0800 Subject: [PATCH 0170/1317] RDMA/mad: Fix receive buffer leak when PKey enforcement fails ib_mad_complete_recv() initializes mad_recv_wc->rmpp_list and then runs ib_mad_enforce_security() before linking recv_buf onto that list. On failure it calls ib_free_recv_mad(), which only walks rmpp_list and frees the ib_mad_private of every buffer found there. As the list is still empty at that point, nothing is freed at all. The caller cannot clean up either: ib_mad_recv_done() sets recv to NULL right after ib_mad_complete_recv() returns, assuming the MAD layer took ownership of the buffer. Every MAD that fails the PKey check therefore leaks one ib_mad_private (about 300 bytes per IB port MAD, ~2K for OPA), and a remote node can trigger this repeatedly by sending MADs with a wrong PKey. Link recv_buf onto rmpp_list right after the list is initialized, so the error path has something to free. Fixes: 47a2b338fe63 ("IB/core: Enforce security on management datagrams") Signed-off-by: Li RongQing Link: https://patch.msgid.link/20260826073216.2367-1-lirongqing@baidu.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/mad.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/core/mad.c b/drivers/infiniband/core/mad.c index e0b3b36b8b14..3c91f00d09c6 100644 --- a/drivers/infiniband/core/mad.c +++ b/drivers/infiniband/core/mad.c @@ -2059,6 +2059,8 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, int ret; INIT_LIST_HEAD(&mad_recv_wc->rmpp_list); + list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); + ret = ib_mad_enforce_security(mad_agent_priv, mad_recv_wc->wc->pkey_index); if (ret) { @@ -2067,7 +2069,6 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, return; } - list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); if (is_kernel_rmpp_data_response(mad_agent_priv, mad_recv_wc)) { spin_lock_irqsave(&mad_agent_priv->lock, flags); mad_send_wr = ib_find_send_mad(mad_agent_priv, mad_recv_wc); From 00baeade709fb66da647e8327e8398bb532e30f7 Mon Sep 17 00:00:00 2001 From: Cheng Xu Date: Fri, 28 Aug 2026 11:03:44 +0800 Subject: [PATCH 0171/1317] RDMA/erdma: Use IRQ-safe XArray helpers for QP and CQ tables Locked QP and CQ lookups from EQ interrupts can deadlock with create-path XArray updates. If an interrupt arrives while the create path holds the plain xa_lock, the lookup spins forever trying to acquire the same lock. Use IRQ-safe XArray helpers for all QP and CQ create-path updates, including the GSI QP store and error paths. Initialize both arrays with XA_FLAGS_LOCK_IRQ so sleeping allocations preserve interrupt state. Fixes: 98df2aee1459 ("RDMA/erdma: Hold CQ references when processing EQ events") Fixes: a52eeff32024 ("RDMA/erdma: Hold QP references for AE and CM processing") Signed-off-by: Cheng Xu Link: https://patch.msgid.link/20260828030344.88021-1-chengyou@linux.alibaba.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/erdma/erdma_main.c | 4 ++-- drivers/infiniband/hw/erdma/erdma_verbs.c | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/infiniband/hw/erdma/erdma_main.c b/drivers/infiniband/hw/erdma/erdma_main.c index 7e87a815e853..445182c6bc5d 100644 --- a/drivers/infiniband/hw/erdma/erdma_main.c +++ b/drivers/infiniband/hw/erdma/erdma_main.c @@ -572,8 +572,8 @@ static int erdma_ib_device_add(struct pci_dev *pdev) INIT_LIST_HEAD(&dev->cep_list); spin_lock_init(&dev->lock); - xa_init_flags(&dev->qp_xa, XA_FLAGS_ALLOC1); - xa_init_flags(&dev->cq_xa, XA_FLAGS_ALLOC1); + xa_init_flags(&dev->qp_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_IRQ); + xa_init_flags(&dev->cq_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_IRQ); dev->next_alloc_cqn = 1; dev->next_alloc_qpn = 1; diff --git a/drivers/infiniband/hw/erdma/erdma_verbs.c b/drivers/infiniband/hw/erdma/erdma_verbs.c index 65b1af1e6623..f18b88bba281 100644 --- a/drivers/infiniband/hw/erdma/erdma_verbs.c +++ b/drivers/infiniband/hw/erdma/erdma_verbs.c @@ -1021,15 +1021,15 @@ int erdma_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, init_completion(&qp->safe_free); if (qp->ibqp.qp_type == IB_QPT_GSI) { - old_entry = xa_store(&dev->qp_xa, 1, qp, GFP_KERNEL); + old_entry = xa_store_irq(&dev->qp_xa, 1, qp, GFP_KERNEL); if (xa_is_err(old_entry)) ret = xa_err(old_entry); else qp->ibqp.qp_num = 1; } else { - ret = xa_alloc_cyclic(&dev->qp_xa, &qp->ibqp.qp_num, qp, - XA_LIMIT(1, dev->attrs.max_qp - 1), - &dev->next_alloc_qpn, GFP_KERNEL); + ret = xa_alloc_cyclic_irq(&dev->qp_xa, &qp->ibqp.qp_num, qp, + XA_LIMIT(1, dev->attrs.max_qp - 1), + &dev->next_alloc_qpn, GFP_KERNEL); } if (ret < 0) { @@ -1089,7 +1089,7 @@ int erdma_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, else free_kernel_qp(qp); err_out_xa: - xa_erase(&dev->qp_xa, QP_ID(qp)); + xa_erase_irq(&dev->qp_xa, QP_ID(qp)); err_out: return ret; } @@ -1993,9 +1993,9 @@ int erdma_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr, refcount_set(&cq->refcount, 1); init_completion(&cq->free); - ret = xa_alloc_cyclic(&dev->cq_xa, &cq->cqn, cq, - XA_LIMIT(1, dev->attrs.max_cq - 1), - &dev->next_alloc_cqn, GFP_KERNEL); + ret = xa_alloc_cyclic_irq(&dev->cq_xa, &cq->cqn, cq, + XA_LIMIT(1, dev->attrs.max_cq - 1), + &dev->next_alloc_cqn, GFP_KERNEL); if (ret < 0) return ret; @@ -2041,7 +2041,7 @@ int erdma_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr, } err_out_xa: - xa_erase(&dev->cq_xa, cq->cqn); + xa_erase_irq(&dev->cq_xa, cq->cqn); return ret; } From 3fb905f07ea45b31c8f67ba6e4668de46f527e65 Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Tue, 1 Sep 2026 16:00:14 +0000 Subject: [PATCH 0172/1317] RDMA/irdma: Enforce local fence for IB_WR_REG_MR Enforce local fence for IB_WR_REG_MR to avoid spurious FASTREG_VALID_MKEY async events during heavy invalidation and registration activity. Commit 69e8e429bca2 ("RDMA/irdma: Enforce local fence for LOCAL_INV WRs") was very similar, but was not sufficient to prevent all occurrences of these async events. Fixes: b48c24c2d710 ("RDMA/irdma: Implement device supported verb APIs") Signed-off-by: Jacob Moroni Link: https://patch.msgid.link/20260901160014.2026285-1-jmoroni@google.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/irdma/verbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index 9cfd84dd6869..5d71300da0ff 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -4281,7 +4281,7 @@ static int irdma_post_send(struct ib_qp *ibqp, stag_info.total_len = iwmr->ibmr.length; stag_info.reg_addr_pa = *palloc->level1.addr; stag_info.first_pm_pbl_index = palloc->level1.idx; - stag_info.local_fence = ib_wr->send_flags & IB_SEND_FENCE; + stag_info.local_fence = true; if (iwmr->npages > IRDMA_MIN_PAGES_PER_FMR) stag_info.chunk_size = 1; err = irdma_sc_mr_fast_register(&iwqp->sc_qp, &stag_info, From b94cec5761d22624d109d859467d7d4ce0a1b88b Mon Sep 17 00:00:00 2001 From: Andy Chiu Date: Tue, 1 Sep 2026 14:23:32 -0500 Subject: [PATCH 0173/1317] riscv: skip software algning code for HAVE_EFFICIENT_UNALIGNED_ACCESS We can jump straight into the copy loop if the kernel is compiled for a hardware that natively supports misaligned access. The user copy bandwidth improvement on K3 and Ascaolon is shown as below: Misaligned user copy, size: 512B (offset: [0:15] except 0, 8) BW Improvement | Write | Read | K3 | 6.19% | 3.43% | Ascalon | 10.0% | 11.4% | Aligned user copy, size: 512B (offset: 0, 8) BW Improvement | Write | Read | K3 | 1.69% | 0.90% | Ascalon | 1.25% | 3.32% | Suggested-by: Anton Blanchard Signed-off-by: Andy Chiu Link: https://patch.msgid.link/20260901192334.3543340-1-tchiu@tenstorrent.com Signed-off-by: Paul Walmsley --- arch/riscv/lib/uaccess.S | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/riscv/lib/uaccess.S b/arch/riscv/lib/uaccess.S index 4efea1b3326c..cf8586a937de 100644 --- a/arch/riscv/lib/uaccess.S +++ b/arch/riscv/lib/uaccess.S @@ -76,6 +76,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) li a3, 9*SZREG-1 /* size must >= (word_copy stride + SZREG-1) */ bltu a2, a3, .Lbyte_copy_tail +#if !defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) /* * Copy first bytes until dst is aligned to word boundary. * a0 - start of dst @@ -103,7 +104,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) /* a1 - start of src */ andi a3, a1, SZREG-1 bnez a3, .Lshift_copy - +#endif .Lword_copy: /* * Both src and dst are aligned, unrolled word copy @@ -137,6 +138,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) addi t0, t0, 8*SZREG /* revert to original value */ j .Lbyte_copy_tail +#if !defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) .Lshift_copy: /* @@ -189,6 +191,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) /* Revert src to original unaligned value */ add a1, a1, a3 +#endif .Lbyte_copy_tail: /* From e6142a8bfc230c7263eb8b0475249c958ce49367 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Wed, 2 Sep 2026 11:42:06 +0100 Subject: [PATCH 0174/1317] smb: client: reject short READ responses in CIFSSMBRead() CIFSSMBRead() reads DataLengthHigh, DataLength and DataOffset out of the READ_RSP returned by the server without first checking that a whole READ_RSP was actually received. The length of the response is recorded in rsp_iov.iov_len, but nothing constrains it to be at least read_rsp_size before those fields are dereferenced. A malicious or compromised SMB1 server can return a response shorter than the READ_RSP header, so that parsing the header itself reads past the end of the receive buffer. SMB1 is not negotiated by default; reaching this code requires an explicit vers=1.0 mount. Reject the response unless it is at least read_rsp_size bytes long. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Suggested-by: Paulo Alcantara Cc: stable@vger.kernel.org # 6.19.x Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index f8aa9e7b4bc6..be13ab37039d 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1719,6 +1719,14 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, pSMBr = (READ_RSP *)rsp_iov.iov_base; if (rc) { cifs_dbg(VFS, "Send error in read = %d\n", rc); + } else if (rsp_iov.iov_len < tcon->ses->server->vals->read_rsp_size) { + /* check that the received response can hold a whole READ_RSP */ + cifs_dbg(FYI, "%s: server returned short header. got=%zu expected=%zu\n", + __func__, rsp_iov.iov_len, + tcon->ses->server->vals->read_rsp_size); + rc = smb_EIO2(smb_eio_trace_read_rsp_short, + rsp_iov.iov_len, tcon->ses->server->vals->read_rsp_size); + *nbytes = 0; } else { int data_length = le16_to_cpu(pSMBr->DataLengthHigh); data_length = data_length << 16; From 5be5bdda5863eacc964b609ba927764f253431b3 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Wed, 2 Sep 2026 11:42:07 +0100 Subject: [PATCH 0175/1317] smb: client: reject out-of-bounds DataOffset in CIFSSMBRead() The SMB1 synchronous read helper CIFSSMBRead() validates the server's DataLength against CIFSMaxBufSize and the caller's count, but never validates DataOffset. The copy source is formed as &pSMBr->hdr.Protocol + le16_to_cpu(pSMBr->DataOffset) and memcpy()'d for DataLength bytes with no check that the [DataOffset, DataOffset + DataLength) range lies within the response actually received from the server. A malicious or compromised SMB1 server can return a response carrying an in-range DataLength and a large DataOffset, driving the source pointer past the end of the response buffer. The memcpy() then copies adjacent kernel heap into the caller's read buffer (information disclosure), or reads unmapped memory and oopses (denial of service). SMB1 is not negotiated by default; reaching this code requires an explicit vers=1.0 mount. Both DataOffset and the received response length recorded in rsp_iov.iov_len are relative to the start of the SMB header, so reject the response unless DataOffset + DataLength fits within that length, using overflow-safe arithmetic, before forming the source pointer. The response length has been validated by the previous patch, so the DataOffset and DataLength fields can be read safely here. While here, make data_length unsigned. It holds a length derived from unsigned on-the-wire fields and is only ever compared against unsigned quantities; print it with %u accordingly, and add __func__ to the cifs_dbg() calls in this function. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org # 6.19.x Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 18 +++++++++++++----- fs/smb/client/trace.h | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index be13ab37039d..f9aff0712794 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1728,7 +1728,8 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, rsp_iov.iov_len, tcon->ses->server->vals->read_rsp_size); *nbytes = 0; } else { - int data_length = le16_to_cpu(pSMBr->DataLengthHigh); + unsigned int data_length = le16_to_cpu(pSMBr->DataLengthHigh); + __u16 data_offset = le16_to_cpu(pSMBr->DataOffset); data_length = data_length << 16; data_length += le16_to_cpu(pSMBr->DataLength); *nbytes = data_length; @@ -1736,14 +1737,21 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, /*check that DataLength would not go beyond end of SMB */ if ((data_length > CIFSMaxBufSize) || (data_length > count)) { - cifs_dbg(FYI, "bad length %d for count %d\n", - data_length, count); + cifs_dbg(FYI, "%s: bad length %u for count %u\n", + __func__, data_length, count); rc = smb_EIO2(smb_eio_trace_read_overlarge, data_length, count); *nbytes = 0; + } else if (data_offset < sizeof(*pSMBr) || + (size_t)data_offset + data_length > rsp_iov.iov_len) { + /* check that the data lies within the received response */ + cifs_dbg(FYI, "%s: bad data offset %u length %u for response of %zu\n", + __func__, data_offset, data_length, rsp_iov.iov_len); + rc = smb_EIO2(smb_eio_trace_read_bad_offset, + data_offset, data_length); + *nbytes = 0; } else { - pReadData = (char *) (&pSMBr->hdr.Protocol) + - le16_to_cpu(pSMBr->DataOffset); + pReadData = (char *) (&pSMBr->hdr.Protocol) + data_offset; /* if (rc = copy_to_user(buf, pReadData, data_length)) { cifs_dbg(VFS, "Faulting on read rc = %d\n",rc); rc = -EFAULT; diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index 12241abb8e2e..b442cccd1530 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -79,6 +79,7 @@ EM(smb_eio_trace_qreparse_setup_count, "qreparse_setup_count") \ EM(smb_eio_trace_qreparse_sizes_wrong, "qreparse_sizes_wrong") \ EM(smb_eio_trace_qsym_bcc_too_small, "qsym_bcc_too_small") \ + EM(smb_eio_trace_read_bad_offset, "read_bad_offset") \ EM(smb_eio_trace_read_mid_state_unknown, "read_mid_state_unknown") \ EM(smb_eio_trace_read_overlarge, "read_overlarge") \ EM(smb_eio_trace_read_rsp_malformed, "read_rsp_malformed") \ From d9d7eeb0cea5b55b82888f443622fd8d4ee064f3 Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Wed, 2 Sep 2026 20:52:13 +0800 Subject: [PATCH 0176/1317] smb: client: reject userspace cifs.idmap descriptions cifs.idmap key descriptions carry authority-bearing fields (owner and group SIDs and uid/gid values in "os:"/"gs:"/"oi:"/"gi:" form) that the cifs.idmap upcall helper treats as kernel-originating inputs. Unlike its sibling cifs.spnego, the cifs.idmap key type has no vet_description hook, so userspace can create keys of this type through request_key(2)/add_key(2) and supply those fields without CIFS origin. A request_key(2) call with a non-NULL callout then drives a root usermodehelper upcall (/sbin/request-key -> cifs.idmap) that consumes the unvetted description in root context. Only accept cifs.idmap descriptions while CIFS is using its private root_cred to request the key. id_to_sid()/sid_to_id() already run under override_creds(root_cred), so the kernel-originated path is unaffected. This mirrors commit 3da1fdf4efbc ("smb: client: reject userspace cifs.spnego descriptions"), which applied the same restriction to cifs.spnego. Fixes: 4d79dba0e007 ("cifs: Add idmap key and related data structures and functions (try #17 repost)") Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Assisted-by: CodeBuddy:Kimi-K3 Signed-off-by: Aohan Mei Acked-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsacl.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 12005f46307d..213a421bf8e9 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -100,8 +100,23 @@ cifs_idmap_key_destroy(struct key *key) kfree(key->payload.data[0]); } +static int +cifs_idmap_key_vet_description(const char *description) +{ + /* + * cifs.idmap descriptions are authority-bearing inputs to the + * cifs.idmap upcall helper. Only allow the kernel to create this + * type of key using the private root_cred installed in + * init_cifs_idmap; reject userspace request_key(2)/add_key(2). + */ + if (current_cred() != root_cred) + return -EPERM; + return 0; +} + static struct key_type cifs_idmap_key_type = { .name = "cifs.idmap", + .vet_description = cifs_idmap_key_vet_description, .instantiate = cifs_idmap_key_instantiate, .destroy = cifs_idmap_key_destroy, .describe = user_describe, From d806d5a85dcbe2a0f181b2f0f9f61ddfbefa1818 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 2 Sep 2026 20:28:14 +0200 Subject: [PATCH 0177/1317] smb: client: pin DFS superblock in iterator callback tcon_super_cb() stores a raw superblock pointer, but __cifs_get_super() takes its active reference only after iterate_supers_type() has dropped s_umount and its passive reference. Concurrent DFS automount expiry can therefore free the superblock before cifs_sb_active() uses it. A deterministic KASAN test reproduces the race as: BUG: KASAN: slab-use-after-free in cifs_sb_active+0x77/0x80 The same test passes with this change applied. Take the active reference in the callback while iterate_supers_type() still holds s_umount shared. cifs_put_tcp_super() remains the matching release. Fixes: bacd704a95ad ("cifs: handle prefix paths in reconnect") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Paulo Alcantara --- fs/smb/client/misc.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index 46e1382e8e04..d4db3f91a91f 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -891,8 +891,14 @@ static void tcon_super_cb(struct super_block *sb, void *arg) t1->ses->dfs_root_ses == t2->ses->dfs_root_ses) && t1->ses->server == t2->ses->server && t2->origin_fullpath && - dfs_src_pathname_equal(t2->origin_fullpath, t1->origin_fullpath)) + dfs_src_pathname_equal(t2->origin_fullpath, t1->origin_fullpath)) { + /* + * Take the active reference while iterate_supers_type() still + * holds s_umount shared. + */ + cifs_sb_active(sb); sd->sb = sb; + } spin_unlock(&t2->tc_lock); } @@ -909,15 +915,8 @@ static struct super_block *__cifs_get_super(void (*f)(struct super_block *, void for (; *fs_type; fs_type++) { iterate_supers_type(*fs_type, f, &sd); - if (sd.sb) { - /* - * Grab an active reference in order to prevent automounts (DFS links) - * of expiring and then freeing up our cifs superblock pointer while - * we're doing failover. - */ - cifs_sb_active(sd.sb); + if (sd.sb) return sd.sb; - } } pr_warn_once("%s: could not find dfs superblock\n", __func__); return ERR_PTR(-EINVAL); From 74cb39735b6cd0aff4b5584158f09376fd97aadf Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 10 Aug 2026 15:10:34 -0700 Subject: [PATCH 0178/1317] ipvs: reject invalid states in connection template sync records IPVS sync receivers validate protocol states before creating or updating a connection. For connection templates, however, they only log states outside the template state range and still store the value in the connection. A template can be returned by ordinary connection lookup. TCP and SCTP then use the invalid state as an index into their transition tables. Reject invalid template states in both sync protocol versions before looking up or modifying a connection. The version 1 path handles both IPv4 and IPv6 records. Fixes: 275411430f89 ("ipvs: add assured state for conn templates") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Kyle Zeng Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_sync.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index ea5fdd4f4ce7..1deb063cd72c 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -999,10 +999,10 @@ static void ip_vs_process_message_v0(struct netns_ipvs *ipvs, const char *buffer pp->name, state); continue; } - } else { - if (state >= IP_VS_CTPL_S_LAST) - IP_VS_DBG(7, "BACKUP v0, Invalid tpl state %u\n", - state); + } else if (state >= IP_VS_CTPL_S_LAST) { + IP_VS_DBG(7, "BACKUP v0, Invalid tpl state %u\n", + state); + continue; } ip_vs_conn_fill_param(ipvs, AF_INET, s->protocol, @@ -1159,10 +1159,10 @@ static inline int ip_vs_proc_sync_conn(struct netns_ipvs *ipvs, __u8 *p, __u8 *m retc = 40; goto out; } - } else { - if (state >= IP_VS_CTPL_S_LAST) - IP_VS_DBG(7, "BACKUP, Invalid tpl state %u\n", - state); + } else if (state >= IP_VS_CTPL_S_LAST) { + IP_VS_DBG(7, "BACKUP, Invalid tpl state %u\n", state); + retc = 40; + goto out; } if (ip_vs_conn_fill_param_sync(ipvs, af, s, ¶m, pe_data, pe_data_len, pe_name, pe_name_len)) { From b04578b74f2d3755548fe9e829e3b2a6c6f966a1 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 10 Aug 2026 15:13:47 -0700 Subject: [PATCH 0179/1317] ipvs: fix reversed sequence option serialization hton_seq() expects the host-order source first and the unaligned network-order destination second. The version 1 sync sender passes these arguments in reverse for both sequence blocks. This leaves 24 bytes of the kmalloc-backed message unwritten. It may disclose stale heap data and replace the live connection sequence state with values read from the buffer. Pass the connection sequence state as the source and the message payload as the destination for both blocks. Fixes: 986a07579533 ("IPVS: Backup, Change sending to Version 1 format") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Kyle Zeng Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_sync.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index 1deb063cd72c..5383aeafb0ae 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -747,9 +747,9 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts) if (cp->flags & IP_VS_CONN_F_SEQ_MASK) { *(p++) = IPVS_OPT_SEQ_DATA; *(p++) = sizeof(struct ip_vs_sync_conn_options); - hton_seq((struct ip_vs_seq *)p, &cp->in_seq); + hton_seq(&cp->in_seq, (struct ip_vs_seq *)p); p += sizeof(struct ip_vs_seq); - hton_seq((struct ip_vs_seq *)p, &cp->out_seq); + hton_seq(&cp->out_seq, (struct ip_vs_seq *)p); p += sizeof(struct ip_vs_seq); } /* Handle pe data */ From e8f8231824b5815f57ce62cba116e511b10196de Mon Sep 17 00:00:00 2001 From: Joas Antonio dos Santos Date: Tue, 18 Aug 2026 06:31:43 -0700 Subject: [PATCH 0180/1317] netfilter: nf_conntrack_sip: fix OOB read in sip_skip_whitespace() sip_skip_whitespace() returns dptr unchanged when its own loop exhausts the buffer (dptr == limit), instead of NULL like its sibling sip_follow_continuation() returns on its own "no more data" path. ct_sip_get_header() only checks for NULL after calling it: dptr = sip_skip_whitespace(dptr, limit); if (dptr == NULL) break; if (*dptr != ':' || ++dptr >= limit) break; so a recognized header name followed only by spaces/tabs running to the exact end of the SIP payload, with no colon, makes the very next statement read one byte past the buffer. Make both "no more data" outcomes return NULL, matching the convention sip_follow_continuation() already uses and that both existing callers already check for. Fixes: ea45f12a2766d ("[NETFILTER]: nf_conntrack_sip: parse SIP headers properly") Signed-off-by: Joas Antonio dos Santos Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_sip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index 3ccf34fc1c53..64bc440b1181 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -423,7 +423,7 @@ static const char *sip_skip_whitespace(const char *dptr, const char *limit) dptr = sip_follow_continuation(dptr, limit); break; } - return dptr; + return dptr < limit ? dptr : NULL; } /* Search within a SIP header value, dealing with continuation lines */ From fec9b1de0d02de8dafa3cc344bcb91cf28660643 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 24 Aug 2026 20:12:38 +0800 Subject: [PATCH 0181/1317] netfilter: cttimeout: prevent UAF during module unload nf_ct_set_timeout() protects the timeout hook dereference and policy lookup with rcu_read_lock(). cttimeout_exit(), however, unregisters the per-net operations before it clears the hook. This allows the following interleaving: CPU 0 CPU 1 cttimeout_exit() nf_ct_set_timeout() unregister_pernet_subsys() rcu_read_lock() kfree(pernet) h = nf_ct_timeout_hook h->timeout_find_get() nfct_timeout_pernet() The hook still points to ctnl_timeout_find_get() when CPU 1 looks up the already freed per-net timeout list. KASAN reported: BUG: KASAN: slab-use-after-free in ctnl_timeout_find_get Read of size 8 by task poc/90 Call Trace: ctnl_timeout_find_get+0x271/0x2a0 [nfnetlink_cttimeout] nf_ct_set_timeout+0x7b/0x3c0 xt_ct_tg_check+0x724/0xb20 xt_check_target+0x234/0xa90 do_ipt_set_ctl+0x570/0x1270 Allocated by task 89: __kmalloc_noprof+0x16e/0x460 ops_init+0x6d/0x420 register_pernet_operations+0x2f6/0x670 Freed by task 91: kfree+0x131/0x390 ops_undo_list+0x3d4/0x730 unregister_pernet_operations+0x232/0x490 unregister_pernet_subsys+0x1c/0x30 cttimeout_exit+0x52/0x970 [nfnetlink_cttimeout] Clear the hook and wait for existing readers before unregistering the per-net operations. This blocks new policy lookups and ensures readers that observed the hook finish before the per-net storage is freed. Fixes: ebfbe67568a7 ("netfilter: cttimeout: use net_generic infra") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nfnetlink_cttimeout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nfnetlink_cttimeout.c b/net/netfilter/nfnetlink_cttimeout.c index 66c2016f6049..132c02ac7c4e 100644 --- a/net/netfilter/nfnetlink_cttimeout.c +++ b/net/netfilter/nfnetlink_cttimeout.c @@ -652,9 +652,9 @@ static void __exit cttimeout_exit(void) { nfnetlink_subsys_unregister(&cttimeout_subsys); - unregister_pernet_subsys(&cttimeout_ops); RCU_INIT_POINTER(nf_ct_timeout_hook, NULL); synchronize_net(); + unregister_pernet_subsys(&cttimeout_ops); } module_init(cttimeout_init); From 2c018cc4842c33f0c732962e2ab58635e8ae5823 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 24 Aug 2026 01:05:38 +0800 Subject: [PATCH 0182/1317] netfilter: nf_log: unregister loggers before per-net teardown nf_log_syslog and nfnetlink_log unregister their per-network namespace operations before unregistering their global logger backends. This leaves a window where a sysctl or netlink writer can rebind the still- registered logger after the per-net pre-exit callback cleared the old selection. The race looks like this: CPU 0 CPU 1 ---- ---- unregister_pernet_subsys() nf_log_unset(net, logger) net->nf.nf_loggers[pf] = NULL lock nf_log_mutex find logger in loggers[][] net->nf.nf_loggers[pf] = logger unlock nf_log_mutex nf_log_unregister(logger) lock nf_log_mutex loggers[pf][type] = NULL unlock nf_log_mutex synchronize_rcu() module exit returns module core frees backend memory Later, a sysctl read or packet logging operation can dereference the stale per-net logger pointer. Fix this by unregistering the global logger backends before tearing down per-net state. Once the global registrations are gone, later writers can no longer rebind the logger. unregister_pernet_subsys() already waits for an RCU grace period after the pre-exit callback clears the per-net selection, while nf_log_unregister() continues to cover readers of the global logger table. Apply this ordering fix to both nf_log backends that combine per-net teardown with global logger registration. Fixes: 5b023fc8d8e0 ("netfilter: enable per netns support for nf_loggers") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_log_syslog.c | 2 +- net/netfilter/nfnetlink_log.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_log_syslog.c b/net/netfilter/nf_log_syslog.c index f24288088c0d..c3fd398ffcd7 100644 --- a/net/netfilter/nf_log_syslog.c +++ b/net/netfilter/nf_log_syslog.c @@ -1073,12 +1073,12 @@ static int __init nf_log_syslog_init(void) static void __exit nf_log_syslog_exit(void) { - unregister_pernet_subsys(&nf_log_syslog_net_ops); nf_log_unregister(&nf_ip_logger); nf_log_unregister(&nf_arp_logger); nf_log_unregister(&nf_ip6_logger); nf_log_unregister(&nf_netdev_logger); nf_log_unregister(&nf_bridge_logger); + unregister_pernet_subsys(&nf_log_syslog_net_ops); } module_init(nf_log_syslog_init); diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c index 6c7fa2ed34f5..9d7fec570abe 100644 --- a/net/netfilter/nfnetlink_log.c +++ b/net/netfilter/nfnetlink_log.c @@ -1233,8 +1233,8 @@ static void __exit nfnetlink_log_fini(void) { nfnetlink_subsys_unregister(&nfulnl_subsys); netlink_unregister_notifier(&nfulnl_rtnl_notifier); - unregister_pernet_subsys(&nfnl_log_net_ops); nf_log_unregister(&nfulnl_logger); + unregister_pernet_subsys(&nfnl_log_net_ops); } MODULE_DESCRIPTION("netfilter userspace logging"); From 115bf3e51538e74159e9fa46199468b69fd5df70 Mon Sep 17 00:00:00 2001 From: Sun Jian Date: Tue, 1 Sep 2026 04:40:11 -0700 Subject: [PATCH 0183/1317] exec: Drop bprm loader before closing bprm->file free_bprm() currently drops what may be the final reference to bprm->file before calling bprm_drop_loader(). Since bprm_drop_loader() is attachable via BPF fentry and bprm->file is exposed as a BTF_TYPE_SAFE_TRUSTED pointer, the file can be observed after its reference has been released. Move bprm_drop_loader() before do_close_execat(bprm->file), keeping the file reference held while the hook runs. This preserves the existing trusted BTF contract without changing verifier behavior. The loader file and bprm->file have independent references, so this reordering does not change their required teardown ordering. Link: https://sashiko.dev/#/patchset/20260831092305.42062-1-tasos.papagiannnis@gmail.com?part=3 Signed-off-by: Sun Jian Link: https://patch.msgid.link/20260901114011.112375-1-sun.jian.kdev@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exec.c b/fs/exec.c index a14f28b15607..263b1f67f1f8 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1469,9 +1469,9 @@ static void free_bprm(struct linux_binprm *bprm) /* exec swapped the mm but failed before setup_new_exec() freed it */ if (bprm->old_mm) exec_mm_put_old(bprm->old_mm); - do_close_execat(bprm->file); /* An unconsumed PT_INTERP substitute from a binfmt_misc loader entry. */ bprm_drop_loader(bprm); + do_close_execat(bprm->file); do_close_execat(bprm->executable); /* If a binfmt changed the interp, free it. */ if (bprm->interp != bprm->filename) From 2f37fba846c9fdff5fc15b6d93656057ccd13031 Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Sat, 25 Jul 2026 15:51:54 +0200 Subject: [PATCH 0184/1317] mac802154: fix use-after-free of sdata via queued RX frames The RX softirq producer ieee802154_subif_frame() queues received beacon and MAC-command frames onto local->rx_beacon_list / rx_mac_cmd_list and schedules a process-context worker, storing a raw mac_pkt->sdata (and skb->dev == sdata->dev) with neither a reference nor any locking: - the lists have no lock: the softirq producer list_add_tail()s while the mac_wq worker list_del()s, so sibling interfaces on the same phy corrupt the list; - the workers dereference the interface after it may have been freed. mac802154_rx_mac_cmd_worker() touches mac_pkt->sdata directly, and mac802154_rx_beacon_worker() -> mac802154_process_beacon() dereferences skb->dev (== sdata->dev). Removing an interface frees its sdata (netdev_priv) while a queued frame still points at it, so a later worker run is a use-after-free. Reproduced under KASAN by flooding a victim interface with MAC command frames and removing it (the beacon path is the same class via skb->dev): BUG: KASAN: slab-use-after-free in mac802154_rx_mac_cmd_worker+0x463/0x630 [mac802154] Read of size 4 at addr ffff888002f9ea18 by task kworker/u8:1/31 Workqueue: phy0-mac-cmds mac802154_rx_mac_cmd_worker [mac802154] Call Trace: mac802154_rx_mac_cmd_worker+0x463/0x630 [mac802154] process_one_work+0x611/0xe80 worker_thread+0x52e/0xdc0 kthread+0x30c/0x630 ret_from_fork+0x2fd/0x3e0 Fix both lists together: - add local->rx_lock and take it around every list access: the softirq producer (plain spin_lock, softirq context) and the workers and flush (spin_lock_bh, process context); - pin the interface for the lifetime of a queued frame with netdev_hold()/netdev_put(), so the worker can safely dereference sdata / skb->dev even while the interface is being removed; - dequeue under the lock at the head and loop-drain the whole list in the workers (they previously processed one frame per run and relied on a later enqueue to drain the rest); - drop not-yet-started frames of an interface before it is unregistered, from ieee802154_if_remove() (after the RCU grace period) and from the ieee802154_remove_interfaces() loop -- the latter is the whole-phy teardown path, which does not go through ieee802154_if_remove(). An in-flight worker that already dequeued a frame keeps its own netdev reference; unregister_netdevice() then waits it out in netdev_run_todo(), which runs at rtnl_unlock() (rtnl released) and after the interface has been closed, so it does not pin rtnl. A worker blocked in an association TX only delays that one interface's unregister (the usual "waiting for %s to become free"), it does not hold rtnl. netdev_hold() is used for this reason instead of a cancel_work_sync() under rtnl, which would block on the worker's unbounded MLME TX wait via ieee802154_sync_queue(). The mac-command worker additionally skips processing for a stopped interface (ieee802154_sdata_running()), avoiding a needless association response during teardown. Fixes: 57588c71177f ("mac802154: Handle passive scanning") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Reviewed-by: Miquel Raynal Link: https://lore.kernel.org/20260725135154.99876-1-security@auditcode.ai Signed-off-by: Stefan Schmidt --- include/net/cfg802154.h | 1 + net/mac802154/ieee802154_i.h | 8 +++ net/mac802154/iface.c | 6 ++ net/mac802154/main.c | 1 + net/mac802154/rx.c | 120 ++++++++++++++++++++++++++++------- net/mac802154/scan.c | 10 +-- 6 files changed, 117 insertions(+), 29 deletions(-) diff --git a/include/net/cfg802154.h b/include/net/cfg802154.h index 76d2cd2e2b30..2e960441ea49 100644 --- a/include/net/cfg802154.h +++ b/include/net/cfg802154.h @@ -376,6 +376,7 @@ struct cfg802154_mac_pkt { struct list_head node; struct sk_buff *skb; struct ieee802154_sub_if_data *sdata; + netdevice_tracker dev_tracker; u8 page; u8 channel; }; diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h index c53aa293a222..992ce6698c20 100644 --- a/net/mac802154/ieee802154_i.h +++ b/net/mac802154/ieee802154_i.h @@ -74,6 +74,10 @@ struct ieee802154_local { struct work_struct rx_beacon_work; struct list_head rx_mac_cmd_list; struct work_struct rx_mac_cmd_work; + /* Serializes rx_beacon_list and rx_mac_cmd_list against the RX + * softirq producer, the mac_wq workers and the teardown flush. + */ + spinlock_t rx_lock; /* Association */ /* assoc_lock protects assoc_dev_extended_addr, assoc_addr, @@ -305,6 +309,10 @@ static inline bool mac802154_is_beaconing(struct ieee802154_local *local) } void mac802154_rx_mac_cmd_worker(struct work_struct *work); +void mac802154_flush_list(struct list_head *list, + struct ieee802154_sub_if_data *sdata); +void mac802154_flush_queued_pkts(struct ieee802154_local *local, + struct ieee802154_sub_if_data *sdata); int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, struct ieee802154_pan_device *coord, diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c index b823720630e7..31353795fa24 100644 --- a/net/mac802154/iface.c +++ b/net/mac802154/iface.c @@ -694,6 +694,7 @@ void ieee802154_if_remove(struct ieee802154_sub_if_data *sdata) mutex_unlock(&sdata->local->iflist_mtx); synchronize_rcu(); + mac802154_flush_queued_pkts(sdata->local, sdata); unregister_netdevice(sdata->dev); } @@ -705,6 +706,11 @@ void ieee802154_remove_interfaces(struct ieee802154_local *local) list_for_each_entry_safe(sdata, tmp, &local->interfaces, list) { list_del_rcu(&sdata->list); + /* Best-effort: a frame the RX softirq queues for this sdata + * after the flush still pins the netdev, so the + * unregister_netdevice() below waits it out. + */ + mac802154_flush_queued_pkts(local, sdata); unregister_netdevice(sdata->dev); } mutex_unlock(&local->iflist_mtx); diff --git a/net/mac802154/main.c b/net/mac802154/main.c index 63e89bd586e3..8ed6de111f5a 100644 --- a/net/mac802154/main.c +++ b/net/mac802154/main.c @@ -91,6 +91,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops) INIT_LIST_HEAD(&local->interfaces); INIT_LIST_HEAD(&local->rx_beacon_list); INIT_LIST_HEAD(&local->rx_mac_cmd_list); + spin_lock_init(&local->rx_lock); mutex_init(&local->iflist_mtx); tasklet_setup(&local->tasklet, ieee802154_tasklet_handler); diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c index cd8f2a11920d..19b5382e85a8 100644 --- a/net/mac802154/rx.c +++ b/net/mac802154/rx.c @@ -35,16 +35,23 @@ void mac802154_rx_beacon_worker(struct work_struct *work) container_of(work, struct ieee802154_local, rx_beacon_work); struct cfg802154_mac_pkt *mac_pkt; - mac_pkt = list_first_entry_or_null(&local->rx_beacon_list, - struct cfg802154_mac_pkt, node); - if (!mac_pkt) - return; + for (;;) { + spin_lock_bh(&local->rx_lock); + mac_pkt = list_first_entry_or_null(&local->rx_beacon_list, + struct cfg802154_mac_pkt, node); + if (mac_pkt) + list_del(&mac_pkt->node); + spin_unlock_bh(&local->rx_lock); + if (!mac_pkt) + break; - mac802154_process_beacon(local, mac_pkt->skb, mac_pkt->page, mac_pkt->channel); + mac802154_process_beacon(local, mac_pkt->skb, + mac_pkt->page, mac_pkt->channel); - list_del(&mac_pkt->node); - kfree_skb(mac_pkt->skb); - kfree(mac_pkt); + netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker); + kfree_skb(mac_pkt->skb); + kfree(mac_pkt); + } } static bool mac802154_should_answer_beacon_req(struct ieee802154_local *local) @@ -68,22 +75,15 @@ static bool mac802154_should_answer_beacon_req(struct ieee802154_local *local) return interval == IEEE802154_ACTIVE_SCAN_DURATION; } -void mac802154_rx_mac_cmd_worker(struct work_struct *work) +static void mac802154_rx_mac_cmd(struct ieee802154_local *local, + struct cfg802154_mac_pkt *mac_pkt) { - struct ieee802154_local *local = - container_of(work, struct ieee802154_local, rx_mac_cmd_work); - struct cfg802154_mac_pkt *mac_pkt; u8 mac_cmd; int rc; - mac_pkt = list_first_entry_or_null(&local->rx_mac_cmd_list, - struct cfg802154_mac_pkt, node); - if (!mac_pkt) - return; - rc = ieee802154_get_mac_cmd(mac_pkt->skb, &mac_cmd); if (rc) - goto out; + return; switch (mac_cmd) { case IEEE802154_CMD_BEACON_REQ: @@ -121,11 +121,81 @@ void mac802154_rx_mac_cmd_worker(struct work_struct *work) default: break; } +} -out: - list_del(&mac_pkt->node); - kfree_skb(mac_pkt->skb); - kfree(mac_pkt); +void mac802154_rx_mac_cmd_worker(struct work_struct *work) +{ + struct ieee802154_local *local = + container_of(work, struct ieee802154_local, rx_mac_cmd_work); + struct cfg802154_mac_pkt *mac_pkt; + + for (;;) { + spin_lock_bh(&local->rx_lock); + mac_pkt = list_first_entry_or_null(&local->rx_mac_cmd_list, + struct cfg802154_mac_pkt, node); + if (mac_pkt) + list_del(&mac_pkt->node); + spin_unlock_bh(&local->rx_lock); + if (!mac_pkt) + break; + + /* A stopped interface cannot transmit; skipping avoids a + * needless association response (and the !netif_running() + * warning it would trip) during teardown. The beacon worker + * needs no such check as it never transmits. + */ + if (ieee802154_sdata_running(mac_pkt->sdata)) + mac802154_rx_mac_cmd(local, mac_pkt); + + netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker); + kfree_skb(mac_pkt->skb); + kfree(mac_pkt); + } +} + +/** + * mac802154_flush_list - free queued RX frames on @list + * @list: rx_beacon_list or rx_mac_cmd_list + * @sdata: only free frames received on this interface, or %NULL for all + * + * Each frame pins the net_device it was received on (via netdev_hold()), + * so release that reference as the frame is dropped. Caller must hold + * local->rx_lock. + */ +void mac802154_flush_list(struct list_head *list, + struct ieee802154_sub_if_data *sdata) +{ + struct cfg802154_mac_pkt *mac_pkt, *tmp; + + list_for_each_entry_safe(mac_pkt, tmp, list, node) { + if (sdata && mac_pkt->sdata != sdata) + continue; + list_del(&mac_pkt->node); + netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker); + kfree_skb(mac_pkt->skb); + kfree(mac_pkt); + } +} + +/** + * mac802154_flush_queued_pkts - drop queued RX work referencing @sdata + * @local: the mac802154 device + * @sdata: interface being removed + * + * The workers dereference the queued frame's interface directly + * (mac_pkt->sdata) or through skb->dev in mac802154_process_beacon(). Drop + * the not-yet-started entries belonging to @sdata before it is unregistered + * so their netdev reference is released; an entry already dequeued by a + * running worker keeps its own reference until the worker completes, which + * unregister_netdevice() then waits out. + */ +void mac802154_flush_queued_pkts(struct ieee802154_local *local, + struct ieee802154_sub_if_data *sdata) +{ + spin_lock_bh(&local->rx_lock); + mac802154_flush_list(&local->rx_beacon_list, sdata); + mac802154_flush_list(&local->rx_mac_cmd_list, sdata); + spin_unlock_bh(&local->rx_lock); } static int @@ -221,7 +291,10 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata, mac_pkt->sdata = sdata; mac_pkt->page = sdata->local->scan_page; mac_pkt->channel = sdata->local->scan_channel; + netdev_hold(sdata->dev, &mac_pkt->dev_tracker, GFP_ATOMIC); + spin_lock(&sdata->local->rx_lock); list_add_tail(&mac_pkt->node, &sdata->local->rx_beacon_list); + spin_unlock(&sdata->local->rx_lock); queue_work(sdata->local->mac_wq, &sdata->local->rx_beacon_work); return NET_RX_SUCCESS; @@ -233,7 +306,10 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata, mac_pkt->skb = skb_get(skb); mac_pkt->sdata = sdata; + netdev_hold(sdata->dev, &mac_pkt->dev_tracker, GFP_ATOMIC); + spin_lock(&sdata->local->rx_lock); list_add_tail(&mac_pkt->node, &sdata->local->rx_mac_cmd_list); + spin_unlock(&sdata->local->rx_lock); queue_work(sdata->local->mac_wq, &sdata->local->rx_mac_cmd_work); return NET_RX_SUCCESS; diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c index dd156c01ac49..d393b1f4e74e 100644 --- a/net/mac802154/scan.c +++ b/net/mac802154/scan.c @@ -104,13 +104,9 @@ static unsigned int mac802154_scan_get_channel_time(u8 duration_order, static void mac802154_flush_queued_beacons(struct ieee802154_local *local) { - struct cfg802154_mac_pkt *mac_pkt, *tmp; - - list_for_each_entry_safe(mac_pkt, tmp, &local->rx_beacon_list, node) { - list_del(&mac_pkt->node); - kfree_skb(mac_pkt->skb); - kfree(mac_pkt); - } + spin_lock_bh(&local->rx_lock); + mac802154_flush_list(&local->rx_beacon_list, NULL); + spin_unlock_bh(&local->rx_lock); } static void From 2c1dde8a69a3a7d64425a6de0add6e289a5a402f Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Fri, 28 Aug 2026 11:08:11 +0530 Subject: [PATCH 0185/1317] powerpc/entry: Clear TIF_SYSCALL_RET before syscall error return Shivaprasad reported a boot failure due to userspace processes crash on abort() from libc.so.6. It was bisected to merge request commit '3424d8c18a7d ("Merge tag 'core-entry-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip")' Upon checking the merge, when syscall_enter_from_user_mode_randomize_stack fails, which could happen when a tracer like seccomp or ptrace intercepts and skips the syscall, the code returns to userspace immediately without clearing the intermediate flag which was set. When the next syscall is made, it immediately aborts the valid syscall since the flag is still set. Hence clear the flag on occurrence of first failure. Reported-by: Shivaprasad G Bhat Closes: https://lore.kernel.org/all/e301014d-568f-4ed5-bc64-b8a85ca0b1e1@linux.ibm.com/ Fixes: 3424d8c18a7d ("Merge tag 'core-entry-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip") Signed-off-by: Shrikanth Hegde Tested-by: Venkat Rao Bagalkote Tested-by: Shivaprasad G Bhat Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260828053811.1042300-1-sshegde@linux.ibm.com --- arch/powerpc/kernel/syscall.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/syscall.c b/arch/powerpc/kernel/syscall.c index 4916c205c4bb..fbefe1927b10 100644 --- a/arch/powerpc/kernel/syscall.c +++ b/arch/powerpc/kernel/syscall.c @@ -18,8 +18,10 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0) long ret; syscall_fn f; - if (unlikely(!syscall_enter_from_user_mode_randomize_stack(regs, &r0))) + if (unlikely(!syscall_enter_from_user_mode_randomize_stack(regs, &r0))) { + clear_thread_flag(TIF_SYSCALL_RET); return syscall_get_error(current, regs); + } if (unlikely(test_and_clear_thread_flag(TIF_SYSCALL_RET))) return syscall_get_error(current, regs); From c7585b8e99ad97a0f5dd21e45c90a33aeab0d92b Mon Sep 17 00:00:00 2001 From: "Ritesh Harjani (IBM)" Date: Sat, 29 Aug 2026 09:49:00 +0530 Subject: [PATCH 0186/1317] powerpc: Don't drop _TIF_RESTOREALL on syscall restart So the syscall return sequence is as follows: A syscall return to userspace is prepared and then a short asm sequence that actually does the RFI. Note that this asm range is restartable i.e. EE is still on, so an interrupt (e.g. decrementer or external interrupt) can hit while SRR/GPRs are being loaded. This is defined via: RESTART_TABLE(.Lsyscall_rst_start, .Lsyscall_rst_end, syscall_restart) This restart table then sends us to syscall_restart rather than resuming in the middle of the RFI. The same stub is also used if irq_happened already has a pending bit (soft-masked irq that has not been replayed yet (PowerPC special case of local_irq_disable())). Here is a bit of a flow of sequence of code to visualize: syscall_exit_prepare decide full-GPR restore (_TIF_RESTOREALL) for signal, rt_sigreturn or syscall trace save that in regs->exit_result and return it in r3 | v .Lsyscall_rst_start .. _end EE still on irq_happened set or interrupt in this range? | no | yes v v cmpdi r3,0 syscall_exit_restart restore all / zero replay irq, try exit again volatiles; RFI must return flags in r3 again for the same cmpdi Now r3 after prepare is the flags word, not the actual syscall return. A nested interrupt clobbers it, so the restart stub reloads RESULT into r3 and the C handler (syscall_exit_restart()) should put the flags back (because later asm checks whether r3 returned from C has _TIF_RESTOREALL set or not): cmpdi r3, 0 bne .Lsyscall_restore_regs Note that syscall_exit_restart() already ORs any new _TIF_RESTOREALL into exit_result, but then it only returns the new sample and not the full regs->exit_result. That sample could be often 0 even when restore-all is still required: - rt_sigreturn / syscall trace set the bit in prepare's local ret and in exit_result. They never set exit_flags, which is what restart samples. - a signal does set exit_flags but restart clears it. A second pass through the stub then returns 0 while exit_result still has the bit. The asm as mentioned earlier then treats r3==0 as the fast path and zeros r0/r4-r12. That means the userspace that needed the full register set could SIGSEGVs, (which could happen often in ld64.so.2 like while doing a parallel kernel build as reported by Venkat). So we should instead return the accumulated exit_result, like how we do in interrupt_exit_user_restart(). Note that prior to this commit 263e5159e00a ("powerpc: Fix exit_flags field placement in pt_regs for ptrace") we were returning regs->exit_result from syscall_exit_restart(), but this commit changed that behaviour. Fixes: 263e5159e00a ("powerpc: Fix exit_flags field placement in pt_regs for ptrace") Reported-by: Venkat Rao Bagalkote Closes: https://lore.kernel.org/all/75419f88-eab9-444b-bf97-28a9765819ad@linux.ibm.com/ Signed-off-by: Ritesh Harjani (IBM) Tested-by: Amit Machhiwal Tested-by: Shrikanth Hegde Tested-by: Venkat Rao Bagalkote Reviewed-by: Amit Machhiwal Reviewed-by: Shrikanth Hegde Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/10c86c909f870d90b3094f76b692b44ebe9caeac.1787976185.git.ritesh.list@gmail.com --- arch/powerpc/kernel/interrupt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/interrupt.c b/arch/powerpc/kernel/interrupt.c index 5b88bf72786c..55f9c0c9922a 100644 --- a/arch/powerpc/kernel/interrupt.c +++ b/arch/powerpc/kernel/interrupt.c @@ -175,7 +175,7 @@ notrace unsigned long syscall_exit_restart(unsigned long r3, struct pt_regs *reg current_thread_info()->exit_flags &= ~_TIF_RESTOREALL; regs->exit_result |= ret; - return ret; + return regs->exit_result; } #endif From c2549d749539487239475fbc8c614a1f9244d655 Mon Sep 17 00:00:00 2001 From: "Ritesh Harjani (IBM)" Date: Sun, 30 Aug 2026 20:24:30 +0530 Subject: [PATCH 0187/1317] powerpc: Do not restore KUAP in arch_exit_to_user_mode_prepare() KUAP means kernel cannot touch user memory unless it explicitly is enabled. In the kernel it should stay AMR_KUAP_BLOCKED. While returning to userspace just before RFI, kernel should restore the user AMR value back. Looks like GENERIC_ENTRY might be treating arch_exit_to_user_mode_prepare() as the last architecture step before returning to userspace. commit bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") therefore called kuap_user_restore() from that hook. But on PowerPC that is too early. After irqentry_exit() / syscall_exit_to_user_mode() we still run platform specific exit routines. e.g. code snippets showing both exception handling and system call handling as the callers of function arch_exit_to_user_mode_prepare() which does kuap_user_restore(). The below path shows that calling kuap_user_restore() is too early when called from arch_exit_to_user_mode_prepare(). Exception handling in exceptions-64s.S ======================================= bl CFUNC(do_page_fault) ..DEFINE_INTERRUPT_HANDLER_ASYNC(do_page_fault) arch_interrupt_async_enter_prepare(regs); state = irqentry_enter(regs); instrumentation_begin(); irq_enter_rcu(); handler(regs); nap_adjust_return(regs); irq_exit_rcu(); instrumentation_end(); arch_interrupt_async_exit_prepare(regs); irqentry_exit(regs, state); <<< too early irqentry_exit_to_user_mode() __exit_to_user_mode_prepare(regs, EXIT_TO_USER_MODE_WORK_IRQ); arch_exit_to_user_mode_prepare(regs, ti_work); <<< too early b interrupt_return_srr .. bl CFUNC(interrupt_exit_user_prepare) <<< already calls kuap_user_restore prep_irq_for_enabled_exit() retry can run kernel code with IRQs on. So only when that routine is fully finished is when the user KUAP should be fully restored which interrupt_exit_user_prepare() already takes care of before returning. Similarly for system call handling in interrupt_64.S ====================================================== bl CFUNC(system_call_exception) .Lsyscall_exit: addi r4,r1,STACK_INT_FRAME_REGS li r5,0 /* !scv */ bl CFUNC(syscall_exit_prepare) .. kuap_assert_locked(); syscall_exit_to_user_mode(regs); <<< too early syscall_exit_to_user_mode_prepare(regs); <<< too early kuap_user_restore(regs); <<< already calls syscall_exit_prepare(), which can enable IRQs, replay a pending interrupt, and only then rfi. Those functions already restore KUAP immediately before rfi. Note that if we restore the user AMR too early like in the current code as shown from the code snippets above, then we get the following warning when CONFIG_PPC_KUAP_DEBUG is enabled: WARNING: arch/powerpc/include/asm/book3s/64/kup.h:293 at interrupt_exit_user_prepare+0x1a0/0x1c0 Hardware name: IBM pSeries (emulated by qemu) POWER10 (architected) TRAP: 0700 LR: c00000000000d8d4 CTR: c0000000021fe500 MSR: CR: 44000804 XER: 20040000 interrupt_exit_user_prepare+0x1a0/0x1c0 interrupt_return_srr_user+0x8/0x12c Fixes: bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") Fixes: 02565a782c1ee ("powerpc: Introduce syscall exit arch functions") Signed-off-by: Ritesh Harjani (IBM) Tested-by: Venkat Rao Bagalkote Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/52fee44fd23acf8e1c024ace668728e626a783a8.1788101609.git.ritesh.list@gmail.com --- arch/powerpc/include/asm/entry-common.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/powerpc/include/asm/entry-common.h b/arch/powerpc/include/asm/entry-common.h index c5adb5006361..94083516df57 100644 --- a/arch/powerpc/include/asm/entry-common.h +++ b/arch/powerpc/include/asm/entry-common.h @@ -515,8 +515,14 @@ static inline void arch_exit_to_user_mode_prepare(struct pt_regs *regs, #ifdef CONFIG_PPC_TRANSACTIONAL_MEM local_paca->tm_scratch = regs->msr; #endif - /* Restore user access locks last */ - kuap_user_restore(regs); + /* + * Do not restore KUAP here. Generic entry might treat this as the last + * arch step before userspace but PowerPC still has kernel work after + * irqentry_exit()/syscall_exit_to_user_mode() i.e. in + * interrupt_exit_user_prepare() / syscall_exit_prepare() may enable + * IRQs and retry. Those functions restore KUAP immediately before rfi, + * which is where it should belong. + */ } #define arch_exit_to_user_mode_prepare arch_exit_to_user_mode_prepare From 2ae16aaa78b5edc6e6d0904c84fd9cdfb762bcda Mon Sep 17 00:00:00 2001 From: Quanye Yang Date: Sun, 30 Aug 2026 15:09:55 +0800 Subject: [PATCH 0188/1317] RDMA/rtrs-clt: Fix CQ pool leak when connect is interrupted The client borrows shared CQ credits in the ADDR_RESOLVED handler via ib_cq_pool_get(), before the peer is connected. create_cm() can return -ERESTARTSYS from wait_event_interruptible_timeout() without destroying the CM ID. The init_conns() and stop-and-destroy paths then call destroy_con_cq_qp() while cq is still NULL (no PUT) and only afterwards rdma_destroy_id(). CMA serializes the handler against rdma_destroy_id() with handler_mutex, but that does not order the GET against destroy_con_cq_qp(). If ADDR_RESOLVED has already passed the DESTROYING check, it can take con_mutex, GET credits, and then lose the con to kfree. Device unregister later hits WARN_ON(cq->cqe_used) in ib_cq_pool_cleanup(). Set a per-connection flag under con_mutex before CQ/QP teardown so a racing ADDR_RESOLVED cannot borrow credits after teardown has begun. Reported-by: syzbot+d396918a29afb8543e1c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d396918a29afb8543e1c Fixes: 3b89e92c2a95 ("RDMA/rtrs: Use new shared CQ mechanism") Signed-off-by: Quanye Yang Link: https://patch.msgid.link/20260830-rdma-rtrs-clt-cq-pool-leak-v1-1-b169434fd3df@proton.me Reviewed-by: Jack Wang Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/rtrs/rtrs-clt.c | 8 ++++++++ drivers/infiniband/ulp/rtrs/rtrs-clt.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt.c b/drivers/infiniband/ulp/rtrs/rtrs-clt.c index 7b2c51ae614f..eac38b57b00d 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt.c +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt.c @@ -1732,6 +1732,8 @@ static void destroy_con_cq_qp(struct rtrs_clt_con *con) /* * Be careful here: destroy_con_cq_qp() can be called even * create_con_cq_qp() failed, see comments there. + * Caller must set con->destroyed under this lock first so a + * racing ADDR_RESOLVED cannot ib_cq_pool_get() after we PUT/SKIP. */ lockdep_assert_held(&con->con_mutex); rtrs_cq_qp_destroy(&con->c); @@ -1766,6 +1768,10 @@ static int rtrs_rdma_addr_resolved(struct rtrs_clt_con *con) int err; mutex_lock(&con->con_mutex); + if (con->destroyed) { + mutex_unlock(&con->con_mutex); + return -ECONNABORTED; + } err = create_con_cq_qp(con); mutex_unlock(&con->con_mutex); if (err) { @@ -2221,6 +2227,7 @@ static void rtrs_clt_stop_and_destroy_conns(struct rtrs_clt_path *clt_path) break; con = to_clt_con(clt_path->s.con[cid]); mutex_lock(&con->con_mutex); + con->destroyed = true; destroy_con_cq_qp(con); mutex_unlock(&con->con_mutex); destroy_cm(con); @@ -2387,6 +2394,7 @@ static int init_conns(struct rtrs_clt_path *clt_path) if (con->c.cm_id) { stop_cm(con); mutex_lock(&con->con_mutex); + con->destroyed = true; destroy_con_cq_qp(con); mutex_unlock(&con->con_mutex); destroy_cm(con); diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt.h b/drivers/infiniband/ulp/rtrs/rtrs-clt.h index 1305601a6251..ad64f4517c4b 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt.h @@ -75,6 +75,8 @@ struct rtrs_clt_con { unsigned int cpu; struct mutex con_mutex; int cm_err; + /* Set under con_mutex before CQ/QP teardown. */ + bool destroyed; }; /** From bc69439d983cc491cc86e01fafc1deb94e1bb85e Mon Sep 17 00:00:00 2001 From: Yudi Yang <2000jedi@gmail.com> Date: Tue, 1 Sep 2026 14:55:11 -0500 Subject: [PATCH 0189/1317] drm/rockchip: analogix_dp: fix unchecked bound endpoint name length rockchip_dp_drm_encoder_enable() uses sprintf() to format a device tree path into a 32-byte stack buffer. Device tree paths are not limited to this size, so a sufficiently long path can overflow the buffer. Use snprintf() with the destination size to truncate the generated name and keep the writes within bounds. Fixes: 729f8eefdcad ("drm/rockchip: analogix_dp: Add support for RK3588") Cc: stable@vger.kernel.org Signed-off-by: Yudi Yang <2000jedi@gmail.com> Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260901195511.2761251-1-2000jedi@gmail.com --- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 587e60232ec7..efd5a98e80bd 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -241,10 +241,11 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder, of_graph_get_remote_port(endpoint.local_node); of_property_read_u32(remote_port, "reg", &port_id); - sprintf(name, "%s vp%d", remote_port_parent->full_name, port_id); + snprintf(name, sizeof(name), "%s vp%d", + remote_port_parent->full_name, port_id); } else { - sprintf(name, "%s %s", - remote_port_parent->full_name, endpoint.id ? "vopl" : "vopb"); + snprintf(name, sizeof(name), "%s %s", + remote_port_parent->full_name, endpoint.id ? "vopl" : "vopb"); } DRM_DEV_DEBUG(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG"); From 42d3358bf145f27d32350037e67d3527e9098c1e Mon Sep 17 00:00:00 2001 From: Fredric Cover Date: Wed, 2 Sep 2026 17:49:01 -0700 Subject: [PATCH 0190/1317] smb: client: fill cache fields after populating cache in copy_ref_data() In copy_ref_data(), struct cache_entry *ce has its fields populated at the beginning of the function. Later, if alloc_target fails with an ERR_PTR, free_tgts() is called on the cache, leaving the cache metadata populated without any targets. Critically, this extends ce->etime, making the cache appear valid for longer without any targets. Also, free_tgts() does not set ce->numtgts to zero. On error, when the cache is freed, ce->numtgts is not zeroed, and other cache users may attempt to access nonexistent entries. Update fields after copying targets to prevent partial-state updates. Set ce->numtgts to zero at the end of free_tgts(). Signed-off-by: Fredric Cover Signed-off-by: Paulo Alcantara --- fs/smb/client/dfs_cache.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 86dba25b7a5a..611e18fe8204 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -123,6 +123,7 @@ static inline void free_tgts(struct cache_entry *ce) kfree(t); } + ce->numtgts = 0; WRITE_ONCE(ce->tgthint, NULL); } @@ -388,13 +389,6 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, struct cache_dfs_tgt *target; int i; - ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL); - ce->etime = get_expire_time(ce->ttl); - ce->srvtype = refs[0].server_type; - ce->hdr_flags = refs[0].flags; - ce->ref_flags = refs[0].ref_flag; - ce->path_consumed = refs[0].path_consumed; - for (i = 0; i < numrefs; i++) { struct cache_dfs_tgt *t; @@ -409,12 +403,19 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, } else { list_add_tail(&t->list, &ce->tlist); } - ce->numtgts++; } target = list_first_entry_or_null(&ce->tlist, struct cache_dfs_tgt, list); + WRITE_ONCE(ce->tgthint, target); + ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL); + ce->etime = get_expire_time(ce->ttl); + ce->srvtype = refs[0].server_type; + ce->hdr_flags = refs[0].flags; + ce->ref_flags = refs[0].ref_flag; + ce->path_consumed = refs[0].path_consumed; + ce->numtgts = numrefs; return 0; } @@ -634,7 +635,6 @@ static int update_cache_entry_locked(struct cache_entry *ce, const struct dfs_in } free_tgts(ce); - ce->numtgts = 0; rc = copy_ref_data(refs, numrefs, ce, th); From 82e664cf1219c459c33aae931b222cf951af9cb7 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Thu, 3 Sep 2026 22:28:41 +0800 Subject: [PATCH 0191/1317] erofs: disable LZ4 rolling decompression for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LZ4 rolling decompression [1] was introduced to reduce the memory footprint of temporary pages: For many cases, it is needed for users to read small data within a compressed extent (pcluster), either due to random small read, or since uptodate folios (typically order-0) cannot be reused for decompression again since decompression algorithm refills already-uptodate folios. Rolling decompression works because LZ4 is LZ77-based and only refers to the most recent 64 KiB of decompressed data, so in theory only a bounded rolling window of temporary pages is needed when decompressing. It can save a lot of temporary memory, e.g. 601,960-byte data can be compressed into a 256k LZ4 compressed extent, which means it needs 146 extra pages per request in the worst case if rolling decompression is disabled. However, the upstream LZ4 implementation is not under EROFS' control: For example, the literal copy memmove() may still **copy long literals backward** on x86 based on the address comparison even when the source and destination ranges do not overlap (IOWs, inline decompression doesn't need to be considered here). That breaks the rolling assumption and makes the optimization broken. Disable it for now to make sure the data correctness first since EROFS is used everywhere now: The rolling window approach can be revived once we either ensure that the official LZ4 code always copies forward for non-overlapping ranges or maintain our own LZ4 implementation in EROFS. The main impact is a higher runtime memory footprint; However, recent commit 0f6273ab4637 ("erofs: add a reserved buffer pool for lz4 decompression") helps mitigate this when enabled but it's still not perfect. [1] https://www.usenix.org/conference/atc19/presentation/gao § 3.3 Decompression Reported-by: "Walther, Jens-Uwe" Closes: https://lore.kernel.org/r/BEZP281MB2102E57CD31862B8D958B33DD2AC2@BEZP281MB2102.DEUP281.PROD.OUTLOOK.COM Fixes: 8e6c8fa9f2e9 ("erofs: enable big pcluster feature") Cc: Yann Collet Signed-off-by: Gao Xiang --- fs/erofs/decompressor.c | 55 +++++++++-------------------------------- fs/erofs/internal.h | 6 +---- fs/erofs/zdata.c | 18 +++----------- 3 files changed, 16 insertions(+), 63 deletions(-) diff --git a/fs/erofs/decompressor.c b/fs/erofs/decompressor.c index 27caf4bebddc..d387b27c4ee2 100644 --- a/fs/erofs/decompressor.c +++ b/fs/erofs/decompressor.c @@ -7,8 +7,6 @@ #include "compress.h" #include -#define LZ4_MAX_DISTANCE_PAGES (DIV_ROUND_UP(LZ4_DISTANCE_MAX, PAGE_SIZE) + 1) - static int z_erofs_load_lz4_config(struct super_block *sb, struct erofs_super_block *dsb, void *data, int size) { @@ -21,8 +19,6 @@ static int z_erofs_load_lz4_config(struct super_block *sb, erofs_err(sb, "invalid lz4 cfgs, size=%u", size); return -EINVAL; } - distance = le16_to_cpu(lz4->max_distance); - sbi->lz4.max_pclusterblks = le16_to_cpu(lz4->max_pclusterblks); if (!sbi->lz4.max_pclusterblks) { sbi->lz4.max_pclusterblks = 1; /* reserved case */ @@ -39,45 +35,25 @@ static int z_erofs_load_lz4_config(struct super_block *sb, sbi->lz4.max_pclusterblks = 1; sbi->available_compr_algs = 1 << Z_EROFS_COMPRESSION_LZ4; } - - sbi->lz4.max_distance_pages = distance ? - DIV_ROUND_UP(distance, PAGE_SIZE) + 1 : - LZ4_MAX_DISTANCE_PAGES; return z_erofs_gbuf_growsize(sbi->lz4.max_pclusterblks); } /* - * Fill all gaps with bounce pages if it's a sparse page list. Also check if - * all physical pages are consecutive, which can be seen for moderate CR. + * Fill all gaps with bounce pages if it's a sparse page list (for example some + * folios are already uptodate and thus can be mapped into userspace). Also + * check if pages are physically consecutive, which can be seen for moderate CR. */ -static int z_erofs_lz4_prepare_dstpages(struct z_erofs_decompress_req *rq, - struct page **pagepool) +static int z_erofs_oneshot_prepare_dstpages(struct z_erofs_decompress_req *rq, + struct page **pagepool) { - struct page *availables[LZ4_MAX_DISTANCE_PAGES] = { NULL }; - unsigned long bounced[DIV_ROUND_UP(LZ4_MAX_DISTANCE_PAGES, - BITS_PER_LONG)] = { 0 }; - unsigned int lz4_max_distance_pages = - EROFS_SB(rq->sb)->lz4.max_distance_pages; void *kaddr = NULL; - unsigned int i, j, top; + unsigned int i; - top = 0; - for (i = j = 0; i < rq->outpages; ++i, ++j) { - struct page *const page = rq->out[i]; - struct page *victim; - - if (j >= lz4_max_distance_pages) - j = 0; - - /* 'valid' bounced can only be tested after a complete round */ - if (!rq->fillgaps && test_bit(j, bounced)) { - DBG_BUGON(i < lz4_max_distance_pages); - DBG_BUGON(top >= lz4_max_distance_pages); - availables[top++] = rq->out[i - lz4_max_distance_pages]; - } + for (i = 0; i < rq->outpages; ++i) { + struct page *page, *victim; + page = rq->out[i]; if (page) { - __clear_bit(j, bounced); if (!PageHighMem(page)) { if (!i) { kaddr = page_address(page); @@ -89,21 +65,14 @@ static int z_erofs_lz4_prepare_dstpages(struct z_erofs_decompress_req *rq, continue; } } - kaddr = NULL; - continue; - } - kaddr = NULL; - __set_bit(j, bounced); - - if (top) { - victim = availables[--top]; } else { victim = __erofs_allocpage(pagepool, rq->gfp, true); if (!victim) return -ENOMEM; set_page_private(victim, Z_EROFS_SHORTLIVED_PAGE); + rq->out[i] = victim; } - rq->out[i] = victim; + kaddr = NULL; } return kaddr ? 1 : 0; } @@ -266,7 +235,7 @@ static const char *z_erofs_lz4_decompress(struct z_erofs_decompress_req *rq, dst_maptype = 0; } else { /* general decoding path which can be used for all cases */ - ret = z_erofs_lz4_prepare_dstpages(rq, pagepool); + ret = z_erofs_oneshot_prepare_dstpages(rq, pagepool); if (ret < 0) return ERR_PTR(ret); if (ret > 0) { diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 65974e57aebf..12e3a5b80a5a 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -71,12 +71,8 @@ struct erofs_dev_context { bool flatdev; }; -/* all filesystem-wide lz4 configurations */ struct erofs_sb_lz4_info { - /* # of pages needed for EROFS lz4 rolling decompression */ - u16 max_distance_pages; - /* maximum possible blocks for pclusters in the filesystem */ - u16 max_pclusterblks; + u16 max_pclusterblks; /* maximum physical blocks for LZ4 pclusters */ }; struct erofs_xattr_prefix_item { diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c index e1e25ca0d190..6b07e73ee2aa 100644 --- a/fs/erofs/zdata.c +++ b/fs/erofs/zdata.c @@ -1259,7 +1259,7 @@ static int z_erofs_decompress_pcluster(struct z_erofs_backend *be, bool eio) const struct z_erofs_decompressor *alg = z_erofs_decomp[pcl->algorithmformat]; bool try_free = true; - int i, j, jtop, err2, err = eio ? -EIO : 0; + int i, err2, err = eio ? -EIO : 0; struct page *page; bool overlapped; const char *reason; @@ -1348,7 +1348,6 @@ static int z_erofs_decompress_pcluster(struct z_erofs_backend *be, bool eio) be->compressed_pages >= be->onstack_pages + Z_EROFS_ONSTACK_PAGES) kvfree(be->compressed_pages); - jtop = 0; z_erofs_fill_other_copies(be, err); for (i = 0; i < be->nr_pages; ++i) { page = be->decompressed_pages[i]; @@ -1356,22 +1355,11 @@ static int z_erofs_decompress_pcluster(struct z_erofs_backend *be, bool eio) continue; DBG_BUGON(z_erofs_page_is_invalidated(page)); - if (!z_erofs_is_shortlived_page(page)) { + if (!z_erofs_is_shortlived_page(page)) erofs_onlinefolio_end(page_folio(page), err, true); - continue; - } - if (pcl->algorithmformat != Z_EROFS_COMPRESSION_LZ4) { + else erofs_pagepool_add(be->pagepool, page); - continue; - } - for (j = 0; j < jtop && be->decompressed_pages[j] != page; ++j) - ; - if (j >= jtop) /* this bounce page is newly detected */ - be->decompressed_pages[jtop++] = page; } - while (jtop) - erofs_pagepool_add(be->pagepool, - be->decompressed_pages[--jtop]); if (be->decompressed_pages != be->onstack_pages) kvfree(be->decompressed_pages); From d1fc569fcbc7be6de06b034cf954a95e30ca1fb2 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Mon, 27 Jul 2026 01:26:15 -0700 Subject: [PATCH 0192/1317] dmaengine: mmp_pdma: fix wrong extended DRCMR base for SpacemiT K3 The extended DRCMR window on SpacemiT K3 starts at 0x1100. Commit 6587b8661a0b ("dmaengine: mmp_pdma: add SpacemiT K3 support") incorrectly set it to 0x1000, causing DRCMR accesses for request IDs >= 64 to target offsets 0x100 too low. The 0x1100 base has been verified on K3 silicon using real SPI and QSPI DMA transactions. The K3 DMA documentation [1] was updated on June 24, 2026, to reflect the corrected register addresses. Drop the bogus DRCMR_EXT_BASE_K3 macro and reuse DRCMR_EXT_BASE_DEFAULT for the K3 ops. Fixes: 6587b8661a0b ("dmaengine: mmp_pdma: add SpacemiT K3 support") Link: https://www.spacemit.com/community/document/info?nodepath=hardware/key_stone/k3/k3_docs/k3_usermanual/16_peripherals/dma.md&lang=en [1] Signed-off-by: Troy Mitchell Reviewed-by: Frank Li Link: https://patch.msgid.link/20260727-k3-pdma-fix-drcmr-base-v2-1-afba55cba1f3@linux.spacemit.com Signed-off-by: Vinod Koul --- drivers/dma/mmp_pdma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index 386e85cd4882..78e3e07e681d 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -52,7 +52,6 @@ #define DCSR_EORINTR BIT(9) /* The end of Receive */ #define DRCMR_BASE 0x0100 -#define DRCMR_EXT_BASE_K3 0x1000 #define DRCMR_EXT_BASE_DEFAULT 0x1100 #define DRCMR_REQ_LIMIT 64 #define DRCMR_MAPVLD BIT(7) /* Map Valid (read / write) */ @@ -1219,7 +1218,7 @@ static const struct mmp_pdma_ops spacemit_k3_pdma_ops = { .get_desc_dst_addr = get_desc_dst_addr_64, .run_bits = (DCSR_RUN | DCSR_LPAEEN | DCSR_EORIRQEN | DCSR_EORSTOPEN), .dma_width = 64, - .drcmr_ext_base = DRCMR_EXT_BASE_K3, + .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT, }; static const struct of_device_id mmp_pdma_dt_ids[] = { From 0294b6dd515256c03ea2dbf508ddd3826d788579 Mon Sep 17 00:00:00 2001 From: Alexander Chesnokov Date: Wed, 12 Aug 2026 08:34:26 +0300 Subject: [PATCH 0193/1317] dmaengine: ti: k3-udma-glue: fix NULL dereference in k3_udma_glue_release_rx_chn() If devm_kcalloc() for rx_chn->flows fails in a channel request function, the error path calls k3_udma_glue_release_rx_chn(), which dereferences the NULL rx_chn->flows pointer in k3_udma_glue_release_rx_flow(). Skip the flow release loop in k3_udma_glue_release_rx_chn() when rx_chn->flows is not allocated. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: d70241913413 ("dmaengine: ti: k3-udma: Add glue layer for non DMAengine users") Cc: stable@vger.kernel.org Reported-by: Pavel Zhigulin Signed-off-by: Alexander Chesnokov Reviewed-by: Frank Li Link: https://patch.msgid.link/20260812053426.3521589-1-Alexander.Chesnokov@kaspersky.com Signed-off-by: Vinod Koul --- drivers/dma/ti/k3-udma-glue.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/dma/ti/k3-udma-glue.c b/drivers/dma/ti/k3-udma-glue.c index 686dc140293e..70eaf7ee57e6 100644 --- a/drivers/dma/ti/k3-udma-glue.c +++ b/drivers/dma/ti/k3-udma-glue.c @@ -1243,8 +1243,9 @@ void k3_udma_glue_release_rx_chn(struct k3_udma_glue_rx_channel *rx_chn) rx_chn->psil_paired = false; } - for (i = 0; i < rx_chn->flow_num; i++) - k3_udma_glue_release_rx_flow(rx_chn, i); + if (rx_chn->flows) + for (i = 0; i < rx_chn->flow_num; i++) + k3_udma_glue_release_rx_flow(rx_chn, i); if (xudma_rflow_is_gp(rx_chn->common.udmax, rx_chn->flow_id_base)) xudma_free_gp_rflow_range(rx_chn->common.udmax, From a7df136ec529ee49a789c5029bc37b98b0d4bedd Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:31:49 +0800 Subject: [PATCH 0194/1317] dmaengine: sprd: Fix runtime PM reference leak in probe pm_runtime_get_sync() increments a device's usage counter even when it fails. sprd_dma_probe() currently jumps directly to controller clock cleanup on that error, bypassing both pm_runtime_put_noidle() and pm_runtime_disable(). This can happen if the preceding unchecked pm_runtime_set_active() fails and the following runtime-resume attempt also returns an error. Enter the existing runtime-PM unwind path instead. This drops the reference without idling the partially initialized device, disables runtime PM, and then releases the controller clocks. The success path and propagated error code are unchanged. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 9b3b8171f7f4 ("dmaengine: sprd: Add Spreadtrum DMA driver") Signed-off-by: Ruoyu Wang Reviewed-by: Frank Li Reviewed-by: Baolin Wang Link: https://patch.msgid.link/20260813153149.3953497-1-ruoyuw560@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/sprd-dma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/sprd-dma.c b/drivers/dma/sprd-dma.c index 087fea3af2e4..19b32a23c882 100644 --- a/drivers/dma/sprd-dma.c +++ b/drivers/dma/sprd-dma.c @@ -1212,7 +1212,7 @@ static int sprd_dma_probe(struct platform_device *pdev) ret = pm_runtime_get_sync(&pdev->dev); if (ret < 0) - goto err_rpm; + goto err_register; ret = dma_async_device_register(&sdev->dma_dev); if (ret < 0) { @@ -1234,7 +1234,6 @@ static int sprd_dma_probe(struct platform_device *pdev) err_register: pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); -err_rpm: sprd_dma_disable(sdev); return ret; } From c83e3e806d4c115f1dad9ef756db4cd91448a262 Mon Sep 17 00:00:00 2001 From: Igor Paunovic Date: Thu, 13 Aug 2026 16:40:18 +0200 Subject: [PATCH 0195/1317] drm/rockchip: dw_dp: Select DRM_BRIDGE_CONNECTOR dw_dp-rockchip.c calls drm_bridge_connector_init(), but ROCKCHIP_DW_DP does not select DRM_BRIDGE_CONNECTOR. A configuration with ROCKCHIP_DW_DP as the only enabled Rockchip output option fails to link: aarch64-linux-gnu-ld: drivers/gpu/drm/rockchip/dw_dp-rockchip.o: in function `dw_dp_rockchip_bind': dw_dp-rockchip.c:(.text+0x1d4): undefined reference to `drm_bridge_connector_init' Five other Rockchip encoder options that call drm_bridge_connector_init() (ROCKCHIP_ANALOGIX_DP, ROCKCHIP_CDN_DP, ROCKCHIP_DW_HDMI_QP, ROCKCHIP_LVDS, ROCKCHIP_RGB) already select it, which masks the gap in any configuration that enables one of them. ROCKCHIP_INNO_HDMI is covered through its DRM_INNO_HDMI core option. The same change was posted by Marius Dinu in March and dropped when the failure stopped reproducing in his build. The failure is configuration-dependent - any other enabled option that selects DRM_BRIDGE_CONNECTOR hides it - and it still reproduces on current drm-misc-next with the configuration described above. Select DRM_BRIDGE_CONNECTOR like the other users do. Fixes: d68ba7bac955 ("drm/rockchip: Add RK3588 DPTX output support") Link: https://lore.kernel.org/r/aneNCDU12OzG99UX@venus # ack to handle this apart from the dw-dp series Link: https://lore.kernel.org/r/20260319155051.1944-1-m95d+git@psihoexpert.ro # earlier submission by Marius Dinu Signed-off-by: Igor Paunovic Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260813144019.12089-2-royalnet026@gmail.com --- drivers/gpu/drm/rockchip/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index e7f49fe845ea..697c0748eeca 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -68,6 +68,7 @@ config ROCKCHIP_CDN_DP config ROCKCHIP_DW_DP bool "Rockchip specific extensions for Synopsys DW DP" + select DRM_BRIDGE_CONNECTOR help This selects support for Rockchip SoC specific extensions to enable Synopsys DesignWare Cores based DisplayPort transmit From d72aa5cf045a69d5fd433cde5dd4e113d9558fd1 Mon Sep 17 00:00:00 2001 From: Igor Paunovic Date: Thu, 13 Aug 2026 16:40:19 +0200 Subject: [PATCH 0196/1317] drm/rockchip: rk3066_hdmi: Add missing Kconfig selects rk3066_hdmi.c calls drm_bridge_connector_init(), but ROCKCHIP_RK3066_HDMI selects neither DRM_BRIDGE_CONNECTOR nor DRM_DISPLAY_HELPER, whose module carries the bridge-connector code. A configuration with ROCKCHIP_RK3066_HDMI as the only enabled Rockchip output option fails to link: aarch64-linux-gnu-ld: drivers/gpu/drm/rockchip/rk3066_hdmi.o: in function `rk3066_hdmi_bind': rk3066_hdmi.c:(.text+0x7a4): undefined reference to `drm_bridge_connector_init' aarch64-linux-gnu-ld: drivers/gpu/drm/rockchip/rk3066_hdmi.o: in function `rk3066_hdmi_bridge_atomic_enable': rk3066_hdmi.c:(.text+0xe74): undefined reference to `drm_atomic_helper_connector_hdmi_update_infoframes' Select both, like ROCKCHIP_CDN_DP, ROCKCHIP_LVDS and ROCKCHIP_RGB do. DRM_BRIDGE_CONNECTOR in turn selects DRM_DISPLAY_HDMI_STATE_HELPER, which resolves the second symbol. Fixes: 57d6811e8a6d ("drm/rockchip: rk3066_hdmi: switch to drm bridge") Signed-off-by: Igor Paunovic Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260813144019.12089-3-royalnet026@gmail.com --- drivers/gpu/drm/rockchip/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 697c0748eeca..4e58685f58ff 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -146,6 +146,8 @@ config ROCKCHIP_RGB config ROCKCHIP_RK3066_HDMI bool "Rockchip specific extensions for RK3066 HDMI" depends on DRM_ROCKCHIP + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR help This selects support for Rockchip SoC specific extensions for the RK3066 HDMI driver. If you want to enable From b3c8d4672f7a8735e2884cefcd89286268e88b2e Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:00 -0500 Subject: [PATCH 0197/1317] accel: ethosu: Fix ethosu_job_open() return value A WARN_ON() returns a 0 or 1, not the original negative errno. Just drop the WARN_ON() as the FD open will pass the return code to userspace and there's only one possible source of the error (drm_sched_entity_init()). Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-1-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 4ced44a65f23..6aa305b1dce3 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -374,12 +374,10 @@ int ethosu_job_open(struct ethosu_file_priv *ethosu_priv) { struct ethosu_device *dev = ethosu_priv->edev; struct drm_gpu_scheduler *sched = &dev->sched; - int ret; - ret = drm_sched_entity_init(ðosu_priv->sched_entity, - DRM_SCHED_PRIORITY_NORMAL, - &sched, 1, NULL); - return WARN_ON(ret); + return drm_sched_entity_init(ðosu_priv->sched_entity, + DRM_SCHED_PRIORITY_NORMAL, + &sched, 1, NULL); } void ethosu_job_close(struct ethosu_file_priv *ethosu_priv) From 2cbd3691565f7c86c0eaca305f5beb3435b4ba70 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:01 -0500 Subject: [PATCH 0198/1317] accel: ethosu: Drop IRQF_SHARED flag The IRQF_SHARED flag doesn't work with runtime-pm as the IRQ handler could run without resuming the device. This could also be fixed with runtime-pm calls in the IRQ handler, but there is no known need for a shared IRQ. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-2-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 6aa305b1dce3..4532ff13edb6 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -343,7 +343,7 @@ int ethosu_job_init(struct ethosu_device *edev) ret = devm_request_threaded_irq(dev, edev->irq, ethosu_job_irq_handler, ethosu_job_irq_handler_thread, - IRQF_SHARED, KBUILD_MODNAME, + 0, KBUILD_MODNAME, edev); if (ret) { dev_err(dev, "failed to request irq\n"); From eb3a41fd35e352fba387c4320a1fa0352f3e551c Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:02 -0500 Subject: [PATCH 0199/1317] accel: ethosu: Ensure cmd stream ends with a stop op While the QSIZE register setting should prevent an out of bounds access of the command stream, it is not clear whether the h/w generates an interrupt in this case as is required (to prevent a timeout). As a stop op is expected end of the command stream, let's just ensure it is present. A stop op in the middle of the command stream also makes no sense. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-3-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_device.h | 1 + drivers/accel/ethosu/ethosu_gem.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h index d4458eac8447..1eca8590e68d 100644 --- a/drivers/accel/ethosu/ethosu_device.h +++ b/drivers/accel/ethosu/ethosu_device.h @@ -87,6 +87,7 @@ struct gen_pool; #define PMU_EV_TYPE_IDLE 0x20 enum ethosu_cmds { + NPU_OP_STOP = 0x0, NPU_OP_CONV = 0x2, NPU_OP_DEPTHWISE = 0x3, NPU_OP_POOL = 0x5, diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c index fa37a190e9ff..9afe2549ec84 100644 --- a/drivers/accel/ethosu/ethosu_gem.c +++ b/drivers/accel/ethosu/ethosu_gem.c @@ -390,6 +390,7 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev, struct ethosu_validated_cmdstream_info __free(kfree) *info = kzalloc_obj(*info); struct ethosu_device *edev = to_ethosu_device(ddev); u32 *bocmds = bo->base.vaddr; + bool ends_with_stop = false; struct cmd_state st; int i, ret; @@ -426,6 +427,11 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev, } switch (cmd) { + case NPU_OP_STOP: + if (i != size / 4 - 1) + return -EINVAL; + ends_with_stop = true; + break; case NPU_OP_DMA_START: srclen = dma_length(info, &st.dma, &st.dma.src); dstlen = dma_length(info, &st.dma, &st.dma.dst); @@ -688,6 +694,9 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev, } } + if (!ends_with_stop) + return -EINVAL; + for (i = 0; i < NPU_BASEP_REGION_MAX; i++) { if (!info->region_size[i]) continue; From f5376d7e0fb703876199d3b6f9f97e128fa2f8a4 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:03 -0500 Subject: [PATCH 0200/1317] accel: ethosu: Ensure SRAM size is 0 on mapping failure On a mapping failure of the SRAM, the SRAM size is left as non-zero. The probe will succeed as the error return is not checked since having SRAM is not a hard requirement. The non-zero size allows jobs to access SRAM which is left pointing to physical base address 0x0. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-4-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_drv.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c index 1cf284e7f300..8108622de258 100644 --- a/drivers/accel/ethosu/ethosu_drv.c +++ b/drivers/accel/ethosu/ethosu_drv.c @@ -281,8 +281,6 @@ static int ethosu_device_suspend(struct device *dev) static int ethosu_sram_init(struct ethosu_device *ethosudev) { - ethosudev->npu_info.sram_size = 0; - ethosudev->srampool = of_gen_pool_get(ethosudev->base.dev->of_node, "sram", 0); if (!ethosudev->srampool) return 0; @@ -293,6 +291,7 @@ static int ethosu_sram_init(struct ethosu_device *ethosudev) ethosudev->npu_info.sram_size, ðosudev->sramphys); if (!ethosudev->sram) { + ethosudev->npu_info.sram_size = 0; dev_err(ethosudev->base.dev, "failed to allocate from SRAM pool\n"); return -ENOMEM; } From 2b39d680c9e0fb4d625f2916980977622e84248c Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:04 -0500 Subject: [PATCH 0201/1317] accel: ethosu: Ensure SRAM region size matches job It is possible for userspace to set the job SRAM size to 0, but then still have SRAM accesses in the command stream. When the job SRAM size is 0, setting the region base register is skipped and a stale base address from a prior job is used. Check the region size against the job's SRAM size instead of just the size of the SRAM. The job's SRAM size was already checked against the total SRAM size. Fixes: 9cff90774872 ("accel: ethosu: Validate SRAM size on submit") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-5-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 4532ff13edb6..8dce74db0cb4 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -447,13 +447,13 @@ static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file if (!cmd_info->region_size[i]) continue; if (i == ETHOSU_SRAM_REGION) { - if (cmd_info->region_size[i] <= edev->npu_info.sram_size) + if (cmd_info->region_size[i] <= ejob->sram_size) continue; dev_err(dev->dev, - "cmd stream region %d size greater than SRAM size (%llu > %u)\n", + "cmd stream region %d size greater than job SRAM size (%llu > %u)\n", i, cmd_info->region_size[i], - edev->npu_info.sram_size); + ejob->sram_size); ret = -EINVAL; goto out_cleanup_job; } From 0a85182723b65ad8bee8131bc38fcf0347d6679b Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 3 Sep 2026 14:06:26 +0800 Subject: [PATCH 0202/1317] sched_ext: Fix NULL sched deref in kfunc sub-sched error paths When the root scheduler has sub-scheds attached, the COMPAT kfunc wrappers scx_bpf_select_cpu_and() and scx_bpf_dsq_insert_vtime() refuse the call and report to @p's scheduler: scx_error(scx_task_sched(p), "... must be used"); The wrappers are reachable with tasks that have no scheduler. scx_bpf_select_cpu_and() is in the select_cpu kfunc group, which scx_kfunc_context_filter() opens to BPF_PROG_TYPE_SYSCALL programs; scx_bpf_dsq_insert_vtime() is in the enqueue_dispatch group, which ops.enqueue() and ops.dispatch() may call with any KF_RCU task -- the group has no kf_tasks validation, and scx_dsq_insert_preamble() checks task ownership with scx_task_on_sched() precisely because @p may be an arbitrary task. scx_task_sched(p) is p->scx.sched, which is NULL for tasks past sched_ext_dead() -- which clears it via scx_disable_and_exit_task() on exit -- and for idle tasks, which the enable paths skip as they are never scheduled through SCX. It is also an rcu_dereference_protected() that expects @p's pi_lock or rq lock, which neither wrapper holds. Passing NULL to scx_error() reaches scx_vexit(), which dereferences sch->exit_info, oopsing the kernel. One concrete trigger exercised while developing the fix: a BPF_PROG_TYPE_SYSCALL program calling the select_cpu_and wrapper on an exited-but-not-reaped task while a sub-scheduler was attached (its pid stays findable while the zombie is unreaped; faulting instruction is the scx_vexit() prologue "mov r15,[rdi+0x398]" with RDI=NULL and 0x398 the offset of sch->exit_info): sched_ext: BPF scheduler "kfunc_subsched_null" enabled sched_ext: BPF sub-scheduler "kfunc_subsched_null" enabled sched_ext: Unassociated program run_select_cpu_ (id 76) BUG: kernel NULL pointer dereference, address: 0000000000000398 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page Oops: Oops: 0000 [#1] SMP NOPTI CPU: 7 UID: 0 PID: 8201 Comm: kfunc_test_runn Tainted: G W RIP: 0010:scx_vexit+0x25/0xa0 Code: ... <4c> 8b bf 98 03 00 00 ... CR2: 0000000000000398 Call Trace: __scx_exit+0x4f/0x70 scx_bpf_select_cpu_and+0xab/0xb0 bpf_prog_430ed61a7b66e03a_run_select_cpu_and+0x9c/0xe7 ? __x64_sys_bpf+0x2c/0x40 bpf_prog_test_run_syscall+0x130/0x2f0 __sys_bpf+0x930/0x10d0 ? __x64_sys_bpf+0x2c/0x40 __x64_sys_bpf+0x2c/0x40 do_syscall_64+0xbc/0x460 entry_SYSCALL_64_after_hwframe+0x76/0x7e Read @p's scheduler under RCU instead, which the wrappers can do from their guard(rcu)(): fault it when it can be determined, and when it can't be determined -- @p is a task past sched_ext_dead() or an idle task -- there is nothing obviously wrong to report, so just refuse the call as before without faulting any scheduler. These COMPAT wrappers are scheduled for eventual removal once the deprecation grace period elapses, but until then -- and regardless of their removal timeline -- they must not oops the kernel on a task they are handed. Cc: stable@vger.kernel.org # v7.1+ Fixes: a5fa0708cbfd ("sched_ext: Enforce scheduling authority in dispatch and select_cpu operations") Suggested-by: Andrea Righi Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 11 +++++++++-- kernel/sched/ext/idle.c | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 5f242ab69cf4..7e414a7c53fc 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -8949,10 +8949,17 @@ __bpf_kfunc void scx_bpf_dsq_insert_vtime(struct task_struct *p, u64 dsq_id, #ifdef CONFIG_EXT_SUB_SCHED /* * Disallow if any sub-scheds are attached. There is no way to tell - * which scheduler called us, just error out @p's scheduler. + * which scheduler called us, so error out @p's scheduler -- read it + * under RCU as @p's locks aren't necessarily held here. @p may be a + * task past sched_ext_dead() or an idle task, in which case its + * scheduler can't be determined and there is nothing obviously wrong + * to report; just refuse the call. */ if (unlikely(!list_empty(&sch->children))) { - scx_error(scx_task_sched(p), "__scx_bpf_dsq_insert_vtime() must be used"); + struct scx_sched *tsch = scx_task_sched_rcu(p); + + if (tsch) + scx_error(tsch, "__scx_bpf_dsq_insert_vtime() must be used"); return; } #endif diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c index d2973fb3af6d..aa9fb6de0ad6 100644 --- a/kernel/sched/ext/idle.c +++ b/kernel/sched/ext/idle.c @@ -1142,10 +1142,17 @@ __bpf_kfunc s32 scx_bpf_select_cpu_and(struct task_struct *p, s32 prev_cpu, u64 #ifdef CONFIG_EXT_SUB_SCHED /* * Disallow if any sub-scheds are attached. There is no way to tell - * which scheduler called us, just error out @p's scheduler. + * which scheduler called us, so error out @p's scheduler -- read it + * under RCU as @p's locks aren't necessarily held here. @p may be a + * task past sched_ext_dead() or an idle task, in which case its + * scheduler can't be determined and there is nothing obviously wrong + * to report; just refuse the call. */ if (unlikely(!list_empty(&sch->children))) { - scx_error(scx_task_sched(p), "__scx_bpf_select_cpu_and() must be used"); + struct scx_sched *tsch = scx_task_sched_rcu(p); + + if (tsch) + scx_error(tsch, "__scx_bpf_select_cpu_and() must be used"); return -EINVAL; } #endif From 35af118326b22d840a3be271daa080728844cc4d Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:05 +0200 Subject: [PATCH 0203/1317] ALSA: hda: ext: Clean up links if their initialization fails snd_hdac_ext_bus_get_ml_capabilities() allocates the hlink nodes one-by-one but the procedure may fails due to -ENOMEM in the middle of it. Clean up the list before returning the error code to simply the function usage. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/hda/core/ext/controller.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/hda/core/ext/controller.c b/sound/hda/core/ext/controller.c index b1f1eff1d181..6d0af7e6f63c 100644 --- a/sound/hda/core/ext/controller.c +++ b/sound/hda/core/ext/controller.c @@ -90,8 +90,10 @@ int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus) for (idx = 0; idx < link_count; idx++) { hlink = kzalloc_obj(*hlink); - if (!hlink) + if (!hlink) { + snd_hdac_ext_link_free_all(bus); return -ENOMEM; + } hlink->index = idx; hlink->bus = bus; hlink->ml_addr = bus->mlcap + AZX_ML_BASE + From 48afc07c4f1b9c3cdbe66ddc3e7677ed395ce75f Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:06 +0200 Subject: [PATCH 0204/1317] ALSA: hda: ext: Clean up streams if their initialization fails snd_hdac_ext_stream_init_all() does not rollback changes done when the allocation fails. Fix that to simplify its usage. Signed-off-by: Cezary Rojewski Acked-by: Takashi Iwai Link: https://patch.msgid.link/20260902081814.1590883-3-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/hda/core/ext/stream.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/hda/core/ext/stream.c b/sound/hda/core/ext/stream.c index 517bd151fcc3..1f96e0484660 100644 --- a/sound/hda/core/ext/stream.c +++ b/sound/hda/core/ext/stream.c @@ -102,8 +102,10 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, for (i = 0; i < num_stream; i++) { struct hdac_ext_stream *hext_stream = kzalloc_obj(*hext_stream); - if (!hext_stream) + if (!hext_stream) { + snd_hdac_ext_stream_free_all(bus); return -ENOMEM; + } tag = ++stream_tag; snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag); idx++; @@ -111,7 +113,6 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, } return 0; - } EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); From ce4d7356660ab7f8fd7c3119c16fef5c9b9adec9 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:07 +0200 Subject: [PATCH 0205/1317] ASoC: Intel: avs: Clean up the bus when its initialization fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_hdac_i915_init() which is part of the initialization may return -EPROBE_DEFER what fails the procedure and the existing avs_bus_init() and avs_pci_probe() do not clean up the bus fields with snd_hdac_ext_bus_exit() when that happens. Fix avs_bus_init() by rearranging the initialization blocks: allocations first, snd_hdac_ext_bus_init() last. Such approach generates no error-path whilst still achieving the goal of cleaning up the bus. For avs_pci_probe() update the existing error-path instead. Co-developed-by: Amadeusz Sławiński Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-4-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/core.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c index 2afe59646896..f45256ff5bac 100644 --- a/sound/soc/intel/avs/core.c +++ b/sound/soc/intel/avs/core.c @@ -383,6 +383,18 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct struct device *dev = &pci->dev; int ret; + ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return -ENOMEM; + + adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!adev->modcfg_buf) + return -ENOMEM; + + ret = avs_ipc_init(ipc, dev); + if (ret < 0) + return ret; + ret = snd_hdac_ext_bus_init(&bus->core, dev, NULL, &soc_hda_ext_bus_ops); if (ret < 0) return ret; @@ -394,17 +406,6 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct bus->mixer_assigned = -1; mutex_init(&bus->prepare_mutex); - ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); - if (!ipc) - return -ENOMEM; - ret = avs_ipc_init(ipc, dev); - if (ret < 0) - return ret; - - adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); - if (!adev->modcfg_buf) - return -ENOMEM; - adev->dev = dev; adev->spec = (const struct avs_spec *)id->driver_data; adev->ipc = ipc; @@ -456,13 +457,14 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) ret = pcim_request_all_regions(pci, "AVS HDAudio"); if (ret < 0) - return ret; + goto err_request_regions; bus->addr = pci_resource_start(pci, 0); bus->remap_addr = pci_ioremap_bar(pci, 0); if (!bus->remap_addr) { dev_err(bus->dev, "ioremap error\n"); - return -ENXIO; + ret = -ENXIO; + goto err_request_regions; } adev->dsp_ba = pci_ioremap_bar(pci, 4); @@ -519,6 +521,8 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) iounmap(adev->dsp_ba); err_remap_bar4: iounmap(bus->remap_addr); +err_request_regions: + snd_hdac_ext_bus_exit(bus); return ret; } From 559ea14b7ae7c7562b48759fa545b64958f35b73 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:08 +0200 Subject: [PATCH 0206/1317] ASoC: Intel: avs: Clean up the bus when fetching ML caps fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_hdac_ext_bus_get_ml_capabilities() may fail and its return code shall be checked and accounted for. Address the issue by updating the error-path for avs_pci_probe(). At the same time, if the function in question succeeds but the next part of avs_pci_probe() fails, the hlink list shall be cleaned up before leaving the scope. Fixes: 1affc44ea5dd ("ASoC: Intel: avs: PCI driver implementation") Co-developed-by: Amadeusz Sławiński Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-5-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/core.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c index f45256ff5bac..8f27a7d43718 100644 --- a/sound/soc/intel/avs/core.c +++ b/sound/soc/intel/avs/core.c @@ -475,8 +475,13 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) } snd_hdac_bus_parse_capabilities(bus); - if (bus->mlcap) - snd_hdac_ext_bus_get_ml_capabilities(bus); + if (bus->mlcap) { + ret = snd_hdac_ext_bus_get_ml_capabilities(bus); + if (ret < 0) { + dev_err(dev, "failed to get ml capabilities: %d\n", ret); + goto err_ml_cap; + } + } if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); @@ -518,6 +523,8 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) snd_hdac_bus_free_stream_pages(bus); snd_hdac_ext_stream_free_all(bus); err_init_streams: + snd_hdac_ext_link_free_all(bus); +err_ml_cap: iounmap(adev->dsp_ba); err_remap_bar4: iounmap(bus->remap_addr); From f4ba00bb56a8511bafbd19826501d27157141c6d Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:09 +0200 Subject: [PATCH 0207/1317] ASoC: Intel: avs: Clean up streams if their initialization fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When streams are being initialized the memory allocation may fail. Have an error path and return early if that is the case. Fixes: 1affc44ea5dd ("ASoC: Intel: avs: PCI driver implementation") Co-developed-by: Amadeusz Sławiński Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-6-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/core.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c index 8f27a7d43718..611ae9f034d4 100644 --- a/sound/soc/intel/avs/core.c +++ b/sound/soc/intel/avs/core.c @@ -92,16 +92,28 @@ static int avs_hdac_bus_init_streams(struct hdac_bus *bus) { unsigned int cp_streams, pb_streams; unsigned int gcap; + int ret; gcap = snd_hdac_chip_readw(bus, GCAP); cp_streams = (gcap >> 8) & 0x0F; pb_streams = (gcap >> 12) & 0x0F; bus->num_streams = cp_streams + pb_streams; - snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); - snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); + ret = snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + ret = snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto err; - return snd_hdac_bus_alloc_stream_pages(bus); + ret = snd_hdac_bus_alloc_stream_pages(bus); + if (ret) + goto err; + + return 0; +err: + snd_hdac_ext_stream_free_all(bus); + return ret; } static bool avs_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) From c6dceca9f78fbd478c41735457c4de9c25a6b1c8 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:10 +0200 Subject: [PATCH 0208/1317] ASoC: Intel: avs: Do not ignore -ENOENT when loading a topology avs_load_topology() combines request_firmware() and snd_soc_tplg_component_load(). The fallback mechanism introduced for the HDAudio based boards honors -ENOENT and checks for a generic topology if no specific is found before giving up and failing the component probing. However, if -ENOENT is returned by the latter function - snd_soc_tplg_component_load() - is shall not be ignored. That means there is an actual problem with the topology file and no fallback shall be attempted. Fixes: 739c031110da ("ASoC: Intel: avs: Provide support for fallback topology") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-7-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/pcm.c | 34 ++++++++++++++++++++-------------- sound/soc/intel/avs/topology.c | 2 +- sound/soc/intel/avs/topology.h | 1 + 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index 2b886fae8209..ad25bd355769 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -6,6 +6,7 @@ // Amadeusz Slawinski // +#include #include #include #include @@ -987,13 +988,25 @@ static int avs_component_load_libraries(struct avs_soc_component *acomp) return ret; } +static int avs_request_topology(struct snd_soc_component *component, const char *name, + const struct firmware **fw) +{ + char *fullname __free(kfree) = NULL; + + fullname = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, name); + if (!fullname) + return -ENOMEM; + + return request_firmware(fw, fullname, component->dev); +} + static int avs_component_probe(struct snd_soc_component *component) { struct snd_soc_card *card = component->card; struct snd_soc_acpi_mach *mach; struct avs_soc_component *acomp; + const struct firmware *fw; struct avs_dev *adev; - char *filename; int ret; dev_dbg(card->dev, "probing %s card %s\n", component->name, card->name); @@ -1009,13 +1022,7 @@ static int avs_component_probe(struct snd_soc_component *component) goto finalize; /* Load specified topology and create debugfs for it. */ - filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, - mach->tplg_filename); - if (!filename) - return -ENOMEM; - - ret = avs_load_topology(component, filename); - kfree(filename); + ret = avs_request_topology(component, mach->tplg_filename, &fw); if (ret == -ENOENT && !strncmp(mach->tplg_filename, "hda-", 4)) { unsigned int vendor_id; @@ -1030,18 +1037,17 @@ static int avs_component_probe(struct snd_soc_component *component) "hda-generic-tplg.bin"); if (!mach->tplg_filename) return -ENOMEM; - filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, - mach->tplg_filename); - if (!filename) - return -ENOMEM; dev_info(card->dev, "trying to load fallback topology %s\n", mach->tplg_filename); - ret = avs_load_topology(component, filename); - kfree(filename); + ret = avs_request_topology(component, mach->tplg_filename, &fw); } if (ret < 0) return ret; + ret = snd_soc_tplg_component_load(component, &avs_tplg_ops, fw); + if (ret) + return ret; + ret = avs_component_load_libraries(acomp); if (ret < 0) { dev_err(card->dev, "libraries loading failed: %d\n", ret); diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index 673ac31f2fea..d5e641c73faf 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -2194,7 +2194,7 @@ avs_control_load(struct snd_soc_component *comp, int index, struct snd_kcontrol_ return 0; } -static const struct snd_soc_tplg_ops avs_tplg_ops = { +const struct snd_soc_tplg_ops avs_tplg_ops = { .io_ops = avs_control_ops, .io_ops_count = ARRAY_SIZE(avs_control_ops), .control_load = avs_control_load, diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h index 1cf7455b6c01..b5799c994b88 100644 --- a/sound/soc/intel/avs/topology.h +++ b/sound/soc/intel/avs/topology.h @@ -230,6 +230,7 @@ struct avs_tplg_module { struct list_head node; }; +extern const struct snd_soc_tplg_ops avs_tplg_ops; struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp); int avs_load_topology(struct snd_soc_component *comp, const char *filename); From 363a6969f7f875b461236bf520d35f35c3b0e5b1 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:11 +0200 Subject: [PATCH 0209/1317] ASoC: Intel: avs: Cancel d0ix_work asynchrounously during recovery Tests with corrupted firmware binaries prove that the recovery procedure can hit deadlock with d0ix_work if the work has been scheduled shortly before the event that triggered the recovery e.g.: timeouts on communication with a dead AudioDSP firmware. At the same time, the ready-check shall be done after acquiring the msg_mutex as the flag might have been modified by the time the lock is granted. The recovery case is one of such examples. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-8-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/ipc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c index 39b0de9831da..5010b0f5be47 100644 --- a/sound/soc/intel/avs/ipc.c +++ b/sound/soc/intel/avs/ipc.c @@ -172,7 +172,7 @@ static void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg /* Avoid deadlock as the exception may be the response to SET_D0IX. */ if (current_work() != &ipc->d0ix_work.work) - cancel_delayed_work_sync(&ipc->d0ix_work); + cancel_delayed_work(&ipc->d0ix_work); ipc->in_d0ix = false; /* Re-enabled on recovery completion. */ pm_runtime_disable(adev->dev); @@ -395,11 +395,11 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request struct avs_ipc *ipc = adev->ipc; int ret; + guard(mutex)(&ipc->msg_mutex); + if (!ipc->ready) return -EPERM; - guard(mutex)(&ipc->msg_mutex); - spin_lock(&ipc->rx_lock); avs_ipc_msg_init(ipc, reply); avs_dsp_send_tx(adev, request, true); From d4fa6f94b91137e329ea3f5b360e140b227bb696 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:12 +0200 Subject: [PATCH 0210/1317] ASoC: Intel: avs: Fix unbalanced module reference count strace_open() invokes try_module_get() which on success takes the module reference. If any follow up operation causes strace_open() to fail, the refcount shall be put down. Fixes: 0a5fb3cc28fd ("ASoC: Intel: avs: Keep module refcount up when gathering traces") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-9-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/debugfs.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c index 9ab503da3b75..bc02737720ab 100644 --- a/sound/soc/intel/avs/debugfs.c +++ b/sound/soc/intel/avs/debugfs.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -236,15 +237,20 @@ static int strace_open(struct inode *inode, struct file *file) if (!try_module_get(adev->dev->driver->owner)) return -ENODEV; - if (kfifo_initialized(&adev->trace_fifo)) - return -EBUSY; + if (kfifo_initialized(&adev->trace_fifo)) { + ret = -EBUSY; + goto err; + } ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); if (ret < 0) - return ret; + goto err; file->private_data = adev; return 0; +err: + module_put(adev->dev->driver->owner); + return ret; } static int strace_release(struct inode *inode, struct file *file) From 681e91035dc794896a904852040837190e5041f5 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:13 +0200 Subject: [PATCH 0211/1317] ASoC: Intel: avs: Refactor and fix init_config access Existing code accesses enties found in ->init_configs array through indexes that are part of ->config_ids array. Those two are limited by: ->num_init_configs and ->num_config_ids respectively. Using ID larger or equal to ->num_init_configs leads to out-of-bounds access: avs_path_module_send_init_configs() loop: (...) &acomp->tplg->init_configs[ids[i]] ^ out-of-bounds candidate Rather than adding another if-statement, refactor the code. There is no need to store the IDs, have a list of pointers to actual config-entries instead. As the verification of ->init_config entries does not differ from verification of other types that are part of the topology.c file, simply reuse the code. Fixes: 8a49ef789b1b ("ASoC: Intel: avs: Send initial config to module if present") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-10-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/path.c | 11 +++----- sound/soc/intel/avs/topology.c | 47 +++++++++++++++++++--------------- sound/soc/intel/avs/topology.h | 4 +-- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c index 213d6ecdd7cc..a8a2b3484338 100644 --- a/sound/soc/intel/avs/path.c +++ b/sound/soc/intel/avs/path.c @@ -836,15 +836,10 @@ static int avs_path_module_type_create(struct avs_dev *adev, struct avs_path_mod static int avs_path_module_send_init_configs(struct avs_dev *adev, struct avs_path_module *mod) { - struct avs_soc_component *acomp; + struct avs_tplg_module *template = mod->template; - acomp = to_avs_soc_component(mod->template->owner->owner->owner->owner->comp); - - u32 num_ids = mod->template->num_config_ids; - u32 *ids = mod->template->config_ids; - - for (int i = 0; i < num_ids; i++) { - struct avs_tplg_init_config *config = &acomp->tplg->init_configs[ids[i]]; + for (int i = 0; i < template->num_init_configs; i++) { + struct avs_tplg_init_config *config = template->init_configs[i]; size_t len = config->length; void *data = config->data; u32 param = config->param; diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index d5e641c73faf..5d70be63a4a7 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -350,6 +350,7 @@ AVS_DEFINE_PTR_PARSER(modcfg_base, struct avs_tplg_modcfg_base, modcfgs_base); AVS_DEFINE_PTR_PARSER(modcfg_ext, struct avs_tplg_modcfg_ext, modcfgs_ext); AVS_DEFINE_PTR_PARSER(pplcfg, struct avs_tplg_pplcfg, pplcfgs); AVS_DEFINE_PTR_PARSER(binding, struct avs_tplg_binding, bindings); +AVS_DEFINE_PTR_PARSER(init_config, struct avs_tplg_init_config, init_configs); AVS_DEFINE_PTR_PARSER(nhlt_config, struct avs_tplg_nhlt_config, nhlt_configs); static int @@ -1198,7 +1199,7 @@ static const struct avs_tplg_token_parser module_parsers[] = { { .token = AVS_TKN_MOD_INIT_CONFIG_NUM_IDS_U32, .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, - .offset = offsetof(struct avs_tplg_module, num_config_ids), + .offset = offsetof(struct avs_tplg_module, num_init_configs), .parse = avs_parse_byte_token, }, { @@ -1214,10 +1215,32 @@ static const struct avs_tplg_token_parser init_config_parsers[] = { .token = AVS_TKN_MOD_INIT_CONFIG_ID_U32, .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, .offset = 0, - .parse = avs_parse_word_token, + .parse = avs_parse_init_config_ptr, }, }; +static int avs_tplg_module_init_configs(struct snd_soc_component *comp, + struct avs_tplg_module *module, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_tplg_init_config **cfgs; + int ret; + + if (!module->num_init_configs) + return -EINVAL; + + cfgs = devm_kcalloc(comp->card->dev, module->num_init_configs, sizeof(*cfgs), GFP_KERNEL); + if (!cfgs) + return -ENOMEM; + + ret = parse_dictionary_entries(comp, tuples, block_size, cfgs, module->num_init_configs, + sizeof(*cfgs), AVS_TKN_MOD_INIT_CONFIG_ID_U32, + init_config_parsers, ARRAY_SIZE(init_config_parsers)); + if (!ret) + module->init_configs = cfgs; + return ret; +} + static struct avs_tplg_module * avs_tplg_module_create(struct snd_soc_component *comp, struct avs_tplg_pipeline *owner, struct snd_soc_tplg_vendor_array *tuples, u32 block_size) @@ -1244,27 +1267,11 @@ avs_tplg_module_create(struct snd_soc_component *comp, struct avs_tplg_pipeline block_size -= esize; /* Parse trailing config ids if any. */ if (block_size) { - u32 num_config_ids = module->num_config_ids; - u32 *config_ids; - - if (!num_config_ids) - return ERR_PTR(-EINVAL); - - config_ids = devm_kcalloc(comp->card->dev, num_config_ids, sizeof(*config_ids), - GFP_KERNEL); - if (!config_ids) - return ERR_PTR(-ENOMEM); - tuples = avs_tplg_vendor_array_at(tuples, esize); - ret = parse_dictionary_entries(comp, tuples, block_size, - config_ids, num_config_ids, sizeof(*config_ids), - AVS_TKN_MOD_INIT_CONFIG_ID_U32, - init_config_parsers, - ARRAY_SIZE(init_config_parsers)); + + ret = avs_tplg_module_init_configs(comp, module, tuples, block_size); if (ret) return ERR_PTR(ret); - - module->config_ids = config_ids; } module->owner = owner; diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h index b5799c994b88..189984ce7b51 100644 --- a/sound/soc/intel/avs/topology.h +++ b/sound/soc/intel/avs/topology.h @@ -221,8 +221,8 @@ struct avs_tplg_module { u8 domain; struct avs_tplg_modcfg_ext *cfg_ext; u32 ctl_id; - u32 num_config_ids; - u32 *config_ids; + u32 num_init_configs; + struct avs_tplg_init_config **init_configs; struct avs_tplg_nhlt_config *nhlt_config; struct avs_tplg_pipeline *owner; From 99f5566a1a884de4a400230835658ada4af6e001 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:14 +0200 Subject: [PATCH 0212/1317] ASoC: Intel: avs: hda: Constrain MSBs on startup Front-end DAI links are marked as dynamic for the card thus the __soc_pcm_open() function never gets to soc_pcm_apply_msb() step which performs MSBs-constraint rule. Do that on link startup instead. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-11-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/hdaudio.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c index 03cfd91202d3..2e15a293298d 100644 --- a/sound/soc/intel/avs/boards/hdaudio.c +++ b/sound/soc/intel/avs/boards/hdaudio.c @@ -15,6 +15,22 @@ #include "../../../codecs/hda.h" #include "../utils.h" +static int avs_link_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + const struct snd_soc_pcm_stream *stream_info; + struct snd_soc_dai *codec_dai; + + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + stream_info = snd_soc_dai_get_pcm_stream(codec_dai, substream->stream); + + return snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, stream_info->sig_bits); +} + +static const struct snd_soc_ops avs_link_ops = { + .startup = avs_link_startup, +}; + static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, struct snd_soc_dai_link **links) { @@ -43,6 +59,7 @@ static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int dl[i].platforms = platform; dl[i].num_platforms = 1; dl[i].ignore_pmdown_time = 1; + dl[i].ops = &avs_link_ops; dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); From 97f8cb91a8c5658fe2ae6f5c2ff6e95474a5eb2f Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Wed, 26 Aug 2026 16:56:42 +0200 Subject: [PATCH 0213/1317] drm/panic: clean new `clippy::needless_range_loop` lint for Rust 1.100.0 Starting with Rust 1.100.0 (expected 2026-11-12), Clippy warns: warning: the loop variable `i` is only used to index `self.decimals` --> drivers/gpu/drm/drm_panic_qr.rs:410:18 | 410 | for i in 0..len { | ^^^^^^ | note: for this index operation --> drivers/gpu/drm/drm_panic_qr.rs:411:13 | 411 | self.decimals[i] = (chunk % 10) as u8; | ^^^^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/main/index.html#needless_range_loop = note: `-W clippy::needless-range-loop` implied by `-W clippy::all` = help: to override `-W clippy::all` add `#[allow(clippy::needless_range_loop)]` help: consider using an iterator | 410 - for i in 0..len { 410 + for in self.decimals.iter_mut().take(len) { | The lint did not trigger here before because it could not handle arrays behind a field access such as `self.decimals` -- Clippy was improved to catch those cases [1][2]. Thus clean the warning by iterating over a slice rather than using `take()` so that an out-of-range `len` still triggers the same bounds check as the indexed loop. Cc: stable@vger.kernel.org # Needed in 6.18.y and later. Link: https://github.com/rust-lang/rust-clippy/issues/16631 [1] Link: https://github.com/rust-lang/rust-clippy/pull/16634 [2] Assisted-by: LLM Reviewed-by: Alexandre Courbot Reviewed-by: Jocelyn Falempe Link: https://patch.msgid.link/20260826145642.43807-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- drivers/gpu/drm/drm_panic_qr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_panic_qr.rs b/drivers/gpu/drm/drm_panic_qr.rs index ac27e86c601c..4d7eb75a3afc 100644 --- a/drivers/gpu/drm/drm_panic_qr.rs +++ b/drivers/gpu/drm/drm_panic_qr.rs @@ -407,8 +407,8 @@ fn push(&mut self, data: u64, len: usize) { for i in (0..self.len).rev() { self.decimals[i + len] = self.decimals[i]; } - for i in 0..len { - self.decimals[i] = (chunk % 10) as u8; + for decimal in &mut self.decimals[..len] { + *decimal = (chunk % 10) as u8; chunk = div10(chunk); } self.len += len; From 18e5e0ec0e9282c897e2aa81a3e43ccaee03b003 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 31 Aug 2026 11:42:34 -0700 Subject: [PATCH 0214/1317] net: bcmasp: clear txcb->last before writing each descriptor bcmasp_xmit() only wrote txcb->last = true for the final fragment of an SKB; non-final fragments left the field untouched. If a descriptor slot was reused while it still held a stale true from a previous SKB (possible when tx_spb_ring_full() underreported fullness), bcmasp_tx_reclaim() would see last == true mid-SKB and call dev_consume_skb_any() prematurely, freeing the sk_buff while its remaining fragments were still in flight. Unconditionally clear txcb->last before the conditional set so every descriptor slot starts from a known false state regardless of what a prior transmission left behind. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Signed-off-by: Justin Chen Signed-off-by: Danesh Petigara Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260831184235.4133351-2-danesh.petigara@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index ed0977832ce4..2bd035f74fa2 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -301,6 +301,7 @@ static netdev_tx_t bcmasp_xmit(struct sk_buff *skb, struct net_device *dev) txcb->bytes_sent = total_bytes; dma_unmap_addr_set(txcb, dma_addr, mapping); dma_unmap_len_set(txcb, dma_len, size); + txcb->last = false; if (!i) { desc->flags |= DESC_SOF; if (csum_hw) From 0c5cf62e72d7a666ee4da757e122dc1600df1ecc Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 31 Aug 2026 11:42:35 -0700 Subject: [PATCH 0215/1317] net: bcmasp: fix tx_spb_ring_full() checking same slot cnt times The loop initialised next_index from intf->tx_spb_index on every iteration, so incr_ring() always produced the same result and only one slot was ever tested. Move the initialisation before the loop so each iteration advances next_index and the function correctly checks that cnt consecutive descriptor slots are available before allowing a new transmission. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Signed-off-by: Justin Chen Signed-off-by: Danesh Petigara Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260831184235.4133351-3-danesh.petigara@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index 2bd035f74fa2..f2176ef3a127 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -148,8 +148,9 @@ static int tx_spb_ring_full(struct bcmasp_intf *intf, int cnt) int next_index, i; /* Check if we have enough room for cnt descriptors */ + next_index = intf->tx_spb_index; for (i = 0; i < cnt; i++) { - next_index = incr_ring(intf->tx_spb_index, DESC_RING_COUNT); + next_index = incr_ring(next_index, DESC_RING_COUNT); if (next_index == intf->tx_spb_clean_index) return 1; } From efdfb1e27a3328085b79540dfe781d537b576ea1 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 1 Sep 2026 10:59:04 +0000 Subject: [PATCH 0216/1317] ipv4: fib: bound automatic table ID allocation fib_empty_table() probes every table ID from 1 until it finds a free one. IPv4 tables are stored in a 256-bucket hash table, so a dense set of IDs makes each probe walk a growing hash chain while RTNL is held. Automatic table assignment ("ip rule ... table 0") is an IPv4-only legacy path. Bound the automatically allocated ID to 4096 so the RTNL hold stays bounded, without changing lookups of explicitly specified table IDs. This changes user-visible behavior. A table-0 rule previously received the lowest free ID in 1..RT_TABLE_MAX (0xFFFFFFFF). After this patch the search stops at 4096 and the rule add fails with ENOBUFS if that range is fully occupied. Explicit table IDs above 4096 remain usable. The automatic path is unused in practice: it is IPv4-only, not documented by ip-rule, uncovered by kernel selftests, and both NetworkManager and systemd refuse table 0. Fixes: b801f54917b7 ("[NET]: Increate RT_TABLE_MAX to 2^32") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Ido Schimmel Signed-off-by: Zihan Xi Reviewed-by: Ido Schimmel Reviewed-by: Petr Vorel Link: https://patch.msgid.link/6f2f2a7a136aee005512a2e1ac8ede62ac8c7bb6.1788258884.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/fib_rules.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c index 4edb0dca7be8..060501b376a8 100644 --- a/net/ipv4/fib_rules.c +++ b/net/ipv4/fib_rules.c @@ -214,6 +214,8 @@ INDIRECT_CALLABLE_SCOPE int fib4_rule_match(struct fib_rule *rule, return 1; } +#define FIB_MAX_AUTO_TABLE_ID 4096 + static struct fib_table *fib_empty_table(struct net *net) { u32 id = 1; @@ -222,7 +224,7 @@ static struct fib_table *fib_empty_table(struct net *net) if (!fib_get_table(net, id)) return fib_new_table(net, id); - if (id++ == RT_TABLE_MAX) + if (id++ == FIB_MAX_AUTO_TABLE_ID) break; } return NULL; From 2ac174dfcdde399fa95ba889541fb5e688d8bb35 Mon Sep 17 00:00:00 2001 From: Taylor Bates Date: Tue, 1 Sep 2026 22:49:46 -0400 Subject: [PATCH 0217/1317] mlxsw: spectrum_ptp: Fix napi_gro_receive() call from GC workqueue context Currently mlxsw_sp1_ptp_ht_gc_collect() is run from the PTP garbage-collection workqueue, rather than the NAPI poll context. For any unmatched PTP entries carrying an SKB, it calls mlxsw_sp1_ptp_unmatched_finish() -> mlxsw_sp1_ptp_packet_finish(). For ingress packets, this calls mlxsw_sp_rx_listener_no_mark_func(). The end of that function is the following: skb->protocol = eth_type_trans(skb, skb->dev); napi_gro_receive(mlxsw_skb_cb(skb)->rx_md_info.napi, skb); The napi pointer is one that was placed in the SKB control block when the trapped packet was received in the NAPI context. Later, when the GC reaps the unmatched entry (up to MLXSW_SP1_PTP_HT_GC_TIMEOUT later), the call to napi_gro_receive() mutates the NAPI instance's GRO list, which is unsafe if the poll is running concurrently on another CPU. In mlxsw_sp1_ptp_ht_gc_collect(), local_bh_disable() is called to prevent softirq processing, but this only applies to the local CPU. Additionally, its comment is stale. It states that mlxsw_sp1_ptp_unmatched_finish() invokes netif_receive_skb(). This has not been accurate since the referenced commit; this patch makes that comment accurate again. mlxsw_pci_napi_devs_init() calls netif_threaded_enable() on the NAPI RX net_device without any conditions. The NAPI instance's poll, which may be running concurrent to the GC, is running as an independently-scheduled kthread which may be on a different CPU. The call to local_bh_disable() does not guard against this. If a tx-timestamp timeout produces an unmatched entry (which can be easily reproduced by running ptp4l and waiting for a port to reach the UNCALIBRATED/SLAVE state) while the owning NAPI thread is in the middle of a poll on another CPU, both sides mutate the GRO list concurrently, as shown below: [39.846] port 1 (swp1): MASTER to UNCALIBRATED on RS_SLAVE list_add corruption. next->prev should be prev (ffff8d620faf4138), but was ffff8d624150f700. (next=ffff8d620faf4138). kernel BUG at lib/list_debug.c:29! Oops: invalid opcode: 0000 [#1] SMP PTI CPU: 1 UID: 0 PID: 539 Comm: napi/mlxsw_rx-0 Not tainted 6.18.48 #1-NixOS PREEMPT(lazy) Hardware name: Mellanox Technologies Ltd. MSN2410/VMOD0001, BIOS 4.6.5 09/13/2018 RIP: 0010:__list_add_valid_or_report+0x79/0xb0 RSP: 0018:ffffcdf8c0f27c08 EFLAGS: 00010246 RAX: 0000000000000075 RBX: ffff8d624150fd00 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff8d6315d1e540 RBP: ffff8d620faf4070 R08: 0000000000000000 R09: 00000000ffffdfff R10: ffffffffa5c60fe0 R11: ffffcdf8c0f27ab8 R12: 0000000000000003 R13: 000000000000003d R14: 00000000000001bc R15: 0000000000000001 FS: 0000000000000000(0000) GS:ffff8d636f63f000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000562689a60c24 CR3: 000000015f224004 CR4: 00000000001726f0 Call Trace: gro_receive_skb+0xee/0x230 mlxsw_sp1_ptp_got_packet+0x61/0x140 [mlxsw_spectrum] mlxsw_core_skb_receive+0xdf/0x1b0 [mlxsw_core] mlxsw_pci_napi_poll_cq_rx+0x780/0x9d0 [mlxsw_pci] __napi_poll+0x31/0x1e0 napi_threaded_poll_loop+0x16b/0x1c0 napi_threaded_poll+0x71/0xa0 kthread+0xfb/0x260 ret_from_fork+0x22d/0x260 ret_from_fork_asm+0x1a/0x30 Kernel panic - not syncing: Fatal exception in interrupt The machinery that leads to this kernel panic has not been changed between 6.18.48 and mainline. This patch adds an ingress-delivery helper for the PTP packet_finish() path that calls netif_receive_skb() instead of napi_gro_receive(). netif_receive_skb(), unlike napi_gro_receive(), can be called from outside of the NAPI instance's poll context, which can occur at the call site for this path. RX stats accounting and the skb->dev assignment are still preserved; the only change is the delivery call itself. This removes GRO batching for any PTP event traffic received by the mlxsw trap, but given the relatively low volume of traffic characteristic of the protocol, and impact limited to only Spectrum-1 ASICs, this is an acceptable solution. Fixes: 1ba06ca96ca2 ("mlxsw: Switch to napi_gro_receive()") Signed-off-by: Taylor Bates Reviewed-by: Petr Machata Link: https://patch.msgid.link/20260902024949.2273997-1-tmbates12@gmail.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlxsw/spectrum_ptp.c | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c index 9939749c47bc..9c5862f4e16a 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c @@ -572,6 +572,38 @@ mlxsw_sp1_ptp_unmatched_remove(struct mlxsw_sp *mlxsw_sp, mlxsw_sp1_ptp_unmatched_ht_params); } +/* mlxsw_sp1_ptp_packet_finish() is reached both from the NAPI poll context + * (mlxsw_sp1_ptp_got_packet(), mlxsw_sp1_ptp_got_piece() and + * mlxsw_sp1_packet_timestamp()) and from process context, by way of the GC + * workqueue (mlxsw_sp1_ptp_ht_gc_collect() -> + * mlxsw_sp1_ptp_unmatched_finish()). + * + * mlxsw_sp_rx_listener_no_mark_func() ends in napi_gro_receive(), using the + * NAPI pointer that was placed in the SKB control block when the trapped + * packet was received in the NAPI context. That pointer may only be used + * from its own poll context, which this call site cannot guarantee. + * + * netif_receive_skb(), unlike napi_gro_receive(), can be called from outside + * of the NAPI instance's poll context. RX stats accounting and the skb->dev + * assignment are still preserved; the only change is the delivery call. + */ +static void mlxsw_sp1_ptp_rx_finish(struct mlxsw_sp_port *mlxsw_sp_port, + struct sk_buff *skb) +{ + struct mlxsw_sp_port_pcpu_stats *pcpu_stats; + + skb->dev = mlxsw_sp_port->dev; + + pcpu_stats = this_cpu_ptr(mlxsw_sp_port->pcpu_stats); + u64_stats_update_begin(&pcpu_stats->syncp); + pcpu_stats->rx_packets++; + pcpu_stats->rx_bytes += skb->len; + u64_stats_update_end(&pcpu_stats->syncp); + + skb->protocol = eth_type_trans(skb, skb->dev); + netif_receive_skb(skb); +} + /* This function is called in the following scenarios: * * 1) When a packet is matched with its timestamp. @@ -600,7 +632,7 @@ static void mlxsw_sp1_ptp_packet_finish(struct mlxsw_sp *mlxsw_sp, if (ingress) { if (hwtstamps) *skb_hwtstamps(skb) = *hwtstamps; - mlxsw_sp_rx_listener_no_mark_func(skb, local_port, mlxsw_sp); + mlxsw_sp1_ptp_rx_finish(mlxsw_sp_port, skb); } else { /* skb_tstamp_tx() allows hwtstamps to be NULL. */ skb_tstamp_tx(skb, hwtstamps); From 6a1094c34d176827b2b173e163dcc964a13af93f Mon Sep 17 00:00:00 2001 From: XingWang Xiang Date: Wed, 2 Sep 2026 17:43:17 +0900 Subject: [PATCH 0218/1317] genetlink: pin family module during policy dump The generic netlink controller's policy dump keeps pointers to the target family's operation and policy tables in its callback state. A dump may be split across multiple skbs and remain pending after the initial request. Netlink pins the module which owns the dump callback, but in this case that is the controller's owner rather than the target family's owner. The target family can consequently be unregistered and its module unloaded while a policy dump is pending. Advancing the dump then dereferences policy memory from the unloaded module. Take a reference to the target family's module when the dump starts. Drop it from the error and done paths. This matches the lifetime for which the dump context retains the family and policy pointers. Fixes: d07dcf9aadd6 ("netlink: add infrastructure to expose policies to userspace") Cc: stable@vger.kernel.org Signed-off-by: XingWang Xiang Link: https://patch.msgid.link/20260902084317.4092542-1-v3rdant.xiang@gmail.com Signed-off-by: Jakub Kicinski --- net/netlink/genetlink.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index 0da39eaed255..41d37442f186 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -1513,6 +1513,7 @@ struct ctrl_dump_policy_ctx { struct netlink_policy_dump_state *state; const struct genl_family *rt; struct genl_op_iter *op_iter; + struct module *owner; u32 op; u16 fam_id; u8 dump_map:1, @@ -1555,6 +1556,9 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) return -ENOENT; ctx->rt = rt; + ctx->owner = rt->module; + if (!try_module_get(ctx->owner)) + return -ENOENT; if (tb[CTRL_ATTR_OP]) { struct genl_split_ops doit, dump; @@ -1565,7 +1569,7 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) err = genl_get_cmd_both(ctx->op, rt, &doit, &dump); if (err) { NL_SET_BAD_ATTR(cb->extack, tb[CTRL_ATTR_OP]); - return err; + goto err_put_owner; } if (doit.policy) { @@ -1583,16 +1587,20 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) goto err_free_state; } - if (!ctx->state) - return -ENODATA; + if (!ctx->state) { + err = -ENODATA; + goto err_put_owner; + } ctx->dump_map = 1; return 0; } ctx->op_iter = kmalloc_obj(*ctx->op_iter); - if (!ctx->op_iter) - return -ENOMEM; + if (!ctx->op_iter) { + err = -ENOMEM; + goto err_put_owner; + } genl_op_iter_init(rt, ctx->op_iter); ctx->dump_map = genl_op_iter_next(ctx->op_iter); @@ -1624,6 +1632,8 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) netlink_policy_dump_free(ctx->state); err_free_op_iter: kfree(ctx->op_iter); +err_put_owner: + module_put(ctx->owner); return err; } @@ -1760,6 +1770,7 @@ static int ctrl_dumppolicy_done(struct netlink_callback *cb) kfree(ctx->op_iter); netlink_policy_dump_free(ctx->state); + module_put(ctx->owner); return 0; } From b58d749633203d92c265317b45fccee555090352 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Wed, 2 Sep 2026 22:01:12 +0300 Subject: [PATCH 0219/1317] tunnels: Drop stale dst when building an ICMP error for PMTUD Bridged UDP tunnels such as VXLAN and GENEVE build an ICMP error packet around an overlay packet if the packet is going to exceed the underlay path MTU. The ICMP error packet is then injected back into the Rx path with the source and destination addresses swapped, so that it will be delivered to the overlay source. If the overlay packet was routed to the UDP tunnel or locally generated, then it is already carrying a valid dst entry and this entry is not dropped when transforming the packet to an ICMP error packet. This causes the IP layer to reuse the dst entry, leading to the ICMP error packet being dropped or routed out of the UDP tunnel interface in case of forwarding. Prior to the blamed commit this could not happen, as skb_tunnel_check_pmtu() did not build ICMP errors for PACKET_HOST packets. Such packets were instead encapsulated and, unless the DF bit was set in the outer header, fragmented by the underlay. Fix this by making sure that the ICMP error packet does not have a valid dst entry, thereby forcing the IP layer to perform a route lookup. Adjust the bridged PMTU exception selftests accordingly. When the local sender in ns_a pings the overlay destination with a deadline (-w), ping exits on the first socket error before any reply is received and returns a non-zero exit code. The test therefore only passed because the ICMP error was never delivered. Use a packet count (-c) like the ns_c line above it, so that the ICMP error counts against the packet budget and the exit code depends on whether echo replies were received. This passes with and without the fix. Fixes: 8930424777e4 ("tunnels: Accept PACKET_HOST in skb_tunnel_check_pmtu().") Cc: stable@vger.kernel.org Reported-by: Laika Price Closes: https://lore.kernel.org/netdev/20260614-master-v3-1-9f5060ba1ed1@gmail.com/ Reported-by: Yaroslav Dudkov Closes: https://lore.kernel.org/netdev/20260901081825.287173-1-aroslavdudkov622@gmail.com/ Reported-by: Charles Bordet Closes: https://lore.kernel.org/netdev/aHVhQLPJIhq-SYPM@eldamar.lan/ Signed-off-by: Ido Schimmel Tested-by: Yaroslav Dudkov Reviewed-by: David Ahern Reviewed-by: Stefano Brivio Reviewed-by: Guillaume Nault Link: https://patch.msgid.link/20260902190112.4126199-1-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_tunnel_core.c | 6 ++++++ tools/testing/selftests/net/pmtu.sh | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c index d3c677e9bff2..5168d546ea2f 100644 --- a/net/ipv4/ip_tunnel_core.c +++ b/net/ipv4/ip_tunnel_core.c @@ -268,6 +268,9 @@ static int iptunnel_pmtud_build_icmp(struct sk_buff *skb, int mtu) eth_header(skb, skb->dev, ntohs(eh.h_proto), eh.h_source, eh.h_dest, 0); skb_reset_mac_header(skb); + if (skb_valid_dst(skb)) + skb_dst_drop(skb); + return skb->len; } @@ -371,6 +374,9 @@ static int iptunnel_pmtud_build_icmpv6(struct sk_buff *skb, int mtu) eth_header(skb, skb->dev, ntohs(eh.h_proto), eh.h_source, eh.h_dest, 0); skb_reset_mac_header(skb); + if (skb_valid_dst(skb)) + skb_dst_drop(skb); + return skb->len; } diff --git a/tools/testing/selftests/net/pmtu.sh b/tools/testing/selftests/net/pmtu.sh index a3323c21f001..c7cd271714ef 100755 --- a/tools/testing/selftests/net/pmtu.sh +++ b/tools/testing/selftests/net/pmtu.sh @@ -1457,7 +1457,7 @@ test_pmtu_ipvX_over_bridged_vxlanY_or_geneveY_exception() { mtu "${ns_b}" ${type}_b $((${ll_mtu} + 1000)) run_cmd ${ns_c} ${ping} -q -M want -i 0.1 -c 10 -s $((${ll_mtu} + 500)) ${dst} || return 1 - run_cmd ${ns_a} ${ping} -q -M want -i 0.1 -w 1 -s $((${ll_mtu} + 500)) ${dst} || return 1 + run_cmd ${ns_a} ${ping} -q -M want -i 0.1 -c 10 -s $((${ll_mtu} + 500)) ${dst} || return 1 # Check that exceptions were created pmtu="$(route_get_dst_pmtu_from_exception "${ns_c}" ${dst})" From a09ceadff95b0075a9b6a5d9dbeb6c1c5f311c60 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 1 Sep 2026 11:21:24 +0200 Subject: [PATCH 0220/1317] mailmap: add entries for Lorenzo Bianconi Add the active email address for Lorenzo Bianconi and map the old, no-longer-used addresses to it, so that git can attribute his contributions to a single identity. This is done to avoid bouncing emails sent to email addresses that are no longer active. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260901-lorenzo-mailmap-v2-1-0ee832de0caf@kernel.org Signed-off-by: Jakub Kicinski --- .mailmap | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.mailmap b/.mailmap index 6803f3bd2865..5fd5c834e0ac 100644 --- a/.mailmap +++ b/.mailmap @@ -550,6 +550,9 @@ Li Yang Lior David Loic Poulain Loic Poulain +Lorenzo Bianconi +Lorenzo Bianconi +Lorenzo Bianconi Lorenzo Pieralisi Lorenzo Stoakes Lorenzo Stoakes From 39b23c1c40e1f73d2b94a09282cc476af647e438 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Wed, 2 Sep 2026 14:39:54 -0700 Subject: [PATCH 0221/1317] bnxt_en: Prevent queue stop with deferred completions When the driver receives a burst of packets, it can mark a BD with the NO_CMPL bit to defer completions. The expectation is that the last packet in the ring will have this bit unset and the completion generated by that packet will cleanup that packet and the ones preceding it. This helps to reduce the number of completions fired. The suppressed completions are controlled by the driver and the number of packets with suppressed completions scales with the size of the ring. SW USO packets, on the other hand, have an upper bound on the maximum number of BDs which can be consumed which does not scale with the ring size. So, for small rings it is possible that: a burst of packets is handed to the driver, the driver defers completions for all of the packets because the number of free descriptors stays above the threshold in the driver. Then, a USO packet arrives, but the number of BDs available is not enough and the USO code exits early. In this case, you end up in a state where the ring is full of packets with their completions suppressed, which can cause the queue to stop and never be restarted. Assuming default CONFIG_MAX_SKB_FRAGS, this is only possible for small rings (<= 457 descriptors, below the driver default value) when a burst of packets fills the ring, followed by a large USO packet that can't fit. For larger rings, the delta between the completion suppression threshold and the BDs required for SW USO is large enough that completions will fire and this case is unreachable. This issue was pointed out by Sashiko and while it seems fairly unlikely given that the queue size must be small to trigger this, it is indeed possible. Fix this by tracking the last BD which deferred completions and centralizing the logic for deciding when to ring the doorbell. The NO_CMPL bit is now cleared in bnxt_txr_db_kick(), so every doorbell site is covered, including the SW USO early exit. This guarantees the ring always ends in a BD which generates a completion to clean it and wake the queue. Fixes: cc5d90667db8 ("net: bnxt: Implement software USO") Cc: # v7.1+: 4e15e89faac9: net: bnxt: ring the doorbell when SW USO exits early Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902213956.4160615-1-joe@dama.to Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 42 +++++++++++++++---- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 + drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c | 21 ++++++---- drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h | 6 +-- 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index d59bcca73a2b..8c6e2ee6bee4 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -462,6 +462,16 @@ u16 bnxt_xmit_get_cfa_action(struct sk_buff *skb) static void bnxt_txr_db_kick(struct bnxt *bp, struct bnxt_tx_ring_info *txr, u16 prod) { + /* If the most recent BD has its completion suppressed, unset the bit + * so that a completion is generated, otherwise nothing is left to + * clean the ring and wake the queue. + */ + if (txr->kick_txbd0) { + txr->kick_txbd0->tx_bd_len_flags_type &= + cpu_to_le32(~TX_BD_FLAGS_NO_CMPL); + txr->kick_txbd0 = NULL; + } + /* Sync BD data before updating doorbell */ wmb(); bnxt_db_write(bp, &txr->tx_db, prod); @@ -485,7 +495,6 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) struct bnxt_sw_tx_bd *tx_buf; __le32 lflags = 0; skb_frag_t *frag; - netdev_tx_t ret; i = skb_get_queue_mapping(skb); if (unlikely(i >= bp->tx_nr_rings)) { @@ -509,11 +518,22 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) if (skb_is_gso(skb) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) && !(bp->flags & BNXT_FLAG_UDP_GSO_CAP)) { - ret = bnxt_sw_udp_gso_xmit(bp, txr, txq, skb); - if (txr->kick_pending) + int rc = bnxt_sw_udp_gso_xmit(bp, txr, txq, skb); + + /* if SW USO queued a packet, the doorbell will be written + * below and there is no reason to track the last BD with + * suppressed completions + */ + if (rc > 0) + txr->kick_txbd0 = NULL; + + /* if a packet was queued by SW USO or a doorbell was pending + * from a previous xmit that was deferred, write the doorbell. + */ + if (rc > 0 || txr->kick_pending) bnxt_txr_db_kick(bp, txr, txr->tx_prod); - return ret; + return rc < 0 ? NETDEV_TX_BUSY : NETDEV_TX_OK; } free_size = bnxt_tx_avail(bp, txr); @@ -751,23 +771,23 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) prod = NEXT_TX(prod); WRITE_ONCE(txr->tx_prod, prod); + txr->kick_txbd0 = NULL; if (!netdev_xmit_more() || netif_xmit_stopped(txq)) { bnxt_txr_db_kick(bp, txr, prod); } else { - if (free_size >= bp->tx_wake_thresh) + if (free_size >= bp->tx_wake_thresh) { txbd0->tx_bd_len_flags_type |= cpu_to_le32(TX_BD_FLAGS_NO_CMPL); + txr->kick_txbd0 = txbd0; + } txr->kick_pending = 1; } tx_done: if (unlikely(bnxt_tx_avail(bp, txr) <= MAX_SKB_FRAGS + 1)) { - if (netdev_xmit_more() && !tx_buf->is_push) { - txbd0->tx_bd_len_flags_type &= - cpu_to_le32(~TX_BD_FLAGS_NO_CMPL); + if (txr->kick_pending) bnxt_txr_db_kick(bp, txr, prod); - } netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr), bp->tx_wake_thresh); @@ -5427,6 +5447,8 @@ static void bnxt_clear_ring_indices(struct bnxt *bp) txr->tx_prod = 0; txr->tx_cons = 0; txr->tx_hw_cons = 0; + txr->kick_pending = 0; + txr->kick_txbd0 = NULL; } rxr = bnapi->rx_ring; @@ -11772,6 +11794,8 @@ static int bnxt_tx_queue_start(struct bnxt *bp, int idx) txr->tx_prod = 0; txr->tx_cons = 0; txr->tx_hw_cons = 0; + txr->kick_pending = 0; + txr->kick_txbd0 = NULL; start_tx: WRITE_ONCE(txr->dev_state, 0); synchronize_net(); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index ab894f8addef..dc5a16ec5943 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -993,6 +993,7 @@ struct bnxt_tx_ring_info { u16 txq_index; u8 tx_napi_idx; u8 kick_pending; + struct tx_bd *kick_txbd0; struct bnxt_db_info tx_db; struct tx_bd *tx_desc_ring[MAX_TX_PAGES]; diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c index f7e18bea0fb8..6c1060fa2ea5 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c @@ -31,10 +31,14 @@ static u32 bnxt_sw_gso_lhint(unsigned int len) return TX_BD_FLAGS_LHINT_2048_AND_LARGER; } -netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, - struct bnxt_tx_ring_info *txr, - struct netdev_queue *txq, - struct sk_buff *skb) +/* Transmit an skb requiring software UDP segmentation. + * + * Returns 1 if the skb was queued and new BDs were produced, 0 if the skb + * was dropped, or -1 if the ring is full and the skb should be retried. + * The caller owns the doorbell for all three cases. + */ +int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr, + struct netdev_queue *txq, struct sk_buff *skb) { unsigned int last_unmap_len __maybe_unused = 0; dma_addr_t last_unmap_addr __maybe_unused = 0; @@ -69,7 +73,7 @@ netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, if (unlikely(bnxt_tx_avail(bp, txr) < bds_needed)) { netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr), bp->tx_wake_thresh); - return NETDEV_TX_BUSY; + return -1; } /* BD backpressure alone cannot prevent overwriting in-flight @@ -77,7 +81,7 @@ netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, */ if (!netif_txq_maybe_stop(txq, bnxt_inline_avail(txr), num_segs, num_segs)) - return NETDEV_TX_BUSY; + return -1; if (unlikely(tso_dma_map_init(&map, &pdev->dev, skb, hdr_len))) goto drop; @@ -223,16 +227,15 @@ netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, netdev_tx_sent_queue(txq, skb->len); WRITE_ONCE(txr->tx_prod, prod); - txr->kick_pending = 1; if (unlikely(bnxt_tx_avail(bp, txr) <= bp->tx_wake_thresh)) netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr), bp->tx_wake_thresh); - return NETDEV_TX_OK; + return 1; drop: dev_kfree_skb_any(skb); dev_core_stats_tx_dropped_inc(bp->dev); - return NETDEV_TX_OK; + return 0; } diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h index 47528c20f311..77d9af97cc22 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h @@ -38,9 +38,7 @@ static inline int bnxt_min_tx_desc_cnt(struct bnxt *bp, return BNXT_MIN_TX_DESC_CNT; } -netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, - struct bnxt_tx_ring_info *txr, - struct netdev_queue *txq, - struct sk_buff *skb); +int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr, + struct netdev_queue *txq, struct sk_buff *skb); #endif From b752e041d5845d03f285cf7a1f30b38ca7ef92bf Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Sun, 30 Aug 2026 15:22:52 -0700 Subject: [PATCH 0222/1317] enic: preserve V2 VF carrier across netdev reopen A V2 VF receives carrier state only from PF MBOX notifications. enic_stop() forces carrier off, but enic_open() does not request a fresh notification or restore the previous one. An ordinary down/up cycle therefore leaves the VF in NO-CARRIER and unable to pass traffic until the PF repeats the link-state command, even when the physical link remained up. Cache each valid PF link-state notification. Serialize updates with the V2 VF datapath running state. Keep carrier off while the netdev is stopped. Restore the cached state after an ordinary open. Before either internal reset reopens the datapath, invalidate the cache. Carrier then remains off until re-registration receives a fresh PF link-state notification. Fixes: 72b65c94058e ("enic: add MBOX VF handlers for capability, register and link state") Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260830-b4-enic-v2-mbox-fixes-net-v1-1-23adf9bfd426@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic.h | 13 ++++++++ drivers/net/ethernet/cisco/enic/enic_main.c | 12 ++++++- drivers/net/ethernet/cisco/enic/enic_mbox.c | 37 ++++++++++++++++++--- drivers/net/ethernet/cisco/enic/enic_mbox.h | 2 ++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 4a67947cfb9f..2822fbfb6474 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -137,6 +137,12 @@ struct enic_port_profile { u8 mac_addr[ETH_ALEN]; }; +enum enic_vf_link_state { + ENIC_VF_LINK_STATE_UNKNOWN, + ENIC_VF_LINK_STATE_DOWN, + ENIC_VF_LINK_STATE_UP, +}; + /* enic_rfs_fltr_node - rfs filter node in hash table * @@keys: IPv4 5 tuple * @flow_id: flow_id of clsf filter provided by kernel @@ -312,6 +318,13 @@ struct enic { unsigned int admin_msg_count; /* current depth of admin_msg_list */ void (*admin_rq_handler)(struct enic *enic, void *buf, unsigned int len); + /* The PF is authoritative for a V2 VF's carrier. Keep the last + * notification across an ordinary netdev close/open and serialize it + * against the open/stop carrier transition. + */ + spinlock_t vf_link_state_lock; + enum enic_vf_link_state vf_link_state; + bool vf_link_running; /* MBOX protocol state — mbox_lock serializes admin WQ sends */ struct mutex mbox_lock; diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index 0baef7a120ec..48d16ef18c49 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -1800,6 +1800,8 @@ static int enic_open(struct net_device *netdev) enic_notify_timer_start(enic); enic_rfs_timer_start(enic); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_set_running(enic, true); return 0; @@ -1853,7 +1855,10 @@ static int enic_stop(struct net_device *netdev) for (i = 0; i < enic->rq_count; i++) napi_disable(&enic->napi[i]); - netif_carrier_off(netdev); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_set_running(enic, false); + else + netif_carrier_off(netdev); if (vnic_dev_get_intr_mode(enic->vdev) == VNIC_DEV_INTR_MODE_MSIX) for (i = 0; i < enic->wq_count; i++) napi_disable(&enic->napi[enic_cq_wq(enic, i)]); @@ -2271,6 +2276,8 @@ static void enic_reset(struct work_struct *work) enic_admin_channel_close(enic); enic_stop(enic->netdev); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_reset(enic); enic_dev_soft_reset(enic); enic_reset_addr_lists(enic); @@ -2315,6 +2322,8 @@ static void enic_tx_hang_reset(struct work_struct *work) enic_dev_hang_notify(enic); enic_stop(enic->netdev); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_reset(enic); enic_dev_hang_reset(enic); enic_reset_addr_lists(enic); @@ -3015,6 +3024,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) enic = netdev_priv(netdev); enic->netdev = netdev; enic->pdev = pdev; + spin_lock_init(&enic->vf_link_state_lock); /* Setup PCI resources */ diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c index 2fb0f1e2ff50..ad79d3951f3d 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -396,25 +396,32 @@ static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) { struct enic_mbox_pf_link_state_notif_msg *notif = payload; struct enic_mbox_pf_link_state_ack_msg ack = {}; + u32 link_state = le32_to_cpu(notif->link_state); int err; - switch (le32_to_cpu(notif->link_state)) { + spin_lock_bh(&enic->vf_link_state_lock); + switch (link_state) { case ENIC_MBOX_LINK_STATE_ENABLE: - if (!netif_carrier_ok(enic->netdev)) + enic->vf_link_state = ENIC_VF_LINK_STATE_UP; + if (enic->vf_link_running && + !netif_carrier_ok(enic->netdev)) netif_carrier_on(enic->netdev); netdev_dbg(enic->netdev, "MBOX: link state -> UP\n"); break; case ENIC_MBOX_LINK_STATE_DISABLE: - if (netif_carrier_ok(enic->netdev)) + enic->vf_link_state = ENIC_VF_LINK_STATE_DOWN; + if (enic->vf_link_running && + netif_carrier_ok(enic->netdev)) netif_carrier_off(enic->netdev); netdev_dbg(enic->netdev, "MBOX: link state -> DOWN\n"); break; default: netdev_warn(enic->netdev, "MBOX: unknown link state %u\n", - le32_to_cpu(notif->link_state)); + link_state); ack.ack.ret_major = cpu_to_le16(ENIC_MBOX_ERR_GENERIC); break; } + spin_unlock_bh(&enic->vf_link_state_lock); err = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK, ENIC_MBOX_DST_PF, &ack, sizeof(ack)); @@ -423,6 +430,28 @@ static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) "MBOX: failed to send link state ACK: %d\n", err); } +void enic_mbox_vf_link_state_reset(struct enic *enic) +{ + spin_lock_bh(&enic->vf_link_state_lock); + enic->vf_link_state = ENIC_VF_LINK_STATE_UNKNOWN; + if (enic->vf_link_running && netif_carrier_ok(enic->netdev)) + netif_carrier_off(enic->netdev); + spin_unlock_bh(&enic->vf_link_state_lock); +} + +void enic_mbox_vf_link_state_set_running(struct enic *enic, bool running) +{ + spin_lock_bh(&enic->vf_link_state_lock); + enic->vf_link_running = running; + if (running && enic->vf_link_state == ENIC_VF_LINK_STATE_UP) { + if (!netif_carrier_ok(enic->netdev)) + netif_carrier_on(enic->netdev); + } else if (netif_carrier_ok(enic->netdev)) { + netif_carrier_off(enic->netdev); + } + spin_unlock_bh(&enic->vf_link_state_lock); +} + static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type, u16 payload_len, size_t min_len) { diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h index 15e30ee2b0ed..60409bad2f28 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.h +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -88,6 +88,8 @@ void enic_mbox_init(struct enic *enic); int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, void *payload, u16 payload_len); int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state); +void enic_mbox_vf_link_state_reset(struct enic *enic); +void enic_mbox_vf_link_state_set_running(struct enic *enic, bool running); int enic_mbox_vf_capability_check(struct enic *enic); int enic_mbox_vf_register(struct enic *enic); int enic_mbox_vf_unregister(struct enic *enic); From 8972d252f495d4be4bbfb32d4d9d6c2ff778fea4 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Sun, 30 Aug 2026 15:22:53 -0700 Subject: [PATCH 0223/1317] enic: match mailbox replies to request numbers The version-1 VF mailbox protocol identifies every message with a message number, and a reply or acknowledgment echoes the number of the message it answers. ENIC instead generates a new number for outgoing replies and accepts a VF reply by message type alone. If a request times out, a delayed reply can therefore satisfy a subsequent request of the same type and cause the VF to consume the result of the old request. Allow replies to reuse the initiating message number. Make the in-tree PF handlers and the VF link-state acknowledgment echo that number. Record the expected reply type and message number on the VF, and require both values to match before accepting a reply. Protect expected-reply state with a lock so reply acceptance and timeout invalidation cannot race. Keep message numbers monotonic across an admin- channel reopen so a delayed reply from an earlier channel generation cannot match a new request. Reply-number echo is part of the established version-1 protocol, so this remains compatible with deployed V2-capable PF implementations that already echo msg_num. Fixes: 72b65c94058e ("enic: add MBOX VF handlers for capability, register and link state") Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260830-b4-enic-v2-mbox-fixes-net-v1-2-23adf9bfd426@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic.h | 13 +- drivers/net/ethernet/cisco/enic/enic_main.c | 13 +- drivers/net/ethernet/cisco/enic/enic_mbox.c | 284 ++++++++++++-------- 3 files changed, 180 insertions(+), 130 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 2822fbfb6474..7a509a056990 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -329,15 +329,14 @@ struct enic { /* MBOX protocol state — mbox_lock serializes admin WQ sends */ struct mutex mbox_lock; u64 mbox_msg_num; - /* MBOX request-reply state. mbox_expected_reply is written and - * cleared by the process-context request helpers (capability/register/ - * unregister) and only read by the admin_msg_work receive handlers, so - * it is annotated with READ_ONCE()/WRITE_ONCE() rather than locked: - * only one request is in flight at a time (requesters run under RTNL or - * single-threaded probe/remove), so each request is serialized and its - * reply completes mbox_comp before the next request is issued. + /* MBOX request-reply state. Existing request callers allow only one + * request in flight. The state lock arbitrates reply acceptance against + * timeout invalidation, while mbox_comp publishes the accepted result to + * the requester. */ struct completion mbox_comp; + spinlock_t mbox_state_lock; /* protects expected reply state */ + u64 mbox_expected_msg_num; u8 mbox_expected_reply; bool mbox_initialized; diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index 48d16ef18c49..14d1637a4342 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -2206,9 +2206,10 @@ static void enic_admin_chan_reopen(struct enic *enic) { int err; - /* Install the MBOX receive handler and reset the sequence number - * before opening the channel, so the handler is in place before the - * admin interrupt is unmasked and no early completion is dropped. + /* Install the MBOX receive handler and clear pending reply state before + * opening the channel, so the handler is in place before the admin + * interrupt is unmasked and no early completion is dropped. Keep the + * sequence number monotonic across channel generations. */ enic_mbox_init(enic); @@ -2220,7 +2221,7 @@ static void enic_admin_chan_reopen(struct enic *enic) * registration over a dead channel. */ if (enic_is_sriov_vf_v2(enic)) - enic->vf_registered = false; + WRITE_ONCE(enic->vf_registered, false); err = enic_admin_channel_open(enic); if (err) { @@ -3349,7 +3350,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err_out_admin_close: if (enic_is_sriov_vf_v2(enic)) { - if (enic->vf_registered) { + if (READ_ONCE(enic->vf_registered)) { int unreg_err = enic_mbox_vf_unregister(enic); if (unreg_err) @@ -3402,7 +3403,7 @@ static void enic_remove(struct pci_dev *pdev) * touching a netdev that is being torn down. */ if (enic_is_sriov_vf_v2(enic)) { - if (enic->vf_registered) { + if (READ_ONCE(enic->vf_registered)) { int unreg_err = enic_mbox_vf_unregister(enic); if (unreg_err) diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c index ad79d3951f3d..5c93ca49552a 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -18,22 +18,25 @@ #define ENIC_MBOX_POLL_TIMEOUT_US 5000000 #define ENIC_MBOX_POLL_INTERVAL_US 100 -static void enic_mbox_fill_hdr(struct enic *enic, struct enic_mbox_hdr *hdr, - u8 msg_type, u16 dst_vnic_id, u16 msg_len) +static void enic_mbox_fill_hdr(struct enic_mbox_hdr *hdr, u8 msg_type, + u16 dst_vnic_id, u16 msg_len, u64 msg_num) { memset(hdr, 0, sizeof(*hdr)); hdr->dst_vnic_id = cpu_to_le16(dst_vnic_id); hdr->msg_type = msg_type; hdr->msg_len = cpu_to_le16(msg_len); - hdr->msg_num = cpu_to_le64(++enic->mbox_msg_num); + hdr->msg_num = cpu_to_le64(msg_num); } -int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, - void *payload, u16 payload_len) +static int enic_mbox_send_msg_id(struct enic *enic, u8 msg_type, + u16 dst_vnic_id, void *payload, + u16 payload_len, u64 msg_num, bool reuse_msg_num, + u8 expected_reply) { size_t total_len = sizeof(struct enic_mbox_hdr) + payload_len; struct vnic_wq *wq = &enic->admin_wq; struct wq_enet_desc *desc; + bool reply_expected = false; unsigned long timeout; dma_addr_t dma_addr; u16 vlan_tag; @@ -68,7 +71,21 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, goto unlock; } - enic_mbox_fill_hdr(enic, buf, msg_type, dst_vnic_id, total_len); + /* Replies reuse the initiating message number. Requests and + * notifications allocate a new one. + */ + if (!reuse_msg_num) + msg_num = ++enic->mbox_msg_num; + if (expected_reply) { + reinit_completion(&enic->mbox_comp); + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_expected_reply = expected_reply; + enic->mbox_expected_msg_num = msg_num; + spin_unlock_bh(&enic->mbox_state_lock); + reply_expected = true; + } + + enic_mbox_fill_hdr(buf, msg_type, dst_vnic_id, total_len, msg_num); if (payload_len) { void *dst = buf + sizeof(struct enic_mbox_hdr); @@ -139,18 +156,66 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, "MBOX send msg_type %u dst %u vlan %u err %d\n", msg_type, dst_vnic_id, vlan_tag, err); unlock: + if (err && reply_expected) { + spin_lock_bh(&enic->mbox_state_lock); + if (enic->mbox_expected_reply == expected_reply && + enic->mbox_expected_msg_num == msg_num) { + enic->mbox_expected_reply = 0; + enic->mbox_expected_msg_num = 0; + } + spin_unlock_bh(&enic->mbox_state_lock); + } mutex_unlock(&enic->mbox_lock); return err; } +int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, + void *payload, u16 payload_len) +{ + return enic_mbox_send_msg_id(enic, msg_type, dst_vnic_id, payload, + payload_len, 0, false, 0); +} + +static int enic_mbox_send_reply(struct enic *enic, u8 msg_type, + u16 dst_vnic_id, void *payload, u16 payload_len, + u64 msg_num) +{ + return enic_mbox_send_msg_id(enic, msg_type, dst_vnic_id, payload, + payload_len, msg_num, true, 0); +} + +static int enic_mbox_vf_send_request(struct enic *enic, u8 request_type, + u8 expected_reply, void *payload, + u16 payload_len) +{ + return enic_mbox_send_msg_id(enic, request_type, ENIC_MBOX_DST_PF, + payload, payload_len, 0, false, + expected_reply); +} + static int enic_mbox_wait_reply(struct enic *enic, unsigned long timeout_ms) { unsigned long left; + int err = 0; left = wait_for_completion_timeout(&enic->mbox_comp, msecs_to_jiffies(timeout_ms)); + if (left) + return 0; - return left ? 0 : -ETIMEDOUT; + /* Invalidate a request that the handler has not already accepted. A + * delayed reply cannot match a later request because message numbers are + * monotonic across channel reopen. + */ + spin_lock_bh(&enic->mbox_state_lock); + if (enic->mbox_expected_reply) { + enic->mbox_expected_reply = 0; + enic->mbox_expected_msg_num = 0; + err = -ETIMEDOUT; + } + spin_unlock_bh(&enic->mbox_state_lock); + + return err; } int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state) @@ -178,8 +243,8 @@ static int enic_mbox_pf_handle_capability(struct enic *enic, void *msg, reply.reply.ret_major = cpu_to_le16(0); reply.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); - return enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REPLY, vf_id, - &reply, sizeof(reply)); + return enic_mbox_send_reply(enic, ENIC_MBOX_VF_CAPABILITY_REPLY, vf_id, + &reply, sizeof(reply), msg_num); } static int enic_mbox_pf_handle_register(struct enic *enic, void *msg, @@ -208,8 +273,8 @@ static int enic_mbox_pf_handle_register(struct enic *enic, void *msg, } reply.reply.ret_major = cpu_to_le16(0); - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REPLY, vf_id, - &reply, sizeof(reply)); + err = enic_mbox_send_reply(enic, ENIC_MBOX_VF_REGISTER_REPLY, vf_id, + &reply, sizeof(reply), msg_num); if (err) return err; @@ -253,8 +318,8 @@ static int enic_mbox_pf_handle_unregister(struct enic *enic, void *msg, enic->vf_state[vf_id].registered = false; reply.reply.ret_major = cpu_to_le16(0); - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REPLY, vf_id, - &reply, sizeof(reply)); + err = enic_mbox_send_reply(enic, ENIC_MBOX_VF_UNREGISTER_REPLY, vf_id, + &reply, sizeof(reply), msg_num); if (net_ratelimit()) netdev_info(enic->netdev, @@ -324,75 +389,57 @@ static void enic_mbox_pf_process_msg(struct enic *enic, hdr->msg_type, vf_id, err); } -static void enic_mbox_vf_handle_capability_reply(struct enic *enic, - void *payload) +static void enic_mbox_vf_handle_reply(struct enic *enic, u8 reply_type, + void *payload, u64 msg_num) { - struct enic_mbox_vf_capability_reply_msg *reply = payload; + struct enic_mbox_generic_reply *reply = payload; + u16 ret_major = le16_to_cpu(reply->ret_major); + u64 expected_msg_num; + u8 expected_type; - if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_CAPABILITY_REPLY) { + spin_lock_bh(&enic->mbox_state_lock); + expected_type = enic->mbox_expected_reply; + expected_msg_num = enic->mbox_expected_msg_num; + if (expected_type != reply_type || expected_msg_num != msg_num) { + spin_unlock_bh(&enic->mbox_state_lock); netdev_warn(enic->netdev, - "MBOX: stale capability reply (expected %u), drop\n", - READ_ONCE(enic->mbox_expected_reply)); + "MBOX: stale reply %u/%llu (expected %u/%llu), drop\n", + reply_type, (unsigned long long)msg_num, + expected_type, (unsigned long long)expected_msg_num); return; } - if (le16_to_cpu(reply->reply.ret_major) == 0) - enic->pf_cap_version = le32_to_cpu(reply->version); - else - netdev_warn(enic->netdev, - "MBOX: PF rejected capability request: %u/%u\n", - le16_to_cpu(reply->reply.ret_major), - le16_to_cpu(reply->reply.ret_minor)); + if (!ret_major) { + switch (reply_type) { + case ENIC_MBOX_VF_CAPABILITY_REPLY: { + struct enic_mbox_vf_capability_reply_msg *cap = payload; + + WRITE_ONCE(enic->pf_cap_version, + le32_to_cpu(cap->version)); + break; + } + case ENIC_MBOX_VF_REGISTER_REPLY: + WRITE_ONCE(enic->vf_registered, true); + break; + case ENIC_MBOX_VF_UNREGISTER_REPLY: + WRITE_ONCE(enic->vf_registered, false); + break; + } + } + enic->mbox_expected_reply = 0; + enic->mbox_expected_msg_num = 0; complete(&enic->mbox_comp); + spin_unlock_bh(&enic->mbox_state_lock); + + if (ret_major) + netdev_warn(enic->netdev, + "MBOX: PF rejected reply type %u: %u/%u\n", + reply_type, ret_major, + le16_to_cpu(reply->ret_minor)); } -static void enic_mbox_vf_handle_register_reply(struct enic *enic, - void *payload) -{ - struct enic_mbox_vf_register_reply_msg *reply = payload; - - if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_REGISTER_REPLY) { - netdev_warn(enic->netdev, - "MBOX: stale register reply (expected %u), drop\n", - READ_ONCE(enic->mbox_expected_reply)); - return; - } - - if (le16_to_cpu(reply->reply.ret_major)) { - netdev_warn(enic->netdev, - "MBOX: VF register rejected by PF: %u/%u\n", - le16_to_cpu(reply->reply.ret_major), - le16_to_cpu(reply->reply.ret_minor)); - } else { - enic->vf_registered = true; - } - complete(&enic->mbox_comp); -} - -static void enic_mbox_vf_handle_unregister_reply(struct enic *enic, - void *payload) -{ - struct enic_mbox_vf_register_reply_msg *reply = payload; - - if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_UNREGISTER_REPLY) { - netdev_warn(enic->netdev, - "MBOX: stale unregister reply (expected %u), drop\n", - READ_ONCE(enic->mbox_expected_reply)); - return; - } - - if (le16_to_cpu(reply->reply.ret_major)) { - netdev_warn(enic->netdev, - "MBOX: VF unregister rejected by PF: %u/%u\n", - le16_to_cpu(reply->reply.ret_major), - le16_to_cpu(reply->reply.ret_minor)); - } else { - enic->vf_registered = false; - } - complete(&enic->mbox_comp); -} - -static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) +static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload, + u64 msg_num) { struct enic_mbox_pf_link_state_notif_msg *notif = payload; struct enic_mbox_pf_link_state_ack_msg ack = {}; @@ -423,8 +470,8 @@ static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) } spin_unlock_bh(&enic->vf_link_state_lock); - err = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK, - ENIC_MBOX_DST_PF, &ack, sizeof(ack)); + err = enic_mbox_send_reply(enic, ENIC_MBOX_PF_LINK_STATE_ACK, + ENIC_MBOX_DST_PF, &ack, sizeof(ack), msg_num); if (err && net_ratelimit()) netdev_warn(enic->netdev, "MBOX: failed to send link state ACK: %d\n", err); @@ -468,6 +515,8 @@ static void enic_mbox_vf_process_msg(struct enic *enic, struct enic_mbox_hdr *hdr, void *payload, u16 payload_len) { + u64 msg_num = le64_to_cpu(hdr->msg_num); + switch (hdr->msg_type) { case ENIC_MBOX_VF_CAPABILITY_REPLY: { size_t exp = sizeof(struct enic_mbox_vf_capability_reply_msg); @@ -475,7 +524,7 @@ static void enic_mbox_vf_process_msg(struct enic *enic, if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, payload_len, exp)) return; - enic_mbox_vf_handle_capability_reply(enic, payload); + enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } case ENIC_MBOX_VF_REGISTER_REPLY: { @@ -484,7 +533,7 @@ static void enic_mbox_vf_process_msg(struct enic *enic, if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, payload_len, exp)) return; - enic_mbox_vf_handle_register_reply(enic, payload); + enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } case ENIC_MBOX_VF_UNREGISTER_REPLY: { @@ -493,7 +542,7 @@ static void enic_mbox_vf_process_msg(struct enic *enic, if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, payload_len, exp)) return; - enic_mbox_vf_handle_unregister_reply(enic, payload); + enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } case ENIC_MBOX_PF_LINK_STATE_NOTIF: { @@ -502,7 +551,7 @@ static void enic_mbox_vf_process_msg(struct enic *enic, if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, payload_len, exp)) return; - enic_mbox_vf_handle_link_state(enic, payload); + enic_mbox_vf_handle_link_state(enic, payload, msg_num); break; } default: @@ -571,32 +620,31 @@ static void enic_mbox_recv_handler(struct enic *enic, void *buf, int enic_mbox_vf_capability_check(struct enic *enic) { struct enic_mbox_vf_capability_msg req = {}; + u32 version; int err; - enic->pf_cap_version = 0; - reinit_completion(&enic->mbox_comp); - WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_CAPABILITY_REPLY); + WRITE_ONCE(enic->pf_cap_version, 0); req.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REQUEST, - ENIC_MBOX_DST_PF, &req, sizeof(req)); - if (err) { - WRITE_ONCE(enic->mbox_expected_reply, 0); + err = enic_mbox_vf_send_request(enic, + ENIC_MBOX_VF_CAPABILITY_REQUEST, + ENIC_MBOX_VF_CAPABILITY_REPLY, + &req, sizeof(req)); + if (err) return err; - } err = enic_mbox_wait_reply(enic, 3000); - WRITE_ONCE(enic->mbox_expected_reply, 0); + version = READ_ONCE(enic->pf_cap_version); if (err) { netdev_warn(enic->netdev, "MBOX: no capability reply from PF\n"); return err; } - if (enic->pf_cap_version < ENIC_MBOX_CAP_VERSION_1) { + if (version < ENIC_MBOX_CAP_VERSION_1) { netdev_warn(enic->netdev, "MBOX: PF rejected capability request or reported unsupported version %u\n", - enic->pf_cap_version); + version); return -EOPNOTSUPP; } @@ -605,28 +653,25 @@ int enic_mbox_vf_capability_check(struct enic *enic) int enic_mbox_vf_register(struct enic *enic) { + bool registered; int err; - enic->vf_registered = false; - reinit_completion(&enic->mbox_comp); - WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_REGISTER_REPLY); + WRITE_ONCE(enic->vf_registered, false); - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REQUEST, - ENIC_MBOX_DST_PF, NULL, 0); - if (err) { - WRITE_ONCE(enic->mbox_expected_reply, 0); + err = enic_mbox_vf_send_request(enic, ENIC_MBOX_VF_REGISTER_REQUEST, + ENIC_MBOX_VF_REGISTER_REPLY, NULL, 0); + if (err) return err; - } err = enic_mbox_wait_reply(enic, 3000); - WRITE_ONCE(enic->mbox_expected_reply, 0); + registered = READ_ONCE(enic->vf_registered); if (err) { netdev_warn(enic->netdev, "MBOX: VF registration with PF timed out\n"); return err; } - if (!enic->vf_registered) + if (!registered) return -ENODEV; return 0; @@ -634,43 +679,48 @@ int enic_mbox_vf_register(struct enic *enic) int enic_mbox_vf_unregister(struct enic *enic) { + bool registered; int err; - if (!enic->vf_registered) + if (!READ_ONCE(enic->vf_registered)) return 0; - reinit_completion(&enic->mbox_comp); - WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_UNREGISTER_REPLY); - - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REQUEST, - ENIC_MBOX_DST_PF, NULL, 0); - if (err) { - WRITE_ONCE(enic->mbox_expected_reply, 0); - return err; - } - - err = enic_mbox_wait_reply(enic, 3000); - WRITE_ONCE(enic->mbox_expected_reply, 0); + err = enic_mbox_vf_send_request(enic, + ENIC_MBOX_VF_UNREGISTER_REQUEST, + ENIC_MBOX_VF_UNREGISTER_REPLY, + NULL, 0); if (err) return err; - if (enic->vf_registered) + + err = enic_mbox_wait_reply(enic, 3000); + registered = READ_ONCE(enic->vf_registered); + if (err) + return err; + if (registered) return -EACCES; return 0; } void enic_mbox_init(struct enic *enic) { - /* mbox_lock and mbox_comp must be initialized exactly once per + bool reinit = enic->mbox_initialized; + + /* MBOX locks and mbox_comp must be initialized exactly once per * device lifetime; the PF sriov_configure path can re-enter this * on each enable cycle where these primitives are already set up. */ - if (!enic->mbox_initialized) { + if (!reinit) { mutex_init(&enic->mbox_lock); init_completion(&enic->mbox_comp); + spin_lock_init(&enic->mbox_state_lock); + enic->mbox_msg_num = 0; enic->mbox_initialized = true; } else { reinit_completion(&enic->mbox_comp); } - enic->mbox_msg_num = 0; + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_expected_reply = 0; + enic->mbox_expected_msg_num = 0; + spin_unlock_bh(&enic->mbox_state_lock); enic->admin_rq_handler = enic_mbox_recv_handler; } From 5ab54837fce04a1c9923d0bfd3d5de51fdc768b3 Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Thu, 3 Sep 2026 13:40:48 +0530 Subject: [PATCH 0224/1317] fs: autofs: fix memory leak in autofs_fill_super() In autofs_fill_super(), we create a new inode using autofs_new_ino(), however, if we fail to create root_inode, (that is, root_inode failure path), we return -ENOMEM without freeing the new inode(ino) that we created causing a memory leak. Fix this by adding autofs_free_ino() to free the inode we created in root_inode failure path before returning ENOMEM. Reported-by: syzbot+df1db6e034b3953e19f5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=df1db6e034b3953e19f5 Fixes: 66917f85db60 ("autofs: add: new_inode check in autofs_fill_super()") Cc: stable@vger.kernel.org Signed-off-by: Jeffin Philip Link: https://patch.msgid.link/20260903081048.132524-1-jeffinphilip14@gmail.com Signed-off-by: Ian Kent Signed-off-by: Christian Brauner (Amutable) --- fs/autofs/inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/autofs/inode.c b/fs/autofs/inode.c index c1e210cec436..6b15a3717ba7 100644 --- a/fs/autofs/inode.c +++ b/fs/autofs/inode.c @@ -323,8 +323,10 @@ static int autofs_fill_super(struct super_block *s, struct fs_context *fc) return -ENOMEM; root_inode = autofs_get_inode(s, S_IFDIR | 0755); - if (!root_inode) + if (!root_inode) { + autofs_free_ino(ino); return -ENOMEM; + } root_inode->i_uid = ctx->uid; root_inode->i_gid = ctx->gid; From af72b5946d493cecced27d0951ea37c1d178601e Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 27 Aug 2026 15:43:02 +1000 Subject: [PATCH 0225/1317] wifi: mac80211: include TIM bitmap control for buffered S1G mcast traffic Currently when building the S1G TIM element, we only build the bitmap control if we have buffered unicast traffic. Since AID 0 sits within the bitmap control if we have buffered multicast traffic with no buffered unicast traffic the bitmap control won't be emitted and dozing stations will be unaware of buffered multicast. To fix, only exclude the bitmap control byte when we don't have both buffered unicast and multicast traffic. Fixes: ee6360945483 ("wifi: mac80211: support block bitmap S1G TIM encoding") Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260827054302.254124-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3a1e2c9e1565..3896c7b2c4e5 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -5089,10 +5089,18 @@ static void ieee80211_beacon_add_tim_pvb(struct ps_data *ps, */ static void ieee80211_s1g_beacon_add_tim_pvb(struct ps_data *ps, struct sk_buff *skb, - bool mcast_traffic) + bool mcast_traffic, + bool ucast_traffic) { int blk; + /* + * if no unicast and multicast traffic don't emit a bitmap control + * or pvb + */ + if (!mcast_traffic && !ucast_traffic) + return; + /* * Emit a bitmap control block with a page slice number of 31 and a * page index of 0 which indicates as per IEEE80211-2024 9.4.2.5.1 @@ -5101,6 +5109,10 @@ static void ieee80211_s1g_beacon_add_tim_pvb(struct ps_data *ps, */ skb_put_u8(skb, mcast_traffic | (31 << 1)); + /* If there's no unicast traffic we don't need to include a PVB. */ + if (!ucast_traffic) + return; + /* Emit an encoded block for each non-zero sub-block */ for (blk = 0; blk < IEEE80211_MAX_SUPPORTED_S1G_TIM_BLOCKS; blk++) { u8 blk_bmap = 0; @@ -5182,25 +5194,16 @@ static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, ps->dtim_bc_mc = mcast_traffic; - if (have_bits) { - if (s1g) - ieee80211_s1g_beacon_add_tim_pvb(ps, skb, - mcast_traffic); - else - ieee80211_beacon_add_tim_pvb(ps, skb, mcast_traffic); + if (s1g) { + ieee80211_s1g_beacon_add_tim_pvb(ps, skb, mcast_traffic, + have_bits); + } else if (have_bits) { + ieee80211_beacon_add_tim_pvb(ps, skb, mcast_traffic); } else { - /* - * If there is no buffered unicast traffic for an S1G - * interface, we can exclude the bitmap control. This is in - * contrast to other phy types as they do include the bitmap - * control and pvb even when there is no buffered traffic. - */ - if (!s1g) { - /* Bitmap control */ - skb_put_u8(skb, mcast_traffic); - /* Part Virt Bitmap */ - skb_put_u8(skb, 0); - } + /* Bitmap control */ + skb_put_u8(skb, mcast_traffic); + /* Part Virt Bitmap */ + skb_put_u8(skb, 0); } tim->datalen = skb_tail_pointer(skb) - tim->data; From e6031f02269c0f51cf67886d177f02bc300b47cd Mon Sep 17 00:00:00 2001 From: Ivan Pustogarov Date: Thu, 3 Sep 2026 17:26:16 +0200 Subject: [PATCH 0226/1317] wifi: mac80211: avoid out-of-bounds read for empty PREQ elements ieee80211_mesh_preq_size_ok() derives the location of the PREQ bottom fields before checking whether the element contains even the fixed header. ieee80211_mesh_hwmp_preq_get_bottom() reads the flags byte to account for the optional Address Extension field. Consequently, an empty PREQ element causes a one-byte read beyond its declared payload. Move the helper call after both size checks, so the bottom fields are only accessed when they are present. Fixes: 8b40b1d24a60 ("wifi: mac80211: Fix overread in PREQ frame processing") Cc: stable@vger.kernel.org Signed-off-by: Ivan Pustogarov Link: https://patch.msgid.link/20260903152616.1646637-1-ivan@ipust.net Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index 7eb15834531c..9e548b9173df 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -361,8 +361,7 @@ ieee80211_mesh_hwmp_perr_get_rcode(const u8 *ie, u8 dst_idx) /* IEEE Std 802.11-2016 9.4.2.113 PREQ element */ static inline bool ieee80211_mesh_preq_size_ok(const u8 *pos, u8 elen) { - struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom = - ieee80211_mesh_hwmp_preq_get_bottom(pos); + struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom; u8 target_count; int needed; @@ -378,6 +377,7 @@ static inline bool ieee80211_mesh_preq_size_ok(const u8 *pos, u8 elen) if (elen < needed) return false; + preq_elem_bottom = ieee80211_mesh_hwmp_preq_get_bottom(pos); target_count = preq_elem_bottom->target_count; /* IEEE Std 802.11-2016 Table 14-10 to 14-16 */ if (target_count < 1) From b5526b780f8b297a76030410b96ba29153afb98f Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Thu, 20 Aug 2026 11:30:59 +0200 Subject: [PATCH 0227/1317] wifi: iwlegacy: fix broadcast stations deallocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the error path of __il4965_up(), il_dealloc_bcast_stations() clears only IL_STA_UCODE_ACTIVE, leaving IL_STA_BCAST set. This causes the same broadcast stations to be deallocated again by __il4965_down(). This can occur when RF_KILL is toggled during driver startup. To fix clear the entire 'used' field, since we will not do any other operations on the station. Reported-and-tested-by: Martin-Éric Racine Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221733 Fixes: c2fd34469d16 ("iwl4965: Fix a memory leak in error handling code of __il4965_up") Cc: # 7.1.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Cc: # 6.x.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Cc: # 5.x.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Signed-off-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260820093059.18779-1-stf_xl@wp.pl Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/iwlegacy/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlegacy/common.c b/drivers/net/wireless/intel/iwlegacy/common.c index 0bb807ff8edf..e5113c6b2d5c 100644 --- a/drivers/net/wireless/intel/iwlegacy/common.c +++ b/drivers/net/wireless/intel/iwlegacy/common.c @@ -2326,7 +2326,7 @@ il_dealloc_bcast_stations(struct il_priv *il) if (!(il->stations[i].used & IL_STA_BCAST)) continue; - il->stations[i].used &= ~IL_STA_UCODE_ACTIVE; + il->stations[i].used = 0; il->num_stations--; if (WARN_ON(il->num_stations < 0)) il->num_stations = 0; From 8a1f3cf89ddcc700e25afe42cfad333059adcc94 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 20 Aug 2026 20:51:26 +0800 Subject: [PATCH 0228/1317] wifi: wlcore: release runtime PM ref on regdomain config failure wlcore_regdomain_config() gets a runtime PM reference before sending the regulatory-domain command. When wlcore_cmd_regdomain_config_locked() fails, the function queues recovery and returns without dropping that reference. Release the reference after handling the command result so both success and failure paths balance the preceding pm_runtime_resume_and_get(). The recovery worker takes a separate runtime PM reference and cannot release the reference held here. Fixes: fa2648a34e73 ("wlcore: Add support for runtime PM") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260820125126.12757-1-runyu.xiao@seu.edu.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/ti/wlcore/main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index 5595f7a1fc0c..edf6ca23c6c3 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -3724,10 +3724,8 @@ void wlcore_regdomain_config(struct wl1271 *wl) goto out; ret = wlcore_cmd_regdomain_config_locked(wl); - if (ret < 0) { + if (ret < 0) wl12xx_queue_recovery_work(wl); - goto out; - } pm_runtime_put_autosuspend(wl->dev); out: From ba6cb7c0868a412c2eb68e8efd5aa38bfb258a14 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 7 Aug 2026 11:52:30 +0000 Subject: [PATCH 0229/1317] wifi: wilc1000: fix out-of-bounds read in P2P public action frames wilc_wfi_p2p_rx() and mgmt_tx() start parsing a frame once ieee80211_is_public_action() returns true. That helper only verifies the frame is long enough for the action category field, that is offsetofend(struct ieee80211_mgmt, u.action.category), 25 bytes. Both functions then read the P2P public action header up to oui_subtype at offset 30 and pass "size - ie_offset" to cfg80211_find_vendor_ie(), where ie_offset is offsetof(struct ieee80211_mgmt, u) + sizeof(*d), i.e. 32. A public action frame of 25 to 31 bytes passes the check but is shorter than that 32 byte header, so oui_subtype can be read out of bounds, and because the length is unsigned, "size - ie_offset" underflows to a value close to 4 GiB. cfg80211_find_vendor_ie() takes an unsigned int length, so even the size_t subtraction in mgmt_tx() is truncated to the same value. It then walks far past the buffer searching for a vendor element until it reaches unmapped memory. In the receive path the frame arrives over the air and needs no association, so a nearby unauthenticated device can crash the host while it is in P2P listen. Reject frames shorter than the P2P public action header in both paths before dereferencing it. Fixes: 4fb8b5aa2a11 ("staging: wilc1000: refactor p2p action frames handling API's") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260807115230.136767-1-ali@iusegentoo.com Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/cfg80211.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/wireless/microchip/wilc1000/cfg80211.c b/drivers/net/wireless/microchip/wilc1000/cfg80211.c index bb2748a19329..9ad21d41e999 100644 --- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c +++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c @@ -1058,6 +1058,13 @@ void wilc_wfi_p2p_rx(struct wilc_vif *vif, u8 *buff, u32 size) if (!ieee80211_is_public_action((struct ieee80211_hdr *)buff, size)) goto out_rx_mgmt; + /* ieee80211_is_public_action() only validates up to the category + * byte, so reject frames too short for the P2P public action header + * before dereferencing it or computing size - ie_offset. + */ + if (size < ie_offset) + goto out_rx_mgmt; + d = (struct wilc_p2p_pub_act_frame *)(&mgmt->u.action); if (d->oui_subtype != GO_NEG_REQ && d->oui_subtype != GO_NEG_RSP && d->oui_subtype != P2P_INV_REQ && d->oui_subtype != P2P_INV_RSP) @@ -1200,6 +1207,13 @@ static int mgmt_tx(struct wiphy *wiphy, goto out_set_timeout; } + /* ieee80211_is_public_action() only validates up to the category + * byte, so reject frames too short for the P2P public action header + * before dereferencing it or computing len - ie_offset. + */ + if (len < ie_offset) + goto out_set_timeout; + d = (struct wilc_p2p_pub_act_frame *)(&mgmt->u.action); if (d->oui_type != WLAN_OUI_TYPE_WFA_P2P || d->oui_subtype != GO_NEG_CONF) { From f9edf7cf63b96d2b776fca8d258d3c5256e40c8e Mon Sep 17 00:00:00 2001 From: Mariano Baragiola Date: Sun, 9 Aug 2026 09:49:47 -0300 Subject: [PATCH 0230/1317] wifi: virt_wifi: free skb when disconnected When the simulated link is disconnected, virt_wifi_start_xmit() returns NET_XMIT_DROP without freeing the skb. dev_hard_start_xmit() treats this return value as consumed, so every packet sent while disconnected leaks its skb. Free the skb before returning the drop status. Fixes: c7cdba31ed8b ("mac80211-next: rtnetlink wifi simulation device") Signed-off-by: Mariano Baragiola Link: https://patch.msgid.link/20260809124947.3590270-1-mbaragiola@linux.com Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/virt_wifi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/virtual/virt_wifi.c b/drivers/net/wireless/virtual/virt_wifi.c index 2335e45db8b8..b69a4650fba8 100644 --- a/drivers/net/wireless/virtual/virt_wifi.c +++ b/drivers/net/wireless/virtual/virt_wifi.c @@ -434,6 +434,7 @@ static netdev_tx_t virt_wifi_start_xmit(struct sk_buff *skb, priv->tx_packets++; if (!priv->is_connected) { priv->tx_failed++; + dev_kfree_skb_any(skb); return NET_XMIT_DROP; } From bbb9a0ab96d44a64529aafc7a16de460a1712f6a Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Sat, 15 Aug 2026 19:57:24 +0800 Subject: [PATCH 0231/1317] wifi: libertas_tf: fix UAF in lbtf_free_adapter() lbtf_free_adapter() calls lbtf_free_cmd_buffer() to free the command buffers before calling timer_delete_sync() to wait for the command timer callback. If the timer callback (command_timer_fn) is already running when lbtf_free_cmd_buffer() frees the command array, the callback dereferences priv->cur_cmd->cmdbuf which points to freed memory. Swap the order so that timer_delete_sync() runs first, ensuring any in-flight callback has completed before the command buffers are freed. Fixes: 06b16ae53192 ("libertas_tf: main.c, data paths and mac80211 handlers") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Link: https://patch.msgid.link/20260815115724.920628-1-yijiangshan@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index 42be6fa22f9c..411f075b6186 100644 --- a/drivers/net/wireless/marvell/libertas_tf/main.c +++ b/drivers/net/wireless/marvell/libertas_tf/main.c @@ -173,8 +173,8 @@ static int lbtf_init_adapter(struct lbtf_private *priv) static void lbtf_free_adapter(struct lbtf_private *priv) { lbtf_deb_enter(LBTF_DEB_MAIN); - lbtf_free_cmd_buffer(priv); timer_delete_sync(&priv->command_timer); + lbtf_free_cmd_buffer(priv); lbtf_deb_leave(LBTF_DEB_MAIN); } From a3d722190cdef18da4878b5efc27c3c386dda248 Mon Sep 17 00:00:00 2001 From: Peng Hao Date: Fri, 28 Aug 2026 19:15:31 +0800 Subject: [PATCH 0232/1317] wifi: mwifiex: fix IRQ leak using wrong index in MSI-X error path mwifiex_pcie_request_irq() registers each MSI-X vector with a per-index dev_id (&card->msix_ctx[i]). On a request_irq() failure the cleanup loop "for (j = 0; j < i; j++)" frees msix_entries[j].vector but passes the failed index's &card->msix_ctx[i] as the dev_id. free_irq() matches on (irq, dev_id), so it fails to find the action registered with &card->msix_ctx[j]: the already-requested IRQ j is not freed (leaked) and free_irq() warns about freeing a non-existent IRQ. Use &card->msix_ctx[j]. Fixes: 99074fc1e67b ("mwifiex: enable pcie MSIx interrupt mode support") Signed-off-by: Peng Hao Link: https://patch.msgid.link/20260828111531.56723-1-flyingpeng@tencent.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c index a760de191fce..a9425e9a94f4 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -3068,7 +3068,7 @@ static int mwifiex_pcie_request_irq(struct mwifiex_adapter *adapter) ret); for (j = 0; j < i; j++) free_irq(card->msix_entries[j].vector, - &card->msix_ctx[i]); + &card->msix_ctx[j]); pci_disable_msix(pdev); } else { mwifiex_dbg(adapter, MSG, "MSIx enabled!"); From fa00193eb991f92b007aefe7afb6a7566976dacf Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 14:21:55 +0800 Subject: [PATCH 0233/1317] wifi: mwifiex: prevent authentication frame length truncation mwifiex_cfg80211_authenticate() derives the authentication frame length from req->ie_len and req->auth_data_len, both of type size_t, but stores it in a u16. NL80211_ATTR_AUTH_DATA only has a minimum length policy. Since nla_len is a u16, a single attribute can carry up to 65531 bytes of payload, so the sum can exceed U16_MAX before it is assigned to pkt_len. The truncated pkt_len determines the skb frame area, while the copy length remains req->auth_data_len - 4, resulting in a heap buffer overflow. For example, with auth_data_len equal to 65510 and no IEs, the sum is 65546. It is truncated to 10 and then reduced by four to 6. The driver appends only six bytes to the skb with skb_put(), but then copies 65506 user-provided bytes into the authentication body. Reaching this path requires CAP_NET_ADMIN in the user namespace owning the network namespace, an up station netdev, and a suitable BSS/SAE authentication request. Compute the length in size_t, reject values that cannot be represented by the firmware's u16 frame length field, and only then assign it to pkt_len. Fixes: 36995892c271 ("wifi: mwifiex: add host mlme for client mode") Cc: stable@vger.kernel.org # 6.12+ Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260820062155.3981976-1-lilinmao@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index 7a1ba32f1fb3..936939ea9c47 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -4277,6 +4277,7 @@ mwifiex_cfg80211_authenticate(struct wiphy *wiphy, struct mwifiex_adapter *adapter = priv->adapter; struct sk_buff *skb; u16 pkt_len, auth_alg; + size_t frame_len; int ret; struct mwifiex_ieee80211_mgmt *mgmt; struct mwifiex_txinfo *tx_info; @@ -4349,10 +4350,17 @@ mwifiex_cfg80211_authenticate(struct wiphy *wiphy, mwifiex_cancel_scan(adapter); - pkt_len = (u16)req->ie_len + req->auth_data_len + + frame_len = req->ie_len + req->auth_data_len + MWIFIEX_MGMT_HEADER_LEN + MWIFIEX_AUTH_BODY_LEN; if (req->auth_data_len >= 4) - pkt_len -= 4; + frame_len -= 4; + + if (frame_len > U16_MAX) { + mwifiex_dbg(priv->adapter, ERROR, + "auth frame too long: %zu bytes\n", frame_len); + return -EINVAL; + } + pkt_len = frame_len; skb = dev_alloc_skb(MWIFIEX_MIN_DATA_HEADER_LEN + MWIFIEX_MGMT_FRAME_HEADER_SIZE + From e2de8d5eb2984416affdd9559e55f37c7f1bbf47 Mon Sep 17 00:00:00 2001 From: Bogdan Nicolae Date: Fri, 7 Aug 2026 11:34:18 -0500 Subject: [PATCH 0234/1317] wifi: brcmfmac: cyw: pass PMKID to firmware if present Zero out auth_status on initialization. Otherwise, garbage will leak from the stack to the firmware (when ssid is less than 32 bytes and/or when params->pmkid is set). Then, pass the params->pmkid to the firmware (without it, the firmware caches a garbage PMKID on successful authentication and denies a subsequent association request that includes the PMKID). Fixes: 66f909308a7c ("wifi: brcmfmac: cyw: support external SAE authentication in station mode") Signed-off-by: Bogdan Nicolae Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260807163418.487508-1-bogdan.nicolae@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c index 545eb9aae966..6d5098f6f00f 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c @@ -198,7 +198,7 @@ brcmf_cyw_external_auth(struct wiphy *wiphy, struct net_device *dev, { struct brcmf_if *ifp; struct brcmf_pub *drvr; - struct brcmf_auth_req_status_le auth_status; + struct brcmf_auth_req_status_le auth_status = {}; int ret = 0; brcmf_dbg(TRACE, "Enter\n"); @@ -206,6 +206,9 @@ brcmf_cyw_external_auth(struct wiphy *wiphy, struct net_device *dev, ifp = netdev_priv(dev); drvr = ifp->drvr; if (params->status == WLAN_STATUS_SUCCESS) { + if (params->pmkid) + memcpy(auth_status.pmkid, params->pmkid, + WLAN_PMKID_LEN); auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_SUCCESS); } else { bphy_err(drvr, "External authentication failed: status=%d\n", From e667aee1c192d67d27c803007bfa9c6e0873e959 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Fri, 14 Aug 2026 15:47:04 +0200 Subject: [PATCH 0235/1317] wifi: mwifiex: bound the pairwise-cipher OUI walk to the IE length mwifiex_search_oui_in_ie() reads a pairwise-cipher (PTK) count from a beacon/probe-response RSN or WPA information element and then walks that many 4-byte OUIs, comparing each with memcmp(). The count comes straight from the (attacker-supplied) IE and is never checked against the element's own length, and the callers admit the element on element_id alone (has_ieee_hdr() / has_vendor_hdr(), no length check). A crafted RSN/WPA IE with a large pairwise count therefore makes the walk read up to 255 * 4 bytes past the element -- an out-of-bounds read of the kmemdup()'d beacon buffer, reachable from any AP whose beacon/probe response is processed during scan-result parsing. Pass the number of IE bytes available at the OUI list and bound the walk to the element. Keep the length signed and reject a negative value before any unsigned arithmetic, so a small or zero IE length cannot underflow to a large size_t and defeat the bound. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 5e6e3a92b9a4 ("wireless: mwifiex: initial commit for Marvell mwifiex driver") Cc: stable@vger.kernel.org Assisted-by: 0sec:multi-model Signed-off-by: Doruk Tan Ozturk Link: https://patch.msgid.link/20260814134704.85902-1-doruk@0sec.ai Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/scan.c | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/scan.c b/drivers/net/wireless/marvell/mwifiex/scan.c index 97c0ec3b822e..473f4623ea19 100644 --- a/drivers/net/wireless/marvell/mwifiex/scan.c +++ b/drivers/net/wireless/marvell/mwifiex/scan.c @@ -104,12 +104,24 @@ has_vendor_hdr(struct ieee_types_vendor_specific *ie, u8 key) * a given oui in PTK. */ static u8 -mwifiex_search_oui_in_ie(struct ie_body *iebody, u8 *oui) +mwifiex_search_oui_in_ie(struct ie_body *iebody, u8 *oui, int ie_len) { + const size_t ptk_body_offset = offsetof(struct ie_body, ptk_body); u8 count; + /* ie_len is the number of bytes available at iebody. Keep it signed + * and reject a negative (underflowed) length before the unsigned + * comparisons below, so a small or zero IE length cannot wrap. + */ + if (ie_len < 0 || (size_t)ie_len < ptk_body_offset) + return MWIFIEX_OUI_NOT_PRESENT; + count = iebody->ptk_cnt[0]; + /* Reject an OUI count whose list would run past the element. */ + if (ptk_body_offset + count * sizeof(iebody->ptk_body) > (size_t)ie_len) + return MWIFIEX_OUI_NOT_PRESENT; + /* There could be multiple OUIs for PTK hence 1) Take the length. 2) Check all the OUIs for AES. @@ -143,11 +155,14 @@ mwifiex_is_rsn_oui_present(struct mwifiex_bssdescriptor *bss_desc, u32 cipher) u8 ret = MWIFIEX_OUI_NOT_PRESENT; if (has_ieee_hdr(bss_desc->bcn_rsn_ie, WLAN_EID_RSN)) { + int ie_len = (int)bss_desc->bcn_rsn_ie->ieee_hdr.len - + RSN_GTK_OUI_OFFSET; + iebody = (struct ie_body *) (((u8 *) bss_desc->bcn_rsn_ie->data) + RSN_GTK_OUI_OFFSET); oui = &mwifiex_rsn_oui[cipher][0]; - ret = mwifiex_search_oui_in_ie(iebody, oui); + ret = mwifiex_search_oui_in_ie(iebody, oui, ie_len); if (ret) return ret; } @@ -169,10 +184,14 @@ mwifiex_is_wpa_oui_present(struct mwifiex_bssdescriptor *bss_desc, u32 cipher) u8 ret = MWIFIEX_OUI_NOT_PRESENT; if (has_vendor_hdr(bss_desc->bcn_wpa_ie, WLAN_EID_VENDOR_SPECIFIC)) { + int ie_len = (int)bss_desc->bcn_wpa_ie->vend_hdr.len - + (int)sizeof(bss_desc->bcn_wpa_ie->vend_hdr.oui) - + WPA_GTK_OUI_OFFSET; + iebody = (struct ie_body *)((u8 *)bss_desc->bcn_wpa_ie->data + WPA_GTK_OUI_OFFSET); oui = &mwifiex_wpa_oui[cipher][0]; - ret = mwifiex_search_oui_in_ie(iebody, oui); + ret = mwifiex_search_oui_in_ie(iebody, oui, ie_len); if (ret) return ret; } From 1c25bfad93e69ce13f744a2fb919f02ea396a985 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Tue, 25 Aug 2026 19:25:23 +0800 Subject: [PATCH 0236/1317] wifi: mwifiex: validate action frame fixed fields mwifiex_process_mgmt_packet() accepts an rx_pkt_length as small as a four-address struct ieee80211_hdr plus the two-byte firmware length prefix. After stripping the prefix, mwifiex_parse_mgmt_packet() can receive a frame equal to sizeof(struct ieee80211_hdr). For action frames, the parser reads the category byte immediately after that header and, for a public action frame, reads the following action code byte without verifying that either field is present. A truncated frame can therefore make the parser consume up to two bytes past the firmware-declared frame length. If those bytes look like a TDLS discovery response, the malformed frame can spuriously update peer signal state. Require the category and public action-code fields before reading them. Use sizeof(*ieee_hdr) so the checks and field accesses directly match the firmware four-address layout being parsed before address4 is removed. Suggested-by: Johannes Berg Suggested-by: Brian Norris Fixes: 72e5aa8d2a6d ("mwifiex: support for parsing TDLS discovery frames") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/66f148d83eb9f0970b9abbccc85d1b61244e54ad.camel@sipsolutions.net/ Link: https://lore.kernel.org/all/20260708195911.84365-8-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/20260723011013.76968-1-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/20260723202257.688-1-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/anuWyiPQja6_5vly@google.com/ Assisted-by: Codex:gpt-5 Assisted-by: Kimi:K3 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260825112523.95774-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/util.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/util.c b/drivers/net/wireless/marvell/mwifiex/util.c index 7d3631d21223..71305efb77ac 100644 --- a/drivers/net/wireless/marvell/mwifiex/util.c +++ b/drivers/net/wireless/marvell/mwifiex/util.c @@ -317,10 +317,16 @@ mwifiex_parse_mgmt_packet(struct mwifiex_private *priv, u8 *payload, u16 len, switch (stype) { case IEEE80211_STYPE_ACTION: - category = *(payload + sizeof(struct ieee80211_hdr)); + if (len < sizeof(*ieee_hdr) + 1) + return -1; + + category = *(payload + sizeof(*ieee_hdr)); switch (category) { case WLAN_CATEGORY_PUBLIC: - action_code = *(payload + sizeof(struct ieee80211_hdr) + if (len < sizeof(*ieee_hdr) + 2) + return -1; + + action_code = *(payload + sizeof(*ieee_hdr) + 1); if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) { addr2 = ieee_hdr->addr2; From 3687d7d48070838cc2953431b3a27717cab0aaf6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 15 Aug 2026 21:52:27 +0800 Subject: [PATCH 0237/1317] wifi: mwifiex: validate scan response extents mwifiex_ret_802_11_scan() subtracts the fixed response fields and the firmware-provided BSS length from resp->size without first proving that either extent fits. A short response or oversized BSS length can therefore underflow tlv_buf_size and make the TLV parser walk beyond the command response. Compute the fixed extent from the selected normal or background scan response. Validate that the fixed fields and BSS data fit before deriving the TLV extent and entering the parser. Fixes: 5e6e3a92b9a4 ("wireless: mwifiex: initial commit for Marvell mwifiex driver") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260815135227.50392-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/scan.c | 29 ++++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/scan.c b/drivers/net/wireless/marvell/mwifiex/scan.c index 473f4623ea19..bdd4b8465863 100644 --- a/drivers/net/wireless/marvell/mwifiex/scan.c +++ b/drivers/net/wireless/marvell/mwifiex/scan.c @@ -2115,6 +2115,7 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, u32 bytes_left; u32 idx; u32 tlv_buf_size; + size_t fixed_size; struct mwifiex_ie_types_chan_band_list_param_set *chan_band_tlv; struct chan_band_param_set *chan_band; u8 is_bgscan_resp; @@ -2130,6 +2131,14 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, else scan_rsp = &resp->params.scan_resp; + scan_resp_size = le16_to_cpu(resp->size); + fixed_size = scan_rsp->bss_desc_and_tlv_buffer - (u8 *)resp; + if (scan_resp_size < fixed_size) { + mwifiex_dbg(adapter, ERROR, + "SCAN_RESP: response is too short\n"); + ret = -1; + goto check_next_scan; + } if (scan_rsp->number_of_sets > MWIFIEX_MAX_AP) { mwifiex_dbg(adapter, ERROR, @@ -2147,8 +2156,6 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, "info: SCAN_RESP: bss_descript_size %d\n", bytes_left); - scan_resp_size = le16_to_cpu(resp->size); - mwifiex_dbg(adapter, INFO, "info: SCAN_RESP: returned %d APs before parsing\n", scan_rsp->number_of_sets); @@ -2156,15 +2163,17 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, bss_info = scan_rsp->bss_desc_and_tlv_buffer; /* - * The size of the TLV buffer is equal to the entire command response - * size (scan_resp_size) minus the fixed fields (sizeof()'s), the - * BSS Descriptions (bss_descript_size as bytesLef) and the command - * response header (S_DS_GEN) + * The TLV buffer follows the command-specific fixed fields and the BSS + * descriptions. Background-scan responses have an additional fixed + * field before scan_rsp, which is included in fixed_size. */ - tlv_buf_size = scan_resp_size - (bytes_left - + sizeof(scan_rsp->bss_descript_size) - + sizeof(scan_rsp->number_of_sets) - + S_DS_GEN); + if (bytes_left > scan_resp_size - fixed_size) { + mwifiex_dbg(adapter, ERROR, + "SCAN_RESP: BSS data exceeds response\n"); + ret = -1; + goto check_next_scan; + } + tlv_buf_size = scan_resp_size - fixed_size - bytes_left; tlv_data = (struct mwifiex_ie_types_data *) (scan_rsp-> bss_desc_and_tlv_buffer + From 5ce5721e8cbe3e80db8f43851cc2a2a92485ef4b Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:10 +0300 Subject: [PATCH 0238/1317] wifi: libipw: reject too-short beacon and probe responses libipw_process_probe_response() and the libipw_network_init() call it makes assume the frame contains the full 36-byte beacon and probe response prefix, but the ipw2100 and ipw2200 receive paths only establish that a management frame carries the generic 24-byte three-address header. libipw_network_init() then computes the information element length as stats->len - sizeof(*beacon) stats->len is a u16 and sizeof() has type size_t, so the subtraction is evaluated as size_t and wraps instead of going negative. Truncating that to the u16 length parameter of libipw_parse_info_param() yields 65524 for a 24-byte beacon, and the parser then walks the receive buffer as if it held almost 64 KiB of information elements, reading past the allocation. Reject the frame before any fixed field is touched. Found by an AI-assisted review of length arithmetic in management frame parsers. Verified with a KUnit case under Generic KASAN on arm64 under QEMU; I do not have the hardware, so it is not tested on a real device. Fixes: b453872c35cf ("[NET] ieee80211 subsystem") Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-2-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c index c8841f9b9ad9..2661dac6985e 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c @@ -1421,6 +1421,9 @@ static void libipw_process_probe_response(struct libipw_device #endif unsigned long flags; + if (stats->len < sizeof(*beacon)) + return; + LIBIPW_DEBUG_SCAN("'%*pE' (%pM): %c%c%c%c %c%c%c%c-%c%c%c%c %c%c%c%c\n", info_element->len, info_element->data, beacon->header.addr3, From adb7118b7d2cfd7e8213c17d7d2829f353017754 Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:11 +0300 Subject: [PATCH 0239/1317] wifi: libipw: reject too-short association responses libipw_handle_assoc_resp() reads the capability, status and aid fields of the 30-byte association response prefix and then computes the information element length as stats->len - sizeof(*frame) stats->len is a u16 and sizeof() has type size_t, so the subtraction is evaluated as size_t and wraps instead of going negative. Truncating that to the u16 length parameter of libipw_parse_info_param() turns a frame shorter than the fixed fields into a length near 64 KiB, and the parser then reads past the receive buffer. Both the ipw2100 and ipw2200 management receive paths reach this function having established only that the frame carries the generic 24-byte three-address header. Reject the frame before any fixed field is touched. Found by an AI-assisted review of length arithmetic in management frame parsers. Verified with a KUnit case under Generic KASAN on arm64 under QEMU; I do not have the hardware, so it is not tested on a real device. Fixes: 9e8571affd1c ("[PATCH] ieee80211: Add QoS (WME) support to the ieee80211 subsystem") Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-3-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c index 2661dac6985e..424349a6935e 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c @@ -1209,6 +1209,9 @@ static int libipw_handle_assoc_resp(struct libipw_device *ieee, struct libipw_as struct libipw_network *network = &network_resp; struct net_device *dev = ieee->dev; + if (stats->len < sizeof(*frame)) + return 1; + network->flags = 0; network->qos_data.active = 0; network->qos_data.supported = 0; From c46cfaf8db42de0806076139fb40744d23041377 Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:12 +0300 Subject: [PATCH 0240/1317] wifi: ipw2x00: bound management frame length to the receive buffer Both management receive paths establish a lower bound on the frame length and no upper bound, even though the length originates from the device. ipw2100_corruption_check() returns 0 without inspecting frame_size for management frames, and __ipw2100_rx_process() only rejects a frame smaller than the three-address header, so any reported size up to the u32 limit reaches libipw_rx_mgt() against a receive allocation of IPW_RX_NIC_BUFFER_LENGTH bytes. Check frame_size itself rather than stats.len, which is a u16: a size of 65566 truncates to 30 on assignment and would pass a check made afterwards. ipw_rx() likewise only rejects a frame shorter than the header length. Bound it against the DMA mapped receive buffer. The size passed to alloc_skb() is rounded up by the allocator, so skb_tailroom() can exceed IPW_RX_BUF_SIZE and is not a usable bound here; the existing uses of that idiom in the data paths are too permissive for the same reason. libipw then hands the remainder to libipw_parse_info_param(), which walks information elements for as long as the length allows, so an over-long reported length reads past the receive buffer without any wraparound being involved. The length is device-reported, so per Documentation/process/threat-model.rst this is a robustness fix rather than a vulnerability. Found by an AI-assisted review of length arithmetic in management frame parsers. Compile-tested only for these two hunks; I do not have the hardware, so they are not tested on a real device. Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-4-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/ipw2100.c | 4 +++- drivers/net/wireless/intel/ipw2x00/ipw2200.c | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2100.c b/drivers/net/wireless/intel/ipw2x00/ipw2100.c index 2b8a23865bfb..43b4e432956b 100644 --- a/drivers/net/wireless/intel/ipw2x00/ipw2100.c +++ b/drivers/net/wireless/intel/ipw2x00/ipw2100.c @@ -2712,7 +2712,9 @@ static void __ipw2100_rx_process(struct ipw2100_priv *priv) break; } #endif - if (stats.len < sizeof(struct libipw_hdr_3addr)) + if (sq->drv[i].frame_size < + sizeof(struct libipw_hdr_3addr) || + sq->drv[i].frame_size > IPW_RX_NIC_BUFFER_LENGTH) break; switch (WLAN_FC_GET_TYPE(le16_to_cpu(u->rx_data.header.frame_ctl))) { case IEEE80211_FTYPE_MGMT: diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2200.c b/drivers/net/wireless/intel/ipw2x00/ipw2200.c index 4bc9bb406e8e..8249d493ee22 100644 --- a/drivers/net/wireless/intel/ipw2x00/ipw2200.c +++ b/drivers/net/wireless/intel/ipw2x00/ipw2200.c @@ -8322,6 +8322,15 @@ static void ipw_rx(struct ipw_priv *priv) break; } + if (unlikely(le16_to_cpu(pkt->u.frame.length) > + IPW_RX_BUF_SIZE - + IPW_RX_FRAME_SIZE)) { + IPW_DEBUG_DROP("Received oversized packet. Dropping.\n"); + priv->net_dev->stats.rx_errors++; + priv->wstats.discard.misc++; + break; + } + switch (WLAN_FC_GET_TYPE (le16_to_cpu(header->frame_ctl))) { From ce858fa6b8a214dee5adb82358885fa024cdd887 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Mon, 31 Aug 2026 02:42:12 +0800 Subject: [PATCH 0241/1317] wifi: p54: validate curve data length in the calibration curve converters p54_convert_rev0() and p54_convert_rev1() read calibration curve data from the device-supplied EEPROM entry using channel and points-per-channel counts taken verbatim from that same entry, so an entry that declares more data than it carries drives an out-of-bounds read past the EEPROM buffer (verified with a KASAN reproducer of the conversion loop). The sibling converters p54_convert_output_limits() and p54_convert_db() already validate their counts against the entry length; this path was missed. Reject the entry when the counts do not fit in the entry data. Fixes: eff1a59c48e3 ("[P54]: add mac80211-based driver for prism54 softmac hardware") Cc: stable@vger.kernel.org Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Link: https://patch.msgid.link/20260831-p54-pda-validation-v2-1-dae566b388c8@cherr.cc Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/eeprom.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c index 95580921d933..0dc848d77c5e 100644 --- a/drivers/net/wireless/intersil/p54/eeprom.c +++ b/drivers/net/wireless/intersil/p54/eeprom.c @@ -414,17 +414,22 @@ static int p54_generate_channel_lists(struct ieee80211_hw *dev) } static int p54_convert_rev0(struct ieee80211_hw *dev, - struct pda_pa_curve_data *curve_data) + struct pda_pa_curve_data *curve_data, size_t len) { struct p54_common *priv = dev->priv; struct p54_pa_curve_data_sample *dst; struct pda_pa_curve_data_sample_rev0 *src; + size_t needed = curve_data->channels * + (sizeof(*src) * curve_data->points_per_channel + 2); size_t cd_len = sizeof(*curve_data) + (curve_data->points_per_channel*sizeof(*dst) + 2) * curve_data->channels; unsigned int i, j; void *source, *target; + if (len < sizeof(*curve_data) + needed) + return -EINVAL; + priv->curve_data = kmalloc(sizeof(*priv->curve_data) + cd_len, GFP_KERNEL); if (!priv->curve_data) @@ -466,17 +471,22 @@ static int p54_convert_rev0(struct ieee80211_hw *dev, } static int p54_convert_rev1(struct ieee80211_hw *dev, - struct pda_pa_curve_data *curve_data) + struct pda_pa_curve_data *curve_data, size_t len) { struct p54_common *priv = dev->priv; struct p54_pa_curve_data_sample *dst; struct pda_pa_curve_data_sample_rev1 *src; + size_t needed = curve_data->channels * + (sizeof(*src) * curve_data->points_per_channel + 3); size_t cd_len = sizeof(*curve_data) + (curve_data->points_per_channel*sizeof(*dst) + 2) * curve_data->channels; unsigned int i, j; void *source, *target; + if (len < sizeof(*curve_data) + needed) + return -EINVAL; + priv->curve_data = kzalloc(cd_len + sizeof(*priv->curve_data), GFP_KERNEL); if (!priv->curve_data) @@ -763,6 +773,7 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) case PDR_PRISM_PA_CAL_CURVE_DATA: { struct pda_pa_curve_data *curve_data = (struct pda_pa_curve_data *)entry->data; + if (data_len < sizeof(*curve_data)) { err = -EINVAL; goto err; @@ -770,10 +781,10 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) switch (curve_data->cal_method_rev) { case 0: - err = p54_convert_rev0(dev, curve_data); + err = p54_convert_rev0(dev, curve_data, data_len); break; case 1: - err = p54_convert_rev1(dev, curve_data); + err = p54_convert_rev1(dev, curve_data, data_len); break; default: wiphy_err(dev->wiphy, From d8efd84f49379ed28624098821f80e992657d935 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Mon, 31 Aug 2026 02:42:13 +0800 Subject: [PATCH 0242/1317] wifi: p54: require a full exp_if record in PDR_INTERFACE_LIST The PDR_INTERFACE_LIST loop only checks that the record start is within the entry before reading an entire struct exp_if from it. A truncated trailing record makes the if_id/variant reads cross the entry boundary into the heap beyond the EEPROM buffer (verified with a KASAN reproducer of the loop). The variant also feeds the synth front-end selection, so this is not only a leak. Advance only while a full record still fits in the entry. Fixes: eff1a59c48e3 ("[P54]: add mac80211-based driver for prism54 softmac hardware") Cc: stable@vger.kernel.org Acked-by: Christian Lamparter Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Link: https://patch.msgid.link/20260831-p54-pda-validation-v2-2-dae566b388c8@cherr.cc Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/eeprom.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c index 0dc848d77c5e..0475222d54fc 100644 --- a/drivers/net/wireless/intersil/p54/eeprom.c +++ b/drivers/net/wireless/intersil/p54/eeprom.c @@ -812,7 +812,8 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) break; case PDR_INTERFACE_LIST: tmp = entry->data; - while ((u8 *)tmp < entry->data + data_len) { + while ((u8 *)tmp + sizeof(struct exp_if) <= + entry->data + data_len) { struct exp_if *exp_if = tmp; if (exp_if->if_id == cpu_to_le16(IF_ID_ISL39000)) synth = le16_to_cpu(exp_if->variant); From 6feadbecdae60a6324c967f3b1493741083793a3 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 6 Aug 2026 13:02:33 +0000 Subject: [PATCH 0243/1317] mmc: core: Cancel SDIO IRQ work before freeing host A host controller that uses sdio_signal_irq() schedules host->sdio_irq_work from its interrupt handler. That work is only cancelled on the suspend path (mmc_sdio_suspend()), not on the remove/free path, so a worker armed just before the controller freed its IRQ can run after mmc_host_classdev_release() has freed the host and dereference it through container_of(). Cancel host->sdio_irq_work in mmc_free_host(), like the existing host->detect drain added by commit 1036f69e2513 ("mmc: core: Cancel delayed work before releasing host"). This issue was found by an in-house static analysis tool. Fixes: 682696605c70 ("mmc: sdio: Add API to manage SDIO IRQs from a workqueue") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/core/host.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index b7ce3137d452..828078b3f962 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -698,6 +698,7 @@ EXPORT_SYMBOL(mmc_remove_host); void mmc_free_host(struct mmc_host *host) { cancel_delayed_work_sync(&host->detect); + cancel_work_sync(&host->sdio_irq_work); mmc_pwrseq_free(host); put_device(&host->class_dev); } From 2b19cf3e50cddaff07b657dae1a8f30f06032852 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 14 Aug 2026 08:25:50 +0000 Subject: [PATCH 0244/1317] mmc: mmci: Fix use-after-free in busy-timeout work ux500_busy_complete() can queue ux500_busy_timeout_work for an R1b command, but mmci_remove() never cancels it. The work can subsequently dereference the devm-allocated mmci_host after it has been released. Mask the controller interrupts and disable the delayed work during removal. This drains any queued instance and stops an IRQ handler that is still in progress from queueing the work again once it has been disabled. This issue was found by an in-house static analysis tool. Fixes: b1a665932dc2 ("mmc: mmci: Add support for SW busy-end timeouts") Cc: stable@vger.kernel.org # v6.10+ Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index e500051bd572..416bdb184ed4 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -2511,6 +2511,9 @@ static void mmci_remove(struct amba_device *dev) writel(0, host->base + MMCICOMMAND); writel(0, host->base + MMCIDATACTRL); + if (variant->busy_detect) + disable_delayed_work_sync(&host->ux500_busy_timeout_work); + mmci_dma_release(host); clk_disable_unprepare(host->clk); } From d3a421c82412344022982d5b91ba23194a0a6f29 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 7 Aug 2026 03:26:54 +0000 Subject: [PATCH 0245/1317] mmc: mxcmmc: cancel data work and watchdog on remove mxcmci_remove() frees the host through the devm tail, but neither it nor mmc_remove_host() drains the driver's own asynchronous state. host->watchdog, a 10 s timer armed on the DMA path in mxcmci_setup_data(), is deleted only by the DMA- and IRQ-complete paths, which the remove path does not explicitly drain; it can therefore fire after the host is freed and dereference it in mxcmci_watchdog(). host->datawork, armed from the IRQ handler on the PIO path, is not cancelled by the remove path either. Free the devm-registered IRQ, then cancel datawork and delete the watchdog in mxcmci_remove(), before dma_release_channel(). Freeing the IRQ first keeps a trailing handler from re-arming datawork between the cancel and the host free. Both callbacks are non-self-rearming. This issue was found by an in-house static analysis tool. Fixes: f6ad0a481342 ("mmc: mxcmmc: fix bug that may block a data transfer forever") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/host/mxcmmc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/host/mxcmmc.c b/drivers/mmc/host/mxcmmc.c index c405cfb8b269..097498a3f8ff 100644 --- a/drivers/mmc/host/mxcmmc.c +++ b/drivers/mmc/host/mxcmmc.c @@ -1173,6 +1173,10 @@ static void mxcmci_remove(struct platform_device *pdev) mmc_remove_host(mmc); + devm_free_irq(&pdev->dev, platform_get_irq(pdev, 0), host); + cancel_work_sync(&host->datawork); + timer_delete_sync(&host->watchdog); + if (host->pdata && host->pdata->exit) host->pdata->exit(&pdev->dev, mmc); From 5d132990475f02cfa1debe03d50b479432864ebd Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 14 Aug 2026 08:23:54 +0000 Subject: [PATCH 0246/1317] mmc: hsq: Fix use-after-free in retry work mmc_hsq_pump_requests() queues retry_work when request_atomic() returns -EBUSY; today sdhci-sprd is the only consumer that implements request_atomic(). The work is embedded in a devm-allocated mmc_hsq, but is never cancelled during driver removal. Work still pending at unbind can therefore run after the devm allocation has been released and dereference hsq->mmc and hsq->mrq. Use devm_work_autocancel() to cancel and drain retry_work before the devm allocation is released. By the time devres cleanup begins, mmc_remove_host() has already stopped the host, so no new requests can arm the work. This issue was found by an in-house static analysis tool. Fixes: 6db96e5810e0 ("mmc: host: Introduce the request_atomic() for the host") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmc_hsq.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/mmc_hsq.c b/drivers/mmc/host/mmc_hsq.c index 79836705c176..57e172bd3487 100644 --- a/drivers/mmc/host/mmc_hsq.c +++ b/drivers/mmc/host/mmc_hsq.c @@ -7,6 +7,7 @@ * Author: Baolin Wang */ +#include #include #include #include @@ -345,6 +346,7 @@ static const struct mmc_cqe_ops mmc_hsq_ops = { int mmc_hsq_init(struct mmc_hsq *hsq, struct mmc_host *mmc) { + int ret; int i; hsq->num_slots = HSQ_NUM_SLOTS; hsq->next_tag = HSQ_INVALID_TAG; @@ -363,7 +365,11 @@ int mmc_hsq_init(struct mmc_hsq *hsq, struct mmc_host *mmc) for (i = 0; i < HSQ_NUM_SLOTS; i++) hsq->tag_slot[i] = HSQ_INVALID_TAG; - INIT_WORK(&hsq->retry_work, mmc_hsq_retry_handler); + ret = devm_work_autocancel(mmc_dev(mmc), &hsq->retry_work, + mmc_hsq_retry_handler); + if (ret) + return ret; + spin_lock_init(&hsq->lock); init_waitqueue_head(&hsq->wait_queue); From ac53977611428db3bc0b4ac0225e19c3e08ae50b Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 31 Aug 2026 14:17:15 -0700 Subject: [PATCH 0247/1317] crypto: x86/aria - add missing vzeroupper in AVX2 code Since the AVX2 optimized ARIA code uses YMM registers, execute vzeroupper before returning from it. This is needed to avoid degrading the performance of any later SSE code that may happen to be executed. Fixes: 37d8d3ae7a58 ("crypto: x86/aria - implement aria-avx2") Cc: stable@vger.kernel.org Cc: Taehee Yoo Signed-off-by: Eric Biggers Signed-off-by: Herbert Xu --- arch/x86/crypto/aria-aesni-avx2-asm_64.S | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/x86/crypto/aria-aesni-avx2-asm_64.S b/arch/x86/crypto/aria-aesni-avx2-asm_64.S index ed53d4f46bd7..fda8cb8a99a8 100644 --- a/arch/x86/crypto/aria-aesni-avx2-asm_64.S +++ b/arch/x86/crypto/aria-aesni-avx2-asm_64.S @@ -982,6 +982,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_encrypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_encrypt_32way) @@ -1007,6 +1008,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_decrypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_decrypt_32way) @@ -1209,6 +1211,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_ctr_crypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %r10); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_ctr_crypt_32way) @@ -1359,6 +1362,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_gfni_encrypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_gfni_encrypt_32way) @@ -1384,6 +1388,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_gfni_decrypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_gfni_decrypt_32way) @@ -1428,6 +1433,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_gfni_ctr_crypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %r10); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_gfni_ctr_crypt_32way) From 60892a384aa1e65d0e703e1c513417bdf0c80777 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 31 Aug 2026 14:18:12 -0700 Subject: [PATCH 0248/1317] crypto: x86/aria - add missing vzeroupper in AVX-512 code Since the AVX-512 optimized ARIA code uses ZMM registers, execute vzeroupper before returning from it. This is needed to avoid degrading the performance of any later SSE code that may happen to be executed. Fixes: c970d42001f2 ("crypto: x86/aria - implement aria-avx512") Cc: stable@vger.kernel.org Cc: Taehee Yoo Signed-off-by: Eric Biggers Signed-off-by: Herbert Xu --- arch/x86/crypto/aria-gfni-avx512-asm_64.S | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/x86/crypto/aria-gfni-avx512-asm_64.S b/arch/x86/crypto/aria-gfni-avx512-asm_64.S index 860887e5d02e..ca83eb126e06 100644 --- a/arch/x86/crypto/aria-gfni-avx512-asm_64.S +++ b/arch/x86/crypto/aria-gfni-avx512-asm_64.S @@ -800,6 +800,7 @@ SYM_TYPED_FUNC_START(aria_gfni_avx512_encrypt_64way) %zmm9, %zmm8, %zmm11, %zmm10, %zmm12, %zmm13, %zmm14, %zmm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_gfni_avx512_encrypt_64way) @@ -825,6 +826,7 @@ SYM_TYPED_FUNC_START(aria_gfni_avx512_decrypt_64way) %zmm9, %zmm8, %zmm11, %zmm10, %zmm12, %zmm13, %zmm14, %zmm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_gfni_avx512_decrypt_64way) @@ -966,6 +968,7 @@ SYM_TYPED_FUNC_START(aria_gfni_avx512_ctr_crypt_64way) %zmm9, %zmm8, %zmm11, %zmm10, %zmm12, %zmm13, %zmm14, %zmm15, %r10); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_gfni_avx512_ctr_crypt_64way) From ff894dced1a7ad7523f9c65dbdb53d02474cca0f Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:57 +0200 Subject: [PATCH 0249/1317] mmc: sdhci_am654: Move tuning_loop to local variable The tuning_loop field in struct sdhci_am654_data is only used within sdhci_am654_platform_execute_tuning() as a loop counter that is initialized to 0 in sdhci_am654_init(). Since it shouldn't persist across function calls, otherwise every failure expends its "budget", move it to a local variable and remove the struct field along with the now-unnecessary initialization. Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Fixes: de31f6ab68a3 ("mmc: sdhci_am654: Reset Command and Data line after tuning") Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 2a27db2f558b..4b74a4115509 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -151,7 +151,6 @@ struct sdhci_am654_data { u32 flags; u32 quirks; bool dll_enable; - u32 tuning_loop; #define SDHCI_AM654_QUIRK_FORCE_CDTEST BIT(0) #define SDHCI_AM654_QUIRK_SUPPRESS_V1P8_ENA BIT(1) @@ -576,13 +575,14 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); unsigned char timing = host->mmc->ios.timing; struct device *dev = mmc_dev(host->mmc); + unsigned int tuning_loop = 0; int itapdly; do { itapdly = sdhci_am654_do_tuning(host, opcode); if (itapdly >= 0) break; - } while (++sdhci_am654->tuning_loop < RETRY_TUNING_MAX); + } while (++tuning_loop < RETRY_TUNING_MAX); if (itapdly < 0) { dev_err(dev, "Failed to find itapdly, fail tuning\n"); @@ -806,9 +806,6 @@ static int sdhci_am654_init(struct sdhci_host *host) regmap_update_bits(sdhci_am654->base, CTL_CFG_3, TUNINGFORSDR50_MASK, TUNINGFORSDR50_MASK); - /* Use to re-execute tuning */ - sdhci_am654->tuning_loop = 0; - ret = sdhci_setup_host(host); if (ret) return ret; From 7197d9107d9545730153b82ea5a411c5208b443f Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:58 +0200 Subject: [PATCH 0250/1317] mmc: sdhci_am654: Reset command and data lines on failed tuning The CMD/DATA reset after tuning should be performed regardless of whether tuning succeeded or failed, since tuning data may remain in the buffer in either case. Move the error return after the reset so that the controller is always cleaned up. Fixes: de31f6ab68a3 ("mmc: sdhci_am654: Reset Command and Data line after tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 4b74a4115509..6abe206ed292 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -442,15 +442,13 @@ static int sdhci_am654_execute_tuning(struct mmc_host *mmc, u32 opcode) struct sdhci_host *host = mmc_priv(mmc); int err = sdhci_execute_tuning(mmc, opcode); - if (err) - return err; /* * Tuning data remains in the buffer after tuning. * Do a command and data reset to get rid of it */ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); - return 0; + return err; } static u32 sdhci_am654_cqhci_irq(struct sdhci_host *host, u32 intmask) From c9f47cc8c37f7659897142ffe216c250fbc1d4ed Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:59 +0200 Subject: [PATCH 0251/1317] mmc: sdhci_am654: Clear ITAPDLY on tuning failure When tuning fails, stale ITAPDLY values can persist and interfere with subsequent I/O accesses, for example in DDR50 mode in cards with no tuning support. Move the ITAPDLY enable setting out of the tuning loop to after successful tuning, and explicitly clear ITAPDLY (delay and enable) when tuning fails so that we are sure only working values are actually left in hardware. Fixes: 901d16e46296 ("mmc: sdhci_am654: Add retry tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 6abe206ed292..49a46583c8bc 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -527,7 +527,6 @@ static int sdhci_am654_do_tuning(struct sdhci_host *host, { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); - unsigned char timing = host->mmc->ios.timing; struct window fail_window[ITAPDLY_LENGTH]; struct device *dev = mmc_dev(host->mmc); u8 curr_pass, itap; @@ -536,11 +535,8 @@ static int sdhci_am654_do_tuning(struct sdhci_host *host, memset(fail_window, 0, sizeof(fail_window)); - /* Enable ITAPDLY */ - sdhci_am654->itap_del_ena[timing] = 0x1; - for (itap = 0; itap < ITAPDLY_LENGTH; itap++) { - sdhci_am654_write_itapdly(sdhci_am654, itap, sdhci_am654->itap_del_ena[timing]); + sdhci_am654_write_itapdly(sdhci_am654, itap, 0x1); curr_pass = !mmc_send_tuning(host->mmc, opcode, NULL); @@ -584,10 +580,16 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, if (itapdly < 0) { dev_err(dev, "Failed to find itapdly, fail tuning\n"); + sdhci_am654_write_itapdly(sdhci_am654, 0, 0); + sdhci_am654->itap_del_ena[timing] = 0; + sdhci_am654->itap_del_sel[timing] = 0; return -1; } dev_dbg(dev, "Passed tuning, final itapdly=%d\n", itapdly); + + /* Enable ITAPDLY */ + sdhci_am654->itap_del_ena[timing] = 0x1; sdhci_am654_write_itapdly(sdhci_am654, itapdly, sdhci_am654->itap_del_ena[timing]); /* Save ITAPDLY */ sdhci_am654->itap_del_sel[timing] = itapdly; From 308d05225281d86150d88141990d6caf8c902349 Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:07:00 +0200 Subject: [PATCH 0252/1317] mmc: sdhci_am654: Fallback to DT-provided itap delay on DDR50 tuning failure DDR50 mode is not required to support the tuning command CMD19, meaning that calibration may fail on cards that do not implement it, in which case a known-good itap delay value should be programmed into the host controller. Do this by reading the (already defined) itap delay DT property for DDR50 and, if tuning fails for this mode, fall back to the DT-provided itap delay value. If the DT does not provide a value for DDR50 fallback then this simply disables using itapdly. Fixes: 901d16e46296 ("mmc: sdhci_am654: Add retry tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Acked-by: Adrian Hunter Reviewed-by: Judith Mendez Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 49a46583c8bc..2e332f52c301 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -126,7 +126,7 @@ static const struct timing_data td[] = { NULL, MMC_CAP_UHS_SDR104}, [MMC_TIMING_UHS_DDR50] = {"ti,otap-del-sel-ddr50", - NULL, + "ti,itap-del-sel-ddr50", MMC_CAP_UHS_DDR50}, [MMC_TIMING_MMC_DDR52] = {"ti,otap-del-sel-ddr52", "ti,itap-del-sel-ddr52", @@ -144,6 +144,8 @@ struct sdhci_am654_data { u32 otap_del_sel[ARRAY_SIZE(td)]; u32 itap_del_sel[ARRAY_SIZE(td)]; u32 itap_del_ena[ARRAY_SIZE(td)]; + u32 itap_del_sel_dt_ddr50; + u32 itap_del_ena_dt_ddr50; int clkbuf_sel; int trm_icp; int drv_strength; @@ -579,10 +581,19 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, } while (++tuning_loop < RETRY_TUNING_MAX); if (itapdly < 0) { - dev_err(dev, "Failed to find itapdly, fail tuning\n"); - sdhci_am654_write_itapdly(sdhci_am654, 0, 0); - sdhci_am654->itap_del_ena[timing] = 0; - sdhci_am654->itap_del_sel[timing] = 0; + if (timing == MMC_TIMING_UHS_DDR50) { + dev_dbg(dev, "Failed DDR50 tuning, fallback to DT ITAP\n"); + sdhci_am654->itap_del_sel[timing] = sdhci_am654->itap_del_sel_dt_ddr50; + sdhci_am654->itap_del_ena[timing] = sdhci_am654->itap_del_ena_dt_ddr50; + } else { + dev_err(dev, "Failed to find itapdly, fail tuning\n"); + sdhci_am654->itap_del_ena[timing] = 0; + sdhci_am654->itap_del_sel[timing] = 0; + } + + sdhci_am654_write_itapdly(sdhci_am654, + sdhci_am654->itap_del_sel[timing], + sdhci_am654->itap_del_ena[timing]); return -1; } @@ -758,6 +769,11 @@ static int sdhci_am654_get_otap_delay(struct sdhci_host *host, } } + sdhci_am654->itap_del_sel_dt_ddr50 = + sdhci_am654->itap_del_sel[MMC_TIMING_UHS_DDR50]; + sdhci_am654->itap_del_ena_dt_ddr50 = + sdhci_am654->itap_del_ena[MMC_TIMING_UHS_DDR50]; + return 0; } From cdd812d0683dee14ead02c9eded568685e61b23f Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Mon, 31 Aug 2026 09:12:21 +0900 Subject: [PATCH 0253/1317] exit: hold a reference to thread_pid across proc_flush_pid Commit 0a36bad01731 ("release_task: kill the no longer needed get/put_pid(thread_pid)") removed the reference around proc_flush_pid(). It assumed that free_pids(post.pids) at the end of release_task() would keep thread_pid alive until then. That assumption is wrong. __change_pid() only records a detached PID in post.pids when pid_has_task() is false for every PIDTYPE. If another task still uses the exiting task's PID as its process group or session ID, __unhash_process() removes the exiting task's PIDTYPE_PID link but leaves the PID out of post.pids. release_task() therefore holds no reference to it after dropping tasklist_lock. The other task can then remove the remaining PIDTYPE links. Its free_pids() call schedules delayed_put_pid(), and the RCU callback can free the PID before the first release_task() reaches proc_flush_pid(). An unprivileged reproducer races wait4(-1) against setsid() to trigger this ordering. Three of three fresh v7.2 KASAN boots reported: BUG: KASAN: slab-use-after-free in proc_invalidate_siblings_dcache+0x3e2/0x3f0 Read of size 8 by task h7_pid_reaper/1921 Call Trace: proc_invalidate_siblings_dcache release_task wait_consider_task __do_wait do_wait kernel_wait4 Freed by task 0: kmem_cache_free put_pid delayed_put_pid rcu_core Last potentially related work creation: __call_rcu_common free_pids ksys_setsid KASAN identified a 144-byte object from the pid cache and located the bad read 80 bytes into the freed object, matching pid->inodes. With an explicit reference, three of three fresh boots completed without a KASAN report. The concurrent RCU callback dropped its reference while proc_flush_pid() was protected, and the balancing put_pid() performed the final free afterward. Take a reference before __unhash_process() clears p->thread_pid and release it after proc_flush_pid() completes. A tested source reproducer is available privately on request. No controlled read or write, information leak, or privilege escalation is claimed. The mainline patch applies directly to v6.19.y and newer; v6.16.y through v6.18.y need a context-adjusted backport. Fixes: 0a36bad01731 ("release_task: kill the no longer needed get/put_pid(thread_pid)") Reported-by: syzbot+0aee5e8066eddbbe7397@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0aee5e8066eddbbe7397 Reported-by: syzbot+e8b3520b53e78e90034e@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=e8b3520b53e78e90034e Cc: stable@vger.kernel.org # see patch description, needs adjustments for 6.16.y-6.18.y Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260831001221.3755948-1-4ncienth@gmail.com Acked-by: Oleg Nesterov Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- kernel/exit.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index 182c06671c78..e12072f1507c 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -261,8 +261,11 @@ void release_task(struct task_struct *p) pidfs_exit(p); cgroup_task_release(p); - /* Retrieve @thread_pid before __unhash_process() may set it to NULL. */ - thread_pid = task_pid(p); + /* + * Pin @thread_pid before __unhash_process() clears it. The last + * PIDTYPE detach can otherwise free it before proc_flush_pid(). + */ + thread_pid = get_pid(task_pid(p)); write_lock_irq(&tasklist_lock); ptrace_release_task(p); @@ -291,8 +294,8 @@ void release_task(struct task_struct *p) } write_unlock_irq(&tasklist_lock); - /* @thread_pid can't go away until free_pids() below */ proc_flush_pid(thread_pid); + put_pid(thread_pid); exit_cred_namespaces(p); add_device_randomness(&p->se.sum_exec_runtime, sizeof(p->se.sum_exec_runtime)); From 5541d897584127b795dbbdc51a78a2348b4eaaa4 Mon Sep 17 00:00:00 2001 From: "Vlastimil Babka (SUSE)" Date: Mon, 31 Aug 2026 18:02:38 +0200 Subject: [PATCH 0254/1317] mm/slab: disallow kfree_rcu_sheaf() on PREEMPT_RT again This partially reverts commit 2a8bb29ec9b2 ("mm/slab: allow kfree_rcu_sheaf() on PREEMPT_RT"). It was based on the assumption that local_trylock() is safe on PREEMPT_RT from any context. However kvfree_rcu() is also called by set_cpus_allowed_force() with task_struct::pi_lock acquired and there it's not safe, as syzbot has reported. For the immediate fix, skip kfree_rcu_sheaf() on PREEMPT_RT again from kvfree_call_rcu(). In theory, kfree_rcu_nolock() would have the same problem when called from under pi_lock on PREEMPT_RT but that can be addressed if such a caller is proposed. Add an explanation comment, courtesy of Sebastian. Reported-by: syzbot+acf142088e0182172e58@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=acf142088e0182172e58 Reported-by: ThangNN99 Fixes: 2a8bb29ec9b2 ("mm/slab: allow kfree_rcu_sheaf() on PREEMPT_RT") Reviewed-by: Sebastian Andrzej Siewior Link: https://patch.msgid.link/20260831-b4-kfree_rcu_hotfix-v1-1-4f0fb882638b@kernel.org Reviewed-by: Harry Yoo (Meta) Signed-off-by: Vlastimil Babka (SUSE) --- mm/slab_common.c | 18 ++++++++---------- mm/slub.c | 5 +++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index b19ba1b31484..7223a7596dab 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -1667,14 +1667,6 @@ static bool kfree_rcu_sheaf(void *obj) { struct kmem_cache *s; struct slab *slab; - unsigned int free_flags = SLAB_FREE_DEFAULT; - - /* - * It is not safe to spin on PREEMPT_RT because the kernel might be - * holding a raw spinlock and slab acquires sleeping locks. - */ - if (IS_ENABLED(CONFIG_PREEMPT_RT)) - free_flags = SLAB_FREE_NOLOCK; if (is_vmalloc_addr(obj)) return false; @@ -1685,7 +1677,7 @@ static bool kfree_rcu_sheaf(void *obj) s = slab->slab_cache; if (likely(!IS_ENABLED(CONFIG_NUMA) || slab_nid(slab) == numa_mem_id())) - return __kfree_rcu_sheaf(s, obj, free_flags); + return __kfree_rcu_sheaf(s, obj, SLAB_FREE_DEFAULT); return false; } @@ -2034,7 +2026,13 @@ void kvfree_call_rcu(struct kvfree_rcu_head *head, void *ptr) if (!head) might_sleep(); - if (kfree_rcu_sheaf(ptr)) + /* + * kvfree_rcu() is called by set_cpus_allowed_force() with + * task_struct::pi_lock acquired. On PREEMPT_RT the local_trylock() + * usage below will acquire the waitlock which must be avoided. + * Therefore avoid it on PREEMPT_RT. + */ + if (!IS_ENABLED(CONFIG_PREEMPT_RT) && kfree_rcu_sheaf(ptr)) return; // Queue the object but don't yet schedule the batch. diff --git a/mm/slub.c b/mm/slub.c index f9b56cb439e7..7a7e906a0e44 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -6088,8 +6088,9 @@ static void rcu_free_sheaf(struct rcu_head *head) /* * kvfree_call_rcu() can be called while holding a raw_spinlock_t. Since * __kfree_rcu_sheaf() may acquire a spinlock_t (sleeping lock on PREEMPT_RT), - * this would violate lock nesting rules. Therefore, kvfree_call_rcu() avoids - * this problem by passing SLAB_FREE_NOLOCK on PREEMPT_RT. + * this would violate lock nesting rules. Therefore, kfree_call_rcu_nolock() + * avoids this problem by passing SLAB_FREE_NOLOCK. kvfree_call_rcu() is + * bypassing the sheaves layer completely on PREEMPT_RT. * * However, lockdep still complains that it is invalid to acquire spinlock_t * while holding raw_spinlock_t, even on !PREEMPT_RT where spinlock_t is a From 4a724bcf5d703e18957397914d79156fa2cf1174 Mon Sep 17 00:00:00 2001 From: "Harry Yoo (Meta)" Date: Thu, 3 Sep 2026 15:32:26 +0100 Subject: [PATCH 0255/1317] mm/slab: take n->list_lock in __slab_try_return_freelist() to avoid race Commit ba7425312607 ("mm, slab: add an optimistic __slab_try_return_freelist()") incorrectly assumed that nobody has freed an object to the slab as long as slab->freelist is NULL and cmpxchg succeeds. However, as reported by Hyunwoo Kim [1], other CPUs might have freed an object to the slab, insert the slab to the partial list, then allocated an object from the slab, and be in the middle of removing the slab from the list under n->list_lock. Since __refill_objects_node() puts the slab back on pc.slabs outside n->list_lock, it might insert the slab into that list while the slab is concurrently being removed from n->partial. This led to a list corruption [1]: list_add corruption. next->prev should be prev (ffff888100000248), but was dead000000000122. (next=ffffea000416e410). kernel BUG at lib/list_debug.c:29! Oops: invalid opcode: 0000 [#1] SMP NOPTI CPU: 1 UID: 65534 PID: 144 Comm: poc Not tainted 7.2.0-16172-gcf72cbb39da8-dirty #1 PREEMPT(lazy) RIP: 0010:__list_add_valid_or_report+0x80/0xd0 ... Call Trace: alloc_from_new_slab+0x183/0x300 ___slab_alloc+0x31c/0x890 __kmalloc_noprof+0x3d4/0x800 lsm_blob_alloc+0x2d/0x50 security_msg_msg_alloc+0x26/0x90 load_msg+0x1aa/0x210 do_msgsnd+0x91/0x800 do_syscall_64+0x109/0x5d0 entry_SYSCALL_64_after_hwframe+0x77/0x7f ... Kernel panic - not syncing: Fatal exception This is a classic ABA problem where cmpxchg succeeds but the state has changed since __refill_objects_node() took the freelist from the slab. As Vlastimil Babka mentioned [2], it should be rare to return more than one slab (due to the racy read of slab->counters in get_partial_node_bulk()). Therefore, instead of introducing additional complexity, acquire and release n->list_lock twice in the worst case. Return the slab directly to the partial list and hold n->list_lock across the cmpxchg and add_partial(). This is similar to the initial version of commit ba7425312607 [3]. This is enough to avoid the race as the list manipulation is serialized by n->list_lock. While at it, bring back unlikely() hint now that the condition is unlikely. Reported-by: Hyunwoo Kim Closes: https://lore.kernel.org/linux-mm/apPa-cGLcyt90l-E@v4bel [1] Link: https://lore.kernel.org/linux-mm/ae25c193-b95f-40c1-83b6-1c2546467e41@kernel.org [2] Link: https://lore.kernel.org/all/20260421-b4-refill-optimistic-return-v1-1-24f0bfc1acff@kernel.org [3] Fixes: ba7425312607 ("mm, slab: add an optimistic __slab_try_return_freelist()") Cc: stable@vger.kernel.org Signed-off-by: Harry Yoo (Meta) Link: https://patch.msgid.link/20260903-slab-fix-aba-v3-1-b44cb6badd54@kernel.org Reviewed-by: Hao Li Signed-off-by: Vlastimil Babka (SUSE) --- mm/slub.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 7a7e906a0e44..54ec12503357 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5680,10 +5680,12 @@ static noinline void free_to_partial_list( * * Fail if the slab isn't full anymore due to a concurrent free. */ -static bool __slab_try_return_freelist(struct kmem_cache *s, struct slab *slab, - void *head, int cnt) +static bool __slab_try_return_freelist(struct kmem_cache *s, + struct kmem_cache_node *n, + struct slab *slab, void *head, int cnt) { struct freelist_counters old, new; + unsigned long flags; old.freelist = slab->freelist; old.counters = slab->counters; @@ -5695,9 +5697,15 @@ static bool __slab_try_return_freelist(struct kmem_cache *s, struct slab *slab, new.counters = old.counters; new.inuse -= cnt; - if (!slab_update_freelist(s, slab, &old, &new, "__slab_try_return_freelist")) - return false; + spin_lock_irqsave(&n->list_lock, flags); + if (!slab_update_freelist(s, slab, &old, &new, "__slab_try_return_freelist")) { + spin_unlock_irqrestore(&n->list_lock, flags); + return false; + } + + add_partial(n, slab, ADD_TO_TAIL); + spin_unlock_irqrestore(&n->list_lock, flags); return true; } @@ -7297,10 +7305,8 @@ __refill_objects_node(struct kmem_cache *s, void **p, gfp_t gfp, unsigned int mi void *head = object; void *tail; - if (__slab_try_return_freelist(s, slab, head, count)) { - list_add(&slab->slab_list, &pc.slabs); + if (__slab_try_return_freelist(s, n, slab, head, count)) break; - } do { tail = object; @@ -7313,7 +7319,7 @@ __refill_objects_node(struct kmem_cache *s, void **p, gfp_t gfp, unsigned int mi break; } - if (!list_empty(&pc.slabs)) { + if (unlikely(!list_empty(&pc.slabs))) { spin_lock_irqsave(&n->list_lock, flags); list_for_each_entry(slab, &pc.slabs, slab_list) From da2ca406f45a6e21760243152ed8d2e8e72915c2 Mon Sep 17 00:00:00 2001 From: Rik van Riel Date: Sat, 8 Aug 2026 10:47:55 -0400 Subject: [PATCH 0256/1317] wifi: mac80211: avoid WARN in set_bitrate_mask when sdata not in driver ieee80211_set_bitrate_mask() checks if the interface is running via ieee80211_sdata_running(), but it does not check if the interface is still present in the driver. When sdata is running but IEEE80211_SDATA_IN_DRIVER is not set, the call reaches drv_set_bitrate_mask() in driver-ops.h which hits wlan1: Failed check-sdata-in-driver check, flags: 0x0 WARNING: net/mac80211/driver-ops.h:884 at drv_set_bitrate_mask Syzkaller triggers this via wext SIOCSIWRATE ioctl. The Call Trace shows wext_ioctl_dispatch() in wext-core.c dispatching the ioctl, calling ioctl_standard_call() for SIOCSIWRATE, which calls cfg80211_wext_siwrate() in wext-compat.c. That builds a bitrate mask and calls rdev_set_bitrate_mask() which ends up in ieee80211_set_bitrate_mask() in cfg.c. The interface is marked running via SDATA_STATE_RUNNING but flags is 0, so check_sdata_in_driver() fails. When the interface is being torn down, or when wext ioctl is issued during interface bringup before drv_add_interface() sets IN_DRIVER, the running check passes while IN_DRIVER is clear. Check IEEE80211_SDATA_IN_DRIVER in ieee80211_set_bitrate_mask() before calling the driver, returning -ENETDOWN. This avoids the WARN_ONCE in driver-ops.h and matches other cfg.c operations that bail early when not in driver. This change should be safe because wiphy mutex is held in cfg80211_wext_siwrate() via guard(wiphy), and IN_DRIVER is set/cleared under RTNL and wiphy paths in drv_add_interface() and drv_remove_interface() in driver-ops.c, so the check is race-free against driver add/remove. Returning -ENETDOWN is the same error other not-running paths use and does not introduce new locking. Reported-by: syzbot+af177aa139efdd13a9da@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=af177aa139efdd13a9da Link: https://lore.kernel.org/all/6a75205c.59b6c763.2bba34.00c3.GAE@google.com/ Fixes: 554a43d5e77e ("mac80211: check sdata_running on ieee80211_set_bitrate_mask") Cc: stable@vger.kernel.org Assisted-by: Hermes:muse-spark-1.2 syzkaller Signed-off-by: Rik van Riel Link: https://patch.msgid.link/20260808104755.319c686e@fangorn Reported-by: syzbot+dcaca020ca8377e7ced0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=dcaca020ca8377e7ced0 [also add second syzbot report] Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 23f4f9ec86d0..1f074799f85f 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4113,6 +4113,9 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, if (!ieee80211_sdata_running(sdata)) return -ENETDOWN; + if (!(sdata->flags & IEEE80211_SDATA_IN_DRIVER)) + return -ENETDOWN; + /* * If active validate the setting and reject it if it doesn't leave * at least one basic rate usable, since we really have to be able From 44caf1844a258534e891d2c4071b011097e19235 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:07:31 +0530 Subject: [PATCH 0257/1317] firmware: arm_scmi: Fix typo "upto" in comment Correct "upto" to "up to", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904103732.7320-1-hemanth.selam@gmail.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index ef29fd223287..fb45b8e6459d 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -477,7 +477,7 @@ void *scmi_notification_instance_data_get(const struct scmi_handle *handle) * - exactly 'next_token' may be NOT available so pick xfer_id >= next_token * using find_next_zero_bit() starting from candidate next_token bit * - * - all tokens ahead upto (MSG_TOKEN_ID_MASK - 1) are used in-flight but we + * - all tokens ahead up to (MSG_TOKEN_ID_MASK - 1) are used in-flight but we * are plenty of free tokens at start, so try a second pass using * find_next_zero_bit() and starting from 0. * From 5ad0af4f4367202b1bc71813052fe39b5116cdb9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 17 Aug 2026 12:33:24 +0200 Subject: [PATCH 0258/1317] thermal: sysfs: switch to use scnprintf() to suppress truncation warning Switch the sysfs code to use scnprintf() to avoid warnings about potential truncation of the names of the sysfs attributes. We can't increase the buffer size because the size is the part of an ABI for some reason. Note, with the current size of buffer the affected attributes have a room for up to 1000 names, which ought to be enough for all cases. There is no functional change, as the same limitation was implied before. Fixes: c56f5c0342df ("Thermal: Make Thermal trip points writeable") Signed-off-by: Andy Shevchenko Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260817103324.1020212-1-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_sysfs.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index adbcb2c011e8..96fe5d591a0a 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -400,8 +400,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) struct thermal_trip_attrs *trip_attrs = &td->trip_attrs; /* create trip type attribute */ - snprintf(trip_attrs->type.name, THERMAL_NAME_LENGTH, - "trip_point_%d_type", i); + scnprintf(trip_attrs->type.name, sizeof(trip_attrs->type.name), + "trip_point_%d_type", i); sysfs_attr_init(&trip_attrs->type.attr.attr); trip_attrs->type.attr.attr.name = trip_attrs->type.name; @@ -410,8 +410,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) attrs[i] = &trip_attrs->type.attr.attr; /* create trip temp attribute */ - snprintf(trip_attrs->temp.name, THERMAL_NAME_LENGTH, - "trip_point_%d_temp", i); + scnprintf(trip_attrs->temp.name, sizeof(trip_attrs->temp.name), + "trip_point_%d_temp", i); sysfs_attr_init(&trip_attrs->temp.attr.attr); trip_attrs->temp.attr.attr.name = trip_attrs->temp.name; @@ -423,8 +423,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) } attrs[i + tz->num_trips] = &trip_attrs->temp.attr.attr; - snprintf(trip_attrs->hyst.name, THERMAL_NAME_LENGTH, - "trip_point_%d_hyst", i); + scnprintf(trip_attrs->hyst.name, sizeof(trip_attrs->hyst.name), + "trip_point_%d_hyst", i); sysfs_attr_init(&trip_attrs->hyst.attr.attr); trip_attrs->hyst.attr.attr.name = trip_attrs->hyst.name; From 3e5d1bf4bd687beb2cb4e32a07af695455925588 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 12:19:15 +0800 Subject: [PATCH 0259/1317] cpufreq: initialize policy rwsem before sysfs publication cpufreq_policy_alloc() initializes policy->rwsem after kobject_init_and_add() has created the policy sysfs directory and its default attributes. A sysfs access can therefore reach a policy callback before the semaphore has been initialized. Initialize policy->rwsem before publishing the policy kobject so sysfs callbacks always see an initialized semaphore. Fixes: 2fc3384dc75b ("cpufreq: Initialize policy->kobj while allocating policy") Cc: All Applicable Link: https://lore.kernel.org/all/20260830155301.2713780-1-runyu.xiao@seu.edu.cn/ Reviewed-by: Zhongqiu Han Signed-off-by: Runyu Xiao Acked-by: Viresh Kumar Link: https://patch.msgid.link/20260902041915.3453421-1-runyu.xiao@seu.edu.cn Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cpufreq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 0d0df986fa3d..9efbf5b1781a 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1258,6 +1258,8 @@ static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu) if (!zalloc_cpumask_var(&policy->real_cpus, GFP_KERNEL)) goto err_free_rcpumask; + init_rwsem(&policy->rwsem); + init_completion(&policy->kobj_unregister); ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq, cpufreq_global_kobject, "policy%u", cpu); @@ -1272,8 +1274,6 @@ static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu) goto err_free_real_cpus; } - init_rwsem(&policy->rwsem); - freq_constraints_init(&policy->constraints); policy->nb_min.notifier_call = cpufreq_notifier_min; From 54d37bcf2f497140b9207968557ddb484058e749 Mon Sep 17 00:00:00 2001 From: Zhongqiu Han Date: Tue, 1 Sep 2026 22:36:35 +0800 Subject: [PATCH 0260/1317] cpufreq: zero-initialize policy cpumask before sysfs publication cpufreq_policy_alloc() allocates policy->cpus with alloc_cpumask_var(), i.e. without __GFP_ZERO, unlike the sibling related_cpus and real_cpus masks. With CONFIG_CPUMASK_OFFSTACK=y the mask is a separate kmalloc_node() allocation, so its bitmap holds whatever the slab allocator left behind: cpufreq_online() cpufreq_policy_alloc() alloc_cpumask_var(&policy->cpus) /* bitmap is uninitialized */ kobject_init_and_add() /* policy%u/ appears in sysfs */ cpufreq_policy_online() cpumask_copy(policy->cpus, cpumask_of(cpu)) /* first valid value */ This leaves a window in which the sysfs attributes are already reachable while policy->cpus is still garbage. show()/store() gate on policy_is_inactive(), i.e. cpumask_empty(policy->cpus), so a non-zero bitmap makes them run the attribute callbacks on a policy that is not initialized yet. Fix this by using zalloc_cpumask_var() for policy->cpus. Fixes: 2fc3384dc75b ("cpufreq: Initialize policy->kobj while allocating policy") Cc: All applicable Signed-off-by: Zhongqiu Han Acked-by: Viresh Kumar Link: https://patch.msgid.link/20260901143635.4106960-1-zhongqiu.han@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 9efbf5b1781a..96515880b4ac 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1249,7 +1249,7 @@ static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu) if (!policy) return NULL; - if (!alloc_cpumask_var(&policy->cpus, GFP_KERNEL)) + if (!zalloc_cpumask_var(&policy->cpus, GFP_KERNEL)) goto err_free_policy; if (!zalloc_cpumask_var(&policy->related_cpus, GFP_KERNEL)) From 4e3bd70facac5b7c412271d0e9cd6c18a3cef6c6 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:57:37 +0800 Subject: [PATCH 0261/1317] ASoC: rt766: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075737.86525-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt766-sdca.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c index 64d763b96a06..5a1c1e10e6d7 100644 --- a/sound/soc/codecs/rt766-sdca.c +++ b/sound/soc/codecs/rt766-sdca.c @@ -936,9 +936,8 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; unsigned int sampling_rate; int retval, port; @@ -957,7 +956,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; if (dai->id == RT766_AIF1) port = 3; else if (dai->id == RT766_AIF2) @@ -965,7 +963,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } else { - direction = SDW_DATA_DIR_TX; if (dai->id == RT766_AIF1) port = 12; else if (dai->id == RT766_AIF3) From 0a9bce8a4ff8357f3b0022e9aeee5db3e9326de5 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:57:57 +0800 Subject: [PATCH 0262/1317] ASoC: rt712: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075757.108427-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index 13574513b181..eda87eb9ab66 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "rt712-sdca.h" @@ -1449,11 +1450,10 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); @@ -1471,7 +1471,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; if (dai->id == RT712_AIF1) port = 1; else if (dai->id == RT712_AIF2) @@ -1479,7 +1478,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } else { - direction = SDW_DATA_DIR_TX; if (dai->id == RT712_AIF1) port = 4; else if (dai->id == RT712_AIF3) @@ -1488,13 +1486,8 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; retval = sdw_stream_add_slave(rt712->slave, &stream_config, From 2c68c4a0e57063a30a8139fa8bcb689323a45f16 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:58:10 +0800 Subject: [PATCH 0263/1317] ASoC: rt1320: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075810.121467-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 90333779971d..72af413306fa 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -3246,7 +3246,7 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; struct sdw_port_config dmic_port_config[2]; struct sdw_stream_runtime *sdw_stream; From c966210ed5f218d0297e94fb1002fba59e831c64 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:58:18 +0800 Subject: [PATCH 0264/1317] ASoC: rt1318: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075818.130215-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1318-sdw.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c index efadb6b1b82d..f9ba16269817 100644 --- a/sound/soc/codecs/rt1318-sdw.c +++ b/sound/soc/codecs/rt1318-sdw.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "rt1318-sdw.h" @@ -564,11 +565,10 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct rt1318_sdw_priv *rt1318 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels, ch_mask; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -582,23 +582,13 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ /* port 1 for playback */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port = 1; - } else { - direction = SDW_DATA_DIR_TX; + else port = 2; - } - num_channels = params_channels(params); - ch_mask = (1 << num_channels) - 1; - - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = num_channels; - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - - port_config.ch_mask = ch_mask; + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; retval = sdw_stream_add_slave(rt1318->sdw_slave, &stream_config, From 0e17e50b993043cf3e21512a43ca63d176b46459 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:58:26 +0800 Subject: [PATCH 0265/1317] ASoC: rt721: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075826.130751-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index b1c3dc060c24..a9479d0e4941 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "rt721-sdca.h" @@ -1269,11 +1270,10 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt721_sdca_priv *rt721 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -1292,7 +1292,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, * RT721_AIF3 with port = 6 for digital-mic capture */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; if (dai->id == RT721_AIF1) port = 1; else if (dai->id == RT721_AIF2) @@ -1300,7 +1299,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } else { - direction = SDW_DATA_DIR_TX; if (dai->id == RT721_AIF1) port = 2; else if (dai->id == RT721_AIF3) @@ -1308,13 +1306,9 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; retval = sdw_stream_add_slave(rt721->slave, &stream_config, From cff381508c1826ed93f7291372c81a50474d5565 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:58:34 +0800 Subject: [PATCH 0266/1317] ASoC: rt722: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075835.130778-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt722-sdca.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c index 4cbe9e909585..149cb6617126 100644 --- a/sound/soc/codecs/rt722-sdca.c +++ b/sound/soc/codecs/rt722-sdca.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "rt722-sdca.h" @@ -1442,11 +1443,10 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -1465,7 +1465,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, * RT722_AIF3 with port = 6 for digital-mic capture */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; if (dai->id == RT722_AIF1) port = 1; else if (dai->id == RT722_AIF2) @@ -1473,7 +1472,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } else { - direction = SDW_DATA_DIR_TX; if (dai->id == RT722_AIF1) port = 2; else if (dai->id == RT722_AIF3) @@ -1481,13 +1479,9 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; retval = sdw_stream_add_slave(rt722->slave, &stream_config, From c1e01ea35093777d59c12128d549ff5016c38cec Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Fri, 4 Sep 2026 18:41:41 +0800 Subject: [PATCH 0267/1317] ASoC: codecs: ES8389: Prevent enable_count underflow in clk_core_disable To prevent enable_count underflow in clk_core_disable, I replaced the direct call to `es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY);` in the code with a helper function. Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260904104141.3885-1-zhangyi@everest-semi.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8389.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c index 80efce3e0a22..2ea0c4052ce1 100644 --- a/sound/soc/codecs/es8389.c +++ b/sound/soc/codecs/es8389.c @@ -812,6 +812,23 @@ static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, return 0; } +static void es8389_standby(struct snd_soc_component *component) +{ + struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); + + regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); + regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); + regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); + usleep_range(70000, 72000); + regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); + regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); + regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); + regmap_write(es8389->regmap, ES8389_RESET, 0x3E); + regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); + usleep_range(8000, 8500); + regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); +} + static int es8389_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { @@ -834,18 +851,7 @@ static int es8389_set_bias_level(struct snd_soc_component *component, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); - regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); - regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); - usleep_range(70000, 72000); - regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); - regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); - regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); - regmap_write(es8389->regmap, ES8389_RESET, 0x3E); - regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); - usleep_range(8000, 8500); - regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); - + es8389_standby(component); clk_disable_unprepare(es8389->mclk); break; case SND_SOC_BIAS_OFF: @@ -1015,7 +1021,7 @@ static int es8389_suspend(struct snd_soc_component *component) { struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); - es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + es8389_standby(component); regcache_cache_only(es8389->regmap, true); regcache_mark_dirty(es8389->regmap); @@ -1084,7 +1090,7 @@ static int es8389_probe(struct snd_soc_component *component) es8389->hpf_freq = ES8389_HPF_DEFAULT; es8389_init(component); - es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + es8389_standby(component); return 0; } From f76017a7663c4ce5e379f8a8d39f032bdb1fd865 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 14 Apr 2026 07:18:05 -0700 Subject: [PATCH 0268/1317] nfsd: fix handling of NFSEXP_PNFS in the netlink codepath The rework of how block layouts were checked moved the check for NFSEXP_PNFS out of nfsd4_setup_layout_type() and into the callers. That patch didn't account for the new call in nfsd4_setup_layout_type(). Cc: Christoph Hellwig Fixes: da9baa5470dc ("exportfs,nfsd: rework checking for layout-based block device access support") Cc: stable@vger.kernel.org Reported-by: Olga Kornievskaia Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260414-pnfs-exp-fix-v1-1-9face14c16c2@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/export.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index a47c90f40422..1f678583f612 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -1006,7 +1006,8 @@ static int nfsd_nl_parse_one_export(struct cache_detail *cd, goto out_uuid; err = 0; - nfsd4_setup_layout_type(&exp); + if (exp.ex_flags & NFSEXP_PNFS) + nfsd4_setup_layout_type(&exp); } expp = svc_export_lookup(&exp); From 5bd9e4e7cdaa03879e9b73b12ab52cceb1edd55b Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Thu, 3 Sep 2026 11:02:59 +0300 Subject: [PATCH 0269/1317] nexthop: Initialize extack in remove_nh_grp_entry() remove_nh_grp_entry() prints the extack message when a listener fails to replace the reduced nexthop group. However, extack is not initialized and listeners are not required to set a message when returning an error. Neither netdevsim nor mlxsw do so when an allocation fails, resulting in the dereference of an uninitialized stack pointer. Fix by zero-initializing extack, as was done in commit 6347c5314cee ("nexthop: initialize extack in nh_res_bucket_migrate()"). Fixes: 833a1065eeb1 ("nexthop: Emit a notification when a nexthop group is reduced") Signed-off-by: Ido Schimmel Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260903080259.10378-1-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv4/nexthop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index a7c2b8dced4e..42e55b5a755e 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -2036,7 +2036,7 @@ remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge, { struct nh_grp_entry *nhges, *new_nhges; struct nexthop *nhp = nhge->nh_parent; - struct netlink_ext_ack extack; + struct netlink_ext_ack extack = {}; struct nexthop *nh = nhge->nh; struct nh_group *nhg, *newg; int i, j, err; From 1746ef2e2df2ad71c66eca56364d56bde284523b Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 3 Sep 2026 14:39:40 +0000 Subject: [PATCH 0270/1317] bonding: use skb_cow_head() in bond_do_alb_xmit() and rlb_arp_xmit() In bond_do_alb_xmit() and rlb_arp_xmit(), make sure to unclone skb head via skb_cow_head() before modifying the source MAC address (Ethernet header and ARP payload) to avoid silent corruption if the skb is shared or cloned. Avoid caching the header pointers across skb_cow_head(). In rlb_arp_xmit(), only modify arp->mac_src if it differs from tx_slave->dev->dev_addr to avoid an unnecessary copy and head reallocation. Also, we should not assume mac header is set in output path. Use skb_eth_hdr() instead of eth_hdr() to fix the issue, and remove now redundant skb_reset_mac_header() calls. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Cc: Jay Vosburgh Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260903143940.1180513-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_alb.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c index 654f051d0023..43ac8e28e418 100644 --- a/drivers/net/bonding/bond_alb.c +++ b/drivers/net/bonding/bond_alb.c @@ -678,9 +678,15 @@ static struct slave *rlb_arp_xmit(struct sk_buff *skb, struct bonding *bond) if (arp->op_code == htons(ARPOP_REPLY)) { /* the arp must be sent on the selected rx channel */ tx_slave = rlb_choose_channel(skb, bond, arp); - if (tx_slave) + if (tx_slave && + !ether_addr_equal_64bits(arp->mac_src, + tx_slave->dev->dev_addr)) { + if (unlikely(skb_cow_head(skb, 0))) + return NULL; + arp = (struct arp_pkt *)skb_network_header(skb); bond_hw_addr_copy(arp->mac_src, tx_slave->dev->dev_addr, tx_slave->dev->addr_len); + } netdev_dbg(bond->dev, "(slave %s): Server sent ARP Reply packet\n", tx_slave ? tx_slave->dev->name : "NULL"); } else if (arp->op_code == htons(ARPOP_REQUEST)) { @@ -1340,7 +1346,6 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond, struct slave *tx_slave) { struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); - struct ethhdr *eth_data = eth_hdr(skb); if (!tx_slave) { /* unbalanced or unassigned, send through primary */ @@ -1351,7 +1356,9 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond, if (tx_slave && bond_slave_can_tx(tx_slave)) { if (tx_slave != rcu_access_pointer(bond->curr_active_slave)) { - ether_addr_copy(eth_data->h_source, + if (unlikely(skb_cow_head(skb, 0))) + return bond_tx_drop(bond->dev, skb); + ether_addr_copy(skb_eth_hdr(skb)->h_source, tx_slave->dev->dev_addr); } @@ -1375,8 +1382,7 @@ struct slave *bond_xmit_tlb_slave_get(struct bonding *bond, struct ethhdr *eth_data; u32 hash_index; - skb_reset_mac_header(skb); - eth_data = eth_hdr(skb); + eth_data = skb_eth_hdr(skb); /* Do not TX balance any multicast or broadcast */ if (!is_multicast_ether_addr(eth_data->h_dest)) { @@ -1428,8 +1434,7 @@ struct slave *bond_xmit_alb_slave_get(struct bonding *bond, u32 hash_index = 0; int hash_size = 0; - skb_reset_mac_header(skb); - eth_data = eth_hdr(skb); + eth_data = skb_eth_hdr(skb); switch (ntohs(skb->protocol)) { case ETH_P_IP: { From cdb719f4b8596d9ccee2d56d204c2c4dce982f46 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:07 -0700 Subject: [PATCH 0271/1317] net: dsa: bcm_sf2: bound the CFP rule dump by the caller's buffer size bcm_sf2_cfp_rule_get_all() walks the whole cfp.unique bitmap into rule_locs[] without consulting nfc->rule_cnt, which is how many entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed CFP rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Fixes: 7318166cacad ("net: dsa: bcm_sf2: Add support for ethtool::rxnfc") Reviewed-by: Jonas Gorski Reviewed-by: Florian Fainelli Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/bcm_sf2_cfp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/dsa/bcm_sf2_cfp.c b/drivers/net/dsa/bcm_sf2_cfp.c index 50d3a818eb1b..84a086c3e99b 100644 --- a/drivers/net/dsa/bcm_sf2_cfp.c +++ b/drivers/net/dsa/bcm_sf2_cfp.c @@ -1088,6 +1088,8 @@ static int bcm_sf2_cfp_rule_get_all(struct bcm_sf2_priv *priv, unsigned int index = 1, rules_cnt = 0; for_each_set_bit_from(index, priv->cfp.unique, priv->num_cfp_rules) { + if (rules_cnt == nfc->rule_cnt) + return -EMSGSIZE; rule_locs[rules_cnt] = index; rules_cnt++; } From f1986bf87b0709c95126fe196cf39e5b8c8453a1 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:08 -0700 Subject: [PATCH 0272/1317] eth: nfp: bound the ntuple rule dump by the caller's buffer size nfp_net_get_fs_loc() dumps every entry of nn->fs.list into rule_locs[] without consulting cmd->rule_cnt, which is how many entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed flow steering rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Bail out with -EMSGSIZE when the buffer fills up, the way the other ntuple capable drivers do, and report how many locations were filled so a shrinking rule list does not leave the caller reading stale slots. Reported-by: VEGA Fixes: 9eb03bb1c035 ("nfp: add ethtool flow steering callbacks") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c index a2a89d48e3ca..9419e1ed8466 100644 --- a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c +++ b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c @@ -1421,7 +1421,8 @@ static int nfp_net_get_fs_rule(struct nfp_net *nn, struct ethtool_rxnfc *cmd) return -ENOENT; } -static int nfp_net_get_fs_loc(struct nfp_net *nn, u32 *rule_locs) +static int nfp_net_get_fs_loc(struct nfp_net *nn, struct ethtool_rxnfc *cmd, + u32 *rule_locs) { struct nfp_fs_entry *entry; u32 count = 0; @@ -1429,8 +1430,12 @@ static int nfp_net_get_fs_loc(struct nfp_net *nn, u32 *rule_locs) if (!(nn->cap_w1 & NFP_NET_CFG_CTRL_FLOW_STEER)) return -EOPNOTSUPP; - list_for_each_entry(entry, &nn->fs.list, node) + list_for_each_entry(entry, &nn->fs.list, node) { + if (count == cmd->rule_cnt) + return -EMSGSIZE; rule_locs[count++] = entry->loc; + } + cmd->rule_cnt = count; return 0; } @@ -1455,7 +1460,7 @@ static int nfp_net_get_rxnfc(struct net_device *netdev, return nfp_net_get_fs_rule(nn, cmd); case ETHTOOL_GRXCLSRLALL: cmd->data = NFP_FS_MAX_ENTRY; - return nfp_net_get_fs_loc(nn, rule_locs); + return nfp_net_get_fs_loc(nn, cmd, rule_locs); default: return -EOPNOTSUPP; } From 108bb2142e3a12c9ad625ad662973127a113ddc6 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:09 -0700 Subject: [PATCH 0273/1317] eth: nfp: drop the replaced rule from the list when reprogramming fails nfp_net_fs_add() replaces an existing rule by deleting it from the hardware, decrementing nn->fs.count and programming the new one. If nfp_net_fs_add_hw() fails the old entry stays on nn->fs.list - only the success path reaches list_replace() - so the list is one longer than nn->fs.count, and it advertises a rule whose hardware entry has already been torn down. nn->fs.count is what ETHTOOL_GRXCLSRLCNT reports, so userspace then sizes its buffer one entry short of what the GRXCLSRLALL walk wants to write. That used to overwrite one u32 past the allocation; since the walk is bounded it is a permanent -EMSGSIZE instead, as nothing ever resyncs the counter. Fixes: 9eb03bb1c035 ("nfp: add ethtool flow steering callbacks") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c index 9419e1ed8466..4e83637715e0 100644 --- a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c +++ b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c @@ -1703,8 +1703,14 @@ static int nfp_net_fs_add(struct nfp_net *nn, struct ethtool_rxnfc *cmd) nn->fs.count--; err = nfp_net_fs_add_hw(nn, new); - if (err) + if (err) { + /* mbox broken, adding the old rule back will + * likely also fail. + */ + list_del(&entry->node); + kfree(entry); goto err; + } nn->fs.count++; list_replace(&entry->node, &new->node); From b1fffc273112e7284c5b705e186b43b5770cd3d5 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:10 -0700 Subject: [PATCH 0274/1317] net: dsa: mv88e6xxx: bound the policy rule dump by the caller's buffer size mv88e6xxx_get_rxnfc() uses rxnfc->rule_cnt as the write index while dumping the policy IDR, clobbering the input value before it has been looked at. That input is the number of entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed policy rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Count into a local so the caller's limit survives the walk, and stop with -EMSGSIZE once it is reached. Fixes: da7dc8755304 ("net: dsa: mv88e6xxx: add RXNFC support") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/chip.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 80b877c74513..7f68a0c55802 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -2438,6 +2438,7 @@ static int mv88e6xxx_get_rxnfc(struct dsa_switch *ds, int port, struct ethtool_rx_flow_spec *fs = &rxnfc->fs; struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_policy *policy; + u32 cnt = 0; int err; int id; @@ -2463,11 +2464,18 @@ static int mv88e6xxx_get_rxnfc(struct dsa_switch *ds, int port, break; case ETHTOOL_GRXCLSRLALL: rxnfc->data = 0; - rxnfc->rule_cnt = 0; - idr_for_each_entry(&chip->policies, policy, id) - if (policy->port == port) - rule_locs[rxnfc->rule_cnt++] = id; err = 0; + idr_for_each_entry(&chip->policies, policy, id) { + if (policy->port != port) + continue; + if (cnt == rxnfc->rule_cnt) { + err = -EMSGSIZE; + break; + } + rule_locs[cnt++] = id; + } + if (!err) + rxnfc->rule_cnt = cnt; break; default: err = -EOPNOTSUPP; From 47a582b2b0e7bb5753e4803988e150a405b57f51 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:11 -0700 Subject: [PATCH 0275/1317] ethtool: document that GRXCLSRLALL rule_cnt is a caller-provided limit Three drivers have shipped a get_rxnfc() which dumps its entire rule table into rule_locs, reading rule_cnt as "how many rules do I have" rather than "how many entries did the caller allocate". Nothing in the callback's documentation contradicted that reading. The distinction only matters because the ioctl lets an unprivileged caller pick rule_cnt directly, so getting it wrong is a heap overflow rather than a truncated dump. Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-6-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/ethtool.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 12683b5d125e..253600c0eccd 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -1057,6 +1057,12 @@ struct kernel_ethtool_ts_info { * @get_sset_count: Get number of strings that @get_strings will write. * @get_rxnfc: Get RX flow classification rules. Returns a negative * error code or zero. + * Note that for %ETHTOOL_GRXCLSRLALL rule_cnt and size of the arrays + * is user-provided, and not guaranteed to match what driver would + * have reported via %ETHTOOL_GRXCLSRLCNT. Drivers must return -%EMSGSIZE + * when rule_cnt is too small. rule_locs is %NULL when rule_cnt is zero. + * On success drivers must set rule_cnt to the number of locations they + * filled in, the core copies out exactly that many. * @set_rxnfc: Set RX flow classification rules. Returns a negative * error code or zero. * @flash_device: Write a firmware image to device's flash memory. From 4b772869a1e5f9da5cef5b9c722ec0aa424ee0a0 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 3 Sep 2026 12:38:51 +0300 Subject: [PATCH 0276/1317] net: bridge: mcast: properly convert mglist to rcu Sashiko reported a bug [1] that br_multicast_del_port_group unlists the port group not using proper rcu helper that preserves the next pointer and after that immediately frees the port group without waiting for rcu grace period. The only rcu walker of mglist is br_multicast_list_adjacent() and it turns out that function has always been buggy because mglist was never properly converted to RCU. Fix it by converting it to rcu and moving its initialization after eth_addr's. Initializing p->next can use RCU_INIT_POINTER because we have a barrier from the hlist_add_head_rcu call later, besides we're initializing an unpublished structure anyway. [1] https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260826014200.362304-1-littleddfu%40gmail.com Fixes: 07f8ac4a1e26 ("bridge: add export of multicast database adjacent to net_dev") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260903093851.1494297-1-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_multicast.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 3e9b10f8abf1..2f9bb30e1a1f 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -1441,16 +1441,17 @@ struct net_bridge_port_group *br_multicast_new_port_group( goto free_out; } - rcu_assign_pointer(p->next, next); timer_setup(&p->timer, br_multicast_port_group_expired, 0); timer_setup(&p->rexmit_timer, br_multicast_port_group_rexmit, 0); - hlist_add_head(&p->mglist, &port->mglist); if (src) memcpy(p->eth_addr, src, ETH_ALEN); else eth_broadcast_addr(p->eth_addr); + RCU_INIT_POINTER(p->next, next); + hlist_add_head_rcu(&p->mglist, &port->mglist); + return p; free_out: @@ -1465,11 +1466,11 @@ void br_multicast_del_port_group(struct net_bridge_port_group *p) struct net_bridge_port *port = p->key.port; __u16 vid = p->key.addr.vid; - hlist_del_init(&p->mglist); + hlist_del_init_rcu(&p->mglist); if (!br_multicast_is_star_g(&p->key.addr)) rhashtable_remove_fast(&port->br->sg_port_tbl, &p->rhnode, br_sg_port_rht_params); - kfree(p); + kfree_rcu(p, rcu); br_multicast_port_ngroups_dec(port, vid); } From 1f29543126dde307e8b5fb6a740c54e59deaa2ff Mon Sep 17 00:00:00 2001 From: Viswajith Murali Date: Tue, 1 Sep 2026 15:13:17 +0530 Subject: [PATCH 0277/1317] octeontx2-af: mcs: Clear stale X2P calibration state before calibration Some firmware versions leave MCSX_MIL_GLOBAL bit 5 set on boot. If the bit is already set when the driver attempts X2P calibration, the hardware sees no rising edge and calibration never triggers. Clear the bit and wait briefly before starting calibration to ensure a clean rising edge. Fixes: ca7f49ff8846 ("octeontx2-af: cn10k: Introduce driver for macsec block.") Signed-off-by: Nitin Shetty J Signed-off-by: Viswajith Murali Link: https://patch.msgid.link/20260901094318.1395356-1-nshettyj@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/mcs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mcs.c b/drivers/net/ethernet/marvell/octeontx2/af/mcs.c index a07e0b3d8d00..211c10aa5880 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mcs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/mcs.c @@ -1417,6 +1417,16 @@ static int mcs_x2p_calibration(struct mcs *mcs) int i, err = 0; u64 val; + /* Clear any stale calibration state left by firmware/bootloader. + * Some firmware versions may leave MCSX_MIL_GLOBAL bit 5 set, + * preventing the hardware from detecting the rising edge needed to + * trigger X2P calibration. + */ + val = mcs_reg_read(mcs, MCSX_MIL_GLOBAL); + val &= ~BIT_ULL(5); + mcs_reg_write(mcs, MCSX_MIL_GLOBAL, val); + usleep_range(100, 200); + /* set X2P calibration */ val = mcs_reg_read(mcs, MCSX_MIL_GLOBAL); val |= BIT_ULL(5); From c91b4d6e5cc30ceea3f23ebe29aec012709a065f Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 1 Sep 2026 05:56:27 +0000 Subject: [PATCH 0278/1317] ionic: use netif_txq_maybe_stop() in ionic_tx() Commit 061b9bedbef1 ("ionic: Rework Tx start/stop flow") replaced ionic_maybe_stop_tx() with netif_txq_maybe_stop() to get the memory barriers around the stop/start bits right, but did not cover the stop in ionic_tx() added by commit 138506ab249b ("ionic: Check stop no restart"). Convert the remaining site. netif_txq_maybe_stop() requires the ring indexes to be updated before it is invoked, so the post has to come first. But ring_dbell comes from __netdev_tx_sent_queue(), which runs after that and reads the stop bit, so it is not known in time to pass to ionic_txq_post(). Post without the doorbell and ring it separately. The stop condition is unchanged. The re-check only clears the stop bit when space has become available, so the doorbell starvation fixed by commit 138506ab249b ("ionic: Check stop no restart") cannot recur. Fixes: 138506ab249b ("ionic: Check stop no restart") Signed-off-by: Nikhil P. Rao Reviewed-by: Brett Creeley Link: https://patch.msgid.link/20260901055627.1373129-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/pensando/ionic/ionic_txrx.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c index e436e3231e86..2543a8ff8547 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c @@ -1672,15 +1672,22 @@ static int ionic_tx(struct net_device *netdev, struct ionic_queue *q, stats->pkts++; stats->bytes += skb->len; + ionic_txq_post(q, false); + if (likely(!ionic_txq_hwstamp_enabled(q))) { struct netdev_queue *ndq = q_to_ndq(netdev, q); - if (unlikely(!ionic_q_has_space(q, MAX_SKB_FRAGS + 1))) - netif_tx_stop_queue(ndq); + netif_txq_maybe_stop(ndq, ionic_q_space_avail(q), + MAX_SKB_FRAGS + 1, MAX_SKB_FRAGS + 1); ring_dbell = __netdev_tx_sent_queue(ndq, skb->len, netdev_xmit_more()); } - ionic_txq_post(q, ring_dbell); + + if (ring_dbell) { + ionic_dbell_ring(q->lif->kern_dbpage, q->hw_type, + q->dbval | q->head_idx); + q->dbell_jiffies = jiffies; + } return 0; } From 78a86d75a70e1e227711c72865c59b1422d0a5ae Mon Sep 17 00:00:00 2001 From: Fourie Zhang Date: Wed, 2 Sep 2026 17:27:12 +0800 Subject: [PATCH 0279/1317] net: mpls: clear inner_protocol when the last label is popped skb_mpls_push() records the pre-encapsulation network header once, gated on !skb->inner_protocol. skb_mpls_pop() never clears that record, so it outlives the encapsulation it describes. Open vSwitch can then re-push MPLS onto a packet whose inner_network_header still points at the older, deeper offset: push a label, pop every label, recirculate (ovs_flow_key_update() re-derives key->eth.type and resets network_header, but leaves inner_*), then push again. ovs_fragment() trusts the record: skb->network_header = skb->inner_network_header; so skb_network_offset() goes negative. The bound check is signed: if (skb_network_offset(skb) > MAX_L2_LEN) a negative offset passes it, and prepare_frag() widens the value: unsigned int hlen = skb_network_offset(skb); memcpy(&data->l2_data, skb->data, hlen); which is a ~4GiB memcpy out of a 30-byte per-CPU buffer. Reproduced on v7.3-rc1. RDX is the truncated length, (unsigned int)(-8): BUG: unable to handle page fault for address: ffffe8ffffc16000 #PF: supervisor write access in kernel mode Oops: 0002 [#1] SMP KASAN NOPTI RIP: 0010:memcpy+0x8/0x20 RDX: 00000000fffffff8 RSI: ffff888105d732db RDI: ffffe8ffffc16000 prepare_frag+0x3df/0x4e0 ovs_fragment+0x589/0x7e0 do_output+0x4ce/0x5e0 do_execute_actions+0x55d2/0x7b30 ovs_execute_actions+0xea/0x450 Same root-cause shape as commit 975b5b067f52 ("ipv6: sr: restore network header before routing and forwarding"): a stale network header offset reaching a consumer that widens it. Here it originates in the MPLS push/pop path. Clear inner_protocol once the packet is no longer MPLS, so a later push re-records the current header. net/sched/act_mpls.c is the only other skb_mpls_pop() caller and gets the same fix; sch_frag.c saves and restores inner_protocol around fragmentation in the same way OVS does. Fixes: 48d2ab609b6b ("net: mpls: Fixups for GSO") Cc: stable@vger.kernel.org Signed-off-by: Fourie Zhang Acked-by: Jiri Benc Link: https://patch.msgid.link/20260902092719.2874481-1-fouriezhang@tencent.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 966af3beed94..cc3b4b70288b 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6690,6 +6690,13 @@ int skb_mpls_pop(struct sk_buff *skb, __be16 next_proto, int mac_len, } skb->protocol = next_proto; + /* The last label is gone, so the inner header recorded by + * skb_mpls_push() no longer describes this packet. Drop it, or a + * later push keeps the stale offset. + */ + if (!eth_p_mpls(next_proto)) + skb->inner_protocol = 0; + return 0; } EXPORT_SYMBOL_GPL(skb_mpls_pop); From 5d50e90add8b4a978395e893e81954d19d58a7c5 Mon Sep 17 00:00:00 2001 From: Jason Winter Date: Wed, 2 Sep 2026 10:40:41 +0200 Subject: [PATCH 0280/1317] net: usb: cx82310_eth: drop URB after 0xffff reboot sentinel to prevent partial_data heap overflow The 0xffff length sentinel detects a router reboot and schedules re-enabling of ethernet mode, but then falls through to the rest of the loop body. The next check is } else if (len > CX82310_MTU) { which is the else of the just-matched if -- it never fires for len == 0xffff. The MTU bound that normally caps the incomplete-packet save path is silently bypassed. With 0xffff > skb->len always true (rx_urb_size is 4096), the incomplete-packet branch saves dev->partial_len = skb->len bytes into dev->partial_data. partial_data is kmalloc(hard_mtu) = kmalloc(CX82310_MTU + 2) = 1516 bytes, but skb->len after the 2-byte header pull can be up to 4094. A device that sends a 4096-byte URB starting with [0xff 0xff] therefore copies 4094 device-provided bytes into a buffer allocated for 1516 bytes, exceeding its requested size by 2578 bytes. The next URB then reads dev->partial_len (4094) back from the same 1516-byte buffer and dev->partial_rem (65535 - 4094 = 61441) from the new URB's ~4KB skb, both well past their allocations, and delivers the spliced result as a 64KB "frame" to the network stack. Bail out of rx_fixup after scheduling the re-enable work; the remainder of a reboot-marker URB is not meaningful packet data. This restores the invariant that partial_len < CX82310_MTU + 2 on the save path, since every other route there has already passed the MTU check. Fixes: ca139d76b0d9 ("cx82310_eth: re-enable ethernet mode after router reboot") Signed-off-by: Jason Winter Link: https://patch.msgid.link/BESP194MB283265DDDC63B6B78D8D34FBB8B72@BESP194MB2832.EURP194.PROD.OUTLOOK.COM Signed-off-by: Jakub Kicinski --- drivers/net/usb/cx82310_eth.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/cx82310_eth.c b/drivers/net/usb/cx82310_eth.c index 068acb052adb..5df657acf3d5 100644 --- a/drivers/net/usb/cx82310_eth.c +++ b/drivers/net/usb/cx82310_eth.c @@ -282,6 +282,7 @@ static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb) if (len == 0xffff) { netdev_info(dev->net, "router was rebooted, re-enabling ethernet mode"); schedule_work(&priv->reenable_work); + return 0; } else if (len > CX82310_MTU) { netdev_err(dev->net, "RX packet too long: %d B\n", len); return 0; From 1668a31e3b1ad358d981ddb6dbd3db1fe0533621 Mon Sep 17 00:00:00 2001 From: Alexandra Winter Date: Wed, 2 Sep 2026 16:34:38 +0200 Subject: [PATCH 0281/1317] dibs: Unregister dibs_class after error In case dibs_loopback_init() fails, e.g. because of -ENOMEM, dibs_init() must unregister dibs_class. Otherwise dibs_class and /sys/class/dibs exist even though the functionality is not available. A retry to load the module fails with -EEXIST. Unregister dibs_class in the error path of dibs_init. Note that before commit ad3dfa80be76 ("dibs: change dibs_class to a const struct") class_destroy(dibs_class) is required instead of class_unregister(&dibs_class). Fixes: 804737349813 ("dibs: Create class dibs") Signed-off-by: Alexandra Winter Link: https://patch.msgid.link/20260902143438.426664-1-wintera@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/dibs/dibs_main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/dibs/dibs_main.c b/drivers/dibs/dibs_main.c index 2b53a9d277dc..20c50997a7cf 100644 --- a/drivers/dibs/dibs_main.c +++ b/drivers/dibs/dibs_main.c @@ -251,13 +251,19 @@ static int __init dibs_init(void) rc = class_register(&dibs_class); if (rc) - return rc; + goto err; rc = dibs_loopback_init(); if (rc) - pr_err("%s fails with %d\n", __func__, rc); + goto err_unregister; return rc; + +err_unregister: + class_unregister(&dibs_class); +err: + pr_err("%s fails with %d\n", __func__, rc); + return rc; } static void __exit dibs_exit(void) From 907a56ab3eb8a58500a58daa76087f17bb2b6826 Mon Sep 17 00:00:00 2001 From: Alexandra Winter Date: Wed, 2 Sep 2026 16:37:33 +0200 Subject: [PATCH 0282/1317] s390/ism: folio_put() after error dmb->cpu_addr was allocated via folio_alloc(). Use folio_put() instead of kfree() in the error exit of ism_alloc_dmb() to avoid slab allocator corruption. While at it, reset dmb->cpu_addr after folio_put to avoid unintentional UAF by future callers. Fixes: 83781384a96b ("s390/ism: Properly fix receive message buffer allocation") Signed-off-by: Alexandra Winter Reviewed-by: Gerd Bayer Link: https://patch.msgid.link/20260902143733.433574-1-wintera@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/ism_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/s390/net/ism_drv.c b/drivers/s390/net/ism_drv.c index 242da20f27e0..035b233abb4e 100644 --- a/drivers/s390/net/ism_drv.c +++ b/drivers/s390/net/ism_drv.c @@ -231,6 +231,7 @@ static void ism_free_dmb(struct ism_dev *ism, struct dibs_dmb *dmb) dma_unmap_page(&ism->pdev->dev, dmb->dma_addr, dmb->dmb_len, DMA_FROM_DEVICE); folio_put(virt_to_folio(dmb->cpu_addr)); + dmb->cpu_addr = NULL; } static int ism_alloc_dmb(struct ism_dev *ism, struct dibs_dmb *dmb) @@ -274,7 +275,8 @@ static int ism_alloc_dmb(struct ism_dev *ism, struct dibs_dmb *dmb) return 0; out_free: - kfree(dmb->cpu_addr); + folio_put(folio); + dmb->cpu_addr = NULL; out_bit: clear_bit(dmb->idx, ism->sba_bitmap); return rc; From 98fc57d167446b95b4e719815fe79edef93f8e7a Mon Sep 17 00:00:00 2001 From: Seungwon Bae Date: Thu, 3 Sep 2026 00:59:56 +0900 Subject: [PATCH 0283/1317] vxlan: reject dynamic fdb entries that reference a nexthop id The commit cited in the Fixes tag allowed VXLAN FDB entries to point to FDB nexthops so that overlay traffic could be load balanced across multiple VTEPs. Such entries can only be configured from user space, cannot be learned and cannot roam. They only make sense with a user space control plane such as E-VPN where data plane learning is disabled. Despite that, the VXLAN driver does not currently prevent such entries from being configured with the "dynamic" flag. The per-nexthop FDB list is only protected by the per-device hash lock, which is not sufficient when two VXLAN devices point to the same FDB nexthop and therefore share the list. Aging runs in softirq context without RTNL, so an entry deleted by one device can race with an addition or deletion from the other, leading to list corruption: list_del corruption. next->prev should be ffff8881069d9548, but was dead000000000122. (next=ffff8881069d9448) WARNING: CPU: 0 PID: 90 at lib/list_debug.c:65 __list_del_entry_valid_or_report+0x1aa/0x210 ... vxlan_fdb_destroy+0x5b8/0xad0 vxlan_cleanup+0x328/0x450 call_timer_fn+0x2a/0x1c0 run_timer_softirq+0x18c/0x210 BUG: KASAN: slab-use-after-free in vxlan_fdb_destroy Fix this by rejecting the bogus configuration of dynamic FDB entries that point to FDB nexthops, both when created and when an existing entry is updated. As such, the per-nexthop FDB list is only ever mutated under the RTNL lock. Add test cases to make sure that this does not regress in the future. Fixes: 1274e1cc4226 ("vxlan: ecmp support for mac fdb entries") Suggested-by: Ido Schimmel Signed-off-by: Seungwon Bae Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260902155956.296699-1-qotmddnjs@ajou.ac.kr Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 11 ++++++++ tools/testing/selftests/net/fib_nexthops.sh | 28 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 459f19f7071e..be95af64a1f5 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -996,6 +996,12 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, return -EOPNOTSUPP; } + if (rcu_access_pointer(f->nh) && + !(state & (NUD_PERMANENT | NUD_NOARP))) { + NL_SET_ERR_MSG(extack, "Cannot make a nexthop fdb dynamic"); + return -EOPNOTSUPP; + } + /* Do not allow an externally learned entry to take over an entry added * by the user. */ @@ -1257,6 +1263,11 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], if (err) return err; + if (nhid && !(ndm->ndm_state & (NUD_PERMANENT | NUD_NOARP))) { + NL_SET_ERR_MSG(extack, "A nexthop fdb cannot be dynamic"); + return -EINVAL; + } + if (vxlan->default_dst.remote_ip.sa.sa_family != ip.sa.sa_family) return -EAFNOSUPPORT; diff --git a/tools/testing/selftests/net/fib_nexthops.sh b/tools/testing/selftests/net/fib_nexthops.sh index 3d347126730a..431d7bed7622 100755 --- a/tools/testing/selftests/net/fib_nexthops.sh +++ b/tools/testing/selftests/net/fib_nexthops.sh @@ -533,6 +533,20 @@ ipv6_fdb_grp_fcnal() run_cmd "$BRIDGE fdb add 02:02:00:00:00:14 dev vx10 nhid 61 self" log_test $? 255 "Fdb mac add with nexthop" + # fdb entries with a nexthop group cannot be aged out + run_cmd "$BRIDGE fdb add 02:02:00:00:00:15 dev vx10 nhid 102 self static" + log_test $? 0 "Fdb mac add with nexthop group and static state" + + run_cmd "$BRIDGE fdb add 02:02:00:00:00:16 dev vx10 nhid 102 self dynamic" + log_test $? 255 "Fdb mac add with nexthop group and dynamic state" + + run_cmd "$BRIDGE fdb add 02:02:00:00:00:17 dev vx10 nhid 102 self" + run_cmd "$BRIDGE fdb replace 02:02:00:00:00:17 dev vx10 dst 2001:db8:91::11 self dynamic" + log_test $? 255 "Fdb mac replace with nexthop group and dynamic state" + + run_cmd "$BRIDGE fdb append 02:02:00:00:00:17 dev vx10 dst 2001:db8:91::11 self dynamic" + log_test $? 255 "Fdb mac append with nexthop group and dynamic state" + run_cmd "$IP -6 ro add 2001:db8:101::1/128 nhid 66" log_test $? 2 "Route add with fdb nexthop" @@ -669,6 +683,20 @@ ipv4_fdb_grp_fcnal() run_cmd "$BRIDGE fdb add 02:02:00:00:00:14 dev vx10 nhid 12 self" log_test $? 255 "Fdb mac add with nexthop" + # fdb entries with a nexthop group cannot be aged out + run_cmd "$BRIDGE fdb add 02:02:00:00:00:15 dev vx10 nhid 102 self static" + log_test $? 0 "Fdb mac add with nexthop group and static state" + + run_cmd "$BRIDGE fdb add 02:02:00:00:00:16 dev vx10 nhid 102 self dynamic" + log_test $? 255 "Fdb mac add with nexthop group and dynamic state" + + run_cmd "$BRIDGE fdb add 02:02:00:00:00:17 dev vx10 nhid 102 self" + run_cmd "$BRIDGE fdb replace 02:02:00:00:00:17 dev vx10 dst 10.0.0.3 self dynamic" + log_test $? 255 "Fdb mac replace with nexthop group and dynamic state" + + run_cmd "$BRIDGE fdb append 02:02:00:00:00:17 dev vx10 dst 10.0.0.3 self dynamic" + log_test $? 255 "Fdb mac append with nexthop group and dynamic state" + run_cmd "$IP ro add 172.16.0.0/22 nhid 16" log_test $? 2 "Route add with fdb nexthop" From 66ab4c59b74db7ab53a1c9083feaaede393a96a0 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 2 Sep 2026 17:29:08 -0400 Subject: [PATCH 0284/1317] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations Several subsystems allocate ring buffers sized by dev->tx_queue_len with no upper bound. An unprivileged user (via unshare -Urn) can set a huge tx_queue_len and exhaust global memory with ring allocations: - pfifo_fast: pfifo_fast_init() and pfifo_fast_change_tx_queue_len() allocate 3 skb_array rings of tx_queue_len entries each. - tun: tun_queue_resize() and the queue-attach path resize ptr_rings to tx_queue_len on the NETDEV_CHANGE_TX_QUEUE_LEN notifier. - tap (macvtap/ipvtap): tap_queue_resize() and tap_init() resize/init ptr_rings to tx_queue_len on the same notifier. netif_change_tx_queue_len() is the single entry point for IFLA_TXQLEN, sysfs, and the SIOCSIFTXQLEN ioctl. Cap new_len at S16_MAX (32767) there so the oversized value is rejected at set time. This takes effect whether the device is up or down, before dev->tx_queue_len is written, before any notifier fires, and before any ring is allocated. The "> S16_MAX" check also subsumes the previous unsigned-long truncation test, and a negative ifr_qlen from the ioctl lands far above the cap after conversion, so both old failure modes are covered by the one comparison. tx_queue_len is ambigious: both a per-ring sizing multiplier and a default queue-length/limit knob for consumers that allocate nothing at set time (pfifo/bfifo/gred/plug/sfb limits, htb direct_qlen, qfq max_classes, teql). 32767 is chosen as the largest value NLA_POLICY_FULL_RANGE can express for the u32 IFLA_TXQLEN policy in patch 2/3 while staying a legitimate queue length on high-BDP paths; the ring-memory trade-off of a shared knob is disclosed below. Conditions to recreate the bug: - CONFIG_NET_SCHED=y, CONFIG_VETH=y, CONFIG_USER_NS=y, CONFIG_NET_NS=y. - Unprivileged user in a fresh user+net namespace (unshare -Urn). - pfifo_fast: create veth pairs, set tx_queue_len to 500000, attach mq+pfifo_fast. ~28 iterations OOMs a 2GB guest. - tun: create 50 tun devices with IFF_MULTI_QUEUE, set tx_queue_len to 500000, open 8 queues each. ~1.6GB of ptr_ring allocations OOMs a 512MB guest. - tap: same as tun with IFF_TAP. ~960MB OOMs a 512MB guest. - On the fixed kernel the oversized tx_queue_len is rejected with -ERANGE at set time (all four paths: RTM_SETLINK, RTM_NEWLINK create, sysfs, ioctl - the latter two via this check, the former two via this check and the 2/3 parse policy respectively). Fixes: 6a643ddb5624 ("net: introduce helper dev_change_tx_queue_len()") Reported-by: Vega Closes: https://lore.kernel.org/netdev/20260828121902.66837-1-jhs@mojatatu.com/ Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2899.v2.20260901233641@mojatatu.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index 290e0f099e6b..ecfbd72d5d1a 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -9982,7 +9982,7 @@ int netif_change_tx_queue_len(struct net_device *dev, unsigned long new_len) unsigned int orig_len = dev->tx_queue_len; int res; - if (new_len != (unsigned int)new_len) + if (new_len > S16_MAX) return -ERANGE; if (new_len != orig_len) { From 1aa9e143bf51405665a793d4cc925e1c4f0c5922 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 2 Sep 2026 17:29:09 -0400 Subject: [PATCH 0285/1317] net: reject oversized tx_queue_len at netlink parse time rtnl_create_link() assigns IFLA_TXQLEN directly to dev->tx_queue_len without going through netif_change_tx_queue_len(), so a device created with "ip link add ... txqueuelen 500000" bypasses the S16_MAX cap and still triggers the oversized ring allocations in pfifo_fast, tun and tap. The veth peer nest (rtnl_nla_parse_ifinfomsg()) and the RTM_NEWLINK-on-existing-device path reach the same sinks. Enforce the cap in ifla_policy instead: IFLA_TXQLEN becomes NLA_POLICY_FULL_RANGE(NLA_U32, &txqlen_range) with txqlen_range = { .min = 0, .max = S16_MAX }. All netlink consumers parse against this policy - rtnl_setlink(), rtnl_newlink() (create and change), and the veth peer nest - so every netlink path is capped at parse time and rejects the attribute with -ERANGE plus a proper "integer out of range" extack message before any device state is modified (the RTM_SETLINK half-application wart is gone with it). Document the bound in the rt-link.yaml netlink spec. Conditions to recreate the bug: - CONFIG_NET_SCHED=y, CONFIG_VETH=y, CONFIG_USER_NS=y, CONFIG_NET_NS=y. - Unprivileged user in a fresh user+net namespace (unshare -Urn): ip link add v0 txqueuelen 500000 type veth peer name v1 -> on the fixed kernel this is rejected with -ERANGE ("integer out of range" extack) instead of installing an oversized tx_queue_len that later inflates pfifo_fast/tun/tap ring allocations. - ip link set v0 txqueuelen 500000 is likewise rejected at parse time. Fixes: 38f7b870d4a6 ("[RTNETLINK]: Link creation API") Reported-by: Vega Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2899.v2.20260901233641@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-link.yaml | 2 ++ net/core/rtnetlink.c | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index b80c2ac3ac31..99f6fba456cc 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -898,6 +898,8 @@ attribute-sets: - name: txqlen type: u32 + checks: + max: 32767 - name: map type: binary diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 81c5a6104dea..be9d1625bac3 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -2287,6 +2287,11 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, return -EMSGSIZE; } +static const struct netlink_range_validation txqlen_range = { + .min = 0, + .max = S16_MAX, +}; + static const struct nla_policy ifla_policy[IFLA_MAX+1] = { [IFLA_UNSPEC] = { .strict_start_type = IFLA_DPLL_PIN }, [IFLA_IFNAME] = { .type = NLA_STRING, .len = IFNAMSIZ-1 }, @@ -2297,7 +2302,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = { [IFLA_LINK] = { .type = NLA_U32 }, [IFLA_MASTER] = { .type = NLA_U32 }, [IFLA_CARRIER] = { .type = NLA_U8 }, - [IFLA_TXQLEN] = { .type = NLA_U32 }, + [IFLA_TXQLEN] = NLA_POLICY_FULL_RANGE(NLA_U32, &txqlen_range), [IFLA_WEIGHT] = { .type = NLA_U32 }, [IFLA_OPERSTATE] = { .type = NLA_U8 }, [IFLA_LINKMODE] = { .type = NLA_U8 }, From 0a7252d7f85478080385de4c1072085e30849fe3 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 2 Sep 2026 17:29:10 -0400 Subject: [PATCH 0286/1317] selftests: tc-testing: add tx_queue_len cap regression tests Add nine test cases for the S16_MAX tx_queue_len cap to the pfifo_fast suite. Netlink cases exercise the ifla_policy bound (2/3); the two new sysfs cases exercise the netif_change_tx_queue_len() choke point that 1/3 owns (SIOCSIFTXQLEN shares it; the ioctl is not portably reachable from tdc): - dbe3: set txqueuelen 32767 (S16_MAX) - accepted, pins the exact boundary value. - b50e: set txqueuelen 32768 - rejected with -ERANGE. - 40f8: write 32768 to /sys/class/net/*/tx_queue_len - rejected (covers patch 1/3 directly; netlink cannot reach this path). - 4b6e: write 32767 via sysfs - accepted, boundary positive control for the patch-1 path. - b90d: create a dummy with txqueuelen 32767 - accepted. - 57ab: create a dummy with txqueuelen 32768 - rejected at netlink parse time. - e777: create a dummy with txqueuelen 500000 - rejected (the v1 bypass path flagged by review). - 31ac: create a veth with an oversized txqueuelen on the peer nest - rejected (the peer nest is parsed against ifla_policy too). - b567: create a veth with txqueuelen on both ends within the cap - accepted (positive control for the peer nest). The three negative-creation verifies assert device absence ("ip -o link show" must not contain the device), not merely absence of a qlen pattern - the device does not exist when creation fails, so the exit code carries the signal and the verify adds content. The v1 04b5 "resize rollback" case is dropped: with the cap checked first, netif_change_tx_queue_len() returns -ERANGE before the write, the notifier or any qdisc resize, so the case exercised no resize and no rollback. It was also nondeterministic: pre-patch, the resize issues three ~11 MB kvmallocs for qlen 500000 which normally succeed, so the case passed on an unfixed kernel only under memory pressure - its outcome depended on the test host's free memory. Test commands run inside the netns, but nsPlugin creates the veth peer in the root namespace, so the teardown deletes the in-ns end only; deleting the peer via the pair is implicit. Note: iproute2 treats "txqueuelen" appearing after "type X" as a link-type attribute and silently drops it, so the creation cases place it before "type" to actually reach the kernel. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2899.v2.20260901233641@mojatatu.com.3 Signed-off-by: Jakub Kicinski --- .../tc-tests/qdiscs/pfifo_fast.json | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json index 30da27fe8806..a6e25e76ecb1 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json @@ -105,5 +105,209 @@ "teardown": [ "$TC qdisc del dev $DUMMY handle 1: root" ] + }, + { + "id": "dbe3", + "name": "Set tx_queue_len to S16_MAX boundary (32767 accepted)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$IP link set dev $DUMMY txqueuelen 32767", + "expExitCode": "0", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 32767$", + "matchCount": "1", + "teardown": [] + }, + { + "id": "b50e", + "name": "Reject tx_queue_len above S16_MAX at set time (32768)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$IP link set dev $DUMMY txqueuelen 32768", + "expExitCode": "2", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 1000$", + "matchCount": "1", + "teardown": [] + }, + { + "id": "40f8", + "name": "Reject tx_queue_len above S16_MAX via sysfs (32768)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "sh -c 'echo 32768 > /sys/class/net/$DUMMY/tx_queue_len'", + "expExitCode": "1", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 1000$", + "matchCount": "1", + "teardown": [] + }, + { + "id": "4b6e", + "name": "Set tx_queue_len to S16_MAX via sysfs (32767 accepted)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "sh -c 'echo 32767 > /sys/class/net/$DUMMY/tx_queue_len'", + "expExitCode": "0", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 32767$", + "matchCount": "1", + "teardown": [] + }, + { + "id": "b90d", + "name": "Create device with tx_queue_len at S16_MAX boundary (32767 accepted)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DUMMY", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 32767 type dummy", + "expExitCode": "0", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 32767$", + "matchCount": "1", + "teardown": [ + [ + "$IP link del dev $DUMMY", + 0, + 1 + ] + ] + }, + { + "id": "57ab", + "name": "Reject creating device with tx_queue_len above S16_MAX (32768)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DUMMY", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 32768 type dummy", + "expExitCode": "2", + "verifyCmd": "$IP -o link show", + "matchPattern": "^[0-9]+: $DUMMY", + "matchCount": "0", + "teardown": [] + }, + { + "id": "e777", + "name": "Reject creating device with oversized tx_queue_len (500000)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DUMMY", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 500000 type dummy", + "expExitCode": "2", + "verifyCmd": "$IP -o link show", + "matchPattern": "^[0-9]+: $DUMMY", + "matchCount": "0", + "teardown": [] + }, + { + "id": "31ac", + "name": "Reject veth peer nest tx_queue_len above S16_MAX at create", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DEV1", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DEV1 type veth peer name $DEV0 txqueuelen 500000", + "expExitCode": "2", + "verifyCmd": "$IP -o link show", + "matchPattern": "^[0-9]+: $DEV1", + "matchCount": "0", + "teardown": [] + }, + { + "id": "b567", + "name": "Accept veth peer nest tx_queue_len within S16_MAX", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DEV1", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DEV1 txqueuelen 100 type veth peer name $DEV0 txqueuelen 200", + "expExitCode": "0", + "verifyCmd": "$IP link show", + "matchPattern": "qlen (100|200)$", + "matchCount": "2", + "teardown": [ + [ + "$IP link del dev $DEV0", + 0, + 1 + ] + ] } ] From 7980325b2f71e3f65c1323c39792e2455da6fab6 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 1 Sep 2026 04:42:17 +0000 Subject: [PATCH 0287/1317] pds_core: fix cmd_regs access racing BAR unmap on reset pdsc_reset_prepare() and pdsc_reset_done()'s pdsc_map_bars() error path clear/iounmap cmd_regs without devcmd_lock, and pdsc_legacy_firmware_update()'s download loop derefs cmd_regs after dropping and retaking the lock without re-checking. An FLR concurrent with a devlink flash can unmap cmd_regs under an in-flight devcmd, causing a NULL deref or a write to unmapped MMIO. Take devcmd_lock across the BAR unmap/remap, and re-check cmd_regs in the download loop. Only the PF maps cmd_regs and runs devcmd, so skip the unmap on a VF, as pdsc_remove() and pdsc_reset_done() already do. A reset that completes entirely within the unlocked window is not a correctness problem for the image: the device clears its update session, so a resumed download is rejected, and it verifies the staged image before writing a flash slot, reporting PDS_RC_BAD_FW rather than activating it. pdsc_unmap_bars() also clears info_regs, intr_status and intr_ctrl. The interrupt and start/stop readers of those are quiesced before the unmap by pdsc_fw_down(), which frees the interrupts and tears down the queues. The debugfs readers are not, since those files outlive a reset; that is pre-existing and out of scope here. Fixes: e96094c1d11c ("pds_core: Clear BARs on reset") Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260708212222.296202-1-nikhil.rao%40amd.com?part=3 Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260901044219.1361466-2-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/fw.c | 10 +++++++++- drivers/net/ethernet/amd/pds_core/main.c | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c index 5ccf017f6af4..19899bf38d40 100644 --- a/drivers/net/ethernet/amd/pds_core/fw.c +++ b/drivers/net/ethernet/amd/pds_core/fw.c @@ -171,8 +171,10 @@ pdsc_legacy_firmware_update(struct pdsc *pdsc, dev_info(pdsc->dev, "Installing firmware\n"); - if (!pdsc->cmd_regs) + if (!pdsc->cmd_regs) { + NL_SET_ERR_MSG_MOD(extack, "BARs not mapped"); return -ENXIO; + } dl = priv_to_devlink(pdsc); devlink_flash_update_status_notify(dl, "Preparing to flash", @@ -198,6 +200,12 @@ pdsc_legacy_firmware_update(struct pdsc *pdsc, copy_sz = min_t(unsigned int, buf_sz, fw->size - offset); mutex_lock(&pdsc->devcmd_lock); + if (!pdsc->cmd_regs) { + mutex_unlock(&pdsc->devcmd_lock); + err = -ENXIO; + NL_SET_ERR_MSG_MOD(extack, "Device reset during flash"); + goto err_out; + } memcpy_toio(&pdsc->cmd_regs->data, fw->data + offset, copy_sz); err = pdsc_devcmd_fw_download_locked(pdsc, data_addr, offset, copy_sz); diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index bb79e7476370..6e1079f9ba0f 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -513,7 +513,11 @@ static void pdsc_reset_prepare(struct pci_dev *pdev) pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev); } - pdsc_unmap_bars(pdsc); + if (!pdev->is_virtfn) { + mutex_lock(&pdsc->devcmd_lock); + pdsc_unmap_bars(pdsc); + mutex_unlock(&pdsc->devcmd_lock); + } pci_release_regions(pdev); if (pci_is_enabled(pdev)) pci_disable_device(pdev); @@ -543,7 +547,9 @@ static void pdsc_reset_done(struct pci_dev *pdev) return; } + mutex_lock(&pdsc->devcmd_lock); err = pdsc_map_bars(pdsc); + mutex_unlock(&pdsc->devcmd_lock); if (err) return; } From 73608de7e59246b4b533c1ffaee158a7048e186e Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 1 Sep 2026 04:42:18 +0000 Subject: [PATCH 0288/1317] pds_core: don't release PCI regions for VFs on reset pdsc_reset_prepare() called pci_release_regions() unconditionally, but only PFs call pci_request_regions() (pdsc_init_pf). On a VF FLR this makes the kernel warn "Trying to free nonexistent resource". Fixes: ffa55858330f ("pds_core: implement pci reset handlers") Reported-by: sashiko-bot Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260804235946.177762-1-nikhil.rao%40amd.com Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260901044219.1361466-3-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index 6e1079f9ba0f..a971c66d36f9 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -517,8 +517,8 @@ static void pdsc_reset_prepare(struct pci_dev *pdev) mutex_lock(&pdsc->devcmd_lock); pdsc_unmap_bars(pdsc); mutex_unlock(&pdsc->devcmd_lock); + pci_release_regions(pdev); } - pci_release_regions(pdev); if (pci_is_enabled(pdev)) pci_disable_device(pdev); pdsc_deferred_dma_free(pdsc); From 80dd7e754b3aa9637a0758ad93fa209f9650ec48 Mon Sep 17 00:00:00 2001 From: Sahil Chandna Date: Tue, 1 Sep 2026 07:17:58 -0500 Subject: [PATCH 0289/1317] net: mana: Reserve extra CQ slot for the fence completion CQE The RX completion queue is sized to hold exactly one CQE per posted RX WQE. MANA_FENCE_RQ makes hardware post an additional CQE_RX_OBJECT_FENCE after the packet CQEs. The current sizing reserves no extra slot for it and in rare cases, CQ has no guaranteed slot for the fence CQE when it is full of packet CQEs. This can lead to dropping the fence completion while the driver waits holding RTNL lock throughout the timeout duration. Reserve one extra CQE slot for CQE_RX_OBJECT_FENCE. mana_gd_alloc_memory() requires queue_size to be a power-of-two and at least MANA_PAGE_SIZE; the reservation pushes cq_size past a power-of-two, so round up the CQ size in mana_create_rxq(). Cc: stable@vger.kernel.org Fixes: 6cc74443a773 ("net: mana: Add RX fencing") Signed-off-by: Sahil Chandna Reviewed-by: Haiyang Zhang Link: https://patch.msgid.link/20260901121837.3503240-1-sahilchandna@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_en.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 7a1ac853e3ab..45a7520491a6 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -2986,6 +2986,10 @@ static int mana_alloc_rx_wqe(struct mana_port_context *apc, *cq_size += COMP_ENTRY_SIZE; } + /* Reserve an extra slot for Fence completion + * event (CQE_RX_OBJECT_FENCE) in case RX CQ is full. + */ + *cq_size += COMP_ENTRY_SIZE; return 0; } @@ -3080,7 +3084,7 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, goto out; rq_size = MANA_PAGE_ALIGN(rq_size); - cq_size = MANA_PAGE_ALIGN(cq_size); + cq_size = MANA_PAGE_ALIGN(roundup_pow_of_two(cq_size)); /* Create RQ */ memset(&spec, 0, sizeof(spec)); From d96171d911e3b89ca2957c04264019cf2f96287b Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Wed, 19 Aug 2026 17:58:22 +0000 Subject: [PATCH 0290/1317] powerpc: pci-ioda: Fix the stale irq chip reference The commit f0ac60e6e311 ("powerpc/powernv/pci: Switch to use msi_create_parent_irq_domain()") removed the legacy MSI irq chip pnv_pci_msi_irq_chip but left behind the static definition of it and its reference in is_pnv_opal_msi(). The KVM IRQ bypass for vfio devices is broken because the comparision in is_pnv_opal_msi() fails on the comparision with stale unused variable showing the below errors in dmesg. kvmppc_set_passthru_irq_hv: Could not assign IRQ map for (X,Y) kvmppc_set_passthru_irq (irq X, gsi Y) fails: -2 vfio-pci A:B:C.D irq bypass producer (eventfd Z) registration fails: -2 The patch removes the stale variable definition and fixes the is_pnv_opal_msi() by comparing against the chip name prefix. Fixes: f0ac60e6e311 ("powerpc/powernv/pci: Switch to use msi_create_parent_irq_domain()") Cc: stable@kernel.org Signed-off-by: Shivaprasad G Bhat Tested-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/178716225364.1437.6201568081502251835.stgit@linux.ibm.com --- arch/powerpc/platforms/powernv/pci-ioda.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/powerpc/platforms/powernv/pci-ioda.c b/arch/powerpc/platforms/powernv/pci-ioda.c index 32ecbc46e74b..728a5610d167 100644 --- a/arch/powerpc/platforms/powernv/pci-ioda.c +++ b/arch/powerpc/platforms/powernv/pci-ioda.c @@ -1623,15 +1623,13 @@ int64_t pnv_opal_pci_msi_eoi(struct irq_data *d) return opal_pci_msi_eoi(phb->opal_id, d->parent_data->hwirq); } -static struct irq_chip pnv_pci_msi_irq_chip; - /* * Returns true iff chip is something that we could call * pnv_opal_pci_msi_eoi for. */ bool is_pnv_opal_msi(struct irq_chip *chip) { - return chip == &pnv_pci_msi_irq_chip; + return chip && chip->name && str_has_prefix(chip->name, "PNV-"); } EXPORT_SYMBOL_GPL(is_pnv_opal_msi); @@ -1728,7 +1726,7 @@ static const struct msi_parent_ops pnv_msi_parent_ops = { .chip_flags = MSI_CHIP_FLAG_SET_EOI, .bus_select_token = DOMAIN_BUS_NEXUS, .bus_select_mask = MATCH_PCI_MSI, - .prefix = "PNV-", + .prefix = "PNV-", /* Note: is_pnv_opal_msi() uses this */ .init_dev_msi_info = pnv_init_dev_msi_info, }; From c5e68706527968282e49de205cc2b935823cb88a Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Tue, 14 Jul 2026 17:16:23 +0000 Subject: [PATCH 0291/1317] powerpc/eeh: Fix recursive locking on devices without EEH sensitive driver The commit 1010b4c012b0 ("powerpc/eeh: Make EEH driver device hotplug safe") refactored the EEH code such that the pci_rescan_remove_lock is held at the beginning of eeh_handle_normal_event() and the eeh_reset_device() is called with that lock being held. Looks like the commit missed to remove the existing lock/unlock inside eeh_rmv_device() which is no longer necessary. This is causing the eehd to hang on the lock which it actually holds when that code path is taken. [<0>] 0xc00000011c78f870 [<0>] __switch_to+0xfc/0x1a0 [<0>] pci_lock_rescan_remove+0x30/0x44 [<0>] eeh_rmv_device+0x290/0x2e0 [<0>] eeh_pe_dev_traverse+0x80/0x130 [<0>] eeh_reset_device+0xcc/0x23c [<0>] eeh_handle_normal_event+0x830/0xa80 [<0>] eeh_event_handler+0xf8/0x190 [<0>] kthread+0x194/0x1b0 [<0>] start_kernel_thread+0x14/0x18 The issue is seen for cases where the errors are detected on the PHB directly AND|OR for devices where the driver error_detected() returns PCI_ERS_RESULT_NEED_RESET, and driver being not EEH sensitive(i.e no error handlers like slot_reset(), resume() etc defined). Fixes: 1010b4c012b0 ("powerpc/eeh: Make EEH driver device hotplug safe") Cc: stable Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Shivaprasad G Bhat Reviewed-by: Amit Machhiwal Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/178404937381.913.2759874335293830160.stgit@linux.ibm.com --- arch/powerpc/kernel/eeh_driver.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c index 028f69158532..d64cce17a4e0 100644 --- a/arch/powerpc/kernel/eeh_driver.c +++ b/arch/powerpc/kernel/eeh_driver.c @@ -533,9 +533,7 @@ static void eeh_rmv_device(struct eeh_dev *edev, void *userdata) if (rmv_data) list_add(&edev->rmv_entry, &rmv_data->removed_vf_list); } else { - pci_lock_rescan_remove(); pci_stop_and_remove_bus_device(dev); - pci_unlock_rescan_remove(); } } From 8a4978c17a144a6583478cce933bcb2dbb25298d Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Fri, 26 Jun 2026 09:13:03 +0000 Subject: [PATCH 0292/1317] powerpc/rtas_pci: No hotplug on permanently removed device on pSeries The eeh_driver disables and offlines the PE permanently when it exceeds the freeze count beyond eeh_max_freeze within the last hour. The PE is only offline, so the device tree entries, eeh device references are all intact till the real unplug of the device from the guest/host takes place. On pSeries, with a new hotplug of any PCI device, the drmgr initiates a system-wide PCI rescan, which finds devices offlined by the eeh_driver and there will be attempts to bring them online. This leads to recurring EEHs either at the config read time itself or a bit later depending on the type of the problem. For PowerNV, the commit d2b0f6f77ee5 ("powerpc/eeh: No hotplug on permanently removed dev") introduced the EEH_DEV_REMOVED flag to prevent such inadvertent rescans on hierarchical toplogies relavent in Baremetal setups. For pSeries, such topologies don't really make sense as the devices are either part of the same PE OR exposed as independent devices on multiple virtual PHBs. However, the inadvertent rescans are still a possibility with either hotplug of a new device or otherwise with manual system-wide pci bus rescan attempts. So the patch checks for EEH_DEV_REMOVED before allowing config space access just like PowerNV, making the PCI core omit the PE, and thus preventing subsequent EEH recurances. The patch is tested on PowerVM and KVM machines with single and multi-function devices, and on the devices behind a switch. The unplug of the affected devices post EEH removal is also working fine as expected. Signed-off-by: Shivaprasad G Bhat Reported-by: Tasmiya Nalatwad Tested-by: Tasmiya Nalatwad Reviewed-by: Harsh Prateek Bora References: d2b0f6f77ee5 ("powerpc/eeh: No hotplug on permanently removed dev") Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/178246517230.1267.12206176311111155505.stgit@linux.ibm.com --- arch/powerpc/kernel/rtas_pci.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/powerpc/kernel/rtas_pci.c b/arch/powerpc/kernel/rtas_pci.c index fccf96e897f6..206c825225c2 100644 --- a/arch/powerpc/kernel/rtas_pci.c +++ b/arch/powerpc/kernel/rtas_pci.c @@ -54,6 +54,10 @@ int rtas_pci_dn_read_config(struct pci_dn *pdn, int where, int size, u32 *val) if (!config_access_valid(pdn, where)) return PCIBIOS_BAD_REGISTER_NUMBER; #ifdef CONFIG_EEH + if (pdn->edev && + (pdn->edev->mode & EEH_DEV_REMOVED)) + return PCIBIOS_DEVICE_NOT_FOUND; + if (pdn->edev && pdn->edev->pe && (pdn->edev->pe->state & EEH_PE_CFG_BLOCKED)) return PCIBIOS_SET_FAILED; @@ -105,6 +109,10 @@ int rtas_pci_dn_write_config(struct pci_dn *pdn, int where, int size, u32 val) if (!config_access_valid(pdn, where)) return PCIBIOS_BAD_REGISTER_NUMBER; #ifdef CONFIG_EEH + if (pdn->edev && + (pdn->edev->mode & EEH_DEV_REMOVED)) + return PCIBIOS_DEVICE_NOT_FOUND; + if (pdn->edev && pdn->edev->pe && (pdn->edev->pe->state & EEH_PE_CFG_BLOCKED)) return PCIBIOS_SET_FAILED; From c6755be4838d6ccd641effbcdc3d917b82631ff9 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 9 Aug 2026 18:24:01 +0200 Subject: [PATCH 0293/1317] powerpc/kexec_file: Use inclusive range checks in add_usable_mem() add_usable_mem() adds usable memory ranges for the kdump kernel. The ranges are inclusive, but the partial overlap check uses exclusive comparisons. This skips ranges with base == loc_end or end == loc_base. Use inclusive comparisons instead. Fixes: 7c64e21a1c5a ("powerpc/kexec_file: Restrict memory usage of kdump kernel") Signed-off-by: Thorsten Blum Reviewed-by: Sourabh Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260809162403.18142-2-thorsten.blum@linux.dev --- arch/powerpc/kexec/file_load_64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c index 6075b1c88511..c2ed0d1c92e7 100644 --- a/arch/powerpc/kexec/file_load_64.c +++ b/arch/powerpc/kexec/file_load_64.c @@ -113,7 +113,7 @@ static int add_usable_mem(struct umem_info *um_info, u64 base, u64 end) loc_end = um_info->ranges[i].end; if (loc_base >= base && loc_end <= end) add = true; - else if (base < loc_end && end > loc_base) { + else if (base <= loc_end && end >= loc_base) { if (loc_base < base) loc_base = base; if (loc_end > end) From 68832eb08751b4ce23e90bd90e9414a465a99da3 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 30 Jul 2026 15:19:40 +0200 Subject: [PATCH 0294/1317] powerpc/kexec: Simplify kdump_extra_elfcorehdr_size() Return the size directly and drop the extra_sz variable to simplify kdump_extra_elfcorehdr_size(). The two warning paths now fall through to the existing return 0 at the end of the function. Signed-off-by: Thorsten Blum Reviewed-by: Christophe Leroy (CS GROUP) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260730131940.597739-2-thorsten.blum@linux.dev --- arch/powerpc/kexec/file_load_64.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c index c2ed0d1c92e7..4a499a69e2d8 100644 --- a/arch/powerpc/kexec/file_load_64.c +++ b/arch/powerpc/kexec/file_load_64.c @@ -377,16 +377,12 @@ static int load_backup_segment(struct kimage *image, struct kexec_buf *kbuf) static unsigned int kdump_extra_elfcorehdr_size(struct crash_mem *cmem) { #if defined(CONFIG_CRASH_HOTPLUG) && defined(CONFIG_MEMORY_HOTPLUG) - unsigned int extra_sz = 0; - if (CONFIG_CRASH_MAX_MEMORY_RANGES > (unsigned int)PN_XNUM) pr_warn("Number of Phdrs %u exceeds max\n", CONFIG_CRASH_MAX_MEMORY_RANGES); else if (cmem->nr_ranges >= CONFIG_CRASH_MAX_MEMORY_RANGES) pr_warn("Configured crash mem ranges may not be enough\n"); else - extra_sz = (CONFIG_CRASH_MAX_MEMORY_RANGES - cmem->nr_ranges) * sizeof(Elf64_Phdr); - - return extra_sz; + return (CONFIG_CRASH_MAX_MEMORY_RANGES - cmem->nr_ranges) * sizeof(Elf64_Phdr); #endif return 0; } From 449f60f99f8f3cbe80a9bd2242945e827c5ed003 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 10 Aug 2026 16:58:27 +0200 Subject: [PATCH 0295/1317] powerpc/kexec_file: Use inclusive range checks for excluded memory arch_check_excluded_range() checks if a kexec segment overlaps an excluded memory range. Both ranges use inclusive end addresses, but the overlap check uses exclusive comparisons. This skips ranges with start == ->ranges[i].end or end == ->ranges[i].start. Use inclusive comparisons instead. Fixes: 6e5250eaa665 ("powerpc/crash: use generic APIs to locate memory hole for kdump") Signed-off-by: Thorsten Blum Reviewed-by: Sourabh Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260810145827.157972-3-thorsten.blum@linux.dev --- arch/powerpc/kexec/file_load_64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c index 4a499a69e2d8..bd80c5fb1b1f 100644 --- a/arch/powerpc/kexec/file_load_64.c +++ b/arch/powerpc/kexec/file_load_64.c @@ -57,7 +57,7 @@ int arch_check_excluded_range(struct kimage *image, unsigned long start, emem = image->arch.exclude_ranges; for (i = 0; i < emem->nr_ranges; i++) - if (start < emem->ranges[i].end && end > emem->ranges[i].start) + if (start <= emem->ranges[i].end && end >= emem->ranges[i].start) return 1; return 0; From b1824233b19c1dffdb5e81283805a9e52e763caa Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Thu, 13 Aug 2026 14:37:31 +0800 Subject: [PATCH 0296/1317] powerpc/pseries/pci: Fix misleading VF limit error message When the number of requested VFs exceeds MAX_VFS_FOR_MAP_PE, the message prints that limit but labels it "Configurable VFs". Report the configurable VF limit and the PE mapping limit with separate error messages. Suggested-by: Christophe Leroy Signed-off-by: Jiangshan Yi Reviewed-by: Christophe Leroy [Maddy: Fixed Christophe's reviewed by tag] Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260813063731.399598-1-yijiangshan@kylinos.cn --- arch/powerpc/platforms/pseries/pci.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/arch/powerpc/platforms/pseries/pci.c b/arch/powerpc/platforms/pseries/pci.c index d11a64a086c1..6fc13f4a79a3 100644 --- a/arch/powerpc/platforms/pseries/pci.c +++ b/arch/powerpc/platforms/pseries/pci.c @@ -132,11 +132,14 @@ static int pseries_pci_sriov_enable(struct pci_dev *pdev, u16 num_vfs) /* First integer stores max config */ max_config_vfs = of_read_number(&max_vfs[0], 1); - if (max_config_vfs < num_vfs || num_vfs > MAX_VFS_FOR_MAP_PE) { - dev_err(&pdev->dev, - "Num VFs %x > %x Configurable VFs\n", - num_vfs, (num_vfs > MAX_VFS_FOR_MAP_PE) ? - MAX_VFS_FOR_MAP_PE : max_config_vfs); + if (max_config_vfs < num_vfs) { + dev_err(&pdev->dev, "Num VFs %x > %x Configurable VFs\n", + num_vfs, max_config_vfs); + return -EINVAL; + } + if (num_vfs > MAX_VFS_FOR_MAP_PE) { + dev_err(&pdev->dev, "Num VFs %x > %x PE mapping limit\n", + num_vfs, MAX_VFS_FOR_MAP_PE); return -EINVAL; } From 2b4707a149a55e8fa75c9ef32b359d60f470a566 Mon Sep 17 00:00:00 2001 From: XingWang Xiang Date: Wed, 2 Sep 2026 15:01:18 +0900 Subject: [PATCH 0297/1317] net: mctp: i3c: serialize probe with bus removal mctp_i3c_probe() drops busdevs_lock after finding the matching bus. A concurrent I3C_NOTIFY_BUS_REMOVE can then unregister and free the bus netdev before probe passes its private data to mctp_i3c_add_device(). The latter consequently adds a list node through a freed mbus pointer. Keep busdevs_lock held until the device has been added. This also satisfies the __must_hold annotation on mctp_i3c_add_device(). Fixes: c8755b29b58e ("mctp i3c: MCTP I3C driver") Signed-off-by: XingWang Xiang Acked-by: Matt Johnston Signed-off-by: David S. Miller --- drivers/net/mctp/mctp-i3c.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c index 88d9e36cd4a2..4e857dd5df64 100644 --- a/drivers/net/mctp/mctp-i3c.c +++ b/drivers/net/mctp/mctp-i3c.c @@ -288,6 +288,7 @@ __must_hold(&busdevs_lock) static int mctp_i3c_probe(struct i3c_device *i3c) { struct mctp_i3c_bus *b = NULL, *mbus = NULL; + int rc; /* Look for a known bus */ mutex_lock(&busdevs_lock); @@ -296,14 +297,16 @@ static int mctp_i3c_probe(struct i3c_device *i3c) mbus = b; break; } - mutex_unlock(&busdevs_lock); if (!mbus) { /* probably no "mctp-controller" property on the i3c bus */ - return -ENODEV; + rc = -ENODEV; + } else { + rc = mctp_i3c_add_device(mbus, i3c); } + mutex_unlock(&busdevs_lock); - return mctp_i3c_add_device(mbus, i3c); + return rc; } static void mctp_i3c_remove_device(struct mctp_i3c_device *mi) From 63a7531ca31f9f097d9cc1cc3fe86ae683cdabdd Mon Sep 17 00:00:00 2001 From: "Mukesh Kumar Chaurasiya (IBM)" Date: Fri, 4 Sep 2026 14:38:58 +0530 Subject: [PATCH 0298/1317] powerpc/entry: Fix irq_soft_mask corruption on replayed interrupt exit When __replay_soft_interrupts() replays a pending interrupt (e.g. PACA_IRQ_DEC -> timer_interrupt), it calls the handler directly with a synthetic pt_regs. The DEFINE_INTERRUPT_HANDLER_ASYNC wrapper around each handler calls arch_interrupt_async_exit_prepare() on the way out, which calls arch_interrupt_exit_prepare() -> local_irq_disable() -> arch_local_irq_disable(), which does: irq_soft_mask_set(IRQS_DISABLED) /* 0x1 */ This unconditionally overwrites irq_soft_mask with IRQS_DISABLED (0x1), stripping the IRQS_PMI_DISABLED (0x2) bit. The result is that irq_soft_mask is 0x1 instead of IRQS_ALL_DISABLED (0x3) when the handler returns to __replay_soft_interrupts(). For a normally-taken interrupt this is harmless: the next interrupt always enters through arch_interrupt_enter_prepare() which unconditionally sets irq_soft_mask to IRQS_ALL_DISABLED. But during replay, next_interrupt() is called directly between replayed handlers without going back through arch_interrupt_enter_prepare(), so the stripped bit is never restored. next_interrupt() then fires a WARNING: WARNING: arch/powerpc/kernel/irq_64.c:75 WARN_ON(irq_soft_mask_return() != IRQS_ALL_DISABLED) The warning was observed early in boot on a POWER10 pseries guest during kmem_cache_init_late(), where a spinlock release triggers interrupt replay that processes a pending timer interrupt. Debugger state confirming the bug: Before timer_interrupt(®s): irq_soft_mask = 0x3 (IRQS_ALL_DISABLED) correct irq_happened = 0x41 (HARD_DIS|REPLAYING) correct After timer_interrupt(®s) returns: irq_soft_mask = 0x1 (IRQS_DISABLED) WRONG - PMI bit stripped irq_happened = 0x41 unchanged The fix is to replace local_irq_disable() with hard_irq_disable(). hard_irq_disable() is the right primitive here for two reasons: 1. On PPC64 (hw_irq.h:301) it calls irq_soft_mask_set_return(IRQS_ALL_DISABLED), setting the soft mask to 0x3 (both IRQS_DISABLED and IRQS_PMI_DISABLED), which preserves the PMI bit and fixes the WARNING. The additional work it does (__hard_irq_disable(), PACA_IRQ_HARD_DIS |=) is redundant but safe since both are already set at this point in the exit path; the trace_hardirqs_off() inside is guarded by if (!arch_irqs_disabled_flags(flags)) so it will not double-fire. 2. On PPC32 (hw_irq.h:467) hard_irq_disable() maps to arch_local_irq_disable() -> __hard_irq_disable(), which clears MSR[EE] in hardware. This is exactly correct: PPC32 has no soft-mask PACA mechanism, so the hardware disable is the right way to satisfy irqentry_exit()'s requirement. This also fixes a build error on PPC32 where irq_soft_mask_set() is only defined under CONFIG_PPC64: arch/powerpc/include/asm/entry-common.h:273: error: implicit declaration of function 'irq_soft_mask_set' Using hard_irq_disable() requires no #ifdef and is consistent with how the rest of the entry code (e.g. entry-common.h:463) handles the same PPC32/PPC64 split. Fixes: 334f3f6d7a16 ("powerpc/entry: Disable interrupts before irqentry_exit") Reported-by: Venkat Rao Bagalkote Closes: https://lore.kernel.org/all/6f9bfb0f-b14c-468e-bb9f-c157d120d0dc@linux.ibm.com/ Tested-by: Venkat Rao Bagalkote Reviewed-by: Shrikanth Hegde Signed-off-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260904090858.128563-1-mkchauras@gmail.com --- arch/powerpc/include/asm/entry-common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/include/asm/entry-common.h b/arch/powerpc/include/asm/entry-common.h index 94083516df57..80b07750b531 100644 --- a/arch/powerpc/include/asm/entry-common.h +++ b/arch/powerpc/include/asm/entry-common.h @@ -270,7 +270,7 @@ static inline void arch_interrupt_exit_prepare(struct pt_regs *regs) } /* irqentry_exit expects to be called with interrupts disabled */ - local_irq_disable(); + hard_irq_disable(); } static inline void arch_interrupt_async_enter_prepare(struct pt_regs *regs) From 9f2e63f1b2d5fc5b5423424902c091123e220e7e Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Thu, 3 Sep 2026 21:28:58 +0000 Subject: [PATCH 0299/1317] smb: client: avoid leaking refcount in cifs_queue_oplock_break() cifs_queue_oplock_break() unconditionally takes a reference on the target file before queueing cifs_oplock_break(). Only that work item decreases the reference counter again. If another oplock break arrives while that work is still queued, queue_work() will return false and not queue this second work item. As a result, we will never reach the point to drop the file reference again and are leaking this reference. This can be triggered when interacting with a slow-responding server. As a result, later unmount operations for this file system will fail with BUG: Dentry ... still in use (1) [unmount of cifs cifs] VFS: Busy inodes after unmount of cifs (cifs) kernel BUG at fs/super.c:777! Fix this by only incrementing the reference count if the work has been queued successfully. Taking it after queue_work() is safe because all three callers hold tcon->open_file_lock across the call and _cifsFileInfo_put() decrements under that same lock, so a worker that starts the handler in the window cannot drop the reference before it has been taken. Fixes: b98749cac4a69 ("CIFS: keep FileInfo handle live during oplock break") Cc: stable@vger.kernel.org Assisted-by: Kiro:claude-opus-5 Signed-off-by: Bjoern Doebel Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/misc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index d4db3f91a91f..945194fe7a97 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -378,10 +378,11 @@ void cifs_queue_oplock_break(struct cifsFileInfo *cfile) * open_file_lock to enforce the validity of it for the oplock * break handler. The matching put is done at the end of the * handler. + * + * Only take a reference if the work is actually queued. */ - cifsFileInfo_get(cfile); - - queue_work(cifsoplockd_wq, &cfile->oplock_break); + if (queue_work(cifsoplockd_wq, &cfile->oplock_break)) + cifsFileInfo_get(cfile); } void cifs_done_oplock_break(struct cifsInodeInfo *cinode) From 23b26f4408ac3f35a482d2e5cf6fc865d4201b71 Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Fri, 4 Sep 2026 10:42:36 +0000 Subject: [PATCH 0300/1317] smb: client: avoid leaking refcount when cifs_sb_tlink() fails cifs_oplock_break() takes over the reference that cifs_queue_oplock_break() acquired when it queued the work, and drops it with _cifsFileInfo_put() once the break has been processed. Only in setups with "-o multiuser", cifs_sb_tlink() may fail, at which point cifs_oplock_break() returns without putting the file reference, mirroring the reference leak we already fixed in the companion patch to cifs_queue_oplock_break(). This would trigger a crash due to busy inodes on the next unmount: BUG: Dentry ... still in use (1) [unmount of cifs cifs] VFS: Busy inodes after unmount of cifs (cifs) Drop the reference on that path as well. Doing so before the out label mirrors the normal path, which also puts the reference before cifs_done_oplock_break(). Found by Sashiko code review. The failure path was not exercised at runtime. Fixes: e8f5f849ffce2 ("cifs: fix potential oops in cifs_oplock_break") Cc: stable@vger.kernel.org Assisted-by: Kiro:claude-opus-5 Signed-off-by: Bjoern Doebel Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index d7b0a9512dfa..27b58d907203 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -3348,8 +3348,11 @@ void cifs_oplock_break(struct work_struct *work) TASK_UNINTERRUPTIBLE); tlink = cifs_sb_tlink(cifs_sb); - if (IS_ERR(tlink)) + if (IS_ERR(tlink)) { + /* drop the reference taken when the break was queued */ + _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false); goto out; + } tcon = tlink_tcon(tlink); server = tcon->ses->server; From 5520e89a5a4f834bced64cf2ac927001cc513a40 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 4 Sep 2026 13:48:11 +0000 Subject: [PATCH 0301/1317] smb: client: fix cifsFileInfo reference leak in deferred close When cifs_close() defers a close, it hands the cifsFileInfo reference of the closing struct file to the queued work. Each execution of smb2_deferred_work_close() drops one such reference. deferred_close_scheduled can be false while the work is pending: the workqueue clears PENDING when the callback starts to run, before the callback clears the flag under deferred_lock. A close in that interval requeues the running work, and the callback then clears the flag, leaving the requeued work pending with the flag down. A later cifs_open() can reuse the handle and its cifs_close() reaches the same branch: queue_delayed_work() fails because the work is still pending, but cifs_close() returns without dropping the closing file's reference. The cifsFileInfo count stays pinned and its tlink, dentry and server handle are leaked. Check the return value and hand off the reference only when work was actually queued. Otherwise, use the shared _cifsFileInfo_put(), like the mod_delayed_work() branch above: the pending execution already owns its reference. This issue was found by an in-house static analysis tool. Fixes: c3f207ab29f7 ("cifs: Deferred close for files") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 27b58d907203..1aa4844f8b8a 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1515,11 +1515,18 @@ int cifs_close(struct inode *inode, struct file *file) trace_smb3_close_cached(tcon->tid, tcon->ses->Suid, cfile->fid.persistent_fid, cifs_sb->ctx->closetimeo); - queue_delayed_work(deferredclose_wq, - &cfile->deferred, cifs_sb->ctx->closetimeo); - cfile->deferred_close_scheduled = true; - spin_unlock(&cinode->deferred_lock); - return 0; + /* + * Each queued execution owns one reference. + * If nothing was queued, the reference of + * the closing file is dropped below. + */ + if (queue_delayed_work(deferredclose_wq, + &cfile->deferred, + cifs_sb->ctx->closetimeo)) { + cfile->deferred_close_scheduled = true; + spin_unlock(&cinode->deferred_lock); + return 0; + } } spin_unlock(&cinode->deferred_lock); _cifsFileInfo_put(cfile, true, false); From e384abeb559d10d6505aec053ede9368d81d4c71 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 27 Aug 2026 20:55:57 +0100 Subject: [PATCH 0302/1317] mm/huge_memory: bypass THP tuneables for huge pfnmap mappings The sysfs THP tuneables at /sys/kernel/mm/transparent_huge_pages/ rather confusingly only control the behaviour of THP in some instances. They are not applicable to MADV_COLLAPSE operations, nor to DAX mappings. Long-term, THP is predicated upon compaction being able to obtain large folios to populate THP ranges. However, vm_normal_folio() returns NULL for PFN map mappings, thus their reference count is maintained by the driver, not core mm. As a consequence, the folios are not subject to reclaim nor compaction, so are not truly part of the THP mechanism at all. However, since commit 5dd40721f147 ("mm: allow THP orders for PFNMAPs") introduced the ability to establish huge PFN maps, they have been subject to THP tuneables. This is incorrect - if a huge PFN map is available (defined by vma->vm_ops->huge_fault being non-NULL for a VMA_PFNMAP_BIT VMA), then it should be mapped huge upon fault-in. Correct this by explicitly checking for this while ensuring that smaps continues to accurately report THPeligible statistics. While here, abstract the entire file-backed THP check in vma_can_map_huge_file(), with sensible separation of logic into helper functions. Note that drm_gem_shmem_mmap() and panthor_gem_mmap() establish huge PFN maps of shmem folios, however they are marked unevictable in drm_gem_get_pages(), and in any case would fail the reference check in __remove_mapping() even if they weren't. Failing to map huge PFN maps has resulted in significant real-world performance degradation, see links for details. [ziy@nvidia.com: rename some functions] Link: https://lore.kernel.org/DL1HIHWYJ7TB.1CY76SJS0V03L@nvidia.com Link: https://lore.kernel.org/20260827-hugepfn-allowable-orders-v1-1-94819c8807c8@kernel.org Fixes: 5dd40721f147 ("mm: allow THP orders for PFNMAPs") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Zi Yan Reported-by: Cedric Le Goater Closes: https://lore.kernel.org/linux-mm/20260805055544.1568534-1-clg@redhat.com/ Reported-by: Saravanan D Closes: https://lore.kernel.org/linux-mm/20260821070520.25759-1-saravanand@crusoe.ai/ Reviewed-by: Zi Yan Tested-by: Saravanan D Tested-by: Lance Yang Reviewed-by: SJ Park Reviewed-by: Baolin Wang Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Jason Gunthorpe Cc: Liam R. Howlett Cc: Peter Xu Cc: Ryan Roberts Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 86 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index afbb5974bd22..1e5d68acf62a 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -92,7 +92,7 @@ unsigned long huge_anon_orders_madvise __read_mostly; unsigned long huge_anon_orders_inherit __read_mostly; static bool anon_orders_configured __initdata; -static inline bool file_thp_enabled(struct vm_area_struct *vma) +static inline bool file_thp_enabled(const struct vm_area_struct *vma) { struct inode *inode; @@ -118,6 +118,67 @@ static bool vma_is_special_huge(const struct vm_area_struct *vma) return vma_test_any(vma, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT); } +static bool vma_file_bypass_thp_tuneables(const struct vm_area_struct *vma, + enum tva_type type) +{ + const bool has_huge_fault = vma->vm_ops->huge_fault; + + /* MADV_COLLAPSE ignores tuneables. */ + if (type == TVA_FORCED_COLLAPSE) + return true; + /* Huge PFN mappings are uncompactable so the policy doesn't apply. */ + if (vma_test(vma, VMA_PFNMAP_BIT) && has_huge_fault) + return true; + return false; +} + +static bool vma_file_allow_thp_tuneables(vm_flags_t vm_flags) +{ + /* THP=always? */ + if (hugepage_global_always()) + return true; + /* THP=madvise and marked MADV_HUGEPAGE? */ + if (hugepage_global_enabled() && (vm_flags & VM_HUGEPAGE)) + return true; + return false; +} + +static bool vma_file_check_thp_tuneables(const struct vm_area_struct *vma, + vm_flags_t vm_flags, enum tva_type type) +{ + return vma_file_bypass_thp_tuneables(vma, type) || + vma_file_allow_thp_tuneables(vm_flags); +} + +static bool vma_can_map_huge_file(const struct vm_area_struct *vma, + vm_flags_t vm_flags, enum tva_type type) +{ + const bool has_huge_fault = vma->vm_ops->huge_fault; + + /* + * Enforce THP collapse requirements as necessary. Anonymous vmas + * were already handled in thp_vma_allowable_orders(). + */ + if (!vma_file_check_thp_tuneables(vma, vm_flags, type)) + return false; + + switch (type) { + case TVA_PAGEFAULT: + /* + * Trust that ->huge_fault() handlers know what they are doing + * in fault path. + */ + return has_huge_fault; + case TVA_SMAPS: + if (has_huge_fault) + return true; + fallthrough; + default: + /* Only regular file is valid in collapse path. */ + return file_thp_enabled(vma); + } +} + unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma, vm_flags_t vm_flags, enum tva_type type, @@ -190,27 +251,8 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma, vma, vma_start_pgoff(vma), 0, forced_collapse); - if (!vma_is_anonymous(vma)) { - /* - * Enforce THP collapse requirements as necessary. Anonymous vmas - * were already handled in thp_vma_allowable_orders(). - */ - if (!forced_collapse && - (!hugepage_global_enabled() || (!(vm_flags & VM_HUGEPAGE) && - !hugepage_global_always()))) - return 0; - - /* - * Trust that ->huge_fault() handlers know what they are doing - * in fault path. - */ - if (((in_pf || smaps)) && vma->vm_ops->huge_fault) - return orders; - /* Only regular file is valid in collapse path */ - if (((!in_pf || smaps)) && file_thp_enabled(vma)) - return orders; - return 0; - } + if (!vma_is_anonymous(vma)) + return vma_can_map_huge_file(vma, vm_flags, type) ? orders : 0; if (vma_is_temporary_stack(vma)) return 0; From 397432cab17bccb600fd6c16ed593f1149042268 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Fri, 28 Aug 2026 12:20:37 +0100 Subject: [PATCH 0303/1317] mm/mremap: account mm->locked_vm correctly for MREMAP_DONTUNMAP When a VMA is mremap()'d with MREMAP_DONTUNMAP set, that results in the VMA being copied, but the source VMA not being unmapped. If the VMA is mlock()'d this is a legal operation, though the source VMA has its VMA_LOCKED_BIT cleared. However this is done in dontunmap_complete(), after mm->locked_vm was incremented via vrm_stat_account(), resulting in double-counting. Worse, this is not even corrected when source VMA is unmapped, due to the VMA_LOCKED_BIT flag having been cleared. This all works fine in the usual mremap() case (without MREMAP_DONTUNMAP), as the source VMA is unmapped with VMA_LOCKED_BIT intact, at which time mm->locked_vm is decremented accordingly. Resolve the issue by invoking vrm_stat_account() only after dontunmap_complete() has run. Note that MREMAP_DONTUNMAP requires old_len == new_len, so no need to account for a delta in size in this case. The bug was introduced by commit b714ccb02a76 ("mm/mremap: complete refactor of move_vma()") which incorrectly reordered the accounting and the clearing of the VMA_LOCKED_BIT flag. Link: https://lore.kernel.org/20260828-mremap-fix-locked-vm-v1-1-c80be7505d1e@kernel.org Fixes: b714ccb02a76 ("mm/mremap: complete refactor of move_vma()") Signed-off-by: Lorenzo Stoakes (ARM) Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260825-fix-mremap-dontunmap-pgoff-v1-1-39a40b2c98b3@kernel.org Reported-by: Kunwu Chan Closes: https://lore.kernel.org/all/20260828094823.594279-1-kunwu.chan@linux.dev/ Acked-by: Vlastimil Babka (SUSE) Tested-by: Kunwu Chan Reviewed-by: Kunwu Chan Cc: Jann Horn Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Signed-off-by: Andrew Morton --- mm/mremap.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index 2b4b523a86b8..7c368440fafe 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1355,12 +1355,11 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, if (vma_is_anonymous(vma) && !vma->vm_file) vma_set_pgoff(vma, pgoff_unfaulted); } - - /* Because we won't unmap we don't need to touch locked_vm. */ } static unsigned long move_vma(struct vma_remap_struct *vrm) { + const bool is_dontunmap = vrm->flags & MREMAP_DONTUNMAP; struct mm_struct *mm = current->mm; struct vm_area_struct *new_vma; unsigned long hiwater_vm; @@ -1401,10 +1400,10 @@ static unsigned long move_vma(struct vma_remap_struct *vrm) */ hiwater_vm = mm->hiwater_vm; - vrm_stat_account(vrm, vrm->new_len); - if (unlikely(!err && (vrm->flags & MREMAP_DONTUNMAP))) + if (unlikely(is_dontunmap && !err)) dontunmap_complete(vrm, new_vma); - else + vrm_stat_account(vrm, vrm->new_len); + if (!is_dontunmap || err) unmap_source_vma(vrm); mm->hiwater_vm = hiwater_vm; From e1d56f046507befa20a5e0837d8075abdf5848fd Mon Sep 17 00:00:00 2001 From: Coiby Xu Date: Fri, 28 Aug 2026 16:41:06 +0800 Subject: [PATCH 0304/1317] mailmap: map Coiby Xu's address Point to my gmail address as I've left Red Hat. Link: https://lore.kernel.org/20260828084106.1494733-1-coiby.xu@gmail.com Signed-off-by: Coiby Xu Signed-off-by: Andrew Morton --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 37aad399e4e0..b355f1c85958 100644 --- a/.mailmap +++ b/.mailmap @@ -222,6 +222,7 @@ Chuck Lever Chuck Lever Chuck Lever Claudiu Beznea +Coiby Xu Colin Ian King Corey Minyard Damian Hobson-Garcia From 12e9ac7bc5b254048f886bf421e3a15491106c1f Mon Sep 17 00:00:00 2001 From: Nhat Pham Date: Fri, 28 Aug 2026 12:14:33 -0700 Subject: [PATCH 0305/1317] mm, swap: fix SWAP_USAGE_OFFLIST_BIT collision with real usage count SWAP_USAGE_OFFLIST_BIT is embedded in the si->inuse_pages usage counter, and is meant to sit above any value that counter can reach. However, it is defined from BITS_PER_TYPE(atomic_t), so it is bit 30. On a system with 4 KiB pages the flag collides with the usage count once that count reaches 4 TiB. swap_usage_in_pages() masks bit 30 out, so whenever the real count has that bit set, every caller of it reads 4 TiB low: * /proc/swaps understates Used by 4 TiB. * A raw count of exactly 2^30 masks to zero, so try_to_unuse() takes its "if (!swap_usage_in_pages(si)) goto success;" early exit and swapoff tears the device down while pages are still swapped out. Nothing in the rest of swapoff aborts the teardown, so those pages are lost. Independently of swapoff, the collision also corrupts the counter and the plist. On a device in normal use, a free that leaves bit 30 set in the count makes swap_usage_sub() see the flag where there is only count, and call add_to_avail_list(). It clears the bit with fetch_and(~SWAP_USAGE_OFFLIST_BIT), leaving the stored count 4 TiB below the real one, and calls plist_add() on a device that is already listed, tripping the WARN_ON(!plist_node_empty(node)) in plist_add() and linking the node a second time. Change the definition of SWAP_USAGE_OFFLIST_BIT to be based on atomic_long_t instead. Note that the usage counter field itself is of this same type, so it is still a valid bit. Link: https://lore.kernel.org/20260828191433.3304458-1-nphamcs@gmail.com Fixes: b228386cf237 ("mm, swap: clean up plist removal and adding") Signed-off-by: Nhat Pham Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260825153238.2695446-1-nphamcs%40gmail.com Suggested-by: Andrew Morton Reviewed-by: Andrew Morton Acked-by: Kairui Song Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Gregory Price Cc: Johannes Weiner Cc: Joshua Hahn Cc: Kemeng Shi Cc: Shakeel Butt Cc: Youngjun Park Cc: Signed-off-by: Andrew Morton --- mm/swapfile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/swapfile.c b/mm/swapfile.c index 53bf01d5f7f1..601979b97f95 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -156,7 +156,7 @@ static struct swap_info_struct *swap_entry_to_info(swp_entry_t entry) * This bit will be set if the device is not on the plist and not * usable, will be cleared if the device is on the plist. */ -#define SWAP_USAGE_OFFLIST_BIT (1UL << (BITS_PER_TYPE(atomic_t) - 2)) +#define SWAP_USAGE_OFFLIST_BIT (1UL << (BITS_PER_TYPE(atomic_long_t) - 2)) #define SWAP_USAGE_COUNTER_MASK (~SWAP_USAGE_OFFLIST_BIT) static long swap_usage_in_pages(struct swap_info_struct *si) { From 6e673d0879ef78c395cfe0d3ba316690a60055d8 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 28 Aug 2026 19:32:51 -0700 Subject: [PATCH 0306/1317] memcg: avoid charging the root memcg from obj_cgroup_charge_pages() obj_cgroup_charge_pages() resolves the objcg to its memcg and calls try_charge_memcg(), which does not short circuit the root memcg. That memcg can be the root memcg: obj_cgroup_is_root() reflects the memcg the objcg was created for and is never updated, while memcg_reparent_objcgs() does redirect objcg->memcg to the parent on rmdir. An objcg of a dying child of root therefore passes every obj_cgroup_is_root() filter but resolves to the root memcg. Folios keep the objcg they were charged with, so this is easy to reach through zswap: allocate anon memory in a cgroup, move the task out, remove the cgroup, then write to the root cgroup's memory.reclaim. The reclaimed folios are charged through the reparented objcg and end up in refill_stock() with the root memcg: WARNING: mm/memcontrol.c:2198 at refill_stock+0x644/0x940 refill_stock+0x644/0x940 try_charge_memcg+0x12d6/0x1570 __obj_cgroup_charge+0x35/0xf0 obj_cgroup_charge+0x1de/0x210 obj_cgroup_charge_zswap+0x83/0x270 zswap_store+0x1620/0x2000 swap_writeout+0x94c/0x14c0 shrink_folio_list+0x3388/0x52b0 [...] try_to_free_mem_cgroup_pages+0x30d/0x830 user_proactive_reclaim+0x504/0x840 memory_reclaim+0x1f/0x30 Beyond the warning, the charge is asymmetric: obj_cgroup_uncharge_pages() skips refill_stock() for the root memcg, so the root's page counter grows and is never uncharged. It is not user visible, since memory.current is not exposed on the root, but it is a leak. Use try_charge(), which returns early for the root memcg, restoring the symmetry with obj_cgroup_uncharge_pages(). The above sequence was scripted into a standalone reproducer (zswap on, swap on a virtio disk, 512MB of anon memory faulted in inside a child of the root cgroup, the task then migrated to the root cgroup, the child removed, followed by "echo 600M swappiness=max > memory.reclaim" on the root) and run in a CONFIG_DEBUG_VM=y VM. It reproduces the splat on the first zswap store of a reparented folio, with the same call chain as the report. With this patch applied the splat is gone while the zswap store count over the run is unchanged, so the same path is still exercised. cgroup selftests test_zswap, test_kmem and test_memcontrol show no new failures. Link: https://lore.kernel.org/20260829023251.474083-1-shakeel.butt@linux.dev Fixes: 20d6c1725228 ("memcg: avoid refill_stock for root memcg") Signed-off-by: Shakeel Butt Reported-by: Farhad Alemi Closes: https://lore.kernel.org/all/CA+0ovCgWzUMK+nNbbtH7eV65Ca=fDN4Ozu7iASgryjvv8Tk8zQ@mail.gmail.com/ Reviewed-by: Muchun Song Reviewed-by: Johannes Weiner Cc: Michal Hocko Cc: Roman Gushchin Cc: Signed-off-by: Andrew Morton --- mm/memcontrol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 1271d390b617..856a7d07586c 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -3158,7 +3158,7 @@ static int obj_cgroup_charge_pages(struct obj_cgroup *objcg, gfp_t gfp, memcg = get_mem_cgroup_from_objcg(objcg); - ret = try_charge_memcg(memcg, gfp, nr_pages); + ret = try_charge(memcg, gfp, nr_pages); if (ret) goto out; From e1d469a8d6c63032a5aed3679a13a086b61bd5cc Mon Sep 17 00:00:00 2001 From: Christopher Obbard Date: Sat, 29 Aug 2026 12:28:23 +0100 Subject: [PATCH 0307/1317] mailmap: update entry for Christopher Obbard I have changed employer; update my mailmap entry to point at my new email address. Link: https://lore.kernel.org/20260829-update-mail-oss-qualcomm-v2-1-1670c515f225@oss.qualcomm.com Signed-off-by: Christopher Obbard Signed-off-by: Andrew Morton --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index b355f1c85958..90ff4831f592 100644 --- a/.mailmap +++ b/.mailmap @@ -211,7 +211,8 @@ Christophe Leroy Christophe Leroy Christophe Leroy Christophe Ricard -Christopher Obbard +Christopher Obbard +Christopher Obbard Christoph Hellwig Christoph Manszewski Christoph Paasch From 848d2ce2fce15fbdc083fbf9691bfa72911033c4 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Sun, 30 Aug 2026 01:36:12 +0800 Subject: [PATCH 0308/1317] mm: filemap: retain mapped dropbehind folios Fault-around can map ready dropbehind folios without going through the normal page-cache lookup that clears dropbehind. A mapping represents a competing cached user, so retain the folio instead of forcibly unmapping it when writeback completes. For a mapped folio, folio_unmap_invalidate() can call unmap_mapping_folio(), which takes i_mmap_rwsem and may sleep. Retaining mapped folios avoids this path when folio_end_dropbehind() runs in non-preemptible task context. Tal was able to trigger a sleeping-in-atomic warning due to this [1]. Unmapped dropbehind folios continue through the existing invalidation path. Link: https://lore.kernel.org/4aba05e1a2c3b61cb337d373eb9b7a8db4ddd822.1788024049.git.qiwenjie@xiaomi.com Link: https://lore.kernel.org/076bb01b-6fcf-4691-be8c-0e8507c9fe64@columbia.edu [1] Fixes: fb7d3bc41493 ("mm/filemap: drop streaming/uncached pages when writeback completes") Signed-off-by: Wenjie Qi Reviewed-by: Matthew Wilcox (Oracle) Reviewed-by: Tal Zussman Tested-by: Tal Zussman Cc: Barry Song Cc: Jan Kara Cc: Jens Axboe Cc: Trond Myklebust Cc: Signed-off-by: Andrew Morton --- mm/filemap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/filemap.c b/mm/filemap.c index 6afec636881f..00fd89cf6f55 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -1616,7 +1616,7 @@ static void filemap_end_dropbehind(struct folio *folio) return; if (!folio_test_clear_dropbehind(folio)) return; - if (mapping) + if (mapping && !folio_mapped(folio)) folio_unmap_invalidate(mapping, folio, 0); } From 641aade99f06df0037e52b5c81645461b7132947 Mon Sep 17 00:00:00 2001 From: Qi Zheng Date: Mon, 17 Aug 2026 17:03:25 +0800 Subject: [PATCH 0309/1317] fs: fix missed removal of super_fs_objects_eligible() Commit 0ef8faff490be ("fs: push nr_cached_objects memcg gating into individual filesystems") was meant to drop the blanket memcg gate in fs/super.c and let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal of super_fs_objects_eligible() and its two call sites in super_cache_scan() / super_cache_count() was lost, so the helper is still gating every ->nr_cached_objects() hook and 0ef8faff490be is effectively a no-op. Consequences of the leftover gate: - XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in fs/super.c exactly the regression from commit 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects in memcg slab shrink") that 0ef8faff490be was written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in. - Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own defeating the whole point of pushing the gating decision down into the callbacks. Drop the leftover helper and its call sites so the intent of 0ef8faff490be actually takes effect. Link: https://lore.kernel.org/cover.1786955972.git.zhengqi.arch@bytedance.com Link: https://lore.kernel.org/3b038d373c70ebac7cdabfb0035bb91d1d6e6cfe.1786955972.git.zhengqi.arch@bytedance.com Link: https://lore.kernel.org/all/20260715103516.2410175-1-usama.arif@linux.dev/ [0] Fixes: 0ef8faff490b ("fs: push nr_cached_objects memcg gating into individual filesystems") Signed-off-by: Qi Zheng Acked-by: Usama Arif Cc: Baolin Wang Cc: Christian Brauner Cc: David Hildenbrand Cc: Hugh Dickins Cc: Johannes Weiner Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Signed-off-by: Andrew Morton --- fs/super.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/fs/super.c b/fs/super.c index 05e443173038..3ecce24328f6 100644 --- a/fs/super.c +++ b/fs/super.c @@ -171,19 +171,6 @@ static void super_wake(struct super_block *sb, unsigned int flag) wake_up_var(&sb->s_flags); } -/* - * The s_op->nr_cached_objects hooks (used for example by btrfs and xfs) - * operate on filesystem-global state and ignore sc->memcg. Driving them - * from per-memcg shrink_slab_memcg() invocations only burns CPU walking - * per-cpu counters and queueing duplicate work: the actual reclaim happens on - * the global path (kswapd or root direct reclaim) regardless. Restrict them - * to that path. - */ -static inline bool super_fs_objects_eligible(struct shrink_control *sc) -{ - return !sc->memcg || mem_cgroup_is_root(sc->memcg); -} - /* * One thing we have to be careful of with a per-sb shrinker is that we don't * drop the last active reference to the superblock from within the shrinker. @@ -213,7 +200,7 @@ static unsigned long super_cache_scan(struct shrinker *shrink, if (!super_trylock_shared(sb)) return SHRINK_STOP; - if (sb->s_op->nr_cached_objects && super_fs_objects_eligible(sc)) + if (sb->s_op->nr_cached_objects) fs_objects = sb->s_op->nr_cached_objects(sb, sc); inodes = list_lru_shrink_count(&sb->s_inode_lru, sc); @@ -274,8 +261,7 @@ static unsigned long super_cache_count(struct shrinker *shrink, return 0; smp_rmb(); - if (sb->s_op && sb->s_op->nr_cached_objects && - super_fs_objects_eligible(sc)) + if (sb->s_op && sb->s_op->nr_cached_objects) total_objects = sb->s_op->nr_cached_objects(sb, sc); total_objects += list_lru_shrink_count(&sb->s_dentry_lru, sc); From 8e2b8614039853e68d5338e37821e8bcee9fc05f Mon Sep 17 00:00:00 2001 From: Seunguk Shin Date: Mon, 3 Aug 2026 13:34:55 +0100 Subject: [PATCH 0310/1317] fs/dax: check zero or empty entry before converting xarray entry Calling dax_to_folio() with empty entry causes kernel panic below when booting a VM with DAX enabled storage. This patch checks empty entry before calling dax_to_folio() on dax_associate_entry(), dax_disassociate_entry(), and dax_busy_page(). Commit 98c183a4fccf ("fs/dax: don't disassociate zero page entries") added guards in the associate and disassociate paths, but the guards still come after dax_to_folio(), and dax_busy_page() still has the same problem. [ 0.737679] EXT4-fs (pmem0p1): mounted filesystem 79676804-7c8b-491a-b2a6-9bae3c72af70 ro with ordered data mode. Quota mode: disabled. [ 0.737891] VFS: Mounted root (ext4 filesystem) readonly on device 259:1. [ 0.739119] devtmpfs: mounted [ 0.739476] Freeing unused kernel memory: 1920K [ 0.740156] Run /sbin/init as init process [ 0.740229] with arguments: [ 0.740286] /sbin/init [ 0.740321] with environment: [ 0.740369] HOME=/ [ 0.740400] TERM=linux [ 0.743162] Unable to handle kernel paging request at virtual address fffffdffbf000008 [ 0.743285] Mem abort info: [ 0.743316] ESR = 0x0000000096000006 [ 0.743371] EC = 0x25: DABT (current EL), IL = 32 bits [ 0.743444] SET = 0, FnV = 0 [ 0.743489] EA = 0, S1PTW = 0 [ 0.743545] FSC = 0x06: level 2 translation fault [ 0.743610] Data abort info: [ 0.743656] ISV = 0, ISS = 0x00000006, ISS2 = 0x00000000 [ 0.743720] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 0.743785] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 0.743848] swapper pgtable: 4k pages, 48-bit VAs, pgdp=00000000b9d17000 [ 0.743931] [fffffdffbf000008] pgd=10000000bfa3d403, p4d=10000000bfa3d403, pud=1000000040bfe403, pmd=0000000000000000 [ 0.744070] Internal error: Oops: 0000000096000006 [#1] SMP [ 0.748888] CPU: 0 UID: 0 PID: 1 Comm: init Not tainted 6.18.4 #1 NONE [ 0.749421] pstate: 004000c5 (nzcv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 0.749969] pc : dax_disassociate_entry.constprop.0+0x20/0x50 [ 0.750444] lr : dax_insert_entry+0xcc/0x408 [ 0.750802] sp : ffff80008000b9e0 [ 0.751083] x29: ffff80008000b9e0 x28: 0000000000000000 x27: 0000000000000000 [ 0.751682] x26: 0000000001963d01 x25: ffff0000004f7d90 x24: 0000000000000000 [ 0.752264] x23: 0000000000000000 x22: ffff80008000bcc8 x21: 0000000000000011 [ 0.752836] x20: ffff80008000ba90 x19: 0000000001963d01 x18: 0000000000000000 [ 0.753407] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000 [ 0.753970] x14: ffffbf3154b9ae70 x13: 0000000000000000 x12: ffffbf3154b9ae70 [ 0.754548] x11: ffffffffffffffff x10: 0000000000000000 x9 : 0000000000000000 [ 0.755122] x8 : 000000000000000d x7 : 000000000000001f x6 : 0000000000000000 [ 0.755707] x5 : 0000000000000000 x4 : 0000000000000000 x3 : fffffdffc0000000 [ 0.756287] x2 : 0000000000000008 x1 : 0000000040000000 x0 : fffffdffbf000000 [ 0.756871] Call trace: [ 0.757107] dax_disassociate_entry.constprop.0+0x20/0x50 (P) [ 0.757592] dax_iomap_pte_fault+0x4fc/0x808 [ 0.757951] dax_iomap_fault+0x28/0x30 [ 0.758258] ext4_dax_huge_fault+0x80/0x2dc [ 0.758594] ext4_dax_fault+0x10/0x3c [ 0.758892] __do_fault+0x38/0x12c [ 0.759175] __handle_mm_fault+0x530/0xcf0 [ 0.759518] handle_mm_fault+0xe4/0x230 [ 0.759833] do_page_fault+0x17c/0x4dc [ 0.760144] do_translation_fault+0x30/0x38 [ 0.760483] do_mem_abort+0x40/0x8c [ 0.760771] el0_ia+0x4c/0x170 [ 0.761032] el0t_64_sync_handler+0xd8/0xdc [ 0.761371] el0t_64_sync+0x168/0x16c [ 0.761677] Code: f9453021 f2dfbfe3 cb813080 8b001860 (f9400401) [ 0.762168] ---[ end trace 0000000000000000 ]--- [ 0.762550] note: init[1] exited with irqs disabled [ 0.762631] Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b Link: https://lore.kernel.org/m2y0enxtzk.fsf@arm.com Fixes: 38607c62b34b ("fs/dax: properly refcount fs dax pages") Signed-off-by: Seunguk Shin Reviewed-by: Jan Kara Reviewed-by: Alistair Popple Reported-by: Kiara Grouwstra Cc: Al Viro Cc: Christian Brauner Cc: Matthew Wilcox (Oracle) Cc: Signed-off-by: Andrew Morton --- fs/dax.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/dax.c b/fs/dax.c index 6ba50142eeb2..1fbba0d21c13 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -480,11 +480,12 @@ static void dax_associate_entry(void *entry, struct address_space *mapping, unsigned long address, bool shared) { unsigned long size = dax_entry_size(entry), index; - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return; + folio = dax_to_folio(entry); index = linear_page_index(vma, address & ~(size - 1)); if (shared && (folio->mapping || dax_folio_is_shared(folio))) { if (folio->mapping) @@ -505,21 +506,23 @@ static void dax_associate_entry(void *entry, struct address_space *mapping, static void dax_disassociate_entry(void *entry, struct address_space *mapping, bool trunc) { - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return; + folio = dax_to_folio(entry); dax_folio_put(folio); } static struct page *dax_busy_page(void *entry) { - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return NULL; + folio = dax_to_folio(entry); if (folio_ref_count(folio) - folio_mapcount(folio)) return &folio->page; else From 0791a234b35d4b72187c497e05817f2c9019c3ab Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Tue, 1 Sep 2026 13:09:29 -0700 Subject: [PATCH 0311/1317] remove old lib/alloc_tag.c This was moved into mm/, but the original lib/ file somehow remained. Remove it. Reported-by: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- lib/alloc_tag.c | 1029 ----------------------------------------------- 1 file changed, 1029 deletions(-) delete mode 100644 lib/alloc_tag.c diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c deleted file mode 100644 index e5b218176c5a..000000000000 --- a/lib/alloc_tag.c +++ /dev/null @@ -1,1029 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ALLOCINFO_FILE_NAME "allocinfo" -#define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag)) -#define SECTION_START(NAME) (CODETAG_SECTION_START_PREFIX NAME) -#define SECTION_STOP(NAME) (CODETAG_SECTION_STOP_PREFIX NAME) - -#ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT -static bool mem_profiling_support = true; -#else -static bool mem_profiling_support; -#endif - -/* - * Memory allocation profiling is permanently disabled and cannot be enabled. - * Must be called after setup_early_mem_profiling(). - */ -bool mem_alloc_profiling_permanently_disabled(void) -{ - return !mem_profiling_support; -} - -static struct codetag_type *alloc_tag_cttype; - -#ifdef CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPU -DEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag); -EXPORT_SYMBOL(_shared_alloc_tag); -#endif - -DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT, - mem_alloc_profiling_key); -EXPORT_SYMBOL(mem_alloc_profiling_key); - -DEFINE_STATIC_KEY_FALSE(mem_profiling_compressed); - -struct alloc_tag_kernel_section kernel_tags = { NULL, 0 }; -unsigned long alloc_tag_ref_mask; -int alloc_tag_ref_offs; - -struct allocinfo_private { - struct codetag_iterator iter; - struct codetag_iterator reported_iter; - bool print_header; -}; - -static void *allocinfo_start(struct seq_file *m, loff_t *pos) -{ - struct allocinfo_private *priv; - loff_t node = *pos; - - priv = (struct allocinfo_private *)m->private; - codetag_lock_module_list(alloc_tag_cttype); - if (node == 0) { - priv->print_header = true; - priv->iter = codetag_get_ct_iter(alloc_tag_cttype); - } else { - priv->iter = priv->reported_iter; - } - codetag_next_ct(&priv->iter); - return priv->iter.ct ? priv : NULL; -} - -static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos) -{ - struct allocinfo_private *priv = (struct allocinfo_private *)arg; - struct codetag *ct; - - priv->reported_iter = priv->iter; - ct = codetag_next_ct(&priv->iter); - (*pos)++; - if (!ct) - return NULL; - - return priv; -} - -static void allocinfo_stop(struct seq_file *m, void *arg) -{ - codetag_unlock_module_list(alloc_tag_cttype); -} - -static void print_allocinfo_header(struct seq_buf *buf) -{ - /* Output format version, so we can change it. */ - seq_buf_printf(buf, "allocinfo - version: 2.0\n"); - seq_buf_printf(buf, "# \n"); -} - -static void alloc_tag_to_text(struct seq_buf *out, struct codetag *ct) -{ - struct alloc_tag *tag = ct_to_alloc_tag(ct); - struct alloc_tag_counters counter = alloc_tag_read(tag); - s64 bytes = counter.bytes; - - seq_buf_printf(out, "%12lli %8llu ", bytes, counter.calls); - codetag_to_text(out, ct); - if (unlikely(alloc_tag_is_inaccurate(tag))) - seq_buf_printf(out, " accurate:no"); - seq_buf_putc(out, ' '); - seq_buf_putc(out, '\n'); -} - -static int allocinfo_show(struct seq_file *m, void *arg) -{ - struct allocinfo_private *priv = (struct allocinfo_private *)arg; - char *bufp; - size_t n = seq_get_buf(m, &bufp); - struct seq_buf buf; - - seq_buf_init(&buf, bufp, n); - if (priv->print_header) { - print_allocinfo_header(&buf); - priv->print_header = false; - } - alloc_tag_to_text(&buf, priv->iter.ct); - seq_commit(m, seq_buf_used(&buf)); - return 0; -} - -static const struct seq_operations allocinfo_seq_op = { - .start = allocinfo_start, - .next = allocinfo_next, - .stop = allocinfo_stop, - .show = allocinfo_show, -}; - -size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep) -{ - struct codetag_iterator iter; - struct codetag *ct; - struct codetag_bytes n; - unsigned int i, nr = 0; - - if (IS_ERR_OR_NULL(alloc_tag_cttype)) - return 0; - - if (can_sleep) - codetag_lock_module_list(alloc_tag_cttype); - else if (!codetag_trylock_module_list(alloc_tag_cttype)) - return 0; - - iter = codetag_get_ct_iter(alloc_tag_cttype); - while ((ct = codetag_next_ct(&iter))) { - struct alloc_tag_counters counter = alloc_tag_read(ct_to_alloc_tag(ct)); - - n.ct = ct; - n.bytes = counter.bytes; - - for (i = 0; i < nr; i++) - if (n.bytes > tags[i].bytes) - break; - - if (i < count) { - nr -= nr == count; - memmove(&tags[i + 1], - &tags[i], - sizeof(tags[0]) * (nr - i)); - nr++; - tags[i] = n; - } - } - - codetag_unlock_module_list(alloc_tag_cttype); - - return nr; -} - -void pgalloc_tag_split(struct folio *folio, int old_order, int new_order) -{ - int i; - struct alloc_tag *tag; - unsigned int nr_pages = 1 << new_order; - - if (!mem_alloc_profiling_enabled()) - return; - - tag = __pgalloc_tag_get(&folio->page); - if (!tag) - return; - - for (i = nr_pages; i < (1 << old_order); i += nr_pages) { - union pgtag_ref_handle handle; - union codetag_ref ref; - - if (get_page_tag_ref(folio_page(folio, i), &ref, &handle)) { - /* Set new reference to point to the original tag */ - alloc_tag_ref_set(&ref, tag); - update_page_tag_ref(handle, &ref); - put_page_tag_ref(handle); - } - } -} - -void pgalloc_tag_swap(struct folio *new, struct folio *old) -{ - union pgtag_ref_handle handle_old, handle_new; - union codetag_ref ref_old, ref_new; - struct alloc_tag *tag_old, *tag_new; - - if (!mem_alloc_profiling_enabled()) - return; - - tag_old = __pgalloc_tag_get(&old->page); - if (!tag_old) - return; - tag_new = __pgalloc_tag_get(&new->page); - if (!tag_new) - return; - - if (!get_page_tag_ref(&old->page, &ref_old, &handle_old)) - return; - if (!get_page_tag_ref(&new->page, &ref_new, &handle_new)) { - put_page_tag_ref(handle_old); - return; - } - - /* - * Clear tag references to avoid debug warning when using - * __alloc_tag_ref_set() with non-empty reference. - */ - set_codetag_empty(&ref_old); - set_codetag_empty(&ref_new); - - /* swap tags */ - __alloc_tag_ref_set(&ref_old, tag_new); - update_page_tag_ref(handle_old, &ref_old); - __alloc_tag_ref_set(&ref_new, tag_old); - update_page_tag_ref(handle_new, &ref_new); - - put_page_tag_ref(handle_old); - put_page_tag_ref(handle_new); -} - -static void shutdown_mem_profiling(bool remove_file) -{ - if (mem_alloc_profiling_enabled()) - static_branch_disable(&mem_alloc_profiling_key); - - if (!mem_profiling_support) - return; - - if (remove_file) - remove_proc_entry(ALLOCINFO_FILE_NAME, NULL); - mem_profiling_support = false; -} - -void __init alloc_tag_sec_init(void) -{ - struct alloc_tag *last_codetag; - - if (!mem_profiling_support) - return; - - if (!static_key_enabled(&mem_profiling_compressed)) - return; - - kernel_tags.first_tag = (struct alloc_tag *)kallsyms_lookup_name( - SECTION_START(ALLOC_TAG_SECTION_NAME)); - last_codetag = (struct alloc_tag *)kallsyms_lookup_name( - SECTION_STOP(ALLOC_TAG_SECTION_NAME)); - kernel_tags.count = last_codetag - kernel_tags.first_tag; - - /* Check if kernel tags fit into page flags */ - if (kernel_tags.count > (1UL << NR_UNUSED_PAGEFLAG_BITS)) { - shutdown_mem_profiling(false); /* allocinfo file does not exist yet */ - pr_err("%lu allocation tags cannot be references using %d available page flag bits. Memory allocation profiling is disabled!\n", - kernel_tags.count, NR_UNUSED_PAGEFLAG_BITS); - return; - } - - alloc_tag_ref_offs = (LRU_REFS_PGOFF - NR_UNUSED_PAGEFLAG_BITS); - alloc_tag_ref_mask = ((1UL << NR_UNUSED_PAGEFLAG_BITS) - 1); - pr_debug("Memory allocation profiling compression is using %d page flag bits!\n", - NR_UNUSED_PAGEFLAG_BITS); -} - -#ifdef CONFIG_MODULES - -static struct maple_tree mod_area_mt = MTREE_INIT(mod_area_mt, MT_FLAGS_ALLOC_RANGE); -static struct vm_struct *vm_module_tags; -/* A dummy object used to indicate an unloaded module */ -static struct module unloaded_mod; -/* A dummy object used to indicate a module prepended area */ -static struct module prepend_mod; - -struct alloc_tag_module_section module_tags; - -static inline unsigned long alloc_tag_align(unsigned long val) -{ - if (!static_key_enabled(&mem_profiling_compressed)) { - /* No alignment requirements when we are not indexing the tags */ - return val; - } - - if (val % sizeof(struct alloc_tag) == 0) - return val; - return ((val / sizeof(struct alloc_tag)) + 1) * sizeof(struct alloc_tag); -} - -static bool ensure_alignment(unsigned long align, unsigned int *prepend) -{ - if (!static_key_enabled(&mem_profiling_compressed)) { - /* No alignment requirements when we are not indexing the tags */ - return true; - } - - /* - * If alloc_tag size is not a multiple of required alignment, tag - * indexing does not work. - */ - if (!IS_ALIGNED(sizeof(struct alloc_tag), align)) - return false; - - /* Ensure prepend consumes multiple of alloc_tag-sized blocks */ - if (*prepend) - *prepend = alloc_tag_align(*prepend); - - return true; -} - -static inline bool tags_addressable(void) -{ - unsigned long tag_idx_count; - - if (!static_key_enabled(&mem_profiling_compressed)) - return true; /* with page_ext tags are always addressable */ - - tag_idx_count = CODETAG_ID_FIRST + kernel_tags.count + - module_tags.size / sizeof(struct alloc_tag); - - return tag_idx_count < (1UL << NR_UNUSED_PAGEFLAG_BITS); -} - -static bool needs_section_mem(struct module *mod, unsigned long size) -{ - if (!mem_profiling_support) - return false; - - return size >= sizeof(struct alloc_tag); -} - -static bool clean_unused_counters(struct alloc_tag *start_tag, - struct alloc_tag *end_tag) -{ - struct alloc_tag *tag; - bool ret = true; - - for (tag = start_tag; tag <= end_tag; tag++) { - struct alloc_tag_counters counter; - - if (!tag->counters) - continue; - - counter = alloc_tag_read(tag); - if (!counter.bytes) { - free_percpu(tag->counters); - tag->counters = NULL; - } else { - ret = false; - } - } - - return ret; -} - -/* Called with mod_area_mt locked */ -static void clean_unused_module_areas_locked(void) -{ - MA_STATE(mas, &mod_area_mt, 0, module_tags.size); - struct module *val; - - mas_for_each(&mas, val, module_tags.size) { - struct alloc_tag *start_tag; - struct alloc_tag *end_tag; - - if (val != &unloaded_mod) - continue; - - /* Release area if all tags are unused */ - start_tag = (struct alloc_tag *)(module_tags.start_addr + mas.index); - end_tag = (struct alloc_tag *)(module_tags.start_addr + mas.last); - if (clean_unused_counters(start_tag, end_tag)) - mas_erase(&mas); - } -} - -/* Called with mod_area_mt locked */ -static bool find_aligned_area(struct ma_state *mas, unsigned long section_size, - unsigned long size, unsigned int prepend, unsigned long align) -{ - bool cleanup_done = false; - -repeat: - /* Try finding exact size and hope the start is aligned */ - if (!mas_empty_area(mas, 0, section_size - 1, prepend + size)) { - if (IS_ALIGNED(mas->index + prepend, align)) - return true; - - /* Try finding larger area to align later */ - mas_reset(mas); - if (!mas_empty_area(mas, 0, section_size - 1, - size + prepend + align - 1)) - return true; - } - - /* No free area, try cleanup stale data and repeat the search once */ - if (!cleanup_done) { - clean_unused_module_areas_locked(); - cleanup_done = true; - mas_reset(mas); - goto repeat; - } - - return false; -} - -static int vm_module_tags_populate(void) -{ - unsigned long phys_end = ALIGN_DOWN(module_tags.start_addr, PAGE_SIZE) + - (vm_module_tags->nr_pages << PAGE_SHIFT); - unsigned long new_end = module_tags.start_addr + module_tags.size; - - if (phys_end < new_end) { - struct page **next_page = vm_module_tags->pages + vm_module_tags->nr_pages; - unsigned long old_shadow_end = ALIGN(phys_end, MODULE_ALIGN); - unsigned long new_shadow_end = ALIGN(new_end, MODULE_ALIGN); - unsigned long more_pages; - unsigned long nr = 0; - - more_pages = ALIGN(new_end - phys_end, PAGE_SIZE) >> PAGE_SHIFT; - while (nr < more_pages) { - unsigned long allocated; - - allocated = alloc_pages_bulk_node(GFP_KERNEL | __GFP_NOWARN, - NUMA_NO_NODE, more_pages - nr, next_page + nr); - - if (!allocated) - break; - nr += allocated; - } - - if (nr < more_pages || - vmap_pages_range(phys_end, phys_end + (nr << PAGE_SHIFT), PAGE_KERNEL, - next_page, PAGE_SHIFT) < 0) { - release_pages_arg arg = { .pages = next_page }; - - /* Clean up and error out */ - release_pages(arg, nr); - return -ENOMEM; - } - - vm_module_tags->nr_pages += nr; - - /* - * Kasan allocates 1 byte of shadow for every 8 bytes of data. - * When kasan_alloc_module_shadow allocates shadow memory, - * its unit of allocation is a page. - * Therefore, here we need to align to MODULE_ALIGN. - */ - if (old_shadow_end < new_shadow_end) - kasan_alloc_module_shadow((void *)old_shadow_end, - new_shadow_end - old_shadow_end, - GFP_KERNEL); - } - - /* - * Mark the pages as accessible, now that they are mapped. - * With hardware tag-based KASAN, marking is skipped for - * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc(). - */ - kasan_unpoison_vmalloc((void *)module_tags.start_addr, - new_end - module_tags.start_addr, - KASAN_VMALLOC_PROT_NORMAL); - - return 0; -} - -static void *reserve_module_tags(struct module *mod, unsigned long size, - unsigned int prepend, unsigned long align) -{ - unsigned long section_size = module_tags.end_addr - module_tags.start_addr; - MA_STATE(mas, &mod_area_mt, 0, section_size - 1); - unsigned long offset; - void *ret = NULL; - - /* If no tags return error */ - if (size < sizeof(struct alloc_tag)) - return ERR_PTR(-EINVAL); - - /* - * align is always power of 2, so we can use IS_ALIGNED and ALIGN. - * align 0 or 1 means no alignment, to simplify set to 1. - */ - if (!align) - align = 1; - - if (!ensure_alignment(align, &prepend)) { - shutdown_mem_profiling(true); - pr_err("%s: alignment %lu is incompatible with allocation tag indexing. Memory allocation profiling is disabled!\n", - mod->name, align); - return ERR_PTR(-EINVAL); - } - - mas_lock(&mas); - if (!find_aligned_area(&mas, section_size, size, prepend, align)) { - ret = ERR_PTR(-ENOMEM); - goto unlock; - } - - /* Mark found area as reserved */ - offset = mas.index; - offset += prepend; - offset = ALIGN(offset, align); - if (offset != mas.index) { - unsigned long pad_start = mas.index; - - mas.last = offset - 1; - mas_store(&mas, &prepend_mod); - if (mas_is_err(&mas)) { - ret = ERR_PTR(xa_err(mas.node)); - goto unlock; - } - mas.index = offset; - mas.last = offset + size - 1; - mas_store(&mas, mod); - if (mas_is_err(&mas)) { - mas.index = pad_start; - mas_erase(&mas); - ret = ERR_PTR(xa_err(mas.node)); - } - } else { - mas.last = offset + size - 1; - mas_store(&mas, mod); - if (mas_is_err(&mas)) - ret = ERR_PTR(xa_err(mas.node)); - } -unlock: - mas_unlock(&mas); - - if (IS_ERR(ret)) - return ret; - - if (module_tags.size < offset + size) { - int grow_res; - - module_tags.size = offset + size; - if (mem_alloc_profiling_enabled() && !tags_addressable()) { - shutdown_mem_profiling(true); - pr_warn("With module %s there are too many tags to fit in %d page flag bits. Memory allocation profiling is disabled!\n", - mod->name, NR_UNUSED_PAGEFLAG_BITS); - } - - grow_res = vm_module_tags_populate(); - if (grow_res) { - shutdown_mem_profiling(true); - pr_err("Failed to allocate memory for allocation tags in the module %s. Memory allocation profiling is disabled!\n", - mod->name); - return ERR_PTR(grow_res); - } - } - - return (struct alloc_tag *)(module_tags.start_addr + offset); -} - -static void release_module_tags(struct module *mod, bool used) -{ - MA_STATE(mas, &mod_area_mt, module_tags.size, module_tags.size); - struct alloc_tag *start_tag; - struct alloc_tag *end_tag; - struct module *val; - - mas_lock(&mas); - mas_for_each_rev(&mas, val, 0) - if (val == mod) - break; - - if (!val) /* module not found */ - goto out; - - if (!used) - goto release_area; - - start_tag = (struct alloc_tag *)(module_tags.start_addr + mas.index); - end_tag = (struct alloc_tag *)(module_tags.start_addr + mas.last); - if (!clean_unused_counters(start_tag, end_tag)) { - struct alloc_tag *tag; - - for (tag = start_tag; tag <= end_tag; tag++) { - struct alloc_tag_counters counter; - - if (!tag->counters) - continue; - - counter = alloc_tag_read(tag); - pr_info("%s:%u module %s func:%s has %llu allocated at module unload\n", - tag->ct.filename, tag->ct.lineno, tag->ct.modname, - tag->ct.function, counter.bytes); - } - } else { - used = false; - } -release_area: - mas_store(&mas, used ? &unloaded_mod : NULL); - val = mas_prev_range(&mas, 0); - if (val == &prepend_mod) - mas_store(&mas, NULL); -out: - mas_unlock(&mas); -} - -static int load_module(struct module *mod, struct codetag *start, struct codetag *stop) -{ - /* Allocate module alloc_tag percpu counters */ - struct alloc_tag *start_tag; - struct alloc_tag *stop_tag; - struct alloc_tag *tag; - - /* percpu counters for core allocations are already statically allocated */ - if (!mod) - return 0; - - start_tag = ct_to_alloc_tag(start); - stop_tag = ct_to_alloc_tag(stop); - for (tag = start_tag; tag < stop_tag; tag++) { - WARN_ON(tag->counters); - tag->counters = alloc_percpu(struct alloc_tag_counters); - if (!tag->counters) { - while (--tag >= start_tag) { - free_percpu(tag->counters); - tag->counters = NULL; - } - pr_err("Failed to allocate memory for allocation tag percpu counters in the module %s\n", - mod->name); - return -ENOMEM; - } - - /* - * Avoid a kmemleak false positive. The pointer to the counters is stored - * in the alloc_tag section of the module and cannot be directly accessed. - */ - kmemleak_ignore_percpu(tag->counters); - } - return 0; -} - -static void replace_module(struct module *mod, struct module *new_mod) -{ - MA_STATE(mas, &mod_area_mt, 0, module_tags.size); - struct module *val; - - mas_lock(&mas); - mas_for_each(&mas, val, module_tags.size) { - if (val != mod) - continue; - - mas_store_gfp(&mas, new_mod, GFP_KERNEL); - break; - } - mas_unlock(&mas); -} - -static int __init alloc_mod_tags_mem(void) -{ - /* Map space to copy allocation tags */ - vm_module_tags = execmem_vmap(MODULE_ALLOC_TAG_VMAP_SIZE); - if (!vm_module_tags) { - pr_err("Failed to map %lu bytes for module allocation tags\n", - MODULE_ALLOC_TAG_VMAP_SIZE); - module_tags.start_addr = 0; - return -ENOMEM; - } - - vm_module_tags->pages = kmalloc_objs(struct page *, - get_vm_area_size(vm_module_tags) >> PAGE_SHIFT, - GFP_KERNEL | __GFP_ZERO); - if (!vm_module_tags->pages) { - free_vm_area(vm_module_tags); - return -ENOMEM; - } - - module_tags.start_addr = (unsigned long)vm_module_tags->addr; - module_tags.end_addr = module_tags.start_addr + MODULE_ALLOC_TAG_VMAP_SIZE; - /* Ensure the base is alloc_tag aligned when required for indexing */ - module_tags.start_addr = alloc_tag_align(module_tags.start_addr); - - return 0; -} - -static void __init free_mod_tags_mem(void) -{ - release_pages_arg arg = { .pages = vm_module_tags->pages }; - - module_tags.start_addr = 0; - release_pages(arg, vm_module_tags->nr_pages); - kfree(vm_module_tags->pages); - free_vm_area(vm_module_tags); -} - -#else /* CONFIG_MODULES */ - -static inline int alloc_mod_tags_mem(void) { return 0; } -static inline void free_mod_tags_mem(void) {} - -#endif /* CONFIG_MODULES */ - -/* See: Documentation/mm/allocation-profiling.rst */ -static int __init setup_early_mem_profiling(char *str) -{ - bool compressed = false; - bool enable; - - if (!str || !str[0]) - return -EINVAL; - - if (!strncmp(str, "never", 5)) { - enable = false; - mem_profiling_support = false; - pr_info("Memory allocation profiling is disabled!\n"); - } else { - char *token = strsep(&str, ","); - - if (kstrtobool(token, &enable)) - return -EINVAL; - - if (str) { - - if (strcmp(str, "compressed")) - return -EINVAL; - - compressed = true; - } - mem_profiling_support = true; - pr_info("Memory allocation profiling is enabled %s compression and is turned %s!\n", - compressed ? "with" : "without", str_on_off(enable)); - } - - if (enable != mem_alloc_profiling_enabled()) { - if (enable) - static_branch_enable(&mem_alloc_profiling_key); - else - static_branch_disable(&mem_alloc_profiling_key); - } - if (compressed != static_key_enabled(&mem_profiling_compressed)) { - if (compressed) - static_branch_enable(&mem_profiling_compressed); - else - static_branch_disable(&mem_profiling_compressed); - } - - return 0; -} -early_param("sysctl.vm.mem_profiling", setup_early_mem_profiling); - -static __init bool need_page_alloc_tagging(void) -{ - if (static_key_enabled(&mem_profiling_compressed)) - return false; - - return mem_profiling_support; -} - -#ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG -/* - * Track page allocations before page_ext is initialized. - * Some pages are allocated before page_ext becomes available, leaving - * their codetag uninitialized. Track these early PFNs so we can clear - * their codetag refs later to avoid warnings when they are freed. - * - * Each page is cast to a pfn_pool: the first few bytes hold metadata - * (next pointer and slot count), the remainder stores PFNs. - */ -struct pfn_pool { - struct pfn_pool *next; - atomic_t count; - unsigned long pfns[]; -}; - -#define PFN_POOL_SIZE ((PAGE_SIZE - offsetof(struct pfn_pool, pfns)) / \ - sizeof(unsigned long)) - -/* - * Skip early PFN recording for a page allocation. Reuses the - * %__GFP_NO_OBJ_EXT bit. Used by __alloc_tag_add_early_pfn() to avoid - * recursion when allocating pages for the early PFN tracking list - * itself. - * - * Codetags of the pages allocated with __GFP_NO_CODETAG should be - * cleared (via clear_page_tag_ref()) before freeing the pages to prevent - * alloc_tag_sub_check() from triggering a warning. - */ -#define __GFP_NO_CODETAG __GFP_NO_OBJ_EXT - -static struct pfn_pool *current_pfn_pool __initdata; - -static void __init __alloc_tag_add_early_pfn(unsigned long pfn) -{ - struct pfn_pool *pool; - int idx; - - do { - pool = READ_ONCE(current_pfn_pool); - if (!pool || atomic_read(&pool->count) >= PFN_POOL_SIZE) { - struct page *new_page = alloc_page(__GFP_HIGH | __GFP_NO_CODETAG); - struct pfn_pool *new; - - if (!new_page) { - pr_warn_once("early PFN tracking page allocation failed\n"); - return; - } - new = page_address(new_page); - new->next = pool; - atomic_set(&new->count, 0); - if (cmpxchg(¤t_pfn_pool, pool, new) != pool) { - clear_page_tag_ref(new_page); - __free_page(new_page); - continue; - } - pool = new; - } - idx = atomic_read(&pool->count); - if (idx >= PFN_POOL_SIZE) - continue; - if (atomic_cmpxchg(&pool->count, idx, idx + 1) == idx) - break; - } while (1); - - pool->pfns[idx] = pfn; -} - -typedef void alloc_tag_add_func(unsigned long pfn); -static alloc_tag_add_func __rcu *alloc_tag_add_early_pfn_ptr __refdata = - RCU_INITIALIZER(__alloc_tag_add_early_pfn); - -void alloc_tag_add_early_pfn(unsigned long pfn, gfp_t gfp_flags) -{ - alloc_tag_add_func *alloc_tag_add; - - if (static_key_enabled(&mem_profiling_compressed)) - return; - - /* Skip allocations for the tracking list itself to avoid recursion. */ - if (gfp_flags & __GFP_NO_CODETAG) - return; - - rcu_read_lock(); - alloc_tag_add = rcu_dereference(alloc_tag_add_early_pfn_ptr); - if (alloc_tag_add) - alloc_tag_add(pfn); - rcu_read_unlock(); -} - -static void __init clear_early_alloc_pfn_tag_refs(void) -{ - struct pfn_pool *pool, *next; - struct page *page; - int i; - - if (static_key_enabled(&mem_profiling_compressed)) - return; - - rcu_assign_pointer(alloc_tag_add_early_pfn_ptr, NULL); - /* Make sure we are not racing with __alloc_tag_add_early_pfn() */ - synchronize_rcu(); - - for (pool = current_pfn_pool; pool; pool = next) { - int nr_pfns = atomic_read(&pool->count); - - for (i = 0; i < nr_pfns; i++) { - unsigned long pfn = pool->pfns[i]; - - if (pfn_valid(pfn)) { - union pgtag_ref_handle handle; - union codetag_ref ref; - - if (get_page_tag_ref(pfn_to_page(pfn), &ref, &handle)) { - /* - * An early-allocated page could be freed and reallocated - * after its page_ext is initialized but before we clear it. - * In that case, it already has a valid tag set. - * We should not overwrite that valid tag - * with CODETAG_EMPTY. - * - * Note: there is still a small race window between checking - * ref.ct and calling set_codetag_empty(). We accept this - * race as it's unlikely and the extra complexity of atomic - * cmpxchg is not worth it for this debug-only code path. - */ - if (ref.ct) { - put_page_tag_ref(handle); - continue; - } - - set_codetag_empty(&ref); - update_page_tag_ref(handle, &ref); - put_page_tag_ref(handle); - } - } - } - - next = pool->next; - page = virt_to_page(pool); - clear_page_tag_ref(page); - __free_page(page); - } -} -#else /* !CONFIG_MEM_ALLOC_PROFILING_DEBUG */ -static inline void __init clear_early_alloc_pfn_tag_refs(void) {} -#endif /* CONFIG_MEM_ALLOC_PROFILING_DEBUG */ - -static __init void init_page_alloc_tagging(void) -{ - clear_early_alloc_pfn_tag_refs(); -} - -struct page_ext_operations page_alloc_tagging_ops = { - .size = sizeof(union codetag_ref), - .need = need_page_alloc_tagging, - .init = init_page_alloc_tagging, -}; -EXPORT_SYMBOL(page_alloc_tagging_ops); - -#ifdef CONFIG_SYSCTL -/* - * Not using proc_do_static_key() directly to prevent enabling profiling - * after it was shut down. - */ -static int proc_mem_profiling_handler(const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos) -{ - if (write) { - /* - * Call from do_sysctl_args() which is a no-op since the same - * value was already set by setup_early_mem_profiling. - * Return success to avoid warnings from do_sysctl_args(). - */ - if (!current->mm) - return 0; - -#ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG - /* User can't toggle profiling while debugging */ - return -EACCES; -#endif - if (!mem_profiling_support) - return -EINVAL; - } - - return proc_do_static_key(table, write, buffer, lenp, ppos); -} - - -static const struct ctl_table memory_allocation_profiling_sysctls[] = { - { - .procname = "mem_profiling", - .data = &mem_alloc_profiling_key, - .mode = 0644, - .proc_handler = proc_mem_profiling_handler, - }, -}; - -static void __init sysctl_init(void) -{ - register_sysctl_init("vm", memory_allocation_profiling_sysctls); -} -#else /* CONFIG_SYSCTL */ -static inline void sysctl_init(void) {} -#endif /* CONFIG_SYSCTL */ - -static int __init alloc_tag_init(void) -{ - const struct codetag_type_desc desc = { - .section = ALLOC_TAG_SECTION_NAME, - .tag_size = sizeof(struct alloc_tag), -#ifdef CONFIG_MODULES - .needs_section_mem = needs_section_mem, - .alloc_section_mem = reserve_module_tags, - .free_section_mem = release_module_tags, - .module_load = load_module, - .module_replaced = replace_module, -#endif - }; - int res; - - sysctl_init(); - - if (!mem_profiling_support) { - pr_info("Memory allocation profiling is not supported!\n"); - return 0; - } - - if (!proc_create_seq_private(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op, - sizeof(struct allocinfo_private), NULL)) { - pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME); - shutdown_mem_profiling(false); - return -ENOMEM; - } - - res = alloc_mod_tags_mem(); - if (res) { - pr_err("Failed to reserve address space for module tags, errno = %d\n", res); - shutdown_mem_profiling(true); - return res; - } - - alloc_tag_cttype = codetag_register_type(&desc); - if (IS_ERR(alloc_tag_cttype)) { - pr_err("Allocation tags registration failed, errno = %pe\n", alloc_tag_cttype); - free_mod_tags_mem(); - shutdown_mem_profiling(true); - return PTR_ERR(alloc_tag_cttype); - } - - return 0; -} -module_init(alloc_tag_init); From e14a3454806468b086fe2e4ca2e1bff95b528531 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Tue, 1 Sep 2026 11:01:09 -0700 Subject: [PATCH 0312/1317] mm/mlock: use the IRQ-safe accessor for NR_MLOCK in __munlock_folio() NR_MLOCK is updated from interrupt context. __free_pages_prepare() clears a stray PG_mlocked and adjusts NR_MLOCK, and a folio can reach it with the flag still set from a bio completion handler: __free_pages_ok+0x6af/0x7a0 __bio_release_pages+0xde/0x260 __iomap_dio_bio_end_io+0x16e/0x1a0 blk_update_request+0x14b/0x3d0 blk_mq_end_request+0x18/0x30 blk_done_softirq+0x49/0x60 The folio gets there like this. A MAP_SHARED file mapping is mlocked, so its page cache folios carry PG_mlocked, and an O_DIRECT write sourced from that mapping GUP-pins those same folios. munlock() then runs mlock_vma_pages_range(), which clears VM_LOCKED before walking the page tables to munlock each folio. A concurrent hole punch reaches the folio through the rmap (i_mmap_rwsem, not mmap_lock) and can land inside that window: __folio_remove_rmap() -> munlock_vma_folio() sees VM_LOCKED already clear, so it neither queues the folio on the mlock batch nor takes a reference, and the pte it clears makes the pending mlock_pte_range() walk skip the folio at its !pte_present() check. filemap_remove_folio() then drops the page cache reference, leaving the bio's pin as the last one, released from the completion handler above. So __zone_stat_mod_folio() here needs interrupts disabled, not merely preemption, and __munlock_folio() has a path where they are not: when the folio has already been taken off the LRU by somebody else the function jumps straight to the counter update without taking the lruvec lock. The read-modify-write of the per-CPU NR_MLOCK diff can then be interrupted by the softirq above, and one of the two decrements is lost, leaving Mlocked in /proc/meminfo permanently overstated. Use zone_stat_mod_folio(). mod_zone_state()'s this_cpu_try_cmpxchg() is atomic against a same-CPU interrupt and retries, and on the path where the lruvec lock is held its cost is negligible next to the lock itself. The UNEVICTABLE_PG* events are deliberately left on the __ accessors: they occupy different vm_event_states slots from the UNEVICTABLE_PGCLEARED that __free_pages_prepare() bumps, and nothing updates those two from interrupt context. Link: https://lore.kernel.org/20260901180109.3797944-1-shakeel.butt@linux.dev Fixes: 2fbb0c10d1e8 ("mm/munlock: mlock_page() munlock_page() batch by pagevec") Signed-off-by: Shakeel Butt Reported-by: syzbot+cd2073ee6d958a8d0fcd@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-mm/6a931c5a.08e933ee.dbf97.0093.GAE@google.com/ Acked-by: Hugh Dickins Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Pedro Falcato Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/mlock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/mlock.c b/mm/mlock.c index efa6716e4dfb..39215a3eab1f 100644 --- a/mm/mlock.c +++ b/mm/mlock.c @@ -141,7 +141,7 @@ static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec munlock: if (folio_test_clear_mlocked(folio)) { - __zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages); + zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages); if (isolated || !folio_test_unevictable(folio)) __count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages); else From e6662f2100f8d33b0f4d0047c219efd6bba186ea Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 2 Sep 2026 23:52:31 +0800 Subject: [PATCH 0313/1317] net/sched: defer qdisc freeing after failed creation An RTM_NEWQDISC request can make clsact bind a populated shared ingress block during ->init(), publishing an embedded mini_Qdisc to lockless readers. If the same request has an invalid TCA_RATE, estimator setup fails after ->init(); the unwind removes the pointer but synchronously frees its containing qdisc while tc_run() may still hold it. Retire failed qdiscs through the same RCU helper as normal destruction. Inline the synchronous free into the callback now that no direct callers remain. Fixes: 51ab2994c387 ("net: sched: allow ingress and clsact qdiscs to share filter blocks") Reported-by: Xiang Mei Link: https://lore.kernel.org/netdev/20260805102505.740806-1-david.lee@trailofbits.com/ Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260902155231.2149915-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- include/net/sch_generic.h | 2 +- net/sched/sch_api.c | 2 +- net/sched/sch_generic.c | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index cbc248776511..f35bd06a6bad 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -793,7 +793,7 @@ void qdisc_offload_query_caps(struct net_device *dev, struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, const struct Qdisc_ops *ops, struct netlink_ext_ack *extack); -void qdisc_free(struct Qdisc *qdisc); +void qdisc_free_rcu(struct Qdisc *qdisc); struct Qdisc *qdisc_create_dflt(struct netdev_queue *dev_queue, const struct Qdisc_ops *ops, u32 parentid, struct netlink_ext_ack *extack); diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 90503e59e6e3..463ededcdcfe 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -1385,7 +1385,7 @@ static struct Qdisc *qdisc_create(struct net_device *dev, err_out3: qdisc_lock_uninit(sch, ops); netdev_put(dev, &sch->dev_tracker); - qdisc_free(sch); + qdisc_free_rcu(sch); err_out2: bpf_module_put(ops, ops->owner); err_out: diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 4539dc2c6d38..6f6a6f0d5eb0 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -1086,21 +1086,21 @@ void qdisc_reset(struct Qdisc *qdisc) } EXPORT_SYMBOL(qdisc_reset); -void qdisc_free(struct Qdisc *qdisc) -{ - if (qdisc_is_percpu_stats(qdisc)) { - free_percpu(qdisc->cpu_bstats); - free_percpu(qdisc->cpu_qstats); - } - - kfree(qdisc); -} - static void qdisc_free_cb(struct rcu_head *head) { struct Qdisc *q = container_of(head, struct Qdisc, rcu); - qdisc_free(q); + if (qdisc_is_percpu_stats(q)) { + free_percpu(q->cpu_bstats); + free_percpu(q->cpu_qstats); + } + + kfree(q); +} + +void qdisc_free_rcu(struct Qdisc *qdisc) +{ + call_rcu(&qdisc->rcu, qdisc_free_cb); } static void __qdisc_destroy(struct Qdisc *qdisc) @@ -1127,7 +1127,7 @@ static void __qdisc_destroy(struct Qdisc *qdisc) trace_qdisc_destroy(qdisc); - call_rcu(&qdisc->rcu, qdisc_free_cb); + qdisc_free_rcu(qdisc); } void qdisc_destroy(struct Qdisc *qdisc) From 802eedcc0b25bb3e1b492f0600ab74325274d53b Mon Sep 17 00:00:00 2001 From: Shahar Shitrit Date: Wed, 2 Sep 2026 19:46:32 +0300 Subject: [PATCH 0314/1317] net/mlx5e: Fix missing FEC mode mapping for RS_544_514_INTERLEAVED_QUAD MLX5E_FEC_RS_544_514_INTERLEAVED_QUAD is missing from pplm_fec_2_ethtool_linkmodes[], leaving index 4 zero-initialized. As a result, when this FEC mode is active, find_first_bit() returns index 4, causing __set_bit() to set bit 0 (ETHTOOL_LINK_MODE_10baseT_Half_BIT) instead of ETHTOOL_LINK_MODE_FEC_RS_BIT. Consequently, ethtool reports: Advertised FEC modes: Not reported Add the missing mapping to ETHTOOL_LINK_MODE_FEC_RS_BIT. Fixes: 4e343c11efbb ("net/mlx5e: Support FEC settings for 200G per lane link modes") Signed-off-by: Shahar Shitrit Reviewed-by: Dragos Tatulea Reviewed-by: Yael Chemla Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902164634.3657606-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index 112926d07634..f285ad88b6d5 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -1013,6 +1013,7 @@ static const u32 pplm_fec_2_ethtool_linkmodes[] = { [MLX5E_FEC_NOFEC] = ETHTOOL_LINK_MODE_FEC_NONE_BIT, [MLX5E_FEC_FIRECODE] = ETHTOOL_LINK_MODE_FEC_BASER_BIT, [MLX5E_FEC_RS_528_514] = ETHTOOL_LINK_MODE_FEC_RS_BIT, + [MLX5E_FEC_RS_544_514_INTERLEAVED_QUAD] = ETHTOOL_LINK_MODE_FEC_RS_BIT, [MLX5E_FEC_RS_544_514] = ETHTOOL_LINK_MODE_FEC_RS_BIT, [MLX5E_FEC_LLRS_272_257_1] = ETHTOOL_LINK_MODE_FEC_LLRS_BIT, }; From b9d755c5a37519fb1354034db1dfeb30e1ba6856 Mon Sep 17 00:00:00 2001 From: Shahar Shitrit Date: Wed, 2 Sep 2026 19:46:33 +0300 Subject: [PATCH 0315/1317] net/mlx5e: Fix setting RS FEC after remapping When a user sets a FEC mode via ethtool, the driver maps the ethtool FEC type to the lowest mlx5 bit of that type. For RS FEC, this is MLX5E_FEC_RS_528_514 (bit 2). The driver then checks whether this bit is supported by at least one link mode by inspecting the fec_override_cap fields via mlx5e_fec_in_caps(), and returns -EOPNOTSUPP if not. This check is incorrect. RS FEC has three supported hardware variants: RS_528_514 (bit 2), RS_544_514_INTERLEAVED_QUAD (bit 4), and RS_544_514 (bit 7). mlx5e_remap_fec_conf_mode() already remaps bit 2 to the appropriate RS variant per link mode when writing the admin fields, but the early capability check is done against the raw unmapped bit. As a result, a device that supports RS_544_514 or RS_544_514_INTERLEAVED_QUAD but not RS_528_514 will incorrectly reject the user's RS FEC request. Remove the early support check from mlx5e_set_fec_mode() and fold it into the existing write loop, checking caps against the remapped policy per link mode. Return -EOPNOTSUPP before the final register write if no link mode accepted the policy. Fixes: 2608a2f831c4 ("net/mlx5e: Fix return status when setting unsupported FEC mode") Signed-off-by: Shahar Shitrit Reviewed-by: Dragos Tatulea Reviewed-by: Yael Chemla Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902164634.3657606-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/port.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/port.c b/drivers/net/ethernet/mellanox/mlx5/core/en/port.c index 6049ccf475bc..a4c096a4fed2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/port.c @@ -557,6 +557,7 @@ int mlx5e_set_fec_mode(struct mlx5_core_dev *dev, u16 fec_policy) u32 in[MLX5_ST_SZ_DW(pplm_reg)] = {}; int sz = MLX5_ST_SZ_BYTES(pplm_reg); u16 fec_policy_auto = 0; + bool fec_set = false; int err; int i; @@ -569,9 +570,6 @@ int mlx5e_set_fec_mode(struct mlx5_core_dev *dev, u16 fec_policy) if (fec_policy >= (1 << MLX5E_FEC_LLRS_272_257_1) && !fec_50g_per_lane) return -EOPNOTSUPP; - if (fec_policy && !mlx5e_fec_in_caps(dev, fec_policy)) - return -EOPNOTSUPP; - MLX5_SET(pplm_reg, in, local_port, 1); err = mlx5_core_access_reg(dev, in, sz, out, sz, MLX5_REG_PPLM, 0, 0); if (err) @@ -591,12 +589,17 @@ int mlx5e_set_fec_mode(struct mlx5_core_dev *dev, u16 fec_policy) mlx5e_get_fec_cap_field(out, &fec_caps, i); /* policy supported for link speed */ - if (fec_caps & conf_fec) + if (fec_caps & conf_fec) { mlx5e_fec_admin_field(out, &conf_fec, 1, i); - else - /* set FEC to auto*/ + fec_set = true; + } else { + /* set FEC to auto */ mlx5e_fec_admin_field(out, &fec_policy_auto, 1, i); + } } + if (fec_policy && !fec_set) + return -EOPNOTSUPP; + return mlx5_core_access_reg(dev, out, sz, out, sz, MLX5_REG_PPLM, 0, 1); } From c84ce45a7a3f3f024502c7f53308db9c76e4ae71 Mon Sep 17 00:00:00 2001 From: Shahar Shitrit Date: Wed, 2 Sep 2026 19:46:34 +0300 Subject: [PATCH 0316/1317] net/mlx5e: Fix reporting support for all RS FEC variants get_fec_supported_advertised() populates the FEC modes reported as supported to userspace. The MLX5E_ADVERTISE_SUPPORTED_FEC macro only checked MLX5E_FEC_RS_528_514, causing devices that support only the other RS variants (RS_544_514_INTERLEAVED_QUAD or RS_544_514) to not advertise RS as supported to ethtool at all. Introduce MLX5E_FEC_RS_MASK covering all three RS bit positions, update the macro to accept a bitmask directly rather than a single enum value, and pass MLX5E_FEC_RS_MASK for the RS entry. Fixes: b5ede32d3329 ("net/mlx5e: Add support for FEC modes based on 50G per lane links") Fixes: 4e343c11efbb ("net/mlx5e: Support FEC settings for 200G per lane link modes") Signed-off-by: Shahar Shitrit Reviewed-by: Dragos Tatulea Reviewed-by: Yael Chemla Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902164634.3657606-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/port.h | 4 ++++ drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/port.h b/drivers/net/ethernet/mellanox/mlx5/core/en/port.h index fa2283dd383b..53dbdf77bcce 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/port.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/port.h @@ -66,4 +66,8 @@ enum { MLX5E_FEC_LLRS_272_257_1 = 9, }; +#define MLX5E_FEC_RS_MASK (BIT(MLX5E_FEC_RS_528_514) | \ + BIT(MLX5E_FEC_RS_544_514_INTERLEAVED_QUAD) | \ + BIT(MLX5E_FEC_RS_544_514)) + #endif diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index f285ad88b6d5..3ed59ced0407 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -1002,9 +1002,9 @@ static u32 pplm2ethtool_fec(u_long fec_mode, unsigned long size) return 0; } -#define MLX5E_ADVERTISE_SUPPORTED_FEC(mlx5_fec, ethtool_fec) \ +#define MLX5E_ADVERTISE_SUPPORTED_FEC(fec_mask, ethtool_fec) \ do { \ - if (mlx5e_fec_in_caps(dev, 1 << (mlx5_fec))) \ + if (mlx5e_fec_in_caps(dev, fec_mask)) \ __set_bit(ethtool_fec, \ link_ksettings->link_modes.supported);\ } while (0) @@ -1030,13 +1030,13 @@ static int get_fec_supported_advertised(struct mlx5_core_dev *dev, if (err) return (err == -EOPNOTSUPP) ? 0 : err; - MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_NOFEC, + MLX5E_ADVERTISE_SUPPORTED_FEC(BIT(MLX5E_FEC_NOFEC), ETHTOOL_LINK_MODE_FEC_NONE_BIT); - MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_FIRECODE, + MLX5E_ADVERTISE_SUPPORTED_FEC(BIT(MLX5E_FEC_FIRECODE), ETHTOOL_LINK_MODE_FEC_BASER_BIT); - MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_RS_528_514, + MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_RS_MASK, ETHTOOL_LINK_MODE_FEC_RS_BIT); - MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_LLRS_272_257_1, + MLX5E_ADVERTISE_SUPPORTED_FEC(BIT(MLX5E_FEC_LLRS_272_257_1), ETHTOOL_LINK_MODE_FEC_LLRS_BIT); active_fec_long = active_fec; From b3c79dee5038c5e8460c59d7d01cb1450bdf5ecb Mon Sep 17 00:00:00 2001 From: Akiva Goldberger Date: Wed, 2 Sep 2026 22:27:40 +0300 Subject: [PATCH 0317/1317] net/mlx5: LAG, use local tracker to update active ports The CREATE_LAG command is handled asynchronously by queuing a work, which stores a local copy of ldev->tracker. When the work is processed, it is possible that the values of the local copy and ldev->tracker have diverged. A single CREATE_LAG command programs two related fields into the firmware: the v2p (virtual-to-physical) map, which selects the physical egress port for each hash bucket, and the active_port bitmask, which tells the firmware which physical ports are currently up so it can redirect QP/TIS away from inactive ports. For the firmware to steer traffic correctly, both must be derived from the same view of the ports' link state. The v2p map is computed by mlx5_infer_tx_affinity_mapping() from the local tracker snapshot, but lag_active_port_bits() called mlx5_infer_tx_enabled() on the live ldev->tracker instead. If ldev->tracker changed between the snapshot and command execution, the two fields reflect different port states: the v2p map may steer a bucket to a port that the active_port mask marks as inactive (or vice versa). The firmware then receives a self-contradictory configuration and can redirect or drop traffic on a port the mapping still points at, until a later event happens to reconcile the state. Update lag_active_port_bits so that it receives the local version of the tracker from when the work was queued, effectively closing the window for injecting an inconsistency. Fixes: c5c13b456cb8 ("net/mlx5: Lag, set active ports if support bypass port select flow table") Signed-off-by: Akiva Goldberger Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902192740.3665435-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/lag/lag.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index 2285c889c215..c655f6e32e9b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -63,14 +63,15 @@ static int get_port_sel_mode(enum mlx5_lag_mode mode, unsigned long flags) return MLX5_LAG_PORT_SELECT_MODE_QUEUE_AFFINITY; } -static u8 lag_active_port_bits(struct mlx5_lag *ldev) +static u8 lag_active_port_bits(struct mlx5_lag *ldev, + struct lag_tracker *tracker) { u8 enabled_ports[MLX5_MAX_PORTS] = {}; u8 active_port = 0; int num_enabled; int idx; - mlx5_infer_tx_enabled(&ldev->tracker, ldev, enabled_ports, + mlx5_infer_tx_enabled(tracker, ldev, enabled_ports, &num_enabled); for (idx = 0; idx < num_enabled; idx++) active_port |= BIT_MASK(enabled_ports[idx]); @@ -79,7 +80,8 @@ static u8 lag_active_port_bits(struct mlx5_lag *ldev) } static int mlx5_cmd_create_lag(struct mlx5_core_dev *dev, struct mlx5_lag *ldev, - int mode, unsigned long flags) + struct lag_tracker *tracker, int mode, + unsigned long flags) { bool fdb_sel_mode = test_bit(MLX5_LAG_MODE_FLAG_FDB_SEL_MODE_NATIVE, &flags); @@ -108,7 +110,7 @@ static int mlx5_cmd_create_lag(struct mlx5_core_dev *dev, struct mlx5_lag *ldev, break; MLX5_SET(lagc, lag_ctx, active_port, - lag_active_port_bits(mlx5_lag_dev(dev))); + lag_active_port_bits(ldev, tracker)); break; default: break; @@ -787,7 +789,8 @@ static int mlx5_cmd_modify_active_port(struct mlx5_core_dev *dev, u8 ports) return mlx5_cmd_exec_in(dev, modify_lag, in); } -static int _mlx5_modify_lag(struct mlx5_lag *ldev, u8 *ports) +static int _mlx5_modify_lag(struct mlx5_lag *ldev, + struct lag_tracker *tracker, u8 *ports) { int idx = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P1); struct mlx5_core_dev *dev0; @@ -804,7 +807,7 @@ static int _mlx5_modify_lag(struct mlx5_lag *ldev, u8 *ports) !MLX5_CAP_PORT_SELECTION(dev0, port_select_flow_table_bypass)) return ret; - active_ports = lag_active_port_bits(ldev); + active_ports = lag_active_port_bits(ldev, tracker); return mlx5_cmd_modify_active_port(dev0, active_ports); } @@ -868,7 +871,7 @@ void mlx5_modify_lag(struct mlx5_lag *ldev, idx = i * ldev->buckets + j; if (ports[idx] == ldev->v2p_map[idx]) continue; - err = _mlx5_modify_lag(ldev, ports); + err = _mlx5_modify_lag(ldev, tracker, ports); if (err) { mlx5_core_err(dev0, "Failed to modify LAG (%d)\n", @@ -976,7 +979,7 @@ static int mlx5_create_lag(struct mlx5_lag *ldev, mlx5_core_info(dev0, "shared_fdb:%d mode:%s\n", shared_fdb, mlx5_get_str_port_sel_mode(mode, flags)); - err = mlx5_cmd_create_lag(dev0, ldev, mode, flags); + err = mlx5_cmd_create_lag(dev0, ldev, tracker, mode, flags); if (err) { mlx5_core_err(dev0, "Failed to create LAG (%d)\n", From e7ee89740800a1cf253713e9249c3ee9203ebe91 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 22:32:24 +0300 Subject: [PATCH 0318/1317] net/mlx5e: Fix ETS zero BW reporting when one TC holds 100% When ETS TCs with zero bandwidth are configured, the driver programs the firmware using an alternate representation. On get, it needs to recognize that representation so those TCs can be translated back and reported as 0% bandwidth. The existing detection relied on the programmed bandwidth because it was enough to identify this representation. However, when a single ETS TC owns 100% of the bandwidth, its firmware representation becomes the same as a strict-priority TC, causing zero-bandwidth ETS TCs to be reported with non-zero bandwidth values. Use the cached TSA instead to distinguish the ETS and strict-priority cases. Fixes: be0f161ef141 ("net/mlx5e: DCBNL, Implement tc with ets type and zero bandwidth") Signed-off-by: Carolina Jubran Reviewed-by: Alex Lazar Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193224.3668743-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c index 00e706e1ede1..741f75b5bfec 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c @@ -148,7 +148,7 @@ static int mlx5e_dcbnl_ieee_getets(struct net_device *netdev, if (err) return err; - if (ets->tc_tx_bw[i] < MLX5E_MAX_BW_ALLOC && + if (priv->dcbx.tc_tsa[i] == IEEE_8021QAZ_TSA_ETS && tc_group[i] == (MLX5E_LOWEST_PRIO_GROUP + 1)) is_zero_bw_ets_tc = true; From af3aef0245abbab5e9f6302e7a7d6407187afb71 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 22:33:41 +0300 Subject: [PATCH 0319/1317] net/mlx5e: Fix use-after-free race in sample_restore_put() Concurrent teardown of TC sample rules sharing the same restore context may re-read restore->count after dropping restore_lock. At that point another thread may already have completed cleanup and freed the restore object. Use the result of the refcount decrement while holding restore_lock to determine whether cleanup is needed. Fixes: 36a3196256bf ("net/mlx5e: TC, Add sampler restore handle API") Signed-off-by: Carolina Jubran Reviewed-by: Shahar Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193341.3668809-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c index 89490f687a9c..93c62d3f3e5b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c @@ -311,12 +311,15 @@ sample_restore_get(struct mlx5e_tc_psample *tc_psample, u32 obj_id, static void sample_restore_put(struct mlx5e_tc_psample *tc_psample, struct mlx5e_sample_restore *restore) { + bool last; + mutex_lock(&tc_psample->restore_lock); - if (--restore->count == 0) + last = --restore->count == 0; + if (last) hash_del(&restore->hlist); mutex_unlock(&tc_psample->restore_lock); - if (!restore->count) { + if (last) { mlx5_del_flow_rules(restore->rule); mlx5_modify_header_dealloc(tc_psample->esw->dev, restore->modify_hdr); kfree(restore); From 7ee07f601f8f507c9faf25c68a49396ab8950596 Mon Sep 17 00:00:00 2001 From: Yael Chemla Date: Wed, 2 Sep 2026 22:35:14 +0300 Subject: [PATCH 0320/1317] net/mlx5: E-Switch: fix use-after-free in mlx5_eswitch_termtbl_put In mlx5_eswitch_termtbl_put(), the zero-ref cleanup check reads tt->ref_count after termtbl_mutex has been released. Two concurrent callers on the same mlx5_termtbl_handle race: one decrements ref_count to zero, removes the hash entry, and calls kfree(tt) while the other has already dropped the mutex and is about to evaluate if (!tt->ref_count), producing a use-after-free. Fix this by capturing the result of the decrement into a stack-local last variable before dropping the mutex. The cleanup decision is now made entirely under termtbl_mutex, and tt is not touched after kfree. Fixes: 10caabdaad5a ("net/mlx5e: Use termination table for VLAN push actions") Signed-off-by: Yael Chemla Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193514.3668880-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c index 19f65d4c4def..d43f07360159 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c @@ -163,12 +163,15 @@ void mlx5_eswitch_termtbl_put(struct mlx5_eswitch *esw, struct mlx5_termtbl_handle *tt) { + bool last; + mutex_lock(&esw->offloads.termtbl_mutex); - if (--tt->ref_count == 0) + last = (--tt->ref_count == 0); + if (last) hash_del(&tt->termtbl_hlist); mutex_unlock(&esw->offloads.termtbl_mutex); - if (!tt->ref_count) { + if (last) { mlx5_del_flow_rules(tt->rule); mlx5_destroy_flow_table(tt->termtbl); kfree(tt); From c0c6f4ba8a37688f7b4d4044898d88f0450d44c2 Mon Sep 17 00:00:00 2001 From: Lama Kayal Date: Wed, 2 Sep 2026 22:38:54 +0300 Subject: [PATCH 0321/1317] net/mlx5: E-Switch, prevent mc_list repopulation during vport disable In mlx5_esw_vport_disable(), move esw_apply_vport_rx_mode() ahead of esw_vport_change_handle_locked() so vport->allmulti_rule is NULL before the change handler observes it. During FW-fatal recovery the disable runs while dev->state == INTERNAL_ERROR. The promisc query inside esw_update_vport_rx_mode() fails and returns early, leaving vport->allmulti_rule intact, so esw_update_vport_mc_promisc() runs and adds MLX5_ACTION_ADD entries to vport->mc_list whose flow rules are then installed in the FDB by esw_add_mc_addr(). esw_destroy_legacy_table() tears down the FDB with those refs still held, corrupting the sub-tree and leaving dangling flow_rule pointers in vport->mc_list. Two-stage failure on `echo 1 > /sys/bus/pci/devices//reset`: refcount_t: underflow; use-after-free. tree_put_node+0xef/0x110 [mlx5_core] clean_tree+0x44/0xd0 [mlx5_core] (x5) mlx5_fs_core_cleanup+0x57/0x1c0 [mlx5_core] mlx5_unload+0x65/0xd0 [mlx5_core] ... mlx5_health_try_recover BUG: unable to handle page fault for address: 0000000003000055 down_write+0x1c/0x60 mlx5_del_flow_rules+0x33/0x1f0 [mlx5_core] esw_del_mc_addr+0x7b/0x170 [mlx5_core] esw_apply_vport_addr_list+0x56/0xf0 [mlx5_core] esw_vport_change_handle_locked+0x28b/0x310 [mlx5_core] mlx5_esw_vport_enable+0x270/0x4a0 [mlx5_core] ... mlx5_load ... mlx5_health_try_recover esw_apply_vport_rx_mode(false, false) clears vport->allmulti_rule via its local state machine even when the FW del fails. With the rule NULL the !IS_ERR_OR_NULL(allmulti_rule) gate in the change handler closes, no rules are installed during disable, and the reload starts with a clean mc_list. Fixes: 922f56e9a795 ("net/mlx5: Fix steering rules cleanup") Signed-off-by: Lama Kayal Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193854.3669035-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index b6e2c153b4f7..4c7fa4a52b0e 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1040,13 +1040,19 @@ void mlx5_esw_vport_disable(struct mlx5_eswitch *esw, struct mlx5_vport *vport) (vport->info.ipsec_crypto_enabled || vport->info.ipsec_packet_enabled)) esw->enabled_ipsec_vf_count--; + /* Clear rx-mode before esw_vport_change_handle_locked(): on + * MLX5_VPORT_PROMISC_CHANGE it calls esw_update_vport_mc_promisc() + * when vport->allmulti_rule is set, repopulating mc_list with FDB + * rules that dangle once the FDB is destroyed. NULL allmulti_rule + * here skips that path. + */ + esw_apply_vport_rx_mode(esw, vport, false, false); /* We don't assume VFs will cleanup after themselves. * Calling vport change handler while vport is disabled will cleanup * the vport resources. */ esw_vport_change_handle_locked(vport); vport->enabled_events = 0; - esw_apply_vport_rx_mode(esw, vport, false, false); esw_vport_cleanup(esw, vport); esw->enabled_vports--; From df99553f840e4c529c1ba4c29bd39396466ca11a Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 22:37:31 +0300 Subject: [PATCH 0322/1317] net/mlx5e: Keep HW timestamp stats monotonic across reconfiguration `mlx5e_stats_ts_get()` currently selects either DMA or port timestamp counters based on `tx_ptp_opened`. This flag is intentionally kept set once the PTP TX queues have been opened so their statistics remain available after queue teardown. As a result, DMA timestamps are no longer reported after switching from port timestamping back to DMA timestamping. The function also reads statistics only from the currently active channels and TCs. Reducing the number of channels or TCs can therefore drop previously accumulated timestamp counters from the reported value. Read the persistent channel statistics instead and always include DMA timestamp counters. Once the PTP TX queues have been opened, also include the port timestamp counters. This also drops state_lock. It previously protected live channel/PTP pointers, the new code only reads persistent channel_stats and ptp_stats via mlx5e_stats_nch_read(), which is already safe for lockless stats access. Fixes: 3579032c08c1 ("net/mlx5e: Implement ethtool hardware timestamping statistics") Signed-off-by: Carolina Jubran Reviewed-by: Shahar Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193731.3668958-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlx5/core/en_stats.c | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c index e7e6db7f6bf1..cd94bb44f6ab 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c @@ -1199,50 +1199,39 @@ void mlx5e_stats_rmon_get(struct mlx5e_priv *priv, void mlx5e_stats_ts_get(struct mlx5e_priv *priv, struct ethtool_ts_stats *ts_stats) { - int i, j; + u16 nch = mlx5e_stats_nch_read(priv); + int i, tc; - mutex_lock(&priv->state_lock); + ts_stats->pkts = 0; + for (i = 0; i < nch; i++) { + struct mlx5e_channel_stats *channel_stats = + priv->channel_stats[i]; + + for (tc = 0; tc < priv->max_opened_tc; tc++) + ts_stats->pkts += channel_stats->sq[tc].timestamps; + } + + /* Accumulate DMA and port timestamp counters so values stay monotonic + * across channel teardown and mode switches. + */ if (priv->tx_ptp_opened) { - struct mlx5e_ptp *ptp = priv->channels.ptp; - - ts_stats->pkts = 0; + /* Err and Lost stats are only relevant for port timestamping, + * as the DMA layer will always successfully timestamp packets. + */ ts_stats->err = 0; ts_stats->lost = 0; - if (!ptp) - goto out; - - /* Aggregate stats across all TCs */ - for (i = 0; i < ptp->num_tc; i++) { + for (tc = 0; tc < priv->max_opened_tc; tc++) { struct mlx5e_ptp_cq_stats *stats = - ptp->ptpsq[i].cq_stats; + &priv->ptp_stats.cq[tc]; ts_stats->pkts += stats->cqe; ts_stats->err += stats->abort + stats->err_cqe + - stats->late_cqe; + stats->late_cqe; ts_stats->lost += stats->lost_cqe; } - } else { - /* DMA layer will always successfully timestamp packets. Other - * counters do not make sense for this layer. - */ - ts_stats->pkts = 0; - - /* Aggregate stats across all SQs */ - for (j = 0; j < priv->channels.num; j++) { - struct mlx5e_channel *c = priv->channels.c[j]; - - for (i = 0; i < c->num_tc; i++) { - struct mlx5e_sq_stats *stats = c->sq[i].stats; - - ts_stats->pkts += stats->timestamps; - } - } } - -out: - mutex_unlock(&priv->state_lock); } #define PPORT_PHY_LAYER_OFF(c) \ From 094cc07f98dfe70a34e2a1923af17fd29b8cf622 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:22 -0400 Subject: [PATCH 0323/1317] net/sched: fq: clamp quantum and initial_quantum in change path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fq change path accepts TCA_FQ_QUANTUM in [1, INT_MAX] and TCA_FQ_INITIAL_QUANTUM up to INT_MAX, while fq_init() already clamps to [1, 1<<20]. A user can override the init clamp via tc qdisc change, restoring the small-quantum deficit spin that the init clamp prevents. Narrow iq_range.max to 1<<20 so TCA_FQ_INITIAL_QUANTUM is rejected at parse time. Clamp TCA_FQ_QUANTUM to [256, 1<<20] in fq_change() and fq_init() quantum to [256, 1<<20] for tiny-MTU devices. Conditions to recreate the bug: CONFIG_NET_SCH_FQ=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root fq tc qdisc change dev dummy0 root fq quantum 1 stab data 32768 size_log 15 cell_log 0 Fixes: 709f34f7c28d ("net/sched: fq: add overflow bounds to quantum and initial quantum") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- net/sched/sch_fq.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c index 6144b5686f13..35f940b2205d 100644 --- a/net/sched/sch_fq.c +++ b/net/sched/sch_fq.c @@ -980,7 +980,7 @@ static int fq_resize(struct Qdisc *sch, u32 log) } static const struct netlink_range_validation iq_range = { - .max = INT_MAX, + .max = 1 << 20, }; static const struct nla_policy fq_policy[TCA_FQ_MAX + 1] = { @@ -1106,14 +1106,10 @@ static int fq_change(struct Qdisc *sch, struct nlattr *opt, nla_get_u32(tb[TCA_FQ_FLOW_PLIMIT])); if (tb[TCA_FQ_QUANTUM]) { - u32 quantum = nla_get_u32(tb[TCA_FQ_QUANTUM]); + u32 quantum = clamp_t(u32, nla_get_u32(tb[TCA_FQ_QUANTUM]), + 256, 1 << 20); - if (quantum > 0 && quantum <= (1 << 20)) { - WRITE_ONCE(q->quantum, quantum); - } else { - NL_SET_ERR_MSG_MOD(extack, "invalid quantum"); - err = -EINVAL; - } + WRITE_ONCE(q->quantum, quantum); } if (tb[TCA_FQ_INITIAL_QUANTUM]) @@ -1232,7 +1228,7 @@ static int fq_init(struct Qdisc *sch, struct nlattr *opt, sch->limit = 10000; q->flow_plimit = 100; mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); - q->quantum = min_t(u32, 2 * mtu, 1 << 20); + q->quantum = clamp_t(u32, 2 * mtu, 256, 1 << 20); q->initial_quantum = min_t(u32, 10 * mtu, 1 << 20); q->flow_refill_delay = msecs_to_jiffies(40); q->flow_max_rate = ~0UL; From 4864f58c53eb47257d55e01f47d4a9f355f7f970 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:23 -0400 Subject: [PATCH 0324/1317] net/sched: fq_pie: clamp quantum in change path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fq_pie_change() accepts any quantum value from userspace, including 1. With a crafted size table qdisc_pkt_len reaches ~2 GiB, so quantum=1 makes the deficit-refill loop spin ~2^31 times under the qdisc lock (a soft lockup / denial of service). Add max(256U, ...) matching fq_codel_change(). Conditions to recreate the bug: CONFIG_NET_SCH_FQ_PIE=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root fq_pie tc qdisc change dev dummy0 root fq_pie quantum 1 stab data 32768 size_log 15 cell_log 0 Fixes: ec97ecf1ebe4 ("net: sched: add Flow Queue PIE packet scheduler") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.3 Signed-off-by: Jakub Kicinski --- net/sched/sch_fq_pie.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_fq_pie.c b/net/sched/sch_fq_pie.c index b27d95418707..5982847df8f8 100644 --- a/net/sched/sch_fq_pie.c +++ b/net/sched/sch_fq_pie.c @@ -341,7 +341,8 @@ static int fq_pie_change(struct Qdisc *sch, struct nlattr *opt, nla_get_u32(tb[TCA_FQ_PIE_BETA])); if (tb[TCA_FQ_PIE_QUANTUM]) - WRITE_ONCE(q->quantum, nla_get_u32(tb[TCA_FQ_PIE_QUANTUM])); + WRITE_ONCE(q->quantum, + max(256U, nla_get_u32(tb[TCA_FQ_PIE_QUANTUM]))); if (tb[TCA_FQ_PIE_MEMORY_LIMIT]) WRITE_ONCE(q->memory_limit, From fb9f88a33c516ea5c0bcd9a22ca288b246b34567 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:24 -0400 Subject: [PATCH 0325/1317] net/sched: sfq: clamp quantum in change path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sfq_change() accepts any non-negative quantum (only rejects (int)ctl->quantum < 0). With a crafted size table qdisc_pkt_len reaches ~2 GiB, so quantum=1 makes the deficit-refill loop spin ~2^31 times under the qdisc lock (a soft lockup / denial of service). Add max(256U, ...) matching fq_codel_change(). Reject quantum > 1<<20 with -EINVAL, matching fq_codel_change() and the init clamp. Conditions to recreate the bug: CONFIG_NET_SCH_SFQ=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root sfq tc qdisc change dev dummy0 root sfq quantum 1 stab data 32768 size_log 15 cell_log 0 Fixes: e4650d7ae425 ("net_sched: sch_sfq: handle bigger packets") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.4 Signed-off-by: Jakub Kicinski --- net/sched/sch_sfq.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c index 187d3ed578f2..8bbcfc9e85d9 100644 --- a/net/sched/sch_sfq.c +++ b/net/sched/sch_sfq.c @@ -660,6 +660,11 @@ static int sfq_change(struct Qdisc *sch, struct nlattr *opt, return -EINVAL; } + if (ctl->quantum > 1 << 20) { + NL_SET_ERR_MSG_MOD(extack, "quantum too large"); + return -EINVAL; + } + if (ctl->perturb_period < 0 || ctl->perturb_period > INT_MAX / HZ) { NL_SET_ERR_MSG_MOD(extack, "invalid perturb period"); @@ -688,7 +693,7 @@ static int sfq_change(struct Qdisc *sch, struct nlattr *opt, /* update and validate configuration */ if (ctl->quantum) - quantum = ctl->quantum; + quantum = max(256U, ctl->quantum); if (ctl->flows) maxflows = min_t(u32, ctl->flows, SFQ_MAX_FLOWS); if (ctl->divisor) { From eb56a495f59baf6cad5ed80e3ffb9078098b1346 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:25 -0400 Subject: [PATCH 0326/1317] net/sched: hhf: clamp quantum in change and init paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hhf_change() accepts any quantum from userspace, including 1. With a crafted size table qdisc_pkt_len reaches ~2 GiB, so quantum=1 makes the deficit-refill loop spin ~2^31 times under the qdisc lock (a soft lockup / denial of service). Add max(256U, ...) in hhf_change() matching fq_codel_change(). Clamp hhf_init() to [256, 1<<20] matching the siblings, and remove the old fallback that only set quantum=256 on overflow. Conditions to recreate the bug: CONFIG_NET_SCH_HHF=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root hhf tc qdisc change dev dummy0 root hhf quantum 1 stab data 32768 size_log 15 cell_log 0 Fixes: 10239edf86f1 ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.5 Signed-off-by: Jakub Kicinski --- net/sched/sch_hhf.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index 96acab6a8da0..fc72f825fbd9 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -551,7 +551,7 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt, return err; if (tb[TCA_HHF_QUANTUM]) - new_quantum = nla_get_u32(tb[TCA_HHF_QUANTUM]); + new_quantum = max(256U, nla_get_u32(tb[TCA_HHF_QUANTUM])); if (tb[TCA_HHF_NON_HH_WEIGHT]) new_hhf_non_hh_weight = nla_get_u32(tb[TCA_HHF_NON_HH_WEIGHT]); @@ -613,7 +613,7 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, int i; sch->limit = 1000; - q->quantum = psched_mtu(qdisc_dev(sch)); + q->quantum = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 256, 1 << 20); get_random_bytes(&q->perturbation, sizeof(q->perturbation)); INIT_LIST_HEAD(&q->new_buckets); INIT_LIST_HEAD(&q->old_buckets); @@ -624,10 +624,6 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, q->hhf_evict_timeout = HZ; /* 1 sec */ q->hhf_non_hh_weight = 2; - if ((int)q->quantum <= 0 || - (u64)q->quantum * q->hhf_non_hh_weight > INT_MAX) - q->quantum = 256; - if (opt) { int err = hhf_change(sch, opt, extack); From 3c01f1ca5dfc6d6911b0e5b37f5062b1dc451b94 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:26 -0400 Subject: [PATCH 0327/1317] net/sched: dualpi2: clamp psched_mtu at all call sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dualpi2_calculate_c_protection(), must_drop(), and get_memory_limit() call psched_mtu() with no clamp. A huge MTU makes (s32)psched_mtu() overflow in the signed multiply for c_protection_init, and 2 * psched_mtu() wraps in get_memory_limit(). With a crafted size table qdisc_pkt_len reaches ~2 GiB, causing a soft lockup / denial of service. Clamp psched_mtu() to [1, 1<<20] at all three call sites. Conditions to recreate the bug: CONFIG_NET_SCH_DUALPI2=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root dualpi2 tc qdisc change dev dummy0 root dualpi2 stab data 32768 size_log 15 cell_log 0 Fixes: 320d031ad6e4 ("sched: Struct definition and parsing of dualpi2 qdisc") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.6 Signed-off-by: Jakub Kicinski --- net/sched/sch_dualpi2.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c index 4f678d4ff10e..4947def7c49e 100644 --- a/net/sched/sch_dualpi2.c +++ b/net/sched/sch_dualpi2.c @@ -208,9 +208,11 @@ static void dualpi2_reset_c_protection(struct dualpi2_sched_data *q) static void dualpi2_calculate_c_protection(struct Qdisc *sch, struct dualpi2_sched_data *q, u32 wc) { + u32 mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); + q->c_protection_wc = wc; q->c_protection_wl = MAX_WC - wc; - q->c_protection_init = (s32)psched_mtu(qdisc_dev(sch)) * + q->c_protection_init = (s32)mtu * ((int)q->c_protection_wc - (int)q->c_protection_wl); dualpi2_reset_c_protection(q); } @@ -285,8 +287,9 @@ static bool must_drop(struct Qdisc *sch, struct dualpi2_sched_data *q, u64 local_l_prob; bool overload; u32 prob; + u32 mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); - if (sch->qstats.backlog < 2 * psched_mtu(qdisc_dev(sch))) + if (sch->qstats.backlog < 2 * mtu) return false; prob = READ_ONCE(q->pi2_prob); @@ -712,7 +715,8 @@ static u32 get_memory_limit(struct Qdisc *sch, u32 limit) /* Apply rule of thumb, i.e., doubling the packet length, * to further include per packet overhead in memory_limit. */ - u64 memlim = mul_u32_u32(limit, 2 * psched_mtu(qdisc_dev(sch))); + u64 memlim = mul_u32_u32(limit, 2 * clamp_t(u32, psched_mtu(qdisc_dev(sch)), + 1, 1 << 20)); if (upper_32_bits(memlim)) return U32_MAX; From 54370e44c002770ae61fc889f28f699e91616ffc Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:27 -0400 Subject: [PATCH 0328/1317] net/sched: pie: clamp psched_mtu in pie_drop_early MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pie_drop_early() calls psched_mtu() with no clamp. With mtu=0x80000000 the bytemode divide silently zeroes the drop probability, disabling AQM. Clamp to [1, 1<<20]. Conditions to recreate the bug: CONFIG_NET_SCH_PIE=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root pie tc qdisc change dev dummy0 root pie stab data 32768 size_log 15 cell_log 0 Fixes: d4b36210c2e6 ("net: pkt_sched: PIE AQM scheme") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.7 Signed-off-by: Jakub Kicinski --- net/sched/sch_pie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/sch_pie.c b/net/sched/sch_pie.c index b41f2def2e2c..3b7863ffd284 100644 --- a/net/sched/sch_pie.c +++ b/net/sched/sch_pie.c @@ -35,7 +35,7 @@ bool pie_drop_early(struct Qdisc *sch, struct pie_params *params, { u64 rnd; u64 local_prob = vars->prob; - u32 mtu = psched_mtu(qdisc_dev(sch)); + u32 mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); /* If there is still burst allowance left skip random early drop */ if (vars->burst_time > 0) From 8382abec0f1568d0a5590d75a3df92f23fcf5196 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:28 -0400 Subject: [PATCH 0329/1317] net/sched: drr: clamp quantum in change class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drr_change_class() rejects explicit quantum==0 but falls back to psched_mtu() with no floor. With a crafted size table qdisc_pkt_len reaches ~2 GiB, so quantum=1 (or a zero psched_mtu on a headerless device) makes the deficit-refill loop spin under the qdisc lock. Add clamp_t(u32, quantum, 256, 1<<20) after the zero reject and on the fallback path. The explicit-zero reject is preserved. Conditions to recreate the bug: CONFIG_NET_SCH_DRR=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root drr tc class add dev dummy0 parent 1: classid 1:1 drr quantum 1 Fixes: 13d2a1d2b032 ("pkt_sched: add DRR scheduler") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.8 Signed-off-by: Jakub Kicinski --- net/sched/sch_drr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c index 91b1ef824afa..8621d057edd9 100644 --- a/net/sched/sch_drr.c +++ b/net/sched/sch_drr.c @@ -82,8 +82,9 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid, NL_SET_ERR_MSG(extack, "Specified DRR quantum cannot be zero"); return -EINVAL; } + quantum = clamp_t(u32, quantum, 256, 1 << 20); } else - quantum = psched_mtu(qdisc_dev(sch)); + quantum = clamp_t(u32, (u32)psched_mtu(qdisc_dev(sch)), 256, 1 << 20); if (cl != NULL) { if (tca[TCA_RATE]) { From 1c38487f46b243bfeefec0c0c86023a3904f2214 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:29 -0400 Subject: [PATCH 0330/1317] net/sched: ets: clamp quantum in parse and fallback paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ets_qdisc_change() falls back to psched_mtu() with no floor for bands without an explicit quantum. With a crafted size table qdisc_pkt_len reaches ~2 GiB, so a zero psched_mtu on a headerless device makes the deficit-refill loop spin under the qdisc lock. Move the floor into ets_quantum_parse() so explicitly configured quanta are also clamped to [256, 1<<20], not just the fallback path. Conditions to recreate the bug: CONFIG_NET_SCH_ETS=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root ets bands 3 strict 2 quanta 1 1 Fixes: dcc68b4d8084 ("net: sch_ets: Add a new Qdisc") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.9 Signed-off-by: Jakub Kicinski --- net/sched/sch_ets.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/net/sched/sch_ets.c b/net/sched/sch_ets.c index 25fcf4079fec..6cc902a03838 100644 --- a/net/sched/sch_ets.c +++ b/net/sched/sch_ets.c @@ -83,11 +83,7 @@ static int ets_quantum_parse(struct Qdisc *sch, const struct nlattr *attr, unsigned int *quantum, struct netlink_ext_ack *extack) { - *quantum = nla_get_u32(attr); - if (!*quantum) { - NL_SET_ERR_MSG(extack, "ETS quantum cannot be zero"); - return -EINVAL; - } + *quantum = clamp_t(u32, nla_get_u32(attr), 256, 1 << 20); return 0; } @@ -632,11 +628,13 @@ static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt, return err; } /* If there are more bands than strict + quanta provided, the remaining - * ones are ETS with quantum of MTU. Initialize the missing values here. + * ones are ETS with quantum of max(MTU, 256). Initialize the missing + * values here. */ for (i = nstrict; i < nbands; i++) { if (!quanta[i]) - quanta[i] = psched_mtu(qdisc_dev(sch)); + quanta[i] = clamp_t(u32, (u32)psched_mtu(qdisc_dev(sch)), + 256, 1 << 20); } /* Before commit, make sure we can allocate all new qdiscs */ From 8f0229bef3cba996bd40e40aafc512150016b696 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:30 -0400 Subject: [PATCH 0331/1317] selftests: tc-testing: update ETS test 41f5 for clamped quanta Commit "net/sched: ets: clamp quantum in parse and fallback paths" moved the quantum floor into ets_quantum_parse(), so every explicitly configured quantum is now clamped to [256, 1 << 20], not just the psched_mtu() fallback. Test 41f5 passes "quanta 4294967294 1 1" and matches the values back verbatim, so all three bands now differ from what it expects: before: bands 3 quanta 4294967294 1 1 after: bands 3 quanta 1048576 256 256 Update the match pattern accordingly. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.10 Signed-off-by: Jakub Kicinski --- tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json index ee09e6d6fdf3..d2eab61c099a 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json @@ -987,7 +987,7 @@ }, { "id": "41f5", - "name": "ETS offload where the sum of quanta wraps u32", + "name": "ETS offload with out-of-range quanta clamped", "category": [ "qdisc", "ets" @@ -1002,7 +1002,7 @@ "cmdUnderTest": "$TC qdisc add dev $ETH root ets quanta 4294967294 1 1", "expExitCode": "0", "verifyCmd": "$TC qdisc show dev $ETH", - "matchPattern": "qdisc ets .*bands 3 quanta 4294967294 1 1", + "matchPattern": "qdisc ets .*bands 3 quanta 1048576 256 256", "matchCount": "1", "teardown": [ "echo \"1\" > /sys/bus/netdevsim/del_device" From 38b6be101006d3e7af972999f45d4f1e8250587a Mon Sep 17 00:00:00 2001 From: Vineeth Karumanchi Date: Wed, 2 Sep 2026 15:58:36 +0530 Subject: [PATCH 0332/1317] net: macb: fix NULL pointer dereference on unbind with fixed-link When the device tree describes a fixed-link and has no "mdio" child node, macb_mii_init() returns early without allocating the MDIO bus, leaving bp->mii_bus as NULL. Two cleanup paths then dereference this NULL bus: 1. On driver unbind, macb_remove() unconditionally calls mdiobus_unregister(bp->mii_bus), which oopses: Unable to handle kernel NULL pointer dereference at virtual address 00000000000004a8 pc : mdiobus_unregister+0x14/0xa4 lr : macb_remove+0x38/0xa4 Call trace: mdiobus_unregister+0x14/0xa4 (P) macb_remove+0x38/0xa4 platform_remove+0x20/0x30 device_release_driver_internal+0x1c8/0x224 unbind_store+0xb4/0xbc 2. On the probe error path in macb_probe(), reached when macb_mii_init() has succeeded but a subsequent step fails, the err_out_unregister_mdio label runs the same unconditional cleanup. mdiobus_unregister() and mdiobus_free() do not guard against a NULL bus, so guard the calls in both macb_remove() and the probe error path. Fixes: d0c3601f2c4e ("net: macb: Avoid 20s boot delay by skipping MDIO bus registration for fixed-link PHY") Signed-off-by: Vineeth Karumanchi Reviewed-by: Xuanqiang Luo Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260902102836.2019355-1-vineeth.karumanchi@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b1939da4c95a..8469df0d89c3 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -5976,8 +5976,10 @@ static int macb_probe(struct platform_device *pdev) macb_free_tieoff(bp); err_out_unregister_mdio: - mdiobus_unregister(bp->mii_bus); - mdiobus_free(bp->mii_bus); + if (bp->mii_bus) { + mdiobus_unregister(bp->mii_bus); + mdiobus_free(bp->mii_bus); + } err_out_phy_exit: phy_exit(bp->phy); @@ -6006,8 +6008,10 @@ static void macb_remove(struct platform_device *pdev) unregister_netdev(netdev); macb_free_tieoff(bp); phy_exit(bp->phy); - mdiobus_unregister(bp->mii_bus); - mdiobus_free(bp->mii_bus); + if (bp->mii_bus) { + mdiobus_unregister(bp->mii_bus); + mdiobus_free(bp->mii_bus); + } device_set_wakeup_enable(&bp->pdev->dev, 0); cancel_delayed_work_sync(&bp->tx_lpi_work); From 3265ef0b670180b0b946d73ee9825d6d91e98a08 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:55 -1000 Subject: [PATCH 0333/1317] sched_ext: Rename sch to root_sch in dispatch_one() dispatch_one() uses the root scheduler for everything it does, including the two decisions to keep running @prev, which are wrong when @prev belongs to a sub-scheduler. The function has to deal with @prev's scheduler too. Rename the root's local from sch to root_sch for clarity and to make room for it. No functional change. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 7e414a7c53fc..120540cdda74 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2919,7 +2919,7 @@ static inline void maybe_queue_balance_callback(struct rq *rq) static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev) { - struct scx_sched *sch = scx_root_protected_live(); + struct scx_sched *root_sch = scx_root_protected_live(); enum scx_dsp_verdict verdict; s32 cpu = cpu_of(rq); @@ -2928,7 +2928,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev scx_process_sync_ecaps(rq, prev); - if ((sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT) && + if ((root_sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT) && unlikely(rq->scx.cpu_released)) { /* * If the previous sched_class for the current CPU was not SCX, @@ -2936,8 +2936,8 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * core. This callback complements ->cpu_release(), which is * emitted in switch_class(). */ - if (sch->ops.cpu_acquire) - SCX_CALL_OP(sch, cpu_acquire, rq, cpu, NULL); + if (root_sch->ops.cpu_acquire) + SCX_CALL_OP(root_sch, cpu_acquire, rq, cpu, NULL); rq->scx.cpu_released = false; } @@ -2955,7 +2955,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * test. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice && - !scx_bypassing(sch, cpu)) { + !scx_bypassing(root_sch, cpu)) { verdict = SCX_DSP_PREV; goto has_tasks; } @@ -2967,7 +2967,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev goto has_tasks; } - verdict = scx_dispatch_sched(sch, rq, prev, false); + verdict = scx_dispatch_sched(root_sch, rq, prev, false); if (verdict != SCX_DSP_NONE) goto has_tasks; @@ -2976,9 +2976,9 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * %SCX_OPS_ENQ_LAST is in effect. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && - (!(sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(sch, cpu)) && + (!(root_sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(root_sch, cpu)) && scx_task_can_stay_on_cpu(rq, prev)) { - __scx_add_event(sch, SCX_EV_DISPATCH_KEEP_LAST, 1); + __scx_add_event(root_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); verdict = SCX_DSP_PREV; goto has_tasks; } From 90f19b2816f5d243a8daf36ca83d9d9d04f00e4c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:56 -1000 Subject: [PATCH 0334/1317] sched_ext: Use @prev's scheduler for the keep decisions in dispatch_one() dispatch_one() tests ops flags and bypass state against the root scheduler in both places where it decides to keep running @prev: the early keep of a @prev with slice left tests the root's bypass state, and the keep-last at the end tests the root's SCX_OPS_ENQ_LAST and bypass state. Both are properties of the scheduler @prev belongs to, and put_prev_task_scx(), which acts on the outcome, reads them from that scheduler. When @prev belongs to a sub-scheduler the two sides disagree. The keep-last case is visible. The root set SCX_OPS_ENQ_LAST, so a lone @prev of a sub-scheduler is not kept and is enqueued with SCX_ENQ_LAST to a sub-scheduler that never opted in. This trips the WARN_ON_ONCE in put_prev_task_scx() for the missing flag, and the sub-scheduler queues the task like any other and triggers no follow-up scheduling event, which can lead to stalls. Test SCX_OPS_ENQ_LAST and bypass state on @prev's sched in both places and charge SCX_EV_DISPATCH_KEEP_LAST to it. Read the sched at each decision, as the dispatch in between can drop the rq lock. Fixes: 88234b075c3f ("sched_ext: Introduce scx_task_sched[_rcu]()") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 120540cdda74..adf5993fa597 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2955,7 +2955,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * test. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice && - !scx_bypassing(root_sch, cpu)) { + !scx_bypassing(scx_task_sched(prev), cpu)) { verdict = SCX_DSP_PREV; goto has_tasks; } @@ -2972,15 +2972,20 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev goto has_tasks; /* - * Didn't find another task to run. Keep running @prev unless - * %SCX_OPS_ENQ_LAST is in effect. + * Didn't find another task to run. Keep running @prev unless its own + * scheduler set %SCX_OPS_ENQ_LAST and takes the enqueue instead, see + * put_prev_task_scx(). Read the scheduler here as the dispatch above + * may have dropped the rq lock while @prev changed class or scheduler. */ - if ((prev->scx.flags & SCX_TASK_QUEUED) && - (!(root_sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(root_sch, cpu)) && - scx_task_can_stay_on_cpu(rq, prev)) { - __scx_add_event(root_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); - verdict = SCX_DSP_PREV; - goto has_tasks; + if (prev->scx.flags & SCX_TASK_QUEUED) { + struct scx_sched *prev_sch = scx_task_sched(prev); + + if ((!(prev_sch->ops.flags & SCX_OPS_ENQ_LAST) || + scx_bypassing(prev_sch, cpu)) && scx_task_can_stay_on_cpu(rq, prev)) { + __scx_add_event(prev_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); + verdict = SCX_DSP_PREV; + goto has_tasks; + } } rq->scx.flags &= ~SCX_RQ_IN_DISPATCH; return SCX_DSP_NONE; From a0d356696f87700c8c2934e3881277b0d37f0b71 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:57 -1000 Subject: [PATCH 0335/1317] sched_ext: scx_qmap: Do not add IMMED to rescue inserts qmap's stranded fallback forces a task that can run on none of its self cids onto its first allowed cid with SCX_ENQ_RESCUE, and adds SCX_ENQ_IMMED when that cid is a time-share it holds. On such a cid the insert stops being a rescue request: 1. A task is enqueued while none of its allowed cids is in self_cids. At attach self_cids is still empty. 2. qmap inserts it into cid 0's local DSQ with SCX_ENQ_RESCUE | SCX_ENQ_IMMED. 3. The kernel finds ENQ_IMMED held on cid 0, admits the insert and skips the rescue diversion. 4. cid 0's cpu is busy, so the IMMED task is bounced back to qmap with SCX_ENQ_REENQ. 5. qmap's enqueue sees the same inputs and repeats step 2. Nothing runs in between. 6. The reenqueue limit ejects qmap with SCX_EXIT_ERROR_REENQ. The caps granted during the parent's ops.sub_attach() are delivered after the sub already holds its tasks, while the per-cid effective caps that mark the time-shares are delivered from the first dispatch after bypass lifts, so every attach that receives a time-share on a task's first allowed cid starts the loop. Drop IMMED from the rescue inserts so that step 3 diverts to the rescue path. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 9f6e61d7ca07..e4e51303bd29 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -358,8 +358,8 @@ s32 BPF_STRUCT_OPS(qmap_select_cid, struct task_struct *p, } /* - * A received time-shared cid is held ENQ_IMMED-only, so inserts must set - * SCX_ENQ_IMMED. + * A received time-shared cid is held ENQ_IMMED-only, so inserts meant to run + * there must set SCX_ENQ_IMMED. */ static u64 needs_immed(s32 cid) { @@ -444,9 +444,11 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) * didn't grant them or we delegated them to children - would starve in * SHARED/FIFO since we only pull from those on self cids. * - * Force it onto its first allowed cid's local DSQ. If we hold that cid - * it runs. Otherwise the insert carries SCX_ENQ_RESCUE and the kernel - * diverts the task to its rescue path. + * Force it onto its first allowed cid's local DSQ with SCX_ENQ_RESCUE. + * If we hold ENQ on that cid it runs. Otherwise the kernel diverts the + * task to its rescue path. IMMED would turn the insert into a legal + * placement on a time-shared cid and the kernel would bounce it back + * here instead of rescuing it. */ if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) { s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); @@ -455,7 +457,7 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) taskc->force_local = false; __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | c, slice_ns, - enq_flags | needs_immed(c) | SCX_ENQ_RESCUE); + enq_flags | SCX_ENQ_RESCUE); return; } } @@ -618,7 +620,7 @@ static bool scan_shared_dsq(bool from_timer) if (c >= 0 && c < scx_bpf_nr_cids()) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, - needs_immed(c) | SCX_ENQ_RESCUE); + SCX_ENQ_RESCUE); } continue; } @@ -659,7 +661,7 @@ static bool scan_shared_dsq(bool from_timer) if (c >= 0 && c < nr_cids) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, - needs_immed(c) | SCX_ENQ_RESCUE); + SCX_ENQ_RESCUE); } continue; } From 63b4ff622244483e7c530e97d787a3d6c2c38a33 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:58 -1000 Subject: [PATCH 0336/1317] sched_ext: scx_qmap: Place only on cids whose caps are in effect qmap decides placements from self_cids, which redistribute() derives from the caps view at ops.sub_caps_updated() time. That view runs ahead of the cpus: a granted cid can be in self_cids before its cpu has reported the caps in effect through ops.sub_ecaps_updated(). ops.update_idle() only comes once BASE is in effect, so the idle-gated placements reach such a cid only through an idle bit left over from an earlier hold. The highpri scan has no gate at all: parent cpu Y, qmap cpu X grants ENQ on X to qmap sub_caps_updated() adds X to self_cids highpri scan moves a task to X with PREEMPT caps not in effect, move denied, task bounced with REENQ_CAP reject drain, enqueue the scan moves it to X again denied again dispatch syncs ecaps, sub_ecaps_updated(X) Every highpri move to X in that window is denied and bounced. The two callbacks are meant to split the roles: ops.sub_caps_updated() tracks what the node holds and drives what it delegates to its children, while ops.sub_ecaps_updated() says whether a task can run on a cpu now. qmap used the first for both. Track the caps in effect from ops.sub_ecaps_updated() as avail_cids and place only on self_cids & avail_cids, so that self_cids stays the delegation split and avail_cids gates the placement. The stranded tests keep self_cids, as they ask whether the split gives the task anywhere at all. A highpri task whose self_cids lack caps in effect waits for them instead of being moved and bounced. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 83 ++++++++++++++++++++++++++-------- tools/sched_ext/scx_qmap.h | 3 ++ 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index e4e51303bd29..062bb22ee65c 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -24,6 +24,9 @@ * time-share that stays self-local. * self - The excl cpus the node kept for itself, plus all of held_shared. * owner - Who holds a cid - a child slot, CID_SELF, or CID_NONE. + * avail - Cpus whose caps are in effect, per ops.sub_ecaps_updated(). + * usable - self AND avail. Placement decisions use this: self is the + * delegation split and can run ahead of what the cpus honor. * * The scheduler splits its held-excl cpus among self and the children in * proportion to each node's cpu.weight, handing each the floor of its share as @@ -208,8 +211,8 @@ static int qmap_spin_lock(struct bpf_res_spin_lock *lock) } /* - * Try prev_cid, then scan cpus_allowed AND idle_cids AND self_cids round-robin - * from prev_cid + 1. Atomic claim retries on race; bounded by + * Try prev_cid, then scan cpus_allowed AND idle_cids AND usable_cids + * round-robin from prev_cid + 1. Atomic claim retries on race; bounded by * IDLE_PICK_RETRIES to keep the verifier's insn budget in check. */ #define IDLE_PICK_RETRIES 16 @@ -221,7 +224,7 @@ static s32 pick_direct_dispatch_cid(struct task_struct *p, s32 prev_cid, s32 cid; u32 i; - if (cmask_test(prev_cid, &qa.self_cids.mask) && + if (cmask_test(prev_cid, &qa.usable_cids.mask) && cmask_test_and_clear(prev_cid, &qa.idle_cids.mask)) return prev_cid; @@ -229,7 +232,7 @@ static s32 pick_direct_dispatch_cid(struct task_struct *p, s32 prev_cid, bpf_for(i, 0, IDLE_PICK_RETRIES) { cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed, &qa.idle_cids.mask, - &qa.self_cids.mask, cid + 1); + &qa.usable_cids.mask, cid + 1); barrier_var(cid); if (cid >= nr_cids) return -1; @@ -542,7 +545,7 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) scx_bpf_dsq_insert(p, SHARED_DSQ, 0, enq_flags); cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed, &qa.idle_cids.mask, - &qa.self_cids.mask, 0); + &qa.usable_cids.mask, 0); if (cid < scx_bpf_nr_cids()) scx_bpf_kick_cid(cid, SCX_KICK_IDLE); return; @@ -646,18 +649,23 @@ static bool scan_shared_dsq(bool from_timer) if (!(taskc = lookup_task_ctx(p))) return false; - /* only run highpri tasks on cids this node holds, not delegated ones */ + /* only run highpri tasks on cids this node can use right now */ if (cmask_test(this_cid, &taskc->cpus_allowed) && - cmask_test(this_cid, &qa.self_cids.mask)) + cmask_test(this_cid, &qa.usable_cids.mask)) cid = this_cid; else cid = cmask_next_and_set_wrap(&taskc->cpus_allowed, - &qa.self_cids.mask, + &qa.usable_cids.mask, this_cid + 1); if (cid >= nr_cids) { - /* stranded after the cull - rescue it from here */ - s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); + s32 c; + /* self cids lack caps in effect yet, leave it queued */ + if (cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) + continue; + + /* stranded after the cull - rescue it from here */ + c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); if (c >= 0 && c < nr_cids) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, @@ -1115,7 +1123,7 @@ void BPF_STRUCT_OPS(qmap_update_idle, s32 cid, bool idle) /* * The kernel delivers update_idle() for every cid this node holds * SCX_CAP_BASE on. Track every cid's idle state regardless of - * delegation: the direct-dispatch pick masks idle_cids with self_cids + * delegation: the direct-dispatch pick masks idle_cids with usable_cids * at selection, so a cid already idle when it returns to self needs no * reseed here. */ @@ -1539,6 +1547,19 @@ static __noinline void account_alloc(void) } } +/* + * usable_cids = self_cids & avail_cids. The inputs have separate writers, + * apply_partition() and qmap_sub_ecaps_updated(), so the result is rebuilt in + * full under the partition guard, in scratch first so that readers never see + * self_cids alone. + */ +static void refresh_usable(void) +{ + cmask_copy(&qa.usable_scratch.mask, &qa.self_cids.mask); + cmask_and(&qa.usable_scratch.mask, &qa.avail_cids.mask); + cmask_copy(&qa.usable_cids.mask, &qa.usable_scratch.mask); +} + /* * apply_partition - execute the plan compute_partition() built * @@ -1561,6 +1582,7 @@ __noinline void apply_partition(void) /* no excl cpu: run own tasks on the held shares, evict children */ if (!qa.part.nr_excl) { cmask_copy(&qa.self_cids.mask, &qa.held_shared.mask); + refresh_usable(); bpf_for(i, 0, MAX_SUB_SCHEDS) if (qa.sub_sched_ctxs[i].cgroup_id) scx_bpf_sub_kill(qa.sub_sched_ctxs[i].cgroup_id, @@ -1598,6 +1620,7 @@ __noinline void apply_partition(void) else if (o == CID_SELF) cmask_set(cid, &qa.self_cids.mask); } + refresh_usable(); /* * Apply each child's exclusive cids as a delta against its previous @@ -1839,8 +1862,11 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init) cmask_init(&qa.rr_cids.mask, 0, nr_cids); cmask_init(&qa.prev_rr_cids.mask, 0, nr_cids); cmask_init(&qa.self_cids.mask, 0, nr_cids); + cmask_init(&qa.avail_cids.mask, 0, nr_cids); + cmask_init(&qa.usable_cids.mask, 0, nr_cids); cmask_init(&qa.to_revoke_cids.mask, 0, nr_cids); cmask_init(&qa.to_grant_cids.mask, 0, nr_cids); + cmask_init(&qa.usable_scratch.mask, 0, nr_cids); cmask_init(&qa.held_excl.mask, 0, nr_cids); cmask_init(&qa.held_shared.mask, 0, nr_cids); @@ -1854,14 +1880,16 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init) } /* - * The root starts holding every cid. qmap_sub_ecaps_updated() maintains - * per-cid shared state as effective caps settle, and redistribute() - * rebuilds owner and self from held caps. A non-root node starts with - * nothing. + * The root starts holding every cid and gets no ecaps notifications, so + * its avail set is fixed here. qmap_sub_ecaps_updated() maintains the + * per-cid state as effective caps settle, and redistribute() rebuilds + * owner and self from held caps. A non-root node starts with nothing. */ bpf_for(i, 0, nr_cids) { if (!sub_cgroup_id) { cmask_set(i, &qa.self_cids.mask); + cmask_set(i, &qa.avail_cids.mask); + cmask_set(i, &qa.usable_cids.mask); qa.part.cid_owner[i] = CID_SELF; } else { qa.part.cid_owner[i] = CID_NONE; @@ -2002,12 +2030,31 @@ void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after) { /* * Effective caps updated. Track which cids hold shared caps so a self - * task placed there enqueues IMMED. + * task placed there enqueues IMMED, and which cids have ENQ_IMMED in + * effect at all (avail, see the header comment). */ - if (after & SCX_CAP_ENQ_IMMED) + if (after & SCX_CAP_ENQ_IMMED) { qa.cid_shared[cid] = (after & SCX_CAP_ENQ) ? 0 : 1; - else + cmask_set(cid, &qa.avail_cids.mask); + } else { qa.cid_shared[cid] = 0; + cmask_clear(cid, &qa.avail_cids.mask); + } + + /* + * When another runner holds the partition guard, set part_pending: + * redistribute() drains it before releasing and rr_advance() checks it + * after, so the deferred refresh lands by the next rr tick. A + * repartition that lost the guard to us runs here. + */ + if (part_try_start()) { + refresh_usable(); + part_end(); + if (__sync_fetch_and_or(&part_pending, 0)) + redistribute(); + } else { + __sync_fetch_and_or(&part_pending, 1); + } } SCX_OPS_CID_DEFINE(qmap_ops, diff --git a/tools/sched_ext/scx_qmap.h b/tools/sched_ext/scx_qmap.h index c78d61806b39..e95fffcf7b23 100644 --- a/tools/sched_ext/scx_qmap.h +++ b/tools/sched_ext/scx_qmap.h @@ -165,12 +165,15 @@ struct qmap_arena { /* bpf-internal cmasks (embedded, see struct qmap_cmask) */ struct qmap_cmask self_cids; /* cids this node runs its own tasks on */ + struct qmap_cmask avail_cids; /* cids with caps in effect on the cpu */ + struct qmap_cmask usable_cids; /* self_cids & avail_cids, placeable right now */ struct qmap_cmask idle_cids; /* idle state of all cids regardless of delegation */ struct qmap_cmask rr_cids; /* the shared pool, as a mask for grant/revoke */ /* scratch cmasks */ struct qmap_cmask to_revoke_cids; /* delta cids to revoke */ struct qmap_cmask to_grant_cids; /* delta cids to grant */ + struct qmap_cmask usable_scratch; /* refresh_usable() build area */ struct qmap_cmask prev_rr_cids; /* previous shared pool, to clear stale grants */ struct qmap_cmask held_excl; /* cids held excl (ENQ): delegatable */ struct qmap_cmask held_shared; /* cids held shared (ENQ_IMMED only): self-local */ From c6709d5e14072d0e3d02f291daee46a199e5dad3 Mon Sep 17 00:00:00 2001 From: Younes Akhouayri Date: Sat, 5 Sep 2026 17:16:51 +0200 Subject: [PATCH 0337/1317] rust: num: seal Integer Bounded relies on Integer implementations to describe primitive integer semantics correctly. In particular, it uses Integer::BITS and Signedness to justify unchecked operations. Integer is currently safe and externally implementable, so an implementation can violate those assumptions and make safe Bounded operations reach undefined behavior. For example, an Integer implementation for a u8 wrapper can report BITS = 16. Safe code can then cast a Bounded containing 256 to that wrapper. Its TryFrom implementation returns Err, and Bounded::cast() calls unwrap_unchecked() on it, causing undefined behavior. Seal Integer so only the primitive implementations provided by the kernel crate can satisfy it. Fixes: 01e345e82ec3 ("rust: num: add Bounded integer wrapping type") Reported-by: Miguel Ojeda Closes: https://lore.kernel.org/rust-for-linux/CANiq72mOfR33s4y+Ueivd5NrC5yre+Pcp57ZOBz0msw9A4AP1Q@mail.gmail.com/ Cc: stable@vger.kernel.org Suggested-by: Miguel Ojeda Signed-off-by: Younes Akhouayri Acked-by: Alexandre Courbot Link: https://patch.msgid.link/20260905-feature-rust-num-seal-integer-v2-1-f1311ffbe6e7@younes.io Signed-off-by: Miguel Ojeda --- rust/kernel/num.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rust/kernel/num.rs b/rust/kernel/num.rs index dbe848e30efe..de589792a77a 100644 --- a/rust/kernel/num.rs +++ b/rust/kernel/num.rs @@ -15,9 +15,14 @@ pub enum Unsigned {} /// Designates signed primitive types. pub enum Signed {} +mod private { + pub trait Sealed {} +} + /// Describes core properties of integer types. pub trait Integer: - Sized + private::Sealed + + Sized + Copy + Clone + PartialEq @@ -56,6 +61,8 @@ pub trait Integer: macro_rules! impl_integer { ($($type:ty: $signedness:ty), *) => { $( + impl private::Sealed for $type {} + impl Integer for $type { type Signedness = $signedness; From 662ade4de9ff5eceb0820a9f8e9fac70ba6a815b Mon Sep 17 00:00:00 2001 From: Quanye Yang Date: Mon, 31 Aug 2026 20:30:58 +0800 Subject: [PATCH 0338/1317] RDMA/ucma: Serialize join and leave on copy_to_user failure rdma_join_multicast() queues RoCE work that later reads the ucma_multicast through event->param.ud.private_data, then list_add()s the CMA multicast at the head of id_priv->mc_list. rdma_leave_multicast() matches only by sockaddr and destroys the first hit. ucma_process_join() used to drop ctx->mutex after a successful join and retake it only if copy_to_user() failed. Two concurrent JOIN_MCAST calls with the same address can therefore insert a second CMA entry before the first thread's leave. leave then cancels the newer work and the older worker still dereferences the ucma_multicast that the first thread frees. Keep ctx->mutex held from rdma_join_multicast() through copy_to_user() and, on -EFAULT, through rdma_leave_multicast() so leave cannot miss this join. Do not leave if join itself failed: that path never published this address on mc_list, and a leave-by-addr would destroy an earlier successful join. Reported-by: syzbot+a6ffe86390c8a6afc818@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a6ffe86390c8a6afc818 Fixes: fe454dc31e84 ("RDMA/ucma: Fix use-after-free bug in ucma_create_uevent") Cc: stable@vger.kernel.org Signed-off-by: Quanye Yang Link: https://patch.msgid.link/20260831-rdma-ucma-mc-uaf-v1-1-b8eeb7046aff@proton.me Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/ucma.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/infiniband/core/ucma.c b/drivers/infiniband/core/ucma.c index 4929636f7c53..a15182f7a7c5 100644 --- a/drivers/infiniband/core/ucma.c +++ b/drivers/infiniband/core/ucma.c @@ -1556,9 +1556,10 @@ static ssize_t ucma_process_join(struct ucma_file *file, mutex_lock(&ctx->mutex); ret = rdma_join_multicast(ctx->cm_id, (struct sockaddr *)&mc->addr, join_state, mc); - mutex_unlock(&ctx->mutex); - if (ret) + if (ret) { + mutex_unlock(&ctx->mutex); goto err_xa_erase; + } resp.id = mc->id; if (copy_to_user(u64_to_user_ptr(cmd->response), @@ -1566,6 +1567,7 @@ static ssize_t ucma_process_join(struct ucma_file *file, ret = -EFAULT; goto err_leave_multicast; } + mutex_unlock(&ctx->mutex); xa_store(&multicast_table, mc->id, mc, 0); @@ -1573,7 +1575,6 @@ static ssize_t ucma_process_join(struct ucma_file *file, return 0; err_leave_multicast: - mutex_lock(&ctx->mutex); rdma_leave_multicast(ctx->cm_id, (struct sockaddr *) &mc->addr); mutex_unlock(&ctx->mutex); ucma_cleanup_mc_events(mc); From 9a141d3dc869d18b2eab35e999f4790a9b84e40f Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 17:06:32 +0300 Subject: [PATCH 0339/1317] IB/IPoIB: Avoid restoring OPER_UP after multicast flush ipoib_ib_dev_flush_light() temporarily clears IPOIB_FLAG_OPER_UP to prevent multicast joins while ipoib_mcast_dev_flush() is running, and restores the flag afterwards if it was previously set. This restore races with ipoib_ib_dev_down(). If the interface is brought down while the flush is in progress, ipoib_ib_dev_down() clears IPOIB_FLAG_OPER_UP, but the flush path may set it again after the device has already gone down. Since commit 894021a75291 ("IB/ipoib: Make the carrier_on_task race aware"), ipoib_mcast_carrier_on_task() relies on IPOIB_FLAG_OPER_UP being cleared to terminate its rtnl_trylock() retry loop. If the flag is left set after shutdown, the workqueue retries forever, causing teardown to deadlock when ipoib_ndo_uninit() waits in destroy_workqueue() while holding RTNL. Instead of overloading IPOIB_FLAG_OPER_UP to block multicast joins during a light flush, introduce a dedicated IPOIB_FLAG_MCAST_FLUSH flag. Use it together with IPOIB_FLAG_OPER_UP to determine whether multicast joins are allowed, avoiding the race with device shutdown. Fixes: 344bacca8cd8 ("IB/ipoib: Don't allow MC joins during light MC flush") Reported-by: Ben Davies Signed-off-by: Carolina Jubran Reviewed-by: Cosmin Ratiu Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260902-avoid-rest-oper-up-v1-1-04fcd4916cae@nvidia.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/ipoib/ipoib.h | 7 +++++++ drivers/infiniband/ulp/ipoib/ipoib_ib.c | 12 +++++++----- drivers/infiniband/ulp/ipoib/ipoib_multicast.c | 16 ++++++++-------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/drivers/infiniband/ulp/ipoib/ipoib.h b/drivers/infiniband/ulp/ipoib/ipoib.h index 91f866e3fb8b..143e03b64902 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib.h +++ b/drivers/infiniband/ulp/ipoib/ipoib.h @@ -87,6 +87,7 @@ enum { IPOIB_FLAG_INITIALIZED = 1, IPOIB_FLAG_ADMIN_UP = 2, IPOIB_PKEY_ASSIGNED = 3, + IPOIB_FLAG_MCAST_FLUSH = 4, IPOIB_FLAG_SUBINTERFACE = 5, IPOIB_STOP_REAPER = 7, IPOIB_FLAG_ADMIN_CM = 9, @@ -414,6 +415,12 @@ struct ipoib_dev_priv { const struct net_device_ops *rn_ops; }; +static inline bool ipoib_mcast_allowed(struct ipoib_dev_priv *priv) +{ + return test_bit(IPOIB_FLAG_OPER_UP, &priv->flags) && + !test_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); +} + struct ipoib_ah { struct net_device *dev; struct ib_ah *ah; diff --git a/drivers/infiniband/ulp/ipoib/ipoib_ib.c b/drivers/infiniband/ulp/ipoib/ipoib_ib.c index 5061d52a7b12..81bbb3f7c113 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_ib.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_ib.c @@ -1227,17 +1227,19 @@ static void __ipoib_ib_dev_flush(struct ipoib_dev_priv *priv, } if (level == IPOIB_FLUSH_LIGHT) { - int oper_up; ipoib_mark_paths_invalid(dev); - /* Set IPoIB operation as down to prevent races between: + /* Set MCAST_FLUSH to prevent races between: * the flush flow which leaves MCG and on the fly joins * which can happen during that time. mcast restart task * should deal with join requests we missed. + * + * Do not clear OPER_UP for this; restoring it races with + * ipoib_ib_dev_down() and can leave OPER_UP set after the + * device is down. */ - oper_up = test_and_clear_bit(IPOIB_FLAG_OPER_UP, &priv->flags); + set_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); ipoib_mcast_dev_flush(dev); - if (oper_up) - set_bit(IPOIB_FLAG_OPER_UP, &priv->flags); + clear_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); ipoib_reap_dead_ahs(priv); } diff --git a/drivers/infiniband/ulp/ipoib/ipoib_multicast.c b/drivers/infiniband/ulp/ipoib/ipoib_multicast.c index 6401af2fd548..379b78374e21 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_multicast.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_multicast.c @@ -74,7 +74,7 @@ static void __ipoib_mcast_schedule_join_thread(struct ipoib_dev_priv *priv, struct ipoib_mcast *mcast, bool delay) { - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) return; /* @@ -469,7 +469,7 @@ static int ipoib_mcast_join(struct net_device *dev, struct ipoib_mcast *mcast) int ret = 0; if (!priv->broadcast || - !test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + !ipoib_mcast_allowed(priv)) return -EINVAL; init_completion(&mcast->done); @@ -555,7 +555,7 @@ void ipoib_mcast_join_task(struct work_struct *work) unsigned long delay_until = 0; struct ipoib_mcast *mcast = NULL; - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) return; if (ib_query_port(priv->ca, priv->port, &port_attr)) { @@ -577,7 +577,7 @@ void ipoib_mcast_join_task(struct work_struct *work) netif_addr_unlock_bh(dev); spin_lock_irq(&priv->lock); - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) goto out; if (!priv->broadcast) { @@ -749,7 +749,7 @@ void ipoib_mcast_send(struct net_device *dev, u8 *daddr, struct sk_buff *skb) spin_lock_irqsave(&priv->lock, flags); - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags) || + if (!ipoib_mcast_allowed(priv) || !priv->broadcast || !test_bit(IPOIB_MCAST_FLAG_ATTACHED, &priv->broadcast->flags)) { ++dev->stats.tx_dropped; @@ -871,7 +871,7 @@ void ipoib_mcast_restart_task(struct work_struct *work) LIST_HEAD(remove_list); struct ib_sa_mcmember_rec rec; - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) /* * shortcut...on shutdown flush is called next, just * let it do all the work @@ -965,9 +965,9 @@ void ipoib_mcast_restart_task(struct work_struct *work) ipoib_mcast_remove_list(&remove_list); /* - * Double check that we are still up + * Double check that we are still up and not flushing */ - if (test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) { + if (ipoib_mcast_allowed(priv)) { spin_lock_irq(&priv->lock); __ipoib_mcast_schedule_join_thread(priv, NULL, 0); spin_unlock_irq(&priv->lock); From 402a9d6aab7ac787ab075adeb562c3db8b8f564b Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Thu, 3 Sep 2026 21:05:21 +0800 Subject: [PATCH 0340/1317] ALSA: caiaq: Decoupling ep1_in_urb in caiaq dev The epq_in_urb object belonging to the caiaq device is coupled within the struct snd_usb_caiaqdev. After usb_submit_urb(epq_in_urb, GFP_KERNEL) executes successfully, epq_in_urb is successfully added to the urbp_list queue of the dummy HCD driver (userspace specifies dummy_hcd as the HCD layer driver for the caiaq USB device). When init_card() calls snd_usb_caiaq_send_command() which subsequently fails due to a timeout, and proceeds to call snd_card_free() to release the card, the embedded ep1_in_urb object is also freed. When the dummy HCD driver detects that the URB has been unlinked, it returns the URB (by usb_hcd_giveback_urb()), which triggers [1]. Decouple the ep1_in_urb object from the struct snd_usb_caiaqdev and switch to using a pointer instead. Separately allocate and manage the memory for ep1_in_urb to prevent the release of the snd_card memory object from interfering with it. midi_out_urb has the same issue as ep1_in_urb and is handled in the same way. [1] BUG: KASAN: slab-use-after-free in usb_free_urb+0x24/0x120 drivers/usb/core/urb.c:96 Write of size 4 at addr ffff88803cee1050 by task ktimers/1/29 Call Trace: usb_free_urb+0x24/0x120 drivers/usb/core/urb.c:96 dummy_timer+0xaac/0x4d50 drivers/usb/gadget/udc/dummy_hcd.c:2019 __run_hrtimer kernel/time/hrtimer.c:2067 [inline] __hrtimer_run_queues+0x3eb/0xaf0 kernel/time/hrtimer.c:2124 hrtimer_run_softirq+0x1e1/0x2e0 kernel/time/hrtimer.c:2141 Allocated by task 36: snd_card_new+0x7b/0x110 sound/core/init.c:184 create_card sound/usb/caiaq/device.c:429 [inline] snd_probe+0x236/0x1af0 sound/usb/caiaq/device.c:544 Freed by task 36: snd_card_free_when_closed sound/core/init.c:630 [inline] snd_card_free+0x138/0x1d0 sound/core/init.c:662 snd_probe+0x162b/0x1af0 sound/usb/caiaq/device.c:553 Fixes: 523f1dce3743 ("[ALSA] Add Native Instrument usb audio device support") Reported-by: syzbot+832ce9fa3face1b7d44d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=832ce9fa3face1b7d44d Tested-by: syzbot+832ce9fa3face1b7d44d@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Link: https://patch.msgid.link/20260903130521.554840-1-eadavis@sina.com Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 36 ++++++++++++++++++++++++------------ sound/usb/caiaq/device.h | 4 ++-- sound/usb/caiaq/midi.c | 6 +++--- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index a16e59248480..3d821fde4582 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -192,8 +192,8 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) break; } - cdev->ep1_in_urb.actual_length = 0; - ret = usb_submit_urb(&cdev->ep1_in_urb, GFP_ATOMIC); + cdev->ep1_in_urb->actual_length = 0; + ret = usb_submit_urb(cdev->ep1_in_urb, GFP_ATOMIC); if (ret < 0) dev_err(dev, "unable to submit urb. OOM!?\n"); } @@ -408,6 +408,10 @@ static void card_free(struct snd_card *card) #endif snd_usb_caiaq_audio_free(cdev); usb_put_dev(cdev->chip.dev); + usb_free_urb(cdev->ep1_in_urb); + cdev->ep1_in_urb = NULL; + usb_free_urb(cdev->midi_out_urb); + cdev->midi_out_urb = NULL; } static int create_card(struct usb_device *usb_dev, @@ -457,22 +461,30 @@ static int init_card(struct snd_usb_caiaqdev *cdev) return -EIO; } - usb_init_urb(&cdev->ep1_in_urb); - usb_init_urb(&cdev->midi_out_urb); + cdev->ep1_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep1_in_urb) + return -ENOMEM; - usb_fill_bulk_urb(&cdev->ep1_in_urb, usb_dev, + cdev->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->midi_out_urb) { + usb_free_urb(cdev->ep1_in_urb); + cdev->ep1_in_urb = NULL; + return -ENOMEM; + } + + usb_fill_bulk_urb(cdev->ep1_in_urb, usb_dev, usb_rcvbulkpipe(usb_dev, 0x1), cdev->ep1_in_buf, EP1_BUFSIZE, usb_ep1_command_reply_dispatch, cdev); - usb_fill_bulk_urb(&cdev->midi_out_urb, usb_dev, + usb_fill_bulk_urb(cdev->midi_out_urb, usb_dev, usb_sndbulkpipe(usb_dev, 0x1), cdev->midi_out_buf, EP1_BUFSIZE, snd_usb_caiaq_midi_output_done, cdev); /* sanity checks of EPs before actually submitting */ - if (usb_urb_ep_type_check(&cdev->ep1_in_urb) || - usb_urb_ep_type_check(&cdev->midi_out_urb)) { + if (usb_urb_ep_type_check(cdev->ep1_in_urb) || + usb_urb_ep_type_check(cdev->midi_out_urb)) { dev_err(dev, "invalid EPs\n"); return -EINVAL; } @@ -480,7 +492,7 @@ static int init_card(struct snd_usb_caiaqdev *cdev) init_waitqueue_head(&cdev->ep1_wait_queue); init_waitqueue_head(&cdev->prepare_wait_queue); - if (usb_submit_urb(&cdev->ep1_in_urb, GFP_KERNEL) != 0) + if (usb_submit_urb(cdev->ep1_in_urb, GFP_KERNEL) != 0) return -EIO; err = snd_usb_caiaq_send_command(cdev, EP1_CMD_GET_DEVICE_INFO, NULL, 0); @@ -530,7 +542,7 @@ static int init_card(struct snd_usb_caiaqdev *cdev) return 0; err_kill_urb: - usb_kill_urb(&cdev->ep1_in_urb); + usb_kill_urb(cdev->ep1_in_urb); return err; } @@ -576,8 +588,8 @@ static void snd_disconnect(struct usb_interface *intf) #endif snd_usb_caiaq_audio_disconnect(cdev); - usb_kill_urb(&cdev->ep1_in_urb); - usb_kill_urb(&cdev->midi_out_urb); + usb_kill_urb(cdev->ep1_in_urb); + usb_kill_urb(cdev->midi_out_urb); snd_card_free_when_closed(card); } diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 743eb0387b5f..1c6f34693fa8 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -60,8 +60,8 @@ struct snd_usb_caiaq_cb_info; struct snd_usb_caiaqdev { struct snd_usb_audio chip; - struct urb ep1_in_urb; - struct urb midi_out_urb; + struct urb *ep1_in_urb; + struct urb *midi_out_urb; struct urb **data_urbs_in; struct urb **data_urbs_out; struct snd_usb_caiaq_cb_info *data_cb_info; diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c index c656d0162432..18529484c8dc 100644 --- a/sound/usb/caiaq/midi.c +++ b/sound/usb/caiaq/midi.c @@ -43,7 +43,7 @@ static int snd_usb_caiaq_midi_output_close(struct snd_rawmidi_substream *substre { struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; if (cdev->midi_out_active) { - usb_kill_urb(&cdev->midi_out_urb); + usb_kill_urb(cdev->midi_out_urb); cdev->midi_out_active = 0; } return 0; @@ -64,9 +64,9 @@ static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *cdev, return; cdev->midi_out_buf[2] = len; - cdev->midi_out_urb.transfer_buffer_length = len+3; + cdev->midi_out_urb->transfer_buffer_length = len+3; - ret = usb_submit_urb(&cdev->midi_out_urb, GFP_ATOMIC); + ret = usb_submit_urb(cdev->midi_out_urb, GFP_ATOMIC); if (ret < 0) dev_err(dev, "snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed," From 07b01b0d8ac4b5f89cbe74e52376221f21db260d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2026 18:04:37 +0200 Subject: [PATCH 0341/1317] ALSA: usb: ua101: Avoid embedded URBs UA101 driver uses URBs embedded in struct ua101, and this is basically a buggy implementation nowadays; since a URB is managed with a refcount, this may lead to a UAF when the URB is released asynchronously. For addressing the problem, this patch converts the embedded URBs to ones that are properly allocated via usb_alloc_urb(). The iso_frame_desc[] is gone, as it's allocated together by usb_alloc_urb(). Along with the dynamic allocation of each URB, the ua101.urbs[] becomes a static array of struct ua101_urb, and struct ua101_urb contains the pointer to struct ua101. Those are needed to handle the ready_list linked list in the complete callback. No functional changes, only compile-tested. Link: https://lore.kernel.org/20260903130757.0668310a.michal.pecio@gmail.com Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260903160458.1938392-2-tiwai@suse.de --- sound/usb/misc/ua101.c | 102 ++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c index b9a62e94e06c..860a62a3d74b 100644 --- a/sound/usb/misc/ua101.c +++ b/sound/usb/misc/ua101.c @@ -109,10 +109,10 @@ struct ua101 { unsigned int buffer_pos; unsigned int queue_length; struct ua101_urb { - struct urb urb; - struct usb_iso_packet_descriptor iso_frame_desc[1]; + struct urb *urb; struct list_head ready_list; - } *urbs[MAX_QUEUE_LENGTH]; + struct ua101 *ua; + } urbs[MAX_QUEUE_LENGTH]; struct { unsigned int size; void *addr; @@ -167,15 +167,15 @@ static void abort_usb_playback(struct ua101 *ua) wake_up(&ua->alsa_playback_wait); } -static void playback_urb_complete(struct urb *usb_urb) +static void playback_urb_complete(struct urb *urb) { - struct ua101_urb *urb = (struct ua101_urb *)usb_urb; - struct ua101 *ua = urb->urb.context; + struct ua101_urb *ua_urb = urb->context; + struct ua101 *ua = ua_urb->ua; - if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ - urb->urb.status == -ENODEV || /* device removed */ - urb->urb.status == -ECONNRESET || /* unlinked */ - urb->urb.status == -ESHUTDOWN)) { /* device disabled */ + if (unlikely(urb->status == -ENOENT || /* unlinked */ + urb->status == -ENODEV || /* device removed */ + urb->status == -ECONNRESET || /* unlinked */ + urb->status == -ESHUTDOWN)) { /* device disabled */ abort_usb_playback(ua); abort_alsa_playback(ua); return; @@ -184,18 +184,19 @@ static void playback_urb_complete(struct urb *usb_urb) if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { /* append URB to FIFO */ guard(spinlock_irqsave)(&ua->lock); - list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); + list_add_tail(&ua_urb->ready_list, &ua->ready_playback_urbs); if (ua->rate_feedback_count > 0) queue_work(system_highpri_wq, &ua->playback_work); ua->playback.substream->runtime->delay -= - urb->urb.iso_frame_desc[0].length / + urb->iso_frame_desc[0].length / ua->playback.frame_bytes; } } static void first_playback_urb_complete(struct urb *urb) { - struct ua101 *ua = urb->context; + struct ua101_urb *ua_urb = urb->context; + struct ua101 *ua = ua_urb->ua; urb->complete = playback_urb_complete; playback_urb_complete(urb); @@ -248,7 +249,8 @@ static void playback_work(struct work_struct *work) { struct ua101 *ua = container_of(work, struct ua101, playback_work); unsigned int frames; - struct ua101_urb *urb; + struct ua101_urb *ua_urb; + struct urb *urb; bool do_period_elapsed = false; int err; @@ -275,23 +277,24 @@ static void playback_work(struct work_struct *work) ua->rate_feedback_count--; /* take URB out of FIFO */ - urb = list_first_entry(&ua->ready_playback_urbs, - struct ua101_urb, ready_list); - list_del(&urb->ready_list); + ua_urb = list_first_entry(&ua->ready_playback_urbs, + struct ua101_urb, ready_list); + list_del(&ua_urb->ready_list); + urb = ua_urb->urb; /* fill packet with data or silence */ - urb->urb.iso_frame_desc[0].length = + urb->iso_frame_desc[0].length = frames * ua->playback.frame_bytes; if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) do_period_elapsed |= copy_playback_data(&ua->playback, - &urb->urb, + urb, frames); else - memset(urb->urb.transfer_buffer, 0, - urb->urb.iso_frame_desc[0].length); + memset(urb->transfer_buffer, 0, + urb->iso_frame_desc[0].length); /* and off you go ... */ - err = usb_submit_urb(&urb->urb, GFP_ATOMIC); + err = usb_submit_urb(urb, GFP_ATOMIC); if (unlikely(err < 0)) { abort_usb_playback(ua); abort_alsa_playback(ua); @@ -342,7 +345,8 @@ static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, static void capture_urb_complete(struct urb *urb) { - struct ua101 *ua = urb->context; + struct ua101_urb *ua_urb = urb->context; + struct ua101 *ua = ua_urb->ua; struct ua101_stream *stream = &ua->capture; unsigned int frames, write_ptr; bool do_period_elapsed; @@ -413,7 +417,8 @@ static void capture_urb_complete(struct urb *urb) static void first_capture_urb_complete(struct urb *urb) { - struct ua101 *ua = urb->context; + struct ua101_urb *ua_urb = urb->context; + struct ua101 *ua = ua_urb->ua; urb->complete = capture_urb_complete; capture_urb_complete(urb); @@ -427,7 +432,7 @@ static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) unsigned int i; for (i = 0; i < stream->queue_length; ++i) { - int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); + int err = usb_submit_urb(stream->urbs[i].urb, GFP_KERNEL); if (err < 0) { dev_err(&ua->dev->dev, "USB request error %d: %s\n", err, usb_error_string(err)); @@ -442,8 +447,8 @@ static void kill_stream_urbs(struct ua101_stream *stream) unsigned int i; for (i = 0; i < stream->queue_length; ++i) - if (stream->urbs[i]) - usb_kill_urb(&stream->urbs[i]->urb); + if (stream->urbs[i].urb) + usb_kill_urb(stream->urbs[i].urb); } static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) @@ -508,7 +513,7 @@ static int start_usb_capture(struct ua101 *ua) return err; clear_bit(CAPTURE_URB_COMPLETED, &ua->states); - ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; + ua->capture.urbs[0].urb->complete = first_capture_urb_complete; ua->rate_feedback_start = 0; ua->rate_feedback_count = 0; @@ -550,7 +555,7 @@ static int start_usb_playback(struct ua101 *ua) return err; clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); - ua->playback.urbs[0]->urb.complete = + ua->playback.urbs[0].urb->complete = first_playback_urb_complete; scoped_guard(spinlock_irq, &ua->lock) { INIT_LIST_HEAD(&ua->ready_playback_urbs); @@ -580,7 +585,7 @@ static int start_usb_playback(struct ua101 *ua) add_with_wraparound(ua, &ua->rate_feedback_start, 1); ua->rate_feedback_count--; } - urb = &ua->playback.urbs[i]->urb; + urb = ua->playback.urbs[i].urb; urb->iso_frame_desc[0].length = frames * ua->playback.frame_bytes; memset(urb->transfer_buffer, 0, @@ -1059,7 +1064,7 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, void (*urb_complete)(struct urb *)) { unsigned max_packet_size = stream->max_packet_bytes; - struct ua101_urb *urb; + struct urb *urb; unsigned int b, u = 0; for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { @@ -1070,23 +1075,24 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, while (size >= max_packet_size) { if (u >= stream->queue_length) goto bufsize_error; - urb = kmalloc_obj(*urb); + urb = usb_alloc_urb(1, GFP_KERNEL); if (!urb) return -ENOMEM; - usb_init_urb(&urb->urb); - urb->urb.dev = ua->dev; - urb->urb.pipe = stream->usb_pipe; - urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP; - urb->urb.transfer_buffer = addr; - urb->urb.transfer_dma = dma; - urb->urb.transfer_buffer_length = max_packet_size; - urb->urb.number_of_packets = 1; - urb->urb.interval = 1; - urb->urb.context = ua; - urb->urb.complete = urb_complete; - urb->urb.iso_frame_desc[0].offset = 0; - urb->urb.iso_frame_desc[0].length = max_packet_size; - stream->urbs[u++] = urb; + urb->dev = ua->dev; + urb->pipe = stream->usb_pipe; + urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = addr; + urb->transfer_dma = dma; + urb->transfer_buffer_length = max_packet_size; + urb->number_of_packets = 1; + urb->interval = 1; + urb->context = &stream->urbs[u]; + urb->complete = urb_complete; + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = max_packet_size; + stream->urbs[u].ua = ua; + stream->urbs[u].urb = urb; + u++; size -= max_packet_size; addr += max_packet_size; dma += max_packet_size; @@ -1104,8 +1110,8 @@ static void free_stream_urbs(struct ua101_stream *stream) unsigned int i; for (i = 0; i < stream->queue_length; ++i) { - kfree(stream->urbs[i]); - stream->urbs[i] = NULL; + usb_free_urb(stream->urbs[i].urb); + stream->urbs[i].urb = NULL; } } From 7a8e247dab54a42aeec0240153c55da286ac5aef Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2026 18:04:38 +0200 Subject: [PATCH 0342/1317] ALSA: usb: hiface: Avoid embedded URBs The hiface driver uses URBs embedded in struct pcm_urb, and this is basically a buggy implementation nowadays; since a URB is managed with a refcount, this may lead to a UAF when the URB is released asynchronously. For addressing the problem, this patch converts the embedded URBs to ones that are properly allocated via usb_alloc_urb(). The conversion is rather straightforward; pcm_urb.instance became a pointer, assigned/freed via usb_alloc_urb() and usb_free_urb(), and the call with this is corrected accordingly. Along with it, the resource release is done in the common destructor that is called from both at the error path and the disconnect. No functional changes, only compile-tested. Link: https://lore.kernel.org/20260903130757.0668310a.michal.pecio@gmail.com Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260903160458.1938392-3-tiwai@suse.de --- sound/usb/hiface/pcm.c | 44 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c index cd1a4c871c5d..3157952e4c1d 100644 --- a/sound/usb/hiface/pcm.c +++ b/sound/usb/hiface/pcm.c @@ -24,7 +24,7 @@ struct pcm_urb { struct hiface_chip *chip; - struct urb instance; + struct urb *instance; struct usb_anchor submitted; u8 *buffer; }; @@ -193,7 +193,7 @@ static void hiface_pcm_stream_stop(struct pcm_runtime *rt) if (!time) usb_kill_anchored_urbs( &rt->out_urbs[i].submitted); - usb_kill_urb(&rt->out_urbs[i].instance); + usb_kill_urb(rt->out_urbs[i].instance); } rt->stream_state = STREAM_DISABLED; @@ -215,9 +215,9 @@ static int hiface_pcm_stream_start(struct pcm_runtime *rt) rt->stream_state = STREAM_STARTING; for (i = 0; i < PCM_N_URBS; i++) { memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE); - usb_anchor_urb(&rt->out_urbs[i].instance, + usb_anchor_urb(rt->out_urbs[i].instance, &rt->out_urbs[i].submitted); - ret = usb_submit_urb(&rt->out_urbs[i].instance, + ret = usb_submit_urb(rt->out_urbs[i].instance, GFP_ATOMIC); if (ret) { hiface_pcm_stream_stop(rt); @@ -334,7 +334,7 @@ static void hiface_pcm_out_urb_handler(struct urb *usb_urb) if (do_period_elapsed) snd_pcm_period_elapsed(sub->instance); - ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC); + ret = usb_submit_urb(out_urb->instance, GFP_ATOMIC); if (ret < 0) goto out_fail; @@ -492,16 +492,18 @@ static int hiface_pcm_init_urb(struct pcm_urb *urb, void (*handler)(struct urb *)) { urb->chip = chip; - usb_init_urb(&urb->instance); + urb->instance = usb_alloc_urb(0, GFP_KERNEL); + if (!urb->instance) + return -ENOMEM; urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL); if (!urb->buffer) return -ENOMEM; - usb_fill_bulk_urb(&urb->instance, chip->dev, + usb_fill_bulk_urb(urb->instance, chip->dev, usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer, PCM_PACKET_SIZE, handler, urb); - if (usb_urb_ep_type_check(&urb->instance)) + if (usb_urb_ep_type_check(urb->instance)) return -EINVAL; init_usb_anchor(&urb->submitted); @@ -520,24 +522,26 @@ void hiface_pcm_abort(struct hiface_chip *chip) } } -static void hiface_pcm_destroy(struct hiface_chip *chip) +static void hiface_pcm_destroy(struct pcm_runtime *rt) { - struct pcm_runtime *rt = chip->pcm; int i; - for (i = 0; i < PCM_N_URBS; i++) - kfree(rt->out_urbs[i].buffer); + if (!rt) + return; - kfree(chip->pcm); - chip->pcm = NULL; + if (rt->chip) + rt->chip->pcm = NULL; + + for (i = 0; i < PCM_N_URBS; i++) { + usb_free_urb(rt->out_urbs[i].instance); + kfree(rt->out_urbs[i].buffer); + } + kfree(rt); } static void hiface_pcm_free(struct snd_pcm *pcm) { - struct pcm_runtime *rt = pcm->private_data; - - if (rt) - hiface_pcm_destroy(rt->chip); + hiface_pcm_destroy(pcm->private_data); } int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) @@ -587,8 +591,6 @@ int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) return 0; error: - for (i = 0; i < PCM_N_URBS; i++) - kfree(rt->out_urbs[i].buffer); - kfree(rt); + hiface_pcm_destroy(rt); return ret; } From 9fe49dbc023e82dfaee7b245997d820d01742a9a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2026 18:04:39 +0200 Subject: [PATCH 0343/1317] ALSA: usb: 6fire: Avoid embedded URBs The USB 6fire driver uses URBs embedded in different structs for PCM, MIDI and communication, and this is basically a buggy implementation nowadays; since a URB is managed with a refcount, this may lead to a UAF when the URB is released asynchronously. For addressing the problem, this patch converts those embedded URBs to ones that are properly allocated via usb_alloc_urb(). The pcm_urb.packets[] is gone, as it's allocated by usb_alloc_urb(), hence it's found in urb.iso_frame_desc[] instead. The conversions are rather straightforward; each embedded struct urb is changed to a pointer, and its callers are updated accordingly. The resource for those structs are released in the common destructor functions (usb6fire_comm_free(), etc), which are called at both the init error path and the disconnect. No functional changes, only compile-tested. Link: https://lore.kernel.org/20260903130757.0668310a.michal.pecio@gmail.com Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260903160458.1938392-4-tiwai@suse.de --- sound/usb/6fire/comm.c | 42 +++++++++----- sound/usb/6fire/comm.h | 2 +- sound/usb/6fire/midi.c | 43 +++++++++----- sound/usb/6fire/midi.h | 2 +- sound/usb/6fire/pcm.c | 128 ++++++++++++++++++++++++----------------- sound/usb/6fire/pcm.h | 5 +- 6 files changed, 136 insertions(+), 86 deletions(-) diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c index d3b7cab85699..510d310824e9 100644 --- a/sound/usb/6fire/comm.c +++ b/sound/usb/6fire/comm.c @@ -21,7 +21,6 @@ enum { static void usb6fire_comm_init_urb(struct comm_runtime *rt, struct urb *urb, u8 *buffer, void *context, void(*handler)(struct urb *urb)) { - usb_init_urb(urb); urb->transfer_buffer = buffer; urb->pipe = usb_sndintpipe(rt->chip->dev, COMM_EP); urb->complete = handler; @@ -142,6 +141,19 @@ static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request, return ret; } +static void usb6fire_comm_free(struct comm_runtime *rt) +{ + if (!rt) + return; + + if (rt->chip) + rt->chip->comm = NULL; + + usb_free_urb(rt->receiver); + kfree(rt->receiver_buffer); + kfree(rt); +} + int usb6fire_comm_init(struct sfire_chip *chip) { struct comm_runtime *rt = kzalloc_obj(struct comm_runtime); @@ -153,14 +165,18 @@ int usb6fire_comm_init(struct sfire_chip *chip) rt->receiver_buffer = kzalloc(COMM_RECEIVER_BUFSIZE, GFP_KERNEL); if (!rt->receiver_buffer) { - kfree(rt); - return -ENOMEM; + ret = -ENOMEM; + goto error; } - urb = &rt->receiver; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + ret = -ENOMEM; + goto error; + } + rt->receiver = urb; rt->serial = 1; rt->chip = chip; - usb_init_urb(urb); rt->init_urb = usb6fire_comm_init_urb; rt->write8 = usb6fire_comm_write8; rt->write16 = usb6fire_comm_write16; @@ -175,13 +191,15 @@ int usb6fire_comm_init(struct sfire_chip *chip) urb->interval = 1; ret = usb_submit_urb(urb, GFP_KERNEL); if (ret < 0) { - kfree(rt->receiver_buffer); - kfree(rt); dev_err(&chip->dev->dev, "cannot create comm data receiver."); - return ret; + goto error; } chip->comm = rt; return 0; + + error: + usb6fire_comm_free(rt); + return ret; } void usb6fire_comm_abort(struct sfire_chip *chip) @@ -189,14 +207,10 @@ void usb6fire_comm_abort(struct sfire_chip *chip) struct comm_runtime *rt = chip->comm; if (rt) - usb_poison_urb(&rt->receiver); + usb_poison_urb(rt->receiver); } void usb6fire_comm_destroy(struct sfire_chip *chip) { - struct comm_runtime *rt = chip->comm; - - kfree(rt->receiver_buffer); - kfree(rt); - chip->comm = NULL; + usb6fire_comm_free(chip->comm); } diff --git a/sound/usb/6fire/comm.h b/sound/usb/6fire/comm.h index 2447d7ecf179..89976f510f6c 100644 --- a/sound/usb/6fire/comm.h +++ b/sound/usb/6fire/comm.h @@ -19,7 +19,7 @@ enum /* settings for comm */ struct comm_runtime { struct sfire_chip *chip; - struct urb receiver; + struct urb *receiver; u8 *receiver_buffer; u8 serial; /* urb serial */ diff --git a/sound/usb/6fire/midi.c b/sound/usb/6fire/midi.c index 6b0bb096f27a..279b449936e7 100644 --- a/sound/usb/6fire/midi.c +++ b/sound/usb/6fire/midi.c @@ -66,7 +66,7 @@ static void usb6fire_midi_out_trigger( struct snd_rawmidi_substream *alsa_sub, int up) { struct midi_runtime *rt = alsa_sub->rmidi->private_data; - struct urb *urb = &rt->out_urb; + struct urb *urb = rt->out_urb; __s8 ret; guard(spinlock_irqsave)(&rt->out_lock); @@ -137,6 +137,19 @@ static const struct snd_rawmidi_ops in_ops = { .trigger = usb6fire_midi_in_trigger }; +static void usb6fire_midi_free(struct midi_runtime *rt) +{ + if (!rt) + return; + + if (rt->chip) + rt->chip->midi = NULL; + + usb_free_urb(rt->out_urb); + kfree(rt->out_buffer); + kfree(rt); +} + int usb6fire_midi_init(struct sfire_chip *chip) { int ret; @@ -148,8 +161,14 @@ int usb6fire_midi_init(struct sfire_chip *chip) rt->out_buffer = kzalloc(MIDI_BUFSIZE, GFP_KERNEL); if (!rt->out_buffer) { - kfree(rt); - return -ENOMEM; + ret = -ENOMEM; + goto error; + } + + rt->out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rt->out_urb) { + ret = -ENOMEM; + goto error; } rt->chip = chip; @@ -160,15 +179,13 @@ int usb6fire_midi_init(struct sfire_chip *chip) spin_lock_init(&rt->in_lock); spin_lock_init(&rt->out_lock); - comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt, + comm_rt->init_urb(comm_rt, rt->out_urb, rt->out_buffer, rt, usb6fire_midi_out_handler); ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance); if (ret < 0) { - kfree(rt->out_buffer); - kfree(rt); dev_err(&chip->dev->dev, "unable to create midi.\n"); - return ret; + goto error; } rt->instance->private_data = rt; strscpy(rt->instance->name, "DMX6FireUSB MIDI"); @@ -182,6 +199,10 @@ int usb6fire_midi_init(struct sfire_chip *chip) chip->midi = rt; return 0; + + error: + usb6fire_midi_free(rt); + return ret; } void usb6fire_midi_abort(struct sfire_chip *chip) @@ -189,14 +210,10 @@ void usb6fire_midi_abort(struct sfire_chip *chip) struct midi_runtime *rt = chip->midi; if (rt) - usb_poison_urb(&rt->out_urb); + usb_poison_urb(rt->out_urb); } void usb6fire_midi_destroy(struct sfire_chip *chip) { - struct midi_runtime *rt = chip->midi; - - kfree(rt->out_buffer); - kfree(rt); - chip->midi = NULL; + usb6fire_midi_free(chip->midi); } diff --git a/sound/usb/6fire/midi.h b/sound/usb/6fire/midi.h index 47640c845903..8716ab8a863a 100644 --- a/sound/usb/6fire/midi.h +++ b/sound/usb/6fire/midi.h @@ -22,7 +22,7 @@ struct midi_runtime { spinlock_t in_lock; spinlock_t out_lock; struct snd_rawmidi_substream *out; - struct urb out_urb; + struct urb *out_urb; u8 out_serial; /* serial number of out packet */ u8 *out_buffer; int buffer_offset; diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index d2e274b731fe..21789db6657d 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -138,8 +138,8 @@ static void usb6fire_pcm_stream_stop(struct pcm_runtime *rt) rt->stream_state = STREAM_STOPPING; for (i = 0; i < PCM_N_URBS; i++) { - usb_kill_urb(&rt->in_urbs[i].instance); - usb_kill_urb(&rt->out_urbs[i].instance); + usb_kill_urb(rt->in_urbs[i].instance); + usb_kill_urb(rt->out_urbs[i].instance); } ctrl_rt->usb_streaming = false; ctrl_rt->update_streaming(ctrl_rt); @@ -161,13 +161,13 @@ static int usb6fire_pcm_stream_start(struct pcm_runtime *rt) rt->stream_state = STREAM_STARTING; for (i = 0; i < PCM_N_URBS; i++) { for (k = 0; k < PCM_N_PACKETS_PER_URB; k++) { - packet = &rt->in_urbs[i].packets[k]; + packet = &rt->in_urbs[i].instance->iso_frame_desc[k]; packet->offset = k * rt->in_packet_size; packet->length = rt->in_packet_size; packet->actual_length = 0; packet->status = 0; } - ret = usb_submit_urb(&rt->in_urbs[i].instance, + ret = usb_submit_urb(rt->in_urbs[i].instance, GFP_ATOMIC); if (ret) { usb6fire_pcm_stream_stop(rt); @@ -197,6 +197,7 @@ static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb) unsigned int total_length = 0; struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance); struct snd_pcm_runtime *alsa_rt = sub->instance->runtime; + struct usb_iso_packet_descriptor *isoc; u32 *src = NULL; u32 *dest = (u32 *) (alsa_rt->dma_area + sub->dma_off * (alsa_rt->frame_bits >> 3)); @@ -207,8 +208,9 @@ static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb) for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { /* at least 4 header bytes for valid packet. * after that: 32 bits per sample for analog channels */ - if (urb->packets[i].actual_length > 4) - frame_count = (urb->packets[i].actual_length - 4) + isoc = &urb->instance->iso_frame_desc[i]; + if (isoc->actual_length > 4) + frame_count = (isoc->actual_length - 4) / (rt->in_n_analog << 2); else frame_count = 0; @@ -220,7 +222,7 @@ static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb) else return; src++; /* skip leading 4 bytes of every packet */ - total_length += urb->packets[i].length; + total_length += isoc->length; for (frame = 0; frame < frame_count; frame++) { memcpy(dest, src, bytes_per_frame); dest += alsa_rt->channels; @@ -244,6 +246,7 @@ static void usb6fire_pcm_playback(struct pcm_substream *sub, int frame_count; struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance); struct snd_pcm_runtime *alsa_rt = sub->instance->runtime; + struct usb_iso_packet_descriptor *isoc; u32 *src = (u32 *) (alsa_rt->dma_area + sub->dma_off * (alsa_rt->frame_bits >> 3)); u32 *src_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size @@ -263,8 +266,9 @@ static void usb6fire_pcm_playback(struct pcm_substream *sub, for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { /* at least 4 header bytes for valid packet. * after that: 32 bits per sample for analog channels */ - if (urb->packets[i].length > 4) - frame_count = (urb->packets[i].length - 4) + isoc = &urb->instance->iso_frame_desc[i]; + if (isoc->length > 4) + frame_count = (isoc->length - 4) / (rt->out_n_analog << 2); else frame_count = 0; @@ -289,6 +293,7 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) struct pcm_urb *out_urb = in_urb->peer; struct pcm_runtime *rt = in_urb->chip->pcm; struct pcm_substream *sub; + struct usb_iso_packet_descriptor *isoc_out, *isoc_in; bool period_elapsed; int total_length = 0; int frame_count; @@ -299,11 +304,13 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING) return; - for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) - if (in_urb->packets[i].status) { + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + isoc_in = &in_urb->instance->iso_frame_desc[i]; + if (isoc_in->status) { rt->panic = true; return; } + } if (rt->stream_state == STREAM_DISABLED) { dev_err(&rt->chip->dev->dev, @@ -328,12 +335,13 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* setup out urb structure */ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { - out_urb->packets[i].offset = total_length; - out_urb->packets[i].length = (in_urb->packets[i].actual_length - - 4) / (rt->in_n_analog << 2) + isoc_out = &out_urb->instance->iso_frame_desc[i]; + isoc_in = &in_urb->instance->iso_frame_desc[i]; + isoc_out->offset = total_length; + isoc_out->length = (isoc_in->actual_length - 4) / (rt->in_n_analog << 2) * (rt->out_n_analog << 2) + 4; - out_urb->packets[i].status = 0; - total_length += out_urb->packets[i].length; + isoc_out->status = 0; + total_length += isoc_out->length; } memset(out_urb->buffer, 0, total_length); @@ -354,9 +362,10 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* setup the 4th byte of each sample (0x40 for analog channels) */ dest = out_urb->buffer; - for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) - if (out_urb->packets[i].length >= 4) { - frame_count = (out_urb->packets[i].length - 4) + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + isoc_out = &out_urb->instance->iso_frame_desc[i]; + if (isoc_out->length >= 4) { + frame_count = (isoc_out->length - 4) / (rt->out_n_analog << 2); *(dest++) = 0xaa; *(dest++) = 0xaa; @@ -370,8 +379,10 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) *(dest++) = 0x40; } } - usb_submit_urb(&out_urb->instance, GFP_ATOMIC); - usb_submit_urb(&in_urb->instance, GFP_ATOMIC); + } + + usb_submit_urb(out_urb->instance, GFP_ATOMIC); + usb_submit_urb(in_urb->instance, GFP_ATOMIC); } static void usb6fire_pcm_out_urb_handler(struct urb *usb_urb) @@ -534,22 +545,25 @@ static const struct snd_pcm_ops pcm_ops = { .pointer = usb6fire_pcm_pointer, }; -static void usb6fire_pcm_init_urb(struct pcm_urb *urb, - struct sfire_chip *chip, bool in, int ep, - void (*handler)(struct urb *)) +static int usb6fire_pcm_init_urb(struct pcm_urb *urb, + struct sfire_chip *chip, bool in, int ep, + void (*handler)(struct urb *)) { urb->chip = chip; - usb_init_urb(&urb->instance); - urb->instance.transfer_buffer = urb->buffer; - urb->instance.transfer_buffer_length = + urb->instance = usb_alloc_urb(PCM_N_PACKETS_PER_URB, GFP_KERNEL); + if (!urb->instance) + return -ENOMEM; + urb->instance->transfer_buffer = urb->buffer; + urb->instance->transfer_buffer_length = PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE; - urb->instance.dev = chip->dev; - urb->instance.pipe = in ? usb_rcvisocpipe(chip->dev, ep) + urb->instance->dev = chip->dev; + urb->instance->pipe = in ? usb_rcvisocpipe(chip->dev, ep) : usb_sndisocpipe(chip->dev, ep); - urb->instance.interval = 1; - urb->instance.complete = handler; - urb->instance.context = urb; - urb->instance.number_of_packets = PCM_N_PACKETS_PER_URB; + urb->instance->interval = 1; + urb->instance->complete = handler; + urb->instance->context = urb; + urb->instance->number_of_packets = PCM_N_PACKETS_PER_URB; + return 0; } static int usb6fire_pcm_buffers_init(struct pcm_runtime *rt) @@ -571,14 +585,23 @@ static int usb6fire_pcm_buffers_init(struct pcm_runtime *rt) return 0; } -static void usb6fire_pcm_buffers_destroy(struct pcm_runtime *rt) +static void usb6fire_pcm_free(struct pcm_runtime *rt) { int i; + if (!rt) + return; + + if (rt->chip) + rt->chip->pcm = NULL; + for (i = 0; i < PCM_N_URBS; i++) { + usb_free_urb(rt->out_urbs[i].instance); kfree(rt->out_urbs[i].buffer); + usb_free_urb(rt->in_urbs[i].instance); kfree(rt->in_urbs[i].buffer); } + kfree(rt); } int usb6fire_pcm_init(struct sfire_chip *chip) @@ -593,11 +616,8 @@ int usb6fire_pcm_init(struct sfire_chip *chip) return -ENOMEM; ret = usb6fire_pcm_buffers_init(rt); - if (ret) { - usb6fire_pcm_buffers_destroy(rt); - kfree(rt); - return ret; - } + if (ret) + goto error; rt->chip = chip; rt->stream_state = STREAM_DISABLED; @@ -609,10 +629,14 @@ int usb6fire_pcm_init(struct sfire_chip *chip) spin_lock_init(&rt->capture.lock); for (i = 0; i < PCM_N_URBS; i++) { - usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP, - usb6fire_pcm_in_urb_handler); - usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP, - usb6fire_pcm_out_urb_handler); + ret = usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP, + usb6fire_pcm_in_urb_handler); + if (ret < 0) + goto error; + ret = usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP, + usb6fire_pcm_out_urb_handler); + if (ret < 0) + goto error; rt->in_urbs[i].peer = &rt->out_urbs[i]; rt->out_urbs[i].peer = &rt->in_urbs[i]; @@ -620,10 +644,8 @@ int usb6fire_pcm_init(struct sfire_chip *chip) ret = snd_pcm_new(chip->card, "DMX6FireUSB", 0, 1, 1, &pcm); if (ret < 0) { - usb6fire_pcm_buffers_destroy(rt); - kfree(rt); dev_err(&chip->dev->dev, "cannot create pcm instance.\n"); - return ret; + goto error; } pcm->private_data = rt; @@ -636,6 +658,10 @@ int usb6fire_pcm_init(struct sfire_chip *chip) chip->pcm = rt; return 0; + + error: + usb6fire_pcm_free(rt); + return ret; } void usb6fire_pcm_abort(struct sfire_chip *chip) @@ -653,8 +679,8 @@ void usb6fire_pcm_abort(struct sfire_chip *chip) snd_pcm_stop_xrun(rt->capture.instance); for (i = 0; i < PCM_N_URBS; i++) { - usb_poison_urb(&rt->in_urbs[i].instance); - usb_poison_urb(&rt->out_urbs[i].instance); + usb_poison_urb(rt->in_urbs[i].instance); + usb_poison_urb(rt->out_urbs[i].instance); } } @@ -662,9 +688,5 @@ void usb6fire_pcm_abort(struct sfire_chip *chip) void usb6fire_pcm_destroy(struct sfire_chip *chip) { - struct pcm_runtime *rt = chip->pcm; - - usb6fire_pcm_buffers_destroy(rt); - kfree(rt); - chip->pcm = NULL; + usb6fire_pcm_free(chip->pcm); } diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h index 5a092dfd69f5..b586fe220fd1 100644 --- a/sound/usb/6fire/pcm.h +++ b/sound/usb/6fire/pcm.h @@ -24,10 +24,7 @@ enum /* settings for pcm */ struct pcm_urb { struct sfire_chip *chip; - /* BEGIN DO NOT SEPARATE */ - struct urb instance; - struct usb_iso_packet_descriptor packets[PCM_N_PACKETS_PER_URB]; - /* END DO NOT SEPARATE */ + struct urb *instance; u8 *buffer; struct pcm_urb *peer; From b26a7a80e6bbf8dd17dacb127d12435d79375cf2 Mon Sep 17 00:00:00 2001 From: Roman Prucha Date: Thu, 3 Sep 2026 23:14:44 +0200 Subject: [PATCH 0344/1317] ALSA: ctxfi: Fix CA20K2 S/PDIF passthrough dao_rsc_init() encodes the DAIO configuration as conf = (desc->msr & 0x7) | (desc->passthru << 3); S/PDIF passthrough uses msr=1 and passthru=1, resulting in conf=9. daio_mgr_dao_init() masks conf with 0xf, but handles only values 1, 2, 4 and 8 when programming ATXCTL_NUC. As a result, conf=9 falls through to the default case and leaves NUC at its previous setting. On a Creative X-Fi Titanium HD SB1270 (CA20K2), this breaks AC3 IEC61937 passthrough when snd_ctxfi runs with reference_rate=48000,multiple=2. The receiver detects a non-audio stream but cannot decode the AC3 payload. With the unmodified driver, multiple=1 makes the same stream work. Handle conf=9 through the same NUC=0 path as conf=1. The change was runtime tested on the SB1270 with multiple=2 using IEC958 stereo PCM, pre-encoded AC3 IEC61937 passthrough and ALSA A52 live 5.1 encoding. Fixes: 26a9630c72eb ("ALSA: ctxfi: cthw20k2: fix mask on conf to allow 4 bits") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Roman Prucha Link: https://patch.msgid.link/20260903-ctxfi-spdif-conf9-fix-v1-1-5e4e3e1f801c@gmail.com Signed-off-by: Takashi Iwai --- sound/pci/ctxfi/cthw20k2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c index 07e1490a6d17..36066ffe8784 100644 --- a/sound/pci/ctxfi/cthw20k2.c +++ b/sound/pci/ctxfi/cthw20k2.c @@ -994,6 +994,7 @@ static int daio_mgr_dao_init(struct hw *hw, void *blk, unsigned int idx, unsigne /* S/PDIF output */ switch ((conf & 0xf)) { case 1: + case 9: set_field(&ctl->txctl[idx], ATXCTL_NUC, 0); break; case 2: From 861111a14740e12c36d363e9830f8daa734279c9 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Fri, 4 Sep 2026 20:58:25 +0000 Subject: [PATCH 0345/1317] ALSA: usbusx2y: fix in04_last array size mismatch with in04_buf The in04_last array in struct usx2ydev is declared as char[24], but in04_buf is allocated as sizeof(struct us428_ctls) which is 21 bytes. In i_usx2y_in04_int(), when ctl_snapshot_last == -2 (initialization path): memcpy(usx2y->in04_last, usx2y->in04_buf, sizeof(usx2y->in04_last)); This copies 24 bytes from a 21-byte slab allocation, reading 3 bytes past the end of the source object. Introduce a USX2Y_IN04_SIZE constant defined as sizeof(struct us428_ctls) and use it consistently for the in04_last array, the in04_buf allocation, the URB transfer length, and the comparison loop, replacing the bare 24 and 21 literals throughout. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Link: https://patch.msgid.link/20260904205826.4071119-1-tristmd@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usbusx2y.c | 6 +++--- sound/usb/usx2y/usbusx2y.h | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c index 4190227c5a2a..200b708aacd6 100644 --- a/sound/usb/usx2y/usbusx2y.c +++ b/sound/usb/usx2y/usbusx2y.c @@ -196,7 +196,7 @@ static void i_usx2y_in04_int(struct urb *urb) memcpy(usx2y->in04_last, usx2y->in04_buf, sizeof(usx2y->in04_last)); us428ctls->ctl_snapshot_last = -1; } else { - for (i = 0; i < 21; i++) { + for (i = 0; i < USX2Y_IN04_SIZE; i++) { if (usx2y->in04_last[i] != ((char *)usx2y->in04_buf)[i]) { if (diff < 0) diff = i; @@ -305,7 +305,7 @@ int usx2y_in04_init(struct usx2ydev *usx2y) goto error; } - usx2y->in04_buf = kmalloc(21, GFP_KERNEL); + usx2y->in04_buf = kmalloc(USX2Y_IN04_SIZE, GFP_KERNEL); if (!usx2y->in04_buf) { err = -ENOMEM; goto error; @@ -313,7 +313,7 @@ int usx2y_in04_init(struct usx2ydev *usx2y) init_waitqueue_head(&usx2y->in04_wait_queue); usb_fill_int_urb(usx2y->in04_urb, usx2y->dev, usb_rcvintpipe(usx2y->dev, 0x4), - usx2y->in04_buf, 21, + usx2y->in04_buf, USX2Y_IN04_SIZE, i_usx2y_in04_int, usx2y, 10); if (usb_urb_ep_type_check(usx2y->in04_urb)) { diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h index 6a76d04bf1c7..7b6deed17d4b 100644 --- a/sound/usb/usx2y/usbusx2y.h +++ b/sound/usb/usx2y/usbusx2y.h @@ -5,6 +5,8 @@ #include "../midi.h" #include "usbus428ctldefs.h" +#define USX2Y_IN04_SIZE sizeof(struct us428_ctls) + #define NRURBS 2 /* Default value used for nr of packs per urb. @@ -55,7 +57,7 @@ struct usx2ydev { int stride; struct urb *in04_urb; void *in04_buf; - char in04_last[24]; + char in04_last[USX2Y_IN04_SIZE]; unsigned int in04_int_calls; struct snd_usx2y_urb_seq *us04; wait_queue_head_t in04_wait_queue; From 8f5ef203abda9dd36b2af473c7b737d544f807bd Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Fri, 4 Sep 2026 20:58:26 +0000 Subject: [PATCH 0346/1317] ALSA: usbusx2y: validate URB actual_length in interrupt callback i_usx2y_in04_int() processes the interrupt URB data without checking urb->actual_length. A short transfer from a malfunctioning device would cause the handler to process uninitialized heap data from the kmalloc-allocated in04_buf, which is then copied to the mmap-accessible ctl_snapshot[] array. Fix by using kzalloc() for in04_buf to zero-initialize the buffer, and adding an actual_length check to skip processing on short transfers while still resubmitting the URB. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Link: https://patch.msgid.link/20260904205826.4071119-2-tristmd@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usbusx2y.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c index 200b708aacd6..108f9bddf9aa 100644 --- a/sound/usb/usx2y/usbusx2y.c +++ b/sound/usb/usx2y/usbusx2y.c @@ -189,6 +189,9 @@ static void i_usx2y_in04_int(struct urb *urb) return; } + if (urb->actual_length < USX2Y_IN04_SIZE) + goto resubmit; + if (us428ctls) { diff = -1; if (us428ctls->ctl_snapshot_last == -2) { @@ -253,6 +256,7 @@ static void i_usx2y_in04_int(struct urb *urb) if (err) dev_err(&urb->dev->dev, "in04_int() usb_submit_urb err=%i\n", err); +resubmit: urb->dev = usx2y->dev; usb_submit_urb(urb, GFP_ATOMIC); } @@ -305,7 +309,7 @@ int usx2y_in04_init(struct usx2ydev *usx2y) goto error; } - usx2y->in04_buf = kmalloc(USX2Y_IN04_SIZE, GFP_KERNEL); + usx2y->in04_buf = kzalloc(USX2Y_IN04_SIZE, GFP_KERNEL); if (!usx2y->in04_buf) { err = -ENOMEM; goto error; From 12cba0ce111c2caabf4ff24dc5538959813664c0 Mon Sep 17 00:00:00 2001 From: Krish Gulati Date: Sun, 6 Sep 2026 03:56:56 +0530 Subject: [PATCH 0347/1317] ALSA: hda/realtek: Add quirk for HP Omen 16-wd0xxx mute LED Add SND_PCI_QUIRK entry for HP Omen 16-wd0xxx (PCI SSID 103c:8ba9) using ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT, which controls the mute LED via a COEF bit write on the ALC245 codec. Without this quirk, the mute LED does not reflect mute state on this model. Tested on the affected hardware; mute LED now toggles correctly. Signed-off-by: Krish Gulati Link: https://patch.msgid.link/20260905222659.10247-1-krishgulati7@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 95b40a177d2b..f7786e1b0878 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7566,6 +7566,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), From 45b5beb60bf7bd41c55ef17f6f2d28b351fad6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dalfu=C3=9F?= Date: Sat, 5 Sep 2026 14:11:12 +0200 Subject: [PATCH 0348/1317] ALSA: usb-audio: Add boot quirk for Behringer CM1A MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a power cycle and reenumeration, the Behringer CM1A* leaves its MIDI endpoint inoperative. USB enumeration and driver binding complete successfully, but MIDI outputs remain pending. A GET_DESCRIPTOR request for the device descriptor, issued after USB configuration, makes the endpoint operational. Add a one time boot quirk to perform that request before ALSA initializes the device. * ID 1397:1234 BEHRINGER International GmbH CM1A Signed-off-by: Sebastian Dalfuß Link: https://patch.msgid.link/apwG4DRfNyvmRzyb@sedf.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 91938172912a..5dede66ea69e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -849,6 +849,27 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) return 0; } +/* + * A post configuration device descriptor read is needed to make the CM1A + * operational after reenumeration. + */ +static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) +{ + struct usb_device_descriptor *desc __free(kfree) = kmalloc_obj(*desc); + int err; + + if (!desc) + return -ENOMEM; + + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, sizeof(*desc)); + if (err < 0) { + dev_err(&dev->dev, "failed to read device descriptor: %d\n", err); + return err; + } + + return 0; +} + /* * Some sound cards from Native Instruments are in fact compliant to the USB * audio standard of version 2 and other approved USB standards, even though @@ -1681,6 +1702,8 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, switch (id) { case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ return snd_usb_motu_m_series_boot_quirk(dev); + case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ + return snd_usb_cm1a_boot_quirk(dev); } return 0; From 33fb59da49c4c3f5c2ec9f9d4447a56857a02c02 Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Fri, 4 Sep 2026 18:44:37 +0530 Subject: [PATCH 0349/1317] RDMA/core: fix refcount bug in iwpm_get_nlmsg_request() iwpm_get_nlmsg_request() initializes refcount _after_ list_add_tail() making it accessible to global list where another CPU can kref_get() on nlmsg_request causing a refcount "addition on 0" bug. Fix this by initializing kref _before_ list_add_tail() so refcount for nlmsg_request can be incremented/decremented normally. In addition, also initialize every field before list_add_tail(). Reported-by: syzbot+bd317784d628820741b5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=bd317784d628820741b5 Fixes: 30dc5e63d6a5 ("RDMA/core: Add support for iWARP Port Mapper user space service") Cc: stable@vger.kernel.org Signed-off-by: Jeffin Philip Link: https://patch.msgid.link/20260904131437.12917-1-jeffinphilip14@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/iwpm_util.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/core/iwpm_util.c b/drivers/infiniband/core/iwpm_util.c index 990cf928b32a..51af8c1c49f9 100644 --- a/drivers/infiniband/core/iwpm_util.c +++ b/drivers/infiniband/core/iwpm_util.c @@ -314,10 +314,6 @@ struct iwpm_nlmsg_request *iwpm_get_nlmsg_request(__u32 nlmsg_seq, if (!nlmsg_request) return NULL; - spin_lock_irqsave(&iwpm_nlmsg_req_lock, flags); - list_add_tail(&nlmsg_request->inprocess_list, &iwpm_nlmsg_req_list); - spin_unlock_irqrestore(&iwpm_nlmsg_req_lock, flags); - kref_init(&nlmsg_request->kref); kref_get(&nlmsg_request->kref); nlmsg_request->nlmsg_seq = nlmsg_seq; @@ -326,6 +322,11 @@ struct iwpm_nlmsg_request *iwpm_get_nlmsg_request(__u32 nlmsg_seq, nlmsg_request->err_code = 0; sema_init(&nlmsg_request->sem, 1); down(&nlmsg_request->sem); + + spin_lock_irqsave(&iwpm_nlmsg_req_lock, flags); + list_add_tail(&nlmsg_request->inprocess_list, &iwpm_nlmsg_req_list); + spin_unlock_irqrestore(&iwpm_nlmsg_req_lock, flags); + return nlmsg_request; } From 89ff16f0713917303210c560eec5cd0c13bd651f Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 12:53:44 -1000 Subject: [PATCH 0350/1317] sched_ext: scx_qmap: Fix pending partition work handoff qmap can leave partition work pending with no runner. The effective-cap callback publishes its request after failing to acquire part_busy, while redistribute() checks for pending work before releasing it. Either ordering can miss a request arriving as the current runner finishes, delaying the update until the round-robin timer runs. Publish requests before trying to become the runner and release part_busy before checking for more work. Have all holders drain pending requests after releasing it, including the stats flush. Distinguish mask refreshes from repartitions so an effective-cap update only rebuilds the partition when a repartition was also requested. Fixes: e9151ed5c944 ("tools/sched_ext: scx_qmap - Expand hierarchical sub-scheduling") Reported-by: Andrea Righi Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 77 ++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 062bb22ee65c..bda56c37acb5 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -1295,11 +1295,16 @@ struct { __type(value, struct round_robin_timer); } round_robin_timer SEC(".maps"); +enum part_pending_flags { + PART_REFRESH = BIT_U64(0), + PART_REDISTRIBUTE = BIT_U64(1), +}; + /* * Partition update synchronization. qa.part can be written from concurrent * contexts. This single-runner guard admits one writer at a time without * holding a lock across the grant/revoke kfuncs. part_pending coalesces - * repartition requests that arrive while it is held. + * refresh and repartition requests that arrive while it is held. * * They live in .bss, not the arena: rr_advance() runs from a bpf_timer * callback, where the verifier rejects atomic ops on arena memory. @@ -1668,33 +1673,46 @@ __noinline void apply_partition(void) } } -/* - * Recompute the split off the node's held caps and apply it. The contexts this - * runs from (the sub-sched and cgroup callbacks, the rr timer) are not - * serialized by the kernel, so a single runner does the work. A caller that - * finds the guard held leaves part_pending set; the holder drains it before - * releasing, with the rr timer as a backstop. +/** + * execute_partition - Run pending partition updates + * + * The rr timer is the backstop if the loop reaches its iteration limit. */ -static void redistribute(void) +static void execute_partition(void) { + u64 pending; s32 i; - __sync_fetch_and_or(&part_pending, 1); - - if (!part_try_start()) - return; - bpf_for(i, 0, 1024) { - __sync_fetch_and_and(&part_pending, 0); - /* charge elapsed time to the current partition before rebuilding it */ - account_alloc(); - compute_partition(); - apply_partition(); + if (!part_try_start()) + break; + + pending = __sync_fetch_and_and(&part_pending, 0); + if (pending & PART_REDISTRIBUTE) { + /* charge elapsed time before repartitioning */ + account_alloc(); + compute_partition(); + apply_partition(); + } else if (pending & PART_REFRESH) { + refresh_usable(); + } + + /* + * Requests are published before trying the guard. Releasing it + * before checking pending work ensures a racing request is + * either observed here or handled by a caller that acquires the + * guard. + */ + part_end(); if (!__sync_fetch_and_or(&part_pending, 0)) break; } +} - part_end(); +static void redistribute(void) +{ + __sync_fetch_and_or(&part_pending, PART_REDISTRIBUTE); + execute_partition(); } /* @@ -1708,6 +1726,7 @@ int flush_alloc(void *ctx) if (part_try_start()) { account_alloc(); part_end(); + execute_partition(); } return 0; } @@ -1765,9 +1784,7 @@ static void rr_advance(void) part_end(); - /* a resplit queued while we held the guard supersedes this rotation */ - if (__sync_fetch_and_or(&part_pending, 0)) - redistribute(); + execute_partition(); } /* advance the time-shared cid pool every round_robin_ns */ @@ -2041,20 +2058,8 @@ void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after) cmask_clear(cid, &qa.avail_cids.mask); } - /* - * When another runner holds the partition guard, set part_pending: - * redistribute() drains it before releasing and rr_advance() checks it - * after, so the deferred refresh lands by the next rr tick. A - * repartition that lost the guard to us runs here. - */ - if (part_try_start()) { - refresh_usable(); - part_end(); - if (__sync_fetch_and_or(&part_pending, 0)) - redistribute(); - } else { - __sync_fetch_and_or(&part_pending, 1); - } + __sync_fetch_and_or(&part_pending, PART_REFRESH); + execute_partition(); } SCX_OPS_CID_DEFINE(qmap_ops, From 2ac74c6db40adaa29c50cbb281ae9a6f63de18e1 Mon Sep 17 00:00:00 2001 From: John Hubbard Date: Sun, 6 Sep 2026 14:58:22 -0700 Subject: [PATCH 0351/1317] rust: allow `clippy::as_underscore` in the generated bindings A CLIPPY=1 build emitted about 15000 `as _` conversion warnings, all of them in bindgen's generated output and none in hand-written code. [ The lint messages look like: error: using `as _` conversion --> rust/bindings/bindings_generated.rs:18947:9 | 18947 | self._bitfield_1.get_const::<0usize, 16u8>() as u32 as _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- | | | help: consider giving the type explicitly: `u32` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#as_underscore = note: `-D clippy::as-underscore` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::as_underscore)]` - Miguel ] bindgen 0.73 returns each bitfield read through a trailing `as _`, and 0.72 returns it through a transmute, which the lint ignores. The bindings and uapi crates allow `clippy::all` over the generated code. That group does not cover `clippy::as_underscore`, a restriction lint. Allow `clippy::as_underscore` by name in the bindings and uapi crates. Assisted-by: LLM Signed-off-by: John Hubbard Link: https://patch.msgid.link/20260906215822.1201022-1-jhubbard@nvidia.com Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). [ Removed CI sentence. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/bindings/lib.rs | 1 + rust/uapi/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 812f8e5a08d5..ad24c920b919 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -22,6 +22,7 @@ #![feature(cfi_encoding)] #[allow(dead_code)] +#[allow(clippy::as_underscore)] #[allow(clippy::cast_lossless)] #[allow(clippy::ptr_as_ptr)] #[allow(clippy::ref_as_ptr)] diff --git a/rust/uapi/lib.rs b/rust/uapi/lib.rs index 797ead5b5626..2df0340e63d1 100644 --- a/rust/uapi/lib.rs +++ b/rust/uapi/lib.rs @@ -10,6 +10,7 @@ #![no_std] #![allow( clippy::all, + clippy::as_underscore, clippy::cast_lossless, clippy::ptr_as_ptr, clippy::ref_as_ptr, From 5ba79d37403d86082ab4083b0f51ec3008a942cb Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 3 Jul 2026 18:58:35 +0200 Subject: [PATCH 0352/1317] powerpc/ps3: Fix repository.c build failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC fails to build ps3_defconfig with the following errors: arch/powerpc/platforms/ps3/repository.c: In function ‘make_first_field.constprop’: arch/powerpc/platforms/ps3/repository.c:78:9: error: ‘strnlen’ specified bound 8 exceeds source size 3 [-Werror=stringop-overread] 78 | memcpy((char *)&n, text, strnlen(text, sizeof(n))); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ arch/powerpc/platforms/ps3/repository.c: In function ‘make_first_field.constprop’: arch/powerpc/platforms/ps3/repository.c:78:9: error: ‘strnlen’ specified bound 8 exceeds source size 4 [-Werror=stringop-overread] 78 | memcpy((char *)&n, text, strnlen(text, sizeof(n))); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The current use of strnlen(text, sizeof(n)) triggers -Wstringop-overread when text is a short string literal that is smaller than sizeof(n), such as "bi" or "bus". Use strlen(text) instead and clamp the copy length to sizeof(n) before memcpy(). Drop the redundant char * cast while at it. Fixes: f94a84a09148 ("powerpc/ps3: refactor strncpy usage") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260703165834.137242-2-thorsten.blum@linux.dev --- arch/powerpc/platforms/ps3/repository.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/ps3/repository.c b/arch/powerpc/platforms/ps3/repository.c index b8c030eab138..0cc755ac3e7f 100644 --- a/arch/powerpc/platforms/ps3/repository.c +++ b/arch/powerpc/platforms/ps3/repository.c @@ -6,6 +6,8 @@ * Copyright 2006 Sony Corp. */ +#include + #include #include "platform.h" @@ -74,8 +76,9 @@ static void _dump_node(unsigned int lpar_id, u64 n1, u64 n2, u64 n3, u64 n4, static u64 make_first_field(const char *text, u64 index) { u64 n = 0; + size_t len = min(strlen(text), sizeof(n)); - memcpy((char *)&n, text, strnlen(text, sizeof(n))); + memcpy(&n, text, len); return PS3_VENDOR_ID_NONE + (n >> 32) + index; } From f789d291bbdac6bc02d9d77e0141e138626c17a8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 20 Aug 2026 20:58:06 -0700 Subject: [PATCH 0353/1317] xfs: fix media verification ioctl for internal rt volumes A media scan of a filesystem containing an internal rt volume produced an error in xfs_scrub phase 6 complaining about a truncated realtime device. The rt device wasn't truncated, but the media scan code thought we were trying to start a scan past the end of m_rtdev_targp. That in turn is an alias for m_ddev_targp, but in xfs_configure_buftarg we set nr_sectors to the size of the data section. We don't account for an internal realtime section, so the kernel doesn't scan any part of it. Oops. Reproducer: # mkfs.xfs -f /dev/sda -r zoned=1 -d rtinherit=1 # mount /dev/sda /mnt # dd if=/dev/zero of=/mnt/a bs=1024k count=100 # sync # xfs_info /mnt meta-data=/dev/sda isize=512 agcount=4, agsize=32768 blks = sectsz=512 attr=2, projid32bit=1 = crc=1 finobt=1, sparse=1, rmapbt=1 = reflink=0 bigtime=1 inobtcount=1 nrext64=1 = exchange=1 metadir=1 data = bsize=4096 blocks=131072, imaxpct=25 = sunit=0 swidth=0 blks naming =version 2 bsize=4096 ascii-ci=0, ftype=1, parent=1 log =internal log bsize=4096 blocks=16384, version=2 = sectsz=512 sunit=0 blks, lazy-count=1 realtime =internal extsz=4096 blocks=1114112, rtextents=1114112 = rgcount=17 rgsize=65536 extents = zoned=1 start=131072 reserved=53248 IOWS: 512M data volume, 3.1G internal rt section. Now let's try some media verification: # xfs_io -c 'verifymedia -d' -c 'verifymedia -r' /mnt verified 536870912/536870912 bytes at offset 0 512 MiB, 1 ops; 0.0496 sec (10.067 GiB/sec and 20.1345 ops/sec) verified 536870912/536870912 bytes at offset 0 512 MiB, 1 ops; 0.0409 sec (12.222 GiB/sec and 24.4439 ops/sec) Notice how xfs_io says we only verified 512M of the rt volume? If you run btrace in the background you'll see that we read the first 512M of the volume (aka the data section) twice and never read anything from the rt section. An earlier fix tried messing with the buftarg geometry, but I've decided on a more targetted fix for the media verification code. All we have to do is calculate the starting and ending daddr for the device that we're verifying, and clamp the user's input values to that range. This leads to some bogosity in the output reporting: # xfs_io -c 'verifymedia -d' -c 'verifymedia -r' /mnt/t verified 536870912/536870912 bytes at offset 0 512 MiB, 1 ops; 0.0606 sec (8.248 GiB/sec and 16.4968 ops/sec) verified 5100273664/5100273664 bytes at offset 0 4.750 GiB, 1 ops; 0.3329 sec (14.267 GiB/sec and 3.0035 ops/sec) Because we don't have a way to report that we didn't really do anything at all for that first 512M of address space of the rt "device". But at least we're no longer ignoring real media. (Note that the fsmap/bmap/fiemap calls all report physical addresses for the internal rt volume as offsets from the start of the data device, and the media verifier call consumes the same. We baked that into the user-visible behavior in 6.15, so we're stuck with that sparse hole at the beginning.) Cc: stable@vger.kernel.org # v6.15 Fixes: bdc03eb5f98f6f ("xfs: allow internal RT devices for zoned mode") Signed-off-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_verify_media.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/fs/xfs/xfs_verify_media.c b/fs/xfs/xfs_verify_media.c index 5ead3976d511..b75c81f8fcc0 100644 --- a/fs/xfs/xfs_verify_media.c +++ b/fs/xfs/xfs_verify_media.c @@ -268,6 +268,8 @@ xfs_verify_media( struct xfs_buftarg *btp = NULL; struct bio *bio; struct folio *folio; + xfs_daddr_t dev_start = 0; + xfs_daddr_t dev_end = 0; xfs_daddr_t daddr; uint64_t bbcount; int error = 0; @@ -277,24 +279,33 @@ xfs_verify_media( switch (me->me_dev) { case XFS_DEV_DATA: btp = mp->m_ddev_targp; + dev_end = XFS_FSB_TO_BB(mp, mp->m_sb.sb_dblocks); break; case XFS_DEV_LOG: - if (mp->m_logdev_targp != mp->m_ddev_targp) + if (mp->m_logdev_targp != mp->m_ddev_targp) { btp = mp->m_logdev_targp; + dev_end = XFS_FSB_TO_BB(mp, mp->m_sb.sb_logblocks); + } break; case XFS_DEV_RT: btp = mp->m_rtdev_targp; + dev_start = XFS_FSB_TO_BB(mp, mp->m_sb.sb_rtstart); + dev_end = XFS_FSB_TO_BB(mp, mp->m_sb.sb_rtstart + + mp->m_sb.sb_rblocks); break; } if (!btp) return -ENODEV; /* - * If the caller told us to verify beyond the end of the disk, tell the - * user exactly where that was. + * If the caller told us to verify before the start or beyond the end + * of the disk volume, tell the user exactly where the volume starts + * and ends. */ - if (me->me_end_daddr > btp->bt_nr_sectors) - me->me_end_daddr = btp->bt_nr_sectors; + if (me->me_end_daddr > dev_end) + me->me_end_daddr = dev_end; + if (me->me_start_daddr < dev_start) + me->me_start_daddr = dev_start; /* start and end have to be aligned to the lba size */ if (!IS_ALIGNED(BBTOB(me->me_start_daddr | me->me_end_daddr), @@ -323,8 +334,7 @@ xfs_verify_media( * verifying. */ daddr = me->me_start_daddr; - bbcount = min_t(sector_t, me->me_end_daddr, btp->bt_nr_sectors) - - me->me_start_daddr; + bbcount = me->me_end_daddr - me->me_start_daddr; folio = xfs_verify_alloc_folio(xfs_verify_iosize(me, btp, bbcount)); if (!folio) From ff99a5f6cbcc9c4810a8dac46fe76473539513f9 Mon Sep 17 00:00:00 2001 From: Hans Holmberg Date: Wed, 26 Aug 2026 14:32:19 +0200 Subject: [PATCH 0354/1317] xfs: prevent race in zoned space reservations xfs_zoned_add_available() checks whether the reservation list is empty before adding blocks to the available-space counter. This check is not serialized against a task adding itself to the reservation list however. This allows the space provider to observe an empty list, after which a reserver can enqueue itself and retry the counter before the new space is added. The provider then adds the space and returns without waking the now-eligible reserver, leaving it asleep until GC or another event provides a wakeup, potentially adding seconds to max write latency. Take the reservation lock before updating the counter and checking the list. Use list_empty() because the list is now inspected under its lock. Taking a per-mount lock when handing back space is far from ideal, but benchmarking with null_blk showed no measurable performance regression. Fixes: 0bb2193056b5 ("xfs: add support for zoned space reservations") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260609075655.1698743-1-hch@lst.de?part=2 Signed-off-by: Hans Holmberg Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_space_resv.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_zone_space_resv.c b/fs/xfs/xfs_zone_space_resv.c index 5c6e6ef627e4..7aa3c74fb2e0 100644 --- a/fs/xfs/xfs_zone_space_resv.c +++ b/fs/xfs/xfs_zone_space_resv.c @@ -85,13 +85,13 @@ xfs_zoned_add_available( struct xfs_zone_info *zi = mp->m_zone_info; struct xfs_zone_reservation *reservation; - if (list_empty_careful(&zi->zi_reclaim_reservations)) { - xfs_add_freecounter(mp, XC_FREE_RTAVAILABLE, count_fsb); + spin_lock(&zi->zi_reservation_lock); + xfs_add_freecounter(mp, XC_FREE_RTAVAILABLE, count_fsb); + if (list_empty(&zi->zi_reclaim_reservations)) { + spin_unlock(&zi->zi_reservation_lock); return; } - spin_lock(&zi->zi_reservation_lock); - xfs_add_freecounter(mp, XC_FREE_RTAVAILABLE, count_fsb); count_fsb = xfs_sum_freecounter(mp, XC_FREE_RTAVAILABLE); list_for_each_entry(reservation, &zi->zi_reclaim_reservations, entry) { if (reservation->count_fsb > count_fsb) From a23eca88448e52eb1a81549862df7adce794fafb Mon Sep 17 00:00:00 2001 From: Lin Jiapeng Date: Tue, 28 Jul 2026 15:19:10 +0800 Subject: [PATCH 0355/1317] xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN When exchanging two full-file ranges, xmi_can_exchange_reflink_flags() can move the reflink inode flag from the file that currently has it to the other file, as long as exactly one side is marked. This assumes that the file contents, and therefore all shared extents, are exchanged. That assumption is not true when XFS_EXCHMAPS_INO1_WRITTEN is set. xfs_exchmaps_can_skip_mapping() can skip hole and unwritten mappings from file1, so an exchange can complete without moving every mapping that the earlier flag-swap decision accounted for. In that case the post-operation cleanup can clear the reflink flag from an inode that still owns shared written extents. Later writes then take the non-reflink write path and may update blocks that should still have been protected by CoW, which shows up as data corruption between reflink-related files. Fix this by disabling the reflink flag exchange whenever XFS_EXCHMAPS_INO1_WRITTEN is requested. The contents exchange can still proceed; the conservative outcome is that both inodes keep the reflink flag. The regular reflink flag cleanup path can drop the extra flag later once the inode no longer has shared extents. Reported-by: Lin Jiapeng (TencentOS Red Team) Fixes: 966ceafc7a43 ("xfs: create deferred log items for file mapping exchanges") Cc: stable@vger.kernel.org # v6.10 Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Lin Jiapeng Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 3efed37cb98a..49eda8d0994d 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -959,6 +959,16 @@ xmi_can_exchange_reflink_flags( { struct xfs_mount *mp = req->ip1->i_mount; + /* + * The INO1_WRITTEN optimization can skip exchanging hole and + * unwritten mappings, which means we cannot guarantee that all + * shared extents actually moved to the other file. Clearing the + * reflink flag of an inode that still holds shared extents breaks + * the CoW write path, so refuse to exchange the flags in that case. + */ + if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) + return false; + /* * The INO1_WRITTEN optimization can skip exchanging hole and * unwritten mappings, which means we cannot guarantee that all From cf3a01684f323dd905d83230b7cb705fabdbcb7b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:38 +0200 Subject: [PATCH 0356/1317] xfs: fix the lock annotation on xfs_iget_cache_hit The newer clang context analysis requires __releases_shared for the RCU pseudo-lock. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_icache.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index a857b8aa255c..82dac88e3c4c 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -497,7 +497,8 @@ xfs_iget_cache_hit( struct xfs_inode *ip, xfs_ino_t ino, int flags, - int lock_flags) __releases(RCU) + int lock_flags) + __releases_shared(RCU) { struct inode *inode = VFS_I(ip); struct xfs_mount *mp = ip->i_mount; From 2078c2d6e1748df6c4c2b28f06797cba4ecda85c Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:39 +0200 Subject: [PATCH 0357/1317] xfs: fix the lock annotation in xfs_extent_busy_update_extent Name the correct lock. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_extent_busy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_extent_busy.c b/fs/xfs/xfs_extent_busy.c index 41cf0605ec22..6da8c1f938aa 100644 --- a/fs/xfs/xfs_extent_busy.c +++ b/fs/xfs/xfs_extent_busy.c @@ -161,8 +161,8 @@ xfs_extent_busy_update_extent( xfs_agblock_t fbno, xfs_extlen_t flen, bool userdata) - __releases(&eb->eb_lock) - __acquires(&eb->eb_lock) + __releases(&xg->xg_busy_extents->eb_lock) + __acquires(&xg->xg_busy_extents->eb_lock) { struct xfs_extent_busy_tree *eb = xg->xg_busy_extents; xfs_agblock_t fend = fbno + flen; From b2ae7f243583e795b1eb3f8977cccfd7e37a8dfc Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:40 +0200 Subject: [PATCH 0358/1317] xfs: fix the lock annotation in xfs_mru_cache_lookup Name the actual lock. Unlike sparse, clang wants the annotation to be correct. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_mru_cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_mru_cache.c b/fs/xfs/xfs_mru_cache.c index d61ec8cb126d..3f3af2e2e31c 100644 --- a/fs/xfs/xfs_mru_cache.c +++ b/fs/xfs/xfs_mru_cache.c @@ -520,7 +520,7 @@ xfs_mru_cache_lookup( if (elem) { list_del(&elem->list_node); _xfs_mru_cache_list_insert(mru, elem); - __release(mru_lock); /* help sparse not be stupid */ + __release(&mru->lock); } else spin_unlock(&mru->lock); From 64e1f211d97b42f6e12bef8e6decc207f8871a2d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:41 +0200 Subject: [PATCH 0359/1317] xfs: improve lock annotations in the log code Improve the __acquires and __releases annotations so that the new clang code that is a bit more picky than sparse is happy. This involves passing an explicit struct xlog argument in a few places because alias analysis can't figure out it is the same lock when dereferencing changing iclogs. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log.c | 31 +++++++++++++++++++------------ fs/xfs/xfs_log_cil.c | 2 +- fs/xfs/xfs_log_priv.h | 4 ++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c index f807f8f4f705..0294ac277f35 100644 --- a/fs/xfs/xfs_log.c +++ b/fs/xfs/xfs_log.c @@ -470,6 +470,8 @@ xlog_state_release_iclog( struct xlog *log, struct xlog_in_core *iclog, struct xlog_ticket *ticket) + __releases(&log->l_icloglock) + __acquires(&log->l_icloglock) { bool last_ref; @@ -744,13 +746,16 @@ xfs_log_mount_cancel( */ static inline int xlog_force_iclog( + struct xlog *log, struct xlog_in_core *iclog) + __releases(&log->l_icloglock) + __acquires(&log->l_icloglock) { atomic_inc(&iclog->ic_refcnt); iclog->ic_flags |= XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FUA; if (iclog->ic_state == XLOG_STATE_ACTIVE) - xlog_state_switch_iclogs(iclog->ic_log, iclog, 0); - return xlog_state_release_iclog(iclog->ic_log, iclog, NULL); + xlog_state_switch_iclogs(log, iclog, 0); + return xlog_state_release_iclog(log, iclog, NULL); } /* @@ -778,11 +783,10 @@ xlog_wait_iclog_completion(struct xlog *log) */ int xlog_wait_on_iclog( + struct xlog *log, struct xlog_in_core *iclog) - __releases(iclog->ic_log->l_icloglock) + __releases(log->l_icloglock) { - struct xlog *log = iclog->ic_log; - trace_xlog_iclog_wait_on(iclog, _RET_IP_); if (!xlog_is_shutdown(log) && iclog->ic_state != XLOG_STATE_ACTIVE && @@ -879,8 +883,8 @@ xlog_unmount_write( spin_lock(&log->l_icloglock); iclog = log->l_iclog; - error = xlog_force_iclog(iclog); - xlog_wait_on_iclog(iclog); + error = xlog_force_iclog(log, iclog); + xlog_wait_on_iclog(log, iclog); if (tic) { trace_xfs_log_umount_write(log, tic); @@ -2741,14 +2745,17 @@ xlog_state_switch_iclogs( */ static int xlog_force_and_check_iclog( + struct xlog *log, struct xlog_in_core *iclog, bool *completed) + __releases(&log->l_icloglock) + __acquires(&log->l_icloglock) { xfs_lsn_t lsn = be64_to_cpu(iclog->ic_header->h_lsn); int error; *completed = false; - error = xlog_force_iclog(iclog); + error = xlog_force_iclog(log, iclog); if (error) return error; @@ -2825,7 +2832,7 @@ xfs_log_force( /* We have exclusive access to this iclog. */ bool completed; - if (xlog_force_and_check_iclog(iclog, &completed)) + if (xlog_force_and_check_iclog(log, iclog, &completed)) goto out_error; if (completed) @@ -2850,7 +2857,7 @@ xfs_log_force( iclog->ic_flags |= XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FUA; if (flags & XFS_LOG_SYNC) - return xlog_wait_on_iclog(iclog); + return xlog_wait_on_iclog(log, iclog); out_unlock: spin_unlock(&log->l_icloglock); return 0; @@ -2920,7 +2927,7 @@ xlog_force_lsn( &log->l_icloglock); return -EAGAIN; } - if (xlog_force_and_check_iclog(iclog, &completed)) + if (xlog_force_and_check_iclog(log, iclog, &completed)) goto out_error; if (log_flushed) *log_flushed = 1; @@ -2948,7 +2955,7 @@ xlog_force_lsn( } if (flags & XFS_LOG_SYNC) - return xlog_wait_on_iclog(iclog); + return xlog_wait_on_iclog(log, iclog); out_unlock: spin_unlock(&log->l_icloglock); return 0; diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index 639f875a8fb2..ae1ed16aeb2f 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1556,7 +1556,7 @@ xlog_cil_push_work( * iclogs older than ic_prev. Hence we only need to wait * on the most recent older iclog here. */ - xlog_wait_on_iclog(ctx->commit_iclog->ic_prev); + xlog_wait_on_iclog(log, ctx->commit_iclog->ic_prev); spin_lock(&log->l_icloglock); } diff --git a/fs/xfs/xfs_log_priv.h b/fs/xfs/xfs_log_priv.h index cf1e4ce61a8c..6d9673c41cdf 100644 --- a/fs/xfs/xfs_log_priv.h +++ b/fs/xfs/xfs_log_priv.h @@ -605,8 +605,8 @@ xlog_wait( remove_wait_queue(wq, &wait); } -int xlog_wait_on_iclog(struct xlog_in_core *iclog) - __releases(iclog->ic_log->l_icloglock); +int xlog_wait_on_iclog(struct xlog *log, struct xlog_in_core *iclog) + __releases(log->l_icloglock); /* Calculate the distance between two LSNs in bytes */ static inline uint64_t From 912a5b8e344ad1e3cb6aefedf243404d522ad822 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:42 +0200 Subject: [PATCH 0360/1317] xfs: add lock annotations to xfs_try_open_zone Improve the __acquires and __releases annotations so that the new clang code that is a bit more picky than sparse is happy. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_alloc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index bdbb60cc5d5b..28c1e48909fa 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -475,6 +475,8 @@ static struct xfs_open_zone * xfs_try_open_zone( struct xfs_mount *mp, enum rw_hint write_hint) + __releases(&mp->m_zone_info->zi_open_zones_lock) + __acquires(&mp->m_zone_info->zi_open_zones_lock) { struct xfs_zone_info *zi = mp->m_zone_info; struct xfs_open_zone *oz; From 2d1b21fcc9f9463f939103d13d6a0bf05a529901 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:43 +0200 Subject: [PATCH 0361/1317] xfs: add lock annotations to xlog_state_shutdown_callbacks Sparse used to get away without these despite dropping and reacquiring l_icloglock Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c index 0294ac277f35..2a34611d81f6 100644 --- a/fs/xfs/xfs_log.c +++ b/fs/xfs/xfs_log.c @@ -422,6 +422,8 @@ xfs_log_reserve( static void xlog_state_shutdown_callbacks( struct xlog *log) + __releases(&log->l_icloglock) + __acquires(&log->l_icloglock) { struct xlog_in_core *iclog; LIST_HEAD(cb_list); From 166089a856859d5f3e2b85c6ffd70da084238439 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:44 +0200 Subject: [PATCH 0362/1317] xfs: add a lock annotation to xlog_cil_push_background This is required to make the clang context analysis happy, which is more strict than the old sparse lock context tracking. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_cil.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index ae1ed16aeb2f..166531018ce4 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1627,6 +1627,7 @@ xlog_cil_push_work( static void xlog_cil_push_background( struct xlog *log) + __releases_shared(&log->l_cilp->xc_ctx_lock) { struct xfs_cil *cil = log->l_cilp; int space_used = atomic_read(&cil->xc_ctx->space_used); From ed4abd8617ba348802575a0ae8e01e6c0f7eb2cf Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:45 +0200 Subject: [PATCH 0363/1317] xfs: add lock annotations to xfs_ail_delete* Pass up the __must_hold as clang requires it, and also fix the formatting of the __must_hold on xfs_ail_check to match how we do it elsewhere. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_trans_ail.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_trans_ail.c b/fs/xfs/xfs_trans_ail.c index 99a9bf3762b7..f955479a08fd 100644 --- a/fs/xfs/xfs_trans_ail.c +++ b/fs/xfs/xfs_trans_ail.c @@ -33,7 +33,7 @@ STATIC void xfs_ail_check( struct xfs_ail *ailp, struct xfs_log_item *lip) - __must_hold(&ailp->ail_lock) + __must_hold(&ailp->ail_lock) { struct xfs_log_item *prev_lip; struct xfs_log_item *next_lip; @@ -321,6 +321,7 @@ static void xfs_ail_delete( struct xfs_ail *ailp, struct xfs_log_item *lip) + __must_hold(&ailp->ail_lock) { xfs_ail_check(ailp, lip); list_del(&lip->li_ail); @@ -899,6 +900,7 @@ xfs_lsn_t xfs_ail_delete_one( struct xfs_ail *ailp, struct xfs_log_item *lip) + __must_hold(&ailp->ail_lock) { struct xfs_log_item *mlip = xfs_ail_min(ailp); xfs_lsn_t lsn = lip->li_lsn; From 6176d21d7bd609632c5b7e87a3adb9be29ec1e72 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:13 -0600 Subject: [PATCH 0364/1317] xfs: initialise error in xfs_defer_finish_one() xfs_defer_finish_one() declares error without an initialiser and only assigns it inside the loop over dfp->dfp_work. When that list is empty the loop body never runs, control falls through to the "Done with the dfp, free it" path, and the function returns an indeterminate value. An item-less pending item reaches this through xfs_defer_add_barrier(), which xfs_reap_ag_blocks() uses on any CONFIG_XFS_ONLINE_REPAIR kernel. xfs_defer_finish_noroll() treats any non-EAGAIN return as fatal, so a non-zero stack value turns a successful barrier into a SHUTDOWN_CORRUPT_INCORE in the middle of a repair. Zero is the correct result: reaching the free path means the item loop drained without a non-zero error. Fixes: 3f3cec031099 ("xfs: force small EFIs for reaping btree extents") Cc: stable@vger.kernel.org Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_defer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index 89501e8bd2f8..843c33304441 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -583,7 +583,7 @@ xfs_defer_finish_one( const struct xfs_defer_op_type *ops = dfp->dfp_ops; struct xfs_btree_cur *state = NULL; struct list_head *li, *n; - int error; + int error = 0; trace_xfs_defer_pending_finish(tp->t_mountp, dfp); From 91c15cee394e9958898ef1ed55d9ced4c26e2800 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:14 -0600 Subject: [PATCH 0365/1317] xfs: give the deferred barrier op type a name xfs_barrier_defer_type is the only xfs_defer_op_type with no .name. Every other one carries a short string used for tracing and reporting: attr, bmap, extent_free, agfl_free, rtextent_free, refcount, rtrefcount, rmap, rtrmap and exchmaps. That has been harmless because nothing dereferences the field, but it leaves a NULL in a table where every other entry is populated, so the first caller to print it gets "(null)" in the kernel and undefined behaviour in the userspace libxfs build of this file, where xfs_alert lands in fprintf. xfs_defer_add() already treats a missing member of this table as worth shutting the filesystem down for, so an unpopulated one is out of step with how the file handles its own ops tables. Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_defer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index 843c33304441..75f0d37914d5 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -229,6 +229,7 @@ xfs_defer_barrier_cancel_item( } static const struct xfs_defer_op_type xfs_barrier_defer_type = { + .name = "barrier", .max_items = 1, .create_intent = xfs_defer_barrier_create_intent, .abort_intent = xfs_defer_barrier_abort_intent, From d058f24b163a8d63866ac6bbe5f06b41aabe97d7 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:15 -0600 Subject: [PATCH 0366/1317] xfs: report the error that made deferred work shut down the fs When a deferred operation fails and shuts the filesystem down, xfs_defer_finish_noroll() reports neither the errno nor which operation originated it, so the log cannot tell a transient -ENOSPC from real corruption. Report the operation type, errno and remaining reservation. trace_xfs_defer_finish_error() runs after xfs_force_shutdown(), which BUGs under fs.xfs.panic_mask and so never fires for the first failure; move it ahead of the shutdown and mirror it to xfs_alert() for systems without tracing armed. Capture the op name while the item is live (dfp is freed once its work list drains) and suppress the alert once the fs is already down. Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_defer.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index 75f0d37914d5..3152acdc335d 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -656,6 +656,7 @@ xfs_defer_finish_noroll( struct xfs_trans **tp) { struct xfs_defer_pending *dfp = NULL; + const char *what = "chain"; int error = 0; LIST_HEAD(dop_pending); LIST_HEAD(dop_paused); @@ -705,9 +706,17 @@ xfs_defer_finish_noroll( struct xfs_defer_pending, dfp_list); if (!dfp) break; + what = dfp->dfp_ops->name; error = xfs_defer_finish_one(*tp, dfp); if (error && error != -EAGAIN) goto out_shutdown; + /* + * A finished item is no longer a candidate for a later + * failure. An -EAGAIN one is not finished, so it keeps the + * attribution across the roll that completes it. + */ + if (!error) + what = "chain"; } /* Requeue the paused items in the outgoing transaction. */ @@ -719,8 +728,12 @@ xfs_defer_finish_noroll( out_shutdown: list_splice_tail_init(&dop_paused, &dop_pending); xfs_defer_trans_abort(*tp, &dop_pending); - xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE); trace_xfs_defer_finish_error(*tp, error); + if (!xfs_is_shutdown((*tp)->t_mountp)) + xfs_alert((*tp)->t_mountp, + "deferred %s work failed, error %d, %u blocks reserved", + what, error, (*tp)->t_blk_res); + xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE); xfs_defer_cancel_list((*tp)->t_mountp, &dop_pending); xfs_defer_cancel(*tp); return error; From 6c0fc3cb4e879927b338b4fcb6de6a25c0a67608 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:16 -0600 Subject: [PATCH 0367/1317] xfs: correct the parent pointer space reservation comment The comment on xfs_parent_calc_space_res() claims parent pointers are "always the first attr in an attr tree". They are not: a parent pointer is recorded per dirent, so by the Nth hardlink the attr fork is already in leaf or node format. The reservation is still correct, because XFS_DAENTER_SPACE_RES() covers a split at every level of a maximum-depth attr dabtree whatever format the fork is in, but anyone auditing a shortfall here is led by the comment to look for a bug that is not there. Rewrite the comment to state what actually bounds the result, and record why the double split allowance and the extent-add term differ from xfs_attr_calc_size(). Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_trans_space.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_trans_space.c b/fs/xfs/libxfs/xfs_trans_space.c index 9b8f495c9049..c4cd547033e5 100644 --- a/fs/xfs/libxfs/xfs_trans_space.c +++ b/fs/xfs/libxfs/xfs_trans_space.c @@ -22,8 +22,23 @@ xfs_parent_calc_space_res( unsigned int namelen) { /* - * Parent pointers are always the first attr in an attr tree, and never - * larger than a block + * A parent pointer is recorded per dirent, so an inode with N links + * carries N of them and the attr fork can already be in leaf or node + * format when one is added. That does not affect the reservation: + * XFS_DAENTER_SPACE_RES covers a split at every level of a + * maximum-depth attr dabtree, whatever format the fork is in now. + * + * The name is a dirent name and the value is a struct xfs_parent_rec, + * so the leaf entry is always local and never exceeds 272 bytes. + * Parent pointers require V5, hence a 1k minimum block size, so the + * entry always stays under half a block and this needs none of the + * double split allowance that xfs_attr_calc_size() makes. + * + * The second term hands a byte count to a macro whose parameter counts + * mappings, so it asks for more extent-add allowance than the single + * mapping a parent pointer adds - how much more depends on the block + * size. It over-reserves either way, which is why it is left alone: + * correcting the unit would shrink a reservation that is only generous. */ return XFS_DAENTER_SPACE_RES(mp, XFS_ATTR_FORK) + XFS_NEXTENTADD_SPACE_RES(mp, namelen, XFS_ATTR_FORK); From 8e4ebb6afaa34bd2e8ce52da231003d24111c2d6 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:17 -0600 Subject: [PATCH 0368/1317] xfs: initialise args->total for parent pointer updates xfs_parent_da_args_init() builds an xfs_da_args from a zeroed xfs_parent_args (kmem_cache_zalloc), leaving args->total == 0. xfs_da_grow_inode_int() treats that field as a running block reservation and subtracts from it; because it is an xfs_extlen_t (uint32_t), the first attr-fork growth wraps it to ~0U. That defeats the free-space check in xfs_alloc_space_available(), and when it coincides with an AG that has exactly zero available blocks the allocation is clamped to maxlen 0 and returns -ENOSPC, which xfs_defer_finish_noroll() escalates to a filesystem shutdown. Set args->total the way the log recovery path does (xfs_attri_recover_work(), xfs_attr_item.c:706), in the add and replace paths that can grow the fork. Removals and lookups never grow it, so they leave the field alone, matching that switch. Fixes: b7c62d90c12c ("xfs: parent pointer attribute creation") Cc: stable@vger.kernel.org # v6.10 Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_parent.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_parent.c b/fs/xfs/libxfs/xfs_parent.c index 8d111c9b6527..a2f2f5fa640e 100644 --- a/fs/xfs/libxfs/xfs_parent.c +++ b/fs/xfs/libxfs/xfs_parent.c @@ -193,7 +193,7 @@ xfs_parent_addname( const struct xfs_name *parent_name, struct xfs_inode *child) { - int error; + int error, local; error = xfs_parent_iread_extents(tp, child); if (error) @@ -203,6 +203,10 @@ xfs_parent_addname( xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child, I_INO(child), parent_name); + /* Growing the attr fork needs a real reservation in args->total. */ + ppargs->args.total = xfs_attr_calc_size(&ppargs->args, &local); + ASSERT(local); + return xfs_attr_setname(&ppargs->args, 0); } @@ -239,7 +243,7 @@ xfs_parent_replacename( const struct xfs_name *new_name, struct xfs_inode *child) { - int error; + int error, local; error = xfs_parent_iread_extents(tp, child); if (error) @@ -249,6 +253,10 @@ xfs_parent_replacename( xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child, I_INO(child), old_name); + /* Growing the attr fork needs a real reservation in args->total. */ + ppargs->args.total = xfs_attr_calc_size(&ppargs->args, &local); + ASSERT(local); + xfs_inode_to_parent_rec(&ppargs->new_rec, new_dp); ppargs->args.new_name = new_name->name; From 0fe77e57588b989450d668f7c978bb0264c5c340 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:18 -0600 Subject: [PATCH 0369/1317] xfs: assert the reservation covers each da fork growth xfs_da_grow_inode_int() subtracts the blocks it just allocated from args->total, the caller's remaining block reservation. The subtraction is unsigned, so a caller that reaches it with too small a total wraps the field instead of failing, and every allocation afterwards runs with a bogus reservation. Assert the remaining reservation still covers the step, so an under-reserved or uninitialised total trips in debug builds instead of silently wrapping. Suggested-by: Darrick J. Wong Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_da_btree.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c index 7938d2324e87..8cbdd6574755 100644 --- a/fs/xfs/libxfs/xfs_da_btree.c +++ b/fs/xfs/libxfs/xfs_da_btree.c @@ -2384,6 +2384,7 @@ xfs_da_grow_inode_int( } /* account for newly allocated blocks in reserved blocks total */ + ASSERT(args->total >= dp->i_nblocks - nblks); args->total -= dp->i_nblocks - nblks; out_free_map: From 9f84792b40d0c96833341602144574bf0bd14a6a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:31:10 -0700 Subject: [PATCH 0370/1317] xfs: don't spin forever on zero-length dirents when salvaging them LOLLM noticed that xrep_dir_recover_data can spin forever if it encounters an unused dirent that claims to have length zero. Fix that, and prevent the same thing from happening with a zero-length entry. Cc: stable@vger.kernel.org # v6.10 Fixes: b1991ee3e7cf85 ("xfs: online repair of directories") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir_repair.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 1c088cfba10e..0c1224d05d57 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -484,18 +484,24 @@ xrep_dir_recover_data( while (offset < end) { struct xfs_dir2_data_unused *dup = bp->b_addr + offset; struct xfs_dir2_data_entry *dep = bp->b_addr + offset; + unsigned int advance; if (xchk_should_terminate(rd->sc, &error)) return error; /* Skip unused entries. */ if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { + if (!dup->length) + break; offset += be16_to_cpu(dup->length); continue; } /* Don't walk off the end of the block. */ - offset += xfs_dir2_data_entsize(rd->sc->mp, dep->namelen); + advance = xfs_dir2_data_entsize(rd->sc->mp, dep->namelen); + if (!advance) + break; + offset += advance; if (offset > end) break; From 865b751e75039fc07838b3200f9740256653da9a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:31:25 -0700 Subject: [PATCH 0371/1317] xfs: don't stash removename operations with unknown ftype LOLLM notices that the behavior of xrep_dir_replay_update changes based on the ftype recorded in the stashed removename information. It also notices that the unlink iops sometimes set that ftype to FT_UNKNOWN because the regular directory tree update code paths don't need to know the ftype of the child. Unfortunately, this results in incorrect link counts, which eventually trips link count errors in later phases of xfs_scrub, or in xfs_repair. Fix this by creating a second xfs_name with the type set correctly. Cc: stable@vger.kernel.org # v6.10 Fixes: 8559b21a64d983 ("xfs: implement live updates for directory repairs") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir_repair.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 0c1224d05d57..31a23c5f386a 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -1381,9 +1381,24 @@ xrep_dir_live_update( if (p->delta > 0) error = xrep_dir_stash_createname(rd, p->name, I_INO(p->ip)); - else - error = xrep_dir_stash_removename(rd, p->name, + else { + /* + * xfs_dentry_to_name in unlink or rename-exchange can + * pass us names with ftype FT_UNKNOWN, but we really + * must know the ftype of the child that is being + * removed so that we can do nlink updates correctly + * without holding inode references. + */ + struct xfs_name name = { + .name = p->name->name, + .len = p->name->len, + .type = xfs_mode_to_ftype( + VFS_IC(p->ip)->i_mode), + }; + + error = xrep_dir_stash_removename(rd, &name, I_INO(p->ip)); + } mutex_unlock(&rd->pscan.lock); if (error) goto out_abort; From ed799148e0d63ef41ab60ce98963a1dc423090d3 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:31:41 -0700 Subject: [PATCH 0372/1317] xfs: log the tempip after we convert it to extents format LOLLM points out that xrep_symlink_swap_prep converts sc->tempip to an extents format file prior to the atomic swap, but incorrectly logs sc->ip immediately afterwards. Fix that, and the other problem that we're supposed to tell xfs_trans_log_inode what to log and don't. Cc: stable@vger.kernel.org # v6.10 Fixes: 2651923d8d8db0 ("xfs: online repair of symbolic links") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/symlink_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/symlink_repair.c b/fs/xfs/scrub/symlink_repair.c index 91c86ea0e0f1..181961364233 100644 --- a/fs/xfs/scrub/symlink_repair.c +++ b/fs/xfs/scrub/symlink_repair.c @@ -291,7 +291,7 @@ xrep_symlink_swap_prep( if (error) return error; - xfs_trans_log_inode(sc->tp, sc->ip, 0); + xfs_trans_log_inode(sc->tp, sc->tempip, logflags); error = xfs_defer_finish(&sc->tp); if (error) From cdc4a083adf7bf15a0722c5bd292a35e00112006 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:31:56 -0700 Subject: [PATCH 0373/1317] xfs: fix parent rec lookup initialization in xrep_metapath_unlink LOLLM notices that xrep_metapath_unlink looks for a parent pointer in the child metafile that it's removing, but initializes the parent handle using the child. This is obviously incorrect, so fix that. Cc: stable@vger.kernel.org # v6.13 Fixes: 0d2c636e489c11 ("xfs: repair metadata directory file path connectivity") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index ff1ff762b300..9f44c82910ea 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -397,7 +397,7 @@ xrep_metapath_unlink( /* Figure out if we're removing a parent pointer too. */ if (xfs_has_parent(mp)) { - xfs_inode_to_parent_rec(&rec, ip); + xfs_inode_to_parent_rec(&rec, mpath->dp); error = xfs_parent_lookup(sc->tp, ip, &mpath->xname, &rec, &mpath->pptr_args); switch (error) { From 9dd6cc92a736a7943dd29776b6eb1938c7f9b94c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:32:12 -0700 Subject: [PATCH 0374/1317] xfs: handle reconnecting metadir subdirectories A longstanding weakness of the metapath repair code is that it can only reattach non-directories to the metadata directory tree. Let's fix that by allowing reconnection of subdirectories. Note that with the initial users of metadir (rtgroups and quota), there's no way to mount a filesystem with broken /rtgroups or /quota subdirectories, so this code won't be all that useful until something adds deeper directory trees. But we shouldn't leave a logic bomb for those futures users wherein we get the link count wrong for a subdir. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 83 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index 9f44c82910ea..a760523f40e6 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -23,6 +23,7 @@ #include "xfs_rtgroup.h" #include "xfs_rtrmap_btree.h" #include "xfs_rtrefcount_btree.h" +#include "xfs_ag.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/trace.h" @@ -348,12 +349,78 @@ xchk_metapath( } #ifdef CONFIG_XFS_ONLINE_REPAIR +/* + * Given a directory @dp, an existing inode @ip, and a @name, link @ip into @dp + * under the given @name. + */ +static int +xrep_metadir_add_child( + struct xchk_metapath *mpath, + xfs_ino_t old_dotdot) +{ + struct xfs_trans *tp = mpath->sc->tp; + struct xfs_dir_update *du = &mpath->du; + struct xfs_inode *dp = du->dp; + const struct xfs_name *name = du->name; + struct xfs_inode *ip = du->ip; + struct xfs_mount *mp = tp->t_mountp; + const unsigned int resblks = mpath->link_resblks; + int error; + + /* + * The metadata file shouldn't be on the unlinked list, but we'll fix + * it if that is the case. + */ + if (VFS_I(ip)->i_nlink == 0) { + struct xfs_perag *pag; + + pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, I_INO(ip))); + error = xfs_iunlink_remove(tp, pag, ip); + xfs_perag_put(pag); + if (error) + return error; + } + + error = xfs_dir_createname(tp, dp, name, I_INO(ip), resblks); + if (error) + return error; + + xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE); + + xfs_bumplink(tp, ip); + + /* update dotdot entry in child */ + if (S_ISDIR(VFS_I(ip)->i_mode)) { + xfs_bumplink(tp, dp); + + /* Replace the dotdot entry in the child */ + if (old_dotdot != I_INO(dp)) { + error = xfs_dir_replace(tp, ip, &xfs_name_dotdot, + I_INO(dp), resblks); + if (error) + return error; + } + } + + /* Update the child's parent pointer */ + if (du->ppargs) { + error = xfs_parent_addname(tp, du->ppargs, dp, name, ip); + if (error) + return error; + } + + xfs_dir_update_hook(dp, ip, 1, name); + return 0; +} + /* Create the dirent represented by the final component of the path. */ STATIC int xrep_metapath_link( struct xchk_metapath *mpath) { struct xfs_scrub *sc = mpath->sc; + xfs_ino_t old_dotdot = NULLFSINO; + int error; mpath->du.dp = mpath->dp; mpath->du.name = &mpath->xname; @@ -366,7 +433,21 @@ xrep_metapath_link( trace_xrep_metapath_link(sc, mpath->path, mpath->dp, I_INO(sc->ip)); - return xfs_dir_add_child(sc->tp, mpath->link_resblks, &mpath->du); + if (S_ISDIR(VFS_I(sc->ip)->i_mode)) { + error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, + &old_dotdot); + if (error && error != -ENOENT) + return error; + + /* + * subdir didn't give us a dotdot entry, so we just give up + * and let the repair get marked as failed. + */ + if (old_dotdot == NULLFSINO) + return 0; + } + + return xrep_metadir_add_child(mpath, old_dotdot); } /* Remove the dirent at the final component of the path. */ From 7538ba528cfd6f176076186c1b1678fdca1c197b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:32:28 -0700 Subject: [PATCH 0375/1317] xfs: lock the healthmon when inserting unmount event LOLLM complains that xfs_healthmon_unmount does an unlocked insert of the unmount event into the health monitor's event list. Fix that. Cc: stable@vger.kernel.org # v7.0 Fixes: 25ca57fa3624ca ("xfs: convey filesystem unmount events to the health monitor") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 4521ffdab9f1..3ae5f4496ad1 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -272,6 +272,8 @@ __xfs_healthmon_insert( { struct timespec64 now; + lockdep_assert_held(&hm->lock); + ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; @@ -294,6 +296,8 @@ __xfs_healthmon_push( { struct timespec64 now; + lockdep_assert_held(&hm->lock); + ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; @@ -415,8 +419,10 @@ xfs_healthmon_unmount( * There's nothing actionable for userspace after an unmount. Once * we've inserted the unmount event, hm no longer owns that event. */ + mutex_lock(&hm->lock); __xfs_healthmon_insert(hm, hm->unmount_event); hm->unmount_event = NULL; + mutex_unlock(&hm->lock); xfs_healthmon_detach(hm); xfs_healthmon_put(hm); From 4164b1e3d728c7cc05e0c1171068aa046542ecc3 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Fri, 21 Aug 2026 17:03:37 -0500 Subject: [PATCH 0376/1317] xfs: fix reclaimed page accounting in xfs_buf_free To obtain nr. of pages in "size" bytes, we need howmany(size, PAGE_SIZE) not howmany(size, PAGE_SHIFT). This over-reports reclaim by orders of magnitude, up to 4096x on a 64k page system. Fixes: e2874632a621 ("xfs: use vmalloc instead of vm_map_area for buffer backing memory") Cc: stable@vger.kernel.org # v6.15+ Signed-off-by: Eric Sandeen Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index ee7c2e9c0340..836515e8feee 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -139,7 +139,7 @@ xfs_buf_free( ASSERT(list_empty(&bp->b_lru)); if (!xfs_buftarg_is_mem(bp->b_target) && size >= PAGE_SIZE) - mm_account_reclaimed_pages(howmany(size, PAGE_SHIFT)); + mm_account_reclaimed_pages(howmany(size, PAGE_SIZE)); if (is_vmalloc_addr(bp->b_addr)) vfree(bp->b_addr); From 5344402f2c65984cee01a053fd1fa92c44b6aa7d Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Fri, 21 Aug 2026 17:26:39 -0500 Subject: [PATCH 0377/1317] xfs: mark slab-allocated xfs_buf backing memory as __GFP_RECLAIMABLE xfs_bufs have a shrinker and are therefore reclaimable, as is the memory backing them. Mark slab-allocated backing memory as __GFP_RECLAIMABLE in the kmalloc path so that it is accounted properly. Signed-off-by: Eric Sandeen Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 836515e8feee..8256c1d13ce2 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -176,7 +176,7 @@ xfs_buf_alloc_kmem( ASSERT(is_power_of_2(size)); ASSERT(size < PAGE_SIZE); - bp->b_addr = kmalloc(size, gfp_mask); + bp->b_addr = kmalloc(size, gfp_mask | __GFP_RECLAIMABLE); if (!bp->b_addr) return -ENOMEM; From 48d2b8351bae6d40b44f544fe5540868a55872b2 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Tue, 1 Sep 2026 11:13:48 +0530 Subject: [PATCH 0378/1317] xfs: release alleged child inode on metapath unlink error If xchk_metapath_ilock_parent_and_child() fails after xchk_iget() succeeds, release the inode reference before returning. Fixes: 0d2c636e489c ("xfs: repair metadata directory file path connectivity") Cc: stable@vger.kernel.org # v6.13 Signed-off-by: Anuj Gupta Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index a760523f40e6..e0ee7d9b903f 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -637,6 +637,8 @@ xrep_metapath_try_unlink( error = xchk_metapath_ilock_parent_and_child(mpath, ip); if (error) { xchk_trans_cancel(sc); + if (ip) + xchk_irele(sc, ip); return error; } xfs_trans_ijoin(sc->tp, mpath->dp, 0); From 05cff7c2b79f76c7cfe90613a60e16aaaa051ef7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:41:59 -0700 Subject: [PATCH 0379/1317] xfs: fix unit conversions in per_binval computation LOLLM noticed that we're doing the unit conversion in the per_binval computation backwards -- xfs_buf_inval_log_space's second parameter is supposed to be in bytes, but max_binval is in units of fsblocks. Hence the conversion should be FSB -> B, not the other way around. Cc: stable@vger.kernel.org # v6.18 Fixes: b2311ec6778fcd ("xfs: compute per-AG extent reap limits dynamically") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/reap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index fcd14c1703ea..496c6eab555e 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -601,7 +601,7 @@ xreap_configure_agextent_limits( /* Maximum overhead of invalidating one buffer. */ const unsigned int per_binval = - xfs_buf_inval_log_space(1, XFS_B_TO_FSBT(mp, max_binval)); + xfs_buf_inval_log_space(1, XFS_FSB_TO_B(mp, max_binval)); /* * For each transaction in a reap chain, we can delete some number of @@ -680,7 +680,7 @@ xreap_configure_agcow_limits( /* Overhead of invalidating one buffer */ const unsigned int per_binval = - xfs_buf_inval_log_space(1, XFS_B_TO_FSBT(mp, max_binval)); + xfs_buf_inval_log_space(1, XFS_FSB_TO_B(mp, max_binval)); /* * For each transaction in a reap chain, we can delete some number of From eacb8479507756c3305994e87fb2fb1183827e98 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:42:15 -0700 Subject: [PATCH 0380/1317] xfs: fix short ifork reaping computation in xreap_bmapi_binval LOLLM got really confused about the update to imap->br_blockcount in xreap_bmapi_binval if xreap_inc_binval returns false. The intent of this code is that we shorten the imap to whatever length of space we invalidated so that the next iteration through the loop will start wherever we left off. Unfortunately, the calculation sets br_blockcount to the amount of *unfinished* work, which means that we pointlessly re-scan blocks that we already reaped. This is benign, but we should fix the computation anyway. Cc: stable@vger.kernel.org # v6.10 Fixes: 5befb047b9f4de ("xfs: add the ability to reap entire inode forks") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/reap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index 496c6eab555e..f698b9be3dd1 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -1399,7 +1399,7 @@ xreap_bmapi_binval( * far we've gotten. */ if (!xreap_inc_binval(rs)) { - imap->br_blockcount = agbno_next - bno; + imap->br_blockcount = bno - agbno; goto out; } } From 72d0a3e4405c1353869bfbbc67c7b8a29df7afd0 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:42:31 -0700 Subject: [PATCH 0381/1317] xfs: fix name string recording in slowpath pptr tracepoints LOLLM observes that we memcpy from the xfs_name object, not the name string pointed to by the xfs_name. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: b961c8bf1fc3d0 ("xfs: deferred scrub of dirents") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/trace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 14aa0ec1f09e..362c6d39e9f5 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -1640,7 +1640,7 @@ DECLARE_EVENT_CLASS(xchk_pptr_class, __entry->dev = ip->i_mount->m_super->s_dev; __entry->ino = I_INO(ip); __entry->namelen = name->len; - memcpy(__get_str(name), name, name->len); + memcpy(__get_str(name), name->name, name->len); __entry->far_ino = far_ino; ), TP_printk("dev %d:%d ino 0x%llx name '%.*s' far_ino 0x%llx", From 2eac8d01d2c776fc26b0ac74aebdf91bc4490891 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:42:47 -0700 Subject: [PATCH 0382/1317] xfs: don't leak dqacct if rhashtable insertion fails LOLLM observes that xqcheck_mod_live_ino_dqtrx doesn't free the newly allocated dqa object if rhashtable insertion fails. Fix this leak. Cc: stable@vger.kernel.org # v6.9 Fixes: 200491875ce144 ("xfs: track quota updates during live quotacheck") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quotacheck.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/quotacheck.c b/fs/xfs/scrub/quotacheck.c index c199d128538e..c32030a05440 100644 --- a/fs/xfs/scrub/quotacheck.c +++ b/fs/xfs/scrub/quotacheck.c @@ -263,8 +263,10 @@ xqcheck_mod_live_ino_dqtrx( dqa->tx_id = p->tx_id; error = rhashtable_insert_fast(&xqc->shadow_dquot_acct, &dqa->hash, xqcheck_dqacct_hash_params); - if (error) + if (error) { + kfree(dqa); goto out_abort; + } } /* Find the shadow dqtrx (or an empty slot) here. */ From aa301322f72f82f26e4ba0826018d41388ab9896 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:43:02 -0700 Subject: [PATCH 0383/1317] xfs: fix the rtrmap and rtrefcount _maxlevels_ondisk functions The _maxlevels_ondisk functions are used to compute the size of in-memory btree cursors for each btree type. Unfortunately, LOLLM noticed that the rtrmap and rtrefcount versions of these functions forget to account for the inode root, which means that we could access beyond the end of the cursor given a sufficiently large btree. Fix this. Cc: stable@vger.kernel.org # v6.14 Fixes: 9abe03a0e4f978 ("xfs: introduce realtime refcount btree ondisk definitions") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 7 +++++-- fs/xfs/libxfs/xfs_rtrmap_btree.c | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index 22acc1411aac..e2950dbe2068 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -489,8 +489,11 @@ xfs_rtrefcountbt_maxlevels_ondisk(void) minrecs[0] = xfs_rtrefcountbt_block_maxrecs(blocklen, true) / 2; minrecs[1] = xfs_rtrefcountbt_block_maxrecs(blocklen, false) / 2; - /* We need at most one record for every block in an rt group. */ - return xfs_btree_compute_maxlevels(minrecs, XFS_MAX_RGBLOCKS); + /* + * We need at most one record for every block in an rt group, and + * one extra level for the inode root. + */ + return xfs_btree_compute_maxlevels(minrecs, XFS_MAX_RGBLOCKS) + 1; } int __init diff --git a/fs/xfs/libxfs/xfs_rtrmap_btree.c b/fs/xfs/libxfs/xfs_rtrmap_btree.c index c264bc5651c0..0cb2113d5b40 100644 --- a/fs/xfs/libxfs/xfs_rtrmap_btree.c +++ b/fs/xfs/libxfs/xfs_rtrmap_btree.c @@ -716,10 +716,12 @@ xfs_rtrmapbt_maxlevels_ondisk(void) * happens, which means that we must compute the max height based on * what the btree will look like if it consumes almost all the blocks * in the data device due to maximal sharing factor. + * + * Add one extra level for the inode root. */ max_dblocks = -1U; /* max ag count */ max_dblocks *= XFS_MAX_CRC_AG_BLOCKS; - return xfs_btree_space_to_height(minrecs, max_dblocks); + return xfs_btree_space_to_height(minrecs, max_dblocks) + 1; } int __init From 022d5f5fce7f0b6125d404eb74044e6238aef369 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:43:34 -0700 Subject: [PATCH 0384/1317] xfs: fix xfs_rtrmapbt_mem_cursor for non-rmap filesystems It's possible to construct an in-memory rtrmap btree for filesystems that don't have the rmap feature enabled. The kernel doesn't do this, but xfs_repair will, if asked to reindex a filesystem that has rtreflink enabled but not rtrmap. Therefore, we must create the cursor with enough levels to handle a maximally sized btree possible. Note that the rtrmapbt btree cursor slab creates objects large enough to handle xfs_rtrmap_maxlevels_ondisk() levels, so setting bc_nlevels to the same value isn't costing us any extra memory. Cc: stable@vger.kernel.org # v6.14 Fixes: 4a61f12eb11958 ("xfs: create a shadow rmap btree during realtime rmap repair") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrmap_btree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_rtrmap_btree.c b/fs/xfs/libxfs/xfs_rtrmap_btree.c index 0cb2113d5b40..a15e460a1ec7 100644 --- a/fs/xfs/libxfs/xfs_rtrmap_btree.c +++ b/fs/xfs/libxfs/xfs_rtrmap_btree.c @@ -618,7 +618,7 @@ xfs_rtrmapbt_mem_cursor( struct xfs_btree_cur *cur; cur = xfs_btree_alloc_cursor(mp, tp, &xfs_rtrmapbt_mem_ops, - mp->m_rtrmap_maxlevels, xfs_rtrmapbt_cur_cache); + xfs_rtrmapbt_maxlevels_ondisk(), xfs_rtrmapbt_cur_cache); cur->bc_mem.xfbtree = xfbt; cur->bc_nlevels = xfbt->nlevels; cur->bc_group = xfs_group_hold(rtg_group(rtg)); From 5287e56cba3be4a64bff9f73bce5964fda2590dc Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:43:50 -0700 Subject: [PATCH 0385/1317] xfs: preserve owner on in-memory btree creation LOLLM points out a minor bug where a higher level function creating an in-memory btree is required to pass in an owner number, but the creation function erases that. In-memory btrees are ephemeral so this really doesn't matter except for debugging. But let's fix this papercut. Cc: stable@vger.kernel.org # v6.9 Fixes: a095686a238352 ("xfs: support in-memory btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree_mem.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/libxfs/xfs_btree_mem.c b/fs/xfs/libxfs/xfs_btree_mem.c index 37136a70e56d..1d83a4251cee 100644 --- a/fs/xfs/libxfs/xfs_btree_mem.c +++ b/fs/xfs/libxfs/xfs_btree_mem.c @@ -117,6 +117,7 @@ xfbtree_init( struct xfs_buftarg *btp, const struct xfs_btree_ops *ops) { + unsigned long long owner = xfbt->owner; unsigned int blocklen = xfbtree_rec_bytes(mp, ops); unsigned int keyptr_len; int error; @@ -133,6 +134,7 @@ xfbtree_init( memset(xfbt, 0, sizeof(*xfbt)); xfbt->target = btp; + xfbt->owner = owner; /* Set up min/maxrecs for this btree. */ keyptr_len = ops->key_len + sizeof(__be64); From f1930bc578095409c2dcfca6e4e898b24f0f0de6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:44:05 -0700 Subject: [PATCH 0386/1317] xfs: don't leak new_bp if xfs_btree_bload_drop_buf fails LOLLM observes that in xfs_btree_bload_prep_block, xfs_btree_bload_drop_buf can hit an IO error if writing the delwri buffer list to disk fails. In this case, we fail to release new_bp, which means we lose a locked buffer. Fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: e069d549705e49 ("xfs: constrain dirty buffers while formatting a staged btree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree_staging.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_btree_staging.c b/fs/xfs/libxfs/xfs_btree_staging.c index 7314dab4bcfb..561fd2c2e950 100644 --- a/fs/xfs/libxfs/xfs_btree_staging.c +++ b/fs/xfs/libxfs/xfs_btree_staging.c @@ -336,8 +336,10 @@ xfs_btree_bload_prep_block( xfs_btree_set_sibling(cur, *blockp, &new_ptr, XFS_BB_RIGHTSIB); ret = xfs_btree_bload_drop_buf(bbl, buffers_list, bpp); - if (ret) + if (ret) { + xfs_buf_relse(new_bp); return ret; + } /* Initialize the new btree block. */ xfs_btree_init_block_cur(cur, new_bp, level, nr_this_block); From b71ae66863e4320a3b7313b53bb4d65f1718d58b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:44:21 -0700 Subject: [PATCH 0387/1317] xfs: actually recover intended file sizes in xfs_xmi_item_recover_intent LOLLM points out that xfs_xmi_item_recover_intent doesn't actually restore the isize1 and isize2 fields that were recovered from an unfinished exchmaps log intent item. Instead, xfs_exchmaps_init_intent sets the wrong isize values from the recovered inodes, with the result that the file sizes are not set correctly when item recovery finishes. Fix this by restoring isize[12] from the log item. Cc: stable@vger.kernel.org # v6.10 Fixes: 966ceafc7a4371 ("xfs: create deferred log items for file mapping exchanges") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchmaps_item.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_exchmaps_item.c b/fs/xfs/xfs_exchmaps_item.c index c3745d33e54e..dd5d92ca1010 100644 --- a/fs/xfs/xfs_exchmaps_item.c +++ b/fs/xfs/xfs_exchmaps_item.c @@ -344,7 +344,17 @@ xfs_xmi_validate( if (!xfs_verify_fileext(mp, xlf->xmi_startoff1, xlf->xmi_blockcount)) return false; - return xfs_verify_fileext(mp, xlf->xmi_startoff2, xlf->xmi_blockcount); + if (!xfs_verify_fileext(mp, xlf->xmi_startoff2, xlf->xmi_blockcount)) + return false; + + if (xlf->xmi_flags & XFS_EXCHMAPS_SET_SIZES) { + if ((int64_t)xlf->xmi_isize1 < 0) + return false; + if ((int64_t)xlf->xmi_isize2 < 0) + return false; + } + + return true; } /* @@ -403,6 +413,13 @@ xfs_xmi_item_recover_intent( *ipp1 = ip1; *ipp2 = ip2; xmi = xfs_exchmaps_init_intent(req); + + /* Restore intended file sizes from recovered logged item */ + if (req->flags & XFS_EXCHMAPS_SET_SIZES) { + xmi->xmi_isize1 = xlf->xmi_isize1; + xmi->xmi_isize2 = xlf->xmi_isize2; + } + xfs_defer_add_item(dfp, &xmi->xmi_list); return xmi; From 365fe37e10ea75840165f13322aa8481ea11dfef Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:44:37 -0700 Subject: [PATCH 0388/1317] xfs: don't modify file attributes or poke fsnotify for dry runs I noticed that we shouldn't be removing file privileges when doing a dry run of an exchange-range operation. LOLLM also points out that a dry run shouldn't poke fsnotify because we don't actually change the files. Fix both by gating them on !DRY_RUN. Cc: stable@vger.kernel.org # v6.10 Fixes: 42672471f938cd ("xfs: bind together the front and back ends of the file range exchange code") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchrange.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index 94965a6c2187..c69ecd6a19de 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -504,6 +504,9 @@ xfs_exchange_range_finish( { int error; + if (fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN) + return 0; + error = file_remove_privs(fxr->file1); if (error) return error; @@ -783,9 +786,12 @@ xfs_exchange_range( if (ret) return ret; - fsnotify_modify(fxr->file1); - if (fxr->file2 != fxr->file1) - fsnotify_modify(fxr->file2); + if (!(fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN)) { + fsnotify_modify(fxr->file1); + if (fxr->file2 != fxr->file1) + fsnotify_modify(fxr->file2); + } + return 0; } From c83d1ef97ee3b0b92797d4b4932a3a813ba05b86 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:44:52 -0700 Subject: [PATCH 0389/1317] xfs: fix bnobt repair space reservation disposal failure LOLLM complains that we don't bubble failures from xrep_abt_dispose_one upwards in the callstack. A failure to clean up the space used (or reserved but not used) by the new bnobt/cntbt should be reported. Cc: stable@vger.kernel.org # v6.8 Fixes: 4bdfd7d15747b1 ("xfs: repair free space btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/alloc_repair.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index dce6ab0429dc..95e318e4f3a6 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -571,7 +571,7 @@ xrep_abt_dispose_one( * allocation, and blocks that didn't get used can be freed via the usual * (deferred) means. */ -STATIC void +STATIC int xrep_abt_dispose_reservations( struct xrep_abt *ra, int error) @@ -582,9 +582,13 @@ xrep_abt_dispose_reservations( goto junkit; list_for_each_entry_safe(resv, n, &ra->new_bnobt.resv_list, list) { - error = xrep_abt_dispose_one(ra, resv); - if (error) + int error2 = xrep_abt_dispose_one(ra, resv); + + if (error2) { + if (!error) + error = error2; goto junkit; + } } junkit: @@ -596,6 +600,7 @@ xrep_abt_dispose_reservations( xrep_newbt_cancel(&ra->new_bnobt); xrep_newbt_cancel(&ra->new_cntbt); + return error; } /* Retrieve free space data for bulk load. */ @@ -801,7 +806,9 @@ xrep_abt_build_new_trees( goto err_newbt; /* Dispose of any unused blocks and the accounting information. */ - xrep_abt_dispose_reservations(ra, error); + error = xrep_abt_dispose_reservations(ra, error); + if (error) + return error; return xrep_roll_ag_trans(sc); @@ -812,8 +819,7 @@ xrep_abt_build_new_trees( xfs_btree_del_cursor(cnt_cur, error); xfs_btree_del_cursor(bno_cur, error); err_newbt: - xrep_abt_dispose_reservations(ra, error); - return error; + return xrep_abt_dispose_reservations(ra, error); } /* From 58a0c7578b25b578c16dea7db2493cfa3a08ecc2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:46:41 -0700 Subject: [PATCH 0390/1317] xfs: always set xfs_healthmon::first_event when inserting at front of list LOLLM complains that while __xfs_healthmon_insert is supposed to insert an event at the head of the list, it doesn't do that correctly if the list isn't empty. In that case it *should* make our new event point to the current head, and then make the head point to the new event, but it doesn't actually update the head so we never see the new event. Fix this by always reassigning first_event. A subsequent patch will clean this up to use a standard list_head, but I felt it important to call out the bug fix first. Cc: stable@vger.kernel.org # v7.0 Fixes: b3a289a2a9397b ("xfs: create event queuing, formatting, and discovery infrastructure") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Reviewed-by: Anuj Gupta Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 3ae5f4496ad1..a4efc084a8fc 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -278,8 +278,7 @@ __xfs_healthmon_insert( event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; event->next = hm->first_event; - if (!hm->first_event) - hm->first_event = event; + hm->first_event = event; if (!hm->last_event) hm->last_event = event; xfs_healthmon_bump_events(hm); From 10b1d5fd7189986a0cdd90dde181089b4b2fe40e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:46:56 -0700 Subject: [PATCH 0391/1317] xfs: move healthmon event merge tracepoint Move the tracepoint into the predicate function so that the list conversion in the next patch is easier. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index a4efc084a8fc..acd41a2b3a1c 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -192,7 +192,7 @@ xfs_healthmon_merge_events( case XFS_HEALTHMON_LOST: existing->lostcount += new->lostcount; - return true; + goto out_merge; case XFS_HEALTHMON_SICK: case XFS_HEALTHMON_CORRUPT: @@ -200,19 +200,19 @@ xfs_healthmon_merge_events( switch (existing->domain) { case XFS_HEALTHMON_FS: existing->fsmask |= new->fsmask; - return true; + goto out_merge; case XFS_HEALTHMON_AG: case XFS_HEALTHMON_RTGROUP: if (existing->group == new->group){ existing->grpmask |= new->grpmask; - return true; + goto out_merge; } return false; case XFS_HEALTHMON_INODE: if (existing->ino == new->ino && existing->gen == new->gen) { existing->imask |= new->imask; - return true; + goto out_merge; } return false; default: @@ -224,18 +224,18 @@ xfs_healthmon_merge_events( case XFS_HEALTHMON_SHUTDOWN: /* yes, we can race to shutdown */ existing->flags |= new->flags; - return true; + goto out_merge; case XFS_HEALTHMON_MEDIA_ERROR: /* physically adjacent errors can merge */ if (existing->daddr + existing->bbcount == new->daddr) { existing->bbcount += new->bbcount; - return true; + goto out_merge; } if (new->daddr + new->bbcount == existing->daddr) { existing->daddr = new->daddr; existing->bbcount += new->bbcount; - return true; + goto out_merge; } return false; @@ -250,18 +250,22 @@ xfs_healthmon_merge_events( if (existing->fpos + existing->flen == new->fpos) { existing->flen += new->flen; - return true; + goto out_merge; } if (new->fpos + new->flen == existing->fpos) { existing->fpos = new->fpos; existing->flen += new->flen; - return true; + goto out_merge; } return false; } return false; + +out_merge: + trace_xfs_healthmon_merge(hm, existing); + return true; } /* Insert an event onto the start of the queue. */ @@ -325,7 +329,6 @@ xfs_healthmon_clear_lost_prev( struct xfs_healthmon_event *event = NULL; if (xfs_healthmon_merge_events(hm->last_event, &lost_event)) { - trace_xfs_healthmon_merge(hm, hm->last_event); wake_up(&hm->wait); goto cleared; } @@ -373,7 +376,6 @@ xfs_healthmon_push( /* Try to merge with the newest event */ if (xfs_healthmon_merge_events(hm->last_event, template)) { - trace_xfs_healthmon_merge(hm, hm->last_event); wake_up(&hm->wait); goto out_unlock; } From 9097f5c03f038cea83d174b6d322d7b2678a3c99 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:47:12 -0700 Subject: [PATCH 0392/1317] xfs: port healthmon event list to list_head Simplify the healthmon codebase by porting the single-link event list to a standard list_head. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 36 +++++++++++++++--------------------- fs/xfs/xfs_healthmon.h | 5 ++--- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index acd41a2b3a1c..073b74e490df 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -87,12 +87,10 @@ xfs_healthmon_put( struct xfs_healthmon *hm) { if (refcount_dec_and_test(&hm->ref)) { - struct xfs_healthmon_event *event; - struct xfs_healthmon_event *next = hm->first_event; + struct xfs_healthmon_event *event, *s; - while ((event = next) != NULL) { + list_for_each_entry_safe(event, s, &hm->event_list, entry) { trace_xfs_healthmon_drop(hm, event); - next = event->next; kfree(event); } @@ -173,9 +171,13 @@ static inline void xfs_healthmon_bump_lost(struct xfs_healthmon *hm) */ static bool xfs_healthmon_merge_events( - struct xfs_healthmon_event *existing, + struct xfs_healthmon *hm, const struct xfs_healthmon_event *new) { + struct xfs_healthmon_event *existing = + list_last_entry_or_null(&hm->event_list, struct + xfs_healthmon_event, entry); + if (!existing) return false; @@ -281,10 +283,7 @@ __xfs_healthmon_insert( ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; - event->next = hm->first_event; - hm->first_event = event; - if (!hm->last_event) - hm->last_event = event; + list_add(&event->entry, &hm->event_list); xfs_healthmon_bump_events(hm); wake_up(&hm->wait); @@ -304,12 +303,7 @@ __xfs_healthmon_push( ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; - if (!hm->first_event) - hm->first_event = event; - if (hm->last_event) - hm->last_event->next = event; - hm->last_event = event; - event->next = NULL; + list_add_tail(&event->entry, &hm->event_list); xfs_healthmon_bump_events(hm); wake_up(&hm->wait); @@ -328,7 +322,7 @@ xfs_healthmon_clear_lost_prev( }; struct xfs_healthmon_event *event = NULL; - if (xfs_healthmon_merge_events(hm->last_event, &lost_event)) { + if (xfs_healthmon_merge_events(hm, &lost_event)) { wake_up(&hm->wait); goto cleared; } @@ -375,7 +369,7 @@ xfs_healthmon_push( } /* Try to merge with the newest event */ - if (xfs_healthmon_merge_events(hm->last_event, template)) { + if (xfs_healthmon_merge_events(hm, template)) { wake_up(&hm->wait); goto out_unlock; } @@ -901,11 +895,10 @@ xfs_healthmon_format_pop( return NULL; mutex_lock(&hm->lock); - event = hm->first_event; + event = list_first_entry_or_null(&hm->event_list, + struct xfs_healthmon_event, entry); if (event) { - if (hm->last_event == event) - hm->last_event = NULL; - hm->first_event = event->next; + list_del_init(&event->entry); hm->events--; trace_xfs_healthmon_pop(hm, event); @@ -1205,6 +1198,7 @@ xfs_ioc_health_monitor( return -ENOMEM; hm->dev = mp->m_super->s_dev; refcount_set(&hm->ref, 1); + INIT_LIST_HEAD(&hm->event_list); mutex_init(&hm->lock); init_waitqueue_head(&hm->wait); diff --git a/fs/xfs/xfs_healthmon.h b/fs/xfs/xfs_healthmon.h index 0e936507037f..fa3deb187a2b 100644 --- a/fs/xfs/xfs_healthmon.h +++ b/fs/xfs/xfs_healthmon.h @@ -31,8 +31,7 @@ struct xfs_healthmon { struct mutex lock; /* list of event objects */ - struct xfs_healthmon_event *first_event; - struct xfs_healthmon_event *last_event; + struct list_head event_list; /* preallocated event for unmount */ struct xfs_healthmon_event *unmount_event; @@ -110,7 +109,7 @@ enum xfs_healthmon_domain { }; struct xfs_healthmon_event { - struct xfs_healthmon_event *next; + struct list_head entry; enum xfs_healthmon_type type; enum xfs_healthmon_domain domain; From 4c98464e12fcfe3a71646f8efa5d3f7f1b5e2bed Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:47:28 -0700 Subject: [PATCH 0393/1317] xfs: merge healthmon insert/push helpers These functions are basically the same except for where in the queue the new event is added. Refactor them as a single function that takes an action verb to tell us where; and rename the tracepoints to describe directly what happens. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 52 +++++++++++++++++++----------------------- fs/xfs/xfs_trace.h | 4 ++-- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 073b74e490df..e012c7545da0 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -270,10 +270,16 @@ xfs_healthmon_merge_events( return true; } -/* Insert an event onto the start of the queue. */ +enum insert_where { + INSERT_HEAD, + INSERT_TAIL, +}; + +/* Add an event onto the start or the end of the queue. */ static inline void __xfs_healthmon_insert( struct xfs_healthmon *hm, + enum insert_where where, struct xfs_healthmon_event *event) { struct timespec64 now; @@ -283,31 +289,21 @@ __xfs_healthmon_insert( ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; - list_add(&event->entry, &hm->event_list); + switch (where) { + case INSERT_HEAD: + trace_xfs_healthmon_insert_head(hm, event); + + list_add(&event->entry, &hm->event_list); + break; + case INSERT_TAIL: + trace_xfs_healthmon_insert_tail(hm, event); + + list_add_tail(&event->entry, &hm->event_list); + break; + } + xfs_healthmon_bump_events(hm); wake_up(&hm->wait); - - trace_xfs_healthmon_insert(hm, event); -} - -/* Push an event onto the end of the queue. */ -static inline void -__xfs_healthmon_push( - struct xfs_healthmon *hm, - struct xfs_healthmon_event *event) -{ - struct timespec64 now; - - lockdep_assert_held(&hm->lock); - - ktime_get_coarse_real_ts64(&now); - event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; - - list_add_tail(&event->entry, &hm->event_list); - xfs_healthmon_bump_events(hm); - wake_up(&hm->wait); - - trace_xfs_healthmon_push(hm, event); } /* Deal with any previously lost events */ @@ -333,7 +329,7 @@ xfs_healthmon_clear_lost_prev( if (!event) return -ENOMEM; - __xfs_healthmon_push(hm, event); + __xfs_healthmon_insert(hm, INSERT_TAIL, event); cleared: hm->lost_prev_event = 0; return 0; @@ -386,7 +382,7 @@ xfs_healthmon_push( goto out_unlock; } - __xfs_healthmon_push(hm, event); + __xfs_healthmon_insert(hm, INSERT_TAIL, event); out_unlock: mutex_unlock(&hm->lock); @@ -415,7 +411,7 @@ xfs_healthmon_unmount( * we've inserted the unmount event, hm no longer owns that event. */ mutex_lock(&hm->lock); - __xfs_healthmon_insert(hm, hm->unmount_event); + __xfs_healthmon_insert(hm, INSERT_HEAD, hm->unmount_event); hm->unmount_event = NULL; mutex_unlock(&hm->lock); @@ -1214,7 +1210,7 @@ xfs_ioc_health_monitor( } running_event->type = XFS_HEALTHMON_RUNNING; running_event->domain = XFS_HEALTHMON_MOUNT; - __xfs_healthmon_insert(hm, running_event); + __xfs_healthmon_insert(hm, INSERT_HEAD, running_event); /* * Preallocate the unmount event so that we can't fail to notify the diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index f333c938fbd9..6aa379c2cf0c 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -6139,8 +6139,8 @@ DEFINE_EVENT(xfs_healthmon_event_class, name, \ TP_PROTO(const struct xfs_healthmon *hm, \ const struct xfs_healthmon_event *event), \ TP_ARGS(hm, event)) -DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_insert); -DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_push); +DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_insert_head); +DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_insert_tail); DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_pop); DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_format); DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_format_overflow); From 74eeb68a628dbc4a8f976351ad2f1ef5463513ee Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:47:43 -0700 Subject: [PATCH 0394/1317] xfs: check healthmon outbuffer space correctly LOLLM notices that the outbuf space check in xfs_healthmon_format_pop isn't quite correct -- it checks that there's enough space to write a xfs_healthmon_event object, but the outbuffer is supposed to contain xfs_health_monitor_event objects. Fix this by adding a helper, and refactoring all three outbuf size checks to use it. Cc: stable@vger.kernel.org # v7.0 Fixes: b3a289a2a9397b ("xfs: create event queuing, formatting, and discovery infrastructure") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index e012c7545da0..78c87761ac89 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -735,6 +735,13 @@ static const unsigned int type_map[] = { [XFS_HEALTHMON_DATALOST] = XFS_HEALTH_MONITOR_TYPE_DATALOST, }; +static inline bool +xfs_healthmon_check_outbuffer_space(const struct xfs_healthmon *hm) +{ + return hm->bufhead + sizeof(struct xfs_health_monitor_event) <= + hm->bufsize; +} + /* Render event as a V0 structure */ STATIC int xfs_healthmon_format_v0( @@ -801,10 +808,10 @@ xfs_healthmon_format_v0( break; } - ASSERT(hm->bufhead + sizeof(hme) <= hm->bufsize); + ASSERT(xfs_healthmon_check_outbuffer_space(hm)); /* copy formatted object to the outbuf */ - if (hm->bufhead + sizeof(hme) <= hm->bufsize) { + if (xfs_healthmon_check_outbuffer_space(hm)) { memcpy(hm->buffer + hm->bufhead, &hme, sizeof(hme)); hm->bufhead += sizeof(hme); } @@ -887,7 +894,11 @@ xfs_healthmon_format_pop( { struct xfs_healthmon_event *event; - if (hm->bufhead + sizeof(*event) > hm->bufsize) + /* + * Don't bother if there's not enough space to format even one event in + * the outbuffer. + */ + if (!xfs_healthmon_check_outbuffer_space(hm)) return NULL; mutex_lock(&hm->lock); From 295f2cfd3e2c814c2ecd2c1d522bc31c5288e414 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:47:59 -0700 Subject: [PATCH 0395/1317] xfs: bump lost_prev_errors if we lose even the healthmon lost event LOLLM observes that we don't bump xfs_healthmon::lost_prev_event even if we can't allocate or queue a LOST event, which means that events can disappear silently when things are going very wrong. Bump the counter to avoid this problem. Cc: stable@vger.kernel.org # v7.0 Fixes: b3a289a2a9397b ("xfs: create event queuing, formatting, and discovery infrastructure") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 78c87761ac89..b57fa033cec4 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -326,8 +326,10 @@ xfs_healthmon_clear_lost_prev( if (hm->events < XFS_HEALTHMON_MAX_EVENTS) event = kmemdup(&lost_event, sizeof(struct xfs_healthmon_event), GFP_NOFS); - if (!event) + if (!event) { + xfs_healthmon_bump_lost(hm); return -ENOMEM; + } __xfs_healthmon_insert(hm, INSERT_TAIL, event); cleared: From 014c1aff607a839b8ddc732da919b94560ae58c2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:48:14 -0700 Subject: [PATCH 0396/1317] xfs: report nonexistent parents as a filesystem corruption LOLLM noticed that when the directory tree scrubber tries to walk up a parent pointer but the parent inumber doesn't point to an allocated inode, we allow the EINVAL/ENOENT error code to bubble up to userspace. That's not right, we should be reporting that as a cross-referencing error so that someone runs the parent pointer checker. Also add a termination check to xchk_dirpath_step_up because it's a loop body function. Cc: stable@vger.kernel.org # v6.10 Fixes: 928b721a11789a ("xfs: teach online scrub to find directory tree structure problems") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree.c | 30 ++++++++++++++++++++++++++++-- fs/xfs/scrub/trace.h | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index b2cf6e5439d9..717cbac29562 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -368,12 +368,38 @@ xchk_dirpath_step_up( struct xfs_inode *dp; xfs_ino_t parent_ino = be64_to_cpu(dl->pptr_rec.p_ino); unsigned int lock_mode; - int error; + int error = 0; + + if (xchk_should_terminate(sc, &error)) + return error; /* Grab and lock the parent directory. */ error = xchk_iget(sc, parent_ino, &dp); - if (error) + switch (error) { + case -EINVAL: + case -ENOENT: + mutex_lock(&dl->lock); + + if (dl->stale) { + /* live update detected a change in this path */ + error = -ESTALE; + } else { + /* inode doesn't exist, path invalid */ + error = -EFSCORRUPTED; + + trace_xchk_dirpath_badino(dl->sc, path->path_nr, + path->nr_steps, &dl->xname, + &dl->pptr_rec); + } + + mutex_unlock(&dl->lock); return error; + case 0: + /* keep going */ + break; + default: + return error; + } lock_mode = xfs_ilock_attr_map_shared(dp); mutex_lock(&dl->lock); diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 362c6d39e9f5..0f5adc293962 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -1706,6 +1706,39 @@ DEFINE_EVENT(xchk_dirtree_class, name, \ DEFINE_XCHK_DIRTREE_EVENT(xchk_dirtree_create_path); DEFINE_XCHK_DIRTREE_EVENT(xchk_dirpath_walk_upwards); +TRACE_EVENT(xchk_dirpath_badino, + TP_PROTO(struct xfs_scrub *sc, unsigned int path_nr, + unsigned int step_nr, const struct xfs_name *name, + const struct xfs_parent_rec *pptr), + TP_ARGS(sc, path_nr, step_nr, name, pptr), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(unsigned int, path_nr) + __field(unsigned int, step_nr) + __field(xfs_ino_t, parent_ino) + __field(unsigned int, parent_gen) + __field(unsigned int, namelen) + __dynamic_array(char, name, name->len) + ), + TP_fast_assign( + __entry->dev = sc->mp->m_super->s_dev; + __entry->path_nr = path_nr; + __entry->step_nr = step_nr; + __entry->parent_ino = be64_to_cpu(pptr->p_ino); + __entry->parent_gen = be32_to_cpu(pptr->p_gen); + __entry->namelen = name->len; + memcpy(__get_str(name), name->name, name->len); + ), + TP_printk("dev %d:%d path %u step %u parent_ino 0x%llx parent_gen 0x%x name '%.*s'", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->path_nr, + __entry->step_nr, + __entry->parent_ino, + __entry->parent_gen, + __entry->namelen, + __get_str(name)) +); + DECLARE_EVENT_CLASS(xchk_dirpath_class, TP_PROTO(struct xfs_scrub *sc, struct xfs_inode *ip, unsigned int path_nr, unsigned int step_nr, From 1a441c6842da75c5b862cc1c9f7969d6a93b54b9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:48:30 -0700 Subject: [PATCH 0397/1317] xfs: destroy seen inode bitmap when we fail to add a dirpath LOLLM observes a memory leak in xchk_dirtree_create_path if we create the directory path object but appending the name to the path fails. When this happens, we don't tear down the (empty) seen inode bitmap. This is a pretty trivial error, but let's not leave logic bombs. Do the same for a similar bug in xrep_dirtree_create_adoption_path. Cc: stable@vger.kernel.org # v6.10 Fixes: 928b721a11789a ("xfs: teach online scrub to find directory tree structure problems") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree.c | 1 + fs/xfs/scrub/dirtree_repair.c | 1 + 2 files changed, 2 insertions(+) diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index 717cbac29562..9b0ab2316612 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -259,6 +259,7 @@ xchk_dirtree_create_path( dl->nr_paths++; return 0; out_path: + xino_bitmap_destroy(&path->seen_inodes); kfree(path); return error; } diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index bbf6acf6fd40..8acd55b8c769 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -618,6 +618,7 @@ xrep_dirtree_create_adoption_path( return 0; out_path: + xino_bitmap_destroy(&path->seen_inodes); kfree(path); return error; } From 8c71ad4d4f3e20c30b663bd292526fcbc4d3913f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:48:45 -0700 Subject: [PATCH 0398/1317] xfs: signal inode btree xref error if get_rec returns an error LOLLM points out that xchk_finobt_xref_inobt and xchk_inobt_xref_finobt both ignore errors being returned from the xfs_btree_get_rec function and proceed with a (possibly stale) "true" value for has_record. If the *simple* btree record checks fail during cross-referencing, we can immediately conclude that there's a cross-referncing error in the other btree. On those grounds, we can bubble up the returned error instead of wasting time cross-referencing with garbage. Cc: stable@vger.kernel.org # v6.4 Fixes: bc0f3b55467e1b ("xfs: directly cross-reference the inode btrees with each other") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/ialloc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/xfs/scrub/ialloc.c b/fs/xfs/scrub/ialloc.c index 19c0b1b2a787..9270ad075fe0 100644 --- a/fs/xfs/scrub/ialloc.c +++ b/fs/xfs/scrub/ialloc.c @@ -85,6 +85,8 @@ xchk_inobt_xref_finobt( goto no_record; error = xfs_inobt_get_rec(cur, &frec, &has_record); + if (error) + return error; if (!has_record) return -EFSCORRUPTED; @@ -188,6 +190,8 @@ xchk_finobt_xref_inobt( goto no_record; error = xfs_inobt_get_rec(cur, &irec, &has_record); + if (error) + return error; if (!has_record) return -EFSCORRUPTED; From 0fc67528f54bb91dac22093749b425207c0fc245 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:49:01 -0700 Subject: [PATCH 0399/1317] xfs: truncate quota file correctly when repairing quota file LOLLM noticed that xrep_quota_data_fork screws up the unit handling when it computes the offset at which to start truncating the quota file. max_dquid_off is the file block offset containing the highest possible dquot, and xfs_bunmapi_range takes the starting file block offset. Therefore, it makes no sense to multiply max_dquid_off by the blocksize; all we need to do is start truncating at the next block. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 487bd4f68ebb..ca3ac6728339 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -455,8 +455,7 @@ xrep_quota_data_fork( if (truncate) { /* Erase everything after the block containing the max dquot */ - error = xfs_bunmapi_range(&sc->tp, sc->ip, 0, - max_dqid_off * sc->mp->m_sb.sb_blocksize, + error = xfs_bunmapi_range(&sc->tp, sc->ip, 0, max_dqid_off + 1, XFS_MAX_FILEOFF); if (error) goto out; From de20f7014917d661afc03d9a1c157d0ae2775b49 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:49:52 -0700 Subject: [PATCH 0400/1317] xfs: compute dquot checksum after resetting dd_lsn in repair LOLLM complains that xrep_quota_block updates dd_lsn after calculating the crc of the ondisk dquot. That's clearly broken, so fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index ca3ac6728339..5c22bb6ccffa 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -363,11 +363,18 @@ xrep_quota_block( ddq->d_rtbcount, &ddq->d_rtbtimer, defq->rtb.time); + /* + * This transaction operates on raw disk buffers, so we don't + * have a dquot log item to assign the LSN for us. Instead, + * set it to zero so that log recovery will always replay any + * logged dquot item atop this buffer. + */ + dqblk->dd_lsn = 0; + /* We only support v5 filesystems so always set these. */ uuid_copy(&dqblk->dd_uuid, &sc->mp->m_sb.sb_meta_uuid); xfs_update_cksum((char *)dqblk, sizeof(struct xfs_dqblk), XFS_DQUOT_CRC_OFF); - dqblk->dd_lsn = 0; } switch (dqtype) { case XFS_DQTYPE_USER: From d7f97be48cbe3751e44a45373c0e91e93b976f98 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:49:32 -0700 Subject: [PATCH 0401/1317] xfs: fix backwards skipping logic in xrep_quota_block LOLLM complains about the logic in xrep_quota_block that skips reinitializing the ondisk dquot if there aren't any problems that would impede a dqiterate walk later. I got the type checking logic backwards, which is the source of the problem. Fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 5c22bb6ccffa..59302e8afc7e 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -325,7 +325,7 @@ xrep_quota_block( * If there's nothing that would impede a dqiterate, we're * done. */ - if ((ddq->d_type & XFS_DQTYPE_REC_MASK) != dqtype || + if ((ddq->d_type & XFS_DQTYPE_REC_MASK) == dqtype && id == be32_to_cpu(ddq->d_id)) { xfs_trans_brelse(sc->tp, bp); return 0; From e8b01aaafffe6b852325debdf1ef4b13ccea1cd7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:49:47 -0700 Subject: [PATCH 0402/1317] xfs: fix backwards mergeability logic in refcount scrubber When we start the refcount or rtrefcount btree scanners, prev_rec is initialized to all zeroes. This is done so that the record mergeability checks skip the first record because you must have two records to compare. Unfortunately, I got the logic backwards, so scrub has never complained about mergeable refcountbt records. Fix this bug that LOLLM noticed. Cc: stable@vger.kernel.org # v6.4 Fixes: db0502b39c21d1 ("xfs: flag refcount btree records that could be merged") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/refcount.c | 2 +- fs/xfs/scrub/rtrefcount.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/refcount.c b/fs/xfs/scrub/refcount.c index 4e1bf23e5b89..f2addaf13c58 100644 --- a/fs/xfs/scrub/refcount.c +++ b/fs/xfs/scrub/refcount.c @@ -410,7 +410,7 @@ xchk_refcount_mergeable( const struct xfs_refcount_irec *r1 = &rrc->prev_rec; /* Ignore if prev_rec is not yet initialized. */ - if (r1->rc_blockcount > 0) + if (r1->rc_blockcount == 0) return false; if (r1->rc_domain != r2->rc_domain) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index 4e7c540c8d23..de100178f41c 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -375,7 +375,7 @@ xchk_rtrefcount_mergeable( const struct xfs_refcount_irec *r1 = &rrc->prev_rec; /* Ignore if prev_rec is not yet initialized. */ - if (r1->rc_blockcount > 0) + if (r1->rc_blockcount == 0) return false; if (r1->rc_startblock + r1->rc_blockcount != r2->rc_startblock) From 79ab1af2034b2ad10c5f18910937b938d2ad2219 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:51:05 -0700 Subject: [PATCH 0403/1317] xfs: cross-reference the rtgroup superblock extent, not block LOLLM noticed that when libxfs creates a realtime superblock, it will create an rtrmapbt record covering the entire rtextent in which the superblock lives. However, the cross-referencing checks only look for the first block, which means that we can miss a corrupt rtrmap record. That will get picked up by the rtrmap scrubber, but we should make the rgsuper scrubber more robust anyway. Cc: stable@vger.kernel.org # v6.13 Fixes: 3f1bdf50ab1b9c ("xfs: scrub the realtime group superblock") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rgsuper.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c index 2bd2c0351b35..6e2abe5dc27c 100644 --- a/fs/xfs/scrub/rgsuper.c +++ b/fs/xfs/scrub/rgsuper.c @@ -36,8 +36,10 @@ xchk_rgsuperblock_xref( if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) return; - xchk_xref_is_used_rt_space(sc, xfs_rgbno_to_rtb(sc->sr.rtg, 0), 1); - xchk_xref_is_only_rt_owned_by(sc, 0, 1, &XFS_RMAP_OINFO_FS); + xchk_xref_is_used_rt_space(sc, xfs_rgbno_to_rtb(sc->sr.rtg, 0), + sc->mp->m_sb.sb_rextsize); + xchk_xref_is_only_rt_owned_by(sc, 0, sc->mp->m_sb.sb_rextsize, + &XFS_RMAP_OINFO_FS); } int From c3085f6c7cca7c162248519ce8d763047cdd8acd Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:51:21 -0700 Subject: [PATCH 0404/1317] xfs: use the rtgroup extent count to find rtrefcount gaps LOLLM noticed an anachronism from the early days of rtrefcount where the refcount btree would handle 64-bit block numbers -- we pass rtblocks into the gap finder, but rtrefcount btrees are sharded by rtgroup now. This isn't really a problem for us since we're only looking for overlapping rtrmap records to flag, but let's fix this sillyness. Also fix some stale comments. Cc: stable@vger.kernel.org # v6.14 Fixes: 30f47950dc2eba ("xfs: check reference counts of gaps between rt refcount records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtrefcount.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index de100178f41c..a024d50cf9d2 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -428,7 +428,7 @@ static inline void xchk_rtrefcountbt_xref_gaps( struct xfs_scrub *sc, struct xchk_rtrefcbt_records *rrc, - xfs_rtblock_t bno) + xfs_rgblock_t bno) { struct xfs_rmap_irec low; struct xfs_rmap_irec high; @@ -538,7 +538,7 @@ xchk_refcount_xref_rmap( xchk_btree_xref_set_corrupt(sc, sc->sr.rmap_cur, 0); } -/* Scrub the refcount btree for some AG. */ +/* Scrub the refcount btree for some rtgroup. */ int xchk_rtrefcountbt( struct xfs_scrub *sc) @@ -564,10 +564,10 @@ xchk_rtrefcountbt( /* * Check that all blocks between the last refcount > 1 record and the - * end of the rt volume have at most one reverse mapping. + * end of the rtgroup have at most one reverse mapping. */ - xchk_rtrefcountbt_xref_gaps(sc, &rrc, sc->mp->m_sb.sb_rblocks); - + xchk_rtrefcountbt_xref_gaps(sc, &rrc, + xfs_rtx_to_rgbno(sc->sr.rtg, sc->mp->m_sb.sb_rgextents)); xchk_refcount_xref_rmap(sc, &btree_oinfo, rrc.cow_blocks); return 0; From 0d43368844a75ad13561a1198a3b027940730756 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:51:36 -0700 Subject: [PATCH 0405/1317] xfs: strengthen the "is cow staging" helpers in scrub LOLLM pointed out a bug in both of the refcount scrub predicates that determine if a range of blocks is marked as CoW staging in the btree. While it compares blockcount < len, this isn't enough to determine that the CoW staging record is at least as large as the range passed into the helper. Fix both of them. Cc: stable@vger.kernel.org # v4.16 Fixes: f6d5fc21fdc713 ("xfs: cross-reference refcount btree during scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/refcount.c | 6 +++++- fs/xfs/scrub/rtrefcount.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/refcount.c b/fs/xfs/scrub/refcount.c index f2addaf13c58..f8c51d8fbb3d 100644 --- a/fs/xfs/scrub/refcount.c +++ b/fs/xfs/scrub/refcount.c @@ -581,8 +581,12 @@ xchk_xref_is_cow_staging( if (rc.rc_domain != XFS_REFC_DOMAIN_COW) xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0); + /* Can't start after bno */ + if (rc.rc_startblock > agbno) + xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0); + /* Must be at least as long as what was passed in */ - if (rc.rc_blockcount < len) + if (rc.rc_startblock + rc.rc_blockcount < agbno + len) xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0); } diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index a024d50cf9d2..652d6b78b7a0 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -609,8 +609,12 @@ xchk_xref_is_rt_cow_staging( if (rc.rc_domain != XFS_REFC_DOMAIN_COW) xchk_btree_xref_set_corrupt(sc, sc->sr.refc_cur, 0); + /* Can't start after bno */ + if (rc.rc_startblock > bno) + xchk_btree_xref_set_corrupt(sc, sc->sr.refc_cur, 0); + /* Must be at least as long as what was passed in */ - if (rc.rc_blockcount < len) + if (rc.rc_startblock + rc.rc_blockcount < bno + len) xchk_btree_xref_set_corrupt(sc, sc->sr.refc_cur, 0); } From 3f9fd694fa429e89fe6de51b22b0ed5fb8b2daf4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:51:52 -0700 Subject: [PATCH 0406/1317] xfs: fix rtrefcount btree block counting in scrub LOLLM started on a long tangent about how xchk_refcount_xref_rmap shouldn't nope out if sc->sa.rmap_cur isn't set, because nothing ever sets that field. It's right about the condition, but misses the bigger problem, which is that to count the rtrefcount btree blocks, we have to walk all rmap records in each AG in the data section. That was papered over by the incorrect !sc->sa.rmap_cur test. In other words, we need a perag iteration loop here. Restructure the code to do that, and now it'll all work properly. Fix the confusing function name prefix. Cc: stable@vger.kernel.org # v6.14 Fixes: c27929670de144 ("xfs: scrub the realtime refcount btree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtrefcount.c | 64 +++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index 652d6b78b7a0..3d916d71a135 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -20,6 +20,7 @@ #include "xfs_metafile.h" #include "xfs_rtrefcount_btree.h" #include "xfs_rtalloc.h" +#include "xfs_ag.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/btree.h" @@ -504,30 +505,75 @@ xchk_rtrefcountbt_rec( return 0; } +/* Count the number of blocks used by the rtrefcount btree file in this AG. */ +static int +xchk_rtrefcount_count_agblocks( + struct xfs_scrub *sc, + xfs_agnumber_t agno, + const struct xfs_owner_info *btree_oinfo, + xfs_filblks_t *blocks) +{ + xfs_filblks_t agblocks = 0; + int error; + + error = xchk_ag_init_existing(sc, agno, &sc->sa); + if (error) + goto out_free; + + /* + * If we don't have an rmap cursor, we can't complete the cross + * referencing, so return EFSCORRUPTED to end the loop and trigger the + * XFAIL flag. + */ + if (!sc->sa.rmap_cur) { + error = -EFSCORRUPTED; + goto out_free; + } + + error = xchk_count_rmap_ownedby_ag(sc, sc->sa.rmap_cur, btree_oinfo, + &agblocks); + if (error) + goto out_free; + + *blocks += agblocks; +out_free: + xchk_ag_free(sc, &sc->sa); + return error; +} + /* Make sure we have as many refc blocks as the rmap says. */ STATIC void -xchk_refcount_xref_rmap( +xchk_rtrefcount_xref_rmap( struct xfs_scrub *sc, const struct xfs_owner_info *btree_oinfo, xfs_extlen_t cow_blocks) { xfs_filblks_t refcbt_blocks = 0; - xfs_filblks_t blocks; - int error; + xfs_filblks_t blocks = 1; /* one for the iroot */ + xfs_agnumber_t agno; + int error = 0; - if (!sc->sr.rmap_cur || !sc->sa.rmap_cur || xchk_skip_xref(sc->sm)) + if (!xfs_has_rmapbt(sc->mp) || xchk_skip_xref(sc->sm)) return; /* Check that we saw as many refcbt blocks as the rmap knows about. */ error = xfs_btree_count_blocks(sc->sr.refc_cur, &refcbt_blocks); if (!xchk_btree_process_error(sc, sc->sr.refc_cur, 0, &error)) return; - error = xchk_count_rmap_ownedby_ag(sc, sc->sa.rmap_cur, btree_oinfo, - &blocks); - if (!xchk_should_check_xref(sc, &error, &sc->sa.rmap_cur)) + + for (agno = 0; agno < sc->mp->m_sb.sb_agcount; agno++) { + error = xchk_rtrefcount_count_agblocks(sc, agno, btree_oinfo, + &blocks); + if (error) + break; + } + if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) return; if (blocks != refcbt_blocks) - xchk_btree_xref_set_corrupt(sc, sc->sa.rmap_cur, 0); + xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0); + + if (!sc->sr.rmap_cur || xchk_skip_xref(sc->sm)) + return; /* Check that we saw as many cow blocks as the rmap knows about. */ error = xchk_count_rmap_ownedby_ag(sc, sc->sr.rmap_cur, @@ -568,7 +614,7 @@ xchk_rtrefcountbt( */ xchk_rtrefcountbt_xref_gaps(sc, &rrc, xfs_rtx_to_rgbno(sc->sr.rtg, sc->mp->m_sb.sb_rgextents)); - xchk_refcount_xref_rmap(sc, &btree_oinfo, rrc.cow_blocks); + xchk_rtrefcount_xref_rmap(sc, &btree_oinfo, rrc.cow_blocks); return 0; } From 6b760b3232b3efc8bcc7c165e76300bc1c9140c5 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:52:07 -0700 Subject: [PATCH 0407/1317] xfs: make the rtsummary repair fix the file size too LOLLM noticed that the rtsummary repair code will create a new rtsummary with the correct file size, but it won't force the new file size to be set on the existing rtsummary file, leaving the rtsummary corrupt. Fix this by setting up the tempfile mapping-exchange to run to the end of both files, which is the magic offset needed to reset the file size. Cc: stable@vger.kernel.org # v6.10 Fixes: abf039e2e4afde ("xfs: online repair of realtime summaries") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtsummary_repair.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rtsummary_repair.c b/fs/xfs/scrub/rtsummary_repair.c index f065c3e51ce2..ed763290aec1 100644 --- a/fs/xfs/scrub/rtsummary_repair.c +++ b/fs/xfs/scrub/rtsummary_repair.c @@ -164,9 +164,10 @@ xrep_rtsummary( /* * Now exchange the contents. Nothing in repair uses the temporary * buffer, so we can reuse it for the tempfile exchrange information. + * Use XFS_MAX_FILEOFF here so that we correct the rtsummary file size. */ error = xrep_tempexch_trans_reserve(sc, XFS_DATA_FORK, 0, - rts->rsumblocks, &rts->tempexch); + XFS_MAX_FILEOFF, &rts->tempexch); if (error) return error; From 4d0624679ae29b469016f1ce4714be58582ed06a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:52:23 -0700 Subject: [PATCH 0408/1317] xfs: count escaped corruption errors in scrub stats The main scrub code will quietly turn bubbled-up EFSCORRUPTED and EFSBADCRC errors into corruption errors. These aren't recorded in the scrub stats code (says LOLLM) so do that now. Cc: stable@vger.kernel.org # v6.6 Fixes: d7a74cad8f4513 ("xfs: track usage statistics of online fsck") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/scrub.c | 3 +-- fs/xfs/scrub/stats.c | 28 +++++++++++++++++++--------- fs/xfs/scrub/stats.h | 4 ++-- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index 8742445c86f4..12c228b7f477 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -765,8 +765,7 @@ xfs_scrub_metadata( out_teardown: error = xchk_teardown(sc, error); out_sc: - if (error != -ENOENT) - xchk_stats_merge(mp, sm, &run); + xchk_stats_merge(mp, sm, error, &run); kfree(sc); out: trace_xchk_done(XFS_I(file_inode(file)), sm, error); diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index ef3f6abdb706..76f2515188d1 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -188,31 +188,37 @@ STATIC void xchk_stats_merge_one( struct xchk_stats *cs, const struct xfs_scrub_metadata *sm, + int error, const struct xchk_stats_run *run) { struct xchk_scrub_stats *css; + unsigned int sm_flags = sm->sm_flags; if (sm->sm_type >= XFS_SCRUB_TYPE_NR) { ASSERT(sm->sm_type < XFS_SCRUB_TYPE_NR); return; } + /* caller applies this same transformation after we return */ + if (error == -EFSCORRUPTED || error == -EFSBADCRC) + sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + css = &cs->cs_stats[sm->sm_type]; spin_lock(&css->css_lock); css->invocations++; - if (!(sm->sm_flags & XFS_SCRUB_OFLAG_UNCLEAN)) + if (!(sm_flags & XFS_SCRUB_OFLAG_UNCLEAN)) css->clean++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) + if (sm_flags & XFS_SCRUB_OFLAG_CORRUPT) css->corrupt++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_PREEN) + if (sm_flags & XFS_SCRUB_OFLAG_PREEN) css->preen++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_XFAIL) + if (sm_flags & XFS_SCRUB_OFLAG_XFAIL) css->xfail++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_XCORRUPT) + if (sm_flags & XFS_SCRUB_OFLAG_XCORRUPT) css->xcorrupt++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE) + if (sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE) css->incomplete++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_WARNING) + if (sm_flags & XFS_SCRUB_OFLAG_WARNING) css->warning++; css->retries += run->retries; css->checktime_us += howmany_64(run->scrub_ns, NSEC_PER_USEC); @@ -230,10 +236,14 @@ void xchk_stats_merge( struct xfs_mount *mp, const struct xfs_scrub_metadata *sm, + int error, const struct xchk_stats_run *run) { - xchk_stats_merge_one(&global_stats, sm, run); - xchk_stats_merge_one(mp->m_scrub_stats, sm, run); + if (error == -ENOENT) + return; + + xchk_stats_merge_one(&global_stats, sm, error, run); + xchk_stats_merge_one(mp->m_scrub_stats, sm, error, run); } /* debugfs boilerplate */ diff --git a/fs/xfs/scrub/stats.h b/fs/xfs/scrub/stats.h index b358ad8d8b90..221052b95dd0 100644 --- a/fs/xfs/scrub/stats.h +++ b/fs/xfs/scrub/stats.h @@ -27,7 +27,7 @@ void xchk_stats_register(struct xchk_stats *cs, struct dentry *parent); void xchk_stats_unregister(struct xchk_stats *cs); void xchk_stats_merge(struct xfs_mount *mp, const struct xfs_scrub_metadata *sm, - const struct xchk_stats_run *run); + int error, const struct xchk_stats_run *run); static inline u64 xchk_stats_now(void) { return ktime_get_ns(); } static inline u64 xchk_stats_elapsed_ns(u64 since) @@ -53,7 +53,7 @@ static inline u64 xchk_stats_elapsed_ns(u64 since) # define xchk_stats_unregister(cs) ((void)0) # define xchk_stats_now() (0) # define xchk_stats_elapsed_ns(x) (0 * (x)) -# define xchk_stats_merge(mp, sm, run) ((void)0) +# define xchk_stats_merge(mp, sm, error, run) ((void)0) #endif /* CONFIG_XFS_ONLINE_SCRUB_STATS */ #endif /* __XFS_SCRUB_STATS_H__ */ From 3837c3f29fbc3b8c12bebf5c62741e2befe3482a Mon Sep 17 00:00:00 2001 From: Magdalena Schulfer Date: Tue, 1 Sep 2026 14:57:47 +0200 Subject: [PATCH 0409/1317] accel/ivpu: Validate full buffer range in ivpu_to_cpu_addr Add a size parameter to ivpu_to_cpu_addr() and validate that the whole [vpu_addr, vpu_addr + size) range stays within the BO. Cc: stable@vger.kernel.org Fixes: 647371a6609d ("accel/ivpu: Add GEM buffer object management") Signed-off-by: Magdalena Schulfer Signed-off-by: Dawid Osuchowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260901125749.404338-2-dawid.osuchowski@linux.intel.com --- drivers/accel/ivpu/ivpu_gem.h | 14 +++++++++++--- drivers/accel/ivpu/ivpu_ipc.c | 7 ++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_gem.h b/drivers/accel/ivpu/ivpu_gem.h index 0c3350f22b55..b1ae020a4fc2 100644 --- a/drivers/accel/ivpu/ivpu_gem.h +++ b/drivers/accel/ivpu/ivpu_gem.h @@ -87,15 +87,23 @@ static inline bool ivpu_bo_is_resident(struct ivpu_bo *bo) return !!bo->base.pages; } -static inline void *ivpu_to_cpu_addr(struct ivpu_bo *bo, u32 vpu_addr) +static inline void *ivpu_to_cpu_addr(struct ivpu_bo *bo, u64 vpu_addr, u64 size) { + u64 bo_size = ivpu_bo_size(bo); + u64 offset; + if (vpu_addr < bo->vpu_addr) return NULL; - if (vpu_addr >= (bo->vpu_addr + ivpu_bo_size(bo))) + if (size > bo_size) return NULL; - return ivpu_bo_vaddr(bo) + (vpu_addr - bo->vpu_addr); + offset = vpu_addr - bo->vpu_addr; + + if (offset > bo_size - size) + return NULL; + + return ivpu_bo_vaddr(bo) + offset; } static inline u32 cpu_to_vpu_addr(struct ivpu_bo *bo, void *cpu_addr) diff --git a/drivers/accel/ivpu/ivpu_ipc.c b/drivers/accel/ivpu/ivpu_ipc.c index 62607ec8ca8f..8e960293b77a 100644 --- a/drivers/accel/ivpu/ivpu_ipc.c +++ b/drivers/accel/ivpu/ivpu_ipc.c @@ -79,7 +79,7 @@ ivpu_ipc_tx_prepare(struct ivpu_device *vdev, struct ivpu_ipc_consumer *cons, return -ENOMEM; } - tx_buf = ivpu_to_cpu_addr(ipc->mem_tx, tx_buf_vpu_addr); + tx_buf = ivpu_to_cpu_addr(ipc->mem_tx, tx_buf_vpu_addr, sizeof(*tx_buf)); if (drm_WARN_ON(&vdev->drm, !tx_buf)) { gen_pool_free(ipc->mm_tx, tx_buf_vpu_addr, sizeof(*tx_buf)); return -EIO; @@ -420,7 +420,7 @@ void ivpu_ipc_irq_handler(struct ivpu_device *vdev) return; } - ipc_hdr = ivpu_to_cpu_addr(ipc->mem_rx, vpu_addr); + ipc_hdr = ivpu_to_cpu_addr(ipc->mem_rx, vpu_addr, sizeof(*ipc_hdr)); if (!ipc_hdr) { ivpu_warn_ratelimited(vdev, "IPC msg 0x%x out of range\n", vpu_addr); continue; @@ -429,7 +429,8 @@ void ivpu_ipc_irq_handler(struct ivpu_device *vdev) jsm_msg = NULL; if (ipc_hdr->channel != IVPU_IPC_CHAN_BOOT_MSG) { - jsm_msg = ivpu_to_cpu_addr(ipc->mem_rx, ipc_hdr->data_addr); + jsm_msg = ivpu_to_cpu_addr(ipc->mem_rx, ipc_hdr->data_addr, + sizeof(*jsm_msg)); if (!jsm_msg) { ivpu_warn_ratelimited(vdev, "JSM msg 0x%x out of range\n", ipc_hdr->data_addr); From 0724afc55c77c36c7feb9a7264b02aa7593c5c2d Mon Sep 17 00:00:00 2001 From: Magdalena Schulfer Date: Tue, 1 Sep 2026 14:57:48 +0200 Subject: [PATCH 0410/1317] accel/ivpu: Validate firmware log buffer metadata The tracing log headers parsed by fw_log_print_buffer() reside in DMA-shared BOs that the NPU firmware can write to. fw_log_from_bo() validated log->header_size and log->size, but fw_log_print_buffer() re-read those same fields from shared memory afterwards, allowing a TOCTOU where firmware changes them between the check and the use, and making the host dereference out-of-bounds addresses while printing logs. Snapshot the validated values once with READ_ONCE() and pass them down explicitly in a new struct ivpu_fw_log_desc instead of re-reading them from the shared struct. Cc: stable@vger.kernel.org Fixes: d4e4257afa6e ("accel/ivpu: Add firmware tracing support") Signed-off-by: Magdalena Schulfer Signed-off-by: Dawid Osuchowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260901125749.404338-3-dawid.osuchowski@linux.intel.com --- drivers/accel/ivpu/ivpu_fw_log.c | 76 +++++++++++++++++++------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_fw_log.c b/drivers/accel/ivpu/ivpu_fw_log.c index 716467aa3156..4f9055aa9d33 100644 --- a/drivers/accel/ivpu/ivpu_fw_log.c +++ b/drivers/accel/ivpu/ivpu_fw_log.c @@ -26,10 +26,17 @@ MODULE_PARM_DESC(fw_log_level, " error=" __stringify(IVPU_FW_LOG_ERROR) " fatal=" __stringify(IVPU_FW_LOG_FATAL)); +struct ivpu_fw_log_desc { + struct vpu_tracing_buffer_header *log; + u32 header_size; + u32 size; +}; + static int fw_log_from_bo(struct ivpu_device *vdev, struct ivpu_bo *bo, u32 *offset, - struct vpu_tracing_buffer_header **out_log) + struct ivpu_fw_log_desc *desc) { struct vpu_tracing_buffer_header *log; + u32 header_size, size; if ((*offset + sizeof(*log)) > ivpu_bo_size(bo)) return -EINVAL; @@ -39,26 +46,32 @@ static int fw_log_from_bo(struct ivpu_device *vdev, struct ivpu_bo *bo, u32 *off if (log->vpu_canary_start != VPU_TRACING_BUFFER_CANARY) return -EINVAL; - if (log->header_size < sizeof(*log) || log->header_size > 1024) { - ivpu_dbg(vdev, FW_BOOT, "Invalid header size 0x%x\n", log->header_size); + header_size = READ_ONCE(log->header_size); + size = READ_ONCE(log->size); + + if (header_size < sizeof(*log) || header_size > 1024) { + ivpu_dbg(vdev, FW_BOOT, "Invalid header size 0x%x\n", header_size); return -EINVAL; } - if (log->size < log->header_size) { - ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", log->size); + if ((char *)log + size > (char *)ivpu_bo_vaddr(bo) + ivpu_bo_size(bo)) { + ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", size); return -EINVAL; } - if ((char *)log + log->size > (char *)ivpu_bo_vaddr(bo) + ivpu_bo_size(bo)) { - ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", log->size); + if (size < header_size) { + ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x < header size 0x%x\n", + size, header_size); return -EINVAL; } - *out_log = log; - *offset += log->size; + desc->log = log; + desc->header_size = header_size; + desc->size = size; + *offset += size; ivpu_dbg(vdev, FW_BOOT, "FW log name \"%s\", write offset 0x%x size 0x%x, wrap count %d, hdr version %d size %d format %d, alignment %d", - log->name, log->write_index, log->size, log->wrap_count, log->header_version, - log->header_size, log->format, log->alignment); + log->name, log->write_index, size, log->wrap_count, log->header_version, + header_size, log->format, log->alignment); return 0; } @@ -94,11 +107,12 @@ static void fw_log_print_lines(char *buffer, u32 size, struct drm_printer *p) drm_printf(p, "%s", line); } -static void fw_log_print_buffer(struct vpu_tracing_buffer_header *log, const char *prefix, +static void fw_log_print_buffer(struct ivpu_fw_log_desc *desc, const char *prefix, bool only_new_msgs, struct drm_printer *p) { - char *log_data = (void *)log + log->header_size; - u32 data_size = log->size - log->header_size; + struct vpu_tracing_buffer_header *log = desc->log; + char *log_data = (void *)log + desc->header_size; + u32 data_size = desc->size - desc->header_size; u32 log_start = only_new_msgs ? READ_ONCE(log->read_index) : 0; u32 log_end = READ_ONCE(log->write_index); @@ -134,11 +148,11 @@ static void fw_log_print_all_in_bo(struct ivpu_device *vdev, const char *name, struct ivpu_bo *bo, bool only_new_msgs, struct drm_printer *p) { - struct vpu_tracing_buffer_header *log; + struct ivpu_fw_log_desc desc; u32 next = 0; - while (fw_log_from_bo(vdev, bo, &next, &log) == 0) - fw_log_print_buffer(log, name, only_new_msgs, p); + while (fw_log_from_bo(vdev, bo, &next, &desc) == 0) + fw_log_print_buffer(&desc, name, only_new_msgs, p); } void ivpu_fw_log_print(struct ivpu_device *vdev, bool only_new_msgs, struct drm_printer *p) @@ -149,36 +163,36 @@ void ivpu_fw_log_print(struct ivpu_device *vdev, bool only_new_msgs, struct drm_ void ivpu_fw_log_mark_read(struct ivpu_device *vdev) { - struct vpu_tracing_buffer_header *log; + struct ivpu_fw_log_desc desc; u32 next; next = 0; - while (fw_log_from_bo(vdev, vdev->fw->mem_log_crit, &next, &log) == 0) { - log->read_index = READ_ONCE(log->write_index); - log->read_wrap_count = READ_ONCE(log->wrap_count); + while (fw_log_from_bo(vdev, vdev->fw->mem_log_crit, &next, &desc) == 0) { + desc.log->read_index = READ_ONCE(desc.log->write_index); + desc.log->read_wrap_count = READ_ONCE(desc.log->wrap_count); } next = 0; - while (fw_log_from_bo(vdev, vdev->fw->mem_log_verb, &next, &log) == 0) { - log->read_index = READ_ONCE(log->write_index); - log->read_wrap_count = READ_ONCE(log->wrap_count); + while (fw_log_from_bo(vdev, vdev->fw->mem_log_verb, &next, &desc) == 0) { + desc.log->read_index = READ_ONCE(desc.log->write_index); + desc.log->read_wrap_count = READ_ONCE(desc.log->wrap_count); } } void ivpu_fw_log_reset(struct ivpu_device *vdev) { - struct vpu_tracing_buffer_header *log; + struct ivpu_fw_log_desc desc; u32 next; next = 0; - while (fw_log_from_bo(vdev, vdev->fw->mem_log_crit, &next, &log) == 0) { - log->read_index = 0; - log->read_wrap_count = 0; + while (fw_log_from_bo(vdev, vdev->fw->mem_log_crit, &next, &desc) == 0) { + desc.log->read_index = 0; + desc.log->read_wrap_count = 0; } next = 0; - while (fw_log_from_bo(vdev, vdev->fw->mem_log_verb, &next, &log) == 0) { - log->read_index = 0; - log->read_wrap_count = 0; + while (fw_log_from_bo(vdev, vdev->fw->mem_log_verb, &next, &desc) == 0) { + desc.log->read_index = 0; + desc.log->read_wrap_count = 0; } } From 95bf070f3225dc7175725438c916ad321d42fe45 Mon Sep 17 00:00:00 2001 From: Dawid Osuchowski Date: Tue, 1 Sep 2026 14:57:49 +0200 Subject: [PATCH 0411/1317] accel/ivpu: Limit firmware log name prints to field size The name in struct vpu_tracing_buffer_header is a fixed-size array populated by the NPU firmware. It is expected to be NUL-terminated, but nothing on the host side enforces this, so printing it with an unbounded string conversion would read past the field if the terminator is ever missing and expose adjacent bytes of the shared tracing BO through dmesg and the debugfs FW log output. Print at most as many characters as the name field holds, so the output never runs past it even if the string is not NUL-terminated. Cc: stable@vger.kernel.org Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260827102339.281799-1-dawid.osuchowski@linux.intel.com?part=2 Fixes: d4e4257afa6e ("accel/ivpu: Add firmware tracing support") Signed-off-by: Dawid Osuchowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260901125749.404338-4-dawid.osuchowski@linux.intel.com --- drivers/accel/ivpu/ivpu_fw_log.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_fw_log.c b/drivers/accel/ivpu/ivpu_fw_log.c index 4f9055aa9d33..9eafc42120b6 100644 --- a/drivers/accel/ivpu/ivpu_fw_log.c +++ b/drivers/accel/ivpu/ivpu_fw_log.c @@ -69,9 +69,9 @@ static int fw_log_from_bo(struct ivpu_device *vdev, struct ivpu_bo *bo, u32 *off *offset += size; ivpu_dbg(vdev, FW_BOOT, - "FW log name \"%s\", write offset 0x%x size 0x%x, wrap count %d, hdr version %d size %d format %d, alignment %d", - log->name, log->write_index, size, log->wrap_count, log->header_version, - header_size, log->format, log->alignment); + "FW log name \"%.*s\", write offset 0x%x size 0x%x, wrap count %d, hdr version %d size %d format %d, alignment %d", + (int)ARRAY_SIZE(log->name), log->name, log->write_index, size, log->wrap_count, + log->header_version, header_size, log->format, log->alignment); return 0; } @@ -123,7 +123,8 @@ static void fw_log_print_buffer(struct ivpu_fw_log_desc *desc, const char *prefi if (log->wrap_count == log->read_wrap_count) { if (log_end <= log_start) { - drm_printf(p, "==== %s \"%s\" log empty ====\n", prefix, log->name); + drm_printf(p, "==== %s \"%.*s\" log empty ====\n", prefix, + (int)ARRAY_SIZE(log->name), log->name); return; } } else if (log->wrap_count == log->read_wrap_count + 1) { @@ -133,7 +134,8 @@ static void fw_log_print_buffer(struct ivpu_fw_log_desc *desc, const char *prefi log_start = log_end; } - drm_printf(p, "==== %s \"%s\" log start ====\n", prefix, log->name); + drm_printf(p, "==== %s \"%.*s\" log start ====\n", prefix, (int)ARRAY_SIZE(log->name), + log->name); if (log_end > log_start) { fw_log_print_lines(log_data + log_start, log_end - log_start, p); } else { @@ -141,7 +143,8 @@ static void fw_log_print_buffer(struct ivpu_fw_log_desc *desc, const char *prefi fw_log_print_lines(log_data, log_end, p); } drm_printf(p, "\n\x1b[0m"); /* add new line and clear formatting */ - drm_printf(p, "==== %s \"%s\" log end ====\n", prefix, log->name); + drm_printf(p, "==== %s \"%.*s\" log end ====\n", prefix, (int)ARRAY_SIZE(log->name), + log->name); } static void From 157dcb8230a4e882e099ec4d1cb1957b0b2c9b26 Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Sat, 5 Sep 2026 09:03:54 +0800 Subject: [PATCH 0412/1317] xfs: remove several unused and never-implemented declarations Over time a number of function declarations in various headers have become stale: either their implementations were removed when their last callers went away, or they were never implemented in the first place. None of them refer to anything anymore. Remove the following dead declarations and the unused stub: - xlog_assign_tail_lsn() and xlog_assign_tail_lsn_locked() - xfs_iext_realloc() - xfs_buf_iodone() - xfs_scrub_tester() and xfs_scrub_setup_inode_bmap_data() (never implemented placeholders) - the !CONFIG_XFS_ONLINE_REPAIR stub of xrep_tempfile_iolock_both() Signed-off-by: Zizhi Wo Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/common.h | 1 - fs/xfs/scrub/scrub.h | 1 - fs/xfs/scrub/tempfile.h | 4 ---- fs/xfs/xfs_buf_item.h | 1 - fs/xfs/xfs_inode.h | 1 - fs/xfs/xfs_log.h | 2 -- 6 files changed, 10 deletions(-) diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h index 9d627fd50687..f0f073a93413 100644 --- a/fs/xfs/scrub/common.h +++ b/fs/xfs/scrub/common.h @@ -74,7 +74,6 @@ int xchk_setup_ag_rmapbt(struct xfs_scrub *sc); int xchk_setup_ag_refcountbt(struct xfs_scrub *sc); int xchk_setup_inode(struct xfs_scrub *sc); int xchk_setup_inode_bmap(struct xfs_scrub *sc); -int xchk_setup_inode_bmap_data(struct xfs_scrub *sc); int xchk_setup_directory(struct xfs_scrub *sc); int xchk_setup_xattr(struct xfs_scrub *sc); int xchk_setup_symlink(struct xfs_scrub *sc); diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h index 6d7d3523b71f..737a5d6db15f 100644 --- a/fs/xfs/scrub/scrub.h +++ b/fs/xfs/scrub/scrub.h @@ -261,7 +261,6 @@ static inline int xchk_nothing(struct xfs_scrub *sc) } /* Metadata scrubbers */ -int xchk_tester(struct xfs_scrub *sc); int xchk_superblock(struct xfs_scrub *sc); int xchk_agf(struct xfs_scrub *sc); int xchk_agfl(struct xfs_scrub *sc); diff --git a/fs/xfs/scrub/tempfile.h b/fs/xfs/scrub/tempfile.h index 71c1b54599c3..d44ed43bafe0 100644 --- a/fs/xfs/scrub/tempfile.h +++ b/fs/xfs/scrub/tempfile.h @@ -39,10 +39,6 @@ int xrep_tempfile_roll_trans(struct xfs_scrub *sc); void xrep_tempfile_copyout_local(struct xfs_scrub *sc, int whichfork); bool xrep_is_tempfile(const struct xfs_inode *ip); #else -static inline void xrep_tempfile_iolock_both(struct xfs_scrub *sc) -{ - xchk_ilock(sc, XFS_IOLOCK_EXCL); -} # define xrep_is_tempfile(ip) (false) # define xrep_tempfile_adjust_directory_tree(sc) (0) # define xrep_tempfile_rele(sc) diff --git a/fs/xfs/xfs_buf_item.h b/fs/xfs/xfs_buf_item.h index 3159325dd17b..28c79989d725 100644 --- a/fs/xfs/xfs_buf_item.h +++ b/fs/xfs/xfs_buf_item.h @@ -60,7 +60,6 @@ static inline void xfs_buf_dquot_iodone(struct xfs_buf *bp) { } #endif /* CONFIG_XFS_QUOTA */ -void xfs_buf_iodone(struct xfs_buf *); bool xfs_buf_log_check_iovec(struct kvec *iovec); unsigned int xfs_buf_inval_log_space(unsigned int map_count, diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index 34c1038ebfcd..1602027cd0aa 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -585,7 +585,6 @@ uint xfs_ilock_attr_map_shared(struct xfs_inode *); int xfs_ifree(struct xfs_trans *, struct xfs_inode *); int xfs_itruncate_extents_flags(struct xfs_trans **, struct xfs_inode *, int, xfs_fsize_t, int); -void xfs_iext_realloc(xfs_inode_t *, int, int); int xfs_log_force_inode(struct xfs_inode *ip); void xfs_iunpin_wait(xfs_inode_t *); diff --git a/fs/xfs/xfs_log.h b/fs/xfs/xfs_log.h index ca66429bf6c9..f715695e8fcb 100644 --- a/fs/xfs/xfs_log.h +++ b/fs/xfs/xfs_log.h @@ -105,8 +105,6 @@ int xfs_log_mount(struct xfs_mount *mp, int num_bblocks); int xfs_log_mount_finish(struct xfs_mount *mp); void xfs_log_mount_cancel(struct xfs_mount *); -xfs_lsn_t xlog_assign_tail_lsn(struct xfs_mount *mp); -xfs_lsn_t xlog_assign_tail_lsn_locked(struct xfs_mount *mp); void xfs_log_space_wake(struct xfs_mount *mp); int xfs_log_reserve(struct xfs_mount *mp, int length, int count, struct xlog_ticket **ticket, bool permanent); From 45a5f7285f835adb3b74c9344c09a7bd2c4fb664 Mon Sep 17 00:00:00 2001 From: Madhavan Srinivasan Date: Tue, 18 Aug 2026 09:20:37 +0530 Subject: [PATCH 0413/1317] MAINTAINERS: powerpc: Add Ritesh and Shrikanth Ritesh and Shrikanth has been helping in the powerpc mailing list patch reviews, adding them as reviewers. Acked-by: Shrikanth Hegde Acked-by: Ritesh Harjani (IBM) Acked-by: Christophe Leroy (CS GROUP) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260818035037.613186-1-maddy@linux.ibm.com --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..342cc5400fb4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15027,6 +15027,8 @@ M: Madhavan Srinivasan R: Michael Ellerman R: Nicholas Piggin R: Christophe Leroy (CS GROUP) +R: Ritesh Harjani (IBM) +R: Shrikanth Hegde L: linuxppc-dev@lists.ozlabs.org S: Supported W: https://github.com/linuxppc/wiki/wiki From 10e59fbdef13597836bd6459095caa02c80af3d7 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 1 Sep 2026 10:23:09 +0800 Subject: [PATCH 0414/1317] media: v4l2-h264: Fix memcmp() size in B1 reference list comparison In v4l2_h264_build_b_ref_lists(), the B0/B1 list equality check passes the entry count builder->num_valid to memcmp() instead of a byte size. Since struct v4l2_h264_reference is two bytes (fields and index), only half of each list is compared, so distinct lists can be wrongly treated as equal and trigger an incorrect swap(b1_reflist[0], b1_reflist[1]). Change the memcmp() size argument to sizeof(b1_reflist[0]) * builder->num_valid so that the full byte length of both reference lists is compared. Fixes: 624922a2739b ("media: v4l2-core: Add helpers to build the H264 P/B0/B1 reflists") Suggested-by: Nicolas Dufresne Cc: stable@vger.kernel.org Signed-off-by: Haotian Zhang Reviewed-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-h264.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/v4l2-core/v4l2-h264.c b/drivers/media/v4l2-core/v4l2-h264.c index c00197d095e7..2323f559c6a3 100644 --- a/drivers/media/v4l2-core/v4l2-h264.c +++ b/drivers/media/v4l2-core/v4l2-h264.c @@ -440,7 +440,8 @@ v4l2_h264_build_b_ref_lists(const struct v4l2_h264_reflist_builder *builder, } if (builder->num_valid > 1 && - !memcmp(b1_reflist, b0_reflist, builder->num_valid)) + !memcmp(b1_reflist, b0_reflist, + sizeof(b1_reflist[0]) * builder->num_valid)) swap(b1_reflist[0], b1_reflist[1]); print_ref_list_b(builder, b0_reflist, 0); From dc694a9929f7cb9c88ef91e45eb982b7bbe5a477 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:18:58 -0400 Subject: [PATCH 0415/1317] media: v4l2-ctrls: validate HEVC tile counts The stateless HEVC decoders read num_tile_columns_minus1 + 1 entries from column_width_minus1[] and num_tile_rows_minus1 + 1 from row_height_minus1[] and use them as tile-loop bounds, but std_validate_compound() does not bound these u8 counts. Reject a V4L2_CTRL_TYPE_HEVC_PPS with tiling enabled whose tile counts exceed the uAPI array capacity, mirroring the existing compound-control range checks. Fixes: 256fa3920874 ("media: v4l: Add definitions for HEVC stateless decoding") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 5b8a594fb9e2..9b6121a3a2d2 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -1253,6 +1253,18 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx, p_hevc_pps->flags &= ~V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED; + } else { + /* + * These count the entries the stateless HEVC drivers + * read from column_width_minus1[] / row_height_minus1[] + * and use as tile-loop bounds. + */ + if (p_hevc_pps->num_tile_columns_minus1 >= + ARRAY_SIZE(p_hevc_pps->column_width_minus1)) + return -EINVAL; + if (p_hevc_pps->num_tile_rows_minus1 >= + ARRAY_SIZE(p_hevc_pps->row_height_minus1)) + return -EINVAL; } if (p_hevc_pps->flags & From 439058ced617fbb3febc017b9e93bb7387f309e0 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:18:59 -0400 Subject: [PATCH 0416/1317] media: v4l2-ctrls: validate AV1 tile counts The stateless AV1 decoders use tile_info.tile_cols and tile_rows as loop bounds and as indices into the mi_*_starts[] and *_in_sbs_minus_1[] arrays, as the divisor for context_update_tile_id, and their product bounds the per-tile descriptor buffers, but std_validate_compound() does not bound these u8 fields. Reject a V4L2_CTRL_TYPE_AV1_FRAME whose tile_cols or tile_rows exceeds V4L2_AV1_MAX_TILE_COLS / _ROWS, or whose product exceeds V4L2_AV1_MAX_TILE_COUNT. A zero tile count is left to the consuming driver so the zero-initialised control that existing userspace submits is still accepted. Fixes: 9de30f579980 ("media: Add AV1 uAPI") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 9b6121a3a2d2..648b88c868bc 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -793,10 +793,30 @@ static int validate_av1_film_grain(struct v4l2_ctrl_av1_film_grain *fg) return 0; } +static int validate_av1_tile_info(struct v4l2_av1_tile_info *t) +{ + /* + * tile_cols and tile_rows index the per-tile descriptor arrays and + * bound the tile loops in the stateless AV1 drivers; the product + * bounds the total tile descriptor count. + */ + if (t->tile_cols > V4L2_AV1_MAX_TILE_COLS || + t->tile_rows > V4L2_AV1_MAX_TILE_ROWS) + return -EINVAL; + + if ((u32)t->tile_cols * t->tile_rows > V4L2_AV1_MAX_TILE_COUNT) + return -EINVAL; + + return 0; +} + static int validate_av1_frame(struct v4l2_ctrl_av1_frame *f) { int ret = 0; + ret = validate_av1_tile_info(&f->tile_info); + if (ret) + return ret; ret = validate_av1_quantization(&f->quantization); if (ret) return ret; From 592dd4f8442a13bed6e946d73d3164ba38b33bbd Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:00 -0400 Subject: [PATCH 0417/1317] media: hevc: add bounded tile-count helpers The stateless HEVC decoders compute the number of tile columns and rows from num_tile_columns_minus1 / num_tile_rows_minus1 and clamp it to the column_width_minus1[] / row_height_minus1[] capacity before using it as a loop bound. Add shared helpers in a new so the rkvdec and hantro drivers do not each open-code the min_t() clamp. Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-8 Fixes: 256fa3920874 ("media: v4l: Add definitions for HEVC stateless decoding") Cc: stable@vger.kernel.org Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- include/media/v4l2-hevc.h | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 include/media/v4l2-hevc.h diff --git a/include/media/v4l2-hevc.h b/include/media/v4l2-hevc.h new file mode 100644 index 000000000000..973c96be16be --- /dev/null +++ b/include/media/v4l2-hevc.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Helper functions for HEVC stateless codecs. + */ + +#ifndef _MEDIA_V4L2_HEVC_H +#define _MEDIA_V4L2_HEVC_H + +#include +#include + +/** + * v4l2_hevc_pps_num_tile_columns - number of HEVC tile columns, bounded + * @pps: the V4L2 HEVC PPS control + * + * Return the number of tile columns (num_tile_columns_minus1 + 1) clamped to + * the capacity of column_width_minus1[]. The control validation already + * rejects out-of-range counts; this keeps the consuming drivers bounded too. + */ +static inline unsigned int +v4l2_hevc_pps_num_tile_columns(const struct v4l2_ctrl_hevc_pps *pps) +{ + return min_t(unsigned int, pps->num_tile_columns_minus1 + 1, + ARRAY_SIZE(pps->column_width_minus1)); +} + +/** + * v4l2_hevc_pps_num_tile_rows - number of HEVC tile rows, bounded + * @pps: the V4L2 HEVC PPS control + * + * Return the number of tile rows (num_tile_rows_minus1 + 1) clamped to the + * capacity of row_height_minus1[]. + */ +static inline unsigned int +v4l2_hevc_pps_num_tile_rows(const struct v4l2_ctrl_hevc_pps *pps) +{ + return min_t(unsigned int, pps->num_tile_rows_minus1 + 1, + ARRAY_SIZE(pps->row_height_minus1)); +} + +#endif /* _MEDIA_V4L2_HEVC_H */ From 81ad46bb33d8fd279aaa33af5296c648814c964b Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:01 -0400 Subject: [PATCH 0418/1317] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity compute_tiles_uniform() and compute_tiles_non_uniform() loop over num_tile_columns_minus1 + 1 / num_tile_rows_minus1 + 1 entries, and assemble_hw_pps() writes one COLUMN_WIDTH / ROW_HEIGHT register per tile and indexes priv_tbl->param_set[] by pic_parameter_set_id, all taken from the untrusted PPS. Use the bounded v4l2_hevc_pps_num_tile_columns() / v4l2_hevc_pps_num_tile_rows() helpers for the tile loops, and bail out of assemble_hw_pps() before indexing priv_tbl->param_set[] with an out-of-range pic_parameter_set_id, so the writes stay within the hardware tables. Fixes: 3595375c2301 ("media: rkvdec: Add HEVC backend") Fixes: c9a59dc2acc7 ("media: rkvdec: Add HEVC support for the VDPU381 variant") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Signed-off-by: Hans Verkuil --- .../platform/rockchip/rkvdec/rkvdec-hevc-common.c | 14 ++++++++++---- .../media/platform/rockchip/rkvdec/rkvdec-hevc.c | 7 +++++-- .../platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c | 2 ++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c index 9c4a6093af32..2b8e04dd1572 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c @@ -16,6 +16,7 @@ */ #include +#include #include #include "rkvdec.h" @@ -37,15 +38,17 @@ void compute_tiles_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size, s32 pic_in_cts_height, u16 *column_width, u16 *row_height) { const struct v4l2_ctrl_hevc_pps *pps = run->pps; + unsigned int num_cols = v4l2_hevc_pps_num_tile_columns(pps); + unsigned int num_rows = v4l2_hevc_pps_num_tile_rows(pps); int i; - for (i = 0; i < pps->num_tile_columns_minus1 + 1; i++) + for (i = 0; i < num_cols; i++) column_width[i] = ((i + 1) * pic_in_cts_width) / (pps->num_tile_columns_minus1 + 1) - (i * pic_in_cts_width) / (pps->num_tile_columns_minus1 + 1); - for (i = 0; i < pps->num_tile_rows_minus1 + 1; i++) + for (i = 0; i < num_rows; i++) row_height[i] = ((i + 1) * pic_in_cts_height) / (pps->num_tile_rows_minus1 + 1) - (i * pic_in_cts_height) / @@ -57,17 +60,20 @@ void compute_tiles_non_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size s32 pic_in_cts_height, u16 *column_width, u16 *row_height) { const struct v4l2_ctrl_hevc_pps *pps = run->pps; + unsigned int num_cols = v4l2_hevc_pps_num_tile_columns(pps); + unsigned int num_rows = v4l2_hevc_pps_num_tile_rows(pps); s32 sum = 0; int i; - for (i = 0; i < pps->num_tile_columns_minus1; i++) { + /* The last tile entry is written after the loop, so iterate one less. */ + for (i = 0; i < num_cols - 1; i++) { column_width[i] = pps->column_width_minus1[i] + 1; sum += column_width[i]; } column_width[i] = pic_in_cts_width - sum; sum = 0; - for (i = 0; i < pps->num_tile_rows_minus1; i++) { + for (i = 0; i < num_rows - 1; i++) { row_height[i] = pps->row_height_minus1[i] + 1; sum += row_height[i]; } diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c index ff3942f91c5d..88e90c438eb9 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c @@ -12,6 +12,7 @@ * Jeffy Chen */ +#include #include #include "rkvdec.h" @@ -135,6 +136,8 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx, * packet unit). so the driver copy SPS/PPS information to the exact PPS * packet unit for HW accessing. */ + if (pps->pic_parameter_set_id >= ARRAY_SIZE(priv_tbl->param_set)) + return; hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id]; memset(hw_ps, 0, sizeof(*hw_ps)); @@ -253,9 +256,9 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx, if (pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) { /* Userspace also provide column width and row height for uniform spacing */ - for (i = 0; i <= pps->num_tile_columns_minus1; i++) + for (i = 0; i < v4l2_hevc_pps_num_tile_columns(pps); i++) WRITE_PPS(pps->column_width_minus1[i], COLUMN_WIDTH(i)); - for (i = 0; i <= pps->num_tile_rows_minus1; i++) + for (i = 0; i < v4l2_hevc_pps_num_tile_rows(pps); i++) WRITE_PPS(pps->row_height_minus1[i], ROW_HEIGHT(i)); } else { WRITE_PPS(DIV_ROUND_UP(sps->pic_width_in_luma_samples, ctb_size_y) - 1, diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c index d07c74679552..e1936e87f45b 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c @@ -145,6 +145,8 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx, * packet unit). so the driver copy SPS/PPS information to the exact PPS * packet unit for HW accessing. */ + if (pps->pic_parameter_set_id >= ARRAY_SIZE(priv_tbl->param_set)) + return; hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id]; memset(hw_ps, 0, sizeof(*hw_ps)); From 06236b094c899c22c12ac5097935eb6719293de8 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:02 -0400 Subject: [PATCH 0419/1317] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity prepare_tile_info_buffer() writes one entry per tile into the tile_sizes DMA buffer, sized for a grid equal to the PPS uAPI array capacity. Use the bounded v4l2_hevc_pps_num_tile_columns() / v4l2_hevc_pps_num_tile_rows() helpers so the loops stay inside the buffer. Fixes: cb5dd5a0fa51 ("media: hantro: Introduce G2/HEVC decoder") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c index e8c2e83379de..e7a7c7a42467 100644 --- a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c +++ b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c @@ -5,6 +5,8 @@ * Copyright (C) 2020 Safran Passenger Innovations LLC */ +#include + #include "hantro_hw.h" #include "hantro_g2_regs.h" @@ -15,8 +17,8 @@ static void prepare_tile_info_buffer(struct hantro_ctx *ctx) const struct v4l2_ctrl_hevc_pps *pps = ctrls->pps; const struct v4l2_ctrl_hevc_sps *sps = ctrls->sps; u16 *p = (u16 *)((u8 *)ctx->hevc_dec.tile_sizes.cpu); - unsigned int num_tile_rows = pps->num_tile_rows_minus1 + 1; - unsigned int num_tile_cols = pps->num_tile_columns_minus1 + 1; + unsigned int num_tile_rows = v4l2_hevc_pps_num_tile_rows(pps); + unsigned int num_tile_cols = v4l2_hevc_pps_num_tile_columns(pps); unsigned int pic_width_in_ctbs, pic_height_in_ctbs; unsigned int max_log2_ctb_size, ctb_size; bool tiles_enabled, uniform_spacing; From b84f6533a8ed2fd7b282fc7ab4b8efadc745a89c Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:03 -0400 Subject: [PATCH 0420/1317] media: verisilicon: rockchip: guard VPU981 AV1 divisor and tile buffer rockchip_vpu981_av1_dec_set_tile_info() divides context_update_tile_id by tile_info->tile_cols and writes one descriptor per tile into the tile_info DMA buffer, which holds AV1_MAX_TILES entries; tile_cols and tile_rows come from the bitstream. Guard the division against a zero tile_cols by initialising the context-update values to zero and computing them only when tile_cols is non-zero, and stop the descriptor writes once the tile_info buffer is full. The tile geometry written to the hardware registers is left unmodified; the per-dimension and total tile bounds are enforced by the control validation. Fixes: 727a400686a2 ("media: verisilicon: Add Rockchip AV1 decoder") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- .../verisilicon/rockchip_vpu981_hw_av1_dec.c | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index e4e21ad37323..fd00dbd79fe4 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -578,16 +578,30 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) const struct v4l2_av1_tile_info *tile_info = &ctrls->frame->tile_info; const struct v4l2_ctrl_av1_tile_group_entry *group_entry = ctrls->tile_group_entry; - int context_update_y = - tile_info->context_update_tile_id / tile_info->tile_cols; - int context_update_x = - tile_info->context_update_tile_id % tile_info->tile_cols; - int context_update_tile_id = - context_update_x * tile_info->tile_rows + context_update_y; + int context_update_y = 0; + int context_update_x = 0; + int context_update_tile_id = 0; u8 *dst = av1_dec->tile_info.cpu; + u8 *dst_end = dst + av1_dec->tile_info.size; struct hantro_dev *vpu = ctx->dev; int tile0, tile1; + /* + * tile_cols and tile_rows are bounded by the V4L2 control validation + * (V4L2_AV1_MAX_TILE_{COLS,ROWS} and V4L2_AV1_MAX_TILE_COUNT). Guard + * the divisor here, and keep the descriptor writes within the + * AV1_MAX_TILES tile_info buffer below; the register values use the + * unmodified tile geometry. + */ + if (tile_info->tile_cols) { + context_update_y = + tile_info->context_update_tile_id / tile_info->tile_cols; + context_update_x = + tile_info->context_update_tile_id % tile_info->tile_cols; + context_update_tile_id = + context_update_x * tile_info->tile_rows + context_update_y; + } + memset(dst, 0, av1_dec->tile_info.size); for (tile0 = 0; tile0 < tile_info->tile_cols; tile0++) { @@ -598,6 +612,10 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) tile_info->height_in_sbs_minus_1[tile1] + 1; u32 x0 = tile_info->width_in_sbs_minus_1[tile0] + 1; + /* Stop once the tile_info descriptor buffer is full. */ + if (dst + 16 > dst_end) + break; + /* tile size in SB units (width,height) */ *dst++ = x0; *dst++ = 0; @@ -622,6 +640,8 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) *dst++ = (end >> 16) & 255; *dst++ = (end >> 24) & 255; } + if (dst + 16 > dst_end) + break; } hantro_reg_write(vpu, &av1_multicore_expect_context_update, !!(context_update_x == 0)); From 367db8b23c26a913d76ed70457bbcd781c422b49 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:04 -0400 Subject: [PATCH 0421/1317] media: verisilicon: rockchip: reject AV1 frames exceeding the tile capacity rockchip_vpu981_av1_dec_set_tile_info() indexes the tile group entry array by tile1 * tile_cols + tile0, reading up to tile_cols * tile_rows entries, lays out one descriptor per tile in the AV1_MAX_TILES tile_info buffer, and programs the real tile_cols / tile_rows into the hardware. The tile group entry control is a dynamic array sized to the number of entries userspace submitted, independent of tile_cols / tile_rows, so a frame that claims more tiles than entries reads past the array. A frame that claims more than AV1_MAX_TILES tiles also leaves the hardware programmed for more tiles than the descriptor buffer holds. Reject both in prepare_run(): tile_cols * tile_rows must not exceed the submitted entry count or AV1_MAX_TILES. The entry count is read via v4l2_ctrl_find() (ctrl->elems). This mirrors the bound the mediatek AV1 decoder already enforces. Fixes: 727a400686a2 ("media: verisilicon: Add Rockchip AV1 decoder") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- .../verisilicon/rockchip_vpu981_hw_av1_dec.c | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index fd00dbd79fe4..00aa566a4ccd 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -431,20 +431,39 @@ static int rockchip_vpu981_av1_dec_prepare_run(struct hantro_ctx *ctx) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_av1_tile_info *tile_info; + struct v4l2_ctrl *tge; + u32 num_tiles; ctrls->sequence = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_SEQUENCE); if (WARN_ON(!ctrls->sequence)) return -EINVAL; - ctrls->tile_group_entry = - hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY); - if (WARN_ON(!ctrls->tile_group_entry)) + tge = v4l2_ctrl_find(&ctx->ctrl_handler, + V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY); + if (WARN_ON(!tge)) return -EINVAL; + ctrls->tile_group_entry = tge->p_cur.p; ctrls->frame = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_FRAME); if (WARN_ON(!ctrls->frame)) return -EINVAL; + /* + * rockchip_vpu981_av1_dec_set_tile_info() indexes the tile group + * entry array by tile1 * tile_cols + tile0, so it reads up to + * tile_cols * tile_rows entries, and lays out one descriptor per tile + * in the AV1_MAX_TILES tile_info buffer while programming the real + * tile geometry into the hardware. Reject a frame that claims more + * tiles than userspace submitted, or more than the hardware tile + * buffer holds, so the read stays in bounds and the programmed + * geometry matches the descriptors written. + */ + tile_info = &ctrls->frame->tile_info; + num_tiles = (u32)tile_info->tile_cols * tile_info->tile_rows; + if (num_tiles > tge->elems || num_tiles > AV1_MAX_TILES) + return -EINVAL; + ctrls->film_grain = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_FILM_GRAIN); From 37bef2170d4c88fc3d708eecf3ef0f4032bc1372 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:05 -0400 Subject: [PATCH 0422/1317] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity vdec_av1_slice_setup_tile() copies tile_cols + 1 / tile_rows + 1 entries into mi_col_starts[] / mi_row_starts[] from the bitstream tile_info. Bound the copy to the array capacity. Fixes: 0934d3759615 ("media: mediatek: vcodec: separate decoder and encoder") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Signed-off-by: Hans Verkuil --- .../mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c index 2d622e85f827..49d9b4a72387 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c @@ -1299,11 +1299,12 @@ static void vdec_av1_slice_setup_tile(struct vdec_av1_slice_frame *frame, tile->uniform_tile_spacing_flag = BIT_FLAG(ctrl_tile, V4L2_AV1_TILE_INFO_FLAG_UNIFORM_TILE_SPACING); - for (i = 0; i < tile->tile_cols + 1; i++) + /* Bound the copy to the mi_col_starts[]/mi_row_starts[] capacity. */ + for (i = 0; i < tile->tile_cols + 1 && i < V4L2_AV1_MAX_TILE_COLS + 1; i++) tile->mi_col_starts[i] = ALIGN(ctrl_tile->mi_col_starts[i], BIT(mib_size_log2)) >> mib_size_log2; - for (i = 0; i < tile->tile_rows + 1; i++) + for (i = 0; i < tile->tile_rows + 1 && i < V4L2_AV1_MAX_TILE_ROWS + 1; i++) tile->mi_row_starts[i] = ALIGN(ctrl_tile->mi_row_starts[i], BIT(mib_size_log2)) >> mib_size_log2; } From b7313376809292f0e6bf2d5750225c8b66e9ccda Mon Sep 17 00:00:00 2001 From: Nick Pegg Date: Sun, 6 Sep 2026 08:55:37 -0700 Subject: [PATCH 0423/1317] ALSA: usb-audio: Add quirk flags for Behringer UV1 The Behringer UV1 is a microphone audio processor with a USB audio interface, which experiences periodic stutters unless implicit_fb is used. This seems to be a similar device to the Behringer UMC series, so I copied the quirks from those. I've confirmed that my own UV1 works great with these flags set. Signed-off-by: Nick Pegg Link: https://patch.msgid.link/20260906155616.1625465-1-nick@nickpegg.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 5dede66ea69e..c5870901a8d4 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2413,6 +2413,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x1397, 0x050c, /* Behringer Flow 8 */ QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), + DEVICE_FLG(0x1397, 0x0510, /* Behringer UV1 */ + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ From ad7265b29995ff12b2ce834a6a6162d616462362 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 7 Sep 2026 09:55:44 +0200 Subject: [PATCH 0424/1317] .get_maintainer.ignore: add myself Since I've touched so many things all over I get CC'ed on far too many things - add myself here to avoid that. I'm also listed in MAINTAINERS for the right things. Signed-off-by: Johannes Berg --- .get_maintainer.ignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.get_maintainer.ignore b/.get_maintainer.ignore index 5ad082b4dd03..d19b725fd803 100644 --- a/.get_maintainer.ignore +++ b/.get_maintainer.ignore @@ -4,6 +4,8 @@ Alyssa Rosenzweig Askar Safin Christoph Hellwig Jeff Kirsher +Johannes Berg +Johannes Berg Marc Gonzalez Nathan Chancellor Ralf Baechle From 113a9796effe3376d2ec5aabcca1fef4fef4cd62 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 12 Aug 2024 16:19:48 +0200 Subject: [PATCH 0425/1317] tick/broadcast: Plug clockevents replacement race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 朱恺乾 reported and decoded the following race condition when a broadcast device is replaced: CPUA CPUB __tick_broadcast_oneshot_control() bc = tick_broadcast_device.evtdev; tick_install_broadcast_device(dev) clockevents_exchange_device(cur, dev) shutdown(cur); detach(cur); cur->handler = noop; tick_broadcast_device.evtdev = dev; tick_broadcast_set_event(bc, next_event); <- FAIL: arms a detached device. If the original broadcast device has a restricted interrupt affinity mask and the last CPU in that mask goes offline then the BUG() in tick_cleanup_dead_cpu() triggers because the clockevent device is not in detached state. The reason for this is that tick_install_broadcast_device() is not serialized vs. tick broadcast operations. The obvious cure is to serialize tick_install_broadcast_device() with tick_broadcast_lock against a concurrent tick broadcast operation. That requires to split clockevents_exchange_device() into two parts, one which does the exchange, shutdown and detach operation and the other which drops the module reference count. This is required because the module reference cannot be dropped while holding tick_broadcast_lock. Let clockevents_exchange_device() do both operations as before, but let the broadcast device code take the two step approach and do the device exchange under tick_broadcast_lock and drop the module reference count after releasing it. Fixes: f8381cba04ba ("[PATCH] tick-management: broadcast functionality") Reported-by: 朱恺乾 Signed-off-by: Thomas Gleixner Signed-off-by: Thomas Gleixner Reviewed-by: Bradley Morgan Tested-by: 刘术高 Cc: stable@vger.kernel.org Link: https://patch.msgid.link/87cymdsu0r.ffs@tglx --- kernel/time/clockevents.c | 35 +++++++++++++++++++++-------------- kernel/time/tick-broadcast.c | 36 ++++++++++++++++++++++-------------- kernel/time/tick-internal.h | 2 ++ 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 0014d163f989..62ad7c000386 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -615,6 +615,24 @@ void clockevents_handle_noop(struct clock_event_device *dev) { } +void __clockevents_exchange_device(struct clock_event_device *old, + struct clock_event_device *new) +{ + /* + * Caller releases a clock event device. We queue it into the + * released list and do a notify add later. + */ + if (old) { + clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED); + list_move(&old->list, &clockevents_released); + } + + if (new) { + WARN_ON(!clockevent_state_detached(new)); + clockevents_shutdown(new); + } +} + /** * clockevents_exchange_device - release and request clock devices * @old: device to release (can be NULL) @@ -626,20 +644,9 @@ void clockevents_handle_noop(struct clock_event_device *dev) void clockevents_exchange_device(struct clock_event_device *old, struct clock_event_device *new) { - /* - * Caller releases a clock event device. We queue it into the - * released list and do a notify add later. - */ - if (old) { + __clockevents_exchange_device(old, new); + if (old) module_put(old->owner); - clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED); - list_move(&old->list, &clockevents_released); - } - - if (new) { - BUG_ON(!clockevent_state_detached(new)); - clockevents_shutdown(new); - } } /** @@ -699,7 +706,7 @@ void tick_offline_cpu(unsigned int cpu) if (cpumask_test_cpu(cpu, dev->cpumask) && cpumask_weight(dev->cpumask) == 1 && !tick_is_broadcast_device(dev)) { - BUG_ON(!clockevent_state_detached(dev)); + WARN_ON(!clockevent_state_detached(dev)); list_del(&dev->list); } } diff --git a/kernel/time/tick-broadcast.c b/kernel/time/tick-broadcast.c index 115e0bf01276..bda3d2391a60 100644 --- a/kernel/time/tick-broadcast.c +++ b/kernel/time/tick-broadcast.c @@ -165,23 +165,31 @@ static bool tick_set_oneshot_wakeup_device(struct clock_event_device *newdev, */ void tick_install_broadcast_device(struct clock_event_device *dev, int cpu) { - struct clock_event_device *cur = tick_broadcast_device.evtdev; + struct clock_event_device *cur; - if (tick_set_oneshot_wakeup_device(dev, cpu)) - return; + scoped_guard(raw_spinlock_irqsave, &tick_broadcast_lock) { - if (!tick_check_broadcast_device(cur, dev)) - return; + if (tick_set_oneshot_wakeup_device(dev, cpu)) + return; - if (!try_module_get(dev->owner)) - return; + cur = tick_broadcast_device.evtdev; + if (!tick_check_broadcast_device(cur, dev)) + return; - clockevents_exchange_device(cur, dev); + if (!try_module_get(dev->owner)) + return; + + __clockevents_exchange_device(cur, dev); + if (cur) + cur->event_handler = clockevents_handle_noop; + WRITE_ONCE(tick_broadcast_device.evtdev, dev); + if (!cpumask_empty(tick_broadcast_mask)) + tick_broadcast_start_periodic(dev); + } + + /* Module release must be outside of the lock */ if (cur) - cur->event_handler = clockevents_handle_noop; - tick_broadcast_device.evtdev = dev; - if (!cpumask_empty(tick_broadcast_mask)) - tick_broadcast_start_periodic(dev); + module_put(cur->owner); if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT)) return; @@ -1218,7 +1226,7 @@ int tick_broadcast_oneshot_active(void) */ bool tick_broadcast_oneshot_available(void) { - struct clock_event_device *bc = tick_broadcast_device.evtdev; + struct clock_event_device *bc = READ_ONCE(tick_broadcast_device.evtdev); return bc ? bc->features & CLOCK_EVT_FEAT_ONESHOT : false; } @@ -1226,7 +1234,7 @@ bool tick_broadcast_oneshot_available(void) #else int __tick_broadcast_oneshot_control(enum tick_broadcast_state state) { - struct clock_event_device *bc = tick_broadcast_device.evtdev; + struct clock_event_device *bc = READ_ONCE(tick_broadcast_device.evtdev); if (!bc || (bc->features & CLOCK_EVT_FEAT_HRTIMER)) return -EBUSY; diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h index 182974c4f21b..65680db95053 100644 --- a/kernel/time/tick-internal.h +++ b/kernel/time/tick-internal.h @@ -55,6 +55,8 @@ static inline void clockevent_set_state(struct clock_event_device *dev, } extern void clockevents_shutdown(struct clock_event_device *dev); +extern void __clockevents_exchange_device(struct clock_event_device *old, + struct clock_event_device *new); extern void clockevents_exchange_device(struct clock_event_device *old, struct clock_event_device *new); extern void clockevents_switch_state(struct clock_event_device *dev, From 954f7a48fa2ae7310c67729fb556caf726783436 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sat, 5 Sep 2026 10:43:57 +0200 Subject: [PATCH 0426/1317] entry: Guard syscall_enter_audit() invocation with CONFIG_AUDITSYSCALL A bunch of older cross compilers notably RISCV64 and S390 fail to eliminate the dead code when CONFIG_AUDITSYSCALL=n. The code in question is: if (unlikely(audit_context()) syscall_enter_audit(regs); and in case of CONFIG_AUDITSYSCALL=n: static inline struct audit_context *audit_context(void) { return NULL; } which should make the compiler eliminate the syscall_enter_audit() call. But a RISV64 GCC12 cross compiler translates that into: if (unlikely(audit_context())) 1c34: 00000097 auipc ra,0x0 1c38: 000080e7 jalr ra # 1c34 <.L785> 1c3c: c511 beqz a0,1c48 <.L787> syscall_enter_audit(regs); 1c3e: 8526 mv a0,s1 1c40: 00000097 auipc ra,0x0 1c44: 000080e7 jalr ra # 1c40 <.L785+0xc> and then claims in the failing link: include/asm-generic/preempt.h:54:(.noinstr.text+0x1a20): undefined reference to 'syscall_enter_audit' which is obviously hallucination. Add an explicit IS_ENABLED(CONFIG_AUDITSYSCALL) check into the condition to cure this compiler madness. Fixes: 6f25517010dd ("entry: Rework syscall_audit_enter()") Reported-by: kernel test robot Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/87tso45bqq.ffs@fw13 Closes: https://lore.kernel.org/oe-kbuild-all/202609031938.ZvZZaRQy-lkp@intel.com/ --- include/linux/entry-common.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 6574b7183c01..fa2854fed1f2 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -102,7 +102,14 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l if (unlikely(work & SYSCALL_WORK_SYSCALL_TRACEPOINT)) trace_syscall_enter(regs); - if (unlikely(audit_context())) + /* + * The config check works around broken compilers which fail to + * eliminate the dead code in case of CONFIG_AUDITSYSCALL=n as they + * insist on creating a always false runtime condition based on + * audit_context() which returns NULL in that case. The explicit + * IS_ENABLED() check makes that madness go away. + */ + if (IS_ENABLED(CONFIG_AUDITSYSCALL) && unlikely(audit_context())) syscall_enter_audit(regs); return true; From 2c6dc792538260a8087ac5b22c31b3b8e47c85d6 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sat, 22 Aug 2026 14:29:00 +0200 Subject: [PATCH 0427/1317] landlock: Fix use-after-free of the source's parent directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit current_check_refer_path() reads old_dentry->d_parent without holding a reference nor a lock on it, and then dereferences it in collect_domain_accesses() and in the audit record. A reference on a child does not pin its parent: __d_move() reassigns dentry->d_parent and drops the reference the child held on its former parent. hook_path_rename() is not affected because the rename path calls lock_rename() before the hook, so the source cannot be reparented under it. hook_path_link() has no such protection: filename_linkat() holds a reference on the source dentry but neither locks nor references its parent, so a concurrent rename(2) can reparent the source while security_path_link() runs, and the former parent can then be removed and freed while the hook walks it. A process can trigger this after entering a Landlock domain that handles at least one filesystem access right. The process can then race a linkat(2) loop against rename(2) and rmdir(2): BUG: KASAN: slab-use-after-free in collect_domain_accesses+0x278/0x290 Read of size 4 at addr ffff888160bd53f4 by task llrepro2/549 collect_domain_accesses+0x278/0x290 current_check_refer_path+0x952/0x1120 security_path_link+0x1be/0x320 filename_linkat+0x342/0x6d0 __x64_sys_linkat+0xfa/0x150 Freed by task 562: kmem_cache_free+0x139/0x4c0 i_callback+0x4b/0x80 rcu_core+0x7dc/0x10a0 Take a reference on the dentry selected as the source parent, using dget() for the common-mount-root case and dget_parent() otherwise. Release it after the hierarchy walk and synchronous audit logging. Cc: stable@vger.kernel.org Fixes: b91c3e4ea756 ("landlock: Add support for file reparenting with LANDLOCK_ACCESS_FS_REFER") Signed-off-by: Norbert Szetei Reviewed-by: Günther Noack Tested-by: Günther Noack Link: https://patch.msgid.link/E9CDD9E6-E960-4DE2-B1AC-5667D52ABB3E@doyensec.com [mic: Clarify the caller, reachability, and reference handling] Signed-off-by: Mickaël Salaün --- security/landlock/fs.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 30aa6ce13590..330a1871bf94 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -1298,11 +1298,12 @@ static int current_check_refer_path(struct dentry *const old_dentry, /* * old_dentry may be the root of the common mount point and * !IS_ROOT(old_dentry) at the same time (e.g. with open_tree() and - * OPEN_TREE_CLONE). We do not need to call dget(old_parent) because - * we keep a reference to old_dentry. + * OPEN_TREE_CLONE). Pin the dentry used as old_parent in either case. + * Otherwise, dget_parent() safely fetches and pins the current parent + * against a concurrent rename(2). */ - old_parent = (old_dentry == mnt_dir.dentry) ? old_dentry : - old_dentry->d_parent; + old_parent = (old_dentry == mnt_dir.dentry) ? dget(old_dentry) : + dget_parent(old_dentry); /* new_dir->dentry is equal to new_dentry->d_parent */ allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry, @@ -1311,8 +1312,10 @@ static int current_check_refer_path(struct dentry *const old_dentry, allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry, new_dir->dentry, &layer_masks_parent2); - if (allow_parent1 && allow_parent2) + if (allow_parent1 && allow_parent2) { + dput(old_parent); return 0; + } /* * To be able to compare source and destination domain access rights, @@ -1324,8 +1327,10 @@ static int current_check_refer_path(struct dentry *const old_dentry, subject->domain, &mnt_dir, access_request_parent1, &layer_masks_parent1, &request1, old_dentry, access_request_parent2, &layer_masks_parent2, &request2, - exchange ? new_dentry : NULL)) + exchange ? new_dentry : NULL)) { + dput(old_parent); return 0; + } if (request1.access) { request1.audit.u.path.dentry = old_parent; @@ -1335,6 +1340,7 @@ static int current_check_refer_path(struct dentry *const old_dentry, request2.audit.u.path.dentry = new_dir->dentry; landlock_log_denial(subject, &request2); } + dput(old_parent); /* * This prioritizes EACCES over EXDEV for all actions, including From 96bf9831fbf423b8104f7948cd8fe7007ecfb46c Mon Sep 17 00:00:00 2001 From: Chengyu Zhu Date: Mon, 7 Sep 2026 16:33:19 +0800 Subject: [PATCH 0428/1317] erofs: delimit inode_share cache key components Previously, inode_share keys were encoded as follows: fingerprint || domain_id It would be better to have a separator between the fingerprint and domain ID so that the fingerprint won't be parsed as part of a domain ID. Change the key encoding as follows: domain_id || '\0' || fingerprint Since domain_id is a NUL-terminated string, this makes the in-memory key indices unambiguous. Signed-off-by: Chengyu Zhu Reviewed-by: Gao Xiang Fixes: e0bf7d1c074d ("erofs: support user-defined fingerprint name") Signed-off-by: Gao Xiang --- fs/erofs/xattr.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/erofs/xattr.c b/fs/erofs/xattr.c index df7ea019526d..57cfb7520782 100644 --- a/fs/erofs/xattr.c +++ b/fs/erofs/xattr.c @@ -620,8 +620,8 @@ int erofs_xattr_fill_inode_fingerprint(struct erofs_inode_fingerprint *fp, { struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb); struct erofs_xattr_prefix_item *prefix; + int domainlen, valuelen, base_index; const char *infix; - int valuelen, base_index; if (!test_opt(&sbi->opt, INODE_SHARE)) return -EOPNOTSUPP; @@ -633,17 +633,18 @@ int erofs_xattr_fill_inode_fingerprint(struct erofs_inode_fingerprint *fp, valuelen = erofs_getxattr(inode, base_index, infix, NULL, 0); if (valuelen <= 0 || valuelen > (1 << sbi->blkszbits)) return -EFSCORRUPTED; - fp->size = valuelen + (domain_id ? strlen(domain_id) : 0); + domainlen = strlen(domain_id); + fp->size = domainlen + 1 + valuelen; fp->opaque = kmalloc(fp->size, GFP_KERNEL); if (!fp->opaque) return -ENOMEM; + memcpy(fp->opaque, domain_id, domainlen + 1); if (valuelen != erofs_getxattr(inode, base_index, infix, - fp->opaque, valuelen)) { + fp->opaque + domainlen + 1, valuelen)) { kfree(fp->opaque); fp->opaque = NULL; return -EFSCORRUPTED; } - memcpy(fp->opaque + valuelen, domain_id, fp->size - valuelen); return 0; } #endif From e7557b9ef7a87570cbd0873a163de05bde80c39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Mon, 7 Sep 2026 12:35:01 +0200 Subject: [PATCH 0429/1317] selftests/landlock: Test abstract socket trace name limits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The landlock_deny_scope_abstract_unix_socket event captures binary socket names with __string_len(), whose dynamic field reserves an extra byte for the NUL terminator. The printer subtracts this byte before escaping the content. Exercise the minimum accepted address length, which has no name content, and the maximum sockaddr_un length, which has 107 content bytes. Check the exact trace output at both boundaries. The existing stream and datagram variants share this event, so the boundary variants only need the stream path. Because these boundary names are fixed, run the fixture in a private network namespace. Abstract UNIX socket names are scoped by network namespace, preventing concurrent bind() calls from colliding. The lower-bound test confirms that the subtraction recovers zero instead of underflowing. Cc: Günther Noack Link: https://patch.msgid.link/CAL4aGcVcT0VWVFmGi_vLqxxZ9KdOHfGXYZtKjBdvoUyFjbu5=A@mail.gmail.com Link: https://patch.msgid.link/20260907103503.109461-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- .../landlock/scoped_abstract_unix_test.c | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c index 6dbe863ea571..5dc0debacb2a 100644 --- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c @@ -1222,7 +1222,7 @@ FIXTURE_SETUP(trace_unix) int ret; set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(0, unshare(CLONE_NEWNS)); + ASSERT_EQ(0, unshare(CLONE_NEWNS | CLONE_NEWNET)); ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)); ret = tracefs_fixture_setup(); @@ -1252,6 +1252,11 @@ FIXTURE_TEARDOWN(trace_unix) clear_cap(_metadata, CAP_SYS_ADMIN); } +static const char + trace_unix_max_name[sizeof(((struct sockaddr_un *)0)->sun_path)] = { + [0 ... sizeof(trace_unix_max_name) - 2] = 'x', + }; + /* clang-format off */ FIXTURE_VARIANT(trace_unix) { /* clang-format on */ @@ -1259,6 +1264,8 @@ FIXTURE_VARIANT(trace_unix) { bool sandbox; bool sandbox_target; /* Peer owned by a domain: peer_domain != 0. */ int expect_denied; + const char *name; /* NULL generates a PID-based binary name. */ + size_t name_len; }; /* clang-format off */ @@ -1281,6 +1288,26 @@ FIXTURE_VARIANT_ADD(trace_unix, stream_allowed) { .sandbox_target = false, .expect_denied = 0, }; +/* Stream: lower abstract-name length boundary. */ +FIXTURE_VARIANT_ADD(trace_unix, stream_denied_empty_name) { + .sock_type = SOCK_STREAM, + .sandbox = true, + .sandbox_target = false, + .expect_denied = 1, + .name = "", + .name_len = 0, +}; + +/* Stream: upper abstract-name length boundary. */ +FIXTURE_VARIANT_ADD(trace_unix, stream_denied_max_name) { + .sock_type = SOCK_STREAM, + .sandbox = true, + .sandbox_target = false, + .expect_denied = 1, + .name = trace_unix_max_name, + .name_len = sizeof(trace_unix_max_name) - 1, +}; + /* Datagram: sandboxed client sendto() an unsandboxed peer (peer_domain=0). */ FIXTURE_VARIANT_ADD(trace_unix, dgram_denied) { .sock_type = SOCK_DGRAM, .sandbox = true, @@ -1304,12 +1331,11 @@ FIXTURE_VARIANT_ADD(trace_unix, dgram_allowed) { /* * A sandboxed thread reaching an abstract unix socket peer through connect(2) * (stream) or sendto(2) (datagram) is denied and emits - * landlock_deny_scope_abstract_unix_socket. The abstract name is crafted with - * a space and an embedded NUL followed by an "END" marker to check the - * tracepoint escaping and its length handling (a raw space would break the - * sun_path field regex; strlen() would truncate at the NUL and drop "END"). - * peer_pid is only meaningful for a stream peer (a datagram peer has no - * SO_PEERCRED), so it is asserted only there. + * landlock_deny_scope_abstract_unix_socket. The default abstract name has a + * space and an embedded NUL followed by an "END" marker to check escaping and + * binary length handling. Additional stream variants cover the minimum and + * maximum abstract-name lengths. peer_pid is only meaningful for a stream peer + * (a datagram peer has no SO_PEERCRED), so it is asserted only there. */ TEST_F(trace_unix, deny_scope_unix) { @@ -1336,12 +1362,19 @@ TEST_F(trace_unix, deny_scope_unix) ASSERT_LE(0, server_fd); addr.sun_path[0] = '\0'; - name_len = snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, - "landlock_trace_test_%d ", getpid()); - addr.sun_path[1 + name_len] = '\0'; - memcpy(addr.sun_path + 1 + name_len + 1, "END", 3); - addr_len = - offsetof(struct sockaddr_un, sun_path) + 1 + name_len + 1 + 3; + if (variant->name) { + ASSERT_LE(variant->name_len, sizeof(addr.sun_path) - 1); + memcpy(addr.sun_path + 1, variant->name, variant->name_len); + name_len = variant->name_len; + } else { + name_len = snprintf(addr.sun_path + 1, + sizeof(addr.sun_path) - 1, + "landlock_trace_test_%d ", getpid()); + addr.sun_path[1 + name_len] = '\0'; + memcpy(addr.sun_path + 1 + name_len + 1, "END", 3); + name_len += 1 + 3; + } + addr_len = offsetof(struct sockaddr_un, sun_path) + 1 + name_len; ASSERT_EQ(0, bind(server_fd, (struct sockaddr *)&addr, addr_len)); if (variant->sock_type == SOCK_STREAM) @@ -1430,19 +1463,18 @@ TEST_F(trace_unix, deny_scope_unix) count, buf); } - /* - * sun_path is escaped: a raw space would break this field's [^ ]*$ - * regex, so a successful extract proves the space was escaped, and its - * full length is honored: the "END" marker after the embedded NUL must - * survive (strlen() would truncate it at the NUL). - */ ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(TRACE_TASK), "sun_path", field, sizeof(field))); - EXPECT_NE(NULL, strstr(field, "END")) - { - TH_LOG("sun_path truncated or unescaped: %s", field); + if (variant->name) { + EXPECT_STREQ(variant->name, field); + } else { + /* An embedded NUL must not truncate the following marker. */ + EXPECT_NE(NULL, strstr(field, "END")) + { + TH_LOG("sun_path truncated or unescaped: %s", field); + } } /* peer_pid is the parent's PID for a stream peer (0 for datagram). */ From 353a95f1cd8da8a5436a3f070be07d2f484486cd Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 09:23:25 +0200 Subject: [PATCH 0430/1317] syscall_user_dispatch: Use CONFIG_SYSCTL for sysctl guard Commit 8d75c338f0bc ("sysctl: remove CONFIG_PROC_SYSCTL, it just mirrors CONFIG_SYSCTL") removed CONFIG_PROC_SYSCTL, but the sysctl added by commit 5b6e32ba7b59 ("syscall_user_dispatch: Add kernel.syscall_user_dispatch sysctl") is still guarded by it. Now that both commits are merged, kernel.syscall_user_dispatch is no longer registered. syscall_user_dispatch_allowed defaults to true. SUD therefore remains available, but administrators cannot disable new activations. Use CONFIG_SYSCTL for the guard and documentation. Fixes: 5b6e32ba7b59 ("syscall_user_dispatch: Add kernel.syscall_user_dispatch sysctl") Assisted-by: Codex:gpt-5.6-sol Acked-by: Oleg Nesterov Reviewed-by: Joel Granados Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Reviewed-by: Bradley Morgan Signed-off-by: Joel Granados --- Documentation/admin-guide/sysctl/kernel.rst | 2 +- kernel/entry/syscall_user_dispatch.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst index b6328cd0f43e..ffea61d448eb 100644 --- a/Documentation/admin-guide/sysctl/kernel.rst +++ b/Documentation/admin-guide/sysctl/kernel.rst @@ -1416,7 +1416,7 @@ Controls whether userspace may arm Syscall User Dispatch via == =================================================================== Only present when the kernel is built with ``CONFIG_SYSCALL_USER_DISPATCH`` -and ``CONFIG_PROC_SYSCTL``. +and ``CONFIG_SYSCTL``. sysctl_writes_strict diff --git a/kernel/entry/syscall_user_dispatch.c b/kernel/entry/syscall_user_dispatch.c index 2002c7aae435..59c861866941 100644 --- a/kernel/entry/syscall_user_dispatch.c +++ b/kernel/entry/syscall_user_dispatch.c @@ -178,7 +178,7 @@ int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long siz (char __user *)(uintptr_t)cfg.selector); } -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static const struct ctl_table syscall_user_dispatch_sysctls[] = { { .procname = "syscall_user_dispatch", @@ -195,4 +195,4 @@ static int __init syscall_user_dispatch_sysctl_init(void) return 0; } late_initcall(syscall_user_dispatch_sysctl_init); -#endif /* CONFIG_PROC_SYSCTL */ +#endif /* CONFIG_SYSCTL */ From d5bcf9ccaa357396089bbfa47fc82b093f109b1e Mon Sep 17 00:00:00 2001 From: Fangyu Yu Date: Tue, 1 Sep 2026 21:39:18 +0800 Subject: [PATCH 0431/1317] iommu/riscv: Add command queue lock Add a raw spinlock to the RISC-V IOMMU queue state so command queue publishing can be serialized by a later change. Fixes: 856c0cfe5c5f ("iommu/riscv: Command and fault queue support") Signed-off-by: Fangyu Yu Reviewed-by: Nutty Liu Signed-off-by: Joerg Roedel --- drivers/iommu/riscv/iommu.c | 1 + drivers/iommu/riscv/iommu.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index cec3ddd7ab10..2c0dcc90cf85 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -1560,6 +1560,7 @@ int riscv_iommu_init(struct riscv_iommu_device *iommu) int rc; RISCV_IOMMU_QUEUE_INIT(&iommu->cmdq, CQ); + raw_spin_lock_init(&iommu->cmdq.lock); RISCV_IOMMU_QUEUE_INIT(&iommu->fltq, FQ); rc = riscv_iommu_init_check(iommu); diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h index 46df79dd5495..5676001548cc 100644 --- a/drivers/iommu/riscv/iommu.h +++ b/drivers/iommu/riscv/iommu.h @@ -12,6 +12,7 @@ #define _RISCV_IOMMU_H_ #include +#include #include #include @@ -23,6 +24,7 @@ struct riscv_iommu_queue { atomic_t prod; /* unbounded producer allocation index */ atomic_t head; /* unbounded shadow ring buffer consumer index */ atomic_t tail; /* unbounded shadow ring buffer producer index */ + raw_spinlock_t lock; /* serialize queue publishing */ unsigned int mask; /* index mask, queue length - 1 */ unsigned int irq; /* allocated interrupt number */ struct riscv_iommu_device *iommu; /* iommu device handling the queue when active */ From ca58afa40946acd252a50fa4d4a86f15847a3d7d Mon Sep 17 00:00:00 2001 From: Fangyu Yu Date: Tue, 1 Sep 2026 21:39:19 +0800 Subject: [PATCH 0432/1317] iommu/riscv: Serialize command queue publishing Serialize command queue publishing so software producer state advances only after a command is written and the hardware tail is updated. Wait for hardware consumption outside the queue lock when the command queue is full so other CPUs are not blocked behind a long poll. Fixes: 856c0cfe5c5f ("iommu/riscv: Command and fault queue support") Signed-off-by: Fangyu Yu Signed-off-by: Joerg Roedel --- drivers/iommu/riscv/iommu.c | 102 +++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index 2c0dcc90cf85..e335beb70e42 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -382,77 +382,93 @@ static int riscv_iommu_queue_wait(struct riscv_iommu_queue *queue, (int)(cons - index) > 0, 0, timeout_us); } -/* Enqueue an entry and wait to be processed if timeout_us > 0 - * - * Error handling for IOMMU hardware not responding in reasonable time - * will be added as separate patch series along with other RAS features. - * For now, only report hardware failure and continue. - */ +static int riscv_iommu_queue_wait_for_space(struct riscv_iommu_queue *queue, + unsigned int last) +{ + unsigned int head; + unsigned int tail; + unsigned int hw_head; + unsigned long flags; + int ret; + + ret = riscv_iommu_readl_timeout(queue->iommu, Q_HEAD(queue), hw_head, + !(hw_head & ~queue->mask) && hw_head != last, + 0, RISCV_IOMMU_QUEUE_TIMEOUT); + if (ret) + return ret; + + raw_spin_lock_irqsave(&queue->lock, flags); + head = atomic_read(&queue->head); + tail = atomic_read(&queue->tail); + if ((tail - head) >= queue->mask) { + last = Q_ITEM(queue, head); + /* + * Re-read hw_head under the lock so that it is consistent with + * the freshly computed 'last'. Using the pre-lock snapshot + * could produce a stale value that wraps around relative to the + * new 'last', advancing the shadow head past entries that have + * not yet been consumed by the hardware. + */ + hw_head = riscv_iommu_readl(queue->iommu, Q_HEAD(queue)); + if (!(hw_head & ~queue->mask) && hw_head != last) + atomic_add((hw_head - last) & queue->mask, &queue->head); + } + raw_spin_unlock_irqrestore(&queue->lock, flags); + + return 0; +} + +/* Enqueue an entry and publish it to the hardware queue. */ static unsigned int riscv_iommu_queue_send(struct riscv_iommu_queue *queue, void *entry, size_t entry_size) { unsigned int prod; unsigned int head; - unsigned int tail; unsigned long flags; + int ret; - /* Do not preempt submission flow. */ - local_irq_save(flags); + /* 1. Wait for space availability and reserve the next slot. */ + for (;;) { + raw_spin_lock_irqsave(&queue->lock, flags); - /* 1. Allocate some space in the queue */ - prod = atomic_inc_return(&queue->prod) - 1; - head = atomic_read(&queue->head); + prod = atomic_read(&queue->tail); + head = atomic_read(&queue->head); - /* 2. Wait for space availability. */ - if ((prod - head) > queue->mask) { - if (readx_poll_timeout(atomic_read, &queue->head, - head, (prod - head) < queue->mask, - 0, RISCV_IOMMU_QUEUE_TIMEOUT)) + if ((prod - head) < queue->mask) + break; + + head = Q_ITEM(queue, head); + raw_spin_unlock_irqrestore(&queue->lock, flags); + + ret = riscv_iommu_queue_wait_for_space(queue, head); + if (ret) goto err_busy; - } else if ((prod - head) == queue->mask) { - const unsigned int last = Q_ITEM(queue, head); - - if (riscv_iommu_readl_timeout(queue->iommu, Q_HEAD(queue), head, - !(head & ~queue->mask) && head != last, - 0, RISCV_IOMMU_QUEUE_TIMEOUT)) - goto err_busy; - atomic_add((head - last) & queue->mask, &queue->head); } - /* 3. Store entry in the ring buffer */ + /* 2. Store entry in the ring buffer. */ memcpy(queue->base + Q_ITEM(queue, prod) * entry_size, entry, entry_size); - /* 4. Wait for all previous entries to be ready */ - if (readx_poll_timeout(atomic_read, &queue->tail, tail, prod == tail, - 0, RISCV_IOMMU_QUEUE_TIMEOUT)) - goto err_busy; - - /* - * 5. Make sure the ring buffer update (whether in normal or I/O memory) is - * completed and visible before signaling the tail doorbell to fetch - * the next command. 'fence ow, ow' - */ + /* 3. Make sure the entry is visible before updating the queue tail. */ dma_wmb(); riscv_iommu_writel(queue->iommu, Q_TAIL(queue), Q_ITEM(queue, prod + 1)); /* - * 6. Make sure the doorbell write to the device has finished before updating - * the shadow tail index in normal memory. 'fence o, w' + * 4. Make sure the doorbell write to the device has finished before + * updating the shadow tail index in normal memory. 'fence o, w' */ #ifdef CONFIG_MMIOWB mmiowb(); #endif - atomic_inc(&queue->tail); + atomic_set(&queue->tail, prod + 1); + atomic_set(&queue->prod, prod + 1); - /* 7. Complete submission and restore local interrupts */ - local_irq_restore(flags); + raw_spin_unlock_irqrestore(&queue->lock, flags); return prod; err_busy: - local_irq_restore(flags); + /* Report the failure and continue; full RAS recovery is not implemented. */ dev_err_once(queue->iommu->dev, "Hardware error: command enqueue failed\n"); - return prod; } From 4c50bec3d54288230aafb7fe3d2930d42beb14fd Mon Sep 17 00:00:00 2001 From: Fangyu Yu Date: Tue, 1 Sep 2026 21:39:20 +0800 Subject: [PATCH 0433/1317] iommu/riscv: Avoid waiting on failed command enqueue Do not wait for IOFENCE.C completion when the command failed to enter the queue. The command was not published to hardware, so waiting for its producer index can only report a misleading execution timeout. Fixes: 856c0cfe5c5f ("iommu/riscv: Command and fault queue support") Signed-off-by: Fangyu Yu Signed-off-by: Joerg Roedel --- drivers/iommu/riscv/iommu.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index e335beb70e42..fe8e6d0f8a23 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -419,8 +419,9 @@ static int riscv_iommu_queue_wait_for_space(struct riscv_iommu_queue *queue, } /* Enqueue an entry and publish it to the hardware queue. */ -static unsigned int riscv_iommu_queue_send(struct riscv_iommu_queue *queue, - void *entry, size_t entry_size) +static int riscv_iommu_queue_send(struct riscv_iommu_queue *queue, + void *entry, size_t entry_size, + unsigned int *out_prod) { unsigned int prod; unsigned int head; @@ -462,14 +463,16 @@ static unsigned int riscv_iommu_queue_send(struct riscv_iommu_queue *queue, atomic_set(&queue->tail, prod + 1); atomic_set(&queue->prod, prod + 1); - raw_spin_unlock_irqrestore(&queue->lock, flags); + if (out_prod) + *out_prod = prod; - return prod; + raw_spin_unlock_irqrestore(&queue->lock, flags); + return 0; err_busy: /* Report the failure and continue; full RAS recovery is not implemented. */ dev_err_once(queue->iommu->dev, "Hardware error: command enqueue failed\n"); - return prod; + return ret; } /* @@ -508,7 +511,7 @@ static irqreturn_t riscv_iommu_cmdq_process(int irq, void *data) static void riscv_iommu_cmd_send(struct riscv_iommu_device *iommu, struct riscv_iommu_command *cmd) { - riscv_iommu_queue_send(&iommu->cmdq, cmd, sizeof(*cmd)); + riscv_iommu_queue_send(&iommu->cmdq, cmd, sizeof(*cmd), NULL); } /* Send IOFENCE.C command and wait for all scheduled commands to complete. */ @@ -517,9 +520,12 @@ static void riscv_iommu_cmd_sync(struct riscv_iommu_device *iommu, { struct riscv_iommu_command cmd; unsigned int prod; + int ret; riscv_iommu_cmd_iofence(&cmd); - prod = riscv_iommu_queue_send(&iommu->cmdq, &cmd, sizeof(cmd)); + ret = riscv_iommu_queue_send(&iommu->cmdq, &cmd, sizeof(cmd), &prod); + if (ret) + return; if (!timeout_us) return; From 20db6573301e66cd65ebf6c130b6563c69374d9d Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Tue, 18 Aug 2026 21:13:17 +0200 Subject: [PATCH 0434/1317] iommu/s390: Fix NULL dereference in iova_to_phys() with ZPCI_TABLE_TYPE_RFX When using a 5-level translation table via ZPCI_TABLE_TYPE_RFX get_rso_from_iova() returns NULL when the region-first entry is invalid. Yet in get_rto_from_iova() the region-second origin rso is not checked to be non-NULL before accessing rso[rsx] leading to a NULL pointer dereference instead of a NULL return when iova_to_phys() is called on a unmapped IOVA. Fix this by adding the missing NULL check. Cc: stable@vger.kernel.org Fixes: 81244074b518 ("iommu/s390: allow larger region tables") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Matthew Rosato Reviewed-by: Farhan Ali Signed-off-by: Joerg Roedel --- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c index f148f559ac56..58ca7727b7f2 100644 --- a/drivers/iommu/s390-iommu.c +++ b/drivers/iommu/s390-iommu.c @@ -974,6 +974,8 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain, case ZPCI_TABLE_TYPE_RFX: case ZPCI_TABLE_TYPE_RSX: rso = get_rso_from_iova(domain, iova); + if (!rso) + return NULL; rsx = calc_rsx(iova); rse = READ_ONCE(rso[rsx]); if (!reg_entry_isvalid(rse)) From 00a7dd64888d6dd72110b40e2824a088cf7b7386 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 19 Aug 2026 05:23:49 +0200 Subject: [PATCH 0435/1317] iommu/amd: Do not reallocate GA log buffers on resume Commit c5e1a1eb9279 ("iommu/amd: Simplify and Consolidate Virtual APIC (AVIC) Enablement") moved the GA log allocation from iommu_init_pci() to enable_iommus_vapic(), which is called on every resume. iommu_init_ga_log() assigns iommu->ga_log and iommu->ga_log_tail unconditionally. Each resume therefore replaces the boot-time pointers and leaks both old allocations. The function also uses GFP_KERNEL from a syscore resume callback, where interrupts are disabled and the non-boot CPUs are offline. Return early if both buffers are already allocated. Clear the pointers in free_ga_log() so a partial allocation failure cannot leave ga_log dangling. Fixes: c5e1a1eb9279 ("iommu/amd: Simplify and Consolidate Virtual APIC (AVIC) Enablement") Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Reviewed-by: Vasant Hegde Reviewed-by: Ankit Soni Signed-off-by: Joerg Roedel --- drivers/iommu/amd/init.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index 40726dfef273..c6b106d5921e 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -909,7 +909,9 @@ static void free_ga_log(struct amd_iommu *iommu) { #ifdef CONFIG_IRQ_REMAP iommu_free_pages(iommu->ga_log); + iommu->ga_log = NULL; iommu_free_pages(iommu->ga_log_tail); + iommu->ga_log_tail = NULL; #endif } @@ -956,6 +958,9 @@ static int iommu_init_ga_log(struct amd_iommu *iommu) if (WARN_ON_ONCE(!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir))) return -EINVAL; + if (iommu->ga_log && iommu->ga_log_tail) + return 0; + iommu->ga_log = iommu_alloc_pages_node_sz(nid, GFP_KERNEL, GA_LOG_SIZE); if (!iommu->ga_log) goto err_out; From eb29b7bbc8ba28bbb0b9fdd655e931e1d1fa625c Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Mon, 24 Aug 2026 06:29:07 +0000 Subject: [PATCH 0436/1317] iommu/amd: Fix premature break in init_iommu_one() again Commit 283d245468a2 ("iommu/amd: Fix premature break in init_iommu_one()") unintentionally broke older platforms - such as the ASRockRack B550D4-4L - where the BIOS advertises incorrect IOMMU features. Move the HATDis check ahead of the GASup check, and re-introduce the break inside the GASup check to restore correct behavior on affected platforms. This is a short-term fix to resolve the regression. Longer term, we should rework how EFRs are tracked and prioritize the MMIO-advertised EFR over the one reported via IVRS. That requires more extensive changes and will be addressed separately. Fixes: 283d245468a2 ("iommu/amd: Fix premature break in init_iommu_one()") Reported-by: Andreas Juch Closes: https://lore.kernel.org/linux-iommu/07b2d390-f7a0-47e2-bc2c-eb0853acf52e@juch.cc/ Tested-by: Andreas Juch Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/init.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index c6b106d5921e..edcc187b8f14 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -1922,19 +1922,20 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h, else iommu->mmio_phys_end = MMIO_CNTR_CONF_OFFSET; - /* XT and GAM require GA mode. */ - if ((h->efr_reg & (0x1 << IOMMU_EFR_GASUP_SHIFT)) == 0) { - amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY; - } else { - if (h->efr_reg & BIT(IOMMU_EFR_XTSUP_SHIFT)) - amd_iommu_xt_mode = IRQ_REMAP_X2APIC_MODE; - } - if (h->efr_attr & BIT(IOMMU_IVHD_ATTR_HATDIS_SHIFT)) { pr_warn_once("Host Address Translation is not supported.\n"); amd_iommu_hatdis = true; } + /* XT and GAM require GA mode. */ + if ((h->efr_reg & (0x1 << IOMMU_EFR_GASUP_SHIFT)) == 0) { + amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY; + break; + } else { + if (h->efr_reg & BIT(IOMMU_EFR_XTSUP_SHIFT)) + amd_iommu_xt_mode = IRQ_REMAP_X2APIC_MODE; + } + early_iommu_features_init(iommu, h); break; From fa5c0827f0b7bac6d0a188f10118151769ae68fd Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 25 Aug 2026 15:35:54 +0530 Subject: [PATCH 0437/1317] iommu/amd: Fix ineffective error check in nested domain allocation amd_iommu_pdom_id_alloc() returns an int: a domain ID on success, or the negative errno from ida_alloc_range() when the ID space is exhausted or memory is short. amd_iommu_alloc_domain_nested() stores that return value in gdom_info->hdom_id, which is a u32, and only then tests it: gdom_info->hdom_id = amd_iommu_pdom_id_alloc(); if (gdom_info->hdom_id <= 0) { The assignment discards the sign, so -ENOSPC becomes 0xffffffe4 and the test never fires. The nested domain is then set up with a host domain ID that was never allocated, instead of the allocation failing with -ENOSPC. Keep the value in an int, test it there, and store it only once it is known to be valid, which is what the other amd_iommu_pdom_id_alloc() callers already do. Fixes: 757d2b1fdf5b ("iommu/amd: Introduce gDomID-to-hDomID Mapping and handle parent domain invalidation") Signed-off-by: Hemanth Selam Reviewed-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/nested.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/amd/nested.c b/drivers/iommu/amd/nested.c index 63b53b29e029..f1c7987fc585 100644 --- a/drivers/iommu/amd/nested.c +++ b/drivers/iommu/amd/nested.c @@ -96,7 +96,7 @@ struct iommu_domain * amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, const struct iommu_user_data *user_data) { - int ret; + int ret, hdom_id; unsigned long irqflags; struct nested_domain *ndom; struct guest_domain_mapping_info *gdom_info; @@ -161,8 +161,8 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, } /* The gDomID does not exist. We allocate new hdom_id */ - gdom_info->hdom_id = amd_iommu_pdom_id_alloc(); - if (gdom_info->hdom_id <= 0) { + hdom_id = amd_iommu_pdom_id_alloc(); + if (hdom_id <= 0) { __xa_cmpxchg(&aviommu->gdomid_array, ndom->gdom_id, gdom_info, NULL, GFP_ATOMIC); xa_unlock_irqrestore(&aviommu->gdomid_array, irqflags); @@ -170,6 +170,7 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, goto out_err_gdom_info; } + gdom_info->hdom_id = hdom_id; ndom->gdom_info = gdom_info; refcount_set(&gdom_info->users, 1); From adbd8a08208dc64bb1381f51b4f11ffdce1343fa Mon Sep 17 00:00:00 2001 From: Daasaradhi Mannava Date: Sat, 5 Sep 2026 15:49:00 +0000 Subject: [PATCH 0438/1317] MAINTAINERS: Drop the nonexistent vsi-iommu.h file entry Commit 917ace84b770 ("iommu: Add verisilicon IOMMU driver") added the VERISILICON IOMMU DRIVER section, including a file entry for include/linux/vsi-iommu.h. That header is not present in the tree and no file includes it; the driver in drivers/iommu/vsi-iommu.c is self-contained. scripts/get_maintainer.pl --self-test=patterns reports the pattern as matching no file. Drop the stale entry so the section only lists files that exist. Assisted-by: LLM Signed-off-by: Daasaradhi Mannava Reviewed-by: Benjamin Gaignard Signed-off-by: Joerg Roedel --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..0c4ef770807f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28607,7 +28607,6 @@ L: iommu@lists.linux.dev S: Maintained F: Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml F: drivers/iommu/vsi-iommu.c -F: include/linux/vsi-iommu.h VF610 NAND DRIVER M: Stefan Agner From cdb669a3b8f844aca71fc3224990157d61562165 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Mon, 24 Aug 2026 11:14:19 +0800 Subject: [PATCH 0439/1317] HID: quirks: add ALWAYS_POLL quirk for SDINNOVATION gaming keyboard The SDINNOVATION gaming keyboard (USB ID 36ae:feab) stops reporting input events after its RGB lighting mode is switched about twice. Disabling USB autosuspend and unbinding the other HID interfaces make no difference; the issue does not occur on Windows. HID_QUIRK_ALWAYS_POLL alone resolves it, verified on 7.1.8 via usbhid.quirks=0x36ae:0xfeab:0x400. Reported-by: Marco Carvalho Link: https://bugzilla.redhat.com/show_bug.cgi?id=2514627 Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-quirks.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index b3aca5aa9176..8b4f4b02aec0 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1296,6 +1296,9 @@ #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 +#define USB_VENDOR_ID_SDINNOVATION 0x36ae +#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab + #define USB_VENDOR_ID_SEMICO 0x1a2c #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 8a0b51d47040..c8c6b29fc04d 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -186,6 +186,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, From e317326d1755ea054b98e7a4833b929157461c35 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 19 Aug 2026 21:05:17 +0200 Subject: [PATCH 0440/1317] hwmon: (ltc4282) Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. ltc428_clk_provider_setup() does not fill in any parent clocks, and assumes that init.num_parents is NULL. However, the latter in uninitialized, and thus may cause a crash. Make sure all members are fully initialized, to fix such bugs, and to avoid future breakage when converting drivers to a different method for specifying the parents. Fixes: cbc29538dbf7d740 ("hwmon: Add driver for LTC4282") Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/8ec3c5cbd2df675a938f090470f5da5f22008517.1787165329.git.geert+renesas@glider.be Reviewed-by: Brian Masney Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4282.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index b1675dc5b3c7..54ba4b8542e9 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -1106,7 +1106,7 @@ static const struct clk_ops ltc4282_ops = { static int ltc428_clk_provider_setup(struct ltc4282_state *st, struct device *dev) { - struct clk_init_data init; + struct clk_init_data init = {}; int ret; if (!IS_ENABLED(CONFIG_COMMON_CLK)) From 9607c245ca6674955e5e43e3606db410ae9e0b90 Mon Sep 17 00:00:00 2001 From: Nikhil Gurudasani Date: Wed, 19 Aug 2026 23:37:01 +0530 Subject: [PATCH 0441/1317] hwmon: (mcp9982) Propagate one-shot polling errors When a device is in standby, the driver starts a one-shot conversion and polls the BUSY flag before reading temperature, alarm, or fault data. The poll result is currently ignored. Therefore, a timeout or a status-register read failure can be hidden by a later successful read, causing stale data to be returned as valid. Return the polling error before reading the requested attribute. Fixes: e2fe950f34e5 ("hwmon: add support for MCP998X") Cc: stable@vger.kernel.org Signed-off-by: Nikhil Gurudasani Link: https://patch.msgid.link/20260819180701.34797-1-nikhilgurudasani314@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/mcp9982.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/mcp9982.c b/drivers/hwmon/mcp9982.c index 9e19e2697e25..3918dc36c946 100644 --- a/drivers/hwmon/mcp9982.c +++ b/drivers/hwmon/mcp9982.c @@ -395,6 +395,8 @@ static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 at reg_status, !(reg_status & MCP9982_STATUS_BUSY), MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_US * 10); + if (ret) + return ret; break; } break; From a2471ed17b0e6ff7bfb6b2ea8e6e5b04c309d293 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 19 Aug 2026 03:33:17 +0000 Subject: [PATCH 0442/1317] hwmon: (gpio-fan) Fix use-after-free in alarm work fan_alarm_irq_handler() queues fan_data->alarm_work, but nothing cancels it. fan_alarm_notify() dereferences fan_data and its hwmon device. On unbind, devres frees the interrupt, which only waits for the handler itself, and then releases the hwmon device and fan_data, so a pending fan_alarm_notify() can run after those frees. Replace INIT_WORK() with devm_work_autocancel(), registered before devm_request_irq(). The devres cleanup then frees the interrupt first, so no new work can be queued, and cancels the work while fan_data and the hwmon device are still alive. This issue was found by an in-house static analysis tool. Fixes: d6fe1360f42e ("hwmon: add generic GPIO fan driver") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260819033317.446191-1-fanwu01@zju.edu.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index 084828e1e281..7f36e5f6f223 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,7 @@ static DEVICE_ATTR_RO(fan1_alarm); static int fan_alarm_init(struct gpio_fan_data *fan_data) { int alarm_irq; + int err; struct device *dev = fan_data->dev; /* @@ -94,7 +96,11 @@ static int fan_alarm_init(struct gpio_fan_data *fan_data) if (alarm_irq <= 0) return 0; - INIT_WORK(&fan_data->alarm_work, fan_alarm_notify); + err = devm_work_autocancel(dev, &fan_data->alarm_work, + fan_alarm_notify); + if (err) + return err; + irq_set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler, IRQF_SHARED, "GPIO fan alarm", fan_data); From b4fffa75c1d6f87e6dc6191dec900f2b5bd23a1c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 20 Aug 2026 21:40:48 -0700 Subject: [PATCH 0443/1317] Documentation/hwmon: Document hwmon_notify_event() The hwmon core provides hwmon_notify_event() for drivers to report events such as alarm or fault conditions to userspace via sysfs notifications and uevents, as well as to the thermal subsystem for temperature sensors. However, this function is not documented in the hwmon kernel API guide. Add the function prototype and description of hwmon_notify_event() to Documentation/hwmon/hwmon-kernel-api.rst. Cc: Kalesh AP Reviewed-by: Kalesh AP Fixes: 1597b374af222 ("hwmon: Add notification support") Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Documentation/hwmon/hwmon-kernel-api.rst b/Documentation/hwmon/hwmon-kernel-api.rst index 9fcde32a140d..c3eb433a78f6 100644 --- a/Documentation/hwmon/hwmon-kernel-api.rst +++ b/Documentation/hwmon/hwmon-kernel-api.rst @@ -42,6 +42,9 @@ register/unregister functions:: char *devm_hwmon_sanitize_name(struct device *dev, const char *name); + int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel); + void hwmon_lock(struct device *dev); void hwmon_unlock(struct device *dev); @@ -90,6 +93,18 @@ implemented in the driver, or debugfs functions, hwmon_lock() and hwmon_unlock() can be used to ensure that calls to those functions are serialized. Those functions also support guard() and scoped_guard() variants. +Drivers can call hwmon_notify_event() to notify userspace and the thermal +subsystem when a hardware monitoring event (such as an alarm or a fault +condition) occurs or clears. The parameters are the hwmon device, the sensor +type, the attribute identifier associated with the event (such as +hwmon_temp_max_alarm or hwmon_fan_fault), and the sensor channel number. +hwmon_notify_event() generates a sysfs event (calling sysfs_notify()) and a +udev event with the attribute name passed in the NAME environment property +(e.g., "NAME=temp1_max_alarm"). If the event is for a temperature sensor and +the sensor is attached to a thermal zone, it also notifies the thermal +subsystem to update the thermal zone. hwmon_notify_event() returns 0 on +success or a negative error code on failure. + Using devm_hwmon_device_register_with_info() -------------------------------------------- From 354ccc99b2dc8ba0cf6d4de34e520bcf6ecca5c2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 20 Aug 2026 10:51:50 -0700 Subject: [PATCH 0444/1317] hwmon: Fix potential UAF in pec_store Sashiko reports: In pec_store(), a guard(mutex)(&hwdev->lock) is taken. If the chip write operation returns an error other than -EOPNOTSUPP, the code jumps to the put label, which calls put_device(hdev). If this drops the final reference, the device is freed. When the function then returns, the guard cleanup function runs and attempts to unlock the freed mutex. Use scoped_guard() instead of guard() to avoid the problem. Fixes: 3ad2a7b9b15d5 ("hwmon: Serialize accesses in hwmon core") Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 41755910a25a..3e65fc6d25eb 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -371,18 +371,17 @@ static ssize_t pec_store(struct device *dev, const struct device_attribute *deva * handling is not required. */ hwdev = to_hwmon_device(hdev); - guard(mutex)(&hwdev->lock); - if (hwdev->chip->ops->write) { - err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val); - if (err && err != -EOPNOTSUPP) - goto put; + scoped_guard(mutex, &hwdev->lock) { + if (hwdev->chip->ops->write) { + err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val); + if (err && err != -EOPNOTSUPP) + goto put; + } + if (!val) + client->flags &= ~I2C_CLIENT_PEC; + else + client->flags |= I2C_CLIENT_PEC; } - - if (!val) - client->flags &= ~I2C_CLIENT_PEC; - else - client->flags |= I2C_CLIENT_PEC; - err = count; put: put_device(hdev); From 8afc94bfb0ffdfc4a168081785820aa4818d1d23 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Thu, 20 Aug 2026 06:09:21 -0700 Subject: [PATCH 0445/1317] hwmon: (ina2xx) Acquire hwmon_lock in shunt_resistor_show() shunt_resistor_store() currently acquires hwmon_lock to set data->rshunt, but the corresponding access in shunt_resistor_show() is unprotected. Acquire the lock in shunt_resistor_show() as well to ensure proper synchronization. Fixes: 3ad867001c91 ("hwmon: (ina2xx) fix sysfs shunt resistor read access") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260729162836.89BDF1F00A3A@smtp.kernel.org/ Signed-off-by: Jared Kangas Link: https://patch.msgid.link/20260820-upstream-ina2xx-in0-curr1-alarms-v2-1-fdce35abc41e@redhat.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 5c6dc2c370d8..f1f988d099a5 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -883,8 +883,12 @@ static ssize_t shunt_resistor_show(struct device *dev, struct device_attribute *da, char *buf) { struct ina2xx_data *data = dev_get_drvdata(dev); + long rshunt; - return sysfs_emit(buf, "%li\n", data->rshunt); + scoped_guard(hwmon_lock, dev) { + rshunt = data->rshunt; + } + return sysfs_emit(buf, "%li\n", rshunt); } static ssize_t shunt_resistor_store(struct device *dev, From 2bf98a6af10388215398a30d723ff1f6ff5ce4f7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 20 Aug 2026 22:19:13 -0700 Subject: [PATCH 0446/1317] hwmon: Ensure that 'dev' passed to hwmon_notify_event() is a hwmon device The device parameter of hwmon_notify_event() must be a hardware monitoring device. Since this is easy to get wrong, and since passing a non-hwmon device may result in a crash, generate a warning traceback and abort if a wrong device class is passed as parameter. Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 3e65fc6d25eb..10d2df3efdfa 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -318,6 +318,11 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) return 1; } +static bool is_hwmon_device(struct device *dev) +{ + return dev->class == &hwmon_class; +} + #if IS_REACHABLE(CONFIG_I2C) /* @@ -338,7 +343,7 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) static int hwmon_match_device(struct device *dev, const void *data) { - return dev->class == &hwmon_class; + return is_hwmon_device(dev); } static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, @@ -781,6 +786,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, const char *template; int base; + if (WARN(!is_hwmon_device(dev), "%s is not a hardware monitoring device\n", + dev_name(dev))) + return -EINVAL; if (type >= ARRAY_SIZE(__templates)) return -EINVAL; if (attr >= __templates_size[type]) From 3d44ab826e0244a8b9eaf0f1f604f4cb8b890325 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Thu, 20 Aug 2026 06:09:22 -0700 Subject: [PATCH 0447/1317] hwmon: (ina2xx) Parameterize ina2xx_data in ina226_alert_read() Mirror ina226_alert_limit_read/write and use struct ina2xx_data instead of struct regmap in ina226_alert_read's parameters. Signed-off-by: Jared Kangas Link: https://patch.msgid.link/20260820-upstream-ina2xx-in0-curr1-alarms-v2-2-fdce35abc41e@redhat.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index f1f988d099a5..0fd17a6d4f90 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -498,12 +498,12 @@ static int ina2xx_chip_read(struct device *dev, u32 attr, long *val) return 0; } -static int ina226_alert_read(struct regmap *regmap, u32 mask, long *val) +static int ina226_alert_read(struct ina2xx_data *data, u32 mask, long *val) { unsigned int regval; int ret; - ret = regmap_read_bypassed(regmap, INA226_MASK_ENABLE, ®val); + ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; @@ -538,9 +538,9 @@ static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val) return ina226_alert_limit_read(data, over_voltage_mask, voltage_reg, val); case hwmon_in_lcrit_alarm: - return ina226_alert_read(regmap, under_voltage_mask, val); + return ina226_alert_read(data, under_voltage_mask, val); case hwmon_in_crit_alarm: - return ina226_alert_read(regmap, over_voltage_mask, val); + return ina226_alert_read(data, over_voltage_mask, val); default: return -EOPNOTSUPP; } @@ -597,7 +597,7 @@ static int ina2xx_power_read(struct device *dev, u32 attr, long *val) return ina226_alert_limit_read(data, INA226_POWER_OVER_LIMIT_MASK, INA2XX_POWER, val); case hwmon_power_crit_alarm: - return ina226_alert_read(data->regmap, INA226_POWER_OVER_LIMIT_MASK, val); + return ina226_alert_read(data, INA226_POWER_OVER_LIMIT_MASK, val); default: return -EOPNOTSUPP; } @@ -639,9 +639,9 @@ static int ina2xx_curr_read(struct device *dev, u32 attr, long *val) return ina226_alert_limit_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, INA2XX_CURRENT, val); case hwmon_curr_lcrit_alarm: - return ina226_alert_read(regmap, INA226_SHUNT_UNDER_VOLTAGE_MASK, val); + return ina226_alert_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, val); case hwmon_curr_crit_alarm: - return ina226_alert_read(regmap, INA226_SHUNT_OVER_VOLTAGE_MASK, val); + return ina226_alert_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, val); default: return -EOPNOTSUPP; } From e92b9208415a90e9cc1d923c5d8c058dde77be68 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Thu, 20 Aug 2026 06:09:23 -0700 Subject: [PATCH 0448/1317] hwmon: (ina2xx) Replace masks with enum in alert functions Instead of passing an explicit mask to alert/limit functions like ina226_alert_read(), introduce an enum ina2xx_alert_type that can be converted to a mask internally. This semantically separates current from shunt voltage in helpers that use function masks, which previously saw the same mask for the two functions. Signed-off-by: Jared Kangas Link: https://patch.msgid.link/20260820-upstream-ina2xx-in0-curr1-alarms-v2-3-fdce35abc41e@redhat.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 90 +++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 0fd17a6d4f90..75e97e30bdcd 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -129,6 +129,17 @@ enum ina2xx_ids { sy24655 }; +enum ina2xx_alert_type { + INA2XX_ALERT_NONE, + INA2XX_ALERT_CURRENT_LOW, + INA2XX_ALERT_CURRENT_HIGH, + INA2XX_ALERT_POWER_HIGH, + INA2XX_ALERT_BUS_VOLTAGE_LOW, + INA2XX_ALERT_BUS_VOLTAGE_HIGH, + INA2XX_ALERT_SHUNT_VOLTAGE_LOW, + INA2XX_ALERT_SHUNT_VOLTAGE_HIGH, +}; + struct ina2xx_config { u16 config_default; bool has_alerts; /* chip supports alerts and limits */ @@ -428,16 +439,43 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val) } } -static int ina226_alert_limit_read(struct ina2xx_data *data, u32 mask, int reg, long *val) +static u32 ina2xx_alert_type_to_mask(enum ina2xx_alert_type alert) +{ + switch (alert) { + case INA2XX_ALERT_CURRENT_LOW: + case INA2XX_ALERT_SHUNT_VOLTAGE_LOW: + return INA226_SHUNT_UNDER_VOLTAGE_MASK; + case INA2XX_ALERT_CURRENT_HIGH: + case INA2XX_ALERT_SHUNT_VOLTAGE_HIGH: + return INA226_SHUNT_OVER_VOLTAGE_MASK; + case INA2XX_ALERT_BUS_VOLTAGE_LOW: + return INA226_BUS_UNDER_VOLTAGE_MASK; + case INA2XX_ALERT_BUS_VOLTAGE_HIGH: + return INA226_BUS_OVER_VOLTAGE_MASK; + case INA2XX_ALERT_POWER_HIGH: + return INA226_POWER_OVER_LIMIT_MASK; + case INA2XX_ALERT_NONE: + return 0; + default: + /* programmer error */ + WARN_ON_ONCE(1); + return 0; + } +} + +static int ina226_alert_limit_read(struct ina2xx_data *data, enum ina2xx_alert_type alert, + int reg, long *val) { struct regmap *regmap = data->regmap; int regval; + u32 mask; int ret; ret = regmap_read(regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; + mask = ina2xx_alert_type_to_mask(alert); if (regval & mask) { ret = regmap_read(regmap, INA226_ALERT_LIMIT, ®val); if (ret) @@ -449,9 +487,11 @@ static int ina226_alert_limit_read(struct ina2xx_data *data, u32 mask, int reg, return 0; } -static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, int reg, long val) +static int ina226_alert_limit_write(struct ina2xx_data *data, enum ina2xx_alert_type alert, + int reg, long val) { struct regmap *regmap = data->regmap; + u32 mask; int ret; if (val < 0) @@ -472,9 +512,11 @@ static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, int reg, if (ret < 0) return ret; - if (val) + if (val) { + mask = ina2xx_alert_type_to_mask(alert); return regmap_update_bits(regmap, INA226_MASK_ENABLE, INA226_ALERT_CONFIG_MASK, mask); + } return 0; } @@ -498,15 +540,17 @@ static int ina2xx_chip_read(struct device *dev, u32 attr, long *val) return 0; } -static int ina226_alert_read(struct ina2xx_data *data, u32 mask, long *val) +static int ina226_alert_read(struct ina2xx_data *data, enum ina2xx_alert_type alert, long *val) { unsigned int regval; + u32 mask; int ret; ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; + mask = ina2xx_alert_type_to_mask(alert); *val = (regval & mask) && (regval & INA226_ALERT_FUNCTION_FLAG); return 0; @@ -515,10 +559,10 @@ static int ina226_alert_read(struct ina2xx_data *data, u32 mask, long *val) static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val) { int voltage_reg = channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE; - u32 under_voltage_mask = channel ? INA226_BUS_UNDER_VOLTAGE_MASK - : INA226_SHUNT_UNDER_VOLTAGE_MASK; - u32 over_voltage_mask = channel ? INA226_BUS_OVER_VOLTAGE_MASK - : INA226_SHUNT_OVER_VOLTAGE_MASK; + enum ina2xx_alert_type under_voltage_alert = channel ? INA2XX_ALERT_BUS_VOLTAGE_LOW + : INA2XX_ALERT_SHUNT_VOLTAGE_LOW; + enum ina2xx_alert_type over_voltage_alert = channel ? INA2XX_ALERT_BUS_VOLTAGE_HIGH + : INA2XX_ALERT_SHUNT_VOLTAGE_HIGH; struct ina2xx_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; unsigned int regval; @@ -532,15 +576,15 @@ static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val) *val = ina2xx_get_value(data, voltage_reg, regval); break; case hwmon_in_lcrit: - return ina226_alert_limit_read(data, under_voltage_mask, + return ina226_alert_limit_read(data, under_voltage_alert, voltage_reg, val); case hwmon_in_crit: - return ina226_alert_limit_read(data, over_voltage_mask, + return ina226_alert_limit_read(data, over_voltage_alert, voltage_reg, val); case hwmon_in_lcrit_alarm: - return ina226_alert_read(data, under_voltage_mask, val); + return ina226_alert_read(data, under_voltage_alert, val); case hwmon_in_crit_alarm: - return ina226_alert_read(data, over_voltage_mask, val); + return ina226_alert_read(data, over_voltage_alert, val); default: return -EOPNOTSUPP; } @@ -594,10 +638,10 @@ static int ina2xx_power_read(struct device *dev, u32 attr, long *val) case hwmon_power_average: return sy24655_average_power_read(data, SY24655_EIN, val); case hwmon_power_crit: - return ina226_alert_limit_read(data, INA226_POWER_OVER_LIMIT_MASK, + return ina226_alert_limit_read(data, INA2XX_ALERT_POWER_HIGH, INA2XX_POWER, val); case hwmon_power_crit_alarm: - return ina226_alert_read(data, INA226_POWER_OVER_LIMIT_MASK, val); + return ina226_alert_read(data, INA2XX_ALERT_POWER_HIGH, val); default: return -EOPNOTSUPP; } @@ -633,15 +677,15 @@ static int ina2xx_curr_read(struct device *dev, u32 attr, long *val) *val = ina2xx_get_value(data, INA2XX_CURRENT, regval); return 0; case hwmon_curr_lcrit: - return ina226_alert_limit_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, + return ina226_alert_limit_read(data, INA2XX_ALERT_CURRENT_LOW, INA2XX_CURRENT, val); case hwmon_curr_crit: - return ina226_alert_limit_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, + return ina226_alert_limit_read(data, INA2XX_ALERT_CURRENT_HIGH, INA2XX_CURRENT, val); case hwmon_curr_lcrit_alarm: - return ina226_alert_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, val); + return ina226_alert_read(data, INA2XX_ALERT_CURRENT_LOW, val); case hwmon_curr_crit_alarm: - return ina226_alert_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, val); + return ina226_alert_read(data, INA2XX_ALERT_CURRENT_HIGH, val); default: return -EOPNOTSUPP; } @@ -685,12 +729,12 @@ static int ina2xx_in_write(struct device *dev, u32 attr, int channel, long val) switch (attr) { case hwmon_in_lcrit: return ina226_alert_limit_write(data, - channel ? INA226_BUS_UNDER_VOLTAGE_MASK : INA226_SHUNT_UNDER_VOLTAGE_MASK, + channel ? INA2XX_ALERT_BUS_VOLTAGE_LOW : INA2XX_ALERT_SHUNT_VOLTAGE_LOW, channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE, val); case hwmon_in_crit: return ina226_alert_limit_write(data, - channel ? INA226_BUS_OVER_VOLTAGE_MASK : INA226_SHUNT_OVER_VOLTAGE_MASK, + channel ? INA2XX_ALERT_BUS_VOLTAGE_HIGH : INA2XX_ALERT_SHUNT_VOLTAGE_HIGH, channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE, val); default: @@ -705,7 +749,7 @@ static int ina2xx_power_write(struct device *dev, u32 attr, long val) switch (attr) { case hwmon_power_crit: - return ina226_alert_limit_write(data, INA226_POWER_OVER_LIMIT_MASK, + return ina226_alert_limit_write(data, INA2XX_ALERT_POWER_HIGH, INA2XX_POWER, val); default: return -EOPNOTSUPP; @@ -719,10 +763,10 @@ static int ina2xx_curr_write(struct device *dev, u32 attr, long val) switch (attr) { case hwmon_curr_lcrit: - return ina226_alert_limit_write(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, + return ina226_alert_limit_write(data, INA2XX_ALERT_CURRENT_LOW, INA2XX_CURRENT, val); case hwmon_curr_crit: - return ina226_alert_limit_write(data, INA226_SHUNT_OVER_VOLTAGE_MASK, + return ina226_alert_limit_write(data, INA2XX_ALERT_CURRENT_HIGH, INA2XX_CURRENT, val); default: return -EOPNOTSUPP; From 35760f5efd7bfa7a44a3831f47e19fe9cbafc905 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Thu, 20 Aug 2026 06:09:24 -0700 Subject: [PATCH 0449/1317] hwmon: (ina2xx) Decouple in0 and curr1 alarms INA2XX current limits are converted into shunt voltage limits internally using the shunt resistor value. Once a current limit's corresponding voltage limit is written to the hardware, shunt voltage and current alarms are indistinguishable from each other. This causes two issues: 1. in0/curr1 alarms may be unintentionally cleared by reading from the opposite input's alarm. 2. When a limit for either in0 (shunt voltage) or curr1 (current) is set, both of their alarms are triggered, and both of their limits read nonzero. An example of this behavior on an INA231: # cd /sys/class/hwmon/hwmon0 # head {curr1,in0}_input ==> curr1_input <== 1713 ==> in0_input <== 2 # echo 1800 >curr1_lcrit # head {curr1,in0}_lcrit_alarm ==> curr1_lcrit_alarm <== 1 ==> in0_lcrit_alarm <== 0 # head {in0,curr1}_lcrit_alarm ==> in0_lcrit_alarm <== 1 ==> curr1_lcrit_alarm <== 0 # head {in0,curr1}_lcrit_alarm ==> in0_lcrit_alarm <== 1 ==> curr1_lcrit_alarm <== 1 This is because curr1 uses the same underlying masks (INA226_SHUNT_*_VOLTAGE_MASK) as in0 on the hardware. As a result, ina2xx_{curr,in}_read() both read the shunt voltage alarms/limits without considering whether the voltage or current is currently set. To fix this, track the active alarm type in ina2xx_data and guard alarm/limit reads with a check that returns zero if the active alarm is for a different type. The new field is initialized based on the MASK_ENABLE register's set function, assuming voltage instead of current when the shunt voltage mask is set. After this fix, the alarms only read back 1 if their corresponding limit is set: # echo 0 >curr1_lcrit # head {curr1,in0}_lcrit_alarm ==> curr1_lcrit_alarm <== 0 ==> in0_lcrit_alarm <== 0 # echo 9999 >curr1_lcrit # head {curr1,in0}_lcrit_alarm ==> curr1_lcrit_alarm <== 1 ==> in0_lcrit_alarm <== 0 # echo 9999 >in0_lcrit # head {curr1,in0}_lcrit_alarm ==> curr1_lcrit_alarm <== 0 ==> in0_lcrit_alarm <== 1 Fixes: 4d5c2d986757 ("hwmon: (ina2xx) Add support for current limits") Signed-off-by: Jared Kangas Link: https://patch.msgid.link/20260820-upstream-ina2xx-in0-curr1-alarms-v2-4-fdce35abc41e@redhat.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 65 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 75e97e30bdcd..19b35f3bf3a3 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -159,6 +160,7 @@ struct ina2xx_data { const struct ina2xx_config *config; enum ina2xx_ids chip; + enum ina2xx_alert_type active_alert; long rshunt; long current_lsb_uA; long power_lsb_uW; @@ -463,6 +465,35 @@ static u32 ina2xx_alert_type_to_mask(enum ina2xx_alert_type alert) } } +static enum ina2xx_alert_type ina2xx_mask_to_alert_type(u32 mask) +{ + int top_bit = fls(mask & INA226_ALERT_CONFIG_MASK); + + if (!top_bit) + return INA2XX_ALERT_NONE; + + /* + * Multiple bits may be set, with the highest-set function taking + * precedence according to the datasheet. Shunt voltage masks are + * assumed to map to voltage monitoring rather than current monitoring, + * since the latter isn't directly implemented in the hardware. + */ + switch (BIT(top_bit - 1)) { + case INA226_SHUNT_OVER_VOLTAGE_MASK: + return INA2XX_ALERT_SHUNT_VOLTAGE_HIGH; + case INA226_SHUNT_UNDER_VOLTAGE_MASK: + return INA2XX_ALERT_SHUNT_VOLTAGE_LOW; + case INA226_BUS_OVER_VOLTAGE_MASK: + return INA2XX_ALERT_BUS_VOLTAGE_HIGH; + case INA226_BUS_UNDER_VOLTAGE_MASK: + return INA2XX_ALERT_BUS_VOLTAGE_LOW; + case INA226_POWER_OVER_LIMIT_MASK: + return INA2XX_ALERT_POWER_HIGH; + default: + return INA2XX_ALERT_NONE; + } +} + static int ina226_alert_limit_read(struct ina2xx_data *data, enum ina2xx_alert_type alert, int reg, long *val) { @@ -471,6 +502,12 @@ static int ina226_alert_limit_read(struct ina2xx_data *data, enum ina2xx_alert_t u32 mask; int ret; + /* Avoid nonzero reads from inactive alerts caused by shared limit register */ + if (data->active_alert != alert) { + *val = 0; + return 0; + } + ret = regmap_read(regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; @@ -506,6 +543,7 @@ static int ina226_alert_limit_write(struct ina2xx_data *data, enum ina2xx_alert_ INA226_ALERT_CONFIG_MASK, 0); if (ret < 0) return ret; + data->active_alert = INA2XX_ALERT_NONE; ret = regmap_write(regmap, INA226_ALERT_LIMIT, ina226_alert_to_reg(data, reg, val)); @@ -514,9 +552,13 @@ static int ina226_alert_limit_write(struct ina2xx_data *data, enum ina2xx_alert_ if (val) { mask = ina2xx_alert_type_to_mask(alert); - return regmap_update_bits(regmap, INA226_MASK_ENABLE, - INA226_ALERT_CONFIG_MASK, mask); + ret = regmap_update_bits(regmap, INA226_MASK_ENABLE, + INA226_ALERT_CONFIG_MASK, mask); + if (ret < 0) + return ret; + data->active_alert = alert; } + return 0; } @@ -546,6 +588,15 @@ static int ina226_alert_read(struct ina2xx_data *data, enum ina2xx_alert_type al u32 mask; int ret; + /* + * With alert latching, reading alerts from hardware also clears the + * alert, so return early if the alert is inactive. + */ + if (data->active_alert != alert) { + *val = 0; + return 0; + } + ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; @@ -988,6 +1039,16 @@ static int ina2xx_init(struct device *dev, struct ina2xx_data *data) if (data->config->has_alerts) { bool active_high = device_property_read_bool(dev, "ti,alert-polarity-active-high"); + unsigned int mask_enable; + + /* + * Infer active alert from MASK_ENABLE in case it's already + * configured (e.g., by a past probe or firmware) + */ + ret = regmap_read(regmap, INA226_MASK_ENABLE, &mask_enable); + if (ret < 0) + return ret; + data->active_alert = ina2xx_mask_to_alert_type(mask_enable); regmap_update_bits(regmap, INA226_MASK_ENABLE, INA226_ALERT_LATCH_ENABLE | INA226_ALERT_POLARITY, From 013c5a93e8062014177d68af799e8867cf03958d Mon Sep 17 00:00:00 2001 From: Antonin Godard Date: Tue, 18 Aug 2026 10:08:40 +0200 Subject: [PATCH 0450/1317] Documentation: hwmon: replace full-width colon by a standard ASCII colon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It prevented the pdfdocs target to complete, prompting the following error: Latexmk: ====Problematic refs and citations with line #s in .tex file: Missing character: There is no : (U+FF1A) in font DejaVu Serif/OT:script=latn;l Fixes: 69001f21ded78 ("hwmon: document: add gpd-fan") Signed-off-by: Antonin Godard Link: https://patch.msgid.link/20260818-doc-hwmon-remove-confusable-v2-1-c1dff1ec01cd@bootlin.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/gpd-fan.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/gpd-fan.rst b/Documentation/hwmon/gpd-fan.rst index 29527a77fe88..b27657d33056 100644 --- a/Documentation/hwmon/gpd-fan.rst +++ b/Documentation/hwmon/gpd-fan.rst @@ -67,7 +67,7 @@ pwm1_enable at full speed. Write "1" to set to manual, write "2" to let the EC control decide fan speed. Read this attribute to see current status. - NB:In consideration of the safety of the device, when setting to manual mode, + NB: In consideration of the safety of the device, when setting to manual mode, the pwm speed will be set to the maximum value (255) by default. You can set a different value by writing pwm1 later. From 100eb7c7d0b28c52ad1b25d51c316fdc46c27c71 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Fri, 21 Aug 2026 19:57:20 +0800 Subject: [PATCH 0451/1317] hwmon: (yogafan) fix non-kernel-doc comment The file description comment starts with "/**" which is reserved for kernel-doc comments, triggering a kernel-doc checker warning. Change it to a plain "/*" comment since it does not document any function or struct. Fixes: c67c248ca406a ("hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring") Signed-off-by: hanzhijian Link: https://patch.msgid.link/20260821115720.2017516-1-hanzhijian1991@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/yogafan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c index 48fa5148d9e2..278cb089b0fd 100644 --- a/drivers/hwmon/yogafan.c +++ b/drivers/hwmon/yogafan.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/** +/* * yoga_fan.c - Lenovo Yoga/Legion Fan Hardware Monitoring Driver * * Provides fan speed monitoring for Lenovo Yoga, Legion, and IdeaPad From 06b7cf395b1fb652a50db39674a759658fbfba0d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 21 Aug 2026 07:49:15 -0700 Subject: [PATCH 0452/1317] hwmon: (sht4x) Add missing locks Sashiko reports: Heater sysfs callbacks (heater_enable_store, heater_power_store, and heater_time_store) are exposed to data races without the hwmon lock. If a user-space process reads hwmon data while another process enables the heater, heater_enable_store() executes without holding hwmon_lock(dev). This can interleave I2C commands and mutate shared state (data->heating_complete and data->data_pending) concurrently with sht4x_read_values(), leading to corrupted I2C sequences. Fixes: 53dfa12299c1 ("hwmon: (sht4x) Rely on subsystem locking") Cc: Alessandro Zini Signed-off-by: Guenter Roeck Link: https://patch.msgid.link/20260821144916.2889031-1-linux@roeck-us.net --- drivers/hwmon/sht4x.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 9cace0e8acda..7a0dc2ed723d 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -277,6 +277,8 @@ static ssize_t heater_enable_store(struct device *dev, heating_time_bound = 1100; } + guard(hwmon_lock)(dev); + if (time_before(jiffies, data->heating_complete)) return -EBUSY; @@ -314,6 +316,8 @@ static ssize_t heater_power_store(struct device *dev, if (power != 20 && power != 110 && power != 200) return -EINVAL; + guard(hwmon_lock)(dev); + data->heater_power = power; return count; @@ -344,6 +348,8 @@ static ssize_t heater_time_store(struct device *dev, if (time != 100 && time != 1000) return -EINVAL; + guard(hwmon_lock)(dev); + data->heater_time = time; return count; From 70c33e211b2b78830f76c908e5236b77ffde63a0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 21 Aug 2026 07:49:16 -0700 Subject: [PATCH 0453/1317] hwmon: (sht4x) Fix return value from heater_enable_store() Sashiko reports: The return value in heater_enable_store() causes an unexpected write failure in user-space. When the heater is successfully enabled, the function returns 0 instead of count: drivers/hwmon/sht4x.c:heater_enable_store() { ... data->heating_complete = jiffies + msecs_to_jiffies(heating_time_bound); data->data_pending = true; return 0; } Returning 0 signals to VFS that no bytes were processed. Standard user-space tools will retry the write with the remaining bytes. On the retry, time_before(jiffies, data->heating_complete) evaluates to true, and the function immediately fails with -EBUSY. Return count as expected to fix the problem. Fixes: 0eed6fc3d2b9e ("hwmon: (sht4x): add heater support") Cc: Antoni Pokusinski Cc: Alessandro Zini Signed-off-by: Guenter Roeck Link: https://patch.msgid.link/20260821144916.2889031-2-linux@roeck-us.net --- drivers/hwmon/sht4x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 7a0dc2ed723d..a97dda9e92dc 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -288,7 +288,7 @@ static ssize_t heater_enable_store(struct device *dev, data->heating_complete = jiffies + msecs_to_jiffies(heating_time_bound); data->data_pending = true; - return 0; + return count; } static ssize_t heater_power_show(struct device *dev, From 5a0aacaa2d593d7582ecfe289529b937b6dc5d3c Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Fri, 28 Aug 2026 17:54:13 +0700 Subject: [PATCH 0454/1317] hwmon: (applesmc) fix key backlight workqueue leak on register failure applesmc_create_key_backlight() allocates applesmc_led_wq before calling led_classdev_register(). When register fails, the error is returned to applesmc_init(), which jumps to out_light_sysfs and skips applesmc_release_key_backlight(), leaking the workqueue. Destroy the workqueue on the register failure path. The bug was introduced when the inline init block was refactored into a helper that returns errors directly, dropping the old out_light_wq unwind label. Fixes: 0b0b5dff8967 ("hwmon: (applesmc) Simplify feature sysfs handling") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://patch.msgid.link/20260828105413.2401385-1-congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/applesmc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 00e603b5e401..d0baa10502f7 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -1128,12 +1128,17 @@ static void applesmc_release_light_sensor(void) static int applesmc_create_key_backlight(void) { + int ret; + if (!smcreg.has_key_backlight) return 0; applesmc_led_wq = create_singlethread_workqueue("applesmc-led"); if (!applesmc_led_wq) return -ENOMEM; - return led_classdev_register(&pdev->dev, &applesmc_backlight); + ret = led_classdev_register(&pdev->dev, &applesmc_backlight); + if (ret) + destroy_workqueue(applesmc_led_wq); + return ret; } static void applesmc_release_key_backlight(void) From 32689f0fc54fd801f1cd11637666e534984cb04a Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:31:03 +0800 Subject: [PATCH 0455/1317] ASoC: bcm: bcm63xx: Publish the OF module aliases The BCM63xx I2S platform driver matches brcm,bcm63xx-i2s using snd_soc_bcm_audio_match. With SND_BCM63XX_I2S_WHISTLER=m, the platform bus emits an OF modalias but snd-soc-63xx does not publish that table. Export the existing OF IDs for module autoloading. The PCM companion and the probe path remain unchanged. Fixes: 88eb404ccc3e ("ASoC: brcm: Add DSL/PON SoC audio driver") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905133103.63432-1-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/bcm/bcm63xx-i2s-whistler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/bcm/bcm63xx-i2s-whistler.c b/sound/soc/bcm/bcm63xx-i2s-whistler.c index c47ed1e6ea2b..14d111fe29d8 100644 --- a/sound/soc/bcm/bcm63xx-i2s-whistler.c +++ b/sound/soc/bcm/bcm63xx-i2s-whistler.c @@ -285,6 +285,7 @@ static const struct of_device_id snd_soc_bcm_audio_match[] = { {.compatible = "brcm,bcm63xx-i2s"}, { } }; +MODULE_DEVICE_TABLE(of, snd_soc_bcm_audio_match); #endif static struct platform_driver bcm63xx_i2s_driver = { From a7c1290eef60711c10289c056ad32ed1f2b47b12 Mon Sep 17 00:00:00 2001 From: Vasileios Almpanis Date: Thu, 30 Jul 2026 11:30:24 +0200 Subject: [PATCH 0456/1317] configfs: pin the symlink target's dirent instead of chasing ->ci_dentry create_link() reads the target's configfs_dirent from item->ci_dentry->d_fsdata, relying on the item reference taken by get_target(). That reference pins the item, not its dentry: the dentry is pinned by DCACHE_PERSISTENT, which configfs_remove_dir() releases via simple_rmdir() while the item is still alive. A symlink racing with rmdir of its target can therefore find ->ci_dentry freed and its dirent released, triggering WARN_ON(!atomic_read(&sd->s_count)) in configfs_get(). Take the dirent in get_target() as well, under ->d_lock and atomically with the item reference, and pass it down to create_link(). A hashed dentry has not been killed yet, so its ->d_fsdata reference keeps the dirent alive there. Cc: stable@vger.kernel.org Fixes: 7063fbf22611 ("[PATCH] configfs: User-driven configuration filesystem") Signed-off-by: Vasileios Almpanis Tested-by: Breno Leitao Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260730093435.195441-2-vasilisalmpanis@gmail.com Signed-off-by: Breno Leitao --- fs/configfs/symlink.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index 31eb28b27309..3b31c714400f 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -76,9 +76,9 @@ static int configfs_get_target_path(struct config_item *item, static int create_link(struct config_item *parent_item, struct config_item *item, + struct configfs_dirent *target_sd, struct dentry *dentry) { - struct configfs_dirent *target_sd = item->ci_dentry->d_fsdata; char *body; int ret; @@ -115,6 +115,7 @@ static int create_link(struct config_item *parent_item, static int get_target(const char *symname, struct config_item **target, + struct configfs_dirent **target_sd, struct super_block *sb) { struct path path __free(path_put) = {}; @@ -125,7 +126,20 @@ static int get_target(const char *symname, struct config_item **target, return ret; if (path.dentry->d_sb != sb) return -EPERM; - *target = configfs_get_config_item(path.dentry); + /* + * A hashed dentry guarantees that neither the item nor the dirent + * have been released yet, as removals unhash before dropping. + * Grab both references here. An item reference alone would not keep + * ->ci_dentry alive. + */ + spin_lock(&path.dentry->d_lock); + if (!d_unhashed(path.dentry)) { + struct configfs_dirent *sd = path.dentry->d_fsdata; + + *target = config_item_get(sd->s_element); + *target_sd = configfs_get(sd); + } + spin_unlock(&path.dentry->d_lock); if (!*target) return -ENOENT; return 0; @@ -139,6 +153,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, struct configfs_dirent *sd; struct config_item *parent_item; struct config_item *target_item = NULL; + struct configfs_dirent *target_sd = NULL; const struct config_item_type *type; sd = dentry->d_parent->d_fsdata; @@ -182,7 +197,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, * AV, a thoroughly annoyed bastard. */ inode_unlock(dir); - ret = get_target(symname, &target_item, dentry->d_sb); + ret = get_target(symname, &target_item, &target_sd, dentry->d_sb); inode_lock(dir); if (ret) goto out_put; @@ -196,13 +211,14 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, ret = type->ct_item_ops->allow_link(parent_item, target_item); if (!ret) { mutex_lock(&configfs_symlink_mutex); - ret = create_link(parent_item, target_item, dentry); + ret = create_link(parent_item, target_item, target_sd, dentry); mutex_unlock(&configfs_symlink_mutex); if (ret && type->ct_item_ops->drop_link) type->ct_item_ops->drop_link(parent_item, target_item); } + configfs_put(target_sd); config_item_put(target_item); out_put: From f06c2d26d1999d37e93299db0ecead04ca7d0b9f Mon Sep 17 00:00:00 2001 From: Vasileios Almpanis Date: Thu, 30 Jul 2026 11:30:25 +0200 Subject: [PATCH 0457/1317] configfs: unhash the dentry before dropping the item in rmdir configfs_get_config_item() treats a hashed dentry as proof that sd->s_element is a live config_item. configfs_rmdir() breaks that: simple_rmdir() leaves the dentry hashed, the last reference to the item is dropped right after, and the dentry is only unhashed by d_delete() once ->rmdir() has returned. configfs_symlink() resolves its target holding no lock on it, so get_target() can land in that window: BUG: KASAN: slab-use-after-free in config_item_get+0x26/0x90 get_target fs/configfs/symlink.c:128 [inline] configfs_symlink+0x4ab/0x1030 fs/configfs/symlink.c:185 Unhash in configfs_remove_dir(), while the item is still guaranteed to be there. A reference obtained just before that stays harmless, as create_link() rechecks CONFIGFS_USET_DROPPING, already set by configfs_detach_prep(). Both configfs_unregister_subsystem() paths d_drop() after detaching, so this only makes rmdir match them. Reported-by: syzbot+6b16e3d085833cbf3e25@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6b16e3d085833cbf3e25 Fixes: 7063fbf22611 ("[PATCH] configfs: User-driven configuration filesystem") Cc: stable@vger.kernel.org Signed-off-by: Vasileios Almpanis Tested-by: Breno Leitao Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260730093435.195441-3-vasilisalmpanis@gmail.com Signed-off-by: Breno Leitao --- fs/configfs/dir.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c index 3c88f13f1ca2..eda80c2a2d38 100644 --- a/fs/configfs/dir.c +++ b/fs/configfs/dir.c @@ -416,6 +416,15 @@ static void configfs_remove_dir(struct dentry *d) if (d_really_is_positive(d)) { if (unlikely(simple_rmdir(d_inode(parent), d))) pr_warn("remove_dir (%pd): attributes remain", d); + else + /* + * configfs_get_config_item() takes a hashed dentry as + * proof that ->s_element is still alive. Our caller + * is about to drop the last reference to the item and + * the VFS will not unhash until after we return, so + * unhash it here. + */ + d_drop(d); } pr_debug(" o %pd removing done (%d)\n", d, d_count(d)); From d112159df5c6cc5ee6ab91cc32bf6ed29939df38 Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:31:33 +0800 Subject: [PATCH 0458/1317] ASoC: Intel: SST: Publish the PCI module aliases The legacy SST PCI driver matches Intel Tangier devices using intel_sst_ids, but its only explicit module alias is "sst". That alias does not match PCI modalias events when this driver is built as a module. Publish its PCI table. The independently configurable SOF driver does not provide aliases for the legacy SST module. Fixes: f533a035e4da ("ASoC: Intel: mrfld - create separate module for pci part") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905133133.63661-1-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/intel/atom/sst/sst_pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/intel/atom/sst/sst_pci.c b/sound/soc/intel/atom/sst/sst_pci.c index 44bb11c69490..1a53c993b57f 100644 --- a/sound/soc/intel/atom/sst/sst_pci.c +++ b/sound/soc/intel/atom/sst/sst_pci.c @@ -167,6 +167,7 @@ static const struct pci_device_id intel_sst_ids[] = { { PCI_DEVICE_DATA(INTEL, SST_TNG, 0) }, { 0, } }; +MODULE_DEVICE_TABLE(pci, intel_sst_ids); static struct pci_driver sst_driver = { .name = SST_DRV_NAME, From 776924b2d9c451fc9dcc40673d17ff255bbc0fef Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 24 Aug 2026 18:47:58 +0200 Subject: [PATCH 0459/1317] btrfs: set space_info before adding new free space in btrfs_make_block_group() btrfs_make_block_group() calls btrfs_add_new_free_space() before assigning cache->space_info. On a zoned filesystem that ends up in __btrfs_add_free_space_zoned(), which dereferences block_group->space_info and thus hits a NULL pointer dereference when a non-initial free space range is added (e.g. during relocation). Assign cache->space_info before the btrfs_add_new_free_space() call. Reviewed-by: Boris Burkov Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/block-group.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 830460a40e86..ee182369254c 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -3074,6 +3074,18 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran return ERR_PTR(ret); } + /* + * Ensure the corresponding space_info object is created and + * assigned to our block group. We want our bg to be added to the rbtree + * with its ->space_info set. + * + * On a zoned filesystem btrfs_add_new_free_space() ends up in + * __btrfs_add_free_space_zoned(), which dereferences + * block_group->space_info, so it has to be set beforehand. + */ + cache->space_info = space_info; + ASSERT(cache->space_info); + ret = btrfs_add_new_free_space(cache, chunk_offset, chunk_offset + size, NULL); btrfs_free_excluded_extents(cache); if (ret) { @@ -3081,14 +3093,6 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran return ERR_PTR(ret); } - /* - * Ensure the corresponding space_info object is created and - * assigned to our block group. We want our bg to be added to the rbtree - * with its ->space_info set. - */ - cache->space_info = space_info; - ASSERT(cache->space_info); - ret = btrfs_add_block_group_cache(cache); if (ret) { btrfs_remove_free_space_cache(cache); From 36f9aafa46f5b9fecf92d9218c5574f1ef6b4907 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 3 Sep 2026 13:15:46 +0100 Subject: [PATCH 0460/1317] btrfs: fix unnecessary transaction commit fallback from btrfs_log_all_parents() When btrfs_log_all_parents() returns without doing any work (because all parent directories were already logged), it returns 1, which is propagated up the fsync call chain up to btrfs_log_dentry_safe(), and that causes btrfs_sync_file() to trigger am unnecessary transaction commit. This all happens because the call to btrfs_search_slot() in btrfs_log_all_parents() always returns 1, as there can not be any inode ref keys with an offset 0 (an invalid inode number), so if the while loop below it does not do any work because all parent directories were already logged, the 'ret' variable remains with a value of 1, which is then returned up the call chain to btrfs_sync_file(). Fix this by setting 'ret' to 0 after the call to btrfs_search_slot(). Fixes: 0f24ea456ae1 ("btrfs: tracepoints: add trace event for btrfs_log_all_parents()") Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-log.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index 7ba7b6098aa5..a00094604e54 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -7286,6 +7286,22 @@ static int btrfs_log_all_parents(struct btrfs_trans_handle *trans, ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); if (ret < 0) goto out; + /* + * There can't be an inode ref key with offset 0 because inode numbers + * start at BTRFS_FIRST_FREE_OBJECTID. + */ + if (WARN_ON_ONCE(ret == 0)) { + btrfs_err(trans->fs_info, + "found inode ref key with offset 0 for root %llu inode %llu", + btrfs_root_id(root), ino); + ret = BTRFS_LOG_FORCE_COMMIT; + goto out; + } + /* + * Set to 0 so that in case we don't do any work below, we won't return + * 1 and trigger an unnecessary transaction commit. + */ + ret = 0; while (true) { struct extent_buffer *leaf = path->nodes[0]; From 2a4513ab53361360329b7ad1496d666b7433289e Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 3 Sep 2026 16:16:32 +0100 Subject: [PATCH 0461/1317] btrfs: tree-checker: validate key offset for inode ref keys For a subvolume tree, the offset of an inode ref key corresponds to an inode number, and that must always be within the range: [ BTRFS_FIRST_FREE_OBJECTID (256), BTRFS_LAST_FREE_OBJECTID (-256) ] Add a check for that in check_inode_ref(). Sashiko complained about such check missing in another unrelated patch. Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 0ce91396b517..a4447c57c2a4 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -1909,6 +1909,16 @@ static int check_inode_ref(struct extent_buffer *leaf, return -EUCLEAN; } + if (unlikely(btrfs_is_fstree(btrfs_header_owner(leaf)) && + (key->offset < BTRFS_FIRST_FREE_OBJECTID || + key->offset > BTRFS_LAST_FREE_OBJECTID))) { + inode_ref_err(leaf, slot, + "invalid offset for ref key, have %llu expect [%llu, %lld]", + key->offset, BTRFS_FIRST_FREE_OBJECTID, + BTRFS_LAST_FREE_OBJECTID); + return -EUCLEAN; + } + ptr = btrfs_item_ptr_offset(leaf, slot); end = ptr + btrfs_item_size(leaf, slot); while (ptr < end) { From b18f0f8334e6d7e4ed4baf1f404658537659cb57 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 3 Sep 2026 16:24:43 +0100 Subject: [PATCH 0462/1317] btrfs: tree-checker: validate parent field for inode extref items For a subvolume tree, the parent field of an inode extref item corresponds to an inode number, and that must always be within the range: [ BTRFS_FIRST_FREE_OBJECTID (256), BTRFS_LAST_FREE_OBJECTID (-256) ] Add a check for that in check_inode_extref(). Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index a4447c57c2a4..83f7b0aaab21 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -1962,12 +1962,14 @@ static int check_inode_extref(struct extent_buffer *leaf, { unsigned long ptr = btrfs_item_ptr_offset(leaf, slot); unsigned long end = ptr + btrfs_item_size(leaf, slot); + const bool is_fstree = btrfs_is_fstree(btrfs_header_owner(leaf)); if (unlikely(!check_prev_ino(leaf, key, slot, prev_key))) return -EUCLEAN; while (ptr < end) { struct btrfs_inode_extref *extref = (struct btrfs_inode_extref *)ptr; + u64 parent; u16 namelen; if (unlikely(ptr + sizeof(*extref) > end)) { @@ -1977,6 +1979,16 @@ static int check_inode_extref(struct extent_buffer *leaf, return -EUCLEAN; } + parent = btrfs_inode_extref_parent(leaf, extref); + if (unlikely(is_fstree && (parent < BTRFS_FIRST_FREE_OBJECTID || + parent > BTRFS_LAST_FREE_OBJECTID))) { + inode_ref_err(leaf, slot, + "invalid parent for extref key, have %llu expect [%llu, %lld]", + parent, BTRFS_FIRST_FREE_OBJECTID, + BTRFS_LAST_FREE_OBJECTID); + return -EUCLEAN; + } + namelen = btrfs_inode_extref_name_len(leaf, extref); if (unlikely(ptr + sizeof(*extref) + namelen > end)) { inode_ref_err(leaf, slot, From 09f1294ee2abee7fe1c2d600671498b7642e0fe0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 3 Sep 2026 17:24:16 +0100 Subject: [PATCH 0463/1317] btrfs: tree-checker: validate name length for extref items We are validating the name length of inode ref items, but we miss the same validation for extref items. Sashiko pointed this out while reviewing other patch. Add the missing validation, similar to what was done in commit 3dc22abc21f5 ("btrfs: tree-checker: validate INODE_REF's namelen"). Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 83f7b0aaab21..ab5abbb475e2 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -1990,6 +1990,13 @@ static int check_inode_extref(struct extent_buffer *leaf, } namelen = btrfs_inode_extref_name_len(leaf, extref); + if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) { + inode_ref_err(leaf, slot, + "invalid inode extref name length, has %u expect [1, %u]", + namelen, BTRFS_NAME_LEN); + return -EUCLEAN; + } + if (unlikely(ptr + sizeof(*extref) + namelen > end)) { inode_ref_err(leaf, slot, "inode extref overflow, ptr %lu end %lu namelen %u", From 387d744fa7e499d2c3748a4e60e02ebb24e7fb16 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 25 Aug 2026 03:36:03 +0200 Subject: [PATCH 0464/1317] netfilter: nfnetlink_log: cope with concurrent instance destruction Instances are refcounted. However, only memory release happens on the 1 -> 0 transition; the unlink from hashes can occur with any refcount. Uncooperative userspace can force a situation where a queue is pending for destruction from netlink event while a different socket with same portid processes an UNBIND request. With right timing, this will unhash the instance again: Oops: general protection fault, [..] Call Trace: nfulnl_recv_config+0x31a/0xd50 nfnetlink_rcv_msg+0x7c2/0xeb0 Fixes: 0597f2680d66 ("[NETFILTER]: Add new "nfnetlink_log" userspace packet logging facility") Reported-by: Eulgyu Kim Reported-by: Jaeyoung Chung Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nfnetlink_log.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c index 9d7fec570abe..d923f2cb1398 100644 --- a/net/netfilter/nfnetlink_log.c +++ b/net/netfilter/nfnetlink_log.c @@ -228,13 +228,18 @@ static void __nfulnl_flush(struct nfulnl_instance *inst); static void __instance_destroy(struct nfulnl_instance *inst) { + spin_lock(&inst->lock); + if (inst->copy_mode == NFULNL_COPY_DISABLED) { + /* attempt to UNBIND a queue already pending + * destruction via netlink close event. Ignore. + */ + spin_unlock(&inst->lock); + return; + } + /* first pull it out of the global list */ hlist_del_rcu(&inst->hlist); - /* then flush all pending packets from skb */ - - spin_lock(&inst->lock); - /* lockless readers wont be able to use us */ inst->copy_mode = NFULNL_COPY_DISABLED; From 0bd7ed1a3263c26cf38fffc035b539a70d88667b Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 25 Aug 2026 12:28:36 +0200 Subject: [PATCH 0465/1317] netfilter: arp_tables: remove the 32bit compat interface This feature is required to use 32bit arptables binary on 64bit kernels. It's already off in many distributions including Debian and Fedora for many years. Zap arptables first, it's the most esoteric of the 4 flavors. Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter_arp/arp_tables.h | 19 - net/ipv4/netfilter/arp_tables.c | 472 +---------------------- net/netfilter/Kconfig | 2 +- 3 files changed, 4 insertions(+), 489 deletions(-) diff --git a/include/linux/netfilter_arp/arp_tables.h b/include/linux/netfilter_arp/arp_tables.h index 05631a25e622..8b8d472eff34 100644 --- a/include/linux/netfilter_arp/arp_tables.h +++ b/include/linux/netfilter_arp/arp_tables.h @@ -56,23 +56,4 @@ void arpt_unregister_table(struct net *net, const char *name); extern unsigned int arpt_do_table(void *priv, struct sk_buff *skb, const struct nf_hook_state *state); -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT -#include - -struct compat_arpt_entry { - struct arpt_arp arp; - __u16 target_offset; - __u16 next_offset; - compat_uint_t comefrom; - struct compat_xt_counters counters; - unsigned char elems[]; -}; - -static inline struct xt_entry_target * -compat_arpt_get_target(struct compat_arpt_entry *e) -{ - return (void *)e + e->target_offset; -} - -#endif /* CONFIG_COMPAT */ #endif /* _ARPTABLES_H */ diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c index a87e07e80d0d..db307fa49f3f 100644 --- a/net/ipv4/netfilter/arp_tables.c +++ b/net/ipv4/netfilter/arp_tables.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -724,80 +723,6 @@ static int copy_entries_to_user(unsigned int total_size, return ret; } -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT -static void compat_standard_from_user(void *dst, const void *src) -{ - int v = *(compat_int_t *)src; - - if (v > 0) - v += xt_compat_calc_jump(NFPROTO_ARP, v); - memcpy(dst, &v, sizeof(v)); -} - -static int compat_standard_to_user(void __user *dst, const void *src) -{ - compat_int_t cv = *(int *)src; - - if (cv > 0) - cv -= xt_compat_calc_jump(NFPROTO_ARP, cv); - return copy_to_user(dst, &cv, sizeof(cv)) ? -EFAULT : 0; -} - -static int compat_calc_entry(const struct arpt_entry *e, - const struct xt_table_info *info, - const void *base, struct xt_table_info *newinfo) -{ - const struct xt_entry_target *t; - unsigned int entry_offset; - int off, i, ret; - - off = sizeof(struct arpt_entry) - sizeof(struct compat_arpt_entry); - entry_offset = (void *)e - base; - - t = arpt_get_target_c(e); - off += xt_compat_target_offset(t->u.kernel.target); - newinfo->size -= off; - ret = xt_compat_add_offset(NFPROTO_ARP, entry_offset, off); - if (ret) - return ret; - - for (i = 0; i < NF_ARP_NUMHOOKS; i++) { - if (info->hook_entry[i] && - (e < (struct arpt_entry *)(base + info->hook_entry[i]))) - newinfo->hook_entry[i] -= off; - if (info->underflow[i] && - (e < (struct arpt_entry *)(base + info->underflow[i]))) - newinfo->underflow[i] -= off; - } - return 0; -} - -static int compat_table_info(const struct xt_table_info *info, - struct xt_table_info *newinfo) -{ - struct arpt_entry *iter; - const void *loc_cpu_entry; - int ret; - - if (!newinfo || !info) - return -EINVAL; - - /* we dont care about newinfo->entries */ - memcpy(newinfo, info, offsetof(struct xt_table_info, entries)); - newinfo->initial_entries = 0; - loc_cpu_entry = info->entries; - ret = xt_compat_init_offsets(NFPROTO_ARP, info->number); - if (ret) - return ret; - xt_entry_foreach(iter, loc_cpu_entry, info->size) { - ret = compat_calc_entry(iter, info, loc_cpu_entry, newinfo); - if (ret != 0) - return ret; - } - return 0; -} -#endif - static int get_info(struct net *net, void __user *user, const int *len) { char name[XT_TABLE_MAXNAMELEN]; @@ -811,23 +736,11 @@ static int get_info(struct net *net, void __user *user, const int *len) return -EFAULT; name[XT_TABLE_MAXNAMELEN-1] = '\0'; -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - if (in_compat_syscall()) - xt_compat_lock(NFPROTO_ARP); -#endif t = xt_request_find_table_lock(net, NFPROTO_ARP, name); if (!IS_ERR(t)) { struct arpt_getinfo info; const struct xt_table_info *private = t->private; -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - struct xt_table_info tmp; - if (in_compat_syscall()) { - ret = compat_table_info(private, &tmp); - xt_compat_flush_offsets(NFPROTO_ARP); - private = &tmp; - } -#endif memset(&info, 0, sizeof(info)); info.valid_hooks = t->valid_hooks; memcpy(info.hook_entry, private->hook_entry, @@ -846,10 +759,7 @@ static int get_info(struct net *net, void __user *user, const int *len) module_put(t->me); } else ret = PTR_ERR(t); -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - if (in_compat_syscall()) - xt_compat_unlock(NFPROTO_ARP); -#endif + return ret; } @@ -1059,367 +969,6 @@ static int do_add_counters(struct net *net, sockptr_t arg, unsigned int len) return ret; } -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT -struct compat_arpt_replace { - char name[XT_TABLE_MAXNAMELEN]; - u32 valid_hooks; - u32 num_entries; - u32 size; - u32 hook_entry[NF_ARP_NUMHOOKS]; - u32 underflow[NF_ARP_NUMHOOKS]; - u32 num_counters; - compat_uptr_t counters; - struct compat_arpt_entry entries[]; -}; - -static inline void compat_release_entry(struct compat_arpt_entry *e) -{ - struct xt_entry_target *t; - - t = compat_arpt_get_target(e); - module_put(t->u.kernel.target->me); -} - -static int -check_compat_entry_size_and_hooks(struct compat_arpt_entry *e, - struct xt_table_info *newinfo, - unsigned int *size, - const unsigned char *base, - const unsigned char *limit) -{ - struct xt_entry_target *t; - struct xt_target *target; - unsigned int entry_offset; - int ret, off; - - if ((unsigned long)e % __alignof__(struct compat_arpt_entry) != 0 || - (unsigned char *)e + sizeof(struct compat_arpt_entry) >= limit || - (unsigned char *)e + e->next_offset > limit) - return -EINVAL; - - if (e->next_offset < sizeof(struct compat_arpt_entry) + - sizeof(struct compat_xt_entry_target)) - return -EINVAL; - - if (!arp_checkentry(&e->arp)) - return -EINVAL; - - ret = xt_compat_check_entry_offsets(e, e->elems, e->target_offset, - e->next_offset); - if (ret) - return ret; - - off = sizeof(struct arpt_entry) - sizeof(struct compat_arpt_entry); - entry_offset = (void *)e - (void *)base; - - t = compat_arpt_get_target(e); - target = xt_request_find_target(NFPROTO_ARP, t->u.user.name, - t->u.user.revision); - if (IS_ERR(target)) { - ret = PTR_ERR(target); - goto out; - } - t->u.kernel.target = target; - - off += xt_compat_target_offset(target); - *size += off; - ret = xt_compat_add_offset(NFPROTO_ARP, entry_offset, off); - if (ret) - goto release_target; - - return 0; - -release_target: - module_put(t->u.kernel.target->me); -out: - return ret; -} - -static void -compat_copy_entry_from_user(struct compat_arpt_entry *e, void **dstptr, - unsigned int *size, - struct xt_table_info *newinfo, unsigned char *base) -{ - struct xt_entry_target *t; - struct arpt_entry *de; - unsigned int origsize; - int h; - - origsize = *size; - de = *dstptr; - memcpy(de, e, sizeof(struct arpt_entry)); - memcpy(&de->counters, &e->counters, sizeof(e->counters)); - - *dstptr += sizeof(struct arpt_entry); - *size += sizeof(struct arpt_entry) - sizeof(struct compat_arpt_entry); - - de->target_offset = e->target_offset - (origsize - *size); - t = compat_arpt_get_target(e); - xt_compat_target_from_user(t, dstptr, size); - - de->next_offset = e->next_offset - (origsize - *size); - for (h = 0; h < NF_ARP_NUMHOOKS; h++) { - if ((unsigned char *)de - base < newinfo->hook_entry[h]) - newinfo->hook_entry[h] -= origsize - *size; - if ((unsigned char *)de - base < newinfo->underflow[h]) - newinfo->underflow[h] -= origsize - *size; - } -} - -static int translate_compat_table(struct net *net, - struct xt_table_info **pinfo, - void **pentry0, - const struct compat_arpt_replace *compatr) -{ - unsigned int i, j; - struct xt_table_info *newinfo, *info; - void *pos, *entry0, *entry1; - struct compat_arpt_entry *iter0; - struct arpt_replace repl; - unsigned int size; - int ret; - - info = *pinfo; - entry0 = *pentry0; - size = compatr->size; - info->number = compatr->num_entries; - - j = 0; - xt_compat_lock(NFPROTO_ARP); - ret = xt_compat_init_offsets(NFPROTO_ARP, compatr->num_entries); - if (ret) - goto out_unlock; - /* Walk through entries, checking offsets. */ - xt_entry_foreach(iter0, entry0, compatr->size) { - ret = check_compat_entry_size_and_hooks(iter0, info, &size, - entry0, - entry0 + compatr->size); - if (ret != 0) - goto out_unlock; - ++j; - } - - ret = -EINVAL; - if (j != compatr->num_entries) - goto out_unlock; - - ret = -ENOMEM; - newinfo = xt_alloc_table_info(size); - if (!newinfo) - goto out_unlock; - - memset(newinfo->entries, 0, size); - - newinfo->number = compatr->num_entries; - for (i = 0; i < NF_ARP_NUMHOOKS; i++) { - newinfo->hook_entry[i] = compatr->hook_entry[i]; - newinfo->underflow[i] = compatr->underflow[i]; - } - entry1 = newinfo->entries; - pos = entry1; - size = compatr->size; - xt_entry_foreach(iter0, entry0, compatr->size) - compat_copy_entry_from_user(iter0, &pos, &size, - newinfo, entry1); - - /* all module references in entry0 are now gone */ - - xt_compat_flush_offsets(NFPROTO_ARP); - xt_compat_unlock(NFPROTO_ARP); - - memcpy(&repl, compatr, sizeof(*compatr)); - - for (i = 0; i < NF_ARP_NUMHOOKS; i++) { - repl.hook_entry[i] = newinfo->hook_entry[i]; - repl.underflow[i] = newinfo->underflow[i]; - } - - repl.num_counters = 0; - repl.counters = NULL; - repl.size = newinfo->size; - ret = translate_table(net, newinfo, entry1, &repl); - if (ret) - goto free_newinfo; - - *pinfo = newinfo; - *pentry0 = entry1; - xt_free_table_info(info); - return 0; - -free_newinfo: - xt_free_table_info(newinfo); - return ret; -out_unlock: - xt_compat_flush_offsets(NFPROTO_ARP); - xt_compat_unlock(NFPROTO_ARP); - xt_entry_foreach(iter0, entry0, compatr->size) { - if (j-- == 0) - break; - compat_release_entry(iter0); - } - return ret; -} - -static int compat_do_replace(struct net *net, sockptr_t arg, unsigned int len) -{ - int ret; - struct compat_arpt_replace tmp; - struct xt_table_info *newinfo; - void *loc_cpu_entry; - struct arpt_entry *iter; - - if (len < sizeof(tmp)) - return -EINVAL; - if (copy_from_sockptr(&tmp, arg, sizeof(tmp)) != 0) - return -EFAULT; - - /* overflow check */ - if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters)) - return -ENOMEM; - if (tmp.num_counters == 0) - return -EINVAL; - if ((u64)len < (u64)tmp.size + sizeof(tmp)) - return -EINVAL; - - tmp.name[sizeof(tmp.name)-1] = 0; - - newinfo = xt_alloc_table_info(tmp.size); - if (!newinfo) - return -ENOMEM; - - loc_cpu_entry = newinfo->entries; - if (copy_from_sockptr_offset(loc_cpu_entry, arg, sizeof(tmp), - tmp.size) != 0) { - ret = -EFAULT; - goto free_newinfo; - } - - ret = translate_compat_table(net, &newinfo, &loc_cpu_entry, &tmp); - if (ret != 0) - goto free_newinfo; - - ret = __do_replace(net, tmp.name, tmp.valid_hooks, newinfo, - tmp.num_counters, compat_ptr(tmp.counters)); - if (ret) - goto free_newinfo_untrans; - return 0; - - free_newinfo_untrans: - xt_entry_foreach(iter, loc_cpu_entry, newinfo->size) - cleanup_entry(iter, net); - free_newinfo: - xt_free_table_info(newinfo); - return ret; -} - -static int compat_copy_entry_to_user(struct arpt_entry *e, void __user **dstptr, - compat_uint_t *size, - struct xt_counters *counters, - unsigned int i) -{ - struct xt_entry_target *t; - struct compat_arpt_entry __user *ce; - u_int16_t target_offset, next_offset; - compat_uint_t origsize; - int ret; - - origsize = *size; - ce = *dstptr; - if (copy_to_user(ce, e, offsetof(struct compat_arpt_entry, counters)) || - copy_to_user(&ce->counters, &counters[i], sizeof(counters[i]))) - return -EFAULT; - - *dstptr += sizeof(struct compat_arpt_entry); - *size -= sizeof(struct arpt_entry) - sizeof(struct compat_arpt_entry); - - target_offset = e->target_offset - (origsize - *size); - - t = arpt_get_target(e); - ret = xt_compat_target_to_user(t, dstptr, size); - if (ret) - return ret; - next_offset = e->next_offset - (origsize - *size); - if (put_user(target_offset, &ce->target_offset) != 0 || - put_user(next_offset, &ce->next_offset) != 0) - return -EFAULT; - return 0; -} - -static int compat_copy_entries_to_user(unsigned int total_size, - struct xt_table *table, - void __user *userptr) -{ - struct xt_counters *counters; - const struct xt_table_info *private = table->private; - void __user *pos; - unsigned int size; - int ret = 0; - unsigned int i = 0; - struct arpt_entry *iter; - - counters = alloc_counters(table); - if (IS_ERR(counters)) - return PTR_ERR(counters); - - pos = userptr; - size = total_size; - xt_entry_foreach(iter, private->entries, total_size) { - ret = compat_copy_entry_to_user(iter, &pos, - &size, counters, i++); - if (ret != 0) - break; - } - vfree(counters); - return ret; -} - -struct compat_arpt_get_entries { - char name[XT_TABLE_MAXNAMELEN]; - compat_uint_t size; - struct compat_arpt_entry entrytable[]; -}; - -static int compat_get_entries(struct net *net, - struct compat_arpt_get_entries __user *uptr, - int *len) -{ - int ret; - struct compat_arpt_get_entries get; - struct xt_table *t; - - if (*len < sizeof(get)) - return -EINVAL; - if (copy_from_user(&get, uptr, sizeof(get)) != 0) - return -EFAULT; - if (*len != sizeof(struct compat_arpt_get_entries) + get.size) - return -EINVAL; - - get.name[sizeof(get.name) - 1] = '\0'; - - xt_compat_lock(NFPROTO_ARP); - t = xt_find_table_lock(net, NFPROTO_ARP, get.name); - if (!IS_ERR(t)) { - const struct xt_table_info *private = t->private; - struct xt_table_info info; - - ret = compat_table_info(private, &info); - if (!ret && get.size == info.size) { - ret = compat_copy_entries_to_user(private->size, - t, uptr->entrytable); - } else if (!ret) - ret = -EAGAIN; - - xt_compat_flush_offsets(NFPROTO_ARP); - module_put(t->me); - xt_table_unlock(t); - } else - ret = PTR_ERR(t); - - xt_compat_unlock(NFPROTO_ARP); - return ret; -} -#endif - static int do_arpt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, unsigned int len) { @@ -1432,12 +981,7 @@ static int do_arpt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, switch (cmd) { case ARPT_SO_SET_REPLACE: -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - if (in_compat_syscall()) - ret = compat_do_replace(sock_net(sk), arg, len); - else -#endif - ret = do_replace(sock_net(sk), arg, len); + ret = do_replace(sock_net(sk), arg, len); break; case ARPT_SO_SET_ADD_COUNTERS: @@ -1466,12 +1010,7 @@ static int do_arpt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len break; case ARPT_SO_GET_ENTRIES: -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - if (in_compat_syscall()) - ret = compat_get_entries(sock_net(sk), user, len); - else -#endif - ret = get_entries(sock_net(sk), user, len); + ret = get_entries(sock_net(sk), user, len); break; case ARPT_SO_GET_REVISION_TARGET: { @@ -1568,11 +1107,6 @@ static struct xt_target arpt_builtin_tg[] __read_mostly = { .name = XT_STANDARD_TARGET, .targetsize = sizeof(int), .family = NFPROTO_ARP, -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - .compatsize = sizeof(compat_int_t), - .compat_from_user = compat_standard_from_user, - .compat_to_user = compat_standard_to_user, -#endif }, { .name = XT_ERROR_TARGET, diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index 4c04cd8d40a2..09874c26fd13 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -739,7 +739,7 @@ config NETFILTER_XTABLES_COMPAT bool "Netfilter Xtables 32bit support" depends on COMPAT help - This option provides a translation layer to run 32bit arp,ip(6),ebtables + This option provides a translation layer to run 32bit ip(6),ebtables binaries on 64bit kernels. If unsure, say N. From da4afc5a956d407443988e97a4d4ca14c2e999c7 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 25 Aug 2026 15:11:24 +0200 Subject: [PATCH 0466/1317] netfilter: ip6_tables: set F_PROTO when proto value is nonzero The ip6tables traverser doesn't search the extension header chain unless userspace did set the IP6T_F_PROTO flag. This also means that userspace that sets the e->ipv6.proto flag can bypass the protocol check for the rule by not setting this flag. That in turn means that all ip6_tables modules and targets that want to reject rules without '-p' flag MUST also check for that flag. Not all do, likely because they got copied from iptables which lacks this flag (no extension headers). Instead of fixing up all the relevant targets, emulate ip6tables behaviour in the kernel (like nft_compat.c) and set the flag if the protocol is set. Reported-by: Zhiling Zou Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/ipv6/netfilter/ip6_tables.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c index f42fb96ef64b..313c4aac377a 100644 --- a/net/ipv6/netfilter/ip6_tables.c +++ b/net/ipv6/netfilter/ip6_tables.c @@ -647,6 +647,11 @@ check_entry_size_and_hooks(struct ip6t_entry *e, /* Clear counters and comefrom */ e->counters = ((struct xt_counters) { 0, 0 }); e->comefrom = 0; + + /* set F_PROTO, else ip6_packet_match won't do the right thing. */ + if (e->ipv6.proto) + e->ipv6.flags |= IP6T_F_PROTO; + return 0; } From 7a099b347fef536a84068076e2d384f044e5cfc5 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 25 Aug 2026 17:27:24 +0200 Subject: [PATCH 0467/1317] netfilter: report NLM_F_DUMP_FILTERED when all is filtered out NLM_F_DUMP_FILTERED is only set on data elements in the conntrack dump. But when everything is filtered out it is confusing for the user space, since the flag is not reported anymore and it looks like the table was empty, which may or may not be the case. 'answer_flags' were introduced precisely for this use case, and the conntrack dump should set the flag in there in case the filtering was applied. This is important, for example, to be able to tell if the filters are supported or not by the kernel without modifying the kernel state. With the proper reporting of NLM_F_DUMP_FILTERED on NLMSG_DONE, an application in user space can just try and dump with an arbitrary filter without worrying that there could be no matching entry. The reported flag will signal that the filtering was applied and therefore supported. Fixes: cb8aa9a3affb ("netfilter: ctnetlink: add kernel side filtering for dump") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_netlink.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 9b4e29557ec3..579ada063b1b 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -1077,6 +1077,8 @@ static int ctnetlink_start(struct netlink_callback *cb) } cb->data = filter; + if (filter) + cb->answer_flags = NLM_F_DUMP_FILTERED; return 0; } From f3e6ef13e24c9f26dca0d35de57fcdf04f78e378 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Fri, 4 Sep 2026 19:56:24 +0900 Subject: [PATCH 0468/1317] regulator: pf1550: fix which regulator is notified The interrupt handler distinguishes the rail that reported the fault, but the body ignores it. Every SW interrupt walks the regulator array looking for the name "SW3" and every LDO interrupt looks for "LDO3", so an over-current on SW1 is reported to the consumers of SW3 while the consumers of SW1 hear nothing. The lookup itself is unreliable as well. rdev_get_name() returns the device tree regulator-name property whenever the board supplies one, and only falls back to the name in the driver descriptor when it does not. The binding example for this device sets regulator-name to "sw3" and "ldo3", which strcmp() does not match against the upper case literals used here, so a board that follows the documentation gets no over-current notification at all. A board that names its rails after the schematic does not match either. No other driver in the tree selects a notification target this way. Replace the name lookup with rdev_get_id(), which returns the descriptor id set by the driver and cannot be overridden from the device tree, and take both the id and the event from a table indexed by the interrupt. The die temperature interrupts keep notifying every regulator since they report a chip wide condition. Fixes: 7320d41c29bb ("regulator: pf1550: Add support for regulator") Signed-off-by: Donggeun Yoo Link: https://patch.msgid.link/20260904105624.48577-1-donggeunyoo.kernel@gmail.com Signed-off-by: Mark Brown --- drivers/regulator/pf1550-regulator.c | 82 ++++++++++++++-------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/drivers/regulator/pf1550-regulator.c b/drivers/regulator/pf1550-regulator.c index 610eac9bb9cb..ceee553a84b2 100644 --- a/drivers/regulator/pf1550-regulator.c +++ b/drivers/regulator/pf1550-regulator.c @@ -283,63 +283,61 @@ static struct pf1550_desc pf1550_regulators[] = { PF_LDO1(PF1550, "ldo3", LDO3, 0x1f, pf1550_ldo13_volts), }; +/* + * The _LS interrupts indicate an over-current event. The _HS + * interrupts, which are more accurate and can detect catastrophic + * faults, issue an error event. The current limit FAULT interrupt is + * similar to the _HS. + */ +static const struct pf1550_regulator_irq { + unsigned int event; + u8 id; +} pf1550_regulator_irqs[] = { + [PF1550_PMIC_IRQ_SW1_LS] = { REGULATOR_EVENT_OVER_CURRENT_WARN, PF1550_SW1 }, + [PF1550_PMIC_IRQ_SW2_LS] = { REGULATOR_EVENT_OVER_CURRENT_WARN, PF1550_SW2 }, + [PF1550_PMIC_IRQ_SW3_LS] = { REGULATOR_EVENT_OVER_CURRENT_WARN, PF1550_SW3 }, + [PF1550_PMIC_IRQ_SW1_HS] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_SW1 }, + [PF1550_PMIC_IRQ_SW2_HS] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_SW2 }, + [PF1550_PMIC_IRQ_SW3_HS] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_SW3 }, + [PF1550_PMIC_IRQ_LDO1_FAULT] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_LDO1 }, + [PF1550_PMIC_IRQ_LDO2_FAULT] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_LDO2 }, + [PF1550_PMIC_IRQ_LDO3_FAULT] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_LDO3 }, +}; + static irqreturn_t pf1550_regulator_irq_handler(int irq, void *data) { + const struct pf1550_regulator_irq *map; struct pf1550_regulator_info *info = data; struct device *dev = info->dev; struct platform_device *pdev = to_platform_device(dev); int i, irq_type = -1; - unsigned int event; for (i = 0; i < PF1550_REGULATOR_IRQ_NR; i++) if (irq == platform_get_irq(pdev, i)) irq_type = i; - switch (irq_type) { - /* The _LS interrupts indicate over-current event. The _HS interrupts - * which are more accurate and can detect catastrophic faults, issue - * an error event. The current limit FAULT interrupt is similar to the - * _HS' - */ - case PF1550_PMIC_IRQ_SW1_LS: - case PF1550_PMIC_IRQ_SW2_LS: - case PF1550_PMIC_IRQ_SW3_LS: - event = REGULATOR_EVENT_OVER_CURRENT_WARN; - for (i = 0; i < PF1550_MAX_REGULATOR; i++) - if (!strcmp(rdev_get_name(info->rdevs[i]), "SW3")) - regulator_notifier_call_chain(info->rdevs[i], - event, NULL); - break; - case PF1550_PMIC_IRQ_SW1_HS: - case PF1550_PMIC_IRQ_SW2_HS: - case PF1550_PMIC_IRQ_SW3_HS: - event = REGULATOR_EVENT_OVER_CURRENT; - for (i = 0; i < PF1550_MAX_REGULATOR; i++) - if (!strcmp(rdev_get_name(info->rdevs[i]), "SW3")) - regulator_notifier_call_chain(info->rdevs[i], - event, NULL); - break; - case PF1550_PMIC_IRQ_LDO1_FAULT: - case PF1550_PMIC_IRQ_LDO2_FAULT: - case PF1550_PMIC_IRQ_LDO3_FAULT: - event = REGULATOR_EVENT_OVER_CURRENT; - for (i = 0; i < PF1550_MAX_REGULATOR; i++) - if (!strcmp(rdev_get_name(info->rdevs[i]), "LDO3")) - regulator_notifier_call_chain(info->rdevs[i], - event, NULL); - break; - case PF1550_PMIC_IRQ_TEMP_110: - case PF1550_PMIC_IRQ_TEMP_125: - event = REGULATOR_EVENT_OVER_TEMP; + /* The die temperature concerns every rail. */ + if (irq_type == PF1550_PMIC_IRQ_TEMP_110 || + irq_type == PF1550_PMIC_IRQ_TEMP_125) { for (i = 0; i < PF1550_MAX_REGULATOR; i++) regulator_notifier_call_chain(info->rdevs[i], - event, NULL); - break; - default: - dev_err(dev, "regulator interrupt: irq %d occurred\n", - irq_type); + REGULATOR_EVENT_OVER_TEMP, + NULL); + return IRQ_HANDLED; } + if (irq_type < 0 || irq_type >= (int)ARRAY_SIZE(pf1550_regulator_irqs)) { + dev_err(dev, "regulator interrupt: irq %d occurred\n", irq_type); + return IRQ_HANDLED; + } + + map = &pf1550_regulator_irqs[irq_type]; + + for (i = 0; i < PF1550_MAX_REGULATOR; i++) + if (rdev_get_id(info->rdevs[i]) == map->id) + regulator_notifier_call_chain(info->rdevs[i], + map->event, NULL); + return IRQ_HANDLED; } From 9c3882ec10399c14c59b7e4599d33c4395367c37 Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:32:10 +0800 Subject: [PATCH 0469/1317] ASoC: mt6351: Publish the OF module alias The MT6351 codec platform driver uses mt6351_of_match to bind devices with compatible mediatek,mt6351-sound. The codec can be a separate module, but the OF table is not exported to module alias metadata. Publish the existing table without changing codec matching, register access or the machine-driver configuration. Fixes: a74d51ba0e17 ("ASoC: add mt6351 codec driver") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905133210.63803-1-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/mt6351.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/mt6351.c b/sound/soc/codecs/mt6351.c index 1768c249650d..3be0491a2588 100644 --- a/sound/soc/codecs/mt6351.c +++ b/sound/soc/codecs/mt6351.c @@ -1478,6 +1478,7 @@ static const struct of_device_id mt6351_of_match[] = { {.compatible = "mediatek,mt6351-sound",}, {} }; +MODULE_DEVICE_TABLE(of, mt6351_of_match); static struct platform_driver mt6351_codec_driver = { .driver = { From 1017911fcc03584b6854b1b8f0aafeb25f5a8d25 Mon Sep 17 00:00:00 2001 From: Sascha Bischoff Date: Mon, 7 Sep 2026 16:49:50 +0000 Subject: [PATCH 0470/1317] irqchip/gic-v5: Preserve ICC_CR0_EL1 state In addition to EN, ICC_CR0_EL1 contains other fields, such as LINK and LINK_IDLE. The driver only needs to modify EN, and must preserve the values of all other fields when enabling or disabling the CPU interface. Define the missing LINK and LINK_IDLE fields, and use read-modify-write accesses to update EN without affecting the rest of ICC_CR0_EL1. Fixes: 7ec80fb3f025 ("irqchip/gic-v5: Add GICv5 PPI support") Reported-by: Sashiko Signed-off-by: Sascha Bischoff Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260907164945.714545-1-sascha.bischoff@arm.com Closes: https://lore.kernel.org/r/20260807121703.D4B7A1F00A3A@smtp.kernel.org --- arch/arm64/tools/sysreg | 4 +++- drivers/irqchip/irq-gic-v5.c | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg index 94bf065c8ac7..e2d37ee221b8 100644 --- a/arch/arm64/tools/sysreg +++ b/arch/arm64/tools/sysreg @@ -3761,7 +3761,9 @@ Sysreg ICC_CR0_EL1 3 1 12 0 1 Res0 63:39 Field 38 PID Field 37:32 IPPT -Res0 31:1 +Res0 31:3 +Field 2 LINK_IDLE +Field 1 LINK Field 0 EN EndSysreg diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c index ac2d423b1723..5f2551cf077d 100644 --- a/drivers/irqchip/irq-gic-v5.c +++ b/drivers/irqchip/irq-gic-v5.c @@ -974,7 +974,8 @@ static void gicv5_cpu_disable_interrupts(void) { u64 cr0; - cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0); + cr0 = read_sysreg_s(SYS_ICC_CR0_EL1); + cr0 &= ~ICC_CR0_EL1_EN_MASK; write_sysreg_s(cr0, SYS_ICC_CR0_EL1); isb(); } @@ -991,7 +992,8 @@ static void gicv5_cpu_enable_interrupts(void) pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, GICV5_IRQ_PRI_MI); write_sysreg_s(pcr, SYS_ICC_PCR_EL1); - cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 1); + cr0 = read_sysreg_s(SYS_ICC_CR0_EL1); + cr0 |= ICC_CR0_EL1_EN_MASK; write_sysreg_s(cr0, SYS_ICC_CR0_EL1); } From 22728415a9cc8da859c7b95b49eb405fb3debd6a Mon Sep 17 00:00:00 2001 From: Aaron Welwood Date: Sun, 6 Sep 2026 21:17:38 -0600 Subject: [PATCH 0471/1317] ASoC: amd: yc: add quirk for Acer Nitro AN17-41 internal mic The Acer Nitro AN17-41 uses "RB" as its board vendor and has no entry in yc_acp_quirk_table, so acp6x_probe() finds no DMI match, registers no card, and the internal digital microphone records only silence. Add a quirk entry for it so the DMIC is enabled. Signed-off-by: Aaron Welwood Link: https://patch.msgid.link/20260907031738.17257-1-abwelwood@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index c64c727e1034..385fede6d77f 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -388,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "RB"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN17-41"), + } + }, { .driver_data = &acp6x_card, .matches = { From 75d276e5bb68778b2916f98a2bc30f142ebadc64 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Mon, 17 Aug 2026 22:32:29 +0000 Subject: [PATCH 0472/1317] virtio_ring: fix stale descriptor flags after a failed packed add In a packed ring the AVAIL and USED bits sit in the descriptor itself, so writing them makes that descriptor available. Those bit combinations flip meaning on every round of the ring, tracked by a wrap counter, so invalidating or validating a descriptor means inverting both bits. Commit 1ce9e6055fa0 ("virtio_ring: introduce packed ring support") has virtqueue_add_packed() make every descriptor of a chain available as it maps the chain, and write the head last. The device consumes the ring in order and stops at a head that is not available yet, so it never reaches the rest. When vring_map_one_sg() fails partway, unmap_release unmaps the segments and restores avail_used_flags, but the descriptors it wrote to in the ring stay marked with AVAIL and USED bits. The head is now the only entry that keeps the device from consuming these stale entries. For example, the ring would look like this now. Z - pre-previous command A - previous command B - aborted command C - current command [A1 DONE] [A2 DONE] [B2] [B3] [Z1 DONE] When the driver now attempts to issue the C command, the next add starts at the same head as B. If C spans less descriptors than B, there is no end marker because AVAIL and USED bits were still in place. And that means the device will start interpreting these stale entries (B2/B3) as another command entry, which then blocks the queue. This effect typically happens in swiotlb configurations under memory pressure, because vring_map_one_sg() can then fail with larger I/O requests which then leads to command abortions. There are broadly 2 ways to avoid leaving those flags behind: 1) Defer those flags too until the chain is complete. 2) Rewrite those flags for the previous wrap counter. Implement the second option in both packed add paths. The first option traverses the chain a second time on every successful add. The second option invalidates all added descriptors when any add fails. With this patch applied, a packed virtqueue keeps completing requests after a failed add. Fixes: 1ce9e6055fa0 ("virtio_ring: introduce packed ring support") Fixes: f6a15d854986 ("virtio_ring: add in order support") Assisted-by: Kiro:claude-opus-5 checkpatch sparse Signed-off-by: Alexander Graf Signed-off-by: Michael S. Tsirkin Message-ID: <20260817223229.28954-1-graf@amazon.com> --- drivers/virtio/virtio_ring.c | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index 5c169fbb418a..db678f5a80e0 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -1670,7 +1670,7 @@ static inline int virtqueue_add_packed(struct vring_virtqueue *vq, struct scatterlist *sg; unsigned int i, n, c, descs_used, err_idx, len; __le16 head_flags, flags; - u16 head, id, prev, curr, avail_used_flags; + u16 head, id, prev, curr, avail_used_flags, unpub_flags; int err; START_USE(vq); @@ -1798,15 +1798,30 @@ static inline int virtqueue_add_packed(struct vring_virtqueue *vq, curr = vq->free_head; vq->packed.avail_used_flags = avail_used_flags; + unpub_flags = avail_used_flags ^ (1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED); for (n = 0; n < total_sg; n++) { if (i == err_idx) break; + /* + * The mapping loop made every descriptor but the head + * available. Stamp the previous wrap counter's AVAIL and USED + * bits on those, so that a later and shorter chain at this head + * does not leave one of them available beyond its own last + * descriptor. Marking them used instead would hand + * is_used_desc_packed() a completion we never made. + */ + if (i != head) + desc[i].flags = cpu_to_le16(unpub_flags); vring_unmap_extra_packed(vq, &vq->packed.desc_extra[curr]); curr = vq->packed.desc_extra[curr].next; i++; - if (i >= vq->packed.vring.num) + if (i >= vq->packed.vring.num) { i = 0; + unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED; + } } END_USE(vq); @@ -1828,7 +1843,7 @@ static inline int virtqueue_add_packed_in_order(struct vring_virtqueue *vq, struct scatterlist *sg; unsigned int i, n, sg_count, err_idx, total_in_len = 0; __le16 head_flags, flags; - u16 head, avail_used_flags; + u16 head, avail_used_flags, unpub_flags; bool avail_wrap_counter; int err; @@ -1955,14 +1970,29 @@ static inline int virtqueue_add_packed_in_order(struct vring_virtqueue *vq, i = head; vq->packed.avail_used_flags = avail_used_flags; vq->packed.avail_wrap_counter = avail_wrap_counter; + unpub_flags = avail_used_flags ^ (1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED); for (n = 0; n < total_sg; n++) { if (i == err_idx) break; + /* + * The mapping loop made every descriptor but the head + * available. Stamp the previous wrap counter's AVAIL and USED + * bits on those, so that a later and shorter chain at this head + * does not leave one of them available beyond its own last + * descriptor. Marking them used instead would hand + * is_used_desc_packed() a completion we never made. + */ + if (i != head) + desc[i].flags = cpu_to_le16(unpub_flags); vring_unmap_extra_packed(vq, &vq->packed.desc_extra[i]); i++; - if (i >= vq->packed.vring.num) + if (i >= vq->packed.vring.num) { i = 0; + unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED; + } } END_USE(vq); From 3f9a0fceb730f5107d52421ead5568eae25a0049 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 21 Aug 2026 23:39:53 +0200 Subject: [PATCH 0473/1317] virtio: fix use-after-free in unregister_virtio_device() device_unregister() is device_del() plus put_device(). When the caller holds no extra reference, that drops the last one and runs the release callback, which for several transports frees the memory the embedded struct virtio_device sits in. unregister_virtio_device() then calls virtio_debug_device_exit(), which reads dev->debugfs_dir out of the freed object. Affected transports are the ones whose release callback frees and whose remove path takes no reference: virtio_mmio, virtio_vdpa, virtio_uml, mlxbf-tmfifo and virtio_ccw. virtio_pci is unaffected because virtio_pci_remove() brackets the call with get_device() and put_device(). Remove the debugfs entries before the device can go away. They are only accessed through the protected debugfs interface, so debugfs_remove_recursive() waits for in-progress file operations before returning. Tearing them down while the device is still alive is therefore safe. Reproduced on User-Mode Linux with CONFIG_KASAN and CONFIG_VIRTIO_DEBUG by unbinding a virtio-uml device: BUG: KASAN: slab-use-after-free in virtio_debug_device_exit+0x36/0x4d Read of size 8 at addr 00000000616e0b10 by task init/1 __asan_report_load8_noabort virtio_debug_device_exit+0x36/0x4d unregister_virtio_device+0x48/0x75 virtio_uml_remove platform_remove device_release_driver_internal unbind_store Freed by task 1: kfree virtio_uml_release_dev device_release kobject_put put_device device_unregister With this applied, the report is gone and unbind is clean. Fixes: 96a8326d69ff ("virtio: add debugfs infrastructure to allow to debug virtio features") Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260821213953.76906-1-kmehltretter@gmail.com> --- drivers/virtio/virtio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio.c b/drivers/virtio/virtio.c index 75bb4ffe3b87..b6c9e927bef5 100644 --- a/drivers/virtio/virtio.c +++ b/drivers/virtio/virtio.c @@ -604,8 +604,8 @@ void unregister_virtio_device(struct virtio_device *dev) { int index = dev->index; /* save for after device release */ - device_unregister(&dev->dev); virtio_debug_device_exit(dev); + device_unregister(&dev->dev); ida_free(&virtio_index_ida, index); } EXPORT_SYMBOL_GPL(unregister_virtio_device); From 894f98e73983f37354214a89a3a7fd35bf9e3072 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Wed, 19 Aug 2026 10:12:30 +0800 Subject: [PATCH 0474/1317] virtio_console: do not free control-out buffers on remove __send_control_msg() publishes &portdev->cpkt as the control-out virtqueue cookie. remove_vqs() walks every virtqueue and passes leftover cookies to free_buf(), which treats them as struct port_buffer and reads sgpages. If a control message is still on c_ovq when the device is unbound, free_buf() reads past the ports_device object. KASAN reported slab-out-of-bounds in free_buf(): free_buf remove_vqs virtcons_remove unbind_store The object was the ports_device allocated in virtcons_probe(). Drain c_ovq without freeing. The packet lives in portdev and is released with it. Fixes: a7a69ec0d8e4 ("virtio_console: free buffers after reset") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260819021230.292696-1-physicalmtea@gmail.com> --- drivers/char/virtio_console.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 7f6cbe851d1e..019bcae81af5 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -1964,13 +1964,28 @@ static const struct file_operations portdev_fops = { static void remove_vqs(struct ports_device *portdev) { struct virtqueue *vq; + bool multiport = use_multiport(portdev); virtio_device_for_each_vq(portdev->vdev, vq) { struct port_buffer *buf; + unsigned int len; - flush_bufs(vq, true); - while ((buf = virtqueue_detach_unused_buf(vq))) - free_buf(buf, true); + /* + * c_ovq cookies are &portdev->cpkt, not port_buffer. + * Detach them but do not free_buf(). + */ + if (multiport && vq == portdev->c_ovq) { + spin_lock(&portdev->c_ovq_lock); + while (virtqueue_get_buf(vq, &len)) + ; + while (virtqueue_detach_unused_buf(vq)) + ; + spin_unlock(&portdev->c_ovq_lock); + } else { + flush_bufs(vq, true); + while ((buf = virtqueue_detach_unused_buf(vq))) + free_buf(buf, true); + } cond_resched(); } portdev->vdev->config->del_vqs(portdev->vdev); From ccb1dc7c527f8c925925cf92afc76ae590dac311 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Mon, 10 Aug 2026 09:03:00 +0800 Subject: [PATCH 0475/1317] vhost/vdpa: reject VRING_NUM larger than device max vhost_vring_set_num() accepts any non-zero power-of-two queue size that fits in 16 bits. vhost-vdpa then passes that value to set_vq_num() without comparing it with get_vq_num_max(). A process with access to /dev/vhost-vdpa-* can therefore configure a queue larger than the device advertises. With vdpa_sim, the worker can walk descriptors beyond the mapped descriptor ring. KASAN reports a 16-byte out-of-bounds read, corresponding to one vring_desc, in the vringh IOTLB path: BUG: KASAN: out-of-bounds in _copy_from_iter Read of size 16 copy_from_iotlb copydesc_iotlb vringh_getdesc_iotlb vdpasim_net_work Cache get_vq_num_max() immediately after reset. Some backends derive it from writable queue-size state, so querying it after SET_NUM may return the current size instead of the device capability. Invalidate the cached value before reset so a failed reset leaves SET_NUM disabled. For VHOST_SET_VRING_NUM, copy the complete vring state once and use the same index and size for validation, vq->num, and set_vq_num(). This ensures that validation and use operate on the same copied values. Fixes: 4c8cf31885f6 ("vhost: introduce vDPA-based backend") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260810010300.132959-1-physicalmtea@gmail.com> --- drivers/vhost/vdpa.c | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index c3d913bd7cac..4eb1eb5e5c79 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -58,6 +58,7 @@ struct vhost_vdpa { struct cdev cdev; atomic_t opened; u32 nvqs; + u16 vq_num_max; int virtio_id; int minor; struct eventfd_ctx *config_ctx; @@ -236,7 +237,9 @@ static void vhost_vdpa_unsetup_vq_irq(struct vhost_vdpa *v, u16 qid) static int _compat_vdpa_reset(struct vhost_vdpa *v) { struct vdpa_device *vdpa = v->vdpa; + const struct vdpa_config_ops *ops = vdpa->config; u32 flags = 0; + int ret; v->suspended = false; @@ -246,7 +249,14 @@ static int _compat_vdpa_reset(struct vhost_vdpa *v) VDPA_RESET_F_CLEAN_MAP : 0; } - return vdpa_reset(vdpa, flags); + v->vq_num_max = 0; + ret = vdpa_reset(vdpa, flags); + if (!ret) { + /* Some backends derive the max from mutable queue state. */ + v->vq_num_max = ops->get_vq_num_max(vdpa); + } + + return ret; } static int vhost_vdpa_reset(struct vhost_vdpa *v) @@ -648,9 +658,15 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, u32 idx; long r; - r = get_user(idx, (u32 __user *)argp); - if (r < 0) - return r; + if (cmd == VHOST_SET_VRING_NUM) { + if (copy_from_user(&s, argp, sizeof(s))) + return -EFAULT; + idx = s.index; + } else { + r = get_user(idx, (u32 __user *)argp); + if (r < 0) + return r; + } if (idx >= v->nvqs) return -ENOBUFS; @@ -659,6 +675,23 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, vq = &v->vqs[idx]; switch (cmd) { + case VHOST_SET_VRING_NUM: + mutex_lock(&vq->mutex); + if (vq->private_data) { + r = -EBUSY; + } else if (!s.num || s.num > 0xffff || + s.num > v->vq_num_max || + (s.num & (s.num - 1))) { + r = -EINVAL; + } else { + vq->num = s.num; + r = 0; + } + mutex_unlock(&vq->mutex); + if (r) + return r; + ops->set_vq_num(vdpa, idx, s.num); + return 0; case VHOST_VDPA_SET_VRING_ENABLE: if (copy_from_user(&s, argp, sizeof(s))) return -EFAULT; @@ -772,9 +805,6 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, ops->set_vq_cb(vdpa, idx, &cb); break; - case VHOST_SET_VRING_NUM: - ops->set_vq_num(vdpa, idx, vq->num); - break; } return r; From e74a9fa50749b9940b4fb13199652325e08d3c4a Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Fri, 7 Aug 2026 20:00:24 +1000 Subject: [PATCH 0476/1317] vhost-vdpa: don't install the eventfd_ctx_fdget() error in config_ctx vhost_vdpa_set_config_call() swaps the eventfd_ctx_fdget() return value into v->config_ctx before checking it, so on failure the field briefly holds an ERR_PTR: ctx = fd == VHOST_FILE_UNBIND ? NULL : eventfd_ctx_fdget(fd); swap(ctx, v->config_ctx); if (!IS_ERR_OR_NULL(ctx)) eventfd_ctx_put(ctx); if (IS_ERR(v->config_ctx)) { long ret = PTR_ERR(v->config_ctx); v->config_ctx = NULL; return ret; } Commit 0bde59c1723a ("vhost-vdpa: set v->config_ctx to NULL if eventfd_ctx_fdget() fails") added that clearing, and spelled out the invariant the rest of the file relies on: "we consider 'v->config_ctx' valid if it is not NULL". The window between the swap and the clearing still breaks it. vhost_vdpa_config_cb() only tests for NULL, so a config interrupt delivered inside the window hands the ERR_PTR to eventfd_signal(). Check the fd before installing it instead. That closes the window and matches how vhost_vring_ioctl() handles the same failure for the vq call fd. It also stops a rejected fd from tearing down a config interrupt that was working: until now the swap replaced the live context and put it, so after an EBADF the device silently stopped delivering config interrupts until userspace installed a new fd. Fixes: 776f395004d8 ("vhost_vdpa: Support config interrupt in vdpa") Signed-off-by: Yu Zhang Signed-off-by: Michael S. Tsirkin Message-ID: <20260807100025.19750-2-yuz08559@gmail.com> --- drivers/vhost/vdpa.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 4eb1eb5e5c79..3e5165b7c094 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -546,18 +546,14 @@ static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) return -EFAULT; ctx = fd == VHOST_FILE_UNBIND ? NULL : eventfd_ctx_fdget(fd); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + swap(ctx, v->config_ctx); - if (!IS_ERR_OR_NULL(ctx)) + if (ctx) eventfd_ctx_put(ctx); - if (IS_ERR(v->config_ctx)) { - long ret = PTR_ERR(v->config_ctx); - - v->config_ctx = NULL; - return ret; - } - v->vdpa->config->set_config_cb(v->vdpa, &cb); return 0; From 62be4e3e5f5f947fbf765b914cebdc478f715d12 Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Fri, 7 Aug 2026 20:00:25 +1000 Subject: [PATCH 0477/1317] vhost-vdpa: protect config_ctx from being freed under the config callback vhost_vdpa_config_cb() loads v->config_ctx and signals it without taking a reference and without holding any lock: struct eventfd_ctx *config_ctx = v->config_ctx; if (config_ctx) eventfd_signal(config_ctx); VHOST_VDPA_SET_CONFIG_CALL replaces that field and drops what is normally the last reference to the old context: swap(ctx, v->config_ctx); if (ctx) eventfd_ctx_put(ctx); eventfd_ctx_put() drops the last kref and frees the context immediately, with no RCU grace period, so a callback that has already loaded the pointer goes on to dereference freed memory. The two sides share no lock: the ioctl runs under vhost_dev.mutex, while the parent invokes the callback from its own interrupt or workqueue context. This is not the reopen refcount underflow fixed by commit f6bbf0010ba0 ("vhost-vdpa: fix use-after-free of v->config_ctx"), which was about vhost_vdpa_config_put() leaving a stale pointer behind. Here the pointer is maintained correctly and it is the read side that is unprotected. With VDUSE as the parent this is reachable from userspace with access to /dev/vduse (root by default). VDUSE_DEV_INJECT_CONFIG_IRQ queues dev->inject, and vduse_dev_irq_inject() runs the callback under VDUSE's own dev->irq_lock, which vhost does not hold. vduse_dev_reset() does flush_work(&dev->inject), but VHOST_VDPA_SET_CONFIG_CALL never goes through reset, so an inject already in flight is not waited for. A process that injects config interrupts on the VDUSE fd while another thread swaps the call fd on the vhost-vdpa fd hits it in seconds: BUG: KASAN: slab-use-after-free in native_queued_spin_lock_slowpath Read of size 4 at addr ffff888107d21808 by task kworker/u17:1/2993 Workqueue: vduse-irq vduse_dev_irq_inject Call Trace: native_queued_spin_lock_slowpath+0x97/0x5b0 _raw_spin_lock_irqsave+0xd4/0xe0 eventfd_signal_mask+0x69/0x120 vhost_vdpa_config_cb+0x34/0x50 vduse_dev_irq_inject+0x46/0x60 process_one_work+0x468/0x950 Allocated by task 2992: do_eventfd+0x50/0x200 __x64_sys_eventfd2+0x2e/0x40 Freed by task 2992: eventfd_ctx_put+0xb9/0xc0 vhost_vdpa_unlocked_ioctl+0x116c/0x2190 Add a spinlock covering every access to config_ctx, so the callback either signals a context that is still alive or observes NULL, and the put happens only once no callback can reach the old value. Clearing the parent's callback before the put would not be enough: of the in-tree set_config_cb() implementations only VDUSE takes a lock, the rest store the pointer unlocked, so that would not order against an in-flight invocation. Fixes: 776f395004d8 ("vhost_vdpa: Support config interrupt in vdpa") Signed-off-by: Yu Zhang Signed-off-by: Michael S. Tsirkin Message-ID: <20260807100025.19750-3-yuz08559@gmail.com> --- drivers/vhost/vdpa.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 3e5165b7c094..a31786796d4c 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -62,6 +62,8 @@ struct vhost_vdpa { int virtio_id; int minor; struct eventfd_ctx *config_ctx; + /* Serialises vhost_vdpa_config_cb() against config_ctx being replaced. */ + spinlock_t config_lock; int in_batch; struct vdpa_iova_range range; u32 batch_asid; @@ -195,10 +197,12 @@ static irqreturn_t vhost_vdpa_virtqueue_cb(void *private) static irqreturn_t vhost_vdpa_config_cb(void *private) { struct vhost_vdpa *v = private; - struct eventfd_ctx *config_ctx = v->config_ctx; + unsigned long flags; - if (config_ctx) - eventfd_signal(config_ctx); + spin_lock_irqsave(&v->config_lock, flags); + if (v->config_ctx) + eventfd_signal(v->config_ctx); + spin_unlock_irqrestore(&v->config_lock, flags); return IRQ_HANDLED; } @@ -528,15 +532,22 @@ static long vhost_vdpa_get_vring_num(struct vhost_vdpa *v, u16 __user *argp) static void vhost_vdpa_config_put(struct vhost_vdpa *v) { - if (v->config_ctx) { - eventfd_ctx_put(v->config_ctx); - v->config_ctx = NULL; - } + struct eventfd_ctx *ctx; + unsigned long flags; + + spin_lock_irqsave(&v->config_lock, flags); + ctx = v->config_ctx; + v->config_ctx = NULL; + spin_unlock_irqrestore(&v->config_lock, flags); + + if (ctx) + eventfd_ctx_put(ctx); } static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) { struct vdpa_callback cb; + unsigned long flags; int fd; struct eventfd_ctx *ctx; @@ -549,8 +560,14 @@ static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) if (IS_ERR(ctx)) return PTR_ERR(ctx); + spin_lock_irqsave(&v->config_lock, flags); swap(ctx, v->config_ctx); + spin_unlock_irqrestore(&v->config_lock, flags); + /* + * The callback can no longer reach the old context, so this is the + * last reference to it. + */ if (ctx) eventfd_ctx_put(ctx); @@ -1639,6 +1656,7 @@ static int vhost_vdpa_probe(struct vdpa_device *vdpa) } atomic_set(&v->opened, 0); + spin_lock_init(&v->config_lock); v->minor = minor; v->vdpa = vdpa; v->nvqs = vdpa->nvqs; From d14d693adb055e98ca705822ba6daebc18602d9a Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 11:29:37 +0800 Subject: [PATCH 0478/1317] virtio_mmio: disable IRQ wake before free_irq When the DT node has "wakeup-source", vm_find_vqs() calls enable_irq_wake() on the shared IRQ, but vm_del_vqs() freed that IRQ without a matching disable_irq_wake(). That leaves a wake reference behind and can warn on later free_irq()/request_irq() cycles. Record whether enable_irq_wake() succeeded, and disable it in vm_del_vqs() before free_irq(). Fixes: 02213273f72a ("virtio_mmio: add support to set IRQ of a virtio device as wakeup source") Cc: stable@vger.kernel.org Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260805032937.1606737-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_mmio.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index 316f03b97356..faae58e3401a 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -88,6 +88,9 @@ struct virtio_mmio_device { void __iomem *base; unsigned long version; + + /* True if enable_irq_wake() succeeded for the shared IRQ. */ + bool wake_irq_enabled; }; /* Configuration interface */ @@ -336,11 +339,17 @@ static void vm_del_vqs(struct virtio_device *vdev) { struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev); struct virtqueue *vq, *n; + int irq = platform_get_irq(vm_dev->pdev, 0); list_for_each_entry_safe(vq, n, &vdev->vqs, list) vm_del_vq(vq); - free_irq(platform_get_irq(vm_dev->pdev, 0), vm_dev); + if (vm_dev->wake_irq_enabled) { + disable_irq_wake(irq); + vm_dev->wake_irq_enabled = false; + } + + free_irq(irq, vm_dev); } static void vm_synchronize_cbs(struct virtio_device *vdev) @@ -467,8 +476,9 @@ static int vm_find_vqs(struct virtio_device *vdev, unsigned int nvqs, if (err) return err; - if (of_property_read_bool(vm_dev->pdev->dev.of_node, "wakeup-source")) - enable_irq_wake(irq); + if (of_property_read_bool(vm_dev->pdev->dev.of_node, "wakeup-source") && + !enable_irq_wake(irq)) + vm_dev->wake_irq_enabled = true; for (i = 0; i < nvqs; ++i) { struct virtqueue_info *vqi = &vqs_info[i]; From 6601d5a00899e7fa7e6b2d18113cee385ed3801b Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Thu, 6 Aug 2026 08:58:09 +0800 Subject: [PATCH 0479/1317] vdpa/pds: check virtqueue notify mapping vp_modern_map_vq_notify() can fail and return NULL. Check the notify mapping while adding a pds vDPA device and use the existing teardown path instead of storing a NULL doorbell pointer in the virtqueue state. Signed-off-by: Xiong Weimin Reviewed-by: Brett Creeley Signed-off-by: Michael S. Tsirkin Message-ID: <20260806005809.1875257-1-xiongweimin@kylinos.cn> --- drivers/vdpa/pds/vdpa_dev.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/vdpa/pds/vdpa_dev.c b/drivers/vdpa/pds/vdpa_dev.c index 43426bd971ac..77d679f6763d 100644 --- a/drivers/vdpa/pds/vdpa_dev.c +++ b/drivers/vdpa/pds/vdpa_dev.c @@ -731,6 +731,12 @@ static int pds_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, notify = vp_modern_map_vq_notify(&pdsv->vdpa_aux->vd_mdev, i, &pdsv->vqs[i].notify_pa); + if (!notify) { + err = -EINVAL; + dev_err(dev, "Fail to map vq notify %d\n", i); + goto err_unmap; + } + pds_vdpa_init_vqs_entry(pdsv, i, notify); } From 9ab9b4f4eb4288588707ec359ac3d5b7ccf07fa6 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:07 +0800 Subject: [PATCH 0480/1317] vdpa: alibaba: Keep DRIVER_OK clear if IRQ setup fails If requesting MSI-X interrupts fails while DRIVER_OK is being set, leave the device status unchanged instead of advertising a ready device without working interrupts. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092608.1344269-1-xiongweimin@kylinos.cn> --- drivers/vdpa/alibaba/eni_vdpa.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/alibaba/eni_vdpa.c b/drivers/vdpa/alibaba/eni_vdpa.c index fd6fdba46094..1288402d3dd8 100644 --- a/drivers/vdpa/alibaba/eni_vdpa.c +++ b/drivers/vdpa/alibaba/eni_vdpa.c @@ -216,7 +216,10 @@ static void eni_vdpa_set_status(struct vdpa_device *vdpa, u8 status) if (status & VIRTIO_CONFIG_S_DRIVER_OK && !(s & VIRTIO_CONFIG_S_DRIVER_OK)) { - eni_vdpa_request_irq(eni_vdpa); + if (eni_vdpa_request_irq(eni_vdpa)) { + WARN_ON(1); + return; + } } vp_legacy_set_status(ldev, status); From e847542ab0545c73354849126150206c29d83929 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 09:51:53 +0800 Subject: [PATCH 0481/1317] vdpa: solidrun: Free IRQs after request failure Unwind IRQs already requested by snet_request_irqs() before returning a VQ IRQ request error so a later DRIVER_OK retry starts from a clean state. The IRQs are requested and freed while the PCI device remains bound, so the driver cannot wait for devres cleanup at detach time. Fixes: 51a8f9d7f587 ("virtio: vdpa: new SolidNET DPU driver.") Cc: stable@vger.kernel.org # v6.3+ Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <178589471328.1556376.15570536900532373521@kylinos.cn> --- drivers/vdpa/solidrun/snet_main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/solidrun/snet_main.c b/drivers/vdpa/solidrun/snet_main.c index 28d55315df2a..3e2cea1e45f3 100644 --- a/drivers/vdpa/solidrun/snet_main.c +++ b/drivers/vdpa/solidrun/snet_main.c @@ -418,11 +418,15 @@ static int snet_request_irqs(struct pci_dev *pdev, struct snet *snet) snet->vqs[i]->irq_name, snet->vqs[i]); if (ret) { SNET_ERR(pdev, "Failed to request IRQ\n"); - return ret; + goto err_free_irqs; } snet->vqs[i]->irq = irq; } return 0; + +err_free_irqs: + snet_free_irqs(snet); + return ret; } static void snet_set_status(struct vdpa_device *vdev, u8 status) From 4d470be71196ca0ce302e6623454533dc31b465b Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 09:51:52 +0800 Subject: [PATCH 0482/1317] vdpa: ifcvf: Put device on unsupported feature error Route unsupported provisioned features through the common error path after vdpa_alloc_device() so the allocated device and adapter pointer are released consistently. Fixes: 46fc0917bbab ("vDPA/ifcvf: implement features provisioning") Cc: stable@vger.kernel.org # v6.3+ Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <178589471294.1556376.4816776800128323034@kylinos.cn> --- drivers/vdpa/ifcvf/ifcvf_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/ifcvf/ifcvf_main.c b/drivers/vdpa/ifcvf/ifcvf_main.c index ab6d6ab3b3d8..2af1cec95884 100644 --- a/drivers/vdpa/ifcvf/ifcvf_main.c +++ b/drivers/vdpa/ifcvf/ifcvf_main.c @@ -724,7 +724,8 @@ static int ifcvf_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, if (config->device_features & ~device_features) { IFCVF_ERR(pdev, "The provisioned features 0x%llx are not supported by this device with features 0x%llx\n", config->device_features, device_features); - return -EINVAL; + ret = -EINVAL; + goto err; } device_features &= config->device_features; } From 6519ca235131c3281a83cc9e8b05af709ab98a89 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:36 +0800 Subject: [PATCH 0483/1317] vdpa: octeon_ep: Check dev_set_name() in dev add Handle dev_set_name() failures before registering the vDPA device so allocation is unwound through the existing put_device() path. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092636.1344431-1-xiongweimin@kylinos.cn> --- drivers/vdpa/octeon_ep/octep_vdpa_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/vdpa/octeon_ep/octep_vdpa_main.c b/drivers/vdpa/octeon_ep/octep_vdpa_main.c index 23e280a29209..85a3d35ea1e4 100644 --- a/drivers/vdpa/octeon_ep/octep_vdpa_main.c +++ b/drivers/vdpa/octeon_ep/octep_vdpa_main.c @@ -600,6 +600,8 @@ static int octep_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, ret = dev_set_name(&vdpa_dev->dev, "%s", name); else ret = dev_set_name(&vdpa_dev->dev, "vdpa%u", vdpa_dev->index); + if (ret) + goto vdpa_dev_put; ret = _vdpa_register_device(&oct_vdpa->vdpa, oct_hw->nr_vring); if (ret) { From ca2c2165a02e499b591a367224346a7e52664d9c Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:49 +0800 Subject: [PATCH 0484/1317] virtio-vdpa: Use queue id when setting vq affinity When optional queues are skipped, pass the compressed vDPA queue id to set_vq_affinity() so affinity is applied to the queue that was actually created. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092649.1344478-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_vdpa.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_vdpa.c b/drivers/virtio/virtio_vdpa.c index de2af696de6c..6bcf4567a929 100644 --- a/drivers/virtio/virtio_vdpa.c +++ b/drivers/virtio/virtio_vdpa.c @@ -352,7 +352,7 @@ static int virtio_vdpa_find_vqs(struct virtio_device *vdev, unsigned int nvqs, continue; } - vqs[i] = virtio_vdpa_setup_vq(vdev, queue_idx++, vqi->callback, + vqs[i] = virtio_vdpa_setup_vq(vdev, queue_idx, vqi->callback, vqi->name, vqi->ctx); if (IS_ERR(vqs[i])) { err = PTR_ERR(vqs[i]); @@ -360,7 +360,8 @@ static int virtio_vdpa_find_vqs(struct virtio_device *vdev, unsigned int nvqs, } if (has_affinity) - ops->set_vq_affinity(vdpa, i, &masks[i]); + ops->set_vq_affinity(vdpa, queue_idx, &masks[i]); + queue_idx++; } cb.callback = virtio_vdpa_config_cb; From 0a8693f00c408d85f086ad85d29e7030bf1e2055 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 1 Sep 2026 17:48:00 +0800 Subject: [PATCH 0485/1317] vdpa_sim_blk: reject out-of-range sector starts vdpasim_blk_check_range() logs an invalid start sector but continues validating the request. The subsequent unsigned capacity subtraction can underflow and let an out-of-range buffer offset reach the data path. The invalid offset is used by three request paths. VIRTIO_BLK_T_OUT copies guest data to blk->buffer + offset through vringh_iov_pull_iotlb(), causing an out-of-bounds write in _copy_from_iter() or memcpy(). VIRTIO_BLK_T_IN copies from blk->buffer + offset to the guest through vringh_iov_push_iotlb(), causing an out-of-bounds read in _copy_to_iter(). VIRTIO_BLK_T_WRITE_ZEROES passes blk->buffer + offset to memset(), causing an out-of-bounds write. Reject starts at or beyond the capacity before the subtraction. Treat the capacity boundary as invalid because the IN and OUT paths round byte counts down to sectors for validation but later copy the original byte counts. A sub-sector request at the capacity boundary would otherwise still access past the end of the buffer. I found this bug myself, though the patch was written with AI assistance. Fixes: 7d189f617f83 ("vdpa_sim_blk: implement ramdisk behaviour") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260901094800.25475-1-linfeng.sun.dev@gmail.com> --- drivers/vdpa/vdpa_sim/vdpa_sim_blk.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c b/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c index f70f454dde8e..76dd5b0828d7 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c @@ -79,10 +79,11 @@ static void vdpasim_blk_buffer_unlock(struct vdpasim_blk *blk) static bool vdpasim_blk_check_range(struct vdpasim *vdpasim, u64 start_sector, u64 num_sectors, u64 max_sectors) { - if (start_sector > VDPASIM_BLK_CAPACITY) { + if (start_sector >= VDPASIM_BLK_CAPACITY) { dev_dbg(&vdpasim->vdpa.dev, "starting sector exceeds the capacity - start: 0x%llx capacity: 0x%x\n", start_sector, VDPASIM_BLK_CAPACITY); + return false; } if (num_sectors > max_sectors) { From 0d195797a80b77f2ec56718cd26d3ee65d0093e8 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 1 Sep 2026 17:48:42 +0800 Subject: [PATCH 0486/1317] vdpa_sim_net: check TX pull result before RX copy vringh_iov_pull_iotlb() returns a signed byte count. A failed TX pull is currently added to the unsigned byte counter and then passed as a size_t length to receive_filter() and vringh_iov_push_iotlb(). A negative error can therefore become a large length in the RX path. Handle non-positive pull results before every length use. Count the TX error and complete the consumed TX descriptor with zero bytes. I found this bug myself, though the patch was written with AI assistance. Fixes: cfe226892913 ("vdpa_sim: filter destination mac address") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260901094842.25875-1-linfeng.sun.dev@gmail.com> --- drivers/vdpa/vdpa_sim/vdpa_sim_net.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim_net.c b/drivers/vdpa/vdpa_sim/vdpa_sim_net.c index 29fd14ce5860..a6514b5ccd86 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim_net.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim_net.c @@ -225,10 +225,15 @@ static void vdpasim_net_work(struct vdpasim *vdpasim) break; } - ++tx_pkts; read = vringh_iov_pull_iotlb(&txq->vring, &txq->out_iov, net->buffer, PAGE_SIZE); + if (read <= 0) { + ++tx_errors; + vdpasim_net_complete(txq, 0); + continue; + } + ++tx_pkts; tx_bytes += read; if (!receive_filter(vdpasim, read)) { From 7034e6c8dadaf4a2c95669890095ebafa8d9cee7 Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Tue, 18 Aug 2026 15:39:13 +0200 Subject: [PATCH 0487/1317] MAINTAINERS: Add a section for virtio-rng At Michael's request, add a MAINTAINERS entry for the virtio-rng driver and list myself as its maintainer. I already maintain the corresponding QEMU implementation. Cc: Michael S. Tsirkin Signed-off-by: Laurent Vivier Signed-off-by: Michael S. Tsirkin Message-ID: <20260818133913.162471-1-lvivier@redhat.com> --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..35ddf814de94 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29017,6 +29017,13 @@ S: Maintained F: drivers/nvdimm/nd_virtio.c F: drivers/nvdimm/virtio_pmem.c +VIRTIO RNG DRIVER +M: Laurent Vivier +L: virtualization@lists.linux.dev +S: Maintained +F: drivers/char/hw_random/virtio-rng.c +F: include/uapi/linux/virtio_rng.h + VIRTIO RTC DRIVER M: Peter Hilber L: virtualization@lists.linux.dev From 84cd1f879968ae75da15c25de4cb390428e89e6d Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Thu, 3 Sep 2026 12:13:33 +0800 Subject: [PATCH 0488/1317] vhost: limit outstanding IOTLB misses per virtqueue vhost allocates a message node whenever address translation misses. If userspace reads these messages without resolving them, repeated virtqueue kicks can grow the pending message list until the host runs out of memory. Virtqueue processing stops at the first translation miss and cannot make progress until userspace installs a mapping. Keep a pointer to that outstanding message in the virtqueue and suppress additional misses until the node is resolved or discarded. The pointer remains set while the message is queued for reading, copied to userspace, or waiting on the pending list. Clear it under the IOTLB lock when the owning node is freed. This bounds outstanding miss messages by the fixed number of virtqueues without introducing an arbitrary queue limit. Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260903-fix-kernel-panic-in-vhost_iotlb_miss_pending_list-v1-1-39b8cd427978@gmail.com> --- drivers/vhost/vhost.c | 38 +++++++++++++++++++++++++++++++++----- drivers/vhost/vhost.h | 3 +++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 14637cff0bd4..02588b64b1bb 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -392,6 +392,7 @@ static void vhost_vq_reset(struct vhost_dev *dev, vq->busyloop_timeout = 0; vq->umem = NULL; vq->iotlb = NULL; + vq->iotlb_miss = NULL; rcu_assign_pointer(vq->worker, NULL); vhost_vring_call_reset(&vq->call_ctx); __vhost_vq_meta_reset(vq); @@ -1180,6 +1181,21 @@ void vhost_dev_stop(struct vhost_dev *dev) } EXPORT_SYMBOL_GPL(vhost_dev_stop); +static void vhost_free_msg_locked(struct vhost_msg_node *node) +{ + if (node->vq->iotlb_miss == node) + node->vq->iotlb_miss = NULL; + kfree(node); +} + +static void vhost_free_msg(struct vhost_dev *dev, + struct vhost_msg_node *node) +{ + spin_lock(&dev->iotlb_lock); + vhost_free_msg_locked(node); + spin_unlock(&dev->iotlb_lock); +} + void vhost_clear_msg(struct vhost_dev *dev) { struct vhost_msg_node *node, *n; @@ -1188,12 +1204,12 @@ void vhost_clear_msg(struct vhost_dev *dev) list_for_each_entry_safe(node, n, &dev->read_list, node) { list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } list_for_each_entry_safe(node, n, &dev->pending_list, node) { list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } spin_unlock(&dev->iotlb_lock); @@ -1602,7 +1618,7 @@ static void vhost_iotlb_notify_vq(struct vhost_dev *d, vq_msg->type == VHOST_IOTLB_MISS) { vhost_poll_queue(&node->vq->poll); list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } } @@ -1816,7 +1832,7 @@ ssize_t vhost_chr_read_iter(struct vhost_dev *dev, struct iov_iter *to, ret = copy_to_iter(start, size, to); if (ret != size || msg->type != VHOST_IOTLB_MISS) { - kfree(node); + vhost_free_msg(dev, node); return ret; } vhost_enqueue_msg(dev, &dev->pending_list, node); @@ -1848,7 +1864,19 @@ static int vhost_iotlb_miss(struct vhost_virtqueue *vq, u64 iova, int access) msg->iova = iova; msg->perm = access; - vhost_enqueue_msg(dev, &dev->read_list, node); + spin_lock(&dev->iotlb_lock); + /* VQ processing stops at the first miss until userspace resolves it. */ + if (vq->iotlb_miss) { + spin_unlock(&dev->iotlb_lock); + kfree(node); + return 0; + } + + vq->iotlb_miss = node; + list_add_tail(&node->node, &dev->read_list); + spin_unlock(&dev->iotlb_lock); + + wake_up_interruptible_poll(&dev->wait, EPOLLIN | EPOLLRDNORM); return 0; } diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 0192ade6e749..fa76b7d44662 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -29,6 +29,7 @@ struct vhost_work { struct vhost_worker; struct vhost_dev; +struct vhost_msg_node; struct vhost_worker_ops { int (*create)(struct vhost_worker *worker, struct vhost_dev *dev, @@ -148,6 +149,8 @@ struct vhost_virtqueue { /* Protected by virtqueue mutex. */ struct vhost_iotlb *umem; struct vhost_iotlb *iotlb; + /* Protected by dev->iotlb_lock. */ + struct vhost_msg_node *iotlb_miss; void *private_data; VIRTIO_DECLARE_FEATURES(acked_features); u64 acked_backend_features; From 8dd505a45de0e63829d8bb4116b1d66db64813be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Thu, 3 Sep 2026 10:18:31 +0200 Subject: [PATCH 0489/1317] =?UTF-8?q?virtio:=20add=20Eugenio=20P=C3=A9rez?= =?UTF-8?q?=20as=20Maintainer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eugenio Pérez Reviewed-by: Stefano Garzarella Signed-off-by: Michael S. Tsirkin Message-ID: <20260903081831.2129729-1-eperezma@redhat.com> --- MAINTAINERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 35ddf814de94..fdbd46fce4b3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28867,8 +28867,8 @@ F: include/uapi/linux/virtio_console.h VIRTIO CORE M: "Michael S. Tsirkin" M: Jason Wang +M: Eugenio Pérez R: Xuan Zhuo -R: Eugenio Pérez L: virtualization@lists.linux.dev S: Maintained F: Documentation/ABI/testing/sysfs-bus-vdpa @@ -28945,7 +28945,7 @@ F: include/uapi/linux/virtio_gpu.h VIRTIO HOST (VHOST) M: "Michael S. Tsirkin" M: Jason Wang -R: Eugenio Pérez +M: Eugenio Pérez L: kvm@vger.kernel.org L: virtualization@lists.linux.dev L: netdev@vger.kernel.org @@ -29000,8 +29000,8 @@ F: include/uapi/linux/virtio_mem.h VIRTIO NET DRIVER M: "Michael S. Tsirkin" M: Jason Wang +M: Eugenio Pérez R: Xuan Zhuo -R: Eugenio Pérez L: netdev@vger.kernel.org L: virtualization@lists.linux.dev S: Maintained From 93fa09455fb1a9624b73d42ac1f83771f4818e80 Mon Sep 17 00:00:00 2001 From: Andrew Stellman Date: Fri, 4 Sep 2026 10:13:18 -0400 Subject: [PATCH 0490/1317] virtio-pci: return IRQ_HANDLED after non-zero ISR vp_interrupt() reads the ISR before dispatching config-change and vring handling. Reading the ISR also clears it, so once the read returns non-zero the interrupt was from this device and has already been consumed. Currently vp_interrupt() returns the result of vp_vring_interrupt(). For a config-change interrupt with no vring work, that can return IRQ_NONE even though the ISR was non-zero and the interrupt was handled. Call vp_vring_interrupt() for any queue work, but once the ISR is non-zero return IRQ_HANDLED. Tested with QEMU virtio-blk-pci forced to INTx using vectors=0 and pci=nomsi. On an idle device, 200 config-change interrupts were generated using QMP block_resize. Before this change, irq_handler_exit reported ret=unhandled and /proc/irq/11/spurious increased from 0 to 200 unhandled interrupts. After this change, irq_handler_exit reported ret=handled and the unhandled count remained at 0. The issue was found during an LLM-assisted Quality Playbook review. Fixes: 77cf524654a8 ("virtio_pci: split up vp_interrupt") Suggested-by: Michael S. Tsirkin Assisted-by: LLM Signed-off-by: Andrew Stellman Message-ID: <20260904141318.30278-1-astellman@stellman-greene.com> Signed-off-by: Michael S. Tsirkin --- drivers/virtio/virtio_pci_common.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c index 10371ecbc054..b90c174450b2 100644 --- a/drivers/virtio/virtio_pci_common.c +++ b/drivers/virtio/virtio_pci_common.c @@ -120,7 +120,9 @@ static irqreturn_t vp_interrupt(int irq, void *opaque) if (isr & VIRTIO_PCI_ISR_CONFIG) vp_config_changed(irq, opaque); - return vp_vring_interrupt(irq, opaque); + vp_vring_interrupt(irq, opaque); + + return IRQ_HANDLED; } static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors, From c952e607cb4aa3640e5ae07243d3f609dac94424 Mon Sep 17 00:00:00 2001 From: Dongli Zhang Date: Sun, 2 Aug 2026 10:24:55 -0700 Subject: [PATCH 0491/1317] vhost-scsi: use kvzalloc for vq array allocation vhost_scsi_open() allocates one "struct vhost_scsi_virtqueue" for each virtqueue. With large max_io_vqs values, this array can require a high-order contiguous allocation and trigger a page allocator warning. hv# cat /sys/module/vhost_scsi/parameters/max_io_vqs 256 [ 766.075787] ------------[ cut here ]------------ [ 766.077030] WARNING: mm/page_alloc.c:5280 at __alloc_frozen_pages_noprof+0x32c/0x15c0, CPU#23: qemu-system-x86/5964 ... ... [ 766.080351] RIP: 0010:__alloc_frozen_pages_noprof+0x32c/0x15c0 ... ... [ 766.085813] Call Trace: [ 766.085969] [ 766.086098] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.086365] ? context_struct_compute_av+0x38a/0x4b0 [ 766.086652] alloc_pages_mpol+0x9f/0x170 [ 766.086883] ___kmalloc_large_node+0xb6/0xd0 [ 766.087124] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.087389] __kmalloc_large_node_noprof+0x18/0xa0 [ 766.087655] __kmalloc_noprof+0x3a0/0x440 [ 766.087877] ? vhost_scsi_open+0xcb/0x2d0 [vhost_scsi] [ 766.088162] vhost_scsi_open+0xcb/0x2d0 [vhost_scsi] [ 766.088449] misc_open+0x123/0x160 [ 766.088679] chrdev_open+0xb1/0x230 [ 766.088885] ? __pfx_chrdev_open+0x10/0x10 [ 766.089157] do_dentry_open+0x11a/0x470 [ 766.089389] vfs_open+0x29/0xf0 [ 766.089596] path_openat+0x7c0/0x1100 [ 766.089821] do_file_open+0xdd/0x190 [ 766.090032] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.090332] do_sys_openat2+0x7e/0x100 [ 766.090601] __x64_sys_openat+0x51/0xa0 [ 766.090857] do_syscall_64+0xfe/0x590 [ 766.091087] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 766.091411] RIP: 0033:0x7f9525a11fa6 The array does not require physical contiguity, so allocate it with kvzalloc_objs() and free it with kvfree(). Signed-off-by: Dongli Zhang Reviewed-by: Mike Christie Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260802172534.260047-2-dongli.zhang@oracle.com> --- drivers/vhost/scsi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 7a1f39a327da..223549313ce9 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2312,7 +2312,7 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) if (!vs->old_inflight) goto err_inflight; - vs->vqs = kmalloc_objs(*vs->vqs, nvqs, GFP_KERNEL | __GFP_ZERO); + vs->vqs = kvzalloc_objs(*vs->vqs, nvqs); if (!vs->vqs) goto err_vqs; @@ -2348,7 +2348,7 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) return 0; err_local_vqs: - kfree(vs->vqs); + kvfree(vs->vqs); err_vqs: kfree(vs->old_inflight); err_inflight: @@ -2369,7 +2369,7 @@ static int vhost_scsi_release(struct inode *inode, struct file *f) vhost_dev_stop(&vs->dev); vhost_dev_cleanup(&vs->dev); kfree(vs->dev.vqs); - kfree(vs->vqs); + kvfree(vs->vqs); kfree(vs->old_inflight); kvfree(vs); return 0; From 4e3ec5b1b427e02082e8b3491731f8c3bcf85c53 Mon Sep 17 00:00:00 2001 From: Dongli Zhang Date: Sun, 2 Aug 2026 10:24:56 -0700 Subject: [PATCH 0492/1317] vhost-scsi: clamp max_io_vqs module parameter max_io_vqs is currently validated only when a vhost-scsi device is opened. This allows sysfs to show values larger than the driver will actually use, e.g. writing 2048 succeeds even though vhost_scsi_open() later clamps it to VHOST_SCSI_MAX_IO_VQ. This makes the sysfs value differ from the value that will actually be used. hv# echo 2048 > /sys/module/vhost_scsi/parameters/max_io_vqs hv# cat /sys/module/vhost_scsi/parameters/max_io_vqs 2048 [ 315.630495] Invalid max_io_vqs of 2048. Using 1024. Keep accepting out-of-range values for compatibility, but clamp them in the module parameter setter and store the effective value. This preserves the existing behavior that invalid values do not make module loading or sysfs writes fail. It also makes reads report the value that will actually be used. With the parameter value kept in range, remove the duplicate validation from vhost_scsi_open(). Signed-off-by: Dongli Zhang Reviewed-by: Mike Christie Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260802172534.260047-3-dongli.zhang@oracle.com> --- drivers/vhost/scsi.c | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 223549313ce9..4f8c0260bc9e 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -210,7 +210,37 @@ static const int vhost_scsi_bits[] = { #define VHOST_SCSI_MAX_EVENT 128 static unsigned vhost_scsi_max_io_vqs = 128; -module_param_named(max_io_vqs, vhost_scsi_max_io_vqs, uint, 0644); + +static int vhost_scsi_set_max_io_vqs(const char *val, + const struct kernel_param *kp) +{ + unsigned int max_io_vqs; + int ret; + + ret = kstrtouint(val, 0, &max_io_vqs); + if (ret) + return ret; + + if (max_io_vqs > VHOST_SCSI_MAX_IO_VQ) { + pr_err("Invalid max_io_vqs of %u. Using %u.\n", + max_io_vqs, VHOST_SCSI_MAX_IO_VQ); + max_io_vqs = VHOST_SCSI_MAX_IO_VQ; + } else if (!max_io_vqs) { + pr_err("Invalid max_io_vqs of 0. Using 1.\n"); + max_io_vqs = 1; + } + + WRITE_ONCE(vhost_scsi_max_io_vqs, max_io_vqs); + return 0; +} + +static const struct kernel_param_ops vhost_scsi_max_io_vqs_op = { + .set = vhost_scsi_set_max_io_vqs, + .get = param_get_uint, +}; + +module_param_cb(max_io_vqs, &vhost_scsi_max_io_vqs_op, + &vhost_scsi_max_io_vqs, 0644); MODULE_PARM_DESC(max_io_vqs, "Set the max number of IO virtqueues a vhost scsi device can support. The default is 128. The max is 1024."); struct vhost_scsi_virtqueue { @@ -2290,21 +2320,14 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) struct vhost_scsi_virtqueue *svq; struct vhost_scsi *vs; struct vhost_virtqueue **vqs; - int r = -ENOMEM, i, nvqs = vhost_scsi_max_io_vqs; + int r = -ENOMEM, i, nvqs; vs = kvzalloc_obj(*vs); if (!vs) goto err_vs; vs->inline_sg_cnt = vhost_scsi_inline_sg_cnt; - if (nvqs > VHOST_SCSI_MAX_IO_VQ) { - pr_err("Invalid max_io_vqs of %d. Using %d.\n", nvqs, - VHOST_SCSI_MAX_IO_VQ); - nvqs = VHOST_SCSI_MAX_IO_VQ; - } else if (nvqs == 0) { - pr_err("Invalid max_io_vqs of %d. Using 1.\n", nvqs); - nvqs = 1; - } + nvqs = READ_ONCE(vhost_scsi_max_io_vqs); nvqs += VHOST_SCSI_VQ_IO; vs->old_inflight = kmalloc_objs(*vs->old_inflight, nvqs, From 7474f3a61043934e9c351febc56f4d85cd5ddc96 Mon Sep 17 00:00:00 2001 From: Nikhil Date: Sun, 30 Aug 2026 04:24:57 +0530 Subject: [PATCH 0493/1317] vduse: do not take dev->rwsem in the virtqueue kick path vduse_vq_kick() runs in the context of the vdpa .kick_vq callback. With the virtio_vdpa bus driver that callback is invoked by virtqueue_notify() from the virtio device driver, which may be an atomic context: virtio-blk kicks from ->queue_rq(), which blk-mq dispatches under rcu_read_lock() (the tag set does not use BLK_MQ_F_BLOCKING), and virtio-net kicks from its xmit path with the tx queue lock held. Commit b282418bc366 ("vduse: Add suspend") made vduse_vq_kick() take dev->rwsem for reading in order to check dev->suspended. down_read() may sleep, so with CONFIG_DEBUG_ATOMIC_SLEEP the first I/O on a VDUSE-backed virtio-blk device bound to virtio_vdpa now triggers: BUG: sleeping function called from invalid context at kernel/locking/rwsem.c:1573 in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid: 27, name: kworker/1:0H preempt_count: 0, expected: 0 RCU nest depth: 1, expected: 0 3 locks held by kworker/1:0H/27: #0: ((wq_completion)kblockd){+.+.}-{0:0}, at: process_one_work+0xac7/0xcf0 #1: ((work_completion)(&(&hctx->run_work)->work)){+.+.}-{0:0}, at: process_one_work+0x51f/0xcf0 #2: (rcu_read_lock){....}-{1:3}, at: blk_mq_run_work_fn+0x119/0x220 Workqueue: kblockd blk_mq_run_work_fn Call Trace: dump_stack_lvl+0x80/0xa0 __might_resched+0x231/0x370 down_read+0x73/0x330 vduse_vq_kick+0x30/0x120 virtio_vdpa_notify+0x63/0x80 virtqueue_notify+0x45/0x70 virtio_queue_rq+0x19d/0x300 blk_mq_dispatch_rq_list+0x269/0xe20 __blk_mq_sched_dispatch_requests+0x761/0xa60 blk_mq_sched_dispatch_requests+0x6b/0xc0 blk_mq_run_work_fn+0x143/0x220 process_one_work+0x581/0xcf0 worker_thread+0x2fc/0x5a0 kthread+0x1cc/0x210 ret_from_fork+0x3c4/0x540 ret_from_fork_asm+0x1a/0x30 Without CONFIG_DEBUG_ATOMIC_SLEEP, a kick that finds the rwsem write-locked by vduse_dev_reset() or vduse_vdpa_suspend() blocks inside an RCU read-side critical section. The vhost_vdpa path kicks from the vhost worker, i.e. process context, which is why this went unnoticed. Check dev->suspended under vq->kick_lock instead, which the kick path already takes, and have vduse_vdpa_suspend() cycle every virtqueue's kick_lock after setting the flag. A kick that observed suspended == false has thus finished signalling before suspend returns, which is the guarantee the rwsem used to provide. The flag is now also read outside the rwsem, so access it with READ_ONCE()/WRITE_ONCE(). Fixes: b282418bc366 ("vduse: Add suspend") Signed-off-by: Nikhil Signed-off-by: Michael S. Tsirkin Message-ID: <20260829225457.1037867-1-nikhilljatt@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 9891cd2cf712..766789a7bbfa 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -506,7 +506,7 @@ static void vduse_dev_reset(struct vduse_dev *dev) } scoped_guard(rwsem_write, &dev->rwsem) { - dev->suspended = false; + WRITE_ONCE(dev->suspended, false); dev->status = 0; dev->driver_features = 0; dev->generation++; @@ -567,11 +567,17 @@ static int vduse_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 idx, static void vduse_vq_kick(struct vduse_virtqueue *vq) { - guard(rwsem_read)(&vq->dev->rwsem); - if (vq->dev->suspended) + /* + * This runs in the context of the vdpa kick_vq op, which may be + * atomic (e.g. virtio-blk kicks from blk-mq dispatch under + * rcu_read_lock()), so dev->rwsem must not be taken here. + * dev->suspended is checked under kick_lock instead and + * vduse_vdpa_suspend() cycles every kick_lock after setting it. + */ + guard(spinlock)(&vq->kick_lock); + if (READ_ONCE(vq->dev->suspended)) return; - guard(spinlock)(&vq->kick_lock); scoped_guard(spinlock_bh, &vq->ready_lock) if (!vq->ready) return; @@ -946,7 +952,17 @@ static int vduse_vdpa_suspend(struct vdpa_device *vdpa) ret = vduse_dev_msg_sync(dev, &msg); if (ret == 0) { scoped_guard(rwsem_write, &dev->rwsem) - dev->suspended = true; + WRITE_ONCE(dev->suspended, true); + + /* + * Kicks check dev->suspended under kick_lock without taking + * the rwsem: cycle each kick_lock so that no kick that has + * already passed the check is still in flight after this. + */ + for (u32 i = 0; i < dev->vq_num; i++) { + spin_lock(&dev->vqs[i]->kick_lock); + spin_unlock(&dev->vqs[i]->kick_lock); + } cancel_work_sync(&dev->inject); for (u32 i = 0; i < dev->vq_num; i++) From fa2c25b4add57888acfa89e398389e267bff3dcf Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Sun, 30 Aug 2026 10:33:54 +0800 Subject: [PATCH 0494/1317] vduse: validate virtqueue alignment vduse_validate_config() only checks the upper bound of vq_align. Invalid values can therefore reach vring_create_virtqueue_map(). The split-ring helpers use align - 1 as a bit mask, so the alignment must be a non-zero power of two. A zero value makes vring_size() drop the descriptor and available-ring part and vring_init() leave the used ring pointer NULL. The VIRTIO spec requires the used ring to start at an address aligned to at least 4 bytes. Reject values below VRING_USED_ALIGN_SIZE as well as non-power-of-two values before they reach the virtio ring helpers. Opening a virtio-net device created with vq_align=0 triggered: BUG: KASAN: null-ptr-deref in virtqueue_kick_prepare_split+0xe3/0x100 Read of size 2 at addr 0000000000000000 by task systemd-network/1062 Call Trace (relevant frames): dump_stack_lvl print_report kasan_report __asan_load2 virtqueue_kick_prepare_split+0xe3/0x100 virtqueue_kick_prepare+0x40/0x60 try_fill_recv+0x857/0x1250 virtnet_open+0x189/0x460 __dev_open+0x225/0x390 __dev_change_flags+0x368/0x3b0 netif_change_flags+0x56/0xc0 do_setlink.isra.0+0x68c/0x1e30 Validate the value before it reaches the virtio ring helpers. Fixes: c8a6153b6c59 ("vduse: Introduce VDUSE - vDPA Device in Userspace") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260830023354.115333-1-physicalmtea@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 766789a7bbfa..4dea4d6a3855 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -2227,7 +2227,9 @@ static bool vduse_validate_config(struct vduse_dev_config *config, return false; } - if (config->vq_align > PAGE_SIZE) + if (config->vq_align < VRING_USED_ALIGN_SIZE || + !is_power_of_2(config->vq_align) || + config->vq_align > PAGE_SIZE) return false; if (config->config_size > PAGE_SIZE) From e4f4761879a230aa59e569102a6ab9851847d833 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Fri, 28 Aug 2026 16:57:21 +0800 Subject: [PATCH 0495/1317] vhost: invalidate vring access on IOTLB transitions When VIRTIO_F_ACCESS_PLATFORM changes, cached vring pointers and IOTLB metadata are interpreted in a different address space. Keeping them across the transition can leave stale ring mappings in use. Clearing d->iotlb before taking the VQ locks also lets a worker observe a transient NULL d->iotlb and fall back to d->umem while translating a descriptor. Add a common vhost_clear_device_iotlb() helper for vhost-net and vhost-vsock. Take all VQ mutexes in index order before dropping the device-wide IOTLB, invalidate each VQ's cached ring access and metadata, clear pending IOTLB messages, and free the old table after the handoff. This serializes the transition with workers and prevents mixed address space mappings. On the first direct-to-IOTLB transition, invalidate the cached vring addresses. When an existing device IOTLB is replaced, preserve the GIOVA ring addresses and reset only the metadata cache. After clearing ACCESS_PLATFORM, userspace must configure the vring addresses for the new address mode. vhost_vq_invalidate_access() clears desc, avail, and used together. Treat the VQ as invalidated only when all three are NULL, since a single GIOVA address may legitimately be zero. Fixes: 6b1e6cc7855b ("vhost: new device IOTLB API") Fixes: e13a6915a03f ("vhost/vsock: add IOTLB API support") Suggested-by: Michael S. Tsirkin Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260828085721.57816-1-physicalmtea@gmail.com> --- drivers/vhost/net.c | 2 ++ drivers/vhost/vhost.c | 57 ++++++++++++++++++++++++++++++++++++++++++- drivers/vhost/vhost.h | 1 + drivers/vhost/vsock.c | 2 ++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index c25929dd4425..2cc730729e08 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1705,6 +1705,8 @@ static int vhost_net_set_features(struct vhost_net *n, const u64 *features) if (virtio_features_test_bit(features, VIRTIO_F_ACCESS_PLATFORM)) { if (vhost_init_device_iotlb(&n->dev)) goto out_unlock; + } else { + vhost_clear_device_iotlb(&n->dev); } for (i = 0; i < VHOST_NET_VQ_MAX; ++i) { diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 02588b64b1bb..44cac11b68d2 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -344,6 +344,17 @@ static void __vhost_vq_meta_reset(struct vhost_virtqueue *vq) vq->meta_iotlb[j] = NULL; } +/* Caller must hold the virtqueue mutex. */ +static void vhost_vq_invalidate_access(struct vhost_virtqueue *vq) +{ + vq->desc = NULL; + vq->avail = NULL; + vq->used = NULL; + vq->log_used = false; + vq->log_addr = -1ull; + __vhost_vq_meta_reset(vq); +} + static void vhost_vq_meta_reset(struct vhost_dev *d) { int i; @@ -1946,6 +1957,13 @@ int vq_meta_prefetch(struct vhost_virtqueue *vq) { unsigned int num = vq->num; + /* + * vhost_vq_invalidate_access() clears all three addresses together. + * A single zero address may be a valid GIOVA in IOTLB mode. + */ + if (!vq->desc && !vq->avail && !vq->used) + return 0; + if (!vq->iotlb) return 1; @@ -2315,6 +2333,40 @@ long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *arg } EXPORT_SYMBOL_GPL(vhost_vring_ioctl); +/* Caller must hold the device mutex. */ +void vhost_clear_device_iotlb(struct vhost_dev *d) +{ + struct vhost_iotlb *iotlb; + int i; + + iotlb = d->iotlb; + if (!iotlb) + return; + + vhost_dev_lock_vqs(d); + + /* + * vhost_dev_lock_vqs() takes all VQ mutexes in index order. Drop the + * device-wide view while they are held, then clear each per-VQ view + * and its cached ring access before releasing the locks. Workers + * cannot observe a mixed address-space state during this handoff. + */ + d->iotlb = NULL; + + for (i = 0; i < d->nvqs; ++i) { + struct vhost_virtqueue *vq = d->vqs[i]; + + vq->iotlb = NULL; + vhost_vq_invalidate_access(vq); + } + + vhost_dev_unlock_vqs(d); + vhost_clear_msg(d); + vhost_iotlb_free(iotlb); + wake_up_interruptible_poll(&d->wait, EPOLLIN | EPOLLRDNORM); +} +EXPORT_SYMBOL_GPL(vhost_clear_device_iotlb); + int vhost_init_device_iotlb(struct vhost_dev *d) { struct vhost_iotlb *niotlb, *oiotlb; @@ -2335,7 +2387,10 @@ int vhost_init_device_iotlb(struct vhost_dev *d) mutex_lock(&vq->mutex); vq->iotlb = niotlb; - __vhost_vq_meta_reset(vq); + if (oiotlb) + __vhost_vq_meta_reset(vq); + else + vhost_vq_invalidate_access(vq); mutex_unlock(&vq->mutex); } diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index fa76b7d44662..39e6121f7525 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -280,6 +280,7 @@ ssize_t vhost_chr_read_iter(struct vhost_dev *dev, struct iov_iter *to, int noblock); ssize_t vhost_chr_write_iter(struct vhost_dev *dev, struct iov_iter *from); +void vhost_clear_device_iotlb(struct vhost_dev *d); int vhost_init_device_iotlb(struct vhost_dev *d); void vhost_iotlb_map_free(struct vhost_iotlb *iotlb, diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 9aaab6bb8061..abed1fbcf66c 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -868,6 +868,8 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features) if ((features & (1ULL << VIRTIO_F_ACCESS_PLATFORM))) { if (vhost_init_device_iotlb(&vsock->dev)) goto err; + } else { + vhost_clear_device_iotlb(&vsock->dev); } vsock->seqpacket_allow = features & (1ULL << VIRTIO_VSOCK_F_SEQPACKET); From 81489b32a21c9360f8750d1fb600155d27452e19 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 11:29:31 +0800 Subject: [PATCH 0496/1317] virtio_input: reset device if input_register_device() fails Probe marks the device DRIVER_OK with virtio_device_ready() before calling input_register_device(). If registration fails, the error path cleared vi->ready and called del_vqs() while the device was still live, so the device could keep DMA to queues that were already torn down. Match remove/freeze: call virtio_reset_device() on that path before tearing down the virtqueues. Fixes: 271c865161c5 ("Add virtio-input driver.") Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260805032931.1606652-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_input.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/virtio/virtio_input.c b/drivers/virtio/virtio_input.c index deec24e8e682..1a87be4c88cf 100644 --- a/drivers/virtio/virtio_input.c +++ b/drivers/virtio/virtio_input.c @@ -331,6 +331,7 @@ static int virtinput_probe(struct virtio_device *vdev) spin_lock_irqsave(&vi->lock, flags); vi->ready = false; spin_unlock_irqrestore(&vi->lock, flags); + virtio_reset_device(vdev); err_mt_init_slots: input_free_device(vi->idev); err_input_alloc: From d7808b37da0a619cf1fa541c2384e783fecc2480 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 17:20:58 +0200 Subject: [PATCH 0497/1317] virtio_input: stop callbacks before unregistering input device virtinput_remove() unregisters the input device before resetting the virtio device. virtinput_recv_events() drops vi->lock around input_event(), so clearing vi->ready does not stop a callback that passed the entry check. It can still use vi->idev, requeue buffers and kick the queue. Reset first, as virtinput_freeze() already does. With the preceding core change, reset waits for callbacks before input_unregister_device() can free vi->idev. Recheck vi->ready after taking the lock again: keep draining completed events so an input packet is not truncated, but stop requeueing buffers and kicking the queue. With evdev attached, input_unregister_handle() currently waits for an RCU grace period, which also waits out IRQ callbacks. This masks the lifetime bug on PCI and MMIO, but does not protect sleepable callbacks on other transports. Fixes: 271c865161c5 ("Add virtio-input driver.") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260905152059.89560-3-kmehltretter@gmail.com> --- drivers/virtio/virtio_input.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_input.c b/drivers/virtio/virtio_input.c index 1a87be4c88cf..e3bd0b9616f9 100644 --- a/drivers/virtio/virtio_input.c +++ b/drivers/virtio/virtio_input.c @@ -49,9 +49,12 @@ static void virtinput_recv_events(struct virtqueue *vq) le16_to_cpu(event->code), le32_to_cpu(event->value)); spin_lock_irqsave(&vi->lock, flags); + if (!vi->ready) + continue; virtinput_queue_evtbuf(vi, event); } - virtqueue_kick(vq); + if (vi->ready) + virtqueue_kick(vq); } spin_unlock_irqrestore(&vi->lock, flags); } @@ -351,8 +354,9 @@ static void virtinput_remove(struct virtio_device *vdev) vi->ready = false; spin_unlock_irqrestore(&vi->lock, flags); - input_unregister_device(vi->idev); + /* Callbacks use vi->idev. */ virtio_reset_device(vdev); + input_unregister_device(vi->idev); while ((buf = virtqueue_detach_unused_buf(vi->sts)) != NULL) kfree(buf); vdev->config->del_vqs(vdev); From 883e78c9e6007c91be96e99a36c35baf41bc8ed5 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 7 Sep 2026 10:36:45 +0100 Subject: [PATCH 0498/1317] ASoC: cs35l56: Fix race between kexec and snd_soc_register_component() Use a reboot notifier and a mutex to prevent snd_soc_register_component() from racing with a kexec reboot. This prevents snd_soc_register_component() from manipulating device lists while device_shutdown() is walking them. Commit 1d80a4792f1de ("ASoC: cs35l56: Fix probe deadlock waiting for SoundWire enumeration") moved snd_soc_register_component() out of probe() into a workqueue item. See the description in that commit for a detailed explanation. That change introduces a race between snd_soc_register_component() and kexec. The reboot notifier and mutex prevent the shutdown race. There is one remaining race with KEXEC_JUMP because it does not invoke reboot notifiers or freeze freezable workqueues. But KEXEC_JUMP is rarely used and is supported on only two architectures (x86 and SuperH). It does not appear to be enabled by default in any distro. It is also unlikely there will be a KEXEC_JUMP before snd_soc_register_component() has had the opportunity to execute. Fixing this can be deferred to a future patch. Fixes: 1d80a4792f1de ("ASoC: cs35l56: Fix probe deadlock waiting for SoundWire enumeration") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260907093645.27407-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56.c | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 890429ab0dfb..35d210626627 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -18,9 +18,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -37,6 +39,13 @@ #include "wm_adsp.h" #include "cs35l56.h" +/* + * snd_soc_register_component() can call component_probe() on all instances + * in a card, so deferred registration must be protected across all instances. + */ +static DEFINE_MUTEX(cs35l56_component_register_lock); +static bool cs35l56_shutting_down; + void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) { /* @@ -1957,6 +1966,11 @@ static void cs35l56_component_register_work(struct work_struct *work) component_register_work); int ret; + guard(mutex)(&cs35l56_component_register_lock); + + if (cs35l56_shutting_down) + return; + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); if (ret) { @@ -2217,6 +2231,37 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { }; #endif +static int cs35l56_reboot_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + guard(mutex)(&cs35l56_component_register_lock); + cs35l56_shutting_down = true; + + return NOTIFY_DONE; +} + +static struct notifier_block cs35l56_reboot_notifier = { + .notifier_call = cs35l56_reboot_notify, +}; + +static int __init cs35l56_modinit(void) +{ + /* + * Use reboot notifier to prevent race between shutdown and + * snd_soc_register_component(). Driver shutdown() callback would + * run too late, after device_shutdown() is already walking the + * device list that component registration can modify. + */ + return register_reboot_notifier(&cs35l56_reboot_notifier); +} +module_init(cs35l56_modinit); + +static void __exit cs35l56_modexit(void) +{ + unregister_reboot_notifier(&cs35l56_reboot_notifier); +} +module_exit(cs35l56_modexit); + MODULE_DESCRIPTION("ASoC CS35L56 driver"); MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); From 74f27fc8642b7e8d139796f8c18ee46df393c2b2 Mon Sep 17 00:00:00 2001 From: Nagamani PV Date: Tue, 1 Sep 2026 17:53:44 +0200 Subject: [PATCH 0499/1317] s390/qeth: allow bridgeport queries despite OS_MISMATCH When HiperSockets interfaces on the same VCHID span different OS families, reads of the sysfs attributes bridge_role and bridge_state fail with -EPERM if bridge port ownership belongs to another OS family. As a result, userspace tools such as 'lszdev -ii' cannot retrieve bridge_role and bridge_state, even though firmware returns valid bridge port data for QUERY_BRIDGE_PORTS requests. The firmware reports IPA_RC_SBP_IQD_OS_MISMATCH (0x0010) to indicate that bridge port ownership belongs to a different OS family. For QUERY_BRIDGE_PORTS operations, firmware still returns valid bridge port data (role=none, state=inactive) together with a primary return code of 0x0000 (success). Allow QUERY_BRIDGE_PORTS requests to return the bridge port data provided by the firmware despite OS_MISMATCH. To make the OS family mismatch visible to userspace, represent the firmware-reported role "none" as "none (OS family mismatch)" while preserving the reported bridge_state. The behavior for non-QUERY bridge port commands is unchanged; SET operations continue to return -EPERM when another OS family owns the bridge port. This restores readability of bridge_role and bridge_state. Fixes: 1b05cf6285c1 ("qeth: Include error message for "OS Mismatch"") Cc: stable@vger.kernel.org Suggested-by: Halil Pasic Reviewed-by: Alexandra Winter Signed-off-by: Nagamani PV Link: https://patch.msgid.link/20260901155344.3561483-1-nagamani@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/qeth_l2.h | 3 ++- drivers/s390/net/qeth_l2_main.c | 26 ++++++++++++++++++++++---- drivers/s390/net/qeth_l2_sys.c | 7 ++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/drivers/s390/net/qeth_l2.h b/drivers/s390/net/qeth_l2.h index 7c646e2fed7e..f94975e970ca 100644 --- a/drivers/s390/net/qeth_l2.h +++ b/drivers/s390/net/qeth_l2.h @@ -13,7 +13,8 @@ extern const struct attribute_group *qeth_l2_attr_groups[]; int qeth_bridgeport_query_ports(struct qeth_card *card, enum qeth_sbp_roles *role, - enum qeth_sbp_states *state); + enum qeth_sbp_states *state, + bool *os_mismatch); int qeth_bridgeport_setrole(struct qeth_card *card, enum qeth_sbp_roles role); int qeth_bridgeport_an_set(struct qeth_card *card, int enable); diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index a9e7d1d637a2..2935c2ecc314 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -1158,7 +1158,7 @@ static void qeth_l2_setup_bridgeport_attrs(struct qeth_card *card) qeth_bridgeport_setrole(card, card->options.sbp.role); /* Let the callback function refresh the stored role value. */ qeth_bridgeport_query_ports(card, &card->options.sbp.role, - NULL); + NULL, NULL); } if (card->options.sbp.hostnotification) { if (qeth_bridgeport_an_set(card, 1)) @@ -1545,6 +1545,7 @@ struct _qeth_sbp_cbctl { struct { enum qeth_sbp_roles *role; enum qeth_sbp_states *state; + bool *os_mismatch; } qports; } data; }; @@ -1721,10 +1722,19 @@ static int qeth_bridgeport_query_ports_cb(struct qeth_card *card, struct qeth_ipa_cmd *cmd = (struct qeth_ipa_cmd *) data; struct _qeth_sbp_cbctl *cbctl = (struct _qeth_sbp_cbctl *)reply->param; struct qeth_sbp_port_data *qports; + u16 sbp_rc; int rc; QETH_CARD_TEXT(card, 2, "brqprtcb"); - rc = qeth_bridgeport_makerc(card, cmd); + sbp_rc = cmd->data.sbp.hdr.return_code; + + /* on OS family mismatch, query still returns valid port data; + * treat as success + */ + if (sbp_rc == IPA_RC_SBP_IQD_OS_MISMATCH && !cmd->hdr.return_code) + rc = 0; + else + rc = qeth_bridgeport_makerc(card, cmd); if (rc) return rc; @@ -1740,6 +1750,9 @@ static int qeth_bridgeport_query_ports_cb(struct qeth_card *card, if (cbctl->data.qports.state) *cbctl->data.qports.state = qports->entry[0].state; } + if (cbctl->data.qports.os_mismatch) + *cbctl->data.qports.os_mismatch = + (sbp_rc == IPA_RC_SBP_IQD_OS_MISMATCH); return 0; } @@ -1748,13 +1761,17 @@ static int qeth_bridgeport_query_ports_cb(struct qeth_card *card, * @card: qeth_card structure pointer. * @role: Role of the port: 0-none, 1-primary, 2-secondary. * @state: State of the port: 0-inactive, 1-standby, 2-active. + * @os_mismatch: if non-NULL, set to true when firmware reports + * OS family mismatch. * * Returns negative errno-compatible error indication or 0 on success. * - * 'role' and 'state' are not updated in case of hardware operation failure. + * 'role', 'state' and 'os_mismatch' are not updated in case of + * hardware operation failure. */ int qeth_bridgeport_query_ports(struct qeth_card *card, - enum qeth_sbp_roles *role, enum qeth_sbp_states *state) + enum qeth_sbp_roles *role, enum qeth_sbp_states *state, + bool *os_mismatch) { struct qeth_cmd_buffer *iob; struct _qeth_sbp_cbctl cbctl = { @@ -1762,6 +1779,7 @@ int qeth_bridgeport_query_ports(struct qeth_card *card, .qports = { .role = role, .state = state, + .os_mismatch = os_mismatch, }, }, }; diff --git a/drivers/s390/net/qeth_l2_sys.c b/drivers/s390/net/qeth_l2_sys.c index 7f592f912517..7101be62eb1d 100644 --- a/drivers/s390/net/qeth_l2_sys.c +++ b/drivers/s390/net/qeth_l2_sys.c @@ -15,6 +15,7 @@ static ssize_t qeth_bridge_port_role_state_show(struct device *dev, { struct qeth_card *card = dev_get_drvdata(dev); enum qeth_sbp_states state = QETH_SBP_STATE_INACTIVE; + bool os_mismatch = false; int rc = 0; char *word; @@ -25,7 +26,7 @@ static ssize_t qeth_bridge_port_role_state_show(struct device *dev, if (qeth_card_hw_is_reachable(card) && card->options.sbp.supported_funcs) rc = qeth_bridgeport_query_ports(card, - &card->options.sbp.role, &state); + &card->options.sbp.role, &state, &os_mismatch); if (!rc) { if (show_state) switch (state) { @@ -52,6 +53,10 @@ static ssize_t qeth_bridge_port_role_state_show(struct device *dev, if (rc) QETH_CARD_TEXT_(card, 2, "SBP%02x:%02x", card->options.sbp.role, state); + else if (!show_state && + card->options.sbp.role == QETH_SBP_ROLE_NONE && + os_mismatch) + rc = sysfs_emit(buf, "%s (OS family mismatch)\n", word); else rc = sysfs_emit(buf, "%s\n", word); } From 94fd4debd2e3a69cf93e766c8b328a810c228119 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 2 Sep 2026 20:21:50 +0000 Subject: [PATCH 0500/1317] af_unix: Update last skb marker in manage_oob(). Fahad Alharbi reported that blocking recv(MSG_PEEK) could hog CPU due to OOB skb. In the following cases, manage_oob() skips OOB skb(s) and returns NULL for the last recv(MSG_PEEK): socketpair(AF_UNIX, SOCK_STREAM, 0, sk); 1) skb -> OOB skb -> NULL send(sk[0], "ab", 2, MSG_OOB); recv(sk[1], buf, 0, MSG_PEEK); 2) skb -> consumed OOB skb -> NULL send(sk[0], "ab", 2, MSG_OOB); recv(sk[1], buf, 1, MSG_OOB); recv(sk[1], buf, 0, MSG_PEEK); 3) consumed OOB skb -> OOB skb -> NULL send(sk[0], "a", 1, MSG_OOB); recv(sk[1], buf, 0, MSG_OOB); send(sk[0], "b", 1, MSG_OOB); recv(sk[1], buf, 1, MSG_PEEK); Then, @copied is 0 in unix_stream_read_generic() (zero-length buffer, or non-OOB skb is not yet consumed), and unix_stream_data_wait() is called. However, it returns immediately because @last is not updated in unix_stream_read_generic(), and the thread busy-waits for a new skb. Let's update @last in manage_oob(). For MSG_PEEK, @last is updated with the skipped OOB, and for the non-peek case, @last matches the returned value (when !copied) because OOB is unlinked. Note that manage_oob() is inlined and no stack canary is added. Fixes: 22dd70eb2c3d ("af_unix: Don't peek OOB data without MSG_OOB.") Reported-by: Fahad Alharbi Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260902202202.892676-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/unix/af_unix.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 13f9926bf205..6861370062df 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -2812,8 +2812,8 @@ static int unix_stream_recv_urg(struct unix_stream_read_state *state) return 1; } -static struct sk_buff *manage_oob(struct sk_buff *skb, struct sock *sk, - int flags, int copied) +static struct sk_buff *manage_oob(struct sk_buff *skb, struct sk_buff **last, + struct sock *sk, int flags, int copied) { struct sk_buff *read_skb = NULL, *unread_skb = NULL; struct unix_sock *u = unix_sk(sk); @@ -2827,11 +2827,13 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sock *sk, if (copied && (!u->oob_skb || skb == u->oob_skb)) { skb = NULL; } else if (flags & MSG_PEEK) { + *last = skb; skb = skb_peek_next(skb, &sk->sk_receive_queue); } else { read_skb = skb; skb = skb_peek_next(skb, &sk->sk_receive_queue); __skb_unlink(read_skb, &sk->sk_receive_queue); + *last = skb; } if (!skb) @@ -2850,8 +2852,10 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sock *sk, __skb_unlink(skb, &sk->sk_receive_queue); unread_skb = skb; skb = skb_peek(&sk->sk_receive_queue); + *last = skb; } } else if (!sock_flag(sk, SOCK_URGINLINE)) { + *last = skb; skb = skb_peek_next(skb, &sk->sk_receive_queue); } @@ -2971,7 +2975,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, again: #if IS_ENABLED(CONFIG_AF_UNIX_OOB) if (skb) { - skb = manage_oob(skb, sk, flags, copied); + skb = manage_oob(skb, &last, sk, flags, copied); if (!skb && copied) { unix_state_unlock(sk); break; From 6e5ee08eb5858d175da6768d75d163817b6a9d4a Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 2 Sep 2026 20:21:51 +0000 Subject: [PATCH 0501/1317] af_unix: Return immediately when manage_oob() returns NULL for 0-length buffer. Fahad Alharbi reported that recv(0, MSG_PEEK) triggers busy-wait in unix_stream_read_generic() if recv() is blocking and the last skb in the queue is MSG_OOB skb. In such a situation, TCP returns 0 immediately regardless of blocking or non-blocking. Let's follow the behaviour. Fixes: 314001f0bf92 ("af_unix: Add OOB support") Reported-by: Fahad Alharbi Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260902202202.892676-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/unix/af_unix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 6861370062df..2da1017f8873 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -2976,7 +2976,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, #if IS_ENABLED(CONFIG_AF_UNIX_OOB) if (skb) { skb = manage_oob(skb, &last, sk, flags, copied); - if (!skb && copied) { + if (!skb && (copied || !state->size)) { unix_state_unlock(sk); break; } From ca0b0a86873e8ded39b7fb196dbdc615d9a9a0e4 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 2 Sep 2026 20:21:52 +0000 Subject: [PATCH 0502/1317] selftest: af_unix: Add zero-buffer test for msg_oob.c The previous patches fixed two issues related to zero-length buffer with MSG_PEEK for MSG_OOB skb. Let's add corresponding tests in msg_oob.c. Without this series: # FAILED: 50 / 60 tests passed. # Totals: pass:50 fail:10 xfail:0 xpass:0 skip:0 error:0 With this series: # PASSED: 60 / 60 tests passed. # Totals: pass:60 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260902202202.892676-4-kuniyu@google.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/af_unix/msg_oob.c | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tools/testing/selftests/net/af_unix/msg_oob.c b/tools/testing/selftests/net/af_unix/msg_oob.c index 1b499d56656c..f051d79f7a8e 100644 --- a/tools/testing/selftests/net/af_unix/msg_oob.c +++ b/tools/testing/selftests/net/af_unix/msg_oob.c @@ -290,6 +290,25 @@ static void __setinlinepair(struct __test_metadata *_metadata, } } +static void __setblockingpair(struct __test_metadata *_metadata, + FIXTURE_DATA(msg_oob) *self) +{ + int i; + + for (i = 0; i < 2; i++) { + int ret, old_flags, flags; + + old_flags = fcntl(self->fd[i * 2 + 1], F_GETFL, 0); + ASSERT_NE(-1, old_flags); + + ret = fcntl(self->fd[i * 2 + 1], F_SETFL, old_flags & ~O_NONBLOCK); + ASSERT_EQ(0, ret); + + flags = fcntl(self->fd[i * 2 + 1], F_GETFL, 0); + ASSERT_EQ(old_flags & ~O_NONBLOCK, flags); + } +} + static void __siocatmarkpair(struct __test_metadata *_metadata, FIXTURE_DATA(msg_oob) *self, bool oob_head) @@ -347,6 +366,9 @@ static void __resetpair(struct __test_metadata *_metadata, #define setinlinepair() \ __setinlinepair(_metadata, self) +#define setblockingpair() \ + __setblockingpair(_metadata, self) + #define resetpair(reset) \ __resetpair(_metadata, self, variant, reset) @@ -888,4 +910,49 @@ TEST_F(msg_oob, inline_ex_oob_siocatmark) resetpair(true); } +TEST_F(msg_oob, zero_buf_oob) +{ + sendpair("a", 1, MSG_OOB); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_oob_blocking) +{ + sendpair("a", 1, MSG_OOB); + setblockingpair(); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_non_oob_oob) +{ + sendpair("ab", 2, MSG_OOB); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_non_oob_oob_blocking) +{ + sendpair("ab", 2, MSG_OOB); + setblockingpair(); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_ex_oob_oob) +{ + sendpair("a", 1, MSG_OOB); + recvpair("a", 1, 1, MSG_OOB); + + sendpair("b", 1, MSG_OOB); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_ex_oob_oob_blocking) +{ + sendpair("a", 1, MSG_OOB); + recvpair("a", 1, 1, MSG_OOB); + + sendpair("b", 1, MSG_OOB); + setblockingpair(); + recvpair("", 0, 0, 0); +} + TEST_HARNESS_MAIN From b83641e0ab8b20eefcc4cdc5a059f897375291a2 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Tue, 1 Sep 2026 22:57:11 +0300 Subject: [PATCH 0503/1317] net: ipv4: Fix UDP length overflow with PMTU discover and big MTU This commit bounds cork->base.fragsize to IP_MAX_MTU to avoid a possible overflow of UDP length that triggers a WARN in udp_set_len_short when setsockopt IP_MTU_DISCOVER is set to IP_PMTUDISC_PROBE, and a large packet is sent over a netdev with an unusually large MTU. Steps to reproduce: 1. Set device MTU bigger than IP_MAX_MTU + 20. cork->base.fragsize will be set to that MTU in ip_setup_cork. 2. Set IP_MTU_DISCOVER to IP_PMTUDISC_PROBE. It lets maxnonfragsize be set to device MTU (cork->fragsize) in __ip_append_data, rather than to IP_MAX_MTU. 3. Send 65528 bytes of payload (+8 bytes of UDP header, +20 bytes of IPv4 header). Device MTU allows it (it's only one byte bigger than IP_MAX_MTU + IPv4 header, and the device MTU is bigger than that). 4. The UDP length in the built packet is 65536, which overflows the 16-bit length field and triggers the WARN in udp_set_len_short. Note: IP_PMTUDISC_DO with IPv4 is safe, because ip_dst_mtu_maybe_forward always clamps at IP_MAX_MTU, unlike ip6_dst_mtu_maybe_forward. The Fixes tag points at the first commit where I could reproduce the overflow with IPv4 and IP_PMTUDISC_PROBE. Fixes: daba287b299e ("ipv4: fix DO and PROBE pmtu mode regarding local fragmentation with UFO/CORK") Reported-by: syzbot+ce13c07d96d04716eaa2@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a6a966c.86abc875.e5c3d.0054.GAE@google.com/ Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901195714.673548-2-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- net/ipv4/ip_output.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index 74e095b6b7ca..a24cc8ee11d3 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -1303,6 +1303,7 @@ static int ip_setup_cork(struct sock *sk, struct inet_cork *cork, cork->fragsize = ip_sk_use_pmtu(sk) ? dst4_mtu(&rt->dst) : READ_ONCE(rt->dst.dev->mtu); + cork->fragsize = min(cork->fragsize, IP_MAX_MTU); if (!inetdev_valid_mtu(cork->fragsize)) return -ENETUNREACH; From 0ae10b6be49b425827659b23bcce498f80eb7182 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Tue, 1 Sep 2026 22:57:12 +0300 Subject: [PATCH 0504/1317] net: ipv6: Fix UDP length overflow with PMTU discover and big MTU This commit bounds cork->base.fragsize to IP6_MAX_MTU for UDP sockets to avoid a possible overflow of UDP length that triggers a WARN in udp_set_len_short when setsockopt IPV6_MTU_DISCOVER is set to IPV6_PMTUDISC_DO or IPV6_PMTUDISC_PROBE, and a large packet is sent over a netdev with an unusually large MTU. Steps to reproduce (included in the new selftest): 1. Set device MTU bigger than IP6_MAX_MTU. cork->base.fragsize will be set to that MTU in ip6_setup_cork. 2. Set IPV6_MTU_DISCOVER to IPV6_PMTUDISC_PROBE or IPV6_PMTUDISC_DO. It lets maxnonfragsize be set to device MTU (cork->fragsize) in __ip6_append_data, rather than to IP6_MAX_MTU. 3. Send 65528 bytes of payload (+8 bytes of UDP header, +40 bytes of IPv6 header). Device MTU allows it (it's only one byte bigger than IP6_MAX_MTU, and the device MTU is bigger than that). 4. The UDP length in the built packet is 65536, which overflows the 16-bit length field and triggers the WARN in udp_set_len_short. To avoid breaking sending UDP jumbograms over raw IPv6 sockets, limit the change to UDP sockets only. The original overflow bug with IPv6 and IPV6_PMTUDISC_DO seems to predate git history (verified reproduction on 2.6.21), was fixed later, and then reappeared in commit 427faee167bc ("net: ipv6: introduce ip6_dst_mtu_maybe_forward"), which is chosen as the Fixes tag here. The overflow with IPV6_PMTUDISC_PROBE reproduces since its introduction in commit 628a5c561890 ("[INET]: Add IP(V6)_PMTUDISC_RPOBE"). Fixes: 427faee167bc ("net: ipv6: introduce ip6_dst_mtu_maybe_forward") Reported-by: syzbot+ce13c07d96d04716eaa2@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a6a966c.86abc875.e5c3d.0054.GAE@google.com/ Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901195714.673548-3-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_output.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 8fc4766c8da9..550965058991 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -1432,6 +1432,8 @@ static int ip6_setup_cork(struct sock *sk, struct inet_cork_full *cork, if (frag_size && frag_size < mtu) mtu = frag_size; + if (sk_is_udp(sk)) + mtu = min(mtu, IP6_MAX_MTU); cork->base.fragsize = mtu; cork->base.gso_size = ipc6->gso_size; cork->base.tx_flags = 0; From 18a9a4342136c5ae954b37d961c374d369615de2 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Tue, 1 Sep 2026 22:57:13 +0300 Subject: [PATCH 0505/1317] selftests: net: Test UDP length overflow with PMTU discover and big MTU Two previous commits fixed overflow of UDP length when setsockopt IP(V6)_MTU_DISCOVER is set to IPV6_PMTUDISC_DO or IP(V6)_PMTUDISC_PROBE, and a large packet is sent over a netdev with an unusually large MTU. This commit adds the selftests that replicate the described steps to reproduce for IPv6 and IPv4, and also one more test that ensures that sending UDP jumbograms over a raw socket is still possible after the fix. Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901195714.673548-4-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/cork_fragsize.py | 187 +++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100755 tools/testing/selftests/net/cork_fragsize.py diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 517c09d60bef..3ee3378f8b26 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -25,6 +25,7 @@ TEST_PROGS := \ cmsg_so_mark.sh \ cmsg_so_priority.sh \ cmsg_time.sh \ + cork_fragsize.py \ double_udp_encap.sh \ drop_monitor_tests.sh \ ecmp_rehash.sh \ diff --git a/tools/testing/selftests/net/cork_fragsize.py b/tools/testing/selftests/net/cork_fragsize.py new file mode 100755 index 000000000000..7afd643d07ec --- /dev/null +++ b/tools/testing/selftests/net/cork_fragsize.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +'''Test possible UDP length overflow in udp_send_skb/udp_v6_send_skb.''' + +import errno +import gzip +import os +import socket +import struct +import subprocess +from contextlib import contextmanager + +from lib.py import ( + KsftNamedVariant, + KsftSkipEx, + NetNS, + NetNSEnter, + defer, + ip, + ksft_eq, + ksft_exit, + ksft_pr, + ksft_raises, + ksft_run, + ksft_true, + ksft_variants, +) + +IP_MTU_DISCOVER = 10 +IP_PMTUDISC_PROBE = 3 +IPV6_MTU_DISCOVER = 23 +IPV6_PMTUDISC_DO = 2 +IPV6_PMTUDISC_PROBE = 3 +IPV6_TLV_JUMBO = 194 + + +def check_kernel_config(option: str) -> bool | None: + ''' + Check whether the option is enabled in the config of the running kernel. + Returns None if the config is not found; otherwise returns True/False + depending on the option value in the config. + ''' + + for filename, method in [ + ('/proc/config.gz', gzip.open), + (f'/boot/config-{os.uname().release}', open), + ]: + try: + with method(filename, 'rt') as config: + for line in config: + if line.rstrip() == f'{option}=y': + return True + return False + except OSError: + continue + return None + + +def assert_debug_kernel() -> None: + ''' + Skip the test if CONFIG_DEBUG_NET is not set in the kernel config. + ''' + + res = check_kernel_config('CONFIG_DEBUG_NET') + if res is None: + ksft_pr("WARN: Can't read kernel config; assuming debug kernel, and running the test") + elif not res: + raise KsftSkipEx('CONFIG_DEBUG_NET is not set') + + +def check_dmesg_clean(func: str) -> bool: + ''' + Check if the given function produced a WARN in dmesg. + ''' + + with subprocess.Popen(['dmesg'], stdout=subprocess.PIPE) as dmesg: + res = subprocess.run(['grep', '-q', f'WARNING:.*{func}'], stdin=dmesg.stdout, check=False) + return res.returncode != 0 and dmesg.returncode == 0 + + +@contextmanager +def dummy_netdev(ns: NetNS, mtu: int, ipv6: bool) -> None: + ''' + Create a dummy netdev inside the given namespace, and tune it for the test. + ''' + + ip('link add dummy type dummy', ns=ns) + with defer(ip, 'link del dummy', ns=ns): + ip(f'link set dummy mtu {mtu}', ns=ns) + ip('link set dummy up', ns=ns) + flag = '-6' if ipv6 else '' + nodad = 'nodad' if ipv6 else '' + local = 'fd00::1/64' if ipv6 else '10.0.0.1/24' + remote = 'fd00::2' if ipv6 else '10.0.0.2' + ip(f'{flag} addr add {local} dev dummy {nodad}', ns=ns) + ip(f'{flag} neigh add {remote} lladdr 02:00:00:00:00:02 dev dummy nud permanent', ns=ns) + yield + + +@ksft_variants([ + KsftNamedVariant( + 'ipv6', + True, + socket.AF_INET6, + (socket.IPPROTO_IPV6, IPV6_MTU_DISCOVER, IPV6_PMTUDISC_DO), + 'fd00::2', + 'udp_v6_send_skb', + ), + KsftNamedVariant( + 'ipv4', + False, + socket.AF_INET, + (socket.IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_PROBE), + '10.0.0.2', + 'udp_send_skb', + ), +]) +def test_udp( + ipv6: bool, + af: socket.AddressFamily, + sockopts: tuple[int, int, int], + destip: str, + func: str +) -> None: + ''' + Test that sending an oversized UDP packet over a UDP socket doesn't overflow + the 16-bit length field in the UDP header, which could happen on older + kernels in udp_send_skb/udp_v6_send_skb. + + IPv4: The packet will be dropped with EMSGSIZE, but the overflow could + happen before it happens. The only way to test this is to check dmesg on + CONFIG_DEBUG_NET=y kernels that have udp_set_len_short with the warning. + + IPv6: The packet will be dropped with EMSGSIZE on fixed kernels, and will be + sent corrupted on older kernels. Test both: sendto must return EMSGSIZE, and + dmesg must be clean of warnings on CONFIG_DEBUG_NET=y kernels. + ''' + + if not ipv6: + assert_debug_kernel() + + with ( + NetNS() as ns, + dummy_netdev(ns, 65556 + 20 * ipv6, ipv6), + NetNSEnter(ns), + socket.socket(af, socket.SOCK_DGRAM) as fd, + ): + fd.setsockopt(*sockopts) + with ksft_raises(OSError) as e: + fd.sendto(b' ' * 65528, (destip, 1234)) + # IPv6: EMSGSIZE happens on kernels with the fix. + # IPv4: EMSGSIZE happens on both fixed and unfixed kernels, after the + # WARN is printed - ignore it and rely on the dmesg check. + if e.exception is not None: + ksft_eq(e.exception.errno, errno.EMSGSIZE) + + ksft_true(check_dmesg_clean(func), 'WARNING detected in dmesg') + + +def test_ipv6_jumbo() -> None: + ''' + Test that sending UDP jumbograms over a raw IPv6 socket works, despite + having the fix for oversized UDP packets. sendto must not raise an OSError + exception (when raised, the test fails automatically). + ''' + + with ( + NetNS() as ns, + dummy_netdev(ns, 65584, True), + NetNSEnter(ns), + socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_UDP) as fd, + ): + hopopts = struct.pack('!BBBBI', 0, 0, IPV6_TLV_JUMBO, 4, 65544) + fd.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_HOPOPTS, hopopts) + fd.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_CHECKSUM, 6) + fd.setsockopt(socket.IPPROTO_IPV6, IPV6_MTU_DISCOVER, IPV6_PMTUDISC_PROBE) + udp = struct.pack('!HHHH', 1234, 1234, 0, 0) + b' ' * 65528 + fd.sendto(udp, ('fd00::2', 0)) + + +if __name__ == "__main__": + ksft_run([ + test_udp, + test_ipv6_jumbo, + ]) + ksft_exit() From 199271ebc71c1e0913b2fad988a7bff330a8828a Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Tue, 1 Sep 2026 22:57:14 +0300 Subject: [PATCH 0506/1317] net: ipv6: Clamp to IP6_MAX_MTU in ip6_dst_mtu_maybe_forward Commit 427faee167bc ("net: ipv6: introduce ip6_dst_mtu_maybe_forward") dropped the IP6_MAX_MTU clamp that used to be present in ip6_mtu(). A similar IPv4 commit ac6627a28dbf ("net: ipv4: Consolidate ipv4_mtu and ip_dst_mtu_maybe_forward") preserves the IP_MAX_MTU clamp. Restore the upper bound in the IPv6 flow to avoid potential 16-bit overflows in forwarding paths. Fixes: 427faee167bc ("net: ipv6: introduce ip6_dst_mtu_maybe_forward") Signed-off-by: Alice Mikityanska Suggested-by: Willem de Bruijn Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901195714.673548-5-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- include/net/ip6_route.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index c69f1c871922..b9e8d2b759e9 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -384,6 +384,8 @@ static inline unsigned int ip6_dst_mtu_maybe_forward(const struct dst_entry *dst rcu_read_unlock(); out: + mtu = min_t(unsigned int, mtu, IP6_MAX_MTU); + return mtu - lwtunnel_headroom(dst->lwtstate, mtu); } From 4ff75f130d1b84f65a6f35a8a0cbca52130127ef Mon Sep 17 00:00:00 2001 From: Sebastian Sjoholm Date: Thu, 3 Sep 2026 20:00:44 +0200 Subject: [PATCH 0507/1317] net: usb: qmi_wwan: add Quectel RG660QB Add support for the Quectel RG660QB 5G module (USB ID 2c7c:013d). Its QMI interface (interface 4) uses class/subclass/protocol ff/ff/ff like the other recent Quectel modules, so match it the same way. The remaining interfaces are handled by the option driver. Tested with an early sample of the module on a Quectel 5G EVB connected over USB 3 to a Raspberry Pi 5: qmicli talks to the module via /dev/cdc-wdm0. Signed-off-by: Sebastian Sjoholm Link: https://patch.msgid.link/20260903180044.6179-1-sebastian.sjoholm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index fdfdcf24ddcf..f51cf9cb9421 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1084,6 +1084,7 @@ static const struct usb_device_id products[] = { }, {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0122)}, /* Quectel RG650V */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0125)}, /* Quectel EC25, EC20 R2.0 Mini PCIe */ + {QMI_MATCH_FF_FF_FF(0x2c7c, 0x013d)}, /* Quectel RG660QB */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0306)}, /* Quectel EP06/EG06/EM06 */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0512)}, /* Quectel EG12/EM12 */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0620)}, /* Quectel EM160R-GL */ From 3b55f350c68a0aceff108f47f9d31f47ebffaf7b Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 7 Sep 2026 14:10:24 +0200 Subject: [PATCH 0508/1317] bpf: Fix bpf_skb_change_tail wrt csum partial skbs Cilium generates ICMP "frag needed" replies from BPF when a LB DSR packet exceeds the egress MTU. The reply is built by first trimming the packet down to target size via bpf_skb_change_tail(), and then pushing the ICMP error headers in front of it. The trim is rejected for skbs which carry a checksum offload, e.g. TCP packets aggregated by GRO on ingress where tcp_gro_complete() leaves the skb as CHECKSUM_PARTIAL. __bpf_skb_min_len() raises the minimum length to the end of the L4 checksum field, so a trim to 42 bytes bails out with -EINVAL given a min_len of 52 in this case, and due to that the ICMP generator fails. This is not the case if GRO is turned off. Fix this bpf_skb_change_tail() restriction and drop the checksum offload when the new length no longer covers the checksum field. The BPF program rewrites the skb into an ICMP error and computes the checksum itself anyway. Fixes: 5293efe62df8 ("bpf: add bpf_skb_change_tail helper") Reported-by: Tom Hadlaw Reported-by: Yusuke Suzuki Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260907121025.1923656-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 61940e753552..8513167a858a 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3961,12 +3961,6 @@ static u32 __bpf_skb_min_len(const struct sk_buff *skb) if (offset > 0) min_len = offset; } - if (skb->ip_summed == CHECKSUM_PARTIAL) { - offset = skb_checksum_start_offset(skb) + - skb->csum_offset + sizeof(__sum16); - if (offset > 0) - min_len = offset; - } return min_len; } @@ -3983,6 +3977,11 @@ static int bpf_skb_grow_rcsum(struct sk_buff *skb, unsigned int new_len) static int bpf_skb_trim_rcsum(struct sk_buff *skb, unsigned int new_len) { + if (skb->ip_summed == CHECKSUM_PARTIAL && + new_len < skb_checksum_start_offset(skb) + skb->csum_offset + + sizeof(__sum16)) + skb->ip_summed = CHECKSUM_NONE; + return __skb_trim_rcsum(skb, new_len); } From 15e2565f1c43771af0bc5324971cabaad79ac286 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 7 Sep 2026 14:10:25 +0200 Subject: [PATCH 0509/1317] selftests/bpf: Add test for bpf_skb_change_tail on csum partial skbs Add a test which builds an ICMP error out of a TCP segment. A tcx prog on the client's egress side trims the first data segment down to the target size and pushes the ICMP error headers in front of it to then reflect the packet back to the sender. # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t tc_change_tail [...] #509 tc_change_tail:OK #510 tc_change_tail_pmtu:OK Summary: 2/0 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260907121025.1923656-2-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- .../bpf/prog_tests/tc_change_tail_pmtu.c | 125 +++++++++++++++++ .../bpf/progs/test_tc_change_tail_pmtu.c | 129 ++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c create mode 100644 tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c diff --git a/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c new file mode 100644 index 000000000000..7acdbd5757a9 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include "test_progs.h" +#include "network_helpers.h" +#include "test_tc_change_tail_pmtu.skel.h" + +#define CLIENT_NS "tc-change-tail-cli-ns" +#define SERVER_NS "tc-change-tail-srv-ns" +#define CLIENT_IP "192.168.1.1" +#define SERVER_IP "192.168.1.2" + +#define TEST_PMTU 1000 +#define TEST_MSS_MAX (TEST_PMTU - 20 - 20) +#define TIMEOUT_MS 3000 +#define XFER_BYTES 8192 + +void test_tc_change_tail_pmtu(void) +{ + LIBBPF_OPTS(bpf_tcx_opts, tcx_opts); + int mss_before = 0, mss_after = 0, ifindex, port; + int srv_fd = -1, srv_conn_fd = -1, cli_fd = -1; + struct test_tc_change_tail_pmtu *skel = NULL; + struct nstoken *nstoken = NULL; + static char buf[XFER_BYTES]; + socklen_t optlen; + ssize_t bytes; + size_t total; + + if (!ASSERT_OK(make_netns(CLIENT_NS), "make client ns")) + return; + if (!ASSERT_OK(make_netns(SERVER_NS), "make server ns")) + goto out_client_ns; + + nstoken = open_netns(CLIENT_NS); + if (!ASSERT_OK_PTR(nstoken, "open client ns")) + goto out; + SYS(out, "ip link add veth1 type veth peer name veth2 netns " SERVER_NS); + SYS(out, "ip -4 addr add " CLIENT_IP "/24 dev veth1"); + SYS(out, "ip link set veth1 up"); + ifindex = if_nametoindex("veth1"); + if (!ASSERT_NEQ(ifindex, 0, "if_nametoindex")) + goto out; + close_netns(nstoken); + nstoken = NULL; + + nstoken = open_netns(SERVER_NS); + if (!ASSERT_OK_PTR(nstoken, "open server ns")) + goto out; + SYS(out, "ip -4 addr add " SERVER_IP "/24 dev veth2"); + SYS(out, "ip link set veth2 up"); + srv_fd = start_server(AF_INET, SOCK_STREAM, SERVER_IP, 0, TIMEOUT_MS); + if (!ASSERT_OK_FD(srv_fd, "start server")) + goto out; + close_netns(nstoken); + nstoken = NULL; + + skel = test_tc_change_tail_pmtu__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open and load skeleton")) + goto out; + + port = get_socket_local_port(srv_fd); + if (!ASSERT_GE(port, 0, "get server port")) + goto out; + + skel->bss->server_port = port; + skel->bss->pmtu = TEST_PMTU; + + nstoken = open_netns(CLIENT_NS); + if (!ASSERT_OK_PTR(nstoken, "open client ns")) + goto out; + + skel->links.change_tail_icmp = + bpf_program__attach_tcx(skel->progs.change_tail_icmp, ifindex, + &tcx_opts); + if (!ASSERT_OK_PTR(skel->links.change_tail_icmp, "attach tcx")) + goto out; + + cli_fd = connect_to_fd(srv_fd, TIMEOUT_MS); + if (!ASSERT_OK_FD(cli_fd, "connect to server")) + goto out; + srv_conn_fd = accept(srv_fd, NULL, NULL); + if (!ASSERT_OK_FD(srv_conn_fd, "accept connection")) + goto out; + if (!ASSERT_OK(settimeo(srv_conn_fd, TIMEOUT_MS), "set server timeout")) + goto out; + + optlen = sizeof(mss_before); + if (!ASSERT_OK(getsockopt(cli_fd, IPPROTO_TCP, TCP_MAXSEG, &mss_before, + &optlen), "get mss before")) + goto out; + + bytes = send(cli_fd, buf, sizeof(buf), 0); + if (!ASSERT_EQ(bytes, (ssize_t)sizeof(buf), "send data")) + goto out; + + for (total = 0; total < sizeof(buf); total += bytes) { + bytes = recv(srv_conn_fd, buf, sizeof(buf), 0); + if (bytes <= 0) + break; + } + + ASSERT_EQ(total, sizeof(buf), "receive data"); + ASSERT_OK(skel->data->change_tail_ret, "change tail"); + ASSERT_OK(skel->bss->adjust_room_ret, "adjust room"); + ASSERT_TRUE(skel->bss->icmp_sent, "icmp sent"); + + optlen = sizeof(mss_after); + if (!ASSERT_OK(getsockopt(cli_fd, IPPROTO_TCP, TCP_MAXSEG, &mss_after, + &optlen), "get mss after")) + goto out; + + ASSERT_LT(mss_after, mss_before, "mss reduced"); + ASSERT_LE(mss_after, TEST_MSS_MAX, "mss below pmtu"); +out: + close(srv_conn_fd); + close(cli_fd); + close(srv_fd); + test_tc_change_tail_pmtu__destroy(skel); + close_netns(nstoken); + remove_netns(SERVER_NS); +out_client_ns: + remove_netns(CLIENT_NS); +} diff --git a/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c new file mode 100644 index 000000000000..5c4c07545bc9 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ICMP_SAMPLE_LEN (sizeof(struct iphdr) + 8) +#define ICMP_HDRS_LEN (sizeof(struct iphdr) + sizeof(struct icmphdr)) + +__be16 server_port = 0; +__u16 pmtu = 0; + +long change_tail_ret = 1; +long adjust_room_ret = 0; +bool icmp_sent = false; +bool icmp_err = false; + +static __always_inline __sum16 csum_fold(__wsum csum) +{ + csum = (csum & 0xffff) + (csum >> 16); + csum = (csum & 0xffff) + (csum >> 16); + + return (__sum16)~csum; +} + +SEC("tc/egress") +int change_tail_icmp(struct __sk_buff *skb) +{ + __u8 smac[ETH_ALEN], dmac[ETH_ALEN]; + void *data, *data_end; + struct icmphdr *icmp; + struct ethhdr *eth; + struct tcphdr *tcp; + __be32 saddr, daddr; + struct iphdr *ip; + __wsum csum; + + if (icmp_sent || icmp_err) + return TCX_PASS; + + data = (void *)(long)skb->data; + data_end = (void *)(long)skb->data_end; + + eth = data; + if ((void *)(eth + 1) > data_end) + return TCX_PASS; + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return TCX_PASS; + + ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return TCX_PASS; + if (ip->ihl != 5 || ip->protocol != IPPROTO_TCP) + return TCX_PASS; + + tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return TCX_PASS; + if (tcp->dest != server_port) + return TCX_PASS; + if (bpf_ntohs(ip->tot_len) <= sizeof(*ip) + tcp->doff * 4) + return TCX_PASS; + + __builtin_memcpy(smac, eth->h_source, ETH_ALEN); + __builtin_memcpy(dmac, eth->h_dest, ETH_ALEN); + saddr = ip->saddr; + daddr = ip->daddr; + + change_tail_ret = bpf_skb_change_tail(skb, ETH_HLEN + ICMP_SAMPLE_LEN, 0); + if (change_tail_ret) { + icmp_err = true; + return TCX_PASS; + } + + adjust_room_ret = bpf_skb_adjust_room(skb, ICMP_HDRS_LEN, + BPF_ADJ_ROOM_MAC, + BPF_F_ADJ_ROOM_NO_CSUM_RESET); + if (adjust_room_ret) { + icmp_err = true; + return TCX_DROP; + } + + data = (void *)(long)skb->data; + data_end = (void *)(long)skb->data_end; + + eth = data; + ip = (void *)(eth + 1); + icmp = (void *)(ip + 1); + if ((void *)icmp + sizeof(*icmp) + ICMP_SAMPLE_LEN > data_end) { + icmp_err = true; + return TCX_DROP; + } + + __builtin_memcpy(eth->h_dest, smac, ETH_ALEN); + __builtin_memcpy(eth->h_source, dmac, ETH_ALEN); + + __builtin_memset(icmp, 0, sizeof(*icmp)); + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_FRAG_NEEDED; + icmp->un.frag.mtu = bpf_htons(pmtu); + + __builtin_memset(ip, 0, sizeof(*ip)); + ip->version = 4; + ip->ihl = 5; + ip->ttl = 64; + ip->protocol = IPPROTO_ICMP; + ip->tot_len = bpf_htons(ICMP_HDRS_LEN + ICMP_SAMPLE_LEN); + ip->saddr = daddr; + ip->daddr = saddr; + + csum = bpf_csum_diff(NULL, 0, (__be32 *)icmp, + sizeof(*icmp) + ICMP_SAMPLE_LEN, 0); + icmp->checksum = csum_fold(csum); + csum = bpf_csum_diff(NULL, 0, (__be32 *)ip, sizeof(*ip), 0); + ip->check = csum_fold(csum); + icmp_sent = true; + return bpf_redirect(skb->ifindex, BPF_F_INGRESS); +} + +char _license[] SEC("license") = "GPL"; From b7c0f8436f077e7f66c9f07714bd57068e2a0c31 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 21 Aug 2026 01:13:25 -0700 Subject: [PATCH 0510/1317] drm/msm/adreno: Only check for PAS when a zap shader is present Commit 0be72be03ca7 ("drm/msm: Switch to generic PAS TZ APIs") replaced the qcom_scm_is_available() check in adreno_zap_shader_load() with qcom_pas_is_available(). These are not equivalent: the former reports whether the SCM transport is up, the latter whether the TrustZone firmware implements the peripheral authentication service. On SC7180 Chromebooks (trogdor) TZ does not implement PAS at all. SCM call-availability queries return 0 for every PAS command while other services answer normally: svc 0x06 cmd 0x01 IS_CALL_AVAIL -> 1 svc 0x02 cmd 0x01 PAS_INIT_IMAGE -> 0 svc 0x02 cmd 0x05 PAS_AUTH_RESET -> 0 svc 0x02 cmd 0x07 PAS_IS_SUPPORTED -> 0 svc 0x0c cmd 0x16 MP_ASSIGN -> 1 svc 0x05 cmd 0x01 IO_READ -> 1 so qcom_scm_probe() never registers a PAS backend and qcom_pas_is_available() is false for the lifetime of the boot. That on its own need not matter, because sc7180-trogdor.dtsi does /delete-node/ &gpu_zap_shader;, and the intended path for such a board is for zap_shader_load_mdt() to find no zap-shader child, clear zap_available, return -ENODEV, and let the caller fall back to SECVID_TRUST_CNTL. The problem is the ordering. zap_available is a static initialised to true and is only ever cleared inside zap_shader_load_mdt(), but adreno_zap_shader_load() consults PAS before calling it. The discovery that decides whether a zap shader is needed at all can therefore never run, the flag is never cleared, and every call returns -EPROBE_DEFER: adreno 5000000.gpu: [drm:adreno_zap_shader_load] *ERROR* PAS is not available msm_dpu ae01000.display-controller: [drm:adreno_load_gpu] *ERROR* gpu hw init failed: -517 Nothing retries that deferral, either. adreno_zap_shader_load() is called from a6xx_hw_init() rather than from probe, so the -EPROBE_DEFER is not a probe return value: it propagates up until adreno_load_gpu() returns NULL. load_gpu() re-attempts on every DRM open while priv->gpu is NULL, each open fails identically, and PAS cannot become available in between - which is why the error repeats and userspace stays on llvmpipe. Move the availability check into zap_shader_load_mdt(), behind the zap-shader node lookup, so the driver only consults PAS once it knows it needs PAS. Boards with no zap-shader node take the intended -ENODEV fallback without ever asking, and boards that do have one keep the qcom_pas_is_available() gate. Fixes: 0be72be03ca7 ("drm/msm: Switch to generic PAS TZ APIs") Link: https://lore.kernel.org/r/20260808034716.58888-1-phollinsky@holtechnik.com Signed-off-by: Paul Hollinsky Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/747583/ Message-ID: <20260821081325.89088-1-phollinsky@holtechnik.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 8cd2020d4b7e..ca5e4e560cde 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -52,6 +52,12 @@ static int zap_shader_load_mdt(struct msm_gpu *gpu, const char *fwname, return -ENODEV; } + /* We need PAS to be able to load the firmware */ + if (!qcom_pas_is_available()) { + DRM_DEV_ERROR(dev, "PAS is not available\n"); + return -EPROBE_DEFER; + } + ret = of_reserved_mem_region_to_resource(np, 0, &r); if (ret) { zap_available = false; @@ -170,18 +176,11 @@ static int zap_shader_load_mdt(struct msm_gpu *gpu, const char *fwname, int adreno_zap_shader_load(struct msm_gpu *gpu, u32 pasid) { struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); - struct platform_device *pdev = gpu->pdev; /* Short cut if we determine the zap shader isn't available/needed */ if (!zap_available) return -ENODEV; - /* We need PAS to be able to load the firmware */ - if (!qcom_pas_is_available()) { - DRM_DEV_ERROR(&pdev->dev, "PAS is not available\n"); - return -EPROBE_DEFER; - } - return zap_shader_load_mdt(gpu, adreno_gpu->info->zapfw, pasid); } From 8d6cd188508513503805c156165de38e4e4a8615 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 3 Sep 2026 14:23:03 +0800 Subject: [PATCH 0511/1317] ipv6: flowlabel: cap duplicate leases per socket ipv6_flowlabel_get() allocates an ipv6_fl_socklist entry for every successful GET. The recheck path for a compatible existing flowlabel links another lease without applying any lease admission check. Repeated GET requests for one shareable label can therefore grow a socket's lease list without bound. Reject a new unprivileged lease once the socket already holds FL_MAX_PER_SOCK leases. Check this on the shared recheck path so reuse of a globally interned label, including the fl_intern() collision path, is covered as well. New-label admission remains under the existing mem_check() policy. Use capable(CAP_NET_ADMIN) rather than ns_capable(), matching mem_check(). An unprivileged user must not bypass the cap by creating a user namespace and a netns where they have CAP_NET_ADMIN, which would still consume host memory. Check the capability only when the socket reaches the limit, so successful unprivileged GET requests below the cap do not generate a capability audit. Do the admission check before updating linger and expires so a rejected GET does not refresh the shared label, matching the existing socket-list allocation failure path. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Ido Schimmel Signed-off-by: Zhiling Zou Reviewed-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/83f8535972ff6e3741548476a1d50dec24c758be.1788415194.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_flowlabel.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/net/ipv6/ip6_flowlabel.c b/net/ipv6/ip6_flowlabel.c index 1ab5ad0dcf24..006585dc8b5c 100644 --- a/net/ipv6/ip6_flowlabel.c +++ b/net/ipv6/ip6_flowlabel.c @@ -461,6 +461,21 @@ fl_create(struct net *net, struct sock *sk, struct in6_flowlabel_req *freq, return NULL; } +static bool fl_sock_at_lease_limit(const struct sock *sk) +{ + const struct ipv6_fl_socklist *sfl; + int count = 0; + + rcu_read_lock(); + for_each_sk_fl_rcu(sk, sfl) { + if (++count >= FL_MAX_PER_SOCK) + break; + } + rcu_read_unlock(); + + return count >= FL_MAX_PER_SOCK; +} + static int mem_check(struct sock *sk) { const int unpriv_total_limit = FL_MAX_SIZE - (FL_MAX_SIZE / 4); @@ -679,6 +694,10 @@ static int ipv6_flowlabel_get(struct sock *sk, struct in6_flowlabel_req *freq, err = -ENOMEM; if (!sfl1) goto release; + err = -ENOBUFS; + if (fl_sock_at_lease_limit(sk) && + !capable(CAP_NET_ADMIN)) + goto release; if (fl->linger > fl1->linger) fl1->linger = fl->linger; if ((long)(fl->expires - fl1->expires) > 0) From 9868f5c077dfe0b606331f2e782484f91a5789a5 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 17 Jun 2026 11:43:03 -0500 Subject: [PATCH 0512/1317] EDAC/altera: Use parent device for devres in altr_portb_setup() Anchor the devres group and the devm-managed IRQ requests in altr_portb_setup() to the actual parent device (device->edac->dev) instead of the embedded struct device inside the copied per-port altr_edac_device_dev. This keeps devres_open_group(), devm_request_irq(), devres_remove_group() and devres_release_group() all referring to the same long-lived device so the group and the resources allocated inside it are torn down together. Fixes: 911049845d70 ("EDAC, altera: Add Arria10 SD-MMC EDAC support") Closes: https://sashiko.dev/#/patchset/20260503212558.2811480-1-dbgh9129%40gmail.com Assisted-by: LLM Signed-off-by: Dinh Nguyen Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260617164303.585555-1-dinguyen@kernel.org --- drivers/edac/altera_edac.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 1d1e2b5ca14c..68846f583eee 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1534,7 +1534,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) altdev = dci->pvt_info; *altdev = *device; - if (!devres_open_group(&altdev->ddev, altr_portb_setup, GFP_KERNEL)) + if (!devres_open_group(device->edac->dev, altr_portb_setup, GFP_KERNEL)) return -ENOMEM; /* Update PortB specific values */ @@ -1562,7 +1562,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) rc = -ENODEV; goto err_release_group_1; } - rc = devm_request_irq(&altdev->ddev, altdev->sb_irq, + rc = devm_request_irq(device->edac->dev, altdev->sb_irq, prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, ecc_name, altdev); if (rc) { @@ -1584,7 +1584,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) rc = -ENODEV; goto err_release_group_1; } - rc = devm_request_irq(&altdev->ddev, altdev->db_irq, + rc = devm_request_irq(device->edac->dev, altdev->db_irq, prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, ecc_name, altdev); if (rc) { @@ -1604,13 +1604,13 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) list_add(&altdev->next, &altdev->edac->a10_ecc_devices); - devres_remove_group(&altdev->ddev, altr_portb_setup); + devres_remove_group(device->edac->dev, altr_portb_setup); return 0; err_release_group_1: edac_device_free_ctl_info(dci); - devres_release_group(&altdev->ddev, altr_portb_setup); + devres_release_group(device->edac->dev, altr_portb_setup); edac_printk(KERN_ERR, EDAC_DEVICE, "%s:Error setting up EDAC device: %d\n", ecc_name, rc); return rc; From fcf57d066444dfa5cb46080b1b61cde517249530 Mon Sep 17 00:00:00 2001 From: Ruairi Anthony Date: Mon, 7 Sep 2026 12:59:42 +0100 Subject: [PATCH 0513/1317] ALSA: hda/realtek: Add quirk for HP Elite Dragonfly Max G2 speaker The HP Elite Dragonfly Max G2 Notebook PC (SSID 103c:8890) uses an ALC285 codec but is missing from the existing HP quirk table, unlike the related "HP Elite Dragonfly G2" entry (SSID 103c:8716). Without the quirk the right speaker amplifier's GPIO is never initialized, leaving it silent while the left speaker plays normally. Apply the existing ALC285_FIXUP_HP_GPIO_AMP_INIT fixup used by the 103c:8716 entry, which resolves the issue. Verified with speaker-test on both channels and confirmed correct routing to both the downward- firing woofer and upward-firing tweeter on each side via the board's passive crossover. Signed-off-by: Ruairi Anthony Link: https://patch.msgid.link/20260907115942.19286-1-ruairi@ruairi.uk Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index f7786e1b0878..3abee617e86e 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7451,6 +7451,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8890, "HP Elite Dragonfly Max G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8895, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED), SND_PCI_QUIRK(0x103c, 0x8896, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8898, "HP EliteBook 845 G8 Notebook PC", ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST), From aefdbd574a362dcf7569bada6d72f64a006b9fb9 Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Tue, 25 Aug 2026 17:48:03 -0400 Subject: [PATCH 0514/1317] x86/amd_node: Fix potential NULL pointer dereference amd_smn_read/write() are exported functions around __amd_smn_rw(), so they are always available even if amd_smn_init() fails. In that case, 'amd_roots' is NULL and __amd_smn_rw() will access uninitialized memory. Then, commit: 83518453074d ("x86/amd_node: Add SMN offsets to exclusive region access") added the 'smn_exclusive' flag, which indicated the calls to pci_request_config_region_exclusive() succeeded, to prevent concurrent userspace access. Commit: 0a4b61d9c2e4 ("x86/amd_node: Fix AMD root device caching") re-ordered initialization so pci_request_config_region_exclusive() is called earlier and a failure exits amd_smn_init() before allocating 'amd_roots'. The setting of 'smn_exclusive' moved to the end of amd_smn_init(), after 'amd_roots' is allocated. It became redundant and can be removed. Replace 'smn_exclusive' with directly checking 'amd_roots', to fix a potential NULL pointer dereference and to simplify the logic. [ bp: Reorg commit message, touchup comment. ] [ mingo: Rebase & further touchups. ] Fixes: 77466b798d59 ("x86/amd_node: Remove dependency on AMD_NB") Signed-off-by: Jason Andryuk Signed-off-by: Borislav Petkov (AMD) Signed-off-by: Ingo Molnar Reviewed-by: Yazen Ghannam Reviewed-by: Mario Limonciello (AMD) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260825214805.39148-3-jason.andryuk@amd.com --- arch/x86/kernel/amd_node.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c index 408b9fd48349..762585775b5a 100644 --- a/arch/x86/kernel/amd_node.c +++ b/arch/x86/kernel/amd_node.c @@ -38,7 +38,6 @@ static struct pci_dev **amd_roots; /* Protect the PCI config register pairs used for SMN. */ static DEFINE_MUTEX(smn_mutex); -static bool smn_exclusive; #define SMN_INDEX_OFFSET 0x60 #define SMN_DATA_OFFSET 0x64 @@ -91,11 +90,16 @@ static int __amd_smn_rw(u8 i_off, u8 d_off, u16 node, u32 address, u32 *value, b if (node >= amd_num_nodes()) return err; - root = amd_roots[node]; - if (!root) + /* + * Uninitialized amd_roots indicates pci_request_config_region_exclusive() + * didn't run or failed and thus the kernel cannot rely on having + * exclusive access to SMN registers so prevent that. + */ + if (!amd_roots) return err; - if (!smn_exclusive) + root = amd_roots[node]; + if (!root) return err; guard(mutex)(&smn_mutex); @@ -313,8 +317,6 @@ static int __init amd_smn_init(void) debugfs_create_file("value", 0600, debugfs_dir, NULL, &smn_value_fops); } - smn_exclusive = true; - return 0; } From d2929113b15bfc06793b852aeba3d2db6d79fcc9 Mon Sep 17 00:00:00 2001 From: Jasjeet Rangi Date: Wed, 12 Aug 2026 16:15:13 -0600 Subject: [PATCH 0515/1317] x86/MCE/AMD: Fix inverted interrupt enablement during storm handling mce_amd_handle_storm() currently does the opposite of what storm handling needs: it enables thresholding interrupts when a storm is detected and disables them when the storm subsides. Flip the "on" function argument before passing it to threshold_restart_bank() as it should have been done. To clarify: "on" to mce_handle_storm() means, the storm is on now when "on" is true, and off when "on" is false. [ bp: Simplify. ] Fixes: 5c4663ed1eac ("x86/mce: Handle AMD threshold interrupt storms") Signed-off-by: Jasjeet Rangi Signed-off-by: Borislav Petkov (AMD) Signed-off-by: Ingo Molnar Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260812221514.598842-2-jrangi@purestorage.com --- arch/x86/kernel/cpu/mce/amd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/mce/amd.c b/arch/x86/kernel/cpu/mce/amd.c index f916fb4c5d13..1cc20b855b7e 100644 --- a/arch/x86/kernel/cpu/mce/amd.c +++ b/arch/x86/kernel/cpu/mce/amd.c @@ -865,7 +865,7 @@ static void amd_deferred_error_interrupt(void) void mce_amd_handle_storm(unsigned int bank, bool on) { - threshold_restart_bank(bank, on); + threshold_restart_bank(bank, !on); } static void amd_reset_thr_limit(unsigned int bank) From 48a4ee65e677559776349128e6a81a6041986c99 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 8 Sep 2026 15:31:51 +0800 Subject: [PATCH 0516/1317] vduse: return compat ioctl results directly The compat handler handles VDUSE_IOTLB_GET_FD and VDUSE_VQ_GET_INFO, but then calls the native handler. Their different command sizes make native dispatch return -ENOIOCTLCMD. For GET_FD, this overwrites receive_fd()'s return value after the descriptor is installed, leaking one fd per call. Return handled compat results directly and use native dispatch only for other commands. Fixes: 455a2a1af926 ("vduse: fix compat handling for VDUSE_IOTLB_GET_FD/VDUSE_VQ_GET_INFO") Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260908-fix-vduse_dev_compat_ioctl-v1-1-62264d9bfb8d@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 4dea4d6a3855..49a231bdf948 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -1882,11 +1882,11 @@ static long vduse_dev_compat_ioctl(struct file *file, unsigned int cmd, break; } default: - ret = -ENOIOCTLCMD; - break; + return vduse_dev_ioctl(file, cmd, + (unsigned long)compat_ptr(arg)); } - return vduse_dev_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); + return ret; } #else #define vduse_dev_compat_ioctl compat_ptr_ioctl From 4e17b5007b6664559cdad2b2fe270526cf786b5b Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:44 -0700 Subject: [PATCH 0517/1317] bnxt_en: Only restore LRO if the device supports TPA With a P5+ device with firmware that reports max_aggs_supported == 0, it is possible to make LRO settable by attaching and detaching an XDP program even though the device does not support TPA. Fix this by testing BNXT_SUPPORTS_TPA before restoring the feature bit. Fixes: f0aa6a37a3db ("eth: bnxt: always recalculate features after XDP clearing, fix null-deref") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-2-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 8c6e2ee6bee4..343d70a98134 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -5026,7 +5026,8 @@ void bnxt_set_rx_skb_mode(struct bnxt *bp, bool page_mode) bnxt_get_max_rings(bp, &rx, &tx, true); if (rx > 1) { bp->flags &= ~BNXT_FLAG_NO_AGG_RINGS; - bp->dev->hw_features |= NETIF_F_LRO; + if (BNXT_SUPPORTS_TPA(bp)) + bp->dev->hw_features |= NETIF_F_LRO; } } From 5ce7f36c334d723954855ac769ede2fe0e8f89c8 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:45 -0700 Subject: [PATCH 0518/1317] bnxt_en: Don't free the live ring's TPA state on queue restart failure bnxt_queue_mem_alloc() shallow copies the live RX ring into the clone: memcpy(clone, rxr, sizeof(*rxr)); the code currently clears pointers that the clone owns (such as rx_agg_bmap), but rx_tpa and rx_tpa_idx_map are left pointing at memory of the live ring that was cloned. If an allocation failure happens later and the err_free_tpa_info label is taken, the live ring's memory can be freed while still in use. Fix this by initializing the clone's pointers to NULL to prevent live ring state from being freed inadvertently. Fixes: bd649c5cc958 ("bnxt_en: handle tpa_info in queue API implementation") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-3-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 343d70a98134..aaf658976865 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -16357,6 +16357,8 @@ static int bnxt_queue_mem_alloc(struct net_device *dev, clone->need_head_pool = false; clone->rx_page_size = qcfg->rx_page_size; clone->rx_agg_bmap = NULL; + clone->rx_tpa = NULL; + clone->rx_tpa_idx_map = NULL; rc = bnxt_alloc_rx_page_pool(bp, clone, rxr->page_pool->p.nid); if (rc) From b814dfbfeb0a68c9a52073f2caa05a2d5247a329 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:46 -0700 Subject: [PATCH 0519/1317] bnxt_en: Propagate TPA buffer allocation failures in bnxt_queue_mem_alloc() bnxt_alloc_one_tpa_info_data() returns -ENOMEM as soon as one allocation fails. This leaves the remaining rxr->rx_tpa[] entries zeroed. bnxt_queue_mem_alloc() discards that return value, so the partially initialized ring is installed by bnxt_queue_start(). Since the agg_id is picked by the hardware and bnxt_alloc_agg_idx maps it to a SW index in rxr->rx_tpa[], it is possible that an uninitialized slot can be chosen which would hand a zero DMA address to the device. Fix this by checking the return value of bnxt_alloc_one_tpa_info_data and unwinding, freeing the ring buffers. Fixes: bd649c5cc958 ("bnxt_en: handle tpa_info in queue API implementation") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-4-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index aaf658976865..ae7150c7de1f 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -16402,11 +16402,16 @@ static int bnxt_queue_mem_alloc(struct net_device *dev, bnxt_alloc_one_rx_ring_skb(bp, clone, idx); if (bp->flags & BNXT_FLAG_AGG_RINGS) bnxt_alloc_one_rx_ring_netmem(bp, clone, idx); - if (bp->flags & BNXT_FLAG_TPA) - bnxt_alloc_one_tpa_info_data(bp, clone); + if (bp->flags & BNXT_FLAG_TPA) { + rc = bnxt_alloc_one_tpa_info_data(bp, clone); + if (rc) + goto err_free_rx_ring_skbs; + } return 0; +err_free_rx_ring_skbs: + bnxt_free_one_rx_ring_skbs(bp, clone); err_free_tpa_info: bnxt_free_one_tpa_info(bp, clone); err_free_rx_agg_ring: From 961e2a17c5e3559b3f8654d2daabdd25a42e770a Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:47 -0700 Subject: [PATCH 0520/1317] bnxt_en: Handle buffer allocation failure in bnxt_rx_ring_reset() bnxt_rx_ring_reset() frees the ring buffers and then reallocates them, ignoring the result. bnxt_alloc_one_rx_ring() can fail in bnxt_alloc_one_tpa_info_data(), which returns -ENOMEM on the first failed allocation and leaves the remaining rxr->rx_tpa[] entries zeroed. The error isn't propagated up, so the loop in bnxt_rx_ring_reset continues and at the end the code re-enables TPA with partially unallocated rx_tpa array. This means that when the agg_id from hardware is mapped to a SW index in rxr->rx_tpa[], an uninitialized slot can be chosen which would hand a zero DMA address to the device. Fix this by falling back to a global reset, which is what the existing code already does when other functions fail, but unlike the other failure cases this particular failure has to return because TPA can't be re-enabled since the allocation failed. Fixes: 8fbf58e17dce ("bnxt_en: Implement RX ring reset in response to buffer errors.") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-5-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index ae7150c7de1f..a2283fd9cdfc 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -14628,7 +14628,14 @@ static void bnxt_rx_ring_reset(struct bnxt *bp) rxr->rx_sw_agg_prod = 0; rxr->rx_next_cons = 0; rxr->bnapi->in_reset = false; - bnxt_alloc_one_rx_ring(bp, i); + rc = bnxt_alloc_one_rx_ring(bp, i); + if (rc) { + netdev_warn(bp->dev, "RX ring reset failed to allocate buffers, rc = %d, falling back to global reset\n", + rc); + bnxt_reset_task(bp, true); + bnxt_rtnl_unlock_sp(bp); + return; + } cpr = &rxr->bnapi->cp_ring; cpr->sw_stats->rx.rx_resets++; if (bp->flags & BNXT_FLAG_AGG_RINGS) From 8e6a850c0746bb4be167aedf1ee57469fcda09a9 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:48 -0700 Subject: [PATCH 0521/1317] bnxt_en: Propagate RX ring init failures in bnxt_init_nic() bnxt_init_rx_rings() returns an error when bnxt_alloc_one_rx_ring() fails, but bnxt_init_nic() discards that return value and calls bnxt_init_chip(), which enables TPA. If an allocation fails, this could leave rxr->rx_tpa[] partially zeroed and TPA would be enabled over an array with zeroed entries. This would lead to a zeroed DMA address being handed out if the agg_idx is translated to a SW index at a zeroed entry. Fix this by propagating the error out of bnxt_init_nic(). Both callers already check its return value and unwind with bnxt_free_skbs() and bnxt_free_mem(), which tolerate a partially initialized RX ring. Fixes: c0c050c58d84 ("bnxt_en: New Broadcom ethernet driver.") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-6-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index a2283fd9cdfc..32c59b3d1cbc 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11363,8 +11363,13 @@ static int bnxt_shutdown_nic(struct bnxt *bp, bool irq_re_init) static int bnxt_init_nic(struct bnxt *bp, bool irq_re_init) { + int rc; + bnxt_init_cp_rings(bp); - bnxt_init_rx_rings(bp); + rc = bnxt_init_rx_rings(bp); + if (rc) + return rc; + bnxt_init_tx_rings(bp); bnxt_init_ring_grps(bp, irq_re_init); bnxt_init_vnics(bp); From c0aceaf65b70b3c000e70dd867f3a673015f24ca Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:49 -0700 Subject: [PATCH 0522/1317] bnxt_en: Bound SW TPA IDs to prevent crashes FW supports up to 1024 concurrent TPAs, so the FW TPA ID is in the range 0..1023 (see commit ec4d8e7cf024 ("bnxt_en: Add TPA ID mapping logic for 57500 chips.")). bnxt_alloc_agg_idx is intended to wrap the FW ID down to a software ID which is used to index rxr->rx_tpa, and to generate a mapping between FW IDs and the wrapped software ID. On a 57608 with firmware version 233, the firmware advertises 32 concurrent TPAs. As of the commit under fixes, bp->max_tpa on this NIC is set to 32. If the software ID from bnxt_alloc_agg_idx is above 31, this results in an invalid address being loaded on this line: tpa_info = &rxr->rx_tpa[agg_id]; because rx_tpa is allocated with only bp->max_tpa (32) entries. Writes to tpa_info later in the code are out of bounds. This bug results in a crash at boot: Oops: general protection fault, kernel NULL pointer dereference 0x8: 0000 [#1] SMP NOPTI RIP: 0010:bnxt_rx_pkt+0xc0/0x1560 RSP: 0018:ffffc900009b8c78 EFLAGS: 00010246 RAX: 0000000000000000 RBX: 0000000000000048 RCX: 0000000206682516 RDX: ffffc900009b8db4 RSI: 0000000000000000 RDI: 01ffffff038fe1c0 RBP: ffffc9006e687480 R08: ffffc9006e687000 R09: 0000000000003048 R10: 0000000000000480 R11: ffff8881c6083900 R12: 0000000006682516 R13: ffff8881c6095400 R14: 0000000000000016 R15: ffff8881c6b66680 FS: 0000000000000000(0000) GS:ffff88fef3c77000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007fc8bda40584 CR3: 000000807c812001 CR4: 0000000008772ef0 PKRU: 55555554 Call Trace: ? __netif_receive_skb_list_core+0x1ca/0x250 __bnxt_poll_work+0x152/0x280 bnxt_poll_p5+0x1cd/0x480 __napi_poll+0x30/0x180 net_rx_action+0x20b/0x3b0 ? note_gp_changes+0x53/0xe0 ? tick_setup_sched_timer+0x180/0x180 ? __napi_schedule+0x9a/0xb0 ? bnxt_msix+0x24/0x30 handle_softirqs+0xdd/0x2c0 __irq_exit_rcu.llvm.3171231171502365008+0x47/0xf0 common_interrupt+0x85/0x90 asm_common_interrupt+0x22/0x40 This stack trace is from a crash triggered when an out of bounds rx_tpa is dereferenced. The invalid write mentioned above is silent in this particular crash. Fix this by allocating rx_tpa with bp->max_tpa rounded up to the next power of 2 (bp->max_tpa_roundup_size) entries and masking the FW TPA ID with that size, so the wrapped ID can never index past the end of the array. Fixes: 54c28fab2fa5 ("bnxt_en: Set bp->max_tpa according to what the FW supports") Reported-by: Raphael Cardoso Fernandes Suggested-by: Michael Chan Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-7-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 27 ++++++++++++++--------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 32c59b3d1cbc..d7728d0c5b6e 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -1534,14 +1534,16 @@ static int bnxt_discard_rx(struct bnxt *bp, struct bnxt_cp_ring_info *cpr, return 0; } -static u16 bnxt_alloc_agg_idx(struct bnxt_rx_ring_info *rxr, u16 agg_id) +static u16 bnxt_alloc_agg_idx(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, + u16 agg_id) { struct bnxt_tpa_idx_map *map = rxr->rx_tpa_idx_map; - u16 idx = agg_id & MAX_TPA_P5_MASK; + u16 idx = agg_id & (bp->max_tpa_roundup_size - 1); if (test_bit(idx, map->agg_idx_bmap)) { - idx = find_first_zero_bit(map->agg_idx_bmap, MAX_TPA_P5); - if (idx >= MAX_TPA_P5) + idx = find_first_zero_bit(map->agg_idx_bmap, + bp->max_tpa_roundup_size); + if (idx >= bp->max_tpa_roundup_size) return INVALID_HW_RING_ID; } __set_bit(idx, map->agg_idx_bmap); @@ -1606,7 +1608,7 @@ static void bnxt_tpa_start(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, if (bp->flags & BNXT_FLAG_CHIP_P5_PLUS) { agg_id = TPA_START_AGG_ID_P5(tpa_start); - agg_id = bnxt_alloc_agg_idx(rxr, agg_id); + agg_id = bnxt_alloc_agg_idx(bp, rxr, agg_id); if (unlikely(agg_id == INVALID_HW_RING_ID)) { netdev_warn(bp->dev, "Unable to allocate agg ID for ring %d, agg 0x%x\n", rxr->bnapi->index, @@ -3604,7 +3606,7 @@ static void bnxt_free_one_tpa_info_data(struct bnxt *bp, { int i; - for (i = 0; i < bp->max_tpa; i++) { + for (i = 0; i < bp->max_tpa_roundup_size; i++) { struct bnxt_tpa_info *tpa_info = &rxr->rx_tpa[i]; u8 *data = tpa_info->data; @@ -3801,7 +3803,7 @@ static void bnxt_free_one_tpa_info(struct bnxt *bp, kfree(rxr->rx_tpa_idx_map); rxr->rx_tpa_idx_map = NULL; if (rxr->rx_tpa) { - for (i = 0; i < bp->max_tpa; i++) { + for (i = 0; i < bp->max_tpa_roundup_size; i++) { kfree(rxr->rx_tpa[i].agg_arr); rxr->rx_tpa[i].agg_arr = NULL; } @@ -3827,13 +3829,14 @@ static int bnxt_alloc_one_tpa_info(struct bnxt *bp, struct rx_agg_cmp *agg; int i; - rxr->rx_tpa = kzalloc_objs(struct bnxt_tpa_info, bp->max_tpa); + rxr->rx_tpa = kzalloc_objs(struct bnxt_tpa_info, + bp->max_tpa_roundup_size); if (!rxr->rx_tpa) return -ENOMEM; if (!(bp->flags & BNXT_FLAG_CHIP_P5_PLUS)) return 0; - for (i = 0; i < bp->max_tpa; i++) { + for (i = 0; i < bp->max_tpa_roundup_size; i++) { agg = kzalloc_objs(*agg, MAX_SKB_FRAGS); if (!agg) return -ENOMEM; @@ -3852,6 +3855,9 @@ static int bnxt_alloc_tpa_info(struct bnxt *bp) bp->max_tpa = MAX_TPA; if (bp->flags & BNXT_FLAG_CHIP_P5_PLUS) { + /* TPA is not supported at all, so there is nothing to + * allocate. + */ if (!bp->max_tpa_v2) return 0; bp->max_tpa = min_t(u16, bp->max_tpa_v2, MAX_TPA_P5); @@ -3859,6 +3865,7 @@ static int bnxt_alloc_tpa_info(struct bnxt *bp) if (bp->max_tpa <= 32 && BNXT_CHIP_P5(bp) && !BNXT_NPAR(bp)) bp->max_tpa = MAX_TPA_P5; } + bp->max_tpa_roundup_size = roundup_pow_of_two(bp->max_tpa); for (i = 0; i < bp->rx_nr_rings; i++) { struct bnxt_rx_ring_info *rxr = &bp->rx_ring[i]; @@ -4571,7 +4578,7 @@ static int bnxt_alloc_one_tpa_info_data(struct bnxt *bp, u8 *data; int i; - for (i = 0; i < bp->max_tpa; i++) { + for (i = 0; i < bp->max_tpa_roundup_size; i++) { data = __bnxt_alloc_rx_frag(bp, &mapping, rxr, GFP_KERNEL); if (!data) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index dc5a16ec5943..c673b2ce4a0d 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -789,7 +789,6 @@ struct nqe_cn { #define MAX_TPA 64 #define MAX_TPA_P5 256 -#define MAX_TPA_P5_MASK (MAX_TPA_P5 - 1) #define MAX_TPA_SEGS_P5 0x3f #if (BNXT_PAGE_SHIFT == 16) @@ -2381,6 +2380,7 @@ struct bnxt { u16 max_tpa_v2; u16 max_tpa; + u16 max_tpa_roundup_size; u32 rx_buf_size; u32 rx_buf_use_size; /* useable size */ u16 rx_offset; From 0523d5c52a450590bf5992bd6925394f3cc403e8 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 3 Sep 2026 12:36:51 +0000 Subject: [PATCH 0523/1317] net: macb: zero the link settings taprio reads back macb_taprio_setup_replace() calls phylink_ethtool_ksettings_get() with an uninitialised kset, and kset is not only an out-parameter. On a fixed link, or an in-band link with no PHY, phylink writes speed and duplex only if kset->base.rate_matching already reads RATE_MATCH_NONE, a field it never writes itself; in PHY mode before the PHY is attached it writes port and supported and nothing more. Either way the speed read back afterwards can be stack garbage. The ethtool core zeroes the structure on every path into the op, which is why its callers never see this; taprio is the only in-kernel caller passing its own variable. Fixes: 89934dbf169e ("net: macb: Add TAPRIO traffic scheduling support") Assisted-by: LLM Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260903123652.23900-2-f@lex.la Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 8469df0d89c3..9be28b4fddb8 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4300,9 +4300,9 @@ static int macb_taprio_setup_replace(struct net_device *netdev, u64 total_on_time = 0, start_time_sec = 0, start_time = conf->base_time; u32 configured_queues = 0, speed = 0, start_time_nsec; struct macb_queue_enst_config *enst_queue; - struct tc_taprio_sched_entry *entry; + struct ethtool_link_ksettings kset = {}; struct macb *bp = netdev_priv(netdev); - struct ethtool_link_ksettings kset; + struct tc_taprio_sched_entry *entry; struct macb_queue *queue; u32 queue_mask; u8 queue_id; From 2b6c0e25a3d713c4032e45f212bdd9e14c50f8a0 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 3 Sep 2026 12:36:52 +0000 Subject: [PATCH 0524/1317] net: macb: reject an unknown link speed in the taprio setup speed is a u32, so SPEED_UNKNOWN arrives as 0xffffffff and passes the "speed <= 0" check, which only ever catches zero. That is what an autonegotiating link reports while it is down: the limit derived from the speed collapses to a nanosecond at most and the first entry fails with a misleading "exceeds hardware limit". Zero stays covered, it is what an interface that was never opened reports, and enst_max_hw_interval() divides by it. Say which case it was in the error. Fixes: 89934dbf169e ("net: macb: Add TAPRIO traffic scheduling support") Assisted-by: LLM Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260903123652.23900-3-f@lex.la Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 9be28b4fddb8..0e75339fa206 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4329,8 +4329,8 @@ static int macb_taprio_setup_replace(struct net_device *netdev, } speed = kset.base.speed; - if (unlikely(speed <= 0)) { - netdev_err(netdev, "Invalid speed: %d\n", speed); + if (unlikely(speed == SPEED_UNKNOWN || !speed)) { + netdev_err(netdev, "Invalid speed %d, link-down?\n", speed); return -EINVAL; } From d3df7ed4683f8c1b35672a40bf20af6a08ef8ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Mon, 7 Sep 2026 12:36:08 +0200 Subject: [PATCH 0525/1317] landlock: Clean up ruleset validation checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit landlock_merge_ruleset() checks for a NULL ruleset after dereferencing it in lockdep_assert_held(). Move the assertion after the check so the defensive path remains effective. The mask-validation comment originated in landlock_add_fs_access_mask() to explain that its WARN_ON_ONCE() checked a caller invariant. It became self-referential when this helper and its network and scope counterparts were inlined into landlock_create_ruleset(). Restate the invariant without naming the caller. Keep both as defensive callee checks. Moving the assertion preserves the NULL check's ability to warn and return -EINVAL, while invalid masks remain warned about and masked. Reported-by: Günther Noack Closes: https://patch.msgid.link/aobYhIt3vcs2xN0b@google.com Closes: https://patch.msgid.link/aobasxUDQ8b7GYXl@google.com Reviewed-by: Günther Noack Link: https://patch.msgid.link/20260907103609.113325-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- security/landlock/domain.c | 3 ++- security/landlock/ruleset.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/security/landlock/domain.c b/security/landlock/domain.c index 93c7104fd6b2..4031b581be07 100644 --- a/security/landlock/domain.c +++ b/security/landlock/domain.c @@ -439,10 +439,11 @@ landlock_merge_ruleset(struct landlock_domain *const parent, int err; might_sleep(); - lockdep_assert_held(&ruleset->lock); if (WARN_ON_ONCE(!ruleset)) return ERR_PTR(-EINVAL); + lockdep_assert_held(&ruleset->lock); + if (parent) { if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS) return ERR_PTR(-E2BIG); diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 0d07707523cd..a5d135d085cb 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -58,7 +58,7 @@ landlock_create_ruleset(const access_mask_t fs_access_mask, new_ruleset->id = landlock_get_id_range(1); #endif /* CONFIG_TRACEPOINTS */ - /* Should already be checked in landlock_create_ruleset(). */ + /* The caller must only pass supported access rights and scopes. */ if (fs_access_mask) { const access_mask_t mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS; From 3125751cd1de76a01b18daef399d6b88d159bd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Mon, 7 Sep 2026 17:43:58 +0200 Subject: [PATCH 0526/1317] landlock: Bound escaped trace path output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filesystem paths may expand fourfold when trace text escapes spaces and other untrusted bytes. A sufficiently long representation can exhaust the shared scratch sequence. A sibling __print_flags() helper may then return an unterminated one-past pointer because TP_printk() argument ordering is unspecified. Use a fixed budget rather than the scratch space available at call time, so output does not vary with sibling evaluation order. Limit an untrusted string to three quarters of the trace sequence, leaving the rest for sibling helpers and final event metadata. Compute and commit complete escaped output transactionally so an exact fill cannot consume the terminating NUL or poison the scratch sequence. For strings that exceed the limit, retain the largest prefix ending at a complete escape unit, then append a raw UTF-8 ellipsis. Keep the helper's existing octal fallback so complete values remain unchanged. Hex fallback would consume the same four bytes per escaped byte without increasing the prefix or strengthening the marker. ESCAPE_NAP renders every non-ASCII input byte in octal, so legitimate data cannot reproduce the marker without being escaped. Cc: Günther Noack Link: https://patch.msgid.link/20260907154401.124362-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 70 +++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index f82588f6f90e..d05253afaf59 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -28,6 +28,16 @@ struct task_struct; #ifdef CREATE_TRACE_POINTS +/* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */ +#define TRACE_UNTRUSTED_STR_OUTPUT_SIZE \ + (TRACE_SEQ_BUFFER_SIZE - TRACE_SEQ_BUFFER_SIZE / 4) + +/* + * A raw UTF-8 ellipsis (…) marks truncation and cannot collide with escaped + * input: ESCAPE_NAP renders every non-ASCII input byte in octal. + */ +#define TRACE_TRUNCATION_MARKER "\xe2\x80\xa6" + /* * Escapes @len bytes of an untrusted string into the trace sequence @p so it * cannot inject field separators or control characters into the ftrace text @@ -37,33 +47,59 @@ struct task_struct; * NUL-terminated or carries embedded NUL bytes (an abstract socket name) is * escaped in full instead of being truncated at the first NUL. * - * Return: a pointer into @p's buffer, or NULL if @src is NULL or the buffer is - * exhausted (normal when the trace buffer is full). + * Strings that exceed the output limit retain the largest complete escaped + * prefix followed by the truncation marker. + * + * Return: a pointer into @p's buffer, or NULL if @src is NULL or the fixed + * output reservation is unavailable. */ static inline const char * __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len) { + const unsigned int escape_flags = ESCAPE_SPACE | ESCAPE_SPECIAL | + ESCAPE_NAP | ESCAPE_APPEND | + ESCAPE_OCTAL; + const size_t marker_len = sizeof(TRACE_TRUNCATION_MARKER) - 1; + size_t buf_size, prefix_len, prefix_size; int escaped_size; char *buf; - size_t buf_size = seq_buf_get_buf(&p->seq, &buf); - const char *ret = trace_seq_buffer_ptr(p); + const char *ret; - /* Buffer exhaustion is normal when the trace buffer is full. */ - if (!src || buf_size == 0) + buf_size = seq_buf_get_buf(&p->seq, &buf); + if (!src || buf_size < TRACE_UNTRUSTED_STR_OUTPUT_SIZE) return NULL; - escaped_size = - string_escape_mem(src, len, buf, buf_size, - ESCAPE_SPACE | ESCAPE_SPECIAL | ESCAPE_NAP | - ESCAPE_APPEND | ESCAPE_OCTAL, - " ='\"\\"); - if (unlikely(escaped_size >= buf_size)) { - /* We need some room for the final '\0'. */ - seq_buf_set_overflow(&p->seq); - p->full = 1; - return NULL; + ret = trace_seq_buffer_ptr(p); + escaped_size = string_escape_mem(src, len, buf, + TRACE_UNTRUSTED_STR_OUTPUT_SIZE, + escape_flags, " ='\"\\"); + if (likely(escaped_size < TRACE_UNTRUSTED_STR_OUTPUT_SIZE)) { + seq_buf_commit(&p->seq, escaped_size); + trace_seq_putc(p, 0); + return ret; } - seq_buf_commit(&p->seq, escaped_size); + + prefix_len = 0; + prefix_size = 0; + while (prefix_len < len) { + const char *const src_char = src + prefix_len; + int char_size; + + char_size = string_escape_mem(src_char, 1, NULL, 0, + escape_flags, " ='\"\\"); + if (char_size > TRACE_UNTRUSTED_STR_OUTPUT_SIZE - marker_len - + 1 - prefix_size) + break; + prefix_size += char_size; + prefix_len++; + } + + escaped_size = string_escape_mem(src, prefix_len, buf, prefix_size, + escape_flags, " ='\"\\"); + if (WARN_ON_ONCE(escaped_size != prefix_size)) + return NULL; + memcpy(buf + prefix_size, TRACE_TRUNCATION_MARKER, marker_len); + seq_buf_commit(&p->seq, prefix_size + marker_len); trace_seq_putc(p, 0); return ret; } From d41d0021a6ea3e9fcd14126a00fead47f981c46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Mon, 7 Sep 2026 17:43:59 +0200 Subject: [PATCH 0527/1317] landlock: Test trace path output boundaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use focused KUnit tests to exercise the renderer's internal boundary and composition contracts with synthetic scratch states, including both sibling-helper evaluation orders. Check the exact output and reservation boundaries, including a four-byte octal escape accepted at exact capacity and rejected one byte short. Also verify an unchanged cursor on failure, that bracketed process names and embedded NUL bytes remain data, and that input ellipsis bytes are escaped rather than mistaken for the raw truncation marker. The composition test requires generic trace output helpers. Enable CONFIG_FTRACE and CONFIG_SCHED_TRACER because the latter selects the otherwise-hidden CONFIG_TRACING support required by trace_print_flags_seq(). Use kselftests to exercise the complete tracefs path for both affected filesystem events. A valid path containing 2640 spaces exceeds the scratch output budget. Require its escaped prefix to end in the raw UTF-8 ellipsis while access_rights and blockers remain intact. This division keeps the exact safety contract compiler-independent while proving that real tracepoints preserve their surrounding symbolic fields. The end-to-end assertions fail after a full fix revert with both GCC and Clang, while the composition KUnit test fails if the scratch reserve is removed. Cc: Günther Noack Link: https://patch.msgid.link/20260907154401.124362-2-mic@digikod.net Signed-off-by: Mickaël Salaün --- security/landlock/.kunitconfig | 2 + security/landlock/trace.c | 182 ++++++++++++++++++ .../selftests/landlock/trace_fs_test.c | 160 +++++++++++++++ 3 files changed, 344 insertions(+) diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig index f9423f01ac5b..fe36228d37ea 100644 --- a/security/landlock/.kunitconfig +++ b/security/landlock/.kunitconfig @@ -1,6 +1,8 @@ CONFIG_AUDIT=y +CONFIG_FTRACE=y CONFIG_KUNIT=y CONFIG_NET=y +CONFIG_SCHED_TRACER=y CONFIG_SECURITY=y CONFIG_SECURITY_LANDLOCK=y CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 2ea7aac8d75d..8c21e5de6f0d 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -6,6 +6,7 @@ * Copyright © 2026 Cloudflare, Inc. */ +#include #include #include #include @@ -183,3 +184,184 @@ void landlock_trace_denial( break; } } + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_trace_seq_init(struct trace_seq *const seq, const size_t size) +{ + memset(seq, 0, sizeof(*seq)); + seq_buf_init(&seq->seq, seq->buffer, size); +} + +static void test_untrusted_str_data(struct kunit *const test) +{ + const char binary[] = { 'a', '\0', '<' }; + static const char ellipsis[] = "\xe2\x80\xa6"; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, "", 10); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, ""); + + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, binary, sizeof(binary)); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "a\\000<"); + + /* Input ellipsis bytes are escaped and cannot mimic the raw marker. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, ellipsis, + sizeof(ellipsis) - 1); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "\\342\\200\\246"); +} + +static void test_untrusted_str_boundaries(struct kunit *const test) +{ + static const char escaped_space[] = "\\040"; + const size_t output_size = TRACE_UNTRUSTED_STR_OUTPUT_SIZE; + const size_t marker_len = sizeof(TRACE_TRUNCATION_MARKER) - 1; + const size_t escape_len = sizeof(escaped_space) - 1; + const size_t exact_prefix_len = + output_size - marker_len - 1 - escape_len; + const size_t short_prefix_len = exact_prefix_len + 1; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const input = kunit_kmalloc(test, output_size + 1, GFP_KERNEL); + char *const expected = kunit_kmalloc(test, output_size, GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, input); + KUNIT_ASSERT_NOT_NULL(test, expected); + + /* The escaped string and its trailing NUL exactly fit the limit. */ + memset(input, 'a', output_size - 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, output_size - 1); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_EQ(test, seq->seq.len, output_size); + KUNIT_EXPECT_EQ(test, memcmp(output, input, output_size - 1), 0); + + /* Stop before a four-byte escape when only three bytes remain. */ + memset(input, 'a', short_prefix_len); + input[short_prefix_len] = ' '; + memset(input + short_prefix_len + 1, 'b', 5); + memset(expected, 'a', short_prefix_len); + memcpy(expected + short_prefix_len, TRACE_TRUNCATION_MARKER, + marker_len + 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, short_prefix_len + 6); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, expected); + + /* Include a four-byte escape that exactly fills the prefix capacity. */ + memset(input, 'a', exact_prefix_len); + input[exact_prefix_len] = ' '; + memset(input + exact_prefix_len + 1, 'b', marker_len + 1); + memset(expected, 'a', exact_prefix_len); + memcpy(expected + exact_prefix_len, escaped_space, escape_len); + memcpy(expected + exact_prefix_len + escape_len, + TRACE_TRUNCATION_MARKER, marker_len + 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, + exact_prefix_len + marker_len + 2); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, expected); + + /* Literal backslashes remain escaped in complete output. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, "/\\000", 5); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "/\\\\000"); +} + +static void test_untrusted_str_cursor(struct kunit *const test) +{ + const size_t padding_len = + TRACE_SEQ_BUFFER_SIZE - TRACE_UNTRUSTED_STR_OUTPUT_SIZE + 1; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const padding = kunit_kzalloc(test, padding_len, GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, padding); + + /* Accept available space exactly equal to the fixed reservation. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + trace_seq_putmem(seq, padding, padding_len - 1); + output = __trace_print_untrusted_str(seq, "/a", 2); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "/a"); + KUNIT_EXPECT_EQ(test, seq->seq.len, padding_len - 1 + sizeof("/a")); + + /* Reject one byte less without changing the scratch cursor. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + trace_seq_putmem(seq, padding, padding_len); + output = __trace_print_untrusted_str(seq, "/a", 2); + KUNIT_EXPECT_NULL(test, output); + KUNIT_EXPECT_EQ(test, seq->seq.len, padding_len); +} + +static void test_untrusted_str_composition(struct kunit *const test) +{ + static const struct trace_print_flags flags[] = { + { .mask = 1, .name = "read" }, + }; + const size_t output_size = TRACE_UNTRUSTED_STR_OUTPUT_SIZE; + const size_t prefix_len = output_size - sizeof(TRACE_TRUNCATION_MARKER); + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const expected = kunit_kmalloc(test, output_size, GFP_KERNEL); + char *const path = kunit_kmalloc(test, output_size, GFP_KERNEL); + const char *flags_output, *path_output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, expected); + KUNIT_ASSERT_NOT_NULL(test, path); + memset(path, 'a', output_size); + memset(expected, 'a', prefix_len); + memcpy(expected + prefix_len, TRACE_TRUNCATION_MARKER, + sizeof(TRACE_TRUNCATION_MARKER)); + + /* Exercise both legal TP_printk() sibling evaluation orders. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + path_output = __trace_print_untrusted_str(seq, path, output_size); + flags_output = + trace_print_flags_seq(seq, "|", 1, flags, ARRAY_SIZE(flags)); + KUNIT_ASSERT_NOT_NULL(test, path_output); + KUNIT_EXPECT_STREQ(test, path_output, expected); + KUNIT_EXPECT_STREQ(test, flags_output, "read"); + + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + flags_output = + trace_print_flags_seq(seq, "|", 1, flags, ARRAY_SIZE(flags)); + path_output = __trace_print_untrusted_str(seq, path, output_size); + KUNIT_ASSERT_NOT_NULL(test, path_output); + KUNIT_EXPECT_STREQ(test, path_output, expected); + KUNIT_EXPECT_STREQ(test, flags_output, "read"); +} + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_untrusted_str_data), + KUNIT_CASE(test_untrusted_str_boundaries), + KUNIT_CASE(test_untrusted_str_cursor), + KUNIT_CASE(test_untrusted_str_composition), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_trace", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c index 5220f6a4bee1..4543a25c1f55 100644 --- a/tools/testing/selftests/landlock/trace_fs_test.c +++ b/tools/testing/selftests/landlock/trace_fs_test.c @@ -6,8 +6,10 @@ */ #define _GNU_SOURCE +#include #include #include +#include #include #include #include @@ -23,6 +25,63 @@ #define TRACE_TASK "trace_fs_test" +/* Mirrors TRACE_SEQ_SIZE, conservatively larger than the usable buffer. */ +#define TRACE_SEQUENCE_SIZE 8192 +#define OCTAL_ESCAPE_LEN 4 +#define LONG_PATH_COMPONENT_COUNT 11 +#define LONG_PATH_COMPONENT_LEN 240 +#define LONG_PATH_LEN \ + (LONG_PATH_COMPONENT_COUNT * (LONG_PATH_COMPONENT_LEN + 1) + \ + sizeof("/tmp")) +#define LONG_ESCAPED_PATH_LEN \ + (LONG_PATH_COMPONENT_COUNT * LONG_PATH_COMPONENT_LEN * OCTAL_ESCAPE_LEN) + +static_assert(LONG_ESCAPED_PATH_LEN > TRACE_SEQUENCE_SIZE, + "escaped path must exceed the trace sequence"); +static_assert(LONG_PATH_LEN < PATH_MAX, "path must fit in PATH_MAX"); + +static void create_long_path(struct __test_metadata *const _metadata, + char *path) +{ + size_t path_len; + + strcpy(path, "/tmp"); + path_len = strlen(path); + + set_cap(_metadata, CAP_SYS_ADMIN); + ASSERT_EQ(0, mount("tmpfs", "/tmp", "tmpfs", 0, NULL)); + clear_cap(_metadata, CAP_SYS_ADMIN); + + for (int i = 0; i < LONG_PATH_COMPONENT_COUNT; i++) { + path[path_len++] = '/'; + memset(path + path_len, ' ', LONG_PATH_COMPONENT_LEN); + path_len += LONG_PATH_COMPONENT_LEN; + path[path_len] = '\0'; + ASSERT_EQ(0, mkdir(path, 0700)); + } +} + +static void expect_truncated_path(struct __test_metadata *const _metadata, + const char *const trace, + const char *const event_regex) +{ + static const char marker[] = "\xe2\x80\xa6"; + char *path; + size_t path_len; + + path = malloc(TRACE_SEQUENCE_SIZE); + ASSERT_NE(NULL, path); + ASSERT_EQ(0, tracefs_extract_field(trace, event_regex, "path", path, + TRACE_SEQUENCE_SIZE)); + EXPECT_EQ(path, strstr(path, "/tmp/")); + EXPECT_NE(NULL, strstr(path, "\\040")); + + path_len = strlen(path); + ASSERT_LE(sizeof(marker) - 1, path_len); + EXPECT_STREQ(marker, path + path_len - (sizeof(marker) - 1)); + free(path); +} + /* * Like REGEX_DENY_ACCESS_FS(), but pins the logged field to a specific value * ("0" or "1") so a test can tell a suppressed (quiet) denial from a logged @@ -183,6 +242,107 @@ TEST_F(trace_fs, add_rule_fs) free(buf); } +/* + * Verifies that a path whose escaping exceeds the trace scratch sequence does + * not corrupt a sibling symbolic field. + */ +TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) +{ + static const char access_prefix[] = "execute|write_file|read_file|"; + static const char access_suffix[] = "|ioctl_dev|resolve_unix"; + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, + }; + struct landlock_path_beneath_attr path_beneath = { + .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE, + }; + char path[PATH_MAX]; + char *buf, field_buf[256]; + size_t field_len; + int ruleset_fd, count; + + create_long_path(_metadata, path); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + path_beneath.parent_fd = open(path, O_PATH | O_DIRECTORY | O_CLOEXEC); + ASSERT_LE(0, path_beneath.parent_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath, 0)); + ASSERT_EQ(0, close(path_beneath.parent_fd)); + ASSERT_EQ(0, close(ruleset_fd)); + + buf = tracefs_read_buf(); + ASSERT_NE(NULL, buf); + + count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf); + } + + /* + * The marker catches a full revert with any compiler. The symbolic + * field also catches scratch-sequence poisoning when the compiler + * evaluates the overflowing path first, as GCC currently does. + */ + ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), + "access_rights", field_buf, + sizeof(field_buf))); + EXPECT_EQ(0, + strncmp(field_buf, access_prefix, sizeof(access_prefix) - 1)); + EXPECT_EQ(NULL, strstr(field_buf, "|refer|")); + field_len = strlen(field_buf); + ASSERT_LE(sizeof(access_suffix) - 1, field_len); + EXPECT_STREQ(access_suffix, + field_buf + field_len - (sizeof(access_suffix) - 1)); + expect_truncated_path(_metadata, buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + + free(buf); +} + +/* + * Verifies that an overflowing denied path does not corrupt its sibling + * symbolic blockers field. + */ +TEST_F(trace_fs, deny_access_fs_escaped_path_overflow) +{ + char path[PATH_MAX]; + char *buf, field_buf[64]; + int count, err; + + create_long_path(_metadata, path); + ASSERT_EQ(0, tracefs_clear_buf()); + + sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR, + LANDLOCK_ACCESS_FS_READ_DIR, path); + + buf = tracefs_read_buf(); + ASSERT_NE(NULL, buf); + + count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 deny_access_fs event, got %d\n%s", count, + buf); + } + + /* + * The marker catches a full revert with any compiler. The symbolic + * field also catches scratch-sequence poisoning when the compiler + * evaluates the overflowing path first, as GCC currently does. + */ + err = tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), + "blockers", field_buf, sizeof(field_buf)); + ASSERT_EQ(0, err); + EXPECT_STREQ("read_dir", field_buf); + expect_truncated_path(_metadata, buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)); + + free(buf); +} + /* * Verifies that an allowed access emits check_rule events (rule matched during * pathwalk) but does NOT emit deny_access events (no denial). From a0de06d0da78a3db53de65dfd7452cc6d111f703 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:29 +0200 Subject: [PATCH 0528/1317] net: ethernet: cortina: Fix budget accounting The gmac_rx() function returns the remaining NAPI budget, but its caller treats the return value as the number of packets received. An idle poll therefore reports a full budget and remains scheduled. Return the number of received packets instead. Preserve the existing free queue refill accounting by adding that count directly; continuing to subtract it from the budget would invert the refill behavior. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Link: https://lore.kernel.org/r/20260509-gemini-ethernet-fixes-v1-4-6c5d20ddc35b@kernel.org Link: https://lore.kernel.org/r/20260512131456.189452-1-pabeni@redhat.com Assisted-by: LLM Reviewed-by: Joe Damato Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-1-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 4c762229ce42..1d9824d1716c 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1450,6 +1450,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) unsigned int frame_len, frag_len; struct gmac_rxdesc *rx = NULL; struct gmac_queue_page *gpage; + unsigned int received = 0; union gmac_rxdesc_0 word0; union gmac_rxdesc_1 word1; union gmac_rxdesc_3 word3; @@ -1545,7 +1546,8 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) napi_gro_frags(&port->napi); skb = NULL; frag_nr = 0; - --budget; + budget--; + received++; } continue; @@ -1565,7 +1567,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) port->rx_skb = skb; port->rx_frag_nr = frag_nr; writew(r, ptr_reg); - return budget; + return received; } static int gmac_napi_poll(struct napi_struct *napi, int budget) @@ -1586,7 +1588,7 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget) ++port->rx_napi_exits; } - port->freeq_refill += (budget - received); + port->freeq_refill += received; if (port->freeq_refill > freeq_threshold) { port->freeq_refill -= freeq_threshold; geth_fill_freeq(geth, true); From baa26841cb9a2cdc7e0e99d6854a4e3359bf7393 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:30 +0200 Subject: [PATCH 0529/1317] net: ethernet: cortina: Finish RX updates before NAPI completion napi_complete_done() releases ownership of the NAPI instance, but the Gemini poll keeps the RX statistics writer section open and updates the free queue after calling it. A new poll can therefore start while the old writer is still active. Finish the statistics and free queue updates before releasing ownership. Only re-enable RX interrupts when napi_complete_done() reports successful completion. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Suggested-by: Joe Damato Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-2-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 1d9824d1716c..6502220362cb 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1581,12 +1581,10 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget) u64_stats_update_begin(&port->rx_stats_syncp); received = gmac_rx(napi->dev, budget); - if (received < budget) { - napi_gro_flush(napi, false); - napi_complete_done(napi, received); - gmac_enable_rx_irq(napi->dev, 1); + if (received < budget) ++port->rx_napi_exits; - } + + u64_stats_update_end(&port->rx_stats_syncp); port->freeq_refill += received; if (port->freeq_refill > freeq_threshold) { @@ -1594,7 +1592,9 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget) geth_fill_freeq(geth, true); } - u64_stats_update_end(&port->rx_stats_syncp); + if (received < budget && napi_complete_done(napi, received)) + gmac_enable_rx_irq(napi->dev, 1); + return received; } From b856c552f556bc0341c1dbe0bf88e630fd1dc4b7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:31 +0200 Subject: [PATCH 0530/1317] net: ethernet: cortina: Count dropped frames as NAPI work The RX loop only consumes budget when it successfully delivers a frame. Error paths keep consuming descriptors without reducing the budget, so a stream of bad frames can process the entire receive ring in one poll. Move the budget accounting to a common end-of-frame path. This counts each completed frame as NAPI work whether it was delivered or dropped, matching the behavior of the vendor driver. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-3-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 6502220362cb..33e9763b32fe 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1501,7 +1501,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) skb = NULL; frag_nr = 0; } - continue; + goto next_desc; } page = gpage->page; @@ -1523,7 +1523,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) } else if (!skb) { put_page(page); - continue; + goto next_desc; } if (word3.bits32 & EOF_BIT) @@ -1546,10 +1546,8 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) napi_gro_frags(&port->napi); skb = NULL; frag_nr = 0; - budget--; - received++; } - continue; + goto next_desc; err_drop: if (skb) { @@ -1562,6 +1560,13 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) put_page(page); port->stats.rx_dropped++; + +next_desc: + /* Final or single-descriptor fragment, advance things */ + if (word3.bits32 & EOF_BIT) { + budget--; + received++; + } } port->rx_skb = skb; From 6520198c430c81bcc367f0dd5e32f2fb740b9d51 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:32 +0200 Subject: [PATCH 0531/1317] net: ethernet: cortina: Count RX drops once per frame The absence of a partial skb means either that the driver is not assembling a frame or that the current frame was already dropped. Consequently, repeated descriptor errors can increment rx_dropped more than once, while an orphaned descriptor chain can reach EOF without being counted at all. Track the dropping state across NAPI polls. Clear it at frame boundaries and route mapping failures and orphaned continuations through the common drop path so each discarded frame is counted exactly once. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Reported-by: Joe Damato Closes: https://lore.kernel.org/netdev/apdK5aMmvYssz35F@devvm20253.cco0.facebook.com/ Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-4-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 41 +++++++++++++++------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 33e9763b32fe..9ba8524fa371 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -124,6 +124,7 @@ struct gemini_ethernet_port { unsigned int rx_coalesce_nsecs; struct sk_buff *rx_skb; unsigned int rx_frag_nr; + bool rx_dropping; unsigned int freeq_refill; struct gmac_txq txq[TX_QUEUE_NUM]; @@ -1451,6 +1452,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) struct gmac_rxdesc *rx = NULL; struct gmac_queue_page *gpage; unsigned int received = 0; + bool dropping = port->rx_dropping; union gmac_rxdesc_0 word0; union gmac_rxdesc_1 word1; union gmac_rxdesc_3 word3; @@ -1472,6 +1474,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) w = rw.bits.wptr; while (budget && w != r) { + page = NULL; rx = port->rxq_ring + r; word0 = rx->word0; word1 = rx->word1; @@ -1485,6 +1488,16 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) frame_len = word1.bits.byte_count; page_offs = mapping & ~PAGE_MASK; + if (word3.bits32 & SOF_BIT) { + if (skb) { + napi_free_frags(&port->napi); + port->stats.rx_dropped++; + skb = NULL; + frag_nr = 0; + } + dropping = false; + } + if (!mapping) { netdev_err(netdev, "rxq[%u]: HW BUG: zero DMA desc\n", r); @@ -1495,24 +1508,11 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) gpage = gmac_get_queue_page(geth, port, mapping + PAGE_SIZE); if (!gpage) { dev_err(geth->dev, "could not find mapping\n"); - port->stats.rx_dropped++; - if (skb) { - napi_free_frags(&port->napi); - skb = NULL; - frag_nr = 0; - } - goto next_desc; + goto err_drop; } page = gpage->page; if (word3.bits32 & SOF_BIT) { - if (skb) { - napi_free_frags(&port->napi); - port->stats.rx_dropped++; - skb = NULL; - frag_nr = 0; - } - skb = gmac_skb_if_good_frame(port, word0, frame_len); if (!skb) goto err_drop; @@ -1522,8 +1522,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) frag_nr = 0; } else if (!skb) { - put_page(page); - goto next_desc; + goto err_drop; } if (word3.bits32 & EOF_BIT) @@ -1556,21 +1555,26 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) frag_nr = 0; } - if (mapping) + if (page) put_page(page); - port->stats.rx_dropped++; + if (!dropping) { + port->stats.rx_dropped++; + dropping = true; + } next_desc: /* Final or single-descriptor fragment, advance things */ if (word3.bits32 & EOF_BIT) { budget--; received++; + dropping = false; } } port->rx_skb = skb; port->rx_frag_nr = frag_nr; + port->rx_dropping = dropping; writew(r, ptr_reg); return received; } @@ -1900,6 +1904,7 @@ static int gmac_stop(struct net_device *netdev) napi_disable(&port->napi); port->rx_skb = NULL; port->rx_frag_nr = 0; + port->rx_dropping = false; gmac_enable_irq(netdev, 0); gmac_cleanup_rxq(netdev); From e89e88ad41d9f31c829c2af39c48313e8e48d5b0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:33 +0200 Subject: [PATCH 0532/1317] net: ethernet: cortina: Count RX descriptors for freeq refill The software free queue provides one buffer fragment for every descriptor moved to an RX queue. The refill heuristic instead advances by NAPI work, which counts frames. A fragmented or discarded frame can consume several queue entries while adding only one to the refill count. Count the RX descriptors as they are consumed and report that separately from NAPI work. Use the descriptor count to drive free queue refills. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Assisted-by: LLM Reviewed-by: Joe Damato Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-5-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 9ba8524fa371..f08de623e6f7 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1440,7 +1440,8 @@ static struct sk_buff *gmac_skb_if_good_frame(struct gemini_ethernet_port *port, return skb; } -static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) +static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget, + unsigned int *freeq_consumed) { struct gemini_ethernet_port *port = netdev_priv(netdev); unsigned short m = (1 << port->rxq_order) - 1; @@ -1448,6 +1449,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) void __iomem *ptr_reg = port->rxq_rwptr; unsigned int frag_nr = port->rx_frag_nr; struct sk_buff *skb = port->rx_skb; + unsigned int consumed = 0; unsigned int frame_len, frag_len; struct gmac_rxdesc *rx = NULL; struct gmac_queue_page *gpage; @@ -1483,6 +1485,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) r++; r &= m; + consumed++; frag_len = word0.bits.buffer_size; frame_len = word1.bits.byte_count; @@ -1575,6 +1578,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) port->rx_skb = skb; port->rx_frag_nr = frag_nr; port->rx_dropping = dropping; + *freeq_consumed = consumed; writew(r, ptr_reg); return received; } @@ -1584,18 +1588,19 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget) struct gemini_ethernet_port *port = netdev_priv(napi->dev); struct gemini_ethernet *geth = port->geth; unsigned int freeq_threshold; + unsigned int freeq_consumed; unsigned int received; freeq_threshold = 1 << (geth->freeq_order - 1); u64_stats_update_begin(&port->rx_stats_syncp); - received = gmac_rx(napi->dev, budget); + received = gmac_rx(napi->dev, budget, &freeq_consumed); if (received < budget) ++port->rx_napi_exits; u64_stats_update_end(&port->rx_stats_syncp); - port->freeq_refill += received; + port->freeq_refill += freeq_consumed; if (port->freeq_refill > freeq_threshold) { port->freeq_refill -= freeq_threshold; geth_fill_freeq(geth, true); From a7783e585360ee05dfe21d3173dbbe985c94f29e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:01 +0200 Subject: [PATCH 0533/1317] wifi: cfg80211: don't get the radio mask for netdev-less wdevs cfg80211_calculate_bi_data() calls rdev_get_radio_mask() with wdev->netdev, which can be NULL and then crashes in mac80211. To avoid that, invert the order of checks since wdev->netdev is always valid for beaconing interfaces. Assisted-by: LLM Fixes: abb4cfe3661a ("wifi: cfg80211: extend interface combination check for multi-radio") Reported-by: syzbot+abff43d2d045e37c0bb2@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=abff43d2d045e37c0bb2 Link: https://patch.msgid.link/20260904165614.2056a8b7dc91.I7412c5062d8166ad6c81ee7252cec49dea19a60f@changeid Signed-off-by: Johannes Berg --- net/wireless/util.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index 3e584d0ca3e2..408ebb10924f 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -2477,16 +2477,15 @@ static void cfg80211_calculate_bi_data(struct wiphy *wiphy, u32 new_beacon_int, if (wdev->valid_links) continue; + wdev_bi = cfg80211_wdev_bi(wdev); + if (!wdev_bi) + continue; + /* skip wdevs not active on the given wiphy radio */ if (radio_idx >= 0 && !(rdev_get_radio_mask(rdev, wdev->netdev) & BIT(radio_idx))) continue; - wdev_bi = cfg80211_wdev_bi(wdev); - - if (!wdev_bi) - continue; - if (!*beacon_int_gcd) { *beacon_int_gcd = wdev_bi; continue; From 48b2c5c628b09cf36cbeca53e0432fc2a7518be7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:02 +0200 Subject: [PATCH 0534/1317] wifi: cfg80211: check IP header size in cfg80211_classify8021d() A frame that looks like IP can be transmitted, but be too short, so the DS field is read incorrectly: BUG: KMSAN: uninit-value in cfg80211_classify8021d+0x99d/0x12b0 net/wireless/util.c:1027 cfg80211_classify8021d+0x99d/0x12b0 net/wireless/util.c:1027 ieee80211_select_queue+0x37a/0x9e0 net/mac80211/wme.c:180 __ieee80211_subif_start_xmit+0x60f/0x1d90 net/mac80211/tx.c:4304 ieee80211_subif_start_xmit+0xa8/0x6d0 net/mac80211/tx.c:4538 ... packet_sendmsg+0x9173/0xa2a0 net/packet/af_packet.c:3108 Use skb_header_pointer() like the MPLS case. Assisted-by: LLM Fixes: e31a16d6f64e ("wireless: move some utility functions from mac80211 to cfg80211") Reported-by: syzbot+878ddc3962f792e9af59@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=878ddc3962f792e9af59 Link: https://patch.msgid.link/20260904165614.5e61a4c80b92.I37d68d3f406cb3b90b32e6943418d66070b65197@changeid Signed-off-by: Johannes Berg --- net/wireless/util.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index 408ebb10924f..5429cf3cfd2f 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1039,12 +1039,30 @@ unsigned int cfg80211_classify8021d(struct sk_buff *skb, } switch (skb->protocol) { - case htons(ETH_P_IP): - dscp = ipv4_get_dsfield(ip_hdr(skb)) & 0xfc; + case htons(ETH_P_IP): { + const struct iphdr *iph; + struct iphdr _iph; + + iph = skb_header_pointer(skb, sizeof(struct ethhdr), + sizeof(*iph), &_iph); + if (!iph) + return 0; + + dscp = ipv4_get_dsfield(iph) & 0xfc; break; - case htons(ETH_P_IPV6): - dscp = ipv6_get_dsfield(ipv6_hdr(skb)) & 0xfc; + } + case htons(ETH_P_IPV6): { + const struct ipv6hdr *ip6h; + struct ipv6hdr _ip6h; + + ip6h = skb_header_pointer(skb, sizeof(struct ethhdr), + sizeof(*ip6h), &_ip6h); + if (!ip6h) + return 0; + + dscp = ipv6_get_dsfield(ip6h) & 0xfc; break; + } case htons(ETH_P_MPLS_UC): case htons(ETH_P_MPLS_MC): { struct mpls_label mpls_tmp, *mpls; From 28cc4d5a75bb07d0eb2fa178db355b04483f5aca Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Tue, 8 Sep 2026 13:59:41 +0800 Subject: [PATCH 0535/1317] drm/sched: Create a fake device for KUnit tests The DRM scheduler KUnit tests pass NULL for the dev field in drm_sched_init_args, which NULL-pointer dereferences in the drm_sched_job trace event via dev_name() on sched->dev. Give the mock scheduler a device with kunit_device_register(), which is also cleaned up at test exit. A per-function counter keeps the device names unique, since some tests create several mock schedulers. Fixes: 5a99350794fe ("drm/sched: Add scheduler unit testing infrastructure and some basic tests") Cc: stable@vger.kernel.org Signed-off-by: Shixiong Ou Acked-by: Maxime Ripard [phasta: removed static variable init to 0 again] Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260908055941.351486-1-oushixiong1025@163.com --- drivers/gpu/drm/scheduler/tests/mock_scheduler.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/gpu/drm/scheduler/tests/mock_scheduler.c b/drivers/gpu/drm/scheduler/tests/mock_scheduler.c index 8e9ae7d980eb..2dfa3efef210 100644 --- a/drivers/gpu/drm/scheduler/tests/mock_scheduler.c +++ b/drivers/gpu/drm/scheduler/tests/mock_scheduler.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2025 Valve Corporation */ +#include + #include "sched_tests.h" /* @@ -288,6 +290,7 @@ static const struct drm_sched_backend_ops drm_mock_scheduler_ops = { */ struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test, long timeout) { + static unsigned int instance; struct drm_sched_init_args args = { .ops = &drm_mock_scheduler_ops, .num_rqs = DRM_SCHED_PRIORITY_COUNT, @@ -297,11 +300,19 @@ struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test, long timeout) .name = "drm-mock-scheduler", }; struct drm_mock_scheduler *sched; + struct device *dev; + char name[64]; int ret; sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sched); + snprintf(name, sizeof(name), "%s-%u", args.name, instance++); + dev = kunit_device_register(test, name); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + args.dev = dev; + ret = drm_sched_init(&sched->base, &args); KUNIT_ASSERT_EQ(test, ret, 0); From 313f798f52a5e46b0e419d05f977d40aaeac4106 Mon Sep 17 00:00:00 2001 From: Qinyun Tan Date: Tue, 1 Sep 2026 16:32:31 +0800 Subject: [PATCH 0536/1317] drm/ast: create blend mode property on cursor plane Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. The ast cursor plane (ARGB4444, ARGB8888) trips this on driver load: [PLANE:37:plane-1] pixel format with alpha exposed but blend mode not setup WARNING: drivers/gpu/drm/drm_mode_config.c:872 at drm_mode_config_validate+0x48f/0x510 [drm] ... Call Trace: drm_dev_register+0x1ce/0x290 [drm] ast_pci_probe+0x19d/0x3f0 [ast] local_pci_probe+0x41/0x90 Per Thomas Zimmermann's review, the ASPEED documentation describes the hardware cursor as blending with straight (non-pre-multiplied) alpha, which corresponds to DRM_MODE_BLEND_COVERAGE. Expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_COVERAGE to make the hardware semantics explicit and silence the warning. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Suggested-by: Thomas Zimmermann Reviewed-by: Thomas Zimmermann Tested-by: Thomas Zimmermann Signed-off-by: Qinyun Tan Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901083234.1828755-2-qinyuntan@linux.alibaba.com --- drivers/gpu/drm/ast/ast_cursor.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/ast/ast_cursor.c b/drivers/gpu/drm/ast/ast_cursor.c index fd19c45f2abe..690d4cd1db5e 100644 --- a/drivers/gpu/drm/ast/ast_cursor.c +++ b/drivers/gpu/drm/ast/ast_cursor.c @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -355,6 +356,8 @@ int ast_cursor_plane_init(struct ast_device *ast) } drm_plane_helper_add(cursor_plane, &ast_cursor_plane_helper_funcs); drm_plane_enable_fb_damage_clips(cursor_plane); + drm_plane_create_blend_mode_property(cursor_plane, + BIT(DRM_MODE_BLEND_COVERAGE)); return 0; } From b67f408a0c2427d5d7c682cfbdad87c4ff73265b Mon Sep 17 00:00:00 2001 From: Qinyun Tan Date: Tue, 1 Sep 2026 16:32:32 +0800 Subject: [PATCH 0537/1317] drm/qxl: create blend mode property on primary and cursor planes Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. Both the qxl primary and cursor planes expose ARGB8888 and trip this on driver load. qxl submits cursors as SPICE_CURSOR_TYPE_ALPHA, which the SPICE protocol explicitly defines as a "pre-multiplied ARGB8888 pixmap" (Spice Protocol, "Cursor channel definition" section [1]). This matches the blend mode userspace has always assumed when the property is not attached. Expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_PREMULTI to make these semantics explicit and silence the warning. The primary plane is the bottom-most plane so its blend mode has no visible effect; advertise the same value there for consistency. No functional change. [1] https://www.spice-space.org/spice-protocol.html Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Reviewed-by: Thomas Zimmermann Signed-off-by: Qinyun Tan Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901083234.1828755-3-qinyuntan@linux.alibaba.com --- drivers/gpu/drm/qxl/qxl_display.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c index 2fc41fb90aaa..0719fc6a52d5 100644 --- a/drivers/gpu/drm/qxl/qxl_display.c +++ b/drivers/gpu/drm/qxl/qxl_display.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -993,6 +994,9 @@ static struct drm_plane *qxl_create_plane(struct qxl_device *qdev, drm_plane_helper_add(plane, helper_funcs); + drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PREMULTI)); + return plane; free_plane: From f2e64f450c1665732505dae8ee34b399da5a5100 Mon Sep 17 00:00:00 2001 From: Qinyun Tan Date: Tue, 1 Sep 2026 16:32:33 +0800 Subject: [PATCH 0538/1317] drm/virtio: create blend mode property on cursor plane Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. The virtio-gpu cursor plane (HOST_ARGB8888) trips this. The virtio-gpu specification does not define the cursor alpha semantics. The host forwards the cursor pixels verbatim to its display frontends, and the remote cursor protocols among them (SPICE alpha cursors, the VNC "Cursor With Alpha" encoding) both define pre-multiplied alpha, matching what userspace has always assumed when the property is not attached. Expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_PREMULTI to make these semantics explicit and silence the warning. The primary plane only exposes HOST_XRGB8888, so the call is gated to the cursor. No functional change. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Reviewed-by: Thomas Zimmermann Signed-off-by: Qinyun Tan Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901083234.1828755-4-qinyuntan@linux.alibaba.com --- drivers/gpu/drm/virtio/virtgpu_plane.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index 1d1b27ece62a..640815af4098 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -609,6 +610,9 @@ struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev, if (type == DRM_PLANE_TYPE_PRIMARY) drm_plane_enable_fb_damage_clips(plane); + else if (type == DRM_PLANE_TYPE_CURSOR) + drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PREMULTI)); return plane; } From 09f7fc4e8e307fcdaa506b3a06cd7b1acffd2584 Mon Sep 17 00:00:00 2001 From: Qinyun Tan Date: Tue, 1 Sep 2026 16:32:34 +0800 Subject: [PATCH 0539/1317] drm/vboxvideo: create blend mode property on planes Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. Both the vboxvideo primary and cursor planes expose ARGB8888 and trip this on driver load. VirtualBox draws the cursor through the host windowing system, which treats the guest-supplied pointer shape as straight (non-pre-multiplied) alpha: the host frontend loads the pixels verbatim into an unpremultiplied ARGB image before handing them to the host cursor APIs. This corresponds to DRM_MODE_BLEND_COVERAGE. Expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_COVERAGE to make these semantics explicit and silence the warning. The primary plane's alpha channel is ignored by the host (opaque blit) and it is the bottom-most plane anyway; advertise the same value there for consistency. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Acked-by: Thomas Zimmermann Signed-off-by: Qinyun Tan Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901083234.1828755-5-qinyuntan@linux.alibaba.com --- drivers/gpu/drm/vboxvideo/vbox_mode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/vboxvideo/vbox_mode.c b/drivers/gpu/drm/vboxvideo/vbox_mode.c index 8e4e5fc9d3c5..3c41238a8268 100644 --- a/drivers/gpu/drm/vboxvideo/vbox_mode.c +++ b/drivers/gpu/drm/vboxvideo/vbox_mode.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -540,6 +541,9 @@ static struct drm_plane *vbox_create_plane(struct vbox_private *vbox, drm_plane_helper_add(plane, helper_funcs); + drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_COVERAGE)); + return plane; free_plane: From fedf002d7d08bee36693aacd1ade2ba39351ea91 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 10:04:26 +0200 Subject: [PATCH 0540/1317] drm/adp: Drop the select of the nonexistent CONFIG_DRM_KMS_DMA_HELPER There is no Kconfig symbol CONFIG_DRM_KMS_DMA_HELPER. The former CONFIG_DRM_KMS_CMA_HELPER was removed by commit 09717af7d13d ("drm: Remove CONFIG_DRM_KMS_CMA_HELPER option") before this driver was added, so the select does nothing. The driver already selects CONFIG_DRM_GEM_DMA_HELPER, which is what it needs. Remove the dead line. Fixes: 332122eba628 ("drm: adp: Add Apple Display Pipe driver") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260905080426.34224-1-kmehltretter@gmail.com --- drivers/gpu/drm/adp/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/adp/Kconfig b/drivers/gpu/drm/adp/Kconfig index 9fcc27eb200d..acfa21ee06d2 100644 --- a/drivers/gpu/drm/adp/Kconfig +++ b/drivers/gpu/drm/adp/Kconfig @@ -6,7 +6,6 @@ config DRM_ADP select DRM_KMS_HELPER select DRM_BRIDGE_CONNECTOR select DRM_DISPLAY_HELPER - select DRM_KMS_DMA_HELPER select DRM_GEM_DMA_HELPER select DRM_PANEL_BRIDGE select VIDEOMODE_HELPERS From f97802dd98b27e45c04293f9926f07642578b23f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 10:03:44 +0200 Subject: [PATCH 0541/1317] drm/logicvc: Drop the select of the nonexistent CONFIG_DRM_KMS_DMA_HELPER CONFIG_DRM_KMS_CMA_HELPER was removed by commit 09717af7d13d ("drm: Remove CONFIG_DRM_KMS_CMA_HELPER option"). When commit 6bcfe8eaeef0 ("drm/fb: rename FB CMA helpers to FB DMA helpers") later renamed the select in this Kconfig to CONFIG_DRM_KMS_DMA_HELPER, no symbol of that name existed, and git log -S finds no Kconfig file that has defined one since. The select is silently ignored. The driver already selects CONFIG_DRM_GEM_DMA_HELPER, which is what it needs. Remove the dead line. Fixes: 6bcfe8eaeef0 ("drm/fb: rename FB CMA helpers to FB DMA helpers") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260905080344.34077-1-kmehltretter@gmail.com --- drivers/gpu/drm/logicvc/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/logicvc/Kconfig b/drivers/gpu/drm/logicvc/Kconfig index 579a358ed5cf..11aae1626199 100644 --- a/drivers/gpu/drm/logicvc/Kconfig +++ b/drivers/gpu/drm/logicvc/Kconfig @@ -4,7 +4,6 @@ config DRM_LOGICVC depends on OF || COMPILE_TEST select DRM_CLIENT_SELECTION select DRM_KMS_HELPER - select DRM_KMS_DMA_HELPER select DRM_GEM_DMA_HELPER select REGMAP select REGMAP_MMIO From aba7b41faeecb7692458095ce6fafc341fe0b80e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 1 Sep 2026 11:10:19 +0800 Subject: [PATCH 0542/1317] soundwire: cadence_master: wait and cancel cdns->work before clock stop A peripheral event could happen during the clock stop process. We need to wait for the event be handled before stopping the bus clock. Otherwise, we will get the IO transfer timed out issue. Fixes: af4cc917826f ("soundwire: cadence: mask Slave interrupt before stopping clock") Signed-off-by: Bard Liao Reviewed-by: David Lin Reviewed-by: Shuming Fan Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260901031019.233254-1-yung-chuan.liao@linux.intel.com Signed-off-by: Vinod Koul --- drivers/soundwire/cadence_master.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index e690237fe981..a0a63b76793e 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -1701,6 +1701,13 @@ int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake) return 0; } + /* + * wait for any in-flight peripheral event handling to complete before stopping the clock. + * No need to disable peripheral interrupts before canceling the work, as the peripheral + * interrupts are already masked before the work is scheduled. + */ + cancel_work_sync(&cdns->work); + /* * Before entering clock stop we mask the Slave * interrupts. This helps avoid having to deal with e.g. a From 4eef4ab3aa3a32725e5bc79032c722f9f4a90172 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 1 Sep 2026 14:33:18 +0200 Subject: [PATCH 0543/1317] s390/pai: Use PAI PMU index as parameter replacing event Use PAI PMU index value as function argument instead of pointer to struct perf_event. Only that index value is used inside functions pai_alloc_cpu() and pai_event_destroy_cpu(). No functional change. Signed-off-by: Thomas Richter Reviewed-by: Sumanth Korikkar Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/perf_pai.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/arch/s390/kernel/perf_pai.c b/arch/s390/kernel/perf_pai.c index 5c18c8b82ab7..109b15227d8f 100644 --- a/arch/s390/kernel/perf_pai.c +++ b/arch/s390/kernel/perf_pai.c @@ -140,16 +140,14 @@ static void pai_free(struct pai_mapptr *mp) /* Adjust usage counters and remove allocated memory when all users are * gone. */ -static void pai_event_destroy_cpu(struct perf_event *event, int cpu) +static void pai_event_destroy_cpu(int idx, int cpu) { - int idx = PAI_PMU_IDX(event); struct pai_mapptr *mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); struct pai_map *cpump = mp->mapptr; mutex_lock(&pai_reserve_mutex); - debug_sprintf_event(paidbg, 5, "%s event %#llx idx %d cpu %d users %d " - "refcnt %u\n", __func__, event->attr.config, idx, - event->cpu, cpump->active_events, + debug_sprintf_event(paidbg, 5, "%s users %d refcnt %u\n", + __func__, cpump->active_events, refcount_read(&cpump->refcnt)); if (refcount_dec_and_test(&cpump->refcnt)) pai_free(mp); @@ -159,17 +157,17 @@ static void pai_event_destroy_cpu(struct perf_event *event, int cpu) static void pai_event_destroy(struct perf_event *event) { - int cpu; + int cpu = 0, idx = PAI_PMU_IDX(event); free_page(PAI_SAVE_AREA(event)); if (event->cpu == -1) { struct cpumask *mask = PAI_CPU_MASK(event); for_each_cpu(cpu, mask) - pai_event_destroy_cpu(event, cpu); + pai_event_destroy_cpu(idx, cpu); kfree(mask); } else { - pai_event_destroy_cpu(event, event->cpu); + pai_event_destroy_cpu(idx, event->cpu); } } @@ -241,12 +239,12 @@ static u64 paicrypt_getall(struct perf_event *event) * * Allocate the memory for the event. */ -static int pai_alloc_cpu(struct perf_event *event, int cpu) +static int pai_alloc_cpu(int idx, int cpu) { - int rc, idx = PAI_PMU_IDX(event); struct pai_map *cpump = NULL; bool need_paiext_cb = false; struct pai_mapptr *mp; + int rc; mutex_lock(&pai_reserve_mutex); /* Allocate root node */ @@ -318,6 +316,7 @@ static int pai_alloc_cpu(struct perf_event *event, int cpu) static int pai_alloc(struct perf_event *event) { + int idx = PAI_PMU_IDX(event); struct cpumask *maskptr; int cpu, rc = -ENOMEM; @@ -326,10 +325,10 @@ static int pai_alloc(struct perf_event *event) goto out; for_each_online_cpu(cpu) { - rc = pai_alloc_cpu(event, cpu); + rc = pai_alloc_cpu(idx, cpu); if (rc) { for_each_cpu(cpu, maskptr) - pai_event_destroy_cpu(event, cpu); + pai_event_destroy_cpu(idx, cpu); kfree(maskptr); goto out; } @@ -392,7 +391,7 @@ static int pai_event_init(struct perf_event *event, int idx) } if (event->cpu >= 0) - rc = pai_alloc_cpu(event, event->cpu); + rc = pai_alloc_cpu(idx, event->cpu); else rc = pai_alloc(event); if (rc) { From e8df39dacb7d98d2b2aea431ca652d9fadf5efa3 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 1 Sep 2026 14:33:19 +0200 Subject: [PATCH 0544/1317] s390/pai: Move locking to event init and delete Move mutex locking from per CPU allocation to event allocation. No functional change. Signed-off-by: Thomas Richter Reviewed-by: Sumanth Korikkar Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/perf_pai.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/arch/s390/kernel/perf_pai.c b/arch/s390/kernel/perf_pai.c index 109b15227d8f..c333158cc945 100644 --- a/arch/s390/kernel/perf_pai.c +++ b/arch/s390/kernel/perf_pai.c @@ -138,21 +138,19 @@ static void pai_free(struct pai_mapptr *mp) } /* Adjust usage counters and remove allocated memory when all users are - * gone. + * gone. Called under mutex_lock. */ static void pai_event_destroy_cpu(int idx, int cpu) { struct pai_mapptr *mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); struct pai_map *cpump = mp->mapptr; - mutex_lock(&pai_reserve_mutex); debug_sprintf_event(paidbg, 5, "%s users %d refcnt %u\n", __func__, cpump->active_events, refcount_read(&cpump->refcnt)); if (refcount_dec_and_test(&cpump->refcnt)) pai_free(mp); pai_root_free(idx); - mutex_unlock(&pai_reserve_mutex); } static void pai_event_destroy(struct perf_event *event) @@ -160,6 +158,7 @@ static void pai_event_destroy(struct perf_event *event) int cpu = 0, idx = PAI_PMU_IDX(event); free_page(PAI_SAVE_AREA(event)); + mutex_lock(&pai_reserve_mutex); if (event->cpu == -1) { struct cpumask *mask = PAI_CPU_MASK(event); @@ -169,6 +168,7 @@ static void pai_event_destroy(struct perf_event *event) } else { pai_event_destroy_cpu(idx, event->cpu); } + mutex_unlock(&pai_reserve_mutex); } static void paicrypt_event_destroy(struct perf_event *event) @@ -232,12 +232,10 @@ static u64 paicrypt_getall(struct perf_event *event) return sum; } -/* Check concurrent access of counting and sampling for crypto events. - * This function is called in process context and it is save to block. - * When the event initialization functions fails, no other call back will - * be invoked. - * - * Allocate the memory for the event. +/* Allocate all per-CPU data structures. This function is called in + * process context and can block. In case of error all partly allocated + * memory is released and the reference counters adjusted correctly. + * Called under mutex_lock. */ static int pai_alloc_cpu(int idx, int cpu) { @@ -246,11 +244,10 @@ static int pai_alloc_cpu(int idx, int cpu) struct pai_mapptr *mp; int rc; - mutex_lock(&pai_reserve_mutex); /* Allocate root node */ rc = pai_root_alloc(idx); if (rc) - goto unlock; + goto out; /* Allocate node for this event */ mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); @@ -308,12 +305,12 @@ static int pai_alloc_cpu(int idx, int cpu) */ pai_root_free(idx); } -unlock: - mutex_unlock(&pai_reserve_mutex); +out: /* If rc is non-zero, no increment of counter/sampler was done. */ return rc; } +/* Called under mutex_lock */ static int pai_alloc(struct perf_event *event) { int idx = PAI_PMU_IDX(event); @@ -390,10 +387,12 @@ static int pai_event_init(struct perf_event *event, int idx) } } + mutex_lock(&pai_reserve_mutex); if (event->cpu >= 0) rc = pai_alloc_cpu(idx, event->cpu); else rc = pai_alloc(event); + mutex_unlock(&pai_reserve_mutex); if (rc) { free_page(PAI_SAVE_AREA(event)); goto out; From 9ecc4d033879f7761f2df07e20cd2fbec00fd90b Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 1 Sep 2026 14:33:20 +0200 Subject: [PATCH 0545/1317] s390/pai: Support CPU hotplug for PMU PAI The command 'perf stat -e pai_crypto/CRYPTO_ALL/ -- ' crashes the kernel when CPUs are hotplug added during that run. Root cause is the missing allocation of per-CPU data structures for that new CPU. The allocation is dynamic and the first event that has task context creates such a structure for each online CPU. This is not sufficient. CPUs may be offline during event creation and can be set online during the perf run time. For example commands # echo 0 > /sys/devices/system/cpu/cpu1/online # perf stat -e cycles -i -- stress-ng -t10s --matrix X # sleep 1 # echo 1 > /sys/devices/system/cpu/cpu1/online Currently without a CPU hotplug handler, that new CPU has no per-CPU data infrastructure. The scheduler runs PMU call back function pai_add() to install the PMU support for that CPU before the task is being scheduled on that new CPU. In pai_add() instructions mp = this_cpu_ptr(pai_root[idx].mapptr); cpump = mp->mapptr; return a NULL pointer and the result is a kernel panic as variable cpump is used inside that function. Add CPU hotplug support for CPU add and delete and create the necessary per-CPU data infrastructure during CPU hotplug add processing. Same for CPU hotplug remove. This is done when the CPU is offline to ensure the data structures are available when CPU is made online and tasks are scheduled on it. [hca@linux.ibm.com: fixup error path in pai_init()] Cc: stable@vger.kernel.org # v6.19 Fixes: 582cc1b28e8c ("s390/pai_ext: Enable per-task and system-wide sampling event") Fixes: 9f66572f2889 ("s390/pai_crypto: Enable per-task and system-wide sampling event") Signed-off-by: Thomas Richter Reviewed-by: Jan Polensky Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/pai.h | 1 - arch/s390/kernel/perf_pai.c | 178 ++++++++++++++++++++++++++---------- 2 files changed, 128 insertions(+), 51 deletions(-) diff --git a/arch/s390/include/asm/pai.h b/arch/s390/include/asm/pai.h index 534d0320e2aa..a3456a36aaa7 100644 --- a/arch/s390/include/asm/pai.h +++ b/arch/s390/include/asm/pai.h @@ -76,7 +76,6 @@ static __always_inline void pai_kernel_exit(struct pt_regs *regs) } #define PAI_SAVE_AREA(x) ((x)->hw.event_base) -#define PAI_CPU_MASK(x) ((x)->hw.addr_filters) #define PAI_PMU_IDX(x) ((x)->hw.last_tag) #define PAI_SWLIST(x) (&(x)->hw.tp_list) diff --git a/arch/s390/kernel/perf_pai.c b/arch/s390/kernel/perf_pai.c index c333158cc945..013c3dae21ec 100644 --- a/arch/s390/kernel/perf_pai.c +++ b/arch/s390/kernel/perf_pai.c @@ -67,6 +67,7 @@ struct pai_mapptr { static struct pai_root { /* Anchor to per CPU data */ refcount_t refcnt; /* Overall active events */ + atomic_t tskctx; /* Overall per-task events */ struct pai_mapptr __percpu *mapptr; } pai_root[PAI_PMU_MAX]; @@ -93,14 +94,15 @@ struct pai_pmu { /* Define PAI PMU characteristics */ static struct pai_pmu pai_pmu[]; /* Forward declaration */ /* Free per CPU data when the last event is removed. */ -static void pai_root_free(int idx) +static void pai_root_free(int idx, int tasks) { - if (refcount_dec_and_test(&pai_root[idx].refcnt)) { + if (refcount_sub_and_test(tasks, &pai_root[idx].refcnt)) { free_percpu(pai_root[idx].mapptr); pai_root[idx].mapptr = NULL; } - debug_sprintf_event(paidbg, 5, "%s root[%d].refcount %d\n", __func__, - idx, refcount_read(&pai_root[idx].refcnt)); + debug_sprintf_event(paidbg, 5, "%s root[%d].refcount %d tskctx %d\n", + __func__, idx, refcount_read(&pai_root[idx].refcnt), + atomic_read(&pai_root[idx].tskctx)); } /* @@ -137,20 +139,36 @@ static void pai_free(struct pai_mapptr *mp) mp->mapptr = NULL; } -/* Adjust usage counters and remove allocated memory when all users are - * gone. Called under mutex_lock. - */ -static void pai_event_destroy_cpu(int idx, int cpu) +/* Called under mutex_lock */ +static void pai_event_destroy_cpu(int idx, int cpu, bool hotplug) { - struct pai_mapptr *mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); - struct pai_map *cpump = mp->mapptr; + struct pai_mapptr *mp; + struct pai_map *cpump; + int tasks = 1; - debug_sprintf_event(paidbg, 5, "%s users %d refcnt %u\n", - __func__, cpump->active_events, - refcount_read(&cpump->refcnt)); - if (refcount_dec_and_test(&cpump->refcnt)) + /* Check reference count and return when all gone. + * 1. An event is installed on online CPU X. + * 2. CPU x is offlined and the per-CPU data is removed. + * 3. Event is destroyed via close system call. + */ + if (!refcount_read(&pai_root[idx].refcnt)) + return; /* No events at all */ + mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); + if (!mp || !mp->mapptr) /* No events on that CPU */ + return; + + /* When hotplug is true, invocation is from CPU hotplug callback. + * Delete per-CPU resource and adjust refcnt when per-task events + * are currently active. This can be more than one. + * In this case adjust counters. + */ + if (hotplug) + tasks = atomic_read(&pai_root[idx].tskctx); + + cpump = mp->mapptr; + if (refcount_sub_and_test(tasks, &cpump->refcnt)) pai_free(mp); - pai_root_free(idx); + pai_root_free(idx, tasks); } static void pai_event_destroy(struct perf_event *event) @@ -158,17 +176,17 @@ static void pai_event_destroy(struct perf_event *event) int cpu = 0, idx = PAI_PMU_IDX(event); free_page(PAI_SAVE_AREA(event)); + cpus_read_lock(); mutex_lock(&pai_reserve_mutex); if (event->cpu == -1) { - struct cpumask *mask = PAI_CPU_MASK(event); - - for_each_cpu(cpu, mask) - pai_event_destroy_cpu(idx, cpu); - kfree(mask); + atomic_dec(&pai_root[idx].tskctx); + for_each_online_cpu(cpu) + pai_event_destroy_cpu(idx, cpu, false); } else { - pai_event_destroy_cpu(idx, event->cpu); + pai_event_destroy_cpu(idx, event->cpu, false); } mutex_unlock(&pai_reserve_mutex); + cpus_read_unlock(); } static void paicrypt_event_destroy(struct perf_event *event) @@ -232,17 +250,25 @@ static u64 paicrypt_getall(struct perf_event *event) return sum; } -/* Allocate all per-CPU data structures. This function is called in - * process context and can block. In case of error all partly allocated - * memory is released and the reference counters adjusted correctly. - * Called under mutex_lock. - */ -static int pai_alloc_cpu(int idx, int cpu) +/* Called under mutex_lock */ +static int pai_alloc_cpu(int idx, int cpu, bool hotplug) { struct pai_map *cpump = NULL; bool need_paiext_cb = false; struct pai_mapptr *mp; - int rc; + int tasks = 1, rc = 0; + + /* When hotplug is true, invocation is from CPU hotplug callback. + * Allocate per-CPU resource when per-task events are currently active. + * This can be more than one. In this case adjust all reference + * counters. Otherwise return, this ensures memory is only allocated + * when needed. + */ + if (hotplug) { + tasks = atomic_read(&pai_root[idx].tskctx); + if (!tasks) + goto out; + } /* Allocate root node */ rc = pai_root_alloc(idx); @@ -291,26 +317,42 @@ static int pai_alloc_cpu(int idx, int cpu) goto undo; } INIT_LIST_HEAD(&cpump->syswide_list); - refcount_set(&cpump->refcnt, 1); + refcount_set(&cpump->refcnt, tasks); rc = 0; } else { - refcount_inc(&cpump->refcnt); + refcount_add(tasks, &cpump->refcnt); } + /* If tasks is greater than 1, we are called from CPU hotplug path + * and need to adjust the pai_root[idx].refcnt by the number of + * per-process events. Function pai_root_alloc(idx) already + * incremented by one. Adjust for the rest. + */ + if (tasks > 1) + refcount_add(tasks - 1, &pai_root[idx].refcnt); undo: if (rc) { /* Error in allocation of event, decrement anchor. Since * the event in not created, its destroy() function is never * invoked. Adjust the reference counter for the anchor. + * The failure happened in the case of variable + * cpump == NULL branch above. The pai_root[XXX].refcnt has + * been incremented by one. Then the per-CPU allocation + * failed, so decrement it by one, regardless of tasks. */ - pai_root_free(idx); + pai_root_free(idx, 1); } out: /* If rc is non-zero, no increment of counter/sampler was done. */ return rc; } -/* Called under mutex_lock */ +/* Check concurrent access of counting and sampling for PAI events. + * This function is called in process context and it is safe to block. + * When the event initialization functions fails, no other call back will + * be invoked. + * Called under mutex_lock. + */ static int pai_alloc(struct perf_event *event) { int idx = PAI_PMU_IDX(event); @@ -322,24 +364,20 @@ static int pai_alloc(struct perf_event *event) goto out; for_each_online_cpu(cpu) { - rc = pai_alloc_cpu(idx, cpu); + rc = pai_alloc_cpu(idx, cpu, false); if (rc) { for_each_cpu(cpu, maskptr) - pai_event_destroy_cpu(idx, cpu); - kfree(maskptr); - goto out; + pai_event_destroy_cpu(idx, cpu, false); + goto undo; } cpumask_set_cpu(cpu, maskptr); } - /* - * On error all cpumask are freed and all events have been destroyed. - * Save of which CPUs data structures have been allocated for. - * Release them in pai_event_destroy call back function - * for this event. - */ - PAI_CPU_MASK(event) = maskptr; rc = 0; + /* Trace per-task events for CPU hotplug. */ + atomic_inc(&pai_root[idx].tskctx); +undo: + kfree(maskptr); out: return rc; } @@ -387,12 +425,14 @@ static int pai_event_init(struct perf_event *event, int idx) } } + cpus_read_lock(); mutex_lock(&pai_reserve_mutex); if (event->cpu >= 0) - rc = pai_alloc_cpu(idx, event->cpu); + rc = pai_alloc_cpu(idx, event->cpu, false); else rc = pai_alloc(event); mutex_unlock(&pai_reserve_mutex); + cpus_read_unlock(); if (rc) { free_page(PAI_SAVE_AREA(event)); goto out; @@ -1237,8 +1277,35 @@ static int __init paipmu_setup(void) return install_ok; } +static int pai_online_cpu(unsigned int cpu) +{ + int rc; + + mutex_lock(&pai_reserve_mutex); + rc = pai_alloc_cpu(PAI_PMU_CRYPTO, cpu, true); + if (rc) + goto out; + rc = pai_alloc_cpu(PAI_PMU_EXT, cpu, true); + if (rc) + pai_event_destroy_cpu(PAI_PMU_CRYPTO, cpu, true); +out: + mutex_unlock(&pai_reserve_mutex); + return rc; +} + +static int pai_offline_cpu(unsigned int cpu) +{ + mutex_lock(&pai_reserve_mutex); + pai_event_destroy_cpu(PAI_PMU_CRYPTO, cpu, true); + pai_event_destroy_cpu(PAI_PMU_EXT, cpu, true); + mutex_unlock(&pai_reserve_mutex); + return 0; +} + static int __init pai_init(void) { + int state, rc; + /* Setup s390dbf facility */ paidbg = debug_register("pai", 1, 1, 128); if (!paidbg) { @@ -1247,13 +1314,24 @@ static int __init pai_init(void) } debug_register_view(paidbg, &debug_sprintf_view); - if (!paipmu_setup()) { - /* No PMU registration, no need for debug buffer */ - debug_unregister_view(paidbg, &debug_sprintf_view); - debug_unregister(paidbg); - return -ENODEV; - } + /* CPUHP_BP_PREPARE_DYN --> before CPU is brought online */ + state = cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "perf/pai:prepare", + pai_online_cpu, pai_offline_cpu); + rc = state < 0 ? state : 0; + if (rc < 0) + goto out_debug; + + rc = -ENODEV; + if (!paipmu_setup()) + goto out_cpuhp; return 0; + +out_cpuhp: + cpuhp_remove_state(state); +out_debug: + debug_unregister_view(paidbg, &debug_sprintf_view); + debug_unregister(paidbg); + return rc; } device_initcall(pai_init); From b1eb31d533cdfcae1011ed53850d52f36afe5774 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Thu, 3 Sep 2026 15:07:31 +0200 Subject: [PATCH 0546/1317] s390/debug: Fix NULL pointer dereference in debug_set_level() Commit a2cec6863709 ("s390/debug: Add s390dbf kernel parameter") incorrectly removed a null-id check from debug_set_level(), introducing a possible NULL pointer dereference for debug-API users that put debug_register() results unchecked into debug_set_level(). Fix this by moving the check from the internal _debug_set_level() variant back to the external debug_set_level() wrapper. Fixes: a2cec6863709 ("s390/debug: Add s390dbf kernel parameter") Signed-off-by: Mikhail Zaslonko Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/debug.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index 14d2b58ad093..e06abf1dbc21 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -1074,9 +1074,6 @@ static void _debug_set_level(debug_info_t *id, int new_level) { unsigned long flags; - if (!id) - return; - if (new_level == DEBUG_OFF_LEVEL) { pr_info("%s: switched off\n", id->name); } else if ((new_level > DEBUG_MAX_LEVEL) || (new_level < 0)) { @@ -1101,6 +1098,9 @@ static void _debug_set_level(debug_info_t *id, int new_level) */ void debug_set_level(debug_info_t *id, int new_level) { + if (!id) + return; + /* Level specified via kernel parameter takes precedence */ debug_get_param(id->name, &new_level, NULL); From 22d4210bf988047bd30803cd6ef5177f113e4004 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Thu, 3 Sep 2026 15:07:32 +0200 Subject: [PATCH 0547/1317] s390/debug: Do not repeat parameter override notice on debug_set_level() Commit a2cec6863709 ("s390/debug: Add s390dbf kernel parameter") calls debug_get_param() from both debug_info_create() and debug_set_level(). Since debug_get_param() emits the override notice unconditionally, and drivers typically call debug_set_level() right after debug_register(), the same line is printed twice per debug area: s390dbf: 0.0.1234: override level to 6 s390dbf: 0.0.1234: override level to 6 For areas registered per device this is multiplied by the device count. With 's390dbf=0.0.*:6' a system with many DASDs emits a large number of redundant lines during boot. Add a quiet parameter to debug_get_param() and pass quiet=true from debug_set_level(), where the override has already been announced during registration. The remaining callers keep printing the notice. Signed-off-by: Mikhail Zaslonko Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/debug.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index e06abf1dbc21..cf411f203571 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -182,7 +182,7 @@ static struct debug_param_t { static int debug_param_num; /* functions */ -static void debug_get_param(const char *name, int *level, int *pages) +static void debug_get_param(const char *name, int *level, int *pages, bool quiet) { struct debug_param_t *p; int i; @@ -192,11 +192,13 @@ static void debug_get_param(const char *name, int *level, int *pages) if (!glob_match(p->name, name)) continue; if (level && p->level != PARAM_UNSET) { - pr_info("%s: override level to %d\n", name, p->level); + if (!quiet) + pr_info("%s: override level to %d\n", name, p->level); *level = p->level; } if (pages && p->pages != PARAM_UNSET) { - pr_info("%s: override pages to %d\n", name, p->pages); + if (!quiet) + pr_info("%s: override pages to %d\n", name, p->pages); *pages = p->pages; } } @@ -251,7 +253,7 @@ static int __init s390dbf_parse(char *arg) * regular memory allocations are possible. */ for (i = 0, id = __s390dbf_info; &id[i] < __s390dbf_info_end; i++) - debug_get_param(id[i]->name, &id[i]->level, NULL); + debug_get_param(id[i]->name, &id[i]->level, NULL, false); return rc; } @@ -395,7 +397,7 @@ static debug_info_t *debug_info_create(const char *name, int pages_per_area, int level = DEBUG_DEFAULT_LEVEL; debug_info_t *rc; - debug_get_param(name, &level, &pages_per_area); + debug_get_param(name, &level, &pages_per_area, false); rc = debug_info_alloc(name, pages_per_area, nr_areas, buf_size, level, ALL_AREAS); if (!rc) goto out; @@ -960,7 +962,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) return; } - debug_get_param(id->name, &id->level, &pages_per_area); + debug_get_param(id->name, &id->level, &pages_per_area, false); copy = debug_info_alloc("", pages_per_area, nr_areas, id->buf_size, id->level, ALL_AREAS); if (!copy) { @@ -1101,8 +1103,11 @@ void debug_set_level(debug_info_t *id, int new_level) if (!id) return; - /* Level specified via kernel parameter takes precedence */ - debug_get_param(id->name, &new_level, NULL); + /* + * Level specified via kernel parameter takes precedence. The override + * was already announced during registration, so stay quiet here. + */ + debug_get_param(id->name, &new_level, NULL, true); _debug_set_level(id, new_level); } From 0945285e6cd67ee87e9313fb10221aba5bd69c6a Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Thu, 3 Sep 2026 15:07:33 +0200 Subject: [PATCH 0548/1317] s390/debug: Fix race between debug area resize and event logging Trace functions check for non-NULL id->areas without lock to minimize overhead. This opens a race window where a NULL pointer dereference occurs if id->areas is set to NULL (e.g. via echo 0 > ../pages) after the check and before id->lock is taken. Fix this by rechecking id->areas under lock. Signed-off-by: Mikhail Zaslonko Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/debug.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index cf411f203571..b5bf8284dbfc 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -1283,7 +1283,7 @@ void debug_set_critical(void) debug_entry_t *debug_event_common(debug_info_t *id, int level, const void *buf, int len) { - debug_entry_t *active; + debug_entry_t *active = NULL; unsigned long flags; if (!debug_active || !id->areas) @@ -1294,6 +1294,8 @@ debug_entry_t *debug_event_common(debug_info_t *id, int level, const void *buf, } else { raw_spin_lock_irqsave(&id->lock, flags); } + if (!id->areas) + goto out; do { active = get_active_entry(id); memcpy(DEBUG_DATA(active), buf, min(len, id->buf_size)); @@ -1303,7 +1305,7 @@ debug_entry_t *debug_event_common(debug_info_t *id, int level, const void *buf, len -= id->buf_size; buf += id->buf_size; } while (len > 0); - +out: raw_spin_unlock_irqrestore(&id->lock, flags); return active; } @@ -1316,7 +1318,7 @@ EXPORT_SYMBOL(debug_event_common); debug_entry_t *debug_exception_common(debug_info_t *id, int level, const void *buf, int len) { - debug_entry_t *active; + debug_entry_t *active = NULL; unsigned long flags; if (!debug_active || !id->areas) @@ -1327,6 +1329,8 @@ debug_entry_t *debug_exception_common(debug_info_t *id, int level, } else { raw_spin_lock_irqsave(&id->lock, flags); } + if (!id->areas) + goto out; do { active = get_active_entry(id); memcpy(DEBUG_DATA(active), buf, min(len, id->buf_size)); @@ -1336,7 +1340,7 @@ debug_entry_t *debug_exception_common(debug_info_t *id, int level, len -= id->buf_size; buf += id->buf_size; } while (len > 0); - +out: raw_spin_unlock_irqrestore(&id->lock, flags); return active; } @@ -1362,7 +1366,7 @@ static inline int debug_count_numargs(char *string) debug_entry_t *__debug_sprintf_event(debug_info_t *id, int level, char *string, ...) { debug_sprintf_entry_t *curr_event; - debug_entry_t *active; + debug_entry_t *active = NULL; unsigned long flags; int numargs, idx; va_list ap; @@ -1377,6 +1381,8 @@ debug_entry_t *__debug_sprintf_event(debug_info_t *id, int level, char *string, } else { raw_spin_lock_irqsave(&id->lock, flags); } + if (!id->areas) + goto out; active = get_active_entry(id); curr_event = (debug_sprintf_entry_t *) DEBUG_DATA(active); va_start(ap, string); @@ -1385,6 +1391,7 @@ debug_entry_t *__debug_sprintf_event(debug_info_t *id, int level, char *string, curr_event->args[idx] = va_arg(ap, long); va_end(ap); debug_finish_entry(id, active, level, 0); +out: raw_spin_unlock_irqrestore(&id->lock, flags); return active; @@ -1397,7 +1404,7 @@ EXPORT_SYMBOL(__debug_sprintf_event); debug_entry_t *__debug_sprintf_exception(debug_info_t *id, int level, char *string, ...) { debug_sprintf_entry_t *curr_event; - debug_entry_t *active; + debug_entry_t *active = NULL; unsigned long flags; int numargs, idx; va_list ap; @@ -1413,6 +1420,8 @@ debug_entry_t *__debug_sprintf_exception(debug_info_t *id, int level, char *stri } else { raw_spin_lock_irqsave(&id->lock, flags); } + if (!id->areas) + goto out; active = get_active_entry(id); curr_event = (debug_sprintf_entry_t *)DEBUG_DATA(active); va_start(ap, string); @@ -1421,6 +1430,7 @@ debug_entry_t *__debug_sprintf_exception(debug_info_t *id, int level, char *stri curr_event->args[idx] = va_arg(ap, long); va_end(ap); debug_finish_entry(id, active, level, 1); +out: raw_spin_unlock_irqrestore(&id->lock, flags); return active; @@ -1663,9 +1673,11 @@ static void debug_flush(debug_info_t *id, int area) unsigned long flags; int i, j; - if (!id || !id->areas) + if (!id) return; raw_spin_lock_irqsave(&id->lock, flags); + if (!id->areas) + goto out; if (area == DEBUG_FLUSH_ALL) { id->active_area = 0; memset(id->active_entries, 0, id->nr_areas * sizeof(int)); @@ -1680,6 +1692,7 @@ static void debug_flush(debug_info_t *id, int area) for (i = 0; i < id->pages_per_area; i++) memset(id->areas[area][i], 0, PAGE_SIZE); } +out: raw_spin_unlock_irqrestore(&id->lock, flags); } From 15fa028589c3a2545f0bff355eff06d5844196bf Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:37:33 +0200 Subject: [PATCH 0549/1317] s390/crypto: Fix skcipher_walk return code handling in aes_s390 The return codes from skcipher_walk_virt() were not properly checked before entering the processing loops in ecb_aes_crypt() and ctr_aes_crypt(). If skcipher_walk_virt() fails, the walk structure may be in an undefined state, and attempting to process data could lead to incorrect behavior or accessing uninitialized memory. Add proper return code checking to ensure correct handling of the walk initialization and walk advance and eventually return to the caller with that return code. Fixes: 7988fb2c03c8 ("crypto: s390/aes - convert to skcipher API") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 5.5+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/aes_s390.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/s390/crypto/aes_s390.c b/arch/s390/crypto/aes_s390.c index 62edc66d5478..366ce22d3623 100644 --- a/arch/s390/crypto/aes_s390.c +++ b/arch/s390/crypto/aes_s390.c @@ -129,7 +129,7 @@ static int ecb_aes_crypt(struct skcipher_request *req, unsigned long modifier) return fallback_skcipher_crypt(sctx, req, modifier); ret = skcipher_walk_virt(&walk, req, false); - while ((nbytes = walk.nbytes) != 0) { + while (!ret && ((nbytes = walk.nbytes) != 0)) { /* only use complete blocks */ n = nbytes & ~(AES_BLOCK_SIZE - 1); cpacf_km(sctx->fc | modifier, sctx->key, @@ -233,7 +233,7 @@ static int cbc_aes_crypt(struct skcipher_request *req, unsigned long modifier) return ret; memcpy(param.iv, walk.iv, AES_BLOCK_SIZE); memcpy(param.key, sctx->key, sctx->key_len); - while ((nbytes = walk.nbytes) != 0) { + while (!ret && ((nbytes = walk.nbytes) != 0)) { /* only use complete blocks */ n = nbytes & ~(AES_BLOCK_SIZE - 1); cpacf_kmc(sctx->fc | modifier, ¶m, @@ -359,7 +359,7 @@ static int xts_aes_crypt(struct skcipher_request *req, unsigned long modifier) memcpy(xts_param.key + offset, xts_ctx->key, xts_ctx->key_len); memcpy(xts_param.init, pcc_param.xts, 16); - while ((nbytes = walk.nbytes) != 0) { + while (!ret && ((nbytes = walk.nbytes) != 0)) { /* only use complete blocks */ n = nbytes & ~(AES_BLOCK_SIZE - 1); cpacf_km(xts_ctx->fc | modifier, xts_param.key + offset, @@ -487,7 +487,7 @@ static int fullxts_aes_crypt(struct skcipher_request *req, unsigned long modifi memcpy(fxts_param.tweak, req->iv, AES_BLOCK_SIZE); fxts_param.nap[0] = 0x01; /* initial alpha power (1, little-endian) */ - while ((nbytes = walk.nbytes) != 0) { + while (!ret && ((nbytes = walk.nbytes) != 0)) { /* only use complete blocks */ n = nbytes & ~(AES_BLOCK_SIZE - 1); cpacf_km(xts_ctx->fc | modifier, fxts_param.key + offset, @@ -577,7 +577,7 @@ static int ctr_aes_crypt(struct skcipher_request *req) locked = mutex_trylock(&ctrblk_lock); ret = skcipher_walk_virt(&walk, req, false); - while ((nbytes = walk.nbytes) >= AES_BLOCK_SIZE) { + while (!ret && ((nbytes = walk.nbytes) >= AES_BLOCK_SIZE)) { n = AES_BLOCK_SIZE; if (nbytes >= 2*AES_BLOCK_SIZE && locked) @@ -596,7 +596,7 @@ static int ctr_aes_crypt(struct skcipher_request *req) /* * final block may be < AES_BLOCK_SIZE, copy only nbytes */ - if (nbytes) { + if (!ret && nbytes) { memset(buf, 0, AES_BLOCK_SIZE); memcpy(buf, walk.src.virt.addr, nbytes); cpacf_kmctr(sctx->fc, sctx->key, buf, buf, From 8b7c3b6914f19caf648d05726a86af6326d3c2c6 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:37:34 +0200 Subject: [PATCH 0550/1317] s390/crypto: Fix missing scrub of temp buffers with AES ctr and gcm algorithm In function ctr_aes_crypt() there is a buffer used to process remaining bytes < AES_BLOCK_SIZE. This buffer was not scrubbed and thus could lead to expose of unwanted data. When the buffer is used explicitly scrub it at the end of the code block to avoid exposure of maybe sensitive data. In a similar way the function gcm_aes_crypt() hat an error path where the CPACF param block was not scrubbed. Instead of return early now these error paths go to end of function where explicit scrubbing is done. Similar with the buffers which are part of the gcm_sg_walk structs from the variables gw_in and gw_out. Fixes: d07f951903fa ("crypto: s390/aes - Fix buffer overread in CTR mode") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.8+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/aes_s390.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/arch/s390/crypto/aes_s390.c b/arch/s390/crypto/aes_s390.c index 366ce22d3623..10561aa687c7 100644 --- a/arch/s390/crypto/aes_s390.c +++ b/arch/s390/crypto/aes_s390.c @@ -604,6 +604,7 @@ static int ctr_aes_crypt(struct skcipher_request *req) memcpy(walk.dst.virt.addr, buf, nbytes); crypto_inc(walk.iv, AES_BLOCK_SIZE); ret = skcipher_walk_done(&walk, 0); + memzero_explicit(buf, sizeof(buf)); } return ret; @@ -895,10 +896,14 @@ static int gcm_aes_crypt(struct aead_request *req, unsigned int flags) gw_in.ptr, aad_bytes); n = aad_bytes + pc_bytes; - if (gcm_in_walk_done(&gw_in, n) != n) - return -ENOMEM; - if (gcm_out_walk_done(&gw_out, n) != n) - return -ENOMEM; + if (gcm_in_walk_done(&gw_in, n) != n) { + ret = -ENOMEM; + goto out; + } + if (gcm_out_walk_done(&gw_out, n) != n) { + ret = -ENOMEM; + goto out; + } aadlen -= aad_bytes; pclen -= pc_bytes; } while (aadlen + pclen > 0); @@ -910,7 +915,10 @@ static int gcm_aes_crypt(struct aead_request *req, unsigned int flags) } else scatterwalk_map_and_copy(param.t, req->dst, len, taglen, 1); +out: memzero_explicit(¶m, sizeof(param)); + memzero_explicit(gw_in.buf, sizeof(gw_in.buf)); + memzero_explicit(gw_out.buf, sizeof(gw_out.buf)); return ret; } From d1c44a7d085473173bb360b7218a43699c3f56c7 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:37:35 +0200 Subject: [PATCH 0551/1317] s390/crypto: Fix use of mutex in atomic context The AES CTR implementation used a mutex to lock one page of exclusive memory for fast CTR processing. Unfortunately a mutex is not save to use in atomic or interrupt context. So use a binary semaphore instead which is save to use in such environments. Furthermore rework the code to get rid of conditional locking. So restructure the AES CRT code by extracting the main loop into a separate function and just give in information about the (locked) page can be used or not (is not locked). Fixes: 7988fb2c03c8 ("crypto: s390/aes - convert to skcipher API") Suggested-by: Heiko Carstens Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 5.5+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/aes_s390.c | 63 +++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/arch/s390/crypto/aes_s390.c b/arch/s390/crypto/aes_s390.c index 10561aa687c7..0be6fa779d2c 100644 --- a/arch/s390/crypto/aes_s390.c +++ b/arch/s390/crypto/aes_s390.c @@ -26,14 +26,14 @@ #include #include #include -#include #include +#include #include #include #include static u8 *ctrblk; -static DEFINE_MUTEX(ctrblk_lock); +static DEFINE_SEMAPHORE(ctrblk_sem, 1); static cpacf_mask_t km_functions, kmc_functions, kmctr_functions, kma_functions; @@ -562,46 +562,61 @@ static unsigned int __ctrblk_init(u8 *ctrptr, u8 *iv, unsigned int nbytes) return n; } +static int __ctr_aes_crypt(struct s390_aes_ctx *sctx, + struct skcipher_walk *walk, bool locked) +{ + unsigned int n, nbytes; + int ret = 0; + u8 *ctrptr; + + while (!ret && ((nbytes = walk->nbytes) >= AES_BLOCK_SIZE)) { + n = AES_BLOCK_SIZE; + if (nbytes >= 2 * AES_BLOCK_SIZE && locked) + n = __ctrblk_init(ctrblk, walk->iv, nbytes); + ctrptr = (n > AES_BLOCK_SIZE) ? ctrblk : walk->iv; + cpacf_kmctr(sctx->fc, sctx->key, walk->dst.virt.addr, + walk->src.virt.addr, n, ctrptr); + if (ctrptr == ctrblk) + memcpy(walk->iv, ctrptr + n - AES_BLOCK_SIZE, + AES_BLOCK_SIZE); + crypto_inc(walk->iv, AES_BLOCK_SIZE); + ret = skcipher_walk_done(walk, nbytes - n); + } + + return ret; +} + static int ctr_aes_crypt(struct skcipher_request *req) { struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); struct s390_aes_ctx *sctx = crypto_skcipher_ctx(tfm); - u8 buf[AES_BLOCK_SIZE], *ctrptr; struct skcipher_walk walk; - unsigned int n, nbytes; - int ret, locked; + u8 buf[AES_BLOCK_SIZE]; + int ret; if (unlikely(!sctx->fc)) return fallback_skcipher_crypt(sctx, req, 0); - locked = mutex_trylock(&ctrblk_lock); - ret = skcipher_walk_virt(&walk, req, false); - while (!ret && ((nbytes = walk.nbytes) >= AES_BLOCK_SIZE)) { - n = AES_BLOCK_SIZE; + if (ret) + return ret; - if (nbytes >= 2*AES_BLOCK_SIZE && locked) - n = __ctrblk_init(ctrblk, walk.iv, nbytes); - ctrptr = (n > AES_BLOCK_SIZE) ? ctrblk : walk.iv; - cpacf_kmctr(sctx->fc, sctx->key, walk.dst.virt.addr, - walk.src.virt.addr, n, ctrptr); - if (ctrptr == ctrblk) - memcpy(walk.iv, ctrptr + n - AES_BLOCK_SIZE, - AES_BLOCK_SIZE); - crypto_inc(walk.iv, AES_BLOCK_SIZE); - ret = skcipher_walk_done(&walk, nbytes - n); + if (down_trylock(&ctrblk_sem) == 0) { + ret = __ctr_aes_crypt(sctx, &walk, true); + up(&ctrblk_sem); + } else { + ret = __ctr_aes_crypt(sctx, &walk, false); } - if (locked) - mutex_unlock(&ctrblk_lock); + /* * final block may be < AES_BLOCK_SIZE, copy only nbytes */ - if (!ret && nbytes) { + if (!ret && walk.nbytes > 0) { memset(buf, 0, AES_BLOCK_SIZE); - memcpy(buf, walk.src.virt.addr, nbytes); + memcpy(buf, walk.src.virt.addr, walk.nbytes); cpacf_kmctr(sctx->fc, sctx->key, buf, buf, AES_BLOCK_SIZE, walk.iv); - memcpy(walk.dst.virt.addr, buf, nbytes); + memcpy(walk.dst.virt.addr, buf, walk.nbytes); crypto_inc(walk.iv, AES_BLOCK_SIZE); ret = skcipher_walk_done(&walk, 0); memzero_explicit(buf, sizeof(buf)); From 403648373816ae8eb76fe4a393836b0d65fa8f85 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:30 +0200 Subject: [PATCH 0552/1317] s390/crypto: Fix return code handling at skcipher_walk_done in PAES algorithms All the 4 PAES cipher processing loops were not checking the return value of skcipher_walk_done() immediately after calling it. This could lead to error masking when both the walk operation failed and a subsequent key conversion was needed (k < n condition). Add immediate error checks after skcipher_walk_done() in all main processing loops (ECB, CBC, CTR, XTS modes) to ensure walk errors are properly propagated and not masked by subsequent operations. With that comes a slight rework around the skcipher_walk_done() invocation. It is now necessary to check if the walk has already been finalized (walk->nbytes is then 0) or not to avoid double de-allocation of resources held by the walk. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 38 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 973436592318..89785ab95e6b 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -432,8 +432,11 @@ static int ecb_paes_do_crypt(struct s390_paes_ctx *ctx, n = nbytes & ~(AES_BLOCK_SIZE - 1); k = cpacf_km(ctx->fc | req_ctx->modifier, param, walk->dst.virt.addr, walk->src.virt.addr, n); - if (k) + if (k) { rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; + } if (k < n) { if (!maysleep) { rc = -EKEYEXPIRED; @@ -495,7 +498,7 @@ static int ecb_paes_crypt(struct skcipher_request *req, unsigned long modifier) atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (rc != -EINPROGRESS && walk->nbytes) skcipher_walk_done(walk, rc); out: @@ -549,7 +552,7 @@ static int ecb_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = ecb_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { return pkey_handle_expired(); - } else if (rc) { + } else if (rc && walk->nbytes) { skcipher_walk_done(walk, rc); } @@ -690,6 +693,8 @@ static int cbc_paes_do_crypt(struct s390_paes_ctx *ctx, if (k) { memcpy(walk->iv, param->iv, AES_BLOCK_SIZE); rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; } if (k < n) { if (!maysleep) { @@ -752,7 +757,7 @@ static int cbc_paes_crypt(struct skcipher_request *req, unsigned long modifier) atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (rc != -EINPROGRESS && walk->nbytes) skcipher_walk_done(walk, rc); out: @@ -806,7 +811,7 @@ static int cbc_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = cbc_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { return pkey_handle_expired(); - } else if (rc) { + } else if (rc && walk->nbytes) { skcipher_walk_done(walk, rc); } @@ -968,6 +973,11 @@ static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, AES_BLOCK_SIZE); crypto_inc(walk->iv, AES_BLOCK_SIZE); rc = skcipher_walk_done(walk, nbytes - k); + if (rc) { + if (locked) + mutex_unlock(&ctrblk_lock); + goto out; + } } if (k < n) { if (!maysleep) { @@ -1061,7 +1071,7 @@ static int ctr_paes_crypt(struct skcipher_request *req) atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (rc != -EINPROGRESS && walk->nbytes) skcipher_walk_done(walk, rc); out: @@ -1105,7 +1115,7 @@ static int ctr_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = ctr_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { return pkey_handle_expired(); - } else if (rc) { + } else if (rc && walk->nbytes) { skcipher_walk_done(walk, rc); } @@ -1283,8 +1293,11 @@ static int xts_paes_do_crypt_fullkey(struct s390_pxts_ctx *ctx, n = nbytes & ~(AES_BLOCK_SIZE - 1); k = cpacf_km(ctx->fc | req_ctx->modifier, param->key + offset, walk->dst.virt.addr, walk->src.virt.addr, n); - if (k) + if (k) { rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; + } if (k < n) { if (!maysleep) { rc = -EKEYEXPIRED; @@ -1377,8 +1390,11 @@ static int xts_paes_do_crypt_2keys(struct s390_pxts_ctx *ctx, n = nbytes & ~(AES_BLOCK_SIZE - 1); k = cpacf_km(ctx->fc | req_ctx->modifier, param->key + offset, walk->dst.virt.addr, walk->src.virt.addr, n); - if (k) + if (k) { rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; + } if (k < n) { if (!maysleep) { rc = -EKEYEXPIRED; @@ -1485,7 +1501,7 @@ static inline int xts_paes_crypt(struct skcipher_request *req, unsigned long mod atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (rc != -EINPROGRESS && walk->nbytes) skcipher_walk_done(walk, rc); out: @@ -1539,7 +1555,7 @@ static int xts_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = xts_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { return pkey_handle_expired(); - } else if (rc) { + } else if (rc && walk->nbytes) { skcipher_walk_done(walk, rc); } From 19a218b46b2471d370c11fb52040f36ac8d05d23 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:31 +0200 Subject: [PATCH 0553/1317] s390/crypto: Fix missing scrub of temp buffers with PAES algorithm In function ctr_paes_do_crypt() there is a buffer used to process remaining bytes < AES_BLOCK_SIZE. This buffer was not scrubbed and thus could lead to expose of unwanted data. Rework the code to explicitly scrub the buffer at the end of the function to avoid exposure of maybe sensitive data. In function __xts_2keys_prep_param() change the existing scrub to clean the whole param block instead of just the key field. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 89785ab95e6b..9c8f9e2570f2 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -1026,6 +1026,7 @@ static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, } out: + memzero_explicit(buf, sizeof(buf)); pr_debug("rc=%d\n", rc); return rc; } @@ -1350,7 +1351,7 @@ static inline int __xts_2keys_prep_param(struct s390_pxts_ctx *ctx, memcpy(param->init, pcc_param.xts, 16); } - memzero_explicit(pcc_param.key, sizeof(pcc_param.key)); + memzero_explicit(&pcc_param, sizeof(pcc_param)); return rc; } From 749990db95d45cc3d96aab999ac7f110259b200c Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:32 +0200 Subject: [PATCH 0554/1317] s390/crypto: Fix use of mutex in atomic context in PAES The PAES CTR implementation used a mutex to lock one page of exclusive memory for fast CTR processing. Unfortunately a mutex is not save to use in atomic or interrupt context. So use a binary semaphore instead which is save to use in such environments. Furthermore rework the code to get rid of conditional locking. So restructure the PAES CRT code by extracting the main loop into a separate function and just give in information about the (locked) page can be used or not (is not locked). Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 113 +++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 9c8f9e2570f2..991a26766d40 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -45,7 +45,7 @@ module_param_named(clrkey, pkey_clrkey_allowed, bool, 0444); MODULE_PARM_DESC(clrkey, "Allow clear key material (default N)"); static u8 *ctrblk; -static DEFINE_MUTEX(ctrblk_lock); +static DEFINE_SEMAPHORE(ctrblk_sem, 1); static cpacf_mask_t km_functions, kmc_functions, kmctr_functions; @@ -919,15 +919,62 @@ static inline unsigned int __ctrblk_init(u8 *ctrptr, u8 *iv, unsigned int nbytes return n; } +static int __ctr_paes_do_crypt(struct s390_paes_ctx *ctx, + struct ctr_param *param, + struct skcipher_walk *walk, + bool tested, bool maysleep, bool locked) +{ + unsigned int nbytes, n, k; + u8 *ctrptr; + int rc = 0; + + /* + * Note that in case of partial processing or failure the walk + * is NOT unmapped here. So a follow up task may reuse the walk + * or in case of unrecoverable failure needs to unmap it. + */ + while ((nbytes = walk->nbytes) >= AES_BLOCK_SIZE) { + n = AES_BLOCK_SIZE; + if (nbytes >= 2 * AES_BLOCK_SIZE && locked) + n = __ctrblk_init(ctrblk, walk->iv, nbytes); + ctrptr = (n > AES_BLOCK_SIZE) ? ctrblk : walk->iv; + k = cpacf_kmctr(ctx->fc, param, walk->dst.virt.addr, + walk->src.virt.addr, n, ctrptr); + if (k) { + if (ctrptr == ctrblk) + memcpy(walk->iv, ctrptr + k - AES_BLOCK_SIZE, + AES_BLOCK_SIZE); + crypto_inc(walk->iv, AES_BLOCK_SIZE); + rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; + } + if (k < n) { + if (!maysleep) { + rc = -EKEYEXPIRED; + goto out; + } + rc = paes_convert_key(ctx, tested); + if (rc) + goto out; + spin_lock_bh(&ctx->pk_lock); + memcpy(param->key, ctx->pk.protkey, sizeof(param->key)); + spin_unlock_bh(&ctx->pk_lock); + } + } + +out: + return rc; +} + static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, struct s390_pctr_req_ctx *req_ctx, bool tested, bool maysleep) { struct ctr_param *param = &req_ctx->param; struct skcipher_walk *walk = &req_ctx->walk; - u8 buf[AES_BLOCK_SIZE], *ctrptr; - unsigned int nbytes, n, k; - int pk_state, locked, rc = 0; + u8 buf[AES_BLOCK_SIZE]; + int pk_state, rc = 0; if (!req_ctx->param_init_done) { /* fetch and check protected key state */ @@ -953,57 +1000,17 @@ static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, if (rc) goto out; - locked = mutex_trylock(&ctrblk_lock); - - /* - * Note that in case of partial processing or failure the walk - * is NOT unmapped here. So a follow up task may reuse the walk - * or in case of unrecoverable failure needs to unmap it. - */ - while ((nbytes = walk->nbytes) >= AES_BLOCK_SIZE) { - n = AES_BLOCK_SIZE; - if (nbytes >= 2 * AES_BLOCK_SIZE && locked) - n = __ctrblk_init(ctrblk, walk->iv, nbytes); - ctrptr = (n > AES_BLOCK_SIZE) ? ctrblk : walk->iv; - k = cpacf_kmctr(ctx->fc, param, walk->dst.virt.addr, - walk->src.virt.addr, n, ctrptr); - if (k) { - if (ctrptr == ctrblk) - memcpy(walk->iv, ctrptr + k - AES_BLOCK_SIZE, - AES_BLOCK_SIZE); - crypto_inc(walk->iv, AES_BLOCK_SIZE); - rc = skcipher_walk_done(walk, nbytes - k); - if (rc) { - if (locked) - mutex_unlock(&ctrblk_lock); - goto out; - } - } - if (k < n) { - if (!maysleep) { - if (locked) - mutex_unlock(&ctrblk_lock); - rc = -EKEYEXPIRED; - goto out; - } - rc = paes_convert_key(ctx, tested); - if (rc) { - if (locked) - mutex_unlock(&ctrblk_lock); - goto out; - } - spin_lock_bh(&ctx->pk_lock); - memcpy(param->key, ctx->pk.protkey, sizeof(param->key)); - spin_unlock_bh(&ctx->pk_lock); - } + if (down_trylock(&ctrblk_sem) == 0) { + rc = __ctr_paes_do_crypt(ctx, param, walk, tested, maysleep, true); + up(&ctrblk_sem); + } else { + rc = __ctr_paes_do_crypt(ctx, param, walk, tested, maysleep, false); } - if (locked) - mutex_unlock(&ctrblk_lock); /* final block may be < AES_BLOCK_SIZE, copy only nbytes */ - if (nbytes) { + if (!rc && walk->nbytes > 0) { memset(buf, 0, AES_BLOCK_SIZE); - memcpy(buf, walk->src.virt.addr, nbytes); + memcpy(buf, walk->src.virt.addr, walk->nbytes); while (1) { if (cpacf_kmctr(ctx->fc, param, buf, buf, AES_BLOCK_SIZE, @@ -1020,7 +1027,7 @@ static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, memcpy(param->key, ctx->pk.protkey, sizeof(param->key)); spin_unlock_bh(&ctx->pk_lock); } - memcpy(walk->dst.virt.addr, buf, nbytes); + memcpy(walk->dst.virt.addr, buf, walk->nbytes); crypto_inc(walk->iv, AES_BLOCK_SIZE); rc = skcipher_walk_done(walk, 0); } From 5b97b969030c099333d973be420edef0d6452e0f Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:33 +0200 Subject: [PATCH 0555/1317] s390/crypto: Fix missing cra_flags in paes_s390 The 4 algorithms implemented in paes_s390 never had any cra_flags set. So add code which sets the cra_flag to CRYPTO_ALG_ASYNC and CRYPTO_ALG_NO_FALLBACK. Fixes: 4ccd065a69df ("crypto: ahash - Add support for drivers with no fallback") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.17+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 991a26766d40..5b7664031ea3 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -570,6 +570,7 @@ static struct skcipher_engine_alg ecb_paes_alg = { .base.cra_name = "ecb(paes)", .base.cra_driver_name = "ecb-paes-s390", .base.cra_priority = 401, /* combo: aes + ecb + 1 */ + .base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NO_FALLBACK, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct s390_paes_ctx), .base.cra_module = THIS_MODULE, @@ -829,6 +830,7 @@ static struct skcipher_engine_alg cbc_paes_alg = { .base.cra_name = "cbc(paes)", .base.cra_driver_name = "cbc-paes-s390", .base.cra_priority = 402, /* cbc-paes-s390 + 1 */ + .base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NO_FALLBACK, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct s390_paes_ctx), .base.cra_module = THIS_MODULE, @@ -1141,6 +1143,7 @@ static struct skcipher_engine_alg ctr_paes_alg = { .base.cra_name = "ctr(paes)", .base.cra_driver_name = "ctr-paes-s390", .base.cra_priority = 402, /* ecb-paes-s390 + 1 */ + .base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NO_FALLBACK, .base.cra_blocksize = 1, .base.cra_ctxsize = sizeof(struct s390_paes_ctx), .base.cra_module = THIS_MODULE, @@ -1581,6 +1584,7 @@ static struct skcipher_engine_alg xts_paes_alg = { .base.cra_name = "xts(paes)", .base.cra_driver_name = "xts-paes-s390", .base.cra_priority = 402, /* ecb-paes-s390 + 1 */ + .base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NO_FALLBACK, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct s390_pxts_ctx), .base.cra_module = THIS_MODULE, From 3fec882c33d9b61983ee31456777f623dccd1a35 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:34 +0200 Subject: [PATCH 0556/1317] s390/crypto: Fix handling of EBUSY in PAES when req is pushed to crypto engine When a request is transferred to the engine via crypto_transfer_skcipher_request_to_engine() there are two return codes signaling a successful transfer: EINPROGRESS and EBUSY. However the correct handling of EBUSY was missing and has been added as a return code indicating a successful transfer to the crypto engine. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 5b7664031ea3..93e0e54ba2e8 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -463,6 +463,7 @@ static int ecb_paes_crypt(struct skcipher_request *req, unsigned long modifier) struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm); struct skcipher_walk *walk = &req_ctx->walk; bool tested = crypto_skcipher_tested(tfm); + bool cleanup = true; int rc; /* @@ -494,15 +495,17 @@ static int ecb_paes_crypt(struct skcipher_request *req, unsigned long modifier) if (rc == 0 || rc == -EKEYEXPIRED) { atomic_inc(&ctx->via_engine_ctr); rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS && walk->nbytes) + if (cleanup && walk->nbytes) skcipher_walk_done(walk, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(&req_ctx->param, sizeof(req_ctx->param)); pr_debug("rc=%d\n", rc); return rc; @@ -723,6 +726,7 @@ static int cbc_paes_crypt(struct skcipher_request *req, unsigned long modifier) struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm); struct skcipher_walk *walk = &req_ctx->walk; bool tested = crypto_skcipher_tested(tfm); + bool cleanup = true; int rc; /* @@ -754,15 +758,17 @@ static int cbc_paes_crypt(struct skcipher_request *req, unsigned long modifier) if (rc == 0 || rc == -EKEYEXPIRED) { atomic_inc(&ctx->via_engine_ctr); rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS && walk->nbytes) + if (cleanup && walk->nbytes) skcipher_walk_done(walk, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(&req_ctx->param, sizeof(req_ctx->param)); pr_debug("rc=%d\n", rc); return rc; @@ -1047,6 +1053,7 @@ static int ctr_paes_crypt(struct skcipher_request *req) struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm); struct skcipher_walk *walk = &req_ctx->walk; bool tested = crypto_skcipher_tested(tfm); + bool cleanup = true; int rc; /* @@ -1077,15 +1084,17 @@ static int ctr_paes_crypt(struct skcipher_request *req) if (rc == 0 || rc == -EKEYEXPIRED) { atomic_inc(&ctx->via_engine_ctr); rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS && walk->nbytes) + if (cleanup && walk->nbytes) skcipher_walk_done(walk, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(&req_ctx->param, sizeof(req_ctx->param)); pr_debug("rc=%d\n", rc); return rc; @@ -1477,6 +1486,7 @@ static inline int xts_paes_crypt(struct skcipher_request *req, unsigned long mod struct s390_pxts_ctx *ctx = crypto_skcipher_ctx(tfm); struct skcipher_walk *walk = &req_ctx->walk; bool tested = crypto_skcipher_tested(tfm); + bool cleanup = true; int rc; /* @@ -1508,15 +1518,17 @@ static inline int xts_paes_crypt(struct skcipher_request *req, unsigned long mod if (rc == 0 || rc == -EKEYEXPIRED) { atomic_inc(&ctx->via_engine_ctr); rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS && walk->nbytes) + if (cleanup && walk->nbytes) skcipher_walk_done(walk, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(&req_ctx->param, sizeof(req_ctx->param)); pr_debug("rc=%d\n", rc); return rc; From 330148371401de474b656eaf521861f12ec1a1ce Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:35 +0200 Subject: [PATCH 0557/1317] s390/crypto: Fix handling of EBUSY in PHMAC when req is pushed to crypto engine When a request is transferred to the engine via crypto_transfer_hash_request_to_engine() there are two return codes signaling a successful transfer: EINPROGRESS and EBUSY. However the correct handling of EBUSY was missing and has been added as a return code indicating a successful transfer to the crypto engine. Fixes: cbbc675506cc ("crypto: s390 - New s390 specific protected key hash phmac") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.17+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/phmac_s390.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/arch/s390/crypto/phmac_s390.c b/arch/s390/crypto/phmac_s390.c index 020a1beb2e22..532fe6c4e89c 100644 --- a/arch/s390/crypto/phmac_s390.c +++ b/arch/s390/crypto/phmac_s390.c @@ -62,8 +62,10 @@ static inline int hwh_prepare(struct ahash_request *req, */ static inline int hwh_advance(struct hash_walk_helper *hwh, int n) { - if (n < 0) + if (n < 0) { + hwh->walkbytes = n; return crypto_hash_walk_done(&hwh->walk, n); + } hwh->walkbytes -= n; hwh->walkaddr += n; @@ -606,6 +608,7 @@ static int phmac_update(struct ahash_request *req) struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm); struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx; struct hash_walk_helper *hwh = &req_ctx->hwh; + bool cleanup = true; int rc; /* prep the walk in the request context */ @@ -629,12 +632,15 @@ static int phmac_update(struct ahash_request *req) req_ctx->async_op = OP_UPDATE; atomic_inc(&tfm_ctx->via_engine_ctr); rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&tfm_ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) { - hwh_advance(hwh, rc); + if (cleanup) { + if (hwh->walkbytes > 0) + hwh_advance(hwh, rc); memzero_explicit(kmac_ctx, sizeof(*kmac_ctx)); } @@ -649,6 +655,7 @@ static int phmac_final(struct ahash_request *req) struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm); struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx; + bool cleanup = true; int rc = 0; /* Try synchronous operation if no active engine usage */ @@ -667,12 +674,14 @@ static int phmac_final(struct ahash_request *req) req_ctx->async_op = OP_FINAL; atomic_inc(&tfm_ctx->via_engine_ctr); rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&tfm_ctx->via_engine_ctr); } out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(kmac_ctx, sizeof(*kmac_ctx)); pr_debug("rc=%d\n", rc); return rc; @@ -685,6 +694,7 @@ static int phmac_finup(struct ahash_request *req) struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm); struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx; struct hash_walk_helper *hwh = &req_ctx->hwh; + bool cleanup = true; int rc; /* prep the walk in the request context */ @@ -716,15 +726,17 @@ static int phmac_finup(struct ahash_request *req) /* req->async_op has been set to either OP_FINUP or OP_FINAL */ atomic_inc(&tfm_ctx->via_engine_ctr); rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&tfm_ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (cleanup && hwh->walkbytes > 0) hwh_advance(hwh, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(kmac_ctx, sizeof(*kmac_ctx)); pr_debug("rc=%d\n", rc); return rc; From ac1481320110b803ab9b79ab4d2ca11a74fc05f2 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:36 +0200 Subject: [PATCH 0558/1317] s390/crypto: Fix wrong return code to engine in asynch callbacks When crypto_finalize_hash_request() or crypto_finalize_skcipher_request() explicitly completes a request, the do_one_request callback must return 0 to indicate successful handling. Returning a negative error code causes the crypto engine to assume the driver failed to take ownership and triggers a second completion via crypto_request_complete(), resulting in a double completion. This pattern occurs in paes_s390.c 4 times and once in phmac_s390.c. Fixed in phmac_do_one_request() and all four paes do_one_request callbacks (ecb, cbc, ctr, xts) by returning 0 after explicit finalization instead of propagating the error code. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 8 ++++---- arch/s390/crypto/phmac_s390.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 93e0e54ba2e8..a4b972459f52 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -565,7 +565,7 @@ static int ecb_paes_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&ctx->via_engine_ctr); crypto_finalize_skcipher_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } static struct skcipher_engine_alg ecb_paes_alg = { @@ -828,7 +828,7 @@ static int cbc_paes_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&ctx->via_engine_ctr); crypto_finalize_skcipher_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } static struct skcipher_engine_alg cbc_paes_alg = { @@ -1144,7 +1144,7 @@ static int ctr_paes_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&ctx->via_engine_ctr); crypto_finalize_skcipher_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } static struct skcipher_engine_alg ctr_paes_alg = { @@ -1588,7 +1588,7 @@ static int xts_paes_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&ctx->via_engine_ctr); crypto_finalize_skcipher_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } static struct skcipher_engine_alg xts_paes_alg = { diff --git a/arch/s390/crypto/phmac_s390.c b/arch/s390/crypto/phmac_s390.c index 532fe6c4e89c..283a00754a06 100644 --- a/arch/s390/crypto/phmac_s390.c +++ b/arch/s390/crypto/phmac_s390.c @@ -926,7 +926,7 @@ static int phmac_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&tfm_ctx->via_engine_ctr); crypto_finalize_hash_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } #define S390_ASYNC_PHMAC_ALG(x) \ From 7a08507ea5b4d06ad8d269287913573f34467565 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:37 +0200 Subject: [PATCH 0559/1317] s390/crypto: Map EBUSY to EIO when key conversion fails repeatedly When hardware persistently returns -EBUSY after exhausting retries, the error propagates to crypto_finalize_*_request(). The crypto API's completion wrapper treats -EBUSY as a queueing status and swallows it, preventing the completion callback from firing. This causes callers using crypto_wait_req() to block indefinitely. Translate persistent -EBUSY to -EIO after retry exhaustion to ensure proper error propagation and callback invocation. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 4 ++++ arch/s390/crypto/phmac_s390.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index a4b972459f52..f987bcbe8f35 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -220,6 +220,10 @@ static inline int convert_key(const u8 *key, unsigned int keylen, xflags); } + /* But finally map -EBUSY to -EIO to indicate an IO failure */ + if (rc == -EBUSY) + rc = -EIO; + out: pr_debug("rc=%d\n", rc); return rc; diff --git a/arch/s390/crypto/phmac_s390.c b/arch/s390/crypto/phmac_s390.c index 283a00754a06..bbf8a6809ecb 100644 --- a/arch/s390/crypto/phmac_s390.c +++ b/arch/s390/crypto/phmac_s390.c @@ -341,6 +341,10 @@ static inline int convert_key(const u8 *key, unsigned int keylen, xflags); } + /* But finally map -EBUSY to -EIO to indicate an IO failure */ + if (rc == -EBUSY) + rc = -EIO; + out: pr_debug("rc=%d\n", rc); return rc; From dc2136341be9835e70ba7c6b36904cf3683fd029 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 31 Aug 2026 10:38:38 +0200 Subject: [PATCH 0560/1317] s390/crypto: Enable CONTEXT_ANALYSIS Enable CONTEXT_ANALYSIS since s390's crypto code compiles now without warnings. Reviewed-by: Harald Freudenberger Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/crypto/Makefile b/arch/s390/crypto/Makefile index 48aeb0c0ffbd..1d6420813935 100644 --- a/arch/s390/crypto/Makefile +++ b/arch/s390/crypto/Makefile @@ -3,6 +3,8 @@ # Cryptographic API # +CONTEXT_ANALYSIS := y + obj-$(CONFIG_CRYPTO_AES_S390) += aes_s390.o obj-$(CONFIG_CRYPTO_PAES_S390) += paes_s390.o obj-$(CONFIG_S390_PRNG) += prng.o From fb5a08026a68be0ec5f660588311fb1091bf5d58 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 16 Aug 2026 00:51:09 +0100 Subject: [PATCH 0561/1317] mips: econet: fix unmet dependencies for ECONET ECONET selects EARLY_PRINTK_8250, SERIAL_OF_PLATFORM, and SERIAL_8250 without ensuring their dependencies, EARLY_PRINTK and TTY are met. This causes unmet dependencies: WARNING: unmet direct dependencies detected for SERIAL_8250 Depends on [n]: TTY [=n] && HAS_IOMEM [=y] && !S390 Selected by [y]: - ECONET [=y] WARNING: unmet direct dependencies detected for EARLY_PRINTK_8250 Depends on [n]: EARLY_PRINTK [=n] && USE_GENERIC_EARLY_PRINTK_8250 [=y] Selected by [y]: - ECONET [=y] WARNING: unmet direct dependencies detected for SERIAL_OF_PLATFORM Depends on [n]: TTY [=n] && HAS_IOMEM [=y] && SERIAL_8250 [=y] && OF [=y] Selected by [y]: - ECONET [=y] However, in the discussion of v1 of this patch, Arnd pointed out that these selects don't belong here in the first place. [1] Let's remove them to resolve the unmet dependencies. These unmet dependencies were found by kconfirm, a static analysis tool for Kconfig. Fixes: 79ee1d20e37c ("mips: econet: Fix incorrect Kconfig dependencies") Fixes: 35fb26f94dfa ("mips: Add EcoNet MIPS platform support") Suggested-by: Arnd Bergmann Signed-off-by: Julian Braha Tested-by: Caleb James DeLisle Signed-off-by: Thomas Bogendoerfer --- arch/mips/Kconfig | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index e2eb9627bd14..c3f69def2008 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -396,10 +396,7 @@ config ECONET bool "EcoNet MIPS family" select BOOT_RAW select DEBUG_ZBOOT if DEBUG_KERNEL - select EARLY_PRINTK_8250 select ECONET_EN751221_TIMER - select SERIAL_8250 - select SERIAL_OF_PLATFORM select SYS_SUPPORTS_BIG_ENDIAN select SYS_HAS_CPU_MIPS32_R1 select SYS_HAS_CPU_MIPS32_R2 From e261bfd3c042bccd25c82b04de079bdf8bdd8c3a Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Mon, 17 Aug 2026 22:03:44 +0000 Subject: [PATCH 0562/1317] MIPS: config: Add EcoNet EN751221 defconfig Add config based on OpenWrt for EN751221 boards. Of note: EcoNet bootloader has a 7.2MB kernel size limit so ZBOOT is assumed to be needed in all cases. Signed-off-by: Caleb James DeLisle Tested-by: Randy Dunlap Signed-off-by: Thomas Bogendoerfer --- MAINTAINERS | 1 + arch/mips/configs/econet_en751221_defconfig | 264 ++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 arch/mips/configs/econet_en751221_defconfig diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..9b9e20d039f6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9324,6 +9324,7 @@ F: Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.y F: Documentation/devicetree/bindings/mips/econet.yaml F: Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml F: arch/mips/boot/dts/econet/ +F: arch/mips/configs/econet_en751221_defconfig F: arch/mips/econet/ F: drivers/clocksource/timer-econet-en751221.c F: drivers/irqchip/irq-econet-en751221.c diff --git a/arch/mips/configs/econet_en751221_defconfig b/arch/mips/configs/econet_en751221_defconfig new file mode 100644 index 000000000000..b35d33fe4044 --- /dev/null +++ b/arch/mips/configs/econet_en751221_defconfig @@ -0,0 +1,264 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_KERNEL_XZ=y +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +# CONFIG_CROSS_MEMORY_ATTACH is not set +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_PREEMPT=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +# CONFIG_TIME_NS is not set +CONFIG_USER_NS=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_RD_GZIP is not set +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +# CONFIG_RD_ZSTD is not set +# CONFIG_INITRAMFS_PRESERVE_MTIME is not set +CONFIG_LD_DEAD_CODE_DATA_ELIMINATION=y +CONFIG_EXPERT=y +# CONFIG_SGETMASK_SYSCALL is not set +# CONFIG_IO_URING is not set +# CONFIG_RSEQ is not set +# CONFIG_CACHESTAT_SYSCALL is not set +CONFIG_ECONET=y +CONFIG_CPU_MIPS32_R2=y +CONFIG_ZBOOT_LOAD_ADDRESS=0x80020000 +CONFIG_ARCH_FORCE_MAX_ORDER=11 +# CONFIG_MIPS_FP_SUPPORT is not set +CONFIG_NR_CPUS=2 +CONFIG_HZ_100=y +CONFIG_MIPS_RAW_APPENDED_DTB=y +# CONFIG_SCHED_SMT is not set +CONFIG_JUMP_LABEL=y +# CONFIG_STACKPROTECTOR_STRONG is not set +# CONFIG_GCC_PLUGINS is not set +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_THROTTLING=y +# CONFIG_BLK_DEBUG_FS is not set +CONFIG_PARTITION_ADVANCED=y +CONFIG_OF_PARTITION=y +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_SLAB_FREELIST_RANDOM=y +CONFIG_SLAB_FREELIST_HARDENED=y +# CONFIG_COMPAT_BRK is not set +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_SYN_COOKIES=y +# CONFIG_INET_DIAG is not set +CONFIG_TCP_CONG_ADVANCED=y +# CONFIG_TCP_CONG_BIC is not set +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_HTCP is not set +# CONFIG_IPV6_SIT is not set +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_MPTCP=y +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_EGRESS is not set +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_MARK=y +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_CONNTRACK_TIMEOUT=y +# CONFIG_NF_CT_PROTO_SCTP is not set +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_IPV4=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_NF_LOG_IPV6=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_FQ_CODEL=y +CONFIG_NET_SCH_DEFAULT=y +CONFIG_DEFAULT_FQ_CODEL=y +CONFIG_NET_SWITCHDEV=y +CONFIG_NET_L3_MASTER_DEV=y +CONFIG_RFKILL=y +# CONFIG_LWTUNNEL_BPF is not set +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIEAER=y +# CONFIG_PCIEASPM is not set +CONFIG_PCI_MSI=y +# CONFIG_VGA_ARB is not set +CONFIG_PCIE_MEDIATEK=y +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_CFI=y +CONFIG_MTD_CFI_INTELEXT=y +CONFIG_MTD_CFI_AMDSTD=y +CONFIG_MTD_COMPLEX_MAPPINGS=y +CONFIG_MTD_SPI_NAND=y +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BEB_LIMIT=13 +CONFIG_MTD_UBI_BLOCK=y +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_LITEX is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +CONFIG_PPP=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOE=m +CONFIG_PPP_ASYNC=m +# CONFIG_WLAN_VENDOR_ADMTEK is not set +# CONFIG_WLAN_VENDOR_ATH is not set +# CONFIG_WLAN_VENDOR_ATMEL is not set +# CONFIG_WLAN_VENDOR_BROADCOM is not set +# CONFIG_WLAN_VENDOR_INTEL is not set +# CONFIG_WLAN_VENDOR_INTERSIL is not set +# CONFIG_WLAN_VENDOR_MARVELL is not set +# CONFIG_WLAN_VENDOR_MEDIATEK is not set +# CONFIG_WLAN_VENDOR_MICROCHIP is not set +# CONFIG_WLAN_VENDOR_PURELIFI is not set +# CONFIG_WLAN_VENDOR_RALINK is not set +# CONFIG_WLAN_VENDOR_REALTEK is not set +# CONFIG_WLAN_VENDOR_RSI is not set +# CONFIG_WLAN_VENDOR_SILABS is not set +# CONFIG_WLAN_VENDOR_ST is not set +# CONFIG_WLAN_VENDOR_TI is not set +# CONFIG_WLAN_VENDOR_ZYDAS is not set +# CONFIG_WLAN_VENDOR_QUANTENNA is not set +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_LEGACY_TIOCSTI is not set +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_OF_PLATFORM=y +# CONFIG_HW_RANDOM is not set +# CONFIG_DEVMEM is not set +CONFIG_SPI=y +# CONFIG_PTP_1588_CLOCK is not set +CONFIG_GPIOLIB=y +# CONFIG_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_MFD_SYSCON=y +# CONFIG_USB_PCI is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=y +CONFIG_LEDS_GPIO=m +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_NETDEV=y +CONFIG_STAGING=y +# CONFIG_MIPS_PLATFORM_DEVICES is not set +CONFIG_COMMON_CLK_EN7523=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_RESET_CONTROLLER=y +CONFIG_PHY_ECONET_PCIE=y +# CONFIG_DNOTIFY is not set +CONFIG_FANOTIFY=y +CONFIG_OVERLAY_FS=y +# CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW is not set +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_SUMMARY=y +CONFIG_JFFS2_FS_XATTR=y +CONFIG_JFFS2_COMPRESSION_OPTIONS=y +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_FILE_DIRECT=y +CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI_PERCPU=y +# CONFIG_SQUASHFS_ZLIB is not set +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_EMBEDDED=y +CONFIG_KEYS=y +CONFIG_INIT_STACK_NONE=y +CONFIG_FORTIFY_SOURCE=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_LIST_HARDENED=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_GCM=y +# CONFIG_CRYPTO_HW is not set +# CONFIG_XZ_DEC_X86 is not set +# CONFIG_XZ_DEC_POWERPC is not set +# CONFIG_XZ_DEC_ARM is not set +# CONFIG_XZ_DEC_ARMTHUMB is not set +# CONFIG_XZ_DEC_ARM64 is not set +# CONFIG_XZ_DEC_SPARC is not set +# CONFIG_XZ_DEC_RISCV is not set +CONFIG_PRINTK_TIME=y +# CONFIG_DEBUG_MISC is not set +CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y +CONFIG_DEBUG_INFO_REDUCED=y +CONFIG_FRAME_WARN=1024 +CONFIG_STRIP_ASM_SYMS=y +CONFIG_MAGIC_SYSRQ=y +# CONFIG_MAGIC_SYSRQ_SERIAL is not set +CONFIG_DEBUG_FS=y +# CONFIG_SLUB_DEBUG is not set +CONFIG_SCHED_STACK_END_CHECK=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=1 +CONFIG_RCU_CPU_STALL_TIMEOUT=60 +# CONFIG_RCU_TRACE is not set +# CONFIG_FTRACE is not set From bbc448c541ed90d52f9ec22f17394304e3bf170e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Fri, 28 Aug 2026 16:07:33 +0200 Subject: [PATCH 0563/1317] mips: select CONFIG_WEAK_REORDERING_BEYOND_LLSC from CONFIG_EYEQ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On I6500 CPU cores, lld and scd give no ordering guarantees (same as all other instructions). To respect the assumption that arch_cmpxchg() is fully ordered, we must inject sync instructions above and below our lld/scd loops using the already in place WEAK_REORDERING_BEYOND_LLSC infrastructure. Otherwise, bad things can happen: [ 34.054496] CPU 3 Unable to handle kernel paging request at virtual address 0000000000000000, epc == a80000080838e01c, ra == a80000080838dfc4 [ 34.054559] Oops[#1]: [ 34.069561] CPU: 3 UID: 0 PID: 170 Comm: pipe_race Not tainted 7.2.0-rc6-01553-gb73c35220968-dirty #103 VOLUNTARY [ 34.079932] Hardware name: Mobile EyeQ5 MP5 Evaluation board [ 34.085592] $ 0 : 0000000000000000 0000000000000001 0000000000000000 0000000000000000 [ 34.093616] $ 4 : a800000808ee2618 000000000b7a879d 0000000000001000 0000000000000000 [ 34.101638] $ 8 : 0000000000e3f2c9 0000000000000000 a800000808a2a9f8 0000000000000000 [ 34.109660] $12 : a8000008139ffcd8 ffffffff84080018 a80000080837fae0 7878787878787878 [ 34.117682] $16 : a800000807e82940 0000000000001000 0000000000000000 0000000000000000 [ 34.125704] $20 : a800000802920e00 a8000008139ffdf8 a800000802649400 0000000000e3f2c9 [ 34.133726] $24 : 0000000000000006 00000001200406e0 [ 34.141783] $28 : a8000008139fc000 a8000008139ffd10 0000000000e3f2c8 a80000080838dfc4 [ 34.149837] epc : a80000080838e01c anon_pipe_read+0xd4/0x428 [ 34.155697] ra : a80000080838dfc4 anon_pipe_read+0x7c/0x428 [ 34.161549] Status: 140000e3 KX SX UX KERNEL EXL IE [ 34.166551] Cause : 40800408 (ExcCode 02) [ 34.170574] BadVA : 0000000000000000 [ 34.174161] PrId : 0001b028 (MIPS I6500) [ 34.178183] Process pipe_race (pid: 170, threadinfo=000000005ca35720, task=00000000e1013890, tls=000000014ebbb780) [ 34.188568] Stack : a800000802649400 0000000000000000 0000000000000000 a8000008139ffdd0 [ 34.196623] 0000000000000fba a800000808ee0000 0000000000000001 a8000008130c3e80 [ 34.204676] a8000008080d1280 a8000008139ffd58 a8000008139ffd58 1dbd2b22ea1dd500 [ 34.212729] a800000802649400 a800000808ee0000 ffffffffffffffea 0000000000000001 [ 34.220783] 0000000000001000 0000000000000000 00000001200ae518 ffffffffffffffff [ 34.228836] 000000fffbe0e530 a80000080837edf4 000000fffbe0e530 0000000000000000 [ 34.236890] 0000000000000000 0000000000000000 000000014ebb55a0 0000000000001000 [ 34.244943] 0000000000000001 a800000802649400 0000000000000000 0000000000000000 [ 34.252996] 0000000000000000 0000400400000000 0000000000000000 1dbd2b22ea1dd500 [ 34.261049] 00000000140000e3 a800000802649400 a800000802649400 a800000808ee0000 [ 34.269103] ... [ 34.271568] Call Trace: [ 34.274026] [] anon_pipe_read+0xd4/0x428 [ 34.279533] [] vfs_read+0x25c/0x318 [ 34.284607] [] ksys_read+0x104/0x138 [ 34.289763] [] syscall_common+0x44/0x68 [ 34.295187] [ 34.296689] Code: f84000cf 02209825 de020010 d8400004 02002825 0040f809 02802025 f84000c3 [ 34.306504] [ 34.308099] ---[ end trace 0000000000000000 ]--- My initial reproducer was the xdp-tools test suite. A standalone reproducer would be an lld/scd loop that, when the read is reordered by the CPU, triggers a fault. We can achieve this from userspace by stressing an anonymous pipe, which uses a mutex. Program used: // SPDX-License-Identifier: GPL-2.0 // pipe_race.c - reproducer for MIPS LL/SC reordering vs fs/pipe.c // // Two userspace processes on an anonymous pipe: // parent = writer: tight write() loop // child = reader: tight read() loop #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include int main(void) { long wrsize = 70; // bytes per write() long rdsize = 4096; // bytes per read() int pfd[2]; char *buf; pid_t pid; int ret; ret = pipe(pfd); assert(!ret); pid = fork(); assert(pid >= 0); if (pid == 0) { /* reader */ close(pfd[1]); buf = malloc(rdsize); assert(buf); for (;;) { ssize_t n = read(pfd[0], buf, rdsize); if (n < 0 && errno == EINTR) continue; if (n <= 0) _exit(n < 0 ? 1 : 0); } } close(pfd[0]); /* writer */ buf = malloc(wrsize); assert(buf); memset(buf, 'x', wrsize); for (;;) { ssize_t n = write(pfd[1], buf, wrsize); if (n < 0 && errno == EINTR) continue; if (n != wrsize) break; } kill(pid, SIGKILL); wait(NULL); return 0; } Fixes: fbe0fae601b7 ("MIPS: mobileye: Add EyeQ6H support") Cc: stable@vger.kernel.org Signed-off-by: Théo Lebrun Reviewed-by: Jiaxun Yang Signed-off-by: Thomas Bogendoerfer --- arch/mips/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index c3f69def2008..d7c67cebe065 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -658,6 +658,7 @@ config EYEQ select USB_UHCI_BIG_ENDIAN_MMIO if CPU_BIG_ENDIAN select USE_OF select HOTPLUG_PARALLEL if HOTPLUG_CPU + select WEAK_REORDERING_BEYOND_LLSC help Select this to build a kernel supporting EyeQ SoC from Mobileye. From 126f16e0a1b353c2ba5c7e2c8626cfa865934f9f Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:33:55 +0000 Subject: [PATCH 0564/1317] MIPS: Octeon: apply USB FDT fixups also when USB is modular The uctl/usbn device-tree fixups in octeon_prune_device_tree() - which set the board's USB reference-clock frequency and type from __cvmx_helper_board_usb_get_clock_type() - are guarded by "#ifdef CONFIG_USB", which is false when USB is built as a module. The fixups then silently disappear and octeon-hcd sees whatever default the DTS carries (12MHz crystal in octeon_3xxx.dts), leaving the PHY dead or the bus erroring on boards with a different reference clock. Use IS_ENABLED() so USB=m gets the same fixups as USB=y. Fixes: 7fd57ab9d9cf ("MIPS: Octeon: Fix compile error when USB is not enabled.") Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Signed-off-by: Thomas Bogendoerfer --- arch/mips/cavium-octeon/octeon-platform.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/mips/cavium-octeon/octeon-platform.c b/arch/mips/cavium-octeon/octeon-platform.c index 47677b5d7ed0..f53c98372e0b 100644 --- a/arch/mips/cavium-octeon/octeon-platform.c +++ b/arch/mips/cavium-octeon/octeon-platform.c @@ -18,7 +18,7 @@ #include #include -#ifdef CONFIG_USB +#if IS_ENABLED(CONFIG_USB) #include #include #include @@ -1080,7 +1080,7 @@ int __init octeon_prune_device_tree(void) ; } -#ifdef CONFIG_USB +#if IS_ENABLED(CONFIG_USB) /* OHCI/UHCI USB */ alias_prop = fdt_getprop(initial_boot_params, aliases, "uctl", NULL); From 71c610aeb1770302ac9c9e0b9a4ecd37f1311928 Mon Sep 17 00:00:00 2001 From: Kazuki Hanai Date: Tue, 8 Sep 2026 20:00:53 +0900 Subject: [PATCH 0565/1317] ALSA: us122l: Prevent write upgrades for read mappings The hwdep mmap callback rejects read-buffer mappings that are initially writable, but leaves VM_MAYWRITE set on mappings created with PROT_READ. A process that can open the hwdep node O_RDWR can later use mprotect() to make the mapping writable. The read allocation begins with struct usb_stream. Its read_size member is used by the fault handler to decide which pages belong to the read buffer. The read VMA intentionally remains expandable because pcm_usb_stream uses mremap() after reading that size. Changing read_size first can therefore map and access pages beyond the allocation. The same member is also consumed by usb_stream_free(), where changing it can make free_pages_exact() release pages outside the allocation. Clear VM_MAYWRITE for read-buffer mappings after rejecting an initially writable VMA. This keeps the separate output-buffer mapping writable while preventing later permission upgrades. Fixes: 030a07e44129 ("ALSA: Add USB US122L driver") Cc: stable@vger.kernel.org Signed-off-by: Kazuki Hanai Link: https://patch.msgid.link/20260908110053.2950767-1-hnkz.64@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/us122l.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index f00b53346abd..a5db0d044ef9 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -180,8 +180,11 @@ static int usb_stream_hwdep_mmap(struct snd_hwdep *hw, guard(mutex)(&us122l->mutex); s = us122l->sk.s; read = offset < s->read_size; - if (read && area->vm_flags & VM_WRITE) - return -EPERM; + if (read) { + if (area->vm_flags & VM_WRITE) + return -EPERM; + vm_flags_clear(area, VM_MAYWRITE); + } /* if userspace tries to mmap beyond end of our buffer, fail */ if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) { dev_warn(hw->card->dev, "%s: size %lu > %u\n", __func__, From 7284788743121ec8bed556b00f830dc52ad9955d Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 8 Sep 2026 22:41:53 +0900 Subject: [PATCH 0566/1317] ALSA: hda: Report a change when only the channel status bytes move The put() callback of "IEC958 Playback Default" returns whether the converted register value moved. The convert_from_spdif_status() helper reads part of the first two channel status bytes and none of the last two, while the get() callback returns all four. So a write that lands only in the bits it does not read changes what userspace reads back and reports no change. Of the 31 bits above the mode bit, 20 are such bits in consumer mode and 29 in professional mode. The core notifies only on a positive return. Toggling status[2] bit 0 on an HDA HDMI codec moves the read-back from 04 00 00 00 to 04 00 01 00 with no event. Toggling the non-audio bit in status[0] gives one. Compare the stored status as well, the way the ac97 code does. The write to the codec stays gated on the converted value. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260908134153.1614273-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/common/codec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 7d17d773cfbf..5d9b53bd64ea 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -2277,6 +2277,7 @@ static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, int idx = kcontrol->private_value; struct hda_spdif_out *spdif; hda_nid_t nid; + unsigned int old_status; unsigned short val; int change; @@ -2285,6 +2286,7 @@ static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, guard(mutex)(&codec->spdif_mutex); spdif = snd_array_elem(&codec->spdif_out, idx); nid = spdif->nid; + old_status = spdif->status; spdif->status = ucontrol->value.iec958.status[0] | ((unsigned int)ucontrol->value.iec958.status[1] << 8) | ((unsigned int)ucontrol->value.iec958.status[2] << 16) | @@ -2295,7 +2297,7 @@ static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, spdif->ctls = val; if (change && nid != (u16)-1) set_dig_out_convert(codec, nid, val & 0xff, (val >> 8) & 0xff); - return change; + return change || spdif->status != old_status; } #define snd_hda_spdif_out_switch_info snd_ctl_boolean_mono_info From cf4d35896621b7298eef51b7a465e5c0cb22f670 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 14:39:44 -0300 Subject: [PATCH 0567/1317] smb: client: fix uid/gid override in getattr with posix extensions When mounting with 'multiuser,posix' options, cifs_getattr() overrides the server-provided uid/gid with the current process's fsuid/fsgid. This is because the condition only checks for unix extensions (tcon->unix_ext) but not posix extensions (tcon->posix_extensions). With SMB3 POSIX extensions, the server provides real uid/gid values just like with unix extensions, so they should be preserved rather than replaced with the caller's credentials. Add a tcon->posix_extensions check to the condition so that uid/gid from the server are properly reported in stat results. Reported-by: Arthur Lesuisse Closes: https://lore.kernel.org/r/DB9P190MB2012266F6B8DECBE5D26A1798DB52@DB9P190MB2012.EURP190.PROD.OUTLOOK.COM Suggested-by: Arthur Lesuisse Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/inode.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 12ed8db10e00..49f9993ad567 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2992,14 +2992,14 @@ int cifs_getattr(struct mnt_idmap *idmap, const struct path *path, stat->attributes |= STATX_ATTR_ENCRYPTED; /* - * If on a multiuser mount without unix extensions or cifsacl being - * enabled, and the admin hasn't overridden them, set the ownership - * to the fsuid/fsgid of the current process. + * If on a multiuser mount without unix extensions, posix extensions + * or cifsacl being enabled, and the admin hasn't overridden them, + * set the ownership to the fsuid/fsgid of the current process. */ sbflags = cifs_sb_flags(cifs_sb); if ((sbflags & CIFS_MOUNT_MULTIUSER) && !(sbflags & CIFS_MOUNT_CIFS_ACL) && - !tcon->unix_ext) { + !tcon->unix_ext && !tcon->posix_extensions) { if (!(sbflags & CIFS_MOUNT_OVERR_UID)) stat->uid = current_fsuid(); if (!(sbflags & CIFS_MOUNT_OVERR_GID)) From 18a72975e9f35aadecc75b031f693f2d1f49308f Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 14:40:04 -0300 Subject: [PATCH 0568/1317] smb: client: honor forceuid/forcegid when mapping SIDs to uid/gid When the administrator mounts with forceuid or forcegid (uid=/gid= mount options), they expect all files to appear owned by the specified user/group. However, several code paths unconditionally called sid_to_id() to overwrite cf_uid/cf_gid with server-provided values, ignoring the administrator's explicit override: - smb311_posix_info_to_fattr() (stat via POSIX extensions) - cifs_posix_to_fattr() (readdir via POSIX extensions) - parse_sec_desc() (CIFS ACL ownership mapping) This allowed an untrusted server to dictate local file ownership even when the mount was configured to force specific uid/gid values. Fix all three call sites to check CIFS_MOUNT_OVERR_UID and CIFS_MOUNT_OVERR_GID before calling sid_to_id(), following the same pattern already used by cifs_unix_basic_to_fattr() for unix extensions. Closes: https://sashiko.dev/#/patchset/20260906155816.603278-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/cifsacl.c | 29 ++++++++++++++++++----------- fs/smb/client/inode.c | 9 +++++++-- fs/smb/client/readdir.c | 9 +++++++-- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 213a421bf8e9..def8908dd7e9 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1346,6 +1346,7 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb, { int rc = 0; struct smb_sid *owner_sid_ptr, *group_sid_ptr; + unsigned int sbflags = cifs_sb_flags(cifs_sb); struct smb_acl *dacl_ptr; /* no need for SACL ptr */ char *end_of_acl; __u32 dacloffset, osidoffset, gsidoffset; @@ -1364,17 +1365,21 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb, cifs_dbg(NOISY, "revision %d type 0x%x ooffset 0x%x goffset 0x%x sacloffset 0x%x dacloffset 0x%x\n", pntsd->revision, pntsd->type, osidoffset, gsidoffset, le32_to_cpu(pntsd->sacloffset), dacloffset); -/* cifs_dump_mem("owner_sid: ", owner_sid_ptr, 64); */ + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + rc = sid_from_sd(pntsd, acl_len, osidoffset, &owner_sid_ptr); if (rc) { cifs_dbg(FYI, "%s: Error %d parsing Owner SID\n", __func__, rc); return rc; } - rc = sid_to_id(cifs_sb, owner_sid_ptr, fattr, SIDOWNER); - if (rc) { - cifs_dbg(FYI, "%s: Error %d mapping Owner SID to uid\n", - __func__, rc); - return rc; + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) { + rc = sid_to_id(cifs_sb, owner_sid_ptr, fattr, SIDOWNER); + if (rc) { + cifs_dbg(FYI, "%s: Error %d mapping Owner SID to uid\n", + __func__, rc); + return rc; + } } rc = sid_from_sd(pntsd, acl_len, gsidoffset, &group_sid_ptr); @@ -1383,11 +1388,13 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb, __func__, rc); return rc; } - rc = sid_to_id(cifs_sb, group_sid_ptr, fattr, SIDGROUP); - if (rc) { - cifs_dbg(FYI, "%s: Error %d mapping Group SID to gid\n", - __func__, rc); - return rc; + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) { + rc = sid_to_id(cifs_sb, group_sid_ptr, fattr, SIDGROUP); + if (rc) { + cifs_dbg(FYI, "%s: Error %d mapping Group SID to gid\n", + __func__, rc); + return rc; + } } if (dacloffset) { diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 49f9993ad567..1fe0ef0a95db 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -851,6 +851,7 @@ static void smb311_posix_info_to_fattr(struct cifs_fattr *fattr, struct smb311_posix_qinfo *info = &data->posix_fi; struct cifs_sb_info *cifs_sb = CIFS_SB(sb); struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); + unsigned int sbflags = cifs_sb_flags(cifs_sb); memset(fattr, 0, sizeof(*fattr)); @@ -895,8 +896,12 @@ static void smb311_posix_info_to_fattr(struct cifs_fattr *fattr, fattr->cf_symlink_target = data->symlink_target; data->symlink_target = NULL; } - sid_to_id(cifs_sb, &data->posix_owner, fattr, SIDOWNER); - sid_to_id(cifs_sb, &data->posix_group, fattr, SIDGROUP); + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) + sid_to_id(cifs_sb, &data->posix_owner, fattr, SIDOWNER); + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) + sid_to_id(cifs_sb, &data->posix_group, fattr, SIDGROUP); cifs_dbg(FYI, "POSIX query info: mode 0x%x uniqueid 0x%llx nlink %d\n", fattr->cf_mode, fattr->cf_uniqueid, fattr->cf_nlink); diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 32a75afca8f5..1ea84f4ada39 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -242,6 +242,7 @@ static void cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, struct cifs_sb_info *cifs_sb) { + unsigned int sbflags = cifs_sb_flags(cifs_sb); struct smb2_posix_info_parsed parsed; posix_info_parse(info, NULL, &parsed); @@ -281,8 +282,12 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, le32_to_cpu(info->ReparseTag), le32_to_cpu(info->Mode)); - sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER); - sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP); + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) + sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER); + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) + sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP); } static void __dir_info_to_fattr(struct cifs_fattr *fattr, const void *info) From cd2b2b57921d4caa7875e83198bb2aa71254328b Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 14:40:23 -0300 Subject: [PATCH 0569/1317] smb: client: fix WSL reparse point uid/gid override wsl_to_fattr() unconditionally overwrites cf_uid/cf_gid with values from WSL extended attributes ($LXUID/$LXGID), ignoring the forceuid and forcegid mount options. Fix this by initializing cf_uid/cf_gid to the mount defaults and gating the $LXUID/$LXGID EA parsing on forceuid/forcegid. Closes: https://sashiko.dev/#/patchset/20260906190803.667489-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 5cc5b0410d48..178da801e775 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1137,10 +1137,14 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, struct cifs_sb_info *cifs_sb, u32 tag, struct cifs_fattr *fattr) { + unsigned int sbflags = cifs_sb_flags(cifs_sb); struct smb2_file_full_ea_info *ea; bool have_xattr_dev = false; u32 next = 0; + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + switch (tag) { case IO_REPARSE_TAG_LX_SYMLINK: fattr->cf_mode |= S_IFLNK; @@ -1177,11 +1181,13 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, nlen = ea->ea_name_length; v = (void *)((u8 *)ea->ea_data + ea->ea_name_length + 1); - if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) - fattr->cf_uid = wsl_make_kuid(cifs_sb, v); - else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) - fattr->cf_gid = wsl_make_kgid(cifs_sb, v); - else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { + if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) { + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) + fattr->cf_uid = wsl_make_kuid(cifs_sb, v); + } else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) { + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) + fattr->cf_gid = wsl_make_kgid(cifs_sb, v); + } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { /* File type in reparse point tag and in xattr mode must match. */ if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v))) return false; From da6e25842431982d5a53cf00d925b98c690f4467 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 14:40:39 -0300 Subject: [PATCH 0570/1317] smb: client: avoid using uninitialized SIDs in cifs_posix_to_fattr() cifs_posix_to_fattr() ignores the return value of posix_info_parse(). When a malformed POSIX directory entry is encountered (e.g. invalid SID lengths from an untrusted server), posix_info_parse() returns -1 without populating the 'parsed' struct. The uninitialized stack memory in parsed.owner and parsed.group is then passed to sid_to_id(), which processes the garbage bytes and passes them to request_key() to construct a SID string, potentially leaking kernel stack contents to the userspace idmap daemon. Fix this by checking the return value and skipping the SID-to-id mapping when parsing fails. The remaining fattr fields (timestamps, mode, etc.) are populated directly from the 'info' pointer so they are unaffected. Closes: https://sashiko.dev/#/patchset/20260906172005.627163-1-pc%40manguebit.org Closes: https://sashiko.dev/#/patchset/20260906181540.647469-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/readdir.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 1ea84f4ada39..9530e5b01564 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -244,8 +244,9 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, { unsigned int sbflags = cifs_sb_flags(cifs_sb); struct smb2_posix_info_parsed parsed; + int rc; - posix_info_parse(info, NULL, &parsed); + rc = posix_info_parse(info, NULL, &parsed); memset(fattr, 0, sizeof(*fattr)); fattr->cf_uniqueid = le64_to_cpu(info->Inode); @@ -284,10 +285,15 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, fattr->cf_uid = cifs_sb->ctx->linux_uid; fattr->cf_gid = cifs_sb->ctx->linux_gid; - if (!(sbflags & CIFS_MOUNT_OVERR_UID)) - sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER); - if (!(sbflags & CIFS_MOUNT_OVERR_GID)) - sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP); + if (rc < 0) { + cifs_dbg(VFS, "%s: failed to parse SIDs: %d\n", + __func__, rc); + } else { + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) + sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER); + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) + sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP); + } } static void __dir_info_to_fattr(struct cifs_fattr *fattr, const void *info) From fa7a2cfcf1e6117fc478cae6809c66c518740969 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 16:01:04 -0300 Subject: [PATCH 0571/1317] smb: client: fix file type corruption in wsl_to_fattr() Setting the file type in cf_mode without clearing the existing S_IFMT bits first is wrong as it corrupts the file type when cf_mode already has type bits set (e.g. S_IFREG | S_IFCHR == S_IFLNK). Clear S_IFMT before the switch statement. Closes: https://sashiko.dev/#/patchset/20260906172005.627163-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 178da801e775..8a19dee564b8 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1145,6 +1145,7 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, fattr->cf_uid = cifs_sb->ctx->linux_uid; fattr->cf_gid = cifs_sb->ctx->linux_gid; + fattr->cf_mode &= ~S_IFMT; switch (tag) { case IO_REPARSE_TAG_LX_SYMLINK: fattr->cf_mode |= S_IFLNK; From 65d5dbdc089be42fc48a6f77bc6b648307f34b17 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 16:01:16 -0300 Subject: [PATCH 0572/1317] smb: client: fix file type corruption in posix_reparse_to_fattr() Setting the file type in cf_mode without clearing the existing S_IFMT bits first is wrong as it corrupts the file type when cf_mode already has type bits set (e.g. S_IFREG | S_IFCHR == S_IFLNK). Use a local ftype variable to collect the new file type and apply it after validation succeeds, clearing S_IFMT and setting the new type in a single assignment. This avoids stripping cf_mode on malformed reparse points where the function returns false early. Closes: https://sashiko.dev/#/patchset/20260906172005.627163-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 8a19dee564b8..616ca2dbfac4 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1212,6 +1212,7 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb, struct cifs_open_info_data *data) { struct reparse_nfs_data_buffer *buf = (struct reparse_nfs_data_buffer *)data->reparse.buf; + umode_t ftype; if (buf == NULL) return true; @@ -1227,7 +1228,7 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb, WARN_ON_ONCE(1); return false; } - fattr->cf_mode |= S_IFCHR; + ftype = S_IFCHR; fattr->cf_rdev = reparse_mkdev(buf->DataBuffer); break; case NFS_SPECFILE_BLK: @@ -1235,22 +1236,23 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb, WARN_ON_ONCE(1); return false; } - fattr->cf_mode |= S_IFBLK; + ftype = S_IFBLK; fattr->cf_rdev = reparse_mkdev(buf->DataBuffer); break; case NFS_SPECFILE_FIFO: - fattr->cf_mode |= S_IFIFO; + ftype = S_IFIFO; break; case NFS_SPECFILE_SOCK: - fattr->cf_mode |= S_IFSOCK; + ftype = S_IFSOCK; break; case NFS_SPECFILE_LNK: - fattr->cf_mode |= S_IFLNK; + ftype = S_IFLNK; break; default: WARN_ON_ONCE(1); return false; } + fattr->cf_mode = (fattr->cf_mode & ~S_IFMT) | ftype; return true; } From 6bd360447941357e959414a525aa62576a448116 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 16:01:24 -0300 Subject: [PATCH 0573/1317] smb: client: fix file type corruption in cifs_reparse_point_to_fattr() Setting the file type in cf_mode without clearing the existing S_IFMT bits first is wrong as it corrupts the file type when cf_mode already has type bits set (e.g. S_IFREG | S_IFLNK == S_IFDIR | S_IFREG). Clear S_IFMT before setting S_IFLNK for native and SMB1 symlinks. Closes: https://sashiko.dev/#/patchset/20260906181540.647469-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 616ca2dbfac4..b6bded042e78 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1280,6 +1280,7 @@ bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, break; case 0: /* SMB1 symlink */ case IO_REPARSE_TAG_SYMLINK: + fattr->cf_mode &= ~S_IFMT; fattr->cf_mode |= S_IFLNK; break; default: From 7e125889f1705fc6326679a3db3b4159f7a8c87e Mon Sep 17 00:00:00 2001 From: Yorick Rommers Date: Mon, 7 Sep 2026 14:12:28 +0200 Subject: [PATCH 0574/1317] ASoC: amd: acp-da7219-max98357a: don't bind on Raven/Picasso boards The "AMDI5682" ACPI HID is matched by two AMD ASoC machine drivers: cz-da7219-max98357a (this driver, Carrizo/Stoney) and acp3x-alc5682-max98357 (Raven/Picasso). cz-da7219-max98357a is linked first and probes the platform device first; its DAI links reference the Stoney ACP, which is absent on Raven/Picasso, so its card can never be instantiated there. This was harmless until commit 42d99857d6f0 ("ASoC: core: Move all users to deferrable card binding"): devm_snd_soc_register_card() now returns 0 for a card left pending instead of propagating -EPROBE_DEFER, so cz_probe() succeeds and permanently binds AMDI5682. acp3x-alc5682-max98357 never binds and the internal speakers and headphone jack get no card. Detect Raven/Picasso (and later) by the ACP3.x audio coprocessor's dedicated PCI function (1022:15e2); Carrizo/Stoney reach the ACP through the GPU driver and have no such device. Return -ENODEV so the driver core continues probing AMDI5682 with acp3x-alc5682-max98357. Fixes: 42d99857d6f0 ("ASoC: core: Move all users to deferrable card binding") Cc: stable@vger.kernel.org Signed-off-by: Yorick Rommers Tested-by: Yorick Rommers Link: https://patch.msgid.link/20260907121228.13754-1-yorick-rommers@hotmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-da7219-max98357a.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index af559653e625..1ac729a58bb4 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "acp.h" #include "../codecs/da7219.h" @@ -742,6 +743,18 @@ static const struct regulator_desc acp_da7219_desc = { .n_voltages = 1, }; +/* + * The ACP3.x+ (Raven/Picasso and later) audio coprocessor is a dedicated PCI + * function. Carrizo/Stoney - the only platforms handled by this driver - reach + * the ACP through the GPU driver and have no such device. + */ +#define ACP3X_PCI_DEV_ID 0x15e2 + +static const struct pci_device_id acp3x_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP3X_PCI_DEV_ID) }, + { 0, }, +}; + static int cz_probe(struct platform_device *pdev) { int ret; @@ -750,6 +763,16 @@ static int cz_probe(struct platform_device *pdev) struct regulator_dev *rdev; struct device *dev = &pdev->dev; + /* + * AMDI5682 is also matched by acp3x-alc5682-max98357 (Raven/Picasso). + * If the ACP3.x PCI function is present this is such a board; return + * -ENODEV so that driver binds instead. + */ + if (pci_dev_present(acp3x_pci_ids)) { + dev_info(dev, "ACP3.x PCI device present, deferring to acp3x-alc5682-max98357\n"); + return -ENODEV; + } + card = (struct snd_soc_card *)acp_soc_is_rltk_max(dev); if (!card) return -ENODEV; From f77de4c33f0edbb33411f92a35d7196965597e6d Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 7 Sep 2026 17:54:44 +0800 Subject: [PATCH 0575/1317] spi: dt-bindings: snps,dw-apb-ssi: Add compatible for UltraRISC DP1000 SoC Add the SoC-specific compatible string and use the generic one as fallback for the UltraRISC DP1000 SPI controller. The DP1000 integrates two SPI controllers. SPI0 supports standard, dual and quad transfers with three native chip-select signals. SPI1 supports standard transfers with four native chip-select signals. Both controllers have one register range and separate reference and APB interface clocks. Signed-off-by: Jia Wang Acked-by: Conor Dooley Link: https://patch.msgid.link/20260907-ultrarisc-dts-v2-5-5eb4c97477c5@ultrarisc.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/snps,dw-apb-ssi.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index 1e70c6804d5d..e8d6d5d858b6 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -44,6 +44,21 @@ allOf: else: properties: starfive,sfc-filter-syscon: false + - if: + properties: + compatible: + contains: + const: ultrarisc,dp1000-spi + then: + properties: + reg: + maxItems: 1 + clocks: + minItems: 2 + clock-names: + minItems: 2 + required: + - clock-names properties: compatible: @@ -63,6 +78,7 @@ properties: - mscc,jaguar2-spi - sophgo,sg2042-spi - thead,th1520-spi + - ultrarisc,dp1000-spi - const: snps,dw-apb-ssi - description: Vendor controllers compatible with v1.01a items: From 159720704d9d652b64390c11fb971e15b0a78d23 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Tue, 8 Sep 2026 14:47:29 +0530 Subject: [PATCH 0576/1317] drm/drm_exec: fix up contended obj when num_objects is 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_exec_prepare_array() silently returns success without calling drm_exec_lock_contended() when num_objects is zero. This breaks the invariant upheld by drm_exec_lock_obj(), where every entry point into the locking sequence must first attempt to lock any previously contended object before proceeding. Drivers that chain multiple drm_exec_prepare_array() calls per drm_exec_until_all_locked() iteration (e.g. amdgpu's userq signal/wait ioctls, which prepare separate read and write BO arrays) can pass an empty array for one of the two calls. If contention is hit while preparing the non-empty array, exec->contended is set and the loop retries; on retry, the empty-array call preceding it is a no-op that never clears exec->contended, so drm_exec_retry_on_contention() immediately jumps back to the top of the loop without ever reaching the call that would resolve the contention. This spins forever. Fix it by having drm_exec_prepare_array() call drm_exec_lock_contended() directly when num_objects is zero, so a pending contended object dont loop infinitely. Fixes: 09593216bff1 ("drm: execution context for GEM buffers v7") CC: stable@vger.kernel.org # v6.6+ Signed-off-by: Sunil Khatri Link: https://lore.kernel.org/r/20260908091729.2749399-1-sunil.khatri@amd.com Reviewed-by: Christian König Signed-off-by: Christian König --- drivers/gpu/drm/drm_exec.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/drm_exec.c b/drivers/gpu/drm/drm_exec.c index 41034a5996ff..2453ec41360f 100644 --- a/drivers/gpu/drm/drm_exec.c +++ b/drivers/gpu/drm/drm_exec.c @@ -322,6 +322,19 @@ int drm_exec_prepare_array(struct drm_exec *exec, { int ret; + /* + * Make sure to lock a contended object even when no objects are + * given, otherwise drm_exec_retry_on_contention() would loop + * forever on patterns like: + * + * ret = drm_exec_prepare_array(exec, objs, num_objects, ...); + * drm_exec_retry_on_contention(exec); + * + * with num_objects == 0. + */ + if (!num_objects) + return drm_exec_lock_contended(exec); + for (unsigned int i = 0; i < num_objects; ++i) { ret = drm_exec_prepare_obj(exec, objects[i], num_fences); if (unlikely(ret)) From 8b0cc8707f65e0f51912e764e1b309b2559db1ec Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 18 Aug 2026 19:31:53 +0800 Subject: [PATCH 0577/1317] mmc: spi: reset bytes_xfered before retrying CRC failures mmc_spi_data_do() updates data->bytes_xfered after each block has been transferred successfully. If a later block in the same data request fails with a CRC error, data->bytes_xfered may therefore contain the number of bytes completed before the failing block. mmc_spi_request() has a private recovery path for such CRC failures. It sends STOP_TRANSMISSION, clears data->error and jumps back to crc_recover to issue the same command and data request again. However, it does not clear data->bytes_xfered before the retry. If the retry succeeds, the request is completed with the bytes from the failed attempt still included in data->bytes_xfered. For a multi-block request this can make the completed request report more bytes than were transferred by the successful retry, and can even exceed the request size when most blocks completed before the CRC error. This is most likely to be observed on MMC-over-SPI systems where long multi-block transfers occasionally hit a data CRC error but the mmc_spi-internal retry succeeds. The data itself is retried, but the completion accounting is not. Clear data->bytes_xfered together with data->error before repeating the request so the final completion reports only the bytes transferred by the successful attempt. Fixes: 061c6c847eeb ("mmc_spi: Recover from CRC errors for r/w operation over SPI.") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmc_spi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c index b471a7795b4d..5217d713c826 100644 --- a/drivers/mmc/host/mmc_spi.c +++ b/drivers/mmc/host/mmc_spi.c @@ -952,6 +952,7 @@ static void mmc_spi_request(struct mmc_host *mmc, struct mmc_request *mrq) status = mmc_spi_command_send(host, mrq, &stop, 0); crc_retry--; mrq->data->error = 0; + mrq->data->bytes_xfered = 0; goto crc_recover; } From dd519eb8f66eaa205bbbdcb753588138a1d18414 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 8 Sep 2026 20:55:17 +0200 Subject: [PATCH 0578/1317] platform/x86: x86-android-tablets: fix gpio_secondary_fwnode_init() not working MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit acpi_bus_find_device_by_name() call returns a pointer to the device object on the ACPI bus, aka the ACPI companion device. gpio_secondary_fwnode_init() then continues with setting the secondary fwnode on this device. But this is not the actual physical device for the GPIO controller (e.g. the GPIO controller platform bus device). This mismatch is causing GPIO lookups by secondary fwnode to not work. Modify gpio_secondary_fwnode_init() to instead set the secondary fwnode of the first physical device associated with the ACPI companion device. This fixes the GPIO lookups not working. Fixes: 1448c2d2ca5c ("platform/x86: x86-android-tablets: enable fwnode matching of GPIO chips") Reviewed-by: Dmitry Torokhov Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260908185517.49047-1-johannes.goede@oss.qualcomm.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/core.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index b028af1c9942..cfff7f5eac5d 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -390,6 +390,7 @@ static int gpio_secondary_fwnode_init(struct device *parent, { const struct software_node *const *swnode; struct fwnode_handle *fwnode; + struct device *phys_dev; int ret; if (!node_group) @@ -417,9 +418,15 @@ static int gpio_secondary_fwnode_init(struct device *parent, if (WARN_ON(!fwnode)) return -ENOENT; - set_secondary_fwnode(dev, fwnode); + phys_dev = acpi_get_first_physical_node(to_acpi_device(dev)); + if (!phys_dev) + return dev_err_probe(parent, -ENODEV, + "No physical device for ACPI GPIO dev: %pfwP\n", + fwnode); - ret = devm_add_action_or_reset(parent, gpio_secondary_unset, get_device(dev)); + set_secondary_fwnode(phys_dev, fwnode); + + ret = devm_add_action_or_reset(parent, gpio_secondary_unset, get_device(phys_dev)); if (ret) return ret; } From 7dd4c829bac2916be98a3e34b41daaba7f42b4c4 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 20 Jul 2026 22:58:46 +0900 Subject: [PATCH 0579/1317] idpf: disable DIM work before freeing q_vectors idpf never drains the Tx/Rx DIM works before freeing the memory they live in. tx_dim and rx_dim are embedded in struct idpf_q_vector, they are queued from the NAPI poll via net_dim(), and idpf_vport_intr_rel() ends with kfree(rsrc->q_vectors). Nothing in the driver cancels them. idpf_tx_dim_work() and idpf_rx_dim_work() then run on freed memory: idpf_vport_intr_write_itr() writes the ITR register through q_vector->intr_reg.tx_itr / rx_itr, void __iomem pointers loaded out of the freed q_vector. No configuration is needed to get there -- IDPF_ITR_IS_DYNAMIC() is defined as (itr_mode) and idpf_vport_alloc() initialises both modes to IDPF_ITR_DYNAMIC. Draining after idpf_vport_intr_napi_dis_all() is not enough on its own. idpf_net_dim() is called from inside the "if (napi_complete_done(napi, work_done))" branch of the poll, and napi_complete_done() has already cleared NAPIF_STATE_SCHED by then. napi_disable_locked() waits only while (val & (NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC)), so napi_disable() can return while the poll tail is still queueing the work, and a plain cancel_work_sync() would be re-armed behind the drain. Use disable_work_sync(): schedule_work() on a work with a non-zero disable count is dropped by clear_pending_if_disabled() before __queue_work() is reached. Move idpf_init_dim() to idpf_vport_intr_alloc() so the works are initialised on every path that can reach the drain -- the three "goto intr_deinit" sites between idpf_vport_intr_init() and idpf_vport_intr_ena() get there without the enable side having run. Nothing re-enables them: rsrc->q_vectors is freed on every exit from idpf_vport_open() and on every idpf_vport_stop(), so the count dies with the object. It is a race, not a deterministic failure -- net_dim() only schedules once DIM_NEVENTS events have accumulated and the profile index changes. A KASAN ifup/ifdown loop under load is the way to see it. Fixes: c2d548cad150 ("idpf: add TX splitq napi poll support") Fixes: 3a8845af66ed ("idpf: add RX splitq napi poll support") Cc: # see patch description, needs adjustments for <= 6.9 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 24b91be25676..9ba9c2952d78 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -4145,6 +4145,26 @@ static void idpf_vport_intr_ena_irq_all(struct idpf_vport *vport, writel(rsrc->noirq_dyn_ctl_ena, rsrc->noirq_dyn_ctl); } +/** + * idpf_vport_intr_dis_dim_all - Disable DIM work for all q_vectors + * @rsrc: pointer to queue and vector resources + * + * The DIM works are embedded in the q_vector array that + * idpf_vport_intr_rel() frees, and the poll arms them after + * napi_complete_done() has already cleared NAPI_STATE_SCHED. Disable + * rather than just cancel, so that a poll tail still running past + * napi_disable() cannot queue them again behind the drain. + */ +static void idpf_vport_intr_dis_dim_all(struct idpf_q_vec_rsrc *rsrc) +{ + for (u16 v_idx = 0; v_idx < rsrc->num_q_vectors; v_idx++) { + struct idpf_q_vector *q_vector = &rsrc->q_vectors[v_idx]; + + disable_work_sync(&q_vector->tx_dim.work); + disable_work_sync(&q_vector->rx_dim.work); + } +} + /** * idpf_vport_intr_deinit - Release all vector associations for the vport * @vport: main vport structure @@ -4155,6 +4175,7 @@ void idpf_vport_intr_deinit(struct idpf_vport *vport, { idpf_vport_intr_dis_irq_all(rsrc); idpf_vport_intr_napi_dis_all(rsrc); + idpf_vport_intr_dis_dim_all(rsrc); idpf_vport_intr_napi_del_all(rsrc); idpf_vport_intr_rel_irq(vport, rsrc); } @@ -4235,7 +4256,6 @@ static void idpf_vport_intr_napi_ena_all(struct idpf_q_vec_rsrc *rsrc) for (u16 q_idx = 0; q_idx < rsrc->num_q_vectors; q_idx++) { struct idpf_q_vector *q_vector = &rsrc->q_vectors[q_idx]; - idpf_init_dim(q_vector); napi_enable(&q_vector->napi); } } @@ -4578,6 +4598,8 @@ int idpf_vport_intr_alloc(struct idpf_vport *vport, q_coal = &user_config->q_coalesce[v_idx]; q_vector->vport = vport; + idpf_init_dim(q_vector); + q_vector->tx_itr_value = q_coal->tx_coalesce_usecs; q_vector->tx_intr_mode = q_coal->tx_intr_mode; q_vector->tx_itr_idx = VIRTCHNL2_ITR_IDX_1; From 650f197d8ea6ebbc9ce1c9fb0358258b65e74291 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 20 Jul 2026 23:35:10 +0900 Subject: [PATCH 0580/1317] idpf: disable PTM on probe failure and on remove idpf_probe() enables PCIe Precision Time Measurement with pci_enable_ptm(), which takes a reference on the device and on every PTM-capable device up the path to the PTM Root. Neither the probe error path nor idpf_remove() drops that reference, so the PTM enable counts of this device and of its upstream path stay elevated with no bound driver, and the device's PTM control bits remain set. pcim_enable_device() only arranges for pci_disable_device() and does not undo the PTM enable. Add the matching pci_disable_ptm() to the common unwind path. pci_enable_ptm() failure is not fatal here, so guard the call with pcie_ptm_enabled(): pci_disable_ptm() decrements dev->ptm_enable_cnt unconditionally and then recurses upstream, so calling it after a failed enable would drive this device's count negative and wrongly decrement parents shared with other endpoints. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 8d5e12c5921c ("idpf: add initial PTP support") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin [TN moved call due to commit 6b284aa2ddf3 ("idpf: refactor idpf to use libie_pci APIs")] Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c b/drivers/net/ethernet/intel/idpf/idpf_main.c index 9840580fbe51..129bccaa6baa 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_main.c +++ b/drivers/net/ethernet/intel/idpf/idpf_main.c @@ -106,6 +106,11 @@ static int idpf_dev_init(struct idpf_adapter *adapter, */ static void idpf_decfg_device(struct idpf_adapter *adapter) { + struct pci_dev *pdev = adapter->pdev; + + if (pcie_ptm_enabled(pdev)) + pci_disable_ptm(pdev); + libie_pci_unmap_all_mmio_regions(&adapter->ctlq_ctx.mmio_info); } From cc6d60ef92278a31ffc2e94966a0921b9646af18 Mon Sep 17 00:00:00 2001 From: Joshua Hay Date: Mon, 27 Jul 2026 16:08:48 -0700 Subject: [PATCH 0581/1317] idpf: account for VLAN header when parsing RSC packet header While parsing the header of a Receive Side Coalesced (RSC) packet, check if a VLAN tag is present and adjust the header parsing accordingly. Otherwise, Rx TCP traffic is completely broken for any VLAN interface whose underlying interface has RSC (rx-gro-hw) enabled. We only need to worry about one VLAN header since Rx packets with multiple VLAN headers are not candidates for RSC. Fixes: 3a8845af66edb ("idpf: add RX splitq napi poll support") Signed-off-by: Joshua Hay Reviewed-by: Emil Tantilov Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 9ba9c2952d78..4311ffa30bb1 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -3299,6 +3299,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, struct libeth_rx_pt decoded) { u16 rsc_segments, rsc_seg_len; + u16 l3_start = 0; bool ipv4, ipv6; int len; @@ -3321,7 +3322,10 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, NAPI_GRO_CB(skb)->count = rsc_segments; skb_shinfo(skb)->gso_size = rsc_seg_len; - skb_reset_network_header(skb); + if (unlikely(eth_type_vlan(skb->protocol))) + l3_start = VLAN_HLEN; + + skb_set_network_header(skb, l3_start); if (ipv4) { struct iphdr *ipv4h = ip_hdr(skb); @@ -3329,7 +3333,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4; /* Reset and set transport header offset in skb */ - skb_set_transport_header(skb, sizeof(struct iphdr)); + skb_set_transport_header(skb, l3_start + sizeof(struct iphdr)); len = skb->len - skb_transport_offset(skb); /* Compute the TCP pseudo header checksum*/ @@ -3339,7 +3343,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, struct ipv6hdr *ipv6h = ipv6_hdr(skb); skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6; - skb_set_transport_header(skb, sizeof(struct ipv6hdr)); + skb_set_transport_header(skb, l3_start + sizeof(struct ipv6hdr)); len = skb->len - skb_transport_offset(skb); tcp_hdr(skb)->check = ~tcp_v6_check(len, &ipv6h->saddr, &ipv6h->daddr, 0); From 53432c4c3e869076350aef319534431af8ba99c1 Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Mon, 6 Jul 2026 16:31:17 -0700 Subject: [PATCH 0582/1317] ice: add missing xa_destroy for sched_node_ids Commit 16dfa49406bc ("ice: Introduce new parameters in ice_sched_node") added a sched_node_ids xarray to the port info structure, but never called xa_destroy on it. Since xarrays can allocate internal memory, this can result in a memory leak even if every element in the xarray has been removed. The xarray is currently embedded in the port_info structure. This appears to have been done because its use is within functions that take the port_info as a primary argument. However, this complicates managing the lifecycle of the field. The port_info structure is allocated in ice_init_hw() using devm, and it is not released until the devm cleanup when the driver is unloaded. The ice_init_hw() function is called in many places, including devlink reload, and possibly during DDP load after updating the Tx scheduler layout. Adding a call of xa_destroy to the ice_deinit_hw() causes Sashiko to raise multiple concerns due to potential ordering issues and possible ways that port_info could be a dangling reference. To handle this, move the sched_node_ids out of port_info and into the hw structure. All users of the array already have a pointer to hw anyways, and there is only one sched_node_ids per adapter. While here, remove the overly verbose comment explaining the nature of the sched_node_ids xarray. Add the missing xa_destroy to the cleanup path and to ice_deinit_hw(), ensuring that we properly release the xarray memory. This was caught by Sashiko during development of unrelated code. Fixes: 16dfa49406bc ("ice: Introduce new parameters in ice_sched_node") Signed-off-by: Jacob Keller Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_common.c | 9 ++++++--- drivers/net/ethernet/intel/ice/ice_sched.c | 4 ++-- drivers/net/ethernet/intel/ice/ice_type.h | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_common.c b/drivers/net/ethernet/intel/ice/ice_common.c index ef1ce106f81b..04633103e3e6 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.c +++ b/drivers/net/ethernet/intel/ice/ice_common.c @@ -1051,14 +1051,13 @@ int ice_init_hw(struct ice_hw *hw) hw->evb_veb = true; - /* init xarray for identifying scheduling nodes uniquely */ - xa_init_flags(&hw->port_info->sched_node_ids, XA_FLAGS_ALLOC); + xa_init_flags(&hw->sched_node_ids, XA_FLAGS_ALLOC); /* Query the allocated resources for Tx scheduler */ status = ice_sched_query_res_alloc(hw); if (status) { ice_debug(hw, ICE_DBG_SCHED, "Failed to get scheduler allocated resources\n"); - goto err_unroll_alloc; + goto err_unroll_xarray; } ice_sched_get_psm_clk_freq(hw); @@ -1146,6 +1145,8 @@ int ice_init_hw(struct ice_hw *hw) ice_cleanup_fltr_mgmt_struct(hw); err_unroll_sched: ice_sched_cleanup_all(hw); +err_unroll_xarray: + xa_destroy(&hw->sched_node_ids); err_unroll_alloc: devm_kfree(ice_hw_to_dev(hw), hw->port_info); err_unroll_cqinit: @@ -1186,6 +1187,8 @@ void ice_deinit_hw(struct ice_hw *hw) /* Clear VSI contexts if not already cleared */ ice_clear_all_vsi_ctx(hw); + + xa_destroy(&hw->sched_node_ids); } /** diff --git a/drivers/net/ethernet/intel/ice/ice_sched.c b/drivers/net/ethernet/intel/ice/ice_sched.c index fff0c1afdb41..ffa18d86729a 100644 --- a/drivers/net/ethernet/intel/ice/ice_sched.c +++ b/drivers/net/ethernet/intel/ice/ice_sched.c @@ -371,7 +371,7 @@ void ice_free_sched_node(struct ice_port_info *pi, struct ice_sched_node *node) devm_kfree(ice_hw_to_dev(hw), node->children); kfree(node->name); - xa_erase(&pi->sched_node_ids, node->id); + xa_erase(&hw->sched_node_ids, node->id); devm_kfree(ice_hw_to_dev(hw), node); } @@ -977,7 +977,7 @@ ice_sched_add_elems(struct ice_port_info *pi, struct ice_sched_node *tc_node, if (!new_node->name) return -ENOMEM; - status = xa_alloc(&pi->sched_node_ids, &new_node->id, NULL, XA_LIMIT(0, UINT_MAX), + status = xa_alloc(&hw->sched_node_ids, &new_node->id, NULL, XA_LIMIT(0, UINT_MAX), GFP_KERNEL); if (status) { ice_debug(hw, ICE_DBG_SCHED, "xa_alloc failed for sched node status =%d\n", diff --git a/drivers/net/ethernet/intel/ice/ice_type.h b/drivers/net/ethernet/intel/ice/ice_type.h index d9a5c1aae7c2..cf147a212707 100644 --- a/drivers/net/ethernet/intel/ice/ice_type.h +++ b/drivers/net/ethernet/intel/ice/ice_type.h @@ -765,7 +765,6 @@ struct ice_port_info { /* List contain profile ID(s) and other params per layer */ struct list_head rl_prof_list[ICE_AQC_TOPO_MAX_LEVEL_NUM]; struct ice_qos_cfg qos_cfg; - struct xarray sched_node_ids; u8 is_vf:1; u8 is_custom_tx_enabled:1; }; @@ -930,6 +929,7 @@ struct ice_hw { u8 sw_entry_point_layer; u16 max_children[ICE_AQC_TOPO_MAX_LEVEL_NUM]; struct list_head agg_list; /* lists all aggregator */ + struct xarray sched_node_ids; struct ice_vsi_ctx *vsi_ctx[ICE_MAX_VSI]; u8 evb_veb; /* true for VEB, false for VEPA */ From b8bf9bfda5f62e11444e483c2b4aaff90c5cfc6b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 20 Aug 2026 19:02:01 -0700 Subject: [PATCH 0583/1317] eth: ice: don't dereference pointers from TP_printk() After forwarding net-next during the v7.3 merge window we started seeing: TRACE EVENT ERROR: Event ice_tx_dim_work has double dereference in TP_printk: REC->q_vector->tx.tx_ring->q_index WARNING: kernel/trace/trace_events.c:420 at test_double_dereference.cold+0x39/0x4b this is due to extra checks added in tracing subsystem in commit b5cc230af5e5 ("tracing: Warn when an event dereferences a pointer in TP_printk()"). Printing happens long after the event was recorded, by which point the pointers may be invalid (the ring or the dim instance). Copy the eight scalars into the event instead. Fixes: 3089cf6d3caa ("ice: add tracepoints") Signed-off-by: Jakub Kicinski Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_trace.h | 64 ++++++++++++++-------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_trace.h b/drivers/net/ethernet/intel/ice/ice_trace.h index 4f35ef8d6b29..7568c917cdbe 100644 --- a/drivers/net/ethernet/intel/ice/ice_trace.h +++ b/drivers/net/ethernet/intel/ice/ice_trace.h @@ -63,23 +63,33 @@ DECLARE_EVENT_CLASS(ice_rx_dim_template, TP_PROTO(struct ice_q_vector *q_vector, struct dim *dim), TP_ARGS(q_vector, dim), - TP_STRUCT__entry(__field(struct ice_q_vector *, q_vector) - __field(struct dim *, dim) + TP_STRUCT__entry(__field(u16, q_index) + __field(u8, state) + __field(u8, profile_ix) + __field(u8, tune_state) + __field(u8, steps_right) + __field(u8, steps_left) + __field(u8, tired) __string(devname, q_vector->rx.rx_ring->netdev->name)), - TP_fast_assign(__entry->q_vector = q_vector; - __entry->dim = dim; + TP_fast_assign(__entry->q_index = q_vector->rx.rx_ring->q_index; + __entry->state = dim->state; + __entry->profile_ix = dim->profile_ix; + __entry->tune_state = dim->tune_state; + __entry->steps_right = dim->steps_right; + __entry->steps_left = dim->steps_left; + __entry->tired = dim->tired; __assign_str(devname);), TP_printk("netdev: %s Rx-Q: %d dim-state: %d dim-profile: %d dim-tune: %d dim-st-right: %d dim-st-left: %d dim-tired: %d", __get_str(devname), - __entry->q_vector->rx.rx_ring->q_index, - __entry->dim->state, - __entry->dim->profile_ix, - __entry->dim->tune_state, - __entry->dim->steps_right, - __entry->dim->steps_left, - __entry->dim->tired) + __entry->q_index, + __entry->state, + __entry->profile_ix, + __entry->tune_state, + __entry->steps_right, + __entry->steps_left, + __entry->tired) ); DEFINE_EVENT(ice_rx_dim_template, ice_rx_dim_work, @@ -90,23 +100,33 @@ DEFINE_EVENT(ice_rx_dim_template, ice_rx_dim_work, DECLARE_EVENT_CLASS(ice_tx_dim_template, TP_PROTO(struct ice_q_vector *q_vector, struct dim *dim), TP_ARGS(q_vector, dim), - TP_STRUCT__entry(__field(struct ice_q_vector *, q_vector) - __field(struct dim *, dim) + TP_STRUCT__entry(__field(u16, q_index) + __field(u8, state) + __field(u8, profile_ix) + __field(u8, tune_state) + __field(u8, steps_right) + __field(u8, steps_left) + __field(u8, tired) __string(devname, q_vector->tx.tx_ring->netdev->name)), - TP_fast_assign(__entry->q_vector = q_vector; - __entry->dim = dim; + TP_fast_assign(__entry->q_index = q_vector->tx.tx_ring->q_index; + __entry->state = dim->state; + __entry->profile_ix = dim->profile_ix; + __entry->tune_state = dim->tune_state; + __entry->steps_right = dim->steps_right; + __entry->steps_left = dim->steps_left; + __entry->tired = dim->tired; __assign_str(devname);), TP_printk("netdev: %s Tx-Q: %d dim-state: %d dim-profile: %d dim-tune: %d dim-st-right: %d dim-st-left: %d dim-tired: %d", __get_str(devname), - __entry->q_vector->tx.tx_ring->q_index, - __entry->dim->state, - __entry->dim->profile_ix, - __entry->dim->tune_state, - __entry->dim->steps_right, - __entry->dim->steps_left, - __entry->dim->tired) + __entry->q_index, + __entry->state, + __entry->profile_ix, + __entry->tune_state, + __entry->steps_right, + __entry->steps_left, + __entry->tired) ); DEFINE_EVENT(ice_tx_dim_template, ice_tx_dim_work, From dcaf83ead130d3067862599089b0999b3da140a4 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 2 Sep 2026 02:19:18 +0800 Subject: [PATCH 0584/1317] Bluetooth: Properly disable remote wakeup for MT7922/MT7925 on Ryzen platform It is reported that a remote wakeup could cause MT7922/MT7925's btusb interface completely unresponsive. Resetting the xHCI root hub doesn't help at all, and recovering from such a state needs a power cycle. All reports seen to be relevant to Ryzen-based laptops. These NICs are usually used as OEM components thanks to some sort of reference designs. Their popularity on other platforms is unclear. While there is still a chance that the quirk may exist on other platforms, be cautious and only apply the quirk to direct children of Ryzen platforms's root hubs for the time being. In most cases the root hub is on the SoC or PCH, which needs the quirk. Unfortunately, this can't distinguish root hubs on PCIe add-in cards. Such roughness should be acceptable, as PCIe USB controller add-in cards are less commonly used nowadays. On the other hand, applying the quirk doesn't hurt any functionalities either, as the device can still be used as a wakeup source if desired. Theoretically, we could retrieve the root hub's PCI vendor ID with some hierarchy magic, but that's too intrusive... Meanwhile, though device_set_wakeup_capable(false) is the correct fix for other NICs with fake remote wakeup capabilities, doing so for MT7922/MT7925 effectively prevents it from being used as wakeup sources as per userspace requests. Hence, return -EBUSY on runtime suspend to prevent the interface from being autosuspended while it's still opened, which has the same effect as device_set_wakeup_capable(false), since disabling remote wakeup simply causes the USB core to gate runtime autosuspend as well due to needs_remote_wakeup == 1. The interface can be safely autosuspended as long as remote wakeup is disabled, i.e., after closing the HCI device. Specifically, the interface may still take the advantage of remote wakeup in order to wake up the system from sleep if userspace has enabled it as a wakeup source. Fixes: e31d761628ad ("Bluetooth: btmtk: Disable remote wakeup for MT7922/MT7925") Tested-by: Rafael Passos Signed-off-by: Rong Zhang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 10 ------ drivers/bluetooth/btusb.c | 73 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index c0ed51567ed4..9589caff925d 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -1374,16 +1374,6 @@ int btmtk_usb_setup(struct hci_dev *hdev) break; case 0x7922: case 0x7925: - /* - * A remote wakeup could cause the device completely unresponsive, and - * recovering from such a state needs a power cycle. - * - * Since the remote wakeup capability is super broken, just disable it - * to get rid of the troubles. The device can still be autosuspended - * when the bluetooth interface is closed. - */ - device_set_wakeup_capable(&btmtk_data->udev->dev, false); - fallthrough; case 0x7961: case 0x7902: case 0x6639: diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index d70a3e7a13f5..95f4640c60e4 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -6,6 +6,7 @@ * Copyright (C) 2005-2008 Marcel Holtmann */ +#include #include #include #include @@ -980,6 +981,7 @@ struct btqca_data { #define BTUSB_USE_ALT3_FOR_WBS 15 #define BTUSB_ALT6_CONTINUOUS_TX 16 #define BTUSB_HW_SSR_ACTIVE 17 +#define BTUSB_WAKEUP_BROKEN 18 struct btusb_data { struct hci_dev *hdev; @@ -2969,10 +2971,25 @@ static int btusb_send_frame_mtk(struct hci_dev *hdev, struct sk_buff *skb) } } +static inline bool platform_is_ryzen(void) +{ +#ifdef CONFIG_X86 + return boot_cpu_has(X86_FEATURE_ZEN); +#else + return false; +#endif +} + +static inline bool is_direct_child_of_root_hub(struct usb_device *udev) +{ + return udev->parent == udev->bus->root_hub; +} + static int btusb_mtk_setup(struct hci_dev *hdev) { struct btusb_data *data = hci_get_drvdata(hdev); struct btmtk_data *btmtk_data = hci_get_priv(hdev); + int err; /* MediaTek WMT vendor cmd requiring below USB resources to * complete the handshake. @@ -2989,7 +3006,40 @@ static int btusb_mtk_setup(struct hci_dev *hdev) btusb_mtk_claim_iso_intf(data); } - return btmtk_usb_setup(hdev); + err = btmtk_usb_setup(hdev); + if (err) + return err; + + switch (btmtk_data->dev_id) { + case 0x7922: + case 0x7925: + /* + * All reports seen to be relevant to Ryzen-based laptops. These + * NICs are usually used as OEM components thanks to some sort + * of reference designs. + * + * Their popularity on other platforms is unclear. While there + * is still a chance that the quirk may exist on other + * platforms, be cautious and only apply the quirk to direct + * children of Ryzen platforms's root hubs for the time being. + * + * In most cases the root hub is on the SoC or PCH, which needs + * the quirk. Unfortunately, this can't distinguish root hubs on + * PCIe add-in cards. Such roughness should be acceptable, as + * PCIe USB controller add-in cards are less commonly used + * nowadays. On the other hand, applying the quirk doesn't hurt + * any functionalities either, as the device can still be used + * as a wakeup source if desired. + * + * Theoretically, we could retrieve the root hub's PCI vendor ID + * with some hierarchy magic, but that's too intrusive... + */ + if (platform_is_ryzen() && is_direct_child_of_root_hub(data->udev)) + set_bit(BTUSB_WAKEUP_BROKEN, &data->flags); + break; + } + + return 0; } static int btusb_mtk_shutdown(struct hci_dev *hdev) @@ -4565,11 +4615,26 @@ static int btusb_suspend(struct usb_interface *intf, pm_message_t message) BT_DBG("intf %p", intf); - /* Don't auto-suspend if there are connections or discovery in - * progress; external suspend calls shall never fail. + /* + * It is reported that remote wakeup events could sometimes cause some + * adapters completely unresponsive. Resetting the xHCI root hub doesn't + * help at all, and recovering from such a state needs a power cycle. + * Since disabling remote wakeup simply causes the USB core to gate + * runtime autosuspend as well due to needs_remote_wakeup == 1, let's do + * this ourselves to make our life easier. The interface can be safely + * autosuspended as long as remote wakeup is disabled, i.e., after + * closing the HCI device. + * + * Don't auto-suspend if there are connections or discovery in progress. + * + * External suspend calls shall never fail. Specifically, a device with + * broken remote wakeup may still take the advantage of remote wakeup in + * order to wake up the system from sleep if userspace has enabled it as + * a wakeup source. */ if (PMSG_IS_AUTO(message) && - (hci_conn_count(data->hdev) || hci_discovery_active(data->hdev))) + ((test_bit(BTUSB_WAKEUP_BROKEN, &data->flags) && data->intf->needs_remote_wakeup) || + hci_conn_count(data->hdev) || hci_discovery_active(data->hdev))) return -EBUSY; if (data->suspend_count++) From 1c12c3117639e78940959d956519c758c57d0849 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Mon, 31 Aug 2026 12:13:10 -0400 Subject: [PATCH 0585/1317] Bluetooth: btusb: Fix UAF of btusb_data by rx_work btusb_close() and btusb_flush() cancel data->rx_work with the asynchronous cancel_delayed_work(), so if btusb_rx_work() is already running on another CPU it keeps running after the cancel returns. btusb_disconnect() calls hci_unregister_dev(), which invokes btusb_close(), and then frees the btusb_data. A still running btusb_rx_work() then dereferences the freed data: while ((skb = skb_dequeue(&data->acl_q))) data->recv_acl(data->hdev, skb); Use cancel_delayed_work_sync() instead. In btusb_close() the cancel also has to happen after btusb_stop_traffic(), otherwise an URB completion racing with the cancel can requeue the work right after it has been waited for. Fixes: 800fe5ec302e ("Bluetooth: btusb: Add support for queuing during polling interval") Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 95f4640c60e4..ddc44ca28722 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -2094,18 +2094,24 @@ static int btusb_close(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - cancel_delayed_work(&data->rx_work); cancel_work_sync(&data->work); cancel_work_sync(&data->waker); - skb_queue_purge(&data->acl_q); - clear_bit(BTUSB_ISOC_RUNNING, &data->flags); clear_bit(BTUSB_BULK_RUNNING, &data->flags); clear_bit(BTUSB_INTR_RUNNING, &data->flags); clear_bit(BTUSB_DIAG_RUNNING, &data->flags); btusb_stop_traffic(data); + + /* rx_work must only be canceled once the URBs that can rearm it are + * gone, and it must be canceled synchronously since btusb_disconnect() + * frees the btusb_data it dereferences right after hci_unregister_dev(). + */ + cancel_delayed_work_sync(&data->rx_work); + + skb_queue_purge(&data->acl_q); + btusb_free_frags(data); err = usb_autopm_get_interface(data->intf); @@ -2131,7 +2137,7 @@ static int btusb_flush(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - cancel_delayed_work(&data->rx_work); + cancel_delayed_work_sync(&data->rx_work); skb_queue_purge(&data->acl_q); From 83e3e515fd261600ed8491fb0a8bcdfb115c904e Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Thu, 3 Sep 2026 03:18:59 +0800 Subject: [PATCH 0586/1317] Bluetooth: btrtl: Don't leak return code when parsing firmware format v2 When key_id from chip is zero, rtlbt_parse_firmware_v2() intentionally ignores all security headers. However, the implementation simply breaks from a switch statement and leaks uninitialized return code `rc' (if the first section is a security one) or the previous section's `rc'. Fix it by really skipping a loop with `continue'. For consistency and readability, also do the same for the default case. Fixes: 9a24ce5e29b1 ("Bluetooth: btrtl: Firmware format v2 support") Cc: stable@vger.kernel.org Signed-off-by: Rong Zhang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btrtl.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 03fa9409e3ee..d29813331603 100644 --- a/drivers/bluetooth/btrtl.c +++ b/drivers/bluetooth/btrtl.c @@ -591,7 +591,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev, * headers. */ if (!key_id) - break; + continue; rc = btrtl_parse_section(hdev, btrtl_dev, opcode, ptr, section_len); break; @@ -600,8 +600,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev, ptr, section_len); break; default: - rc = 0; - break; + continue; } if (rc < 0) { rtl_dev_err(hdev, "RTL: Parse section (%u) err %d", From 6436e1b5331b1aebf905c13e0880a37032719b75 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Thu, 3 Sep 2026 20:21:01 +0530 Subject: [PATCH 0587/1317] Bluetooth: btintel_pcie: validate packet_len before skb_put_data btintel_pcie_submit_rx_work() reads packet_len from rfh_hdr without checking if it exceeds the RX buffer size. An oversized packet_len can lead to an out-of-bounds read in skb_put_data(). Validate packet_len to ensure it is non-zero and does not exceed BTINTEL_PCIE_BUFFER_SIZE - sizeof(*rfh_hdr), logging an error when invalid. This issue was reported by Claude Mythos. It can be simulated either by using customized firmware configured to return an invalid packet_len or by modifying rfh_hdr->packet_len in the driver before calling btintel_pcie_submit_rx_work(). Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index eec95e5f3dbb..968608932fb7 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1599,7 +1599,9 @@ static int btintel_pcie_submit_rx_work(struct btintel_pcie_data *data, u8 status rfh_hdr = buf; len = rfh_hdr->packet_len; - if (len <= 0) { + if (len == 0 || len > BTINTEL_PCIE_BUFFER_SIZE - sizeof(*rfh_hdr)) { + bt_dev_err(data->hdev, "Invalid packet_len %d (max %zu)", len, + BTINTEL_PCIE_BUFFER_SIZE - sizeof(*rfh_hdr)); ret = -EINVAL; goto resubmit; } From 3dd1b41f96aad08444be1b7626c89de2b9f2abd4 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Thu, 3 Sep 2026 20:21:02 +0530 Subject: [PATCH 0588/1317] Bluetooth: btintel_pcie: fix tx_handle bounds off-by-one Valid indices into txq->urbd0s/tfds/bufs are 0..txq->count-1, so tfd_index == txq->count is already out of range. Change the guard in btintel_pcie_msix_tx_handle() from '> txq->count' to '>= txq->count'. This issue was reported by Claude Mythos. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 968608932fb7..6d9649776ae7 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1099,7 +1099,7 @@ static void btintel_pcie_msix_tx_handle(struct btintel_pcie_data *data) urbd0 = &txq->urbd0s[cr_tia]; - if (urbd0->tfd_index > txq->count) + if (urbd0->tfd_index >= txq->count) return; cr_tia = (cr_tia + 1) % txq->count; From 3621f78d43b0a9563d5ade68370434c532eea259 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Wed, 2 Sep 2026 13:16:26 -0400 Subject: [PATCH 0589/1317] Bluetooth: hci_sync: Fix not setting CE length properly Both hci_le_set_def_rate_sync() and hci_le_conn_rate_request_sync() were leaving Min_CE_Length and Max_CE_Length set to 0x0000, but the connection event length recommended in requests by a Peripheral has a valid range of 0x0001 to 0x7CFF (Time = N * 125 us, Time Range: 0.125 ms to 3.999875 s), so 0x0000 cannot be used. Set both to the minimum valid value, which is safe since the Controller is not required to use these values: BLUETOOTH CORE SPECIFICATION Version 6.2 | Vol 4, Part E 7.8.157. LE Connection Rate Request command 7.8.158. LE Set Default Rate Parameters command The Min_CE_Length and Max_CE_Length parameters provide the Controller with the expected minimum and maximum length of the connection events. The Controller is not required to use these values. Fixes: 2f8784cfe8a9 ("Bluetooth: Add support for Shorter Connection Interval (SCI) feature") Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index ffd7b37e7401..3ab5fa3dce96 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -4797,6 +4797,24 @@ static int hci_le_set_def_rate_sync(struct hci_dev *hdev) cp.cont_num = cpu_to_le16(0x0001); cp.supv_timeout = cpu_to_le16(0x000c); /* 120 ms */ + /* The connection event length recommended in requests by a Peripheral + * uses units of 125 us with a valid range of 0x0001 to 0x7CFF + * (0.125 ms to 3.999875 s), so 0x0000 cannot be used. Also note that + * the Controller is not required to use these values: + * + * BLUETOOTH CORE SPECIFICATION Version 6.2 | Vol 4, Part E + * 7.8.158. LE Set Default Rate Parameters command + * + * The Min_CE_Length and Max_CE_Length parameters provide the + * Controller with the expected minimum and maximum length of the + * connection events. The Controller is not required to use these + * values. + * + * So it is safe to just use the minimum. + */ + cp.min_ce_len = cpu_to_le16(0x0001); + cp.max_ce_len = cpu_to_le16(0x0001); + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_DEF_RATE, sizeof(cp), &cp, HCI_CMD_TIMEOUT); } @@ -7467,8 +7485,24 @@ static int hci_le_conn_rate_request_sync(struct hci_dev *hdev, void *data) cp.max_latency = cpu_to_le16(params->max_latency); cp.cont_num = cpu_to_le16(params->cont_num); cp.supv_timeout = cpu_to_le16(params->rate_supv_timeout); - cp.min_ce_len = cpu_to_le16(0x0000); - cp.max_ce_len = cpu_to_le16(0x0000); + + /* The connection event length recommended in requests by a Peripheral + * uses units of 125 us with a valid range of 0x0001 to 0x7CFF + * (0.125 ms to 3.999875 s), so 0x0000 cannot be used. Also note that + * the Controller is not required to use these values: + * + * BLUETOOTH CORE SPECIFICATION Version 6.2 | Vol 4, Part E + * 7.8.157. LE Connection Rate Request command + * + * The Min_CE_Length and Max_CE_Length parameters provide the + * Controller with the expected minimum and maximum length of the + * connection events. The Controller is not required to use these + * values. + * + * So it is safe to just use the minimum. + */ + cp.min_ce_len = cpu_to_le16(0x0001); + cp.max_ce_len = cpu_to_le16(0x0001); hci_dev_unlock(hdev); From 3d8a8e81ea8ad8813d4c82a12ba53ecb597b217d Mon Sep 17 00:00:00 2001 From: Ivan Hu Date: Fri, 4 Sep 2026 13:30:07 +0800 Subject: [PATCH 0590/1317] Bluetooth: btmtk: Declare MT7920 (MT7961 1a) Bluetooth firmware btmtk_fw_get_filename() constructs the firmware name at runtime, so for the MT7920 variant (dev_id 0x7961 with fw_flavor set) it requests "mediatek/BT_RAM_CODE_MT7961_1a_2_hdr.bin" without ever declaring it via MODULE_FIRMWARE(). Tools that select firmware from module metadata (e.g. "modinfo -F firmware") therefore omit this blob, so request_firmware() fails and Bluetooth does not initialise on MT7920, even though the file is present in linux-firmware. Declare it with MODULE_FIRMWARE(), as the mt76 driver already does for the corresponding MT7920 wifi firmware. Fixes: 1cb63d80fff6 ("Bluetooth: btusb: Add support Mediatek MT7920") Signed-off-by: Ivan Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 1 + drivers/bluetooth/btmtk.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 9589caff925d..26d525acd659 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -1577,5 +1577,6 @@ MODULE_FIRMWARE(FIRMWARE_MT7663); MODULE_FIRMWARE(FIRMWARE_MT7668); MODULE_FIRMWARE(FIRMWARE_MT7922); MODULE_FIRMWARE(FIRMWARE_MT7961); +MODULE_FIRMWARE(FIRMWARE_MT7920); MODULE_FIRMWARE(FIRMWARE_MT7925); MODULE_FIRMWARE(FIRMWARE_MT7927); diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h index c83c24897c95..bc26148ec544 100644 --- a/drivers/bluetooth/btmtk.h +++ b/drivers/bluetooth/btmtk.h @@ -7,6 +7,7 @@ #define FIRMWARE_MT7922 "mediatek/BT_RAM_CODE_MT7922_1_1_hdr.bin" #define FIRMWARE_MT7902 "mediatek/BT_RAM_CODE_MT7902_1_1_hdr.bin" #define FIRMWARE_MT7961 "mediatek/BT_RAM_CODE_MT7961_1_2_hdr.bin" +#define FIRMWARE_MT7920 "mediatek/BT_RAM_CODE_MT7961_1a_2_hdr.bin" #define FIRMWARE_MT7925 "mediatek/mt7925/BT_RAM_CODE_MT7925_1_1_hdr.bin" #define FIRMWARE_MT7927 "mediatek/mt7927/BT_RAM_CODE_MT6639_2_1_hdr.bin" From 9b851b09b392da68bd715601f10a5adb2d8d19b8 Mon Sep 17 00:00:00 2001 From: Krystian Kaniewski Date: Fri, 4 Sep 2026 12:24:22 +0000 Subject: [PATCH 0591/1317] Bluetooth: hci_sysfs: Fix NULL pointer dereference in device_del() A NULL pointer dereference in klist_put() occurs when a child device (such as a BNEP network device in bnep_session) is concurrently being unregistered while hci_conn_del_sysfs() reparents child devices. This is caused by a race condition between hci_conn_del_sysfs() and concurrent child device unregistration (e.g. bnep_session calling unregister_netdev()). During device unregistration, device_del() snapshots a non-NULL parent pointer. Concurrently, hci_conn_del_sysfs() finds the child device using device_find_any_child() and calls device_move() to reparent it to NULL, which removes the node from its parent's klist and clears knode_parent. Subsequently, device_del() calls klist_del(&dev->p->knode_parent) using the stale parent snapshot, causing klist_put() to dereference knode_klist(n)->put on an already removed node, resulting in a NULL pointer dereference. This race was introduced by commit 27aabf27fd01 ("Bluetooth: fix use-after-free in device_for_each_child()"), which replaced device_find_child(..., __match_tty) with device_find_any_child() in hci_conn_del_sysfs(). That change was intended to avoid a use-after-free where conn->dev outlived its parent hdev->dev when child devices held references to conn->dev, because conn->dev only held a reference to hdev->dev while registered in sysfs. Fix the issue properly by taking an explicit reference to the parent device with get_device(&hdev->dev) in hci_conn_init_sysfs() and dropping it with put_device(parent) in bt_link_release() when the conn device is freed. This ensures that hdev->dev remains valid for the entire lifecycle of conn->dev, resolving the underlying use-after-free. With the parent reference held properly, restore the __match_tty filter in hci_conn_del_sysfs() so that device_move() is only invoked on persistent RFCOMM TTY devices as originally intended, eliminating the race condition with unregistering network devices. Fixes: 27aabf27fd01 ("Bluetooth: fix use-after-free in device_for_each_child()") Assisted-by: Gemini:gemini-3.7-flash syzbot Reported-by: syzbot+6df45dd3d03e1a9aca96@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6df45dd3d03e1a9aca96 Link: https://syzkaller.appspot.com/ai_job?id=f1c0e740-db21-40af-a9ff-84db0fd8b8bd Signed-off-by: Krystian Kaniewski Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sysfs.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_sysfs.c b/net/bluetooth/hci_sysfs.c index 8957ce7c21b7..c2065abf753e 100644 --- a/net/bluetooth/hci_sysfs.c +++ b/net/bluetooth/hci_sysfs.c @@ -13,7 +13,10 @@ static const struct class bt_class = { static void bt_link_release(struct device *dev) { struct hci_conn *conn = to_hci_conn(dev); + struct device *parent = dev->parent; + kfree(conn); + put_device(parent); } static const struct device_type bt_link = { @@ -21,6 +24,16 @@ static const struct device_type bt_link = { .release = bt_link_release, }; +/* + * The rfcomm tty device will possibly retain even when conn + * is down, and sysfs doesn't support move zombie device, + * so we should move the device before conn device is destroyed. + */ +static int __match_tty(struct device *dev, const void *data) +{ + return !strncmp(dev_name(dev), "rfcomm", 6); +} + void hci_conn_init_sysfs(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; @@ -29,7 +42,7 @@ void hci_conn_init_sysfs(struct hci_conn *conn) conn->dev.type = &bt_link; conn->dev.class = &bt_class; - conn->dev.parent = &hdev->dev; + conn->dev.parent = get_device(&hdev->dev); device_initialize(&conn->dev); } @@ -69,7 +82,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn) while (1) { struct device *dev; - dev = device_find_any_child(&conn->dev); + dev = device_find_child(&conn->dev, NULL, __match_tty); if (!dev) break; device_move(dev, NULL, DPM_ORDER_DEV_LAST); From f5a427b16e45210dee656b0860728f3d496dee85 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Fri, 4 Sep 2026 10:54:57 +0800 Subject: [PATCH 0592/1317] Bluetooth: btqcomsmd: destroy RPMsg endpoints before freeing hci_dev The command and ACL RPMsg endpoints store struct btqcomsmd as their callback private data. The receive callbacks dereference btq->hdev without taking an hci_dev reference. The current teardown order frees the hci_dev before destroying the RPMsg endpoints in both the hci_register_dev() error path and the driver remove path. If WCNSS delivers data in that window, the endpoint callback can run with an already freed hci_dev and pass it to the Bluetooth core. For qcom_smd endpoints, rpmsg_destroy_ept() closes the channel and clears the callback under the channel recv_lock. The receive path holds the same lock while invoking the callback, so destroying the endpoints first both prevents new callbacks and serializes with any callback already running. Destroy the command and ACL endpoints before hci_free_dev(). Keep hci_unregister_dev() first during remove so the HCI core stops issuing operations before the transport endpoints are shut down. In the full registration-error cleanup path, return directly after freeing the hci_dev to avoid falling through to the partial-construction labels and destroying the endpoints twice. Fixes: 5052de8deff5 ("soc: qcom: smd: Transition client drivers from smd to rpmsg") Fixes: 9a39a927be01 ("Bluetooth: btqcomsmd: Fix a resource leak in error handling paths in the probe function") Cc: stable@vger.kernel.org Acked-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov Signed-off-by: Xu Rao Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btqcomsmd.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btqcomsmd.c b/drivers/bluetooth/btqcomsmd.c index d2e13fcb6bab..d669ea4eb3eb 100644 --- a/drivers/bluetooth/btqcomsmd.c +++ b/drivers/bluetooth/btqcomsmd.c @@ -188,7 +188,10 @@ static int btqcomsmd_probe(struct platform_device *pdev) return 0; hci_free_dev: + rpmsg_destroy_ept(btq->cmd_channel); + rpmsg_destroy_ept(btq->acl_channel); hci_free_dev(hdev); + return ret; destroy_cmd_channel: rpmsg_destroy_ept(btq->cmd_channel); destroy_acl_channel: @@ -202,10 +205,11 @@ static void btqcomsmd_remove(struct platform_device *pdev) struct btqcomsmd *btq = platform_get_drvdata(pdev); hci_unregister_dev(btq->hdev); - hci_free_dev(btq->hdev); rpmsg_destroy_ept(btq->cmd_channel); rpmsg_destroy_ept(btq->acl_channel); + + hci_free_dev(btq->hdev); } static const struct of_device_id btqcomsmd_of_match[] = { From e486a891c412d9d82ee865987f4eead6196e1f96 Mon Sep 17 00:00:00 2001 From: Jiajia Liu Date: Fri, 4 Sep 2026 16:03:50 +0800 Subject: [PATCH 0593/1317] Bluetooth: btusb: mediatek: Fix leaked runtime PM reference in reset MT7925 on HP Pro Mini 260 sometimes timed out during reloading driver and reset usb device. btusb_suspend is not called again after closing bluetooth interface. usbcore: registered new interface driver btusb Bluetooth: hci0: HW/SW Version: 0x00000000, Build Time: 20260605184935 Bluetooth: hci0: Execution of wmt command timed out Bluetooth: hci0: Failed to send wmt patch dwnld (-110) Bluetooth: hci0: Failed to set up firmware (-110) usb 3-10: reset high-speed USB device number 4 using xhci_hcd Bluetooth: hci0: HW/SW Version: 0x00000000, Build Time: 20260605184935 Bluetooth: hci0: Device setup in 1856545 usecs Bluetooth: hci0: AOSP extensions version v1.00 Bluetooth: hci0: AOSP quality report is supported Bluetooth: MGMT ver 1.23 btusb_mtk_reset calls usb_autopm_get_interface to resume the device before driving the hardware reset, but never calls the matching usb_autopm_put_interface. Every hardware reset therefore leaks a PM usage reference of the interface, preventing the device from being runtime suspended again until it is unbound. Add the BTUSB_RESET flag. It is set before usb_queue_reset_device and is cleared in btusb_disconnect, which drops the reference as well. If the flag is already set when a new reset is requested, drop one reference. Also clear BTMTK_HW_RESET_ACTIVE if usb_autopm_get_interface fails, otherwise no further reset could ever be attempted. Fixes: 25b6d7593a3a ("Bluetooth: btmtk: introduce btmtk reset work") Assisted-by: Claude:qwen3.8-max Signed-off-by: Jiajia Liu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index ddc44ca28722..9372fb521575 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -982,6 +982,7 @@ struct btqca_data { #define BTUSB_ALT6_CONTINUOUS_TX 16 #define BTUSB_HW_SSR_ACTIVE 17 #define BTUSB_WAKEUP_BROKEN 18 +#define BTUSB_RESET 19 struct btusb_data { struct hci_dev *hdev; @@ -2931,8 +2932,11 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) } err = usb_autopm_get_interface(data->intf); - if (err < 0) + if (err < 0) { + bt_dev_err(hdev, "Failed usb_autopm_get_interface: %d", err); + clear_bit(BTMTK_HW_RESET_ACTIVE, &btmtk_data->flags); return err; + } /* Release MediaTek ISO data interface */ btusb_mtk_release_iso_intf(hdev); @@ -2954,6 +2958,11 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) err = btmtk_usb_subsys_reset(hdev, btmtk_data->dev_id); + if (test_and_set_bit(BTUSB_RESET, &data->flags)) { + bt_dev_err(hdev, "last usb reset failed? Resetting again"); + usb_autopm_put_interface_no_suspend(data->intf); + } + usb_queue_reset_device(data->intf); clear_bit(BTMTK_HW_RESET_ACTIVE, &btmtk_data->flags); @@ -4596,6 +4605,9 @@ static void btusb_disconnect(struct usb_interface *intf) if (data->reset_gpio) gpiod_put(data->reset_gpio); + if (test_and_clear_bit(BTUSB_RESET, &data->flags)) + usb_autopm_put_interface_no_suspend(data->intf); + if (intf == data->intf) { if (data->isoc) usb_driver_release_interface(&btusb_driver, data->isoc); From c93922dd316b7273a8667d29084632066fa8a2d3 Mon Sep 17 00:00:00 2001 From: Jiajia Liu Date: Fri, 4 Sep 2026 16:03:51 +0800 Subject: [PATCH 0594/1317] Bluetooth: btusb: Fix leaked runtime PM reference in btusb_reset btusb_reset calls usb_autopm_get_interface to resume the device before queuing a reset of it, but never calls the matching usb_autopm_put_interface. usb_queue_reset_device ends up in usb_reset_device(), and since btusb provides no pre_reset/post_reset callbacks the interface is merely unbound and rebound: the interface device object survives this cycle, and so does its PM usage count, which is not cleared when the driver is unbound. As a result every reset permanently leaks a PM usage reference, preventing the interface from being runtime suspended again until it is unbound. Set BTUSB_RESET flag before usb_queue_reset_device so that btusb_disconnect drops the reference. If the flag is already set, drop one reference. Fixes: c9209b269afd ("Bluetooth: btusb: Introduce generic USB reset") Assisted-by: Claude:qwen3.8-max Signed-off-by: Jiajia Liu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 9372fb521575..002b9f975710 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1057,13 +1057,15 @@ static void btusb_reset(struct hci_dev *hdev) int err; data = hci_get_drvdata(hdev); - /* This is not an unbalanced PM reference since the device will reset */ err = usb_autopm_get_interface(data->intf); if (err) { bt_dev_err(hdev, "Failed usb_autopm_get_interface: %d", err); return; } + if (test_and_set_bit(BTUSB_RESET, &data->flags)) + usb_autopm_put_interface_no_suspend(data->intf); + bt_dev_err(hdev, "Resetting usb device."); usb_queue_reset_device(data->intf); } From f4c3e38111fd84c2c7ae5785755f4a4d476e1cba Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Tue, 8 Sep 2026 19:05:39 +0200 Subject: [PATCH 0595/1317] rust: allow `unknown_lints` in generated bindings for Rust < 1.88 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting with bindgen 0.73.2 [1], `#[allow(unnecessary_transmutes)]` are used, even when `--rust-target 1.85` is passed. However, the lint was introduced in Rust 1.88.0. Thus building with older Rust versions warns like: error: unknown lint: `unnecessary_transmutes` --> rust/uapi/uapi_generated.rs:26294:13 | 26294 | #[allow(unnecessary_transmutes)] | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D unknown-lints` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unknown_lints)]` Thus allow `unknown_lints` in the generated bindings -- only when building with older Rust versions. I have asked upstream if this is intentional [1], i.e. if we are supposed to always allow unknown lints in case `bindgen` uses such attributes, or whether it is an oversight. [ Emilio said it wasn't intentional -- we will work around it for now on the kernel side. - Miguel ] Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). Cc: Emilio Cobos Álvarez Link: https://github.com/rust-lang/rust-bindgen/pull/3455#issuecomment-5588526559 [1] Assisted-by: LLM Link: https://patch.msgid.link/20260908170539.345207-1-ojeda@kernel.org [ Removed the `cfg` for `allow(unnecessary_transmutes)` as suggested by Gary. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/bindings/lib.rs | 3 ++- rust/uapi/lib.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index ad24c920b919..439ab88a5da1 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -27,7 +27,8 @@ #[allow(clippy::ptr_as_ptr)] #[allow(clippy::ref_as_ptr)] #[allow(clippy::undocumented_unsafe_blocks)] -#[cfg_attr(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES, allow(unnecessary_transmutes))] +#[cfg_attr(not(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES), allow(unknown_lints))] +#[allow(unnecessary_transmutes)] #[cfg_attr( CONFIG_RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS, allow(suspicious_runtime_symbol_definitions) diff --git a/rust/uapi/lib.rs b/rust/uapi/lib.rs index 2df0340e63d1..003e6d4f7c4b 100644 --- a/rust/uapi/lib.rs +++ b/rust/uapi/lib.rs @@ -24,7 +24,8 @@ unreachable_pub, unsafe_op_in_unsafe_fn )] -#![cfg_attr(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES, allow(unnecessary_transmutes))] +#![cfg_attr(not(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES), allow(unknown_lints))] +#![allow(unnecessary_transmutes)] #![cfg_attr( CONFIG_RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS, allow(suspicious_runtime_symbol_definitions) From f6d61fe4c19cf448e5cba6d8767b4e6966f58606 Mon Sep 17 00:00:00 2001 From: Long Li Date: Wed, 2 Sep 2026 10:51:53 -0700 Subject: [PATCH 0596/1317] net: mana: Clear RDMA teardown and suspend state in mana_rdma_probe() mana_rdma_remove() sets gd->rdma_teardown to stop mana_rdma_service_handle() from acting on servicing events, but nothing ever clears it. A hardware service reset (GDMA_EQE_HWC_RESET_REQUEST) goes through mana_gd_suspend() -> mana_rdma_remove() and mana_gd_resume() -> mana_rdma_probe(), so from the first reset onwards every GDMA_EQE_HWC_SOC_SERVICE event returns early and RDMA suspend/resume servicing is silently dropped for the life of the device. gd->is_suspended has the same problem: it is set when servicing removes the adev and is cleared only by a matching resume. A reset while RDMA is suspended re-adds the adev but leaves is_suspended set, so a later resume event calls add_adev() on top of a live gd->adev and leaks it. This is currently masked by the rdma_teardown bug. Clear both in mana_rdma_probe(). On the reset path mana_rdma_remove() has closed the gate and drained the service workqueue, so clear is_suspended first and re-open the gate with smp_store_release(), paired with smp_load_acquire() in the handler, so the handler cannot observe an open gate with a stale is_suspended. On the initial probe path the gate was never closed and both flags are already clear. This does not order gd->adev, which add_adev() publishes afterwards. A servicing event arriving in that window is still dropped, as it is in mainline today on the initial probe path; closing it needs probe and the handler to be serialized and is left to a separate change. Fixes: 505cc26bcae0 ("net: mana: Add support for auxiliary device servicing events") Signed-off-by: Long Li Link: https://patch.msgid.link/20260902175153.3410560-1-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_en.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 45a7520491a6..591fb4191d90 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -3987,7 +3987,8 @@ static void mana_rdma_service_handle(struct work_struct *work) struct device *dev = gd->gdma_context->dev; int ret; - if (READ_ONCE(gd->rdma_teardown)) + /* Pairs with the smp_store_release() in mana_rdma_probe(). */ + if (smp_load_acquire(&gd->rdma_teardown)) goto out; switch (serv_work->event) { @@ -4283,6 +4284,21 @@ int mana_rdma_probe(struct gdma_dev *gd) if (err) return err; + /* Clear the state left by a previous mana_rdma_remove() so servicing + * events are handled again after a reset cycle. + */ + gd->is_suspended = false; + + /* Publish is_suspended before re-opening the gate, so the handler + * cannot observe an open gate with a stale is_suspended. Pairs + * with the smp_load_acquire() in mana_rdma_service_handle(). This + * matters on the reset path, where mana_rdma_remove() closed the + * gate and drained the workqueue; on the initial probe path the + * gate was never closed and both flags are already clear. It does + * not order gd->adev, which add_adev() publishes below. + */ + smp_store_release(&gd->rdma_teardown, false); + err = add_adev(gd, "rdma"); if (err) mana_gd_deregister_device(gd); From 8dc5d98a16fa23c00999aecf10018c9f69fa5bf4 Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Thu, 3 Sep 2026 08:21:29 +0200 Subject: [PATCH 0597/1317] ppp_async: drop the errored frame instead of resetting its headroom ppp_receive_nonmp_frame() prepends a two-byte direction tag before running the pass/active BPF filters: *(__be16 *)skb_push(skb, 2) = htons(PPP_FILTER_INBOUND_TAG); Nothing on the receive path guarantees those two bytes of headroom. The frame-error path in ppp_async's process_input_packet() resets a reused skb's headroom to zero while claiming to restore it to a freshly allocated state - but a fresh skb from dev_alloc_skb() carries NET_SKB_PAD: err: if (skb) { /* make skb appear as freshly allocated */ skb_trim(skb, 0); skb_reserve(skb, - skb_headroom(skb)); } ap->rpkt still points at that skb, so the next frame is reassembled into it with no headroom at all. A peer that sends a bad-FCS frame followed by one beginning ff 03 then leaves a single byte of headroom by the time the filter tag is pushed, which lands one byte below skb->head: skbuff: skb_under_panic: len:49 put:2 head:ffff888003c10000 data:ffff888003c0ffff tail:0x30 end:0x640 dev: kernel BUG at net/core/skbuff.c:214! RIP: 0010:skb_panic+0x13e/0x230 Call Trace: skb_push+0xbd/0x100 ppp_receive_nonmp_frame+0x48a/0x1d10 ppp_input+0x4e9/0x2f80 ppp_async_process+0x2a/0xe0 tasklet_action_common+0x20f/0x8a0 handle_softirqs+0x18e/0x590 Kernel panic - not syncing: Fatal exception in interrupt Zeroing the headroom violates the NET_SKB_PAD guarantee that dev_alloc_skb() gives the rest of the receive path. Besides the filter panic above, when CCP compression is enabled ppp_decompress_frame() hands skb->data - 2 to ->decompress()/->incomp(), which then reads out of bounds before skb->head for the same reason. Rather than restore the headroom, drop the errored frame - as ppp_synctty already does on its error path - and clear ap->rpkt so the next frame is reassembled into a fresh skb with proper headroom. This is simpler and fixes both the filter under-panic and the CCP out-of-bounds read. The original V1 of this patch made room in ppp_receive_nonmp_frame() with skb_cow_head(); Eric pointed out that fixing the root cause in the transport is the right approach. Found by fuzzing the PPP receive path with a mutating peer on a pty; it is an interesting (remote) DoS: root configures PPP, the peer supplies two crashing frames. The reproducer (repro-ppp-skb.c, unchanged from v1) panics in about a second, and returns cleanly with this applied. Fixes: 6722e78c9005 ("[PPP]: handle misaligned accesses") Suggested-by: Eric Dumazet Signed-off-by: Vlatko Kosturjak Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/apkR6ZU+tqP2C3Fl@griffin.linux.hr Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_async.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c index ea7fe9608ffd..6e6e2b944128 100644 --- a/drivers/net/ppp/ppp_async.c +++ b/drivers/net/ppp/ppp_async.c @@ -742,11 +742,8 @@ process_input_packet(struct asyncppp *ap) err: /* frame had an error, remember that, reset SC_TOSS & SC_ESCAPE */ ap->state = SC_PREV_ERROR; - if (skb) { - /* make skb appear as freshly allocated */ - skb_trim(skb, 0); - skb_reserve(skb, - skb_headroom(skb)); - } + kfree_skb(skb); + ap->rpkt = NULL; } /* Called when the tty driver has data for us. Runs parallel with the From e24279bffec6c9aa3fef7e3c64bd4000aca9d698 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 12:06:08 +0200 Subject: [PATCH 0598/1317] MAINTAINERS: Update the so_txtime selftest path in SOCKET TIMESTAMPING Commit 5c6baef3885c ("selftests: drv-net: convert so_txtime to drv-net") moved the test to tools/testing/selftests/drivers/net/, but the SOCKET TIMESTAMPING entry still lists the old path and scripts/get_maintainer.pl --self-test=patterns reports it as matching nothing. Point the pattern at the new location. Signed-off-by: Karl Mehltretter Reviewed-by: Jason Xing Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260905100608.42539-1-kmehltretter@gmail.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index fc320b373656..6f2c8cb67c7d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25335,8 +25335,8 @@ F: Documentation/networking/timestamping.rst F: include/linux/net_tstamp.h F: include/uapi/linux/net_tstamp.h F: tools/testing/selftests/bpf/*/net_timestamping* +F: tools/testing/selftests/drivers/net/so_txtime.* F: tools/testing/selftests/net/*timestamp* -F: tools/testing/selftests/net/so_txtime.c SOEKRIS NET48XX LED SUPPORT M: Chris Boot From cdca92eddc025fdb90071be97738f7d55a65f8dd Mon Sep 17 00:00:00 2001 From: Naman Gulati Date: Fri, 4 Sep 2026 18:06:44 +0000 Subject: [PATCH 0599/1317] ipv6: null-check fib6_node before accessing in __ip6_del_rt_siblings() syzbot reported a null-ptr-deref in __ip6_del_rt_siblings() [0]. The stack trace hinted towards a null dereference of rt->fib6_node when fn->leaf is accessed in __ip6_del_rt_siblings(). With RTNL_FLAG_DOIT_UNLOCKED set, inet6_rtm_delroute() operations run concurrently without acquiring the RTNL lock. In ip6_route_del(), the route lookup happens under rcu_read_lock() without acquiring table->tb6_lock. Between ip6_route_del() looking up the route and __ip6_del_rt_siblings() acquiring table->tb6_lock, another thread can modify the routing table. For example, when an ECMP route is replaced via RTM_NEWROUTE with NLM_F_REPLACE, fib6_add_rt2node() unlinks all old siblings and sets iter->fib6_node = NULL. A reproducer was found that triggers this [1]. Add a check to ensure rt->fib6_node is non-null before accessing it. [0] KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027] RIP: 0010:__ip6_del_rt_siblings+0x31e/0x7c0 net/ipv6/route.c:4056 Call Trace: ip6_route_del+0x1054/0x1110 net/ipv6/route.c:4232 inet6_rtm_delroute+0x5d7/0x6d0 net/ipv6/route.c:5669 rtnetlink_rcv_msg+0x802/0xc00 net/core/rtnetlink.c:7132 netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2556 netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline] netlink_unicast+0x7f5/0x990 net/netlink/af_netlink.c:1345 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900 sock_sendmsg_nosec+0x13a/0x180 net/socket.c:800 __sock_sendmsg net/socket.c:815 [inline] ____sys_sendmsg+0x565/0x870 net/socket.c:2713 ___sys_sendmsg+0x2a5/0x360 net/socket.c:2767 __sys_sendmsg net/socket.c:2799 [inline] __do_sys_sendmsg net/socket.c:2804 [inline] __se_sys_sendmsg net/socket.c:2802 [inline] __x64_sys_sendmsg+0x1b7/0x290 net/socket.c:2802 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f [1] https://gist.github.com/NamanGulati/0766a1159b6ca61928faaf87425ff899 Fixes: bd11ff421d36 ("ipv6: Get rid of RTNL for SIOCDELRT and RTM_DELROUTE.") Reported-by: syzbot+a73e5ee0fd534fed75bd@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a9b03f9.04649fcc.10325f.0003.GAE@google.com Signed-off-by: Naman Gulati Reviewed-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Reviewed-by: Fernando Fernandez Mancera Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260904180645.706425-1-namangulati@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 9658939511e0..08bd68f1b5bb 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -4019,6 +4019,7 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) struct net *net = info->nl_net; struct sk_buff *skb = NULL; struct fib6_table *table; + struct fib6_node *fn; int err = -ENOENT; if (rt == net->ipv6.fib6_null_entry) @@ -4026,9 +4027,13 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) table = rt->fib6_table; spin_lock_bh(&table->tb6_lock); + fn = rcu_dereference_protected(rt->fib6_node, + lockdep_is_held(&table->tb6_lock)); + if (!fn) + goto out_unlock; + if (rt->fib6_nsiblings && cfg->fc_delete_all_nh) { struct fib6_info *sibling, *next_sibling; - struct fib6_node *fn; /* prefer to send a single notification with all hops */ skb = nlmsg_new(rt6_nlmsg_size(rt), GFP_ATOMIC); @@ -4051,8 +4056,6 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) * and emit a replace or delete notification, respectively. */ info->skip_notify_kernel = 1; - fn = rcu_dereference_protected(rt->fib6_node, - lockdep_is_held(&table->tb6_lock)); if (rcu_access_pointer(fn->leaf) == rt) { struct fib6_info *last_sibling, *replace_rt; From ba4ba11ed6eb8972c69070417fc27b48deb002e8 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sun, 6 Sep 2026 10:21:09 +0200 Subject: [PATCH 0600/1317] net: openvswitch: fix use-after-free of the flow table mask array tbl_mask_array_realloc() retires the old mask_array before it stops being reachable: old = ovsl_dereference(tbl->mask_array); if (old) { ... call_rcu(&old->rcu, mask_array_rcu_cb); } rcu_assign_pointer(tbl->mask_array, new); call_rcu() only waits for read-side critical sections already in flight. tbl->mask_array still points at old between the call_rcu() and the rcu_assign_pointer(), so a reader entering ovs_flow_tbl_lookup_stats() in that window picks up old in a fresh critical section that the pending grace period does not cover. tbl_mask_array_realloc() runs in process context under ovs_mutex, so the window is preemptible and can outlast the grace period. Then mask_array_rcu_cb() frees old before the swap runs: BUG: KASAN: slab-use-after-free in flow_lookup.constprop.0+0x2bf/0x2f0 Read of size 8 at addr ffff888020b3e018 by task poc/741 flow_lookup.constprop.0+0x2bf/0x2f0 ovs_flow_tbl_lookup_stats+0x4a3/0x5c0 ovs_dp_process_packet+0x19c/0x710 ovs_vport_receive+0x243/0x390 internal_dev_xmit+0x81/0x170 Freed by task 728: kfree+0x16a/0x4e0 rcu_core+0x853/0x1030 Publish the new array before retiring the old one. The kfree_rcu() that call_rcu() replaced ran after the swap. Fixes: eac87c413bf9 ("net: openvswitch: reorder masks array based on usage") Cc: stable@vger.kernel.org Signed-off-by: Norbert Szetei Reviewed-by: Ilya Maximets Acked-by: Eelco Chaudron echaudro@redhat.com Link: https://patch.msgid.link/DE115F9C-2545-423E-A702-986FC952FD62@doyensec.com Signed-off-by: Jakub Kicinski --- net/openvswitch/flow_table.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c index 67d5b8c0fe79..1e0f9d193eb0 100644 --- a/net/openvswitch/flow_table.c +++ b/net/openvswitch/flow_table.c @@ -257,11 +257,13 @@ static int tbl_mask_array_realloc(struct flow_table *tbl, int size) if (ovsl_dereference(old->masks[i])) new->masks[new->count++] = old->masks[i]; } - call_rcu(&old->rcu, mask_array_rcu_cb); } rcu_assign_pointer(tbl->mask_array, new); + if (old) + call_rcu(&old->rcu, mask_array_rcu_cb); + return 0; } From 7a49e6b16f36b8e085521699adbca3e321b6dd0c Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 3 Sep 2026 14:56:14 +0800 Subject: [PATCH 0601/1317] net: bridge: use option bits for CFM/MRP frame handlers CFM and MRP register a global br_frame_type whose hlist_node is linked into the per-bridge frame_type_list when the first MEP/MRP instance is created. Enabling the protocol on multiple bridges therefore inserts the same node into multiple lists. Unregistering it on one bridge then corrupts list state belonging to another. These handlers can only be installed once per bridge, and they are uncommon. Track their per-bridge enable state with net_bridge option bits, which already live on the Rx hot cache line, and dispatch the matching handler directly from the receive path. Check both bits together first as an unlikely case. Remove the generic frame_type_list and br_frame_type helpers, which have had no other users since CFM and MRP were added. That shrinks struct net_bridge by 8 bytes and drops the list walk from the fast path. When neither protocol is compiled in, BR_CFM_MRP_OPTS is 0 and the compiler prunes the branch. Fixes: 90c628dd47ff ("net: bridge: extend the process of special frames") Fixes: dc32cbb3dbd7 ("bridge: cfm: Kernel space implementation of CFM. CCM frame RX added.") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Nikolay Aleksandrov Co-developed-by: Yilin Zhu Signed-off-by: Yilin Zhu Signed-off-by: Zhiling Zou Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/0345b9d5aa60ba416f6738ff1b87140f0a749cb8.1788417901.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/bridge/br_cfm.c | 11 +++-------- net/bridge/br_device.c | 1 - net/bridge/br_input.c | 35 ++++++++++++++--------------------- net/bridge/br_mrp.c | 13 +++---------- net/bridge/br_private.h | 26 +++++++++++++++----------- 5 files changed, 35 insertions(+), 51 deletions(-) diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c index dea56fffa1c1..9dcc97d63a6f 100644 --- a/net/bridge/br_cfm.c +++ b/net/bridge/br_cfm.c @@ -367,7 +367,7 @@ static u32 ccm_tlv_extract(struct sk_buff *skb, u32 index, } /* note: already called with rcu_read_lock */ -static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb) +int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb) { u32 mdlevel, interval, size, index, max; const struct br_cfm_common_hdr *hdr; @@ -489,11 +489,6 @@ static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb) return 1; } -static struct br_frame_type cfm_frame_type __read_mostly = { - .type = cpu_to_be16(ETH_P_CFM), - .frame_handler = br_cfm_frame_rx, -}; - int br_cfm_mep_create(struct net_bridge *br, const u32 instance, struct br_cfm_mep_create *const create, @@ -559,7 +554,7 @@ int br_cfm_mep_create(struct net_bridge *br, INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired); if (hlist_empty(&br->mep_list)) - br_add_frame(br, &cfm_frame_type); + br_opt_toggle(br, BROPT_CFM_ENABLED, true); hlist_add_tail_rcu(&mep->head, &br->mep_list); @@ -588,7 +583,7 @@ static void mep_delete_implementation(struct net_bridge *br, kfree_rcu(mep, rcu); if (hlist_empty(&br->mep_list)) - br_del_frame(br, &cfm_frame_type); + br_opt_toggle(br, BROPT_CFM_ENABLED, false); } int br_cfm_mep_delete(struct net_bridge *br, diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c index ff55dab73632..e01c44a90d84 100644 --- a/net/bridge/br_device.c +++ b/net/bridge/br_device.c @@ -503,7 +503,6 @@ void br_dev_setup(struct net_device *dev) spin_lock_init(&br->lock); INIT_LIST_HEAD(&br->port_list); INIT_HLIST_HEAD(&br->fdb_list); - INIT_HLIST_HEAD(&br->frame_type_list); #if IS_ENABLED(CONFIG_BRIDGE_MRP) INIT_HLIST_HEAD(&br->mrp_list); #endif diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index d87a5f9fa92b..8bed72baf161 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c @@ -317,17 +317,25 @@ static int nf_hook_bridge_pre(struct sk_buff *skb, struct sk_buff **pskb) return RX_HANDLER_CONSUMED; } +#define BR_CFM_MRP_OPTS \ + ((IS_ENABLED(CONFIG_BRIDGE_CFM) ? BIT(BROPT_CFM_ENABLED) : 0UL) | \ + (IS_ENABLED(CONFIG_BRIDGE_MRP) ? BIT(BROPT_MRP_ENABLED) : 0UL)) + /* Return 0 if the frame was not processed otherwise 1 * note: already called with rcu_read_lock */ static int br_process_frame_type(struct net_bridge_port *p, struct sk_buff *skb) { - struct br_frame_type *tmp; + struct net_bridge *br = p->br; - hlist_for_each_entry_rcu(tmp, &p->br->frame_type_list, list) - if (unlikely(tmp->type == skb->protocol)) - return tmp->frame_handler(p, skb); + if (skb->protocol == htons(ETH_P_CFM) && + br_opt_get(br, BROPT_CFM_ENABLED)) + return br_cfm_frame_rx(p, skb); + + if (skb->protocol == htons(ETH_P_MRP) && + br_opt_get(br, BROPT_MRP_ENABLED)) + return br_mrp_process(p, skb); return 0; } @@ -425,7 +433,8 @@ static rx_handler_result_t br_handle_frame(struct sk_buff **pskb) } } - if (unlikely(br_process_frame_type(p, skb))) + if (unlikely((READ_ONCE(p->br->options) & BR_CFM_MRP_OPTS) && + br_process_frame_type(p, skb))) return RX_HANDLER_PASS; forward: @@ -467,19 +476,3 @@ rx_handler_func_t *br_get_rx_handler(const struct net_device *dev) return br_handle_frame; } - -void br_add_frame(struct net_bridge *br, struct br_frame_type *ft) -{ - hlist_add_head_rcu(&ft->list, &br->frame_type_list); -} - -void br_del_frame(struct net_bridge *br, struct br_frame_type *ft) -{ - struct br_frame_type *tmp; - - hlist_for_each_entry(tmp, &br->frame_type_list, list) - if (ft == tmp) { - hlist_del_rcu(&ft->list); - return; - } -} diff --git a/net/bridge/br_mrp.c b/net/bridge/br_mrp.c index ef16d0703924..dce6efa96c4c 100644 --- a/net/bridge/br_mrp.c +++ b/net/bridge/br_mrp.c @@ -6,13 +6,6 @@ static const u8 mrp_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x1 }; static const u8 mrp_in_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x3 }; -static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb); - -static struct br_frame_type mrp_frame_type __read_mostly = { - .type = cpu_to_be16(ETH_P_MRP), - .frame_handler = br_mrp_process, -}; - static bool br_mrp_is_ring_port(struct net_bridge_port *p_port, struct net_bridge_port *s_port, struct net_bridge_port *port) @@ -486,7 +479,7 @@ static void br_mrp_del_impl(struct net_bridge *br, struct br_mrp *mrp) kfree_rcu(mrp, rcu); if (hlist_empty(&br->mrp_list)) - br_del_frame(br, &mrp_frame_type); + br_opt_toggle(br, BROPT_MRP_ENABLED, false); } /* Adds a new MRP instance. @@ -536,7 +529,7 @@ int br_mrp_add(struct net_bridge *br, struct br_mrp_instance *instance) rcu_assign_pointer(mrp->s_port, p); if (hlist_empty(&br->mrp_list)) - br_add_frame(br, &mrp_frame_type); + br_opt_toggle(br, BROPT_MRP_ENABLED, true); INIT_DELAYED_WORK(&mrp->test_work, br_mrp_test_work_expired); INIT_DELAYED_WORK(&mrp->in_test_work, br_mrp_in_test_work_expired); @@ -1241,7 +1234,7 @@ static int br_mrp_rcv(struct net_bridge_port *p, * normal forwarding. * note: already called with rcu_read_lock */ -static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb) +int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb) { /* If there is no MRP instance do normal forwarding */ if (likely(!test_bit(BR_MRP_AWARE_BIT, &p->flags))) diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index d337b1cfb980..b01997ea9508 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -495,12 +495,13 @@ enum net_bridge_opts { BROPT_MST_ENABLED, BROPT_MDB_OFFLOAD_FAIL_NOTIFICATION, BROPT_FDB_LOCAL_VLAN_0, + BROPT_CFM_ENABLED, + BROPT_MRP_ENABLED, }; struct net_bridge { spinlock_t lock; spinlock_t hash_lock; - struct hlist_head frame_type_list; struct net_device *dev; unsigned long options; /* These fields are accessed on each packet */ @@ -932,16 +933,6 @@ int nbp_backup_change(struct net_bridge_port *p, struct net_device *backup_dev); int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb); rx_handler_func_t *br_get_rx_handler(const struct net_device *dev); -struct br_frame_type { - __be16 type; - int (*frame_handler)(struct net_bridge_port *port, - struct sk_buff *skb); - struct hlist_node list; -}; - -void br_add_frame(struct net_bridge *br, struct br_frame_type *ft); -void br_del_frame(struct net_bridge *br, struct br_frame_type *ft); - static inline bool br_rx_handler_check_rcu(const struct net_device *dev) { return rcu_dereference(dev->rx_handler) == br_get_rx_handler(dev); @@ -2080,6 +2071,7 @@ int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p, bool br_mrp_enabled(struct net_bridge *br); void br_mrp_port_del(struct net_bridge *br, struct net_bridge_port *p); int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br); +int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb); #else static inline int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p, struct nlattr *attr, int cmd, @@ -2103,6 +2095,11 @@ static inline int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br) return 0; } +static inline int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb) +{ + return 0; +} + #endif /* br_cfm.c */ @@ -2111,6 +2108,7 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p, struct nlattr *attr, int cmd, struct netlink_ext_ack *extack); bool br_cfm_created(struct net_bridge *br); void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *p); +int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb); int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br); int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br, @@ -2135,6 +2133,12 @@ static inline void br_cfm_port_del(struct net_bridge *br, { } +static inline int br_cfm_frame_rx(struct net_bridge_port *port, + struct sk_buff *skb) +{ + return 0; +} + static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br) { return -EOPNOTSUPP; From 7d059f390750152b9bd69df934198651b94fc26d Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 7 Sep 2026 23:08:55 +0200 Subject: [PATCH 0602/1317] net: macb: destroy the phylink instance on the probe error path macb_mii_init() creates a phylink instance on both of its success paths, but the probe unwind frees the netdev without destroying it, so a failing macb_alloc_tieoff() or register_netdev() leaks the instance. Destroy it at err_out_unregister_mdio, which is only reachable once macb_mii_init() has succeeded, so bp->phylink is valid there. Fixes: 7897b071ac3b ("net: macb: convert to phylink") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260907210856.1673589-2-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 0e75339fa206..50ecfa80c660 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -5980,6 +5980,7 @@ static int macb_probe(struct platform_device *pdev) mdiobus_unregister(bp->mii_bus); mdiobus_free(bp->mii_bus); } + phylink_destroy(bp->phylink); err_out_phy_exit: phy_exit(bp->phy); From 382a373d9ea7a6ac4de9c022385b6217f65ae3cc Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 7 Sep 2026 23:08:56 +0200 Subject: [PATCH 0603/1317] net: macb: put the "mdio" child node reference on success macb_mii_init() holds the reference returned by of_get_child_by_name() for macb_mdiobus_register() and drops it only on the error paths, so every successful probe leaks a node reference. On a CM5, overlay removal after four bind cycles reports OF: ERROR: memory leak, expected refcount 1 instead of 5 Drop the reference after registration, where __mdiobus_register() has already taken its own for the lifetime of the bus. Fixes: 8a6631f1cece ("net: macb: avoid redundant lookup for "mdio" child node in MDIO setup") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260907210856.1673589-3-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 50ecfa80c660..77dec2d6e3fb 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -1164,6 +1164,8 @@ static int macb_mii_init(struct macb *bp) if (err) goto err_out_unregister_bus; + of_node_put(mdio_np); + return 0; err_out_unregister_bus: From 796aa0547557e63338657ed1c487906f9fac4c73 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 8 Sep 2026 17:53:20 -0600 Subject: [PATCH 0604/1317] io_uring/rw: end write accounting from ->ki_complete Commit b000145e9907 moved both the fsnotify calls and the write accounting out of the kiocb completion handler and into the io_req_rw_complete() task_work. However, only the fsnotify part actually needed to move as it may sleep. Ending the write accounting is just a percpu_up_read() on the superblock writers sem. Deferring it is a problem, because it makes dropping SB_FREEZE_WRITE protection depend on the ring owner getting to running task_work. But the task may be blocked in freeze_super(), causing it to never get to that: task io-wq worker -------------------------------------------------------------- io_write() io_kiocb_start_write() (takes sb_writers, hidden from lockdep by __sb_writers_release) write_iter() -> -EIOCBQUEUED ioctl(FS_IOC_SHUTDOWN) bdev_freeze() freeze_super() percpu_down_write() <- waits for the reader above io_write() kiocb_start_write() percpu_down_read() <- queued behind the writer io_complete_rw() queues io_req_rw_complete() <- never runs, task is in D state End the write from io_complete_rw() instead, and leave only the fsnotify calls in task_work. Reported-by: syzbot+2eb3d983669d3e49d4fa@syzkaller.appspotmail.com Cc: stable@vger.kernel.org Fixes: b000145e9907 ("io_uring/rw: defer fsnotify calls to task context") Signed-off-by: Jens Axboe --- io_uring/rw.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/io_uring/rw.c b/io_uring/rw.c index 95106dd1d7eb..3e22f294bdf2 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -517,20 +517,25 @@ static void io_req_end_write(struct io_kiocb *req) } } -/* - * Trigger the notifications after having done some IO, and finish the write - * accounting, if any. - */ +/* Trigger the notifications after having done some IO. */ +static void io_req_io_notify(struct io_kiocb *req) +{ + struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw); + + if (rw->kiocb.ki_flags & IOCB_WRITE) + fsnotify_modify(req->file); + else + fsnotify_access(req->file); +} + +/* Finish write accounting and notify, for inline completions only. */ static void io_req_io_end(struct io_kiocb *req) { struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw); - if (rw->kiocb.ki_flags & IOCB_WRITE) { + if (rw->kiocb.ki_flags & IOCB_WRITE) io_req_end_write(req); - fsnotify_modify(req->file); - } else { - fsnotify_access(req->file); - } + io_req_io_notify(req); } static void __io_complete_rw_common(struct io_kiocb *req, long res) @@ -563,7 +568,7 @@ void io_req_rw_complete(struct io_tw_req tw_req, io_tw_token_t tw) { struct io_kiocb *req = tw_req.req; - io_req_io_end(req); + io_req_io_notify(req); if (req->flags & (REQ_F_BUFFER_SELECTED|REQ_F_BUFFER_RING)) req->cqe.flags |= io_put_kbuf(req, max(req->cqe.res, 0), NULL); @@ -577,6 +582,10 @@ static void io_complete_rw(struct kiocb *kiocb, long res) struct io_rw *rw = container_of(kiocb, struct io_rw, kiocb); struct io_kiocb *req = cmd_to_io_kiocb(rw); + /* ring owner may block in freeze_super() before task_work runs */ + if (kiocb->ki_flags & IOCB_WRITE) + io_req_end_write(req); + __io_complete_rw_common(req, res); io_req_set_res(req, io_fixup_rw_res(req, res), 0); req->io_task_work.func = io_req_rw_complete; From dcbd1c054848848a1937ca0768ce2bdbc31ae621 Mon Sep 17 00:00:00 2001 From: Gabriel Krisman Bertazi Date: Wed, 2 Sep 2026 20:00:40 -0300 Subject: [PATCH 0605/1317] io_uring/net: let io_recv_buf_select return the length of the buffer region In preparation to using this field as an upper limit to truncation, return the size of the allocated region. Fixes: ae98dbf43d75 ("io_uring/kbuf: add support for incremental buffer consumption") Cc: stable@vger.kernel.org Signed-off-by: Gabriel Krisman Bertazi Link: https://patch.msgid.link/20260902230041.1320658-2-krisman@suse.de Signed-off-by: Jens Axboe --- io_uring/net.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/io_uring/net.c b/io_uring/net.c index fbe719d86c46..647156c9331a 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -1108,6 +1108,7 @@ static int io_recv_buf_select(struct io_kiocb *req, struct io_async_msghdr *kmsg struct io_br_sel *sel, unsigned int issue_flags) { struct io_sr_msg *sr = io_kiocb_to_cmd(req, struct io_sr_msg); + size_t len; int ret; /* @@ -1153,13 +1154,14 @@ static int io_recv_buf_select(struct io_kiocb *req, struct io_async_msghdr *kmsg /* special case 1 vec, can be a fast path */ if (ret == 1) { sr->buf = arg.iovs[0].iov_base; - sr->len = arg.iovs[0].iov_len; + len = sr->len = arg.iovs[0].iov_len; goto map_ubuf; } iov_iter_init(&kmsg->msg.msg_iter, ITER_DEST, arg.iovs, ret, - arg.out_len); + arg.out_len); + len = arg.out_len; } else { - size_t len = sel->val; + len = sel->val; *sel = io_buffer_select(req, &len, sr->buf_group, issue_flags); if (!sel->addr) @@ -1173,7 +1175,7 @@ static int io_recv_buf_select(struct io_kiocb *req, struct io_async_msghdr *kmsg return ret; } - return 0; + return len; } int io_recv(struct io_kiocb *req, unsigned int issue_flags) From 1f6de65e3314519ce462bfd3a1d29e918f97e4e5 Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Tue, 8 Sep 2026 12:37:27 +0000 Subject: [PATCH 0606/1317] opp: fix use after free in _update_opp_table_clk() dev_pm_opp_put_opp_table() frees the opp_table which is subsquently used by dev_err_probe(). This causes an Oops during boot on gs101-oriole. cpu cpu0: error 000000006b6b6b6b: Couldn't find clock Unable to handle kernel paging request at virtual address 006b6b6b6b6b6cd3 ... Hardware name: Oriole (DT) pstate: 00400005 (nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : _of_add_table_indexed+0x80/0xbb0 lr : _of_add_table_indexed+0x6c/0xbb0 ... Call trace: _of_add_table_indexed+0x80/0xbb0 (P) dev_pm_opp_of_cpumask_add_table+0x70/0x120 dt_cpufreq_probe+0x23c/0x480 platform_probe+0x64/0xb8 Fixes: 84f05af0975c9 ("opp: Use clk_get_optional() to avoid leaving opp_table->clk as an error pointer") Signed-off-by: Peter Griffin Reviewed-by: Tudor Ambarus [ Viresh: use return value of dev_err_probe() ] Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 1e3b80a1f88e..1da7d86241ae 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1581,6 +1581,8 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, struct opp_table *opp_table, bool getclk) { + int ret; + /* * Return early if we don't need to get clk or we have already done it * earlier. @@ -1607,9 +1609,9 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, opp_table->clk = clk_get_optional(dev, NULL); if (IS_ERR(opp_table->clk)) { + ret = dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); dev_pm_opp_put_opp_table(opp_table); - dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); - return ERR_CAST(opp_table->clk); + return ERR_PTR(ret); } if (opp_table->clk) From 6cc27d82196385fe06853319f74312a7d8019726 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 2 Sep 2026 19:08:08 +0100 Subject: [PATCH 0607/1317] mm/vma: correctly unaccount on mmap_prepare() failure __mmap_setup() accounts memory for relevant mappings via: security_vm_enough_memory_mm() -> __vm_enough_memory() -> vm_acct_memory() If __mmap_setup() fails, this indicates that this accounting did not take place, and thus it's appropriate for __mmap_region() to jump to abort_munmap. However if call_mmap_prepare() fails, it also jumps there and any accounted memory is not correctly unaccounted. Fix this by handling each error separately. Link: https://lore.kernel.org/20260902-fix-unaccount-mmap_prepare-v1-1-ea070189fdfb@kernel.org Fixes: c84bf6dd2b83 ("mm: introduce new .mmap_prepare() file callback") Signed-off-by: Lorenzo Stoakes (ARM) Cc: Jann Horn Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/vma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mm/vma.c b/mm/vma.c index 35e7a64855fa..f29abb30956b 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -2859,10 +2859,12 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, map.check_ksm_early = can_set_ksm_flags_early(&map); error = __mmap_setup(&map, &desc, uf); - if (!error && have_mmap_prepare) - error = call_mmap_prepare(&map, &desc); if (error) goto abort_munmap; + if (have_mmap_prepare) + error = call_mmap_prepare(&map, &desc); + if (error) + goto unacct_error; if (map.check_ksm_early) update_ksm_flags(&map); From 932cfb25e7ce98d1f93895671ec186a3087e4f80 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 2 Sep 2026 15:37:59 +0800 Subject: [PATCH 0608/1317] mm/shrinker: fix bogus set_shrinker_bit() with cgroup.memory=nokmem With cgroup.memory=nokmem, shrinker_memcg_alloc() bails out early and never allocates an id, so shrinker->id keeps the 0 it got from the kzalloc() in shrinker_alloc(). __list_lru_init() then copies that 0 into lru->shrinker_id, where it looks like a valid bit index. Nothing calls expand_shrinker_info() on nokmem either, so shrinker_nr_max stays 0 and every memcg ends up with an empty map (map_nr_max == 0). deferred_split_folio() hands a real memcg to __list_lru_add() regardless of whether the lru is memcg aware, so the first THP queued in a cgroup does set_shrinker_bit(memcg, nid, 0) and trips the bounds check: WARNING: mm/shrinker.c:212 at set_shrinker_bit+0x7d/0x90, CPU#126 Call Trace: deferred_split_folio+0x18c/0x220 map_anon_folio_pmd_nopf+0xdd/0x130 map_anon_folio_pmd_pf+0x14/0xb0 do_huge_pmd_anonymous_page+0x1a1/0x620 __handle_mm_fault+0xea9/0x10d0 handle_mm_fault+0xe5/0x320 do_user_addr_fault+0x1cc/0x870 exc_page_fault+0x81/0x1b0 asm_exc_page_fault+0x27/0x30 Harmless, the WARN_ON_ONCE() is what keeps the out of bounds unit[] read from happening, but the id should not look valid in the first place. Clear it before returning. Two other spots could paper over this: drop the id in __list_lru_init() when nokmem turns memcg_aware off, or make deferred_split_folio() pass NULL like list_lru_add_obj() does. Both leave shrinker->id lying around for the next caller, so fix it where the id is handed out. Link: https://lore.kernel.org/20260902073800.305481-1-jiayuan.chen@linux.dev Fixes: fafaeceb89a5 ("mm: switch deferred split shrinker to list_lru") Signed-off-by: Jiayuan Chen Acked-by: Shakeel Butt Cc: Usama Arif Cc: Dave Chinner Cc: Johannes Weiner Cc: Kairui Song Cc: Muchun Song Cc: Roman Gushchin Cc: Signed-off-by: Andrew Morton --- mm/shrinker.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/shrinker.c b/mm/shrinker.c index a70aab124a0e..7ec2a9704f6f 100644 --- a/mm/shrinker.c +++ b/mm/shrinker.c @@ -227,6 +227,8 @@ static int shrinker_memcg_alloc(struct shrinker *shrinker) { int id; + shrinker->id = -1; + if (mem_cgroup_disabled()) return -ENOSYS; if (mem_cgroup_kmem_disabled() && !(shrinker->flags & SHRINKER_NONSLAB)) From 7891fbb9512f127826e1d5dbf380ee212bd15eb0 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Tue, 1 Sep 2026 20:38:40 -0700 Subject: [PATCH 0609/1317] mm/folio: EXPORT_SYMBOL_FOR_KVM(lru_cache_drain_for_folio) To simplify independent development in the KVM and MM subsystems, now export to KVM the lru_cache_drain_for_folio() which MM added in 7.3-rc1. Link: https://lore.kernel.org/lkml/bd6c9c74-e374-a9d3-ba1f-8b6f430894fc@google.com/T/#u Link: https://lore.kernel.org/02876cea-5727-2ca4-bead-73659ea6fec4@google.com Signed-off-by: Ackerley Tng Signed-off-by: Hugh Dickins Acked-by: Vlastimil Babka (SUSE) Suggested-by: David Hildenbrand Reviewed-by: Fuad Tabba Reviewed-by: Binbin Wu Cc: Matthew Wilcox (Oracle) Cc: Sean Christopherson Signed-off-by: Andrew Morton --- mm/folio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/folio.c b/mm/folio.c index c02dcea9c03c..50a6dbe55998 100644 --- a/mm/folio.c +++ b/mm/folio.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "internal.h" #include "page_alloc.h" @@ -926,6 +927,7 @@ void lru_cache_drain_for_folio(const struct folio *folio, *drained = LRU_CACHE_DRAINED_ALL; } } +EXPORT_SYMBOL_FOR_KVM(lru_cache_drain_for_folio); atomic_t lru_disable_count = ATOMIC_INIT(0); From 504981db4f69bdd28054fb98c96a3a67f7248dde Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sat, 5 Sep 2026 16:47:27 +0900 Subject: [PATCH 0610/1317] dma-coherent: report a failed reserved memory assignment rmem_dma_device_init() drops the return value of dma_assign_coherent_memory() and always reports success. That call fails with -EBUSY when the device already has a coherent pool, and the file allows only "*one* such region of memory" per device. of_reserved_mem_device_init_by_idx() reads the zero as success. It logs "assigned reserved memory node" for a region that was not assigned and records the pairing, so of_reserved_mem_device_release() later runs rmem_dma_device_release() for it. That clears dev->dma_mem without looking at which region it was called for, dropping the pool the device did get and leaving it on ordinary memory. dma_declare_coherent_memory() checks the same call and releases the memory on failure, and rmem_swiotlb_device_init() propagates its own errors. Return the error here as well, so a device tree that assigns two pools to one device fails the probe instead of half working. Fixes: 7bfa5ab6fa1b ("drivers: dma-coherent: add initialization from device tree") Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260905074727.108029-1-donggeunyoo.kernel@gmail.com Signed-off-by: Marek Szyprowski --- kernel/dma/coherent.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c index 45bbae947f4b..4d0266893bcc 100644 --- a/kernel/dma/coherent.c +++ b/kernel/dma/coherent.c @@ -352,8 +352,7 @@ static int rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev) min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit)) dev_warn(dev, "reserved memory is beyond device's set DMA address range\n"); - dma_assign_coherent_memory(dev, mem); - return 0; + return dma_assign_coherent_memory(dev, mem); } static void rmem_dma_device_release(struct reserved_mem *rmem, From b7d7914a9ae3097e63d113007e4fb44d33d515b1 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sat, 5 Sep 2026 17:42:10 +0900 Subject: [PATCH 0611/1317] swiotlb: use the adjusted address for the highmem page lookup swiotlb_bounce() reads the page frame number from the slot's recorded orig_addr, then advances orig_addr by tlb_offset to reach the address the caller asked about. The highmem branch mixes the two: the offset within the page comes from the adjusted address, the page from the value before it. Once the adjustment crosses a page boundary the pair no longer describes one location, and the whole copy lands one page below the intended one for a positive tlb_offset, one above for a negative one. DMA_FROM_DEVICE writes the device data over the wrong page and leaves the intended one stale, DMA_TO_DEVICE feeds the device from a page the mapping may not cover. Partial syncs through dma_sync_single_range_for_*() are what make tlb_offset non-zero. The branch test is picked the same way, so a slot recorded in lowmem can be adjusted into highmem and the lowmem path then hands a highmem address to phys_to_virt(). Take both from orig_addr once it is final and keep pfn in the branch that uses it. PhysHighMem() asks the question straight from the address, as dma-debug already does. Fixes: 5f89468e2f06 ("swiotlb: manipulate orig_addr when tlb_addr has offset") Cc: stable@vger.kernel.org Signed-off-by: Donggeun Yoo Reviewed-by: Michael Kelley Link: https://lore.kernel.org/r/20260905084210.148255-1-donggeunyoo.kernel@gmail.com Signed-off-by: Marek Szyprowski --- kernel/dma/swiotlb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c index ded7016a46a7..aa2f1c4588b9 100644 --- a/kernel/dma/swiotlb.c +++ b/kernel/dma/swiotlb.c @@ -1019,7 +1019,6 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size int index = (tlb_addr - mem->start) >> IO_TLB_SHIFT; phys_addr_t orig_addr = mem->slots[index].orig_addr; size_t alloc_size = mem->slots[index].alloc_size; - unsigned long pfn = PFN_DOWN(orig_addr); unsigned char *vaddr = mem->vaddr + tlb_addr - mem->start; int tlb_offset; @@ -1052,7 +1051,8 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size size = alloc_size; } - if (PageHighMem(pfn_to_page(pfn))) { + if (PhysHighMem(orig_addr)) { + unsigned long pfn = PFN_DOWN(orig_addr); unsigned int offset = orig_addr & ~PAGE_MASK; struct page *page; unsigned int sz = 0; From a1c7570cedd03372812a5b693732880867babbca Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:24 +0300 Subject: [PATCH 0612/1317] x86/mm/pat: Acquire init_mm write lock on collapse to avoid UAF x86 implements page attribute modification using its Change Page Attributes (CPA) mechanism. This tracks properties of ranges such as cache mode through x86 page attributes, and as part of that logic manipulates kernel page tables. Since commit: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") ranges of kernel page table entries can be collapsed into huge page table entries as part of this logic. As part of this collapse, it frees the page tables which the collapsed entries previously pointed to, and it does so without any relevant locks being held to preclude concurrent kernel page table walkers. The only way this code can be reached is if CPA_COLLAPSE is specified, and this is only set in set_memory_rox() via: set_memory_rox() -> change_page_attr_set_clr() -> cpa_flush() -> cpa_collapse_large_pages() Notable users of this are execmem and BPF when manipulating executable mappings. However, this is problematic for ptdump as it walks ranges it does not own and thus runs the risk of a use-after-free on page tables freed underneath it. In addition, concurrent CPA collapse operations are possible which can also cause races. Resolve the issue by acquiring the mmap write lock on init_mm across the whole operation. It is safe to acquire a sleeping lock as all the callers invoke set_memory_rox() from process context and in any case, change_page_attr_set_clr() calls vm_unmap_alias() which ultimately takes a mutex, disallowing atomic context here. Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: David Hildenbrand (Arm) Reviewed-by: Dave Hansen Reviewed-by: Will Deacon Reviewed-by: David Carlier Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-1-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 15 ++++++++++++++- include/linux/mmap_lock.h | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index c38faf39ce15..4abddd763882 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -409,7 +410,7 @@ static void __cpa_flush_tlb(void *data) static int collapse_large_pages(unsigned long addr, struct list_head *pgtables); -static void cpa_collapse_large_pages(struct cpa_data *cpa) +static void __cpa_collapse_large_pages(struct cpa_data *cpa) { unsigned long start, addr, end; struct ptdesc *ptdesc, *tmp; @@ -443,6 +444,18 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) } } +static void cpa_collapse_large_pages(struct cpa_data *cpa) +{ + /* + * Take the mmap write lock on init_mm to: + * - Avoid a use-after-free if raced by ptdump (which takes its own + * write lock on init_mm). + * - Serialise concurrent CPA walkers. + */ + scoped_guard(mmap_write_lock, &init_mm) + __cpa_collapse_large_pages(cpa); +} + static void cpa_flush(struct cpa_data *cpa, int cache) { unsigned int i; diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h index bec0eab6ef03..b8a13b8d36a4 100644 --- a/include/linux/mmap_lock.h +++ b/include/linux/mmap_lock.h @@ -630,6 +630,8 @@ static inline void mmap_read_unlock(struct mm_struct *mm) DEFINE_GUARD(mmap_read_lock, struct mm_struct *, mmap_read_lock(_T), mmap_read_unlock(_T)) DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T)) +DEFINE_GUARD(mmap_write_lock, struct mm_struct *, + mmap_write_lock(_T), mmap_write_unlock(_T)) static inline void mmap_read_unlock_non_owner(struct mm_struct *mm) { From d5d8b8662e6e5a565b47a0388640e88402f23274 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:25 +0300 Subject: [PATCH 0613/1317] x86/mm/pat: Acquire init_mm read lock on attribute changes to avoid UAF A previous commit protected against races between ptdump and CPA collapse, however one still exists between attribute changes and collapse as reported by Denis V. Lunev (linked). When an attribute change arises, a lockless page table walker obtains a PTE entry, which is later written to via set_pte_atomic(): ... -> change_page_attr_set_clr() -> __change_page_attr_set_clr() -> __change_page_attr() -> _lookup_address_cpa() -> lookup_address_in_pgd_attr() -> [ lockless page table walker ] -> set_pte_atomic() There is nothing preventing a concurrent CPA collapse which can free the PTE that was retrieved here, resulting in a use-after-free. With the mmap write lock taken on init_mm over CPA collapse, resolve this race by acquiring an mmap read lock on init_mm over __change_page_attr_set_clr(). This locks across the whole operation over which the walk and the PTE entry write occurs, solving the race. It is safe to do this here, as no spinlocks are held upon entry to __change_page_attr_set_clr(). However, the lock must not be held over an allocation, as allocation can trigger reclaim and shrinkers may call into CPA recursively, making deadlocks possible (init_mm -> ... -> fs_reclaim -> init_mm). A page table is allocated when a huge page needs to be split: -> change_page_attr_set_clr() -> __change_page_attr_set_clr() -> __change_page_attr() -> split_large_page() [ pagetable_alloc() ] -> __split_large_page() Avoid deadlocks by dropping the mmap lock across pagetable_alloc() in split_large_page() and track whether this is needed by adding a new 'init_mm_read_locked' flag to struct cpa_data. This is safe as __split_large_page() (called with locks re-established) revalidates that the page table entry is the same as it was prior to the locks being dropped and __change_page_attr() repeats the entire page table walk whenever a split occurs, so concurrent split and collapse are accounted for. Concurrent ptdump is also safe as the lock is only dropped over page table allocation during which time the page table has not yet been modified. The CPA_COLLAPSE flag is only set by set_memory_rox(), which exclusively operates upon vmalloc ranges, and on x86 only within the module mapping space. This is important, because some callers directly invoke __change_page_attr_set_clr(), bypassing this lock. However, none of these operate within the module mapping space. * cpa_process_alias() - a recursive helper called by __change_page_attr_set_clr(). * __set_memory_enc_pgtable() - operates on the direct mapping and (via __vmbus_establish_gpadl()) the vmalloc mapping space. * __set_pages_[n]p() - called by set_direct_map_[invalid, default, valid]_noflush(), __kernel_map_pages() - operates on the direct map. * kernel_[un]map_pages_in_pgd() - operates on EFI ranges. This work is based upon Denis V. Lunev's excellent analysis of the bug with gratitude. [ dhansen: move to imperative voice in changelog ] Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Link: https://lore.kernel.org/all/20260626163213.2284080-1-den@openvz.org/ Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-2-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index 4abddd763882..cb5d6d6f71a4 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -50,7 +50,8 @@ struct cpa_data { unsigned int flags; unsigned int force_split : 1, force_static_prot : 1, - force_flush_all : 1; + force_flush_all : 1, + init_mm_read_locked : 1; struct page **pages; }; @@ -1240,7 +1241,11 @@ static int split_large_page(struct cpa_data *cpa, pte_t *kpte, struct ptdesc *ptdesc; spin_unlock(&cpa_lock); + if (cpa->init_mm_read_locked) + mmap_read_unlock(&init_mm); ptdesc = pagetable_alloc(GFP_KERNEL, 0); + if (cpa->init_mm_read_locked) + mmap_read_lock(&init_mm); spin_lock(&cpa_lock); if (!ptdesc) return -ENOMEM; @@ -2134,7 +2139,11 @@ static int change_page_attr_set_clr(unsigned long *addr, int numpages, cpa.curpage = 0; cpa.force_split = force_split; - ret = __change_page_attr_set_clr(&cpa, 1); + /* Avoid race with concurrent CPA collapse. */ + cpa.init_mm_read_locked = true; + scoped_guard(mmap_read_lock, &init_mm) + ret = __change_page_attr_set_clr(&cpa, 1); + cpa.init_mm_read_locked = false; /* * Check whether we really changed something: From 1587d3394e254639cc36516256031334095e6ef3 Mon Sep 17 00:00:00 2001 From: Pedro Falcato Date: Thu, 13 Aug 2026 12:01:26 +0300 Subject: [PATCH 0614/1317] x86/alternatives: Exclude text poking against change_page_attr() From time to time, the following BUG can be observed in the x86 alternatives patching code [0]: > kernel BUG at arch/x86/kernel/alternative.c:2576! > Oops: invalid opcode: 0000 [#1] SMP NOPTI > CPU: 0 UID: 0 PID: 355 Comm: (udev-worker) Not tainted 7.1.3-1-default #1 PREEMPT(full) openSUSE Tumbleweed 8c1795b03ec64f997e57a8ad38b1161e3b98da64 > Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS unknown 02/02/2022 > RIP: 0010:__text_poke+0x2aa/0x450 > Call Trace: > > smp_text_poke_batch_finish+0x2a7/0x320 > __static_call_transform+0xb7/0x220 > arch_static_call_transform+0x5b/0xb0 > __static_call_init+0xe9/0x270 > static_call_module_notify+0x11f/0x150 > notifier_call_chain+0x61/0xe0 > blocking_notifier_call_chain_robust+0x63/0xc0 > load_module+0x1c92/0x20c0 > init_module_from_file+0xd8/0x140 > idempotent_init_module+0x100/0x2f0 > __x64_sys_finit_module+0x71/0xe0 > do_syscall_64+0xe1/0x610 > entry_SYSCALL_64_after_hwframe+0x76/0x7e which matches the following BUG_ON() in alternative.c: /* * If something went wrong, crash and burn since recovery paths are not * implemented. */ BUG_ON(!pages[0] || (cross_page_boundary && !pages[1])); This can happen if vmalloc_to_page() fails, for any reason. Such can happen if text poking races with CPA, which can possibly result in the collapsing of page tables (or breaking of PMD hugepages). It is not a problem for most users of vmalloc_to_page() (they solely own the vmalloc'd range) but, when CONFIG_ARCH_HAS_EXECMEM_ROX=y, various modules own a single execmem vmalloc range, and can call set_memory_*() in parallel on it. This can happen to race against __text_poke and cause havoc in vmalloc_to_page(). Fix it by excluding against CPA using the init_mm mmap read lock. [ dhansen: Fix up SoB ordering. The actual code flow here was: Pedro=>Lorenzo=>Mike=>Me which is reflected in the SoB chain now. I *believe* Mike simply picked up Lorenzo's update to Pedro's post from the Link ] Fixes: 64f6a4e10c05 ("x86: re-enable EXECMEM_ROX support") Reported-by: Jiri Slaby Reported-by: Steffen Dirkwinkel Signed-off-by: Pedro Falcato Signed-off-by: Lorenzo Stoakes (ARM) Co-developed-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Tested-by: Jiri Slaby Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc: stable@vger.kernel.org Link: https://bugzilla.opensuse.org/show_bug.cgi?id=1271202 [0] Link: https://lore.kernel.org/linux-mm/555ea1d43a12c30a8f1eaf10c899b3790d728f33.camel@dirkwinkel.cc/ Link: https://patch.msgid.link/20260813-cpa-fixes-v2-3-39b4ff90f91d@kernel.org --- arch/x86/kernel/alternative.c | 39 ++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index 91b1cdd16569..add62db3e82c 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include #include @@ -2372,6 +2375,38 @@ static void text_poke_memset(void *dst, const void *src, size_t len) typedef void text_poke_f(void *dst, const void *src, size_t len); +static void __poke_vmalloc_pages(struct page **pages, void *addr, + bool cross_page_boundary) +{ + pages[0] = vmalloc_to_page(addr); + if (cross_page_boundary) + pages[1] = vmalloc_to_page(addr + PAGE_SIZE); +} + +static void poke_vmalloc_pages(struct page **pages, void *addr, + bool cross_page_boundary) +{ + if (in_dbg_master()) { + /* + * If called from kgdb cannot sleep, but all other CPUs stopped + * anyway so safe to proceed without locks + */ + __poke_vmalloc_pages(pages, addr, cross_page_boundary); + } else { + /* + * execmem ROX ranges are shared between modules and can be + * collapsed to huge PMD entries, and this collapse can happen + * concurrently with a racing set_memory_rox(). + * + * Prevent vmalloc_to_page() from racing by acquiring an + * init_mm read lock which pairs with the init_mm write lock in + * cpa_collapse_large_pages(). + */ + guard(mmap_read_lock)(&init_mm); + __poke_vmalloc_pages(pages, addr, cross_page_boundary); + } +} + static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t len) { bool cross_page_boundary = offset_in_page(addr) + len > PAGE_SIZE; @@ -2389,9 +2424,7 @@ static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t l BUG_ON(!after_bootmem); if (!core_kernel_text((unsigned long)addr)) { - pages[0] = vmalloc_to_page(addr); - if (cross_page_boundary) - pages[1] = vmalloc_to_page(addr + PAGE_SIZE); + poke_vmalloc_pages(pages, addr, cross_page_boundary); } else { pages[0] = virt_to_page(addr); WARN_ON(!PageReserved(pages[0])); From 9e4a3ec3411bb6bb59e3c1f29b75609f1e87aac4 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:27 +0300 Subject: [PATCH 0615/1317] x86/mm/pat: Allocate split page tables as kernel page tables A PTE is allocated directly without going through the standard page table allocation routines (such as pte_alloc_one_kernel()) when the CPA code splits a large page (__split_large_page()). This means the page table constructor is never called nor is the page table marked as a kernel page table. The former results in the folio associated with the page table not being marked as a page table (__pagetable_ctor() is never called thus neither is __folio_set_pgtable()) nor are statistics updated to reflect it (lruvec_stat_add_folio() is never called). The latter issue of failing to mark the page table as a kernel page table (ptdesc_set_kernel() is never called) is far more problematic. Since commit: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") kernel page table freeing has been batched and since the subsequent commit: e37d5a2d60a3 ("iommu/sva: invalidate stale IOTLB entries for kernel address space") IOTLB cache entries for kernel page tables have been invalidated upon being freed. Since split page tables are freed without this invalidation, the IOTLB can contain stale entries for them. Resolve the issue by using the ordinary PTE allocation API at split time. This results in these kernel page tables invoking a page table constructor, and thus requires a page table destructor. Destructors are not always present, like for early allocated direct map page tables). Conditionally call pagetable_dtor_free() if the PG_table folio flag for the ptdesc is set, otherwise we free the page table via pagetable_free(). Regardless of which path is taken page tables marked as kernel page tables, which now includes split page tables, take the correct route through pagetable_free_kernel(). There is a user-visible side effect in that split page tables will appear in nr_page_table_pages in /proc/vmstat (as do other kernel page tables allocated after early boot), however this is a positive change. This issue started being markedly problematic after commit: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") so choose this as the Fixes target. [ dhansen: rephrase in imperative mood ] Fixes: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Acked-by: Vishal Moola Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-4-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index cb5d6d6f71a4..4652487b5572 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -441,7 +441,15 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) list_for_each_entry_safe(ptdesc, tmp, &pgtables, pt_list) { list_del(&ptdesc->pt_list); - pagetable_free(ptdesc); + /* + * Only early alloc'd direct map should not be flagged PG_table + * here and those shouldn't be collapsed. However be abundantly + * cautious and handle the !PG_table case too. + */ + if (PageTable((ptdesc_page(ptdesc)))) + pagetable_dtor_free(ptdesc); + else + pagetable_free(ptdesc); } } @@ -1134,11 +1142,10 @@ static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn, static int __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, - struct ptdesc *ptdesc) + pte_t *pbase) { unsigned long lpaddr, lpinc, ref_pfn, pfn, pfninc = 1; - struct page *base = ptdesc_page(ptdesc); - pte_t *pbase = (pte_t *)page_address(base); + struct page *base = virt_to_page(pbase); unsigned int i, level; pgprot_t ref_prot; bool nx, rw; @@ -1238,20 +1245,20 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, static int split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address) { - struct ptdesc *ptdesc; + pte_t *pte; spin_unlock(&cpa_lock); if (cpa->init_mm_read_locked) mmap_read_unlock(&init_mm); - ptdesc = pagetable_alloc(GFP_KERNEL, 0); + pte = pte_alloc_one_kernel(&init_mm); if (cpa->init_mm_read_locked) mmap_read_lock(&init_mm); spin_lock(&cpa_lock); - if (!ptdesc) + if (!pte) return -ENOMEM; - if (__split_large_page(cpa, kpte, address, ptdesc)) - pagetable_free(ptdesc); + if (__split_large_page(cpa, kpte, address, pte)) + pte_free_kernel(&init_mm, pte); return 0; } From f7491d7c81db0e7c304a7bd757a76d2fbeaff80e Mon Sep 17 00:00:00 2001 From: Vernon Yang Date: Thu, 3 Sep 2026 11:16:08 +0800 Subject: [PATCH 0616/1317] x86/mm: Fix user-space data loss with MADV_FREE and THP Some of users of Polars (a data analytics library) have lost production data from this bug. They seem to have just the right combination of huge pages, MADV_FREE and heavy reclaim pressure. pmd_modify() masks the old value with (_HPAGE_CHG_MASK & ~_PAGE_DIRTY), silently discarding the hardware dirty bit. The subsequent pmd_mksaveddirty() call is supposed to transfer _PAGE_DIRTY into _PAGE_SAVED_DIRTY when write-protecting, but the dirty bit was already stripped from the value, so there is nothing left to transfer. Contrast with pte_modify(), which keeps _PAGE_DIRTY_BITS in its mask, and pud_modify(), which keeps _HPAGE_CHG_MASK untouched: pmd_modify() is the odd one out. Any pmd_modify() on a writable, dirty PMD loses the dirty state. One visible consequence is data loss with MADV_FREE on PMD-mapped THP: memset(buf, 0x5A, size); // PMD-mapped THP, PMD dirty madvise(buf, size, MADV_FREE); // PMD cleaned but left writable, // folio marked lazyfree memset(buf, 0x5A, size); // hardware sets _PAGE_DIRTY again mprotect(buf, size, PROT_READ); // pmd_modify() drops the dirty bit mprotect(buf, size, PROT_READ|PROT_WRITE); // ... memory pressure ... Reclaim (e.g. under memcg pressure) then finds the lazyfree folio with no dirty bit set anywhere and frees it in __discard_anon_folio_pmd_locked(), even though the data was rewritten after MADV_FREE; subsequent reads fault in fresh zero pages. NUMA hinting alone can trigger the same loss, as do_huge_pmd_numa_page() restores the PMD through pmd_modify() as well. PMD-mapped file THPs are affected too: mprotect()/NUMA hinting dropping the dirty bit means rewritten data is never written back. Fix it by keeping _PAGE_DIRTY in the preserved mask, exactly like pte_modify() and pud_modify() do. The existing pmd_mksaveddirty()/pmd_clear_saveddirty() pair then performs the hardware-dirty <-> saved-dirty transition based on the write bit, preserving the shadow-stack encoding rules. Fixes: bb3aadf7d446 ("x86/mm: Start actually marking _PAGE_SAVED_DIRTY") Closes: https://lore.kernel.org/r/CAJxLxMUGu1-L+O_nAONOwOXnS=cNbNApCWqdthRjd76LThtSPg@mail.gmail.com/ Reported-by: Orson Peters Signed-off-by: Vernon Yang Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Reviewed-by: Rick Edgecombe Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260903031608.1194238-1-vernon2gm@gmail.com --- arch/x86/include/asm/pgtable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h index d5f4917c1edc..d551120a7c88 100644 --- a/arch/x86/include/asm/pgtable.h +++ b/arch/x86/include/asm/pgtable.h @@ -806,7 +806,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) pmdval_t val = pmd_val(pmd), oldval = val; pmd_t pmd_result; - val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY); + val &= _HPAGE_CHG_MASK; val |= check_pgprot(newprot) & ~_HPAGE_CHG_MASK; val = flip_protnone_guard(oldval, val, PHYSICAL_PMD_PAGE_MASK); From e780259b54e618ceb4763fbc21314acf3565e813 Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Mon, 7 Sep 2026 23:26:32 +0200 Subject: [PATCH 0617/1317] exec: do_close_on_exec() before taking exec_update_lock do_close_on_exec() currently happens while holding the exec_update_lock, which is used in a lot of places that access process state to synchronize access checks. I recently added another such use of exec_update_lock, causing a regression. do_close_on_exec() can block waiting for a reply from a filesystem. That means a hung filesystem can block codepaths that use exec_update_lock; and it also means that a FUSE filesystem which attempts to inspect the calling process can deadlock. To avoid such problems, move do_close_on_exec() before the exec_update_lock is taken, but after the FD table has been copied if necessary. I have looked through all the calls between the old and new position of the do_close_on_exec() call; there seems to be no file descriptor table access in between. Reported-by: Benjamin Peterson Closes: https://lore.kernel.org/r/f5e8166a-88be-46c5-8939-1e5227ffe4c2@app.fastmail.com Fixes: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)") Cc: stable@vger.kernel.org Signed-off-by: Jann Horn Link: https://patch.msgid.link/20260907-cloexec-before-exec-update-lock-v1-1-8018c201a7df@google.com Tested-by: Benjamin Peterson Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 263b1f67f1f8..f419a512de63 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1161,6 +1161,20 @@ int begin_new_exec(struct linux_binprm * bprm) if (retval) goto out; + /* + * We have to apply CLOEXEC before we change whether the process is + * dumpable (in setup_new_exec) to avoid a race with a process in userspace + * trying to access the should-be-closed file descriptors of a process + * undergoing exec(2). + * + * This can block on filesystem ->flush() handlers, including waiting + * for FUSE daemons, so do it before exec_mmap takes the + * exec_update_lock. + * This must happen after the point of no return, and after unsharing + * the FD table. + */ + do_close_on_exec(me->files); + /* * Must be called _before_ exec_mmap() as bprm->mm is * not visible until then. Doing it here also ensures @@ -1211,14 +1225,6 @@ int begin_new_exec(struct linux_binprm * bprm) clear_syscall_work_syscall_user_dispatch(me); - /* - * We have to apply CLOEXEC before we change whether the process is - * dumpable (in setup_new_exec) to avoid a race with a process in userspace - * trying to access the should-be-closed file descriptors of a process - * undergoing exec(2). - */ - do_close_on_exec(me->files); - if (bprm->secureexec) { /* Make sure parent cannot signal privileged process. */ me->pdeath_signal = 0; From 56ea4e86832d8abe8930394473566c194d189f85 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Mon, 7 Sep 2026 16:22:17 +0200 Subject: [PATCH 0618/1317] nstree: check listing permission before taking a namespace reference legitimize_ns() takes a reference on the candidate namespace before may_list_ns() has decided whether the caller may see it. The __free(ns_put) cleanup on the denied path can drop the last reference to a mount namespace while we still hold the rcu read lock, and put_mnt_ns() may sleep there. This is the same problem commit 2ec2aff3c8e2 ("ns: make sure reference are dropped outside of rcu lock") fixed for the put_user() path. Neither ns_requested() nor may_list_ns() needs a reference, both only look at the namespace type and at the caller's own namespaces, so do the checks first and take the reference last. Splat: Voluntary context switch within RCU read-side critical section! WARNING: kernel/rcu/tree_plugin.h:332 at rcu_note_context_switch+0x238/0x2a0, CPU#5: a/3442 CPU: 5 UID: 1000 PID: 3442 Comm: a Not tainted 7.0.0-30-generic #30-Ubuntu PREEMPT(lazy) RIP: 0010:rcu_note_context_switch+0x238/0x2a0 Call Trace: __schedule+0xcf/0x650 schedule+0x27/0x90 schedule_preempt_disabled+0x15/0x30 __mutex_lock.constprop.0+0x550/0xaf0 __mutex_lock_slowpath+0x13/0x20 mutex_lock+0x3b/0x50 exp_funnel_lock+0xb2/0x260 synchronize_rcu_expedited+0xe7/0x220 namespace_unlock+0x26a/0x320 put_mnt_ns+0xd3/0x120 mntns_put+0xe/0x20 do_listns+0x13e/0x560 __do_sys_listns+0x126/0x2d0 __x64_sys_listns+0x20/0x30 x64_sys_call+0x2366/0x2390 do_syscall_64+0x105/0x5a0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Fixes: 76b6f5dfb3fd ("nstree: add listns()") Signed-off-by: Norbert Szetei Link: https://patch.msgid.link/ABA32239-733B-438C-B95A-B13ED69FF0F3@doyensec.com Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- kernel/nstree.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/kernel/nstree.c b/kernel/nstree.c index 6d12e5900ac0..831f279d174a 100644 --- a/kernel/nstree.c +++ b/kernel/nstree.c @@ -533,19 +533,13 @@ DEFINE_FREE(ns_put, struct ns_common *, if (!IS_ERR_OR_NULL(_T)) ns_put(_T)) static inline struct ns_common *__must_check legitimize_ns(const struct klistns *kls, struct ns_common *candidate) { - struct ns_common *ns __free(ns_put) = NULL; - if (!ns_requested(kls, candidate)) return NULL; - ns = ns_get_unless_inactive(candidate); - if (!ns) + if (!may_list_ns(kls, candidate)) return NULL; - if (!may_list_ns(kls, ns)) - return NULL; - - return no_free_ptr(ns); + return ns_get_unless_inactive(candidate); } static ssize_t do_listns_userns(struct klistns *kls) From ed761e0693950fcb4f6b0f60387a3961b972adf3 Mon Sep 17 00:00:00 2001 From: Leonardo Costa Date: Mon, 6 Jul 2026 10:24:17 -0300 Subject: [PATCH 0619/1317] drm/bridge: tc358768: Enforce input bus flags via atomic_check The tc358768 declares static bridge timings requiring pixel data to be sampled on the positive clock edge. However, the DRM core default propagation simply copies the output-side bus flags, coming from the next bridge, connector or panel, to the input side. If the propagated flags are incompatible with the bridge ones, the data is wrongly sampled, typically resulting in visual artifacts on the panel. Implement the atomic_check hook, replacing the mutually exclusive mode_fixup, and set the bridge state input bus flags to the ones required by the tc358768. The sync polarity defaulting previously done in mode_fixup is carried over into atomic_check unchanged. Fixes: ff1ca6397b1d ("drm/bridge: Add tc358768 driver") Cc: stable@vger.kernel.org Signed-off-by: Leonardo Costa Reviewed-by: Francesco Dolcini Reviewed-by: Swamil Jain Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260706132440.1594239-1-leoreis.costa@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/tc358768.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c index d1fc6af37cc5..19b43efcf93f 100644 --- a/drivers/gpu/drm/bridge/tc358768.c +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -1263,10 +1263,13 @@ tc358768_atomic_get_input_bus_fmts(struct drm_bridge *bridge, return input_fmts; } -static bool tc358768_mode_fixup(struct drm_bridge *bridge, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) +static int tc358768_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) { + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + /* Default to positive sync */ if (!(adjusted_mode->flags & @@ -1277,13 +1280,15 @@ static bool tc358768_mode_fixup(struct drm_bridge *bridge, (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC))) adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC; - return true; + bridge_state->input_bus_cfg.flags = bridge->timings->input_bus_flags; + + return 0; } static const struct drm_bridge_funcs tc358768_bridge_funcs = { .attach = tc358768_bridge_attach, .mode_valid = tc358768_bridge_mode_valid, - .mode_fixup = tc358768_mode_fixup, + .atomic_check = tc358768_bridge_atomic_check, .atomic_pre_enable = tc358768_bridge_atomic_pre_enable, .atomic_enable = tc358768_bridge_atomic_enable, .atomic_disable = tc358768_bridge_atomic_disable, From 92c6a8d6470f7e7aa86c1f144818d8fa4fcbd5aa Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 21:01:24 +0900 Subject: [PATCH 0620/1317] dma-mapping: don't trace the DMA address when the allocation fails dma_alloc_attrs() passes *dma_handle to trace_dma_alloc() without checking whether the allocation succeeded. No backend writes it on failure: dma_direct_alloc(), iommu_dma_alloc() and the dma_map_ops instances assign it only on the path that returns a buffer. Callers usually pass an uninitialized automatic variable, so a failed allocation records whatever the stack held, next to the virt_addr=(null) that marks the record as an error: dma_alloc: dmatrace dir=BIDIRECTIONAL dma_addr=deadbeefdeadbeef size=1099511627776 virt_addr=0000000000000000 The device coherent pool path reaches the same call: a non-zero return from dma_alloc_from_dev_coherent() means the request was handled, not that it succeeded, so cpu_addr is NULL and dma_handle is untouched once the pool runs out. For an allocation event a NULL virt_addr already means the request failed, so the address field carries nothing. Report 0 for it in the event class rather than at each call site, which covers dma_alloc_pages() and dma_alloc_sgt_err() as well. Fixes: 038eb433dc14 ("dma-mapping: add tracing for dma-mapping API calls") Fixes: 68b6dbf1f441 ("dma-mapping: trace more error paths") Suggested-by: Marek Szyprowski Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907120124.603373-1-donggeunyoo.kernel@gmail.com Reviewed-by: Sean Anderson Signed-off-by: Marek Szyprowski --- include/trace/events/dma.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/trace/events/dma.h b/include/trace/events/dma.h index 9df02c1511de..b06d8f99922d 100644 --- a/include/trace/events/dma.h +++ b/include/trace/events/dma.h @@ -134,7 +134,7 @@ DECLARE_EVENT_CLASS(dma_alloc_class, TP_fast_assign( __assign_str(device); __entry->virt_addr = virt_addr; - __entry->dma_addr = dma_addr; + __entry->dma_addr = virt_addr ? dma_addr : 0; __entry->size = size; __entry->flags = flags; __entry->dir = dir; From 53823e25793a97d07e6e98e0904bbf74cac8bc76 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 22 Aug 2026 02:58:47 +0800 Subject: [PATCH 0621/1317] mmc: sdio_uart: fix xmit_fifo leak when the port table is full sdio_uart_add_port() allocates the transmit fifo before claiming a slot in sdio_uart_table[]. When all UART_NR slots are taken, it returns -EBUSY with the fifo still allocated, but the probe error path only kfree()s the port, leaking the transmit fifo. Free the fifo in the failure path of sdio_uart_add_port() itself so the function retains nothing on error. Fixes: 8b197a5ce7a7 ("sdio_uart: Use kfifo instead of the messy circ stuff") Signed-off-by: Felix Gu Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/core/sdio_uart.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/core/sdio_uart.c b/drivers/mmc/core/sdio_uart.c index 7fd5dedf3ac0..705163c42972 100644 --- a/drivers/mmc/core/sdio_uart.c +++ b/drivers/mmc/core/sdio_uart.c @@ -104,6 +104,9 @@ static int sdio_uart_add_port(struct sdio_uart_port *port) } spin_unlock(&sdio_uart_table_lock); + if (ret) + kfifo_free(&port->xmit_fifo); + return ret; } From 424103642d009f5a7dc33300a49d5606d1bf4fb5 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:03 +0000 Subject: [PATCH 0622/1317] dmaengine: add dma_device_get() helper Add dma_device_get() helper to match dma_device_put() to make code symmetric. It wraps open-coded kref_get_unless_zero() and asserts that dma_list_mutex is held, matching its put counterpart. No functional change intended. Suggested-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-1-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 6ffd8bd82154..6b8af8607e5c 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -433,6 +433,12 @@ static void dma_device_release(struct kref *ref) device->device_release(device); } +static int __must_check dma_device_get(struct dma_device *device) +{ + lockdep_assert_held(&dma_list_mutex); + return kref_get_unless_zero(&device->ref); +} + static void dma_device_put(struct dma_device *device) { lockdep_assert_held(&dma_list_mutex); @@ -460,8 +466,7 @@ static int dma_chan_get(struct dma_chan *chan) if (!try_module_get(owner)) return -ENODEV; - ret = kref_get_unless_zero(&chan->device->ref); - if (!ret) { + if (!dma_device_get(chan->device)) { ret = -ENODEV; goto module_put_out; } From 44dab659064eb5c10adb0306510eebe848ed592d Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:04 +0000 Subject: [PATCH 0623/1317] dmaengine: Fix device kref underflow in dma_chan_put() dma_chan_get() takes chan->device->ref only on the slow path: /* no kref on fast path */ if (chan->client_count) { __module_get(owner); chan->client_count++; return 0; } if (!try_module_get(owner)) return -ENODEV; if (!dma_device_get(chan->device)) { // calls kref_get_unless_zero() dma_chan_put() drops the ref unconditionally, so every fast-path get/put pair drops one extra device reference. The bug fires when two conditions hold together: a non-private provider has a persistent client holding chan->client_count > 0 and another client cycles dmaengine_get()/dmaengine_put(). When the kref hits zero, the subsequent dma_find_channel() returns NULL even though the provider module is still loaded. Fix this by dropping device->ref only on the last put, matching the single slow-path get. Fixes: 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-2-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 6b8af8607e5c..e380b801df73 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -520,7 +520,9 @@ static void dma_chan_put(struct dma_chan *chan) chan->route_data = NULL; } - dma_device_put(chan->device); + /* This channel is not in use anymore, drop the device ref */ + if (!chan->client_count) + dma_device_put(chan->device); module_put(dma_chan_to_owner(chan)); } From e873c74132f0c5f1452816cd9bb26208f0bba1e1 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:05 +0000 Subject: [PATCH 0624/1317] dmaengine: fix use-after-free in dma_chan_put() and dma_release_channel() When dma_device_put() drops the last reference on chan->device->ref, dma_device_release() runs and may free the dma_device along with its channels. dma_chan_put() then still reads chan->device->owner via dma_chan_to_owner() for the trailing module_put(). KASAN catches it: slab-use-after-free in dma_chan_put+0x3e6/0x4c0 Read of size 8 by task insmod/6319 Freed by task 6319: kfree+0x225/0x470 dma_chan_put+0x395/0x4c0 dmaengine_put+0xf8/0x160 Cache the module owner in dma_chan_put() before the put so the trailing module_put() does not need chan->device. Fixes: 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") Suggested-by: Sashiko Link: https://sashiko.dev/#/patchset/20260518-dmaengine-kref-fix-v1-1-4d6125048fb7@amd.com Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-3-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index e380b801df73..872ae0d2ed95 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -500,10 +500,13 @@ static int dma_chan_get(struct dma_chan *chan) */ static void dma_chan_put(struct dma_chan *chan) { + struct module *owner; + /* This channel is not in use, bail out */ if (!chan->client_count) return; + owner = dma_chan_to_owner(chan); chan->client_count--; /* This channel is not in use anymore, free it */ @@ -523,7 +526,7 @@ static void dma_chan_put(struct dma_chan *chan) /* This channel is not in use anymore, drop the device ref */ if (!chan->client_count) dma_device_put(chan->device); - module_put(dma_chan_to_owner(chan)); + module_put(owner); } enum dma_status dma_sync_wait(struct dma_chan *chan, dma_cookie_t cookie) From dc750422170a563c7a81f6e49d36bb02c62ae37f Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:06 +0000 Subject: [PATCH 0625/1317] dmaengine: wait for RCU readers before releasing dma_device dma_issue_pending_all() walks the dma_device_list with list_for_each_entry_rcu() under rcu_read_lock(). dma_device_release() unlinks the device with list_del_rcu() and then calls device->device_release() (which in many drivers, such as plx_dma.c, directly calls kfree()). Because there is no grace period between unlinking the device and freeing it, concurrent RCU readers in dma_issue_pending_all() can access the device after it has been freed. The lockless walk originally relied on clients holding a dmaengine reference to pin the provider module, and therefore the device, for as long as they might traverse the list. Commit 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") decoupled the dma_device lifetime from the module reference, so the device can now be released while a reader is still walking the list. Add synchronize_rcu() before the device is freed, so RCU readers are guaranteed to have finished. Keep it unconditional: providers that do not implement device_release() free the device themselves once dma_async_device_unregister() returns. This call will delay for a grace period with dma_list_mutex held, which is safe and only teardown path is delayed. Fixes: 2ba05622b8b1 ("dmaengine: provide a common 'issue_pending_all' implementation") Suggested-by: Sashiko Link: https://sashiko.dev/#/patchset/20260526-dmaengine-kref-fix-v2-0-3df60afac01d@amd.com Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-4-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 872ae0d2ed95..c71763047126 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -428,6 +428,7 @@ static void dma_device_release(struct kref *ref) list_del_rcu(&device->global_node); dma_channel_rebalance(); + synchronize_rcu(); if (device->device_release) device->device_release(device); From dab68a74e90b8e07f08ed9deaa5884857a3cfe89 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:03 +0200 Subject: [PATCH 0626/1317] wifi: cfg80211: don't free driver-owned scan requests When an interface goes down while a scan is running, cfg80211 completes the scan towards userspace and frees the scan request. However, the driver can be convinced that it owns the request, since the cancellation is (intended to be) asynchronous. The WARN_ON() in the netdev notifier was meant to catch this, but it's not actually avoidable, so it triggers and we get a UAF in scan_done(). There doesn't seem to be a great way around it, so just track that the driver is still convinced it owns the request, and then just free it on completion if it was already cancelled. Also remove the warnings since they can trigger in the intended architecture. Assisted-by: LLM Fixes: 4a58e7c38443 ("cfg80211: don't "leak" uncompleted scans") Reported-by: syzbot+189dcafc06865d38178d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=189dcafc06865d38178d Link: https://patch.msgid.link/20260904165614.375e543228b1.I03cbb5a54cb02d6bba5034286af1ed73aba134d1@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 11 +++++------ net/wireless/core.h | 10 ++++++++++ net/wireless/rdev-ops.h | 3 +++ net/wireless/scan.c | 31 +++++++++++++++++++++++++++++-- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index d13310fef691..8bb2cbd66b48 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -244,9 +244,8 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev, rdev->opencount--; if (rdev->scan_req && rdev->scan_req->req.wdev == wdev) { - if (WARN_ON(!rdev->scan_req->notified && - (!rdev->int_scan_req || - !rdev->int_scan_req->notified))) + if (!rdev->scan_req->notified && + (!rdev->int_scan_req || !rdev->int_scan_req->notified)) rdev->scan_req->info.aborted = true; ___cfg80211_scan_done(rdev, false); } @@ -1758,9 +1757,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, wiphy_lock(&rdev->wiphy); cfg80211_update_iface_num(rdev, wdev->iftype, -1); if (rdev->scan_req && rdev->scan_req->req.wdev == wdev) { - if (WARN_ON(!rdev->scan_req->notified && - (!rdev->int_scan_req || - !rdev->int_scan_req->notified))) + if (!rdev->scan_req->notified && + (!rdev->int_scan_req || + !rdev->int_scan_req->notified)) rdev->scan_req->info.aborted = true; ___cfg80211_scan_done(rdev, false); } diff --git a/net/wireless/core.h b/net/wireless/core.h index b4610f6685dc..a0c2b6ebe31f 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -24,6 +24,16 @@ struct cfg80211_scan_request_int { struct cfg80211_scan_info info; bool notified; + /* + * set while the request is handed to the driver, i.e. between + * rdev_scan() and cfg80211_scan_done() + */ + bool driver_owns; + /* + * set when cfg80211 is done with the request but the driver still + * owns it, so that cfg80211_scan_done() knows to just free it + */ + bool stale; /* must be last - variable members */ struct cfg80211_scan_request req; }; diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 46849fe8d0b3..adcfd0278da3 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -464,7 +464,10 @@ static inline int rdev_scan(struct cfg80211_registered_device *rdev, return -EINVAL; trace_rdev_scan(&rdev->wiphy, request); + request->driver_owns = true; ret = rdev->ops->scan(&rdev->wiphy, &request->req); + if (ret) + request->driver_owns = false; trace_rdev_return_int(&rdev->wiphy, ret); return ret; } diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 9e934b185e34..4fe114f6aee3 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1114,6 +1114,21 @@ int cfg80211_scan(struct cfg80211_registered_device *rdev) return 0; } +/* + * Release the scan request, but free it only if the driver is also done, + * e.g. mac80211 may cancel it asynchronously and still use it. + */ +static void cfg80211_put_scan_req(struct cfg80211_scan_request_int *req) +{ + if (!req) + return; + + if (req->driver_owns) + req->stale = true; + else + kfree(req); +} + void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool send_message) { @@ -1173,10 +1188,10 @@ void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, dev_put(wdev->netdev); - kfree(rdev->int_scan_req); + cfg80211_put_scan_req(rdev->int_scan_req); rdev->int_scan_req = NULL; - kfree(rdev->scan_req); + cfg80211_put_scan_req(rdev->scan_req); rdev->scan_req = NULL; if (!send_message) @@ -1199,6 +1214,18 @@ void cfg80211_scan_done(struct cfg80211_scan_request *request, struct cfg80211_scan_info old_info = intreq->info; trace_cfg80211_scan_done(intreq, info); + + intreq->driver_owns = false; + + if (intreq->stale) { + /* + * The scan is already completed as far as we're concerned, + * it was just kept around for the driver - done now, free it. + */ + kfree(intreq); + return; + } + WARN_ON(intreq != rdev->scan_req && intreq != rdev->int_scan_req); From 955d86e5f3b95b731991fdb84966c50b16314629 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sun, 9 Aug 2026 21:36:15 +0000 Subject: [PATCH 0627/1317] arm64: hibernate: pass HVC_SET_VECTORS args to the resume hvc swsusp_arch_suspend_exit() reinstalls the restored kernel's hyp stub vectors with an hvc, but never passes the arguments. x0 is not set to HVC_SET_VECTORS and x1 is not set to the vector address, so the stub dispatch falls through and returns without writing vbar_el2. EL2 is left pointing at the trans_pgd copy of the vectors, a page that swsusp_free() releases right after resume. Set the arguments up the same way __hyp_set_vectors() does. Without this fix, Vladimir was able to trigger a hang when resuming from hibernation with CONFIG_PAGE_POISONING=y and page_poison=on. Fixes: 788bfdd97434 ("arm64: trans_pgd: hibernate: Add trans_pgd_copy_el2_vectors") Cc: stable@vger.kernel.org Signed-off-by: Bradley Morgan Reviewed-by: Vladimir Murzin Tested-by: Vladimir Murzin Acked-by: Mark Rutland Signed-off-by: Will Deacon --- arch/arm64/kernel/hibernate-asm.S | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/kernel/hibernate-asm.S b/arch/arm64/kernel/hibernate-asm.S index 0e1d9c3c6a93..2baefe7a82d3 100644 --- a/arch/arm64/kernel/hibernate-asm.S +++ b/arch/arm64/kernel/hibernate-asm.S @@ -89,6 +89,8 @@ alternative_insn "dc cvau, x4", "dc civac, x4", ARM64_WORKAROUND_CLEAN_CACHE isb cbz x24, 3f /* Do we need to re-initialise EL2? */ + mov x1, x24 + mov x0, #HVC_SET_VECTORS hvc #0 3: ret SYM_CODE_END(swsusp_arch_suspend_exit) From 068843ed0902c552a13860c5ec6b2ca65b57a065 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:04 +0200 Subject: [PATCH 0628/1317] wifi: cfg80211: only group hidden BSSes with beacon entries When a probe response for an unknown BSS comes in, __cfg80211_bss_update() looks for an existing entry with the same BSSID and a hidden (zero-length or NUL-filled) SSID, and if it finds one it groups them, using the beacon IEs from the existing entry. But that could find another entry without a beacon, if it was also from a probe response (with SSID), so there's a group without beacon elements. If a beacon with a hidden SSID for that BSSID arrives later, cfg80211_combine_bsses() goes looking for the probe response entries that belong to it - i.e. entries with the same BSSID and channel that have no beacon IEs - and finds those two. They are already grouped with each other, so it hits its WARN_ON_ONCE(bss->pub.hidden_beacon_bss) WARN_ON_ONCE(!list_empty(&bss->hidden_list)) which are there because an entry without beacon elements is not supposed to be part of a group yet. Only combine entries when a beacon was already received, ones that are kept separate will be combined when a beacon arrives. Assisted-by: LLM Fixes: 4593c4cbe1c9 ("cfg80211: fix BSS list hidden SSID lookup") Reported-by: syzbot+1a797e1c81be78a2ace7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1a797e1c81be78a2ace7 Link: https://patch.msgid.link/20260904165614.bcfa64715745.Iad740347c86de56d4ff4f96a95f3c3afc47c42de@changeid Signed-off-by: Johannes Berg --- net/wireless/scan.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 4fe114f6aee3..604b10ef0f94 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -2077,6 +2077,13 @@ __cfg80211_bss_update(struct cfg80211_registered_device *rdev, if (!hidden) hidden = rb_find_bss(rdev, tmp, BSS_CMP_HIDE_NUL); + /* + * Only group with an entry with beacon data, otherwise + * beacon data can never be filled/updated. + */ + if (hidden && + !rcu_access_pointer(hidden->pub.beacon_ies)) + hidden = NULL; if (hidden) { new->pub.hidden_beacon_bss = &hidden->pub; list_add(&new->hidden_list, From b377e1000d963e7182a987082b4b06580bd7ac84 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:05 +0200 Subject: [PATCH 0629/1317] wifi: cfg80211: don't filter by BSS type when removing stale entries When an assoc AP switches to a channel that already has a BSS entry, cfg80211_update_assoc_bss_entry() removes that entry before rehashing the real one, since the two would otherwise collide in the BSS rbtree. The lookup for that entry also required it to match the connection's BSS type, so an entry advertising e.g. the IBSS capability bit was left in place, and the following cfg80211_rehash_bss() then ran into it: WARN_ON(!cmp) Changing the type shouldn't really happen, but can be triggered by a rogue AP/device, so drop the check and remove any entries matching the comparison. Assisted-by: LLM Fixes: 0afd425b1b64 ("cfg80211: fix duplicated scan entries after channel switch") Reported-by: syzbot+dc6f4dce0d707900cdea@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=dc6f4dce0d707900cdea Link: https://patch.msgid.link/20260904165614.1f05dae1c546.Ib52d57b57caa912efee020f9d4a033a5160617ce@changeid Signed-off-by: Johannes Berg --- net/wireless/scan.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 604b10ef0f94..caa9c6495f20 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -3502,11 +3502,6 @@ void cfg80211_update_assoc_bss_entry(struct wireless_dev *wdev, cbss->pub.channel = chan; list_for_each_entry(bss, &rdev->bss_list, list) { - if (!cfg80211_bss_type_match(bss->pub.capability, - bss->pub.channel->band, - wdev->conn_bss_type)) - continue; - if (bss == cbss) continue; From 708f9d43d6a2eb9c6b83fe62af628de9dffd9314 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:06 +0200 Subject: [PATCH 0630/1317] wifi: cfg80211: ibss: ref BSS entry for joined event When the IBSS is joined, we only record the BSSID/channel in the event and look up the BSS entry when processing it. However, that's racy, e.g. a new scan with NL80211_SCAN_FLAG_FLUSH can remove it, causing a warning in the event work: !bss WARNING: net/wireless/ibss.c:37 at __cfg80211_ibss_joined+0x3d3/0x440 Workqueue: cfg80211 cfg80211_event_work cfg80211_process_wdev_events+0x39f/0x5b0 net/wireless/util.c:1144 cfg80211_process_rdev_events+0xa1/0x110 net/wireless/util.c:1179 cfg80211_event_work+0x2f/0x40 net/wireless/core.c:393 Do the lookup early (the driver is expected to only join an IBSS that has a BSS entry) and keep a reference to it. Assisted-by: LLM Fixes: 667503ddcb96 ("cfg80211: fix locking") Reported-by: syzbot+7f064ba1704c2466e36d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7f064ba1704c2466e36d Link: https://patch.msgid.link/20260904165614.f49a213f0e49.I192bfe738750ebb5f2c4faa3019a428da64cd3ec@changeid Signed-off-by: Johannes Berg --- net/wireless/core.h | 6 ++---- net/wireless/ibss.c | 38 +++++++++++++++++++++----------------- net/wireless/util.c | 3 +-- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/net/wireless/core.h b/net/wireless/core.h index a0c2b6ebe31f..85dfb3ac803b 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -290,8 +290,7 @@ struct cfg80211_event { bool locally_generated; } dc; struct { - u8 bssid[ETH_ALEN]; - struct ieee80211_channel *channel; + struct cfg80211_bss *bss; } ij; struct { u8 peer_addr[ETH_ALEN]; @@ -354,8 +353,7 @@ int __cfg80211_join_ibss(struct cfg80211_registered_device *rdev, void cfg80211_clear_ibss(struct net_device *dev, bool nowext); int cfg80211_leave_ibss(struct cfg80211_registered_device *rdev, struct net_device *dev, bool nowext); -void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, - struct ieee80211_channel *channel); +void __cfg80211_ibss_joined(struct net_device *dev, struct cfg80211_bss *bss); int cfg80211_ibss_wext_join(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev); diff --git a/net/wireless/ibss.c b/net/wireless/ibss.c index b1d748bdb504..7f6779d326b8 100644 --- a/net/wireless/ibss.c +++ b/net/wireless/ibss.c @@ -16,26 +16,18 @@ #include "rdev-ops.h" -void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, - struct ieee80211_channel *channel) +void __cfg80211_ibss_joined(struct net_device *dev, struct cfg80211_bss *bss) { struct wireless_dev *wdev = dev->ieee80211_ptr; - struct cfg80211_bss *bss; #ifdef CONFIG_CFG80211_WEXT union iwreq_data wrqu; #endif if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC)) - return; + goto put_bss; if (!wdev->u.ibss.ssid_len) - return; - - bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, NULL, 0, - IEEE80211_BSS_TYPE_IBSS, IEEE80211_PRIVACY_ANY); - - if (WARN_ON(!bss)) - return; + goto put_bss; if (wdev->u.ibss.current_bss) { cfg80211_unhold_bss(wdev->u.ibss.current_bss); @@ -43,17 +35,22 @@ void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, } cfg80211_hold_bss(bss_from_pub(bss)); + /* the reference from the event is transferred to current_bss */ wdev->u.ibss.current_bss = bss_from_pub(bss); cfg80211_upload_connect_keys(wdev); - nl80211_send_ibss_bssid(wiphy_to_rdev(wdev->wiphy), dev, bssid, + nl80211_send_ibss_bssid(wiphy_to_rdev(wdev->wiphy), dev, bss->bssid, GFP_KERNEL); #ifdef CONFIG_CFG80211_WEXT memset(&wrqu, 0, sizeof(wrqu)); - memcpy(wrqu.ap_addr.sa_data, bssid, ETH_ALEN); + memcpy(wrqu.ap_addr.sa_data, bss->bssid, ETH_ALEN); wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); #endif + return; + +put_bss: + cfg80211_put_bss(wdev->wiphy, bss); } void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, @@ -62,6 +59,7 @@ void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, struct wireless_dev *wdev = dev->ieee80211_ptr; struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); struct cfg80211_event *ev; + struct cfg80211_bss *bss; unsigned long flags; trace_cfg80211_ibss_joined(dev, bssid, channel); @@ -69,13 +67,19 @@ void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, if (WARN_ON(!channel)) return; - ev = kzalloc_obj(*ev, gfp); - if (!ev) + bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, NULL, 0, + IEEE80211_BSS_TYPE_IBSS, IEEE80211_PRIVACY_ANY); + if (WARN_ON(!bss)) return; + ev = kzalloc_obj(*ev, gfp); + if (!ev) { + cfg80211_put_bss(wdev->wiphy, bss); + return; + } + ev->type = EVENT_IBSS_JOINED; - memcpy(ev->ij.bssid, bssid, ETH_ALEN); - ev->ij.channel = channel; + ev->ij.bss = bss; spin_lock_irqsave(&wdev->event_lock, flags); list_add_tail(&ev->list, &wdev->event_list); diff --git a/net/wireless/util.c b/net/wireless/util.c index 5429cf3cfd2f..f2464d2ce0d5 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1235,8 +1235,7 @@ void cfg80211_process_wdev_events(struct wireless_dev *wdev) !ev->dc.locally_generated); break; case EVENT_IBSS_JOINED: - __cfg80211_ibss_joined(wdev->netdev, ev->ij.bssid, - ev->ij.channel); + __cfg80211_ibss_joined(wdev->netdev, ev->ij.bss); break; case EVENT_STOPPED: /* From 17a5f8571d1d40c88b78cfc154a7da0d60f13f37 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:07 +0200 Subject: [PATCH 0631/1317] wifi: cfg80211: fix NAN regulatory enforcement reg_wdev_chan_valid() returns early for any wdev that has no netdev, which is fine for P2P originally (and later PD still), but NAN has no netdev and yet enforcement code was added and is needed, but is dead code right now. Use wdev_running() instead so that netdev-less wdevs aren't skipped. P2P/PD don't do anything in the later switch, but NAN code can now be reached. Assisted-by: LLM Fixes: 0e8ec738a71e ("wifi: cfg80211: add support for NAN data interface") Link: https://patch.msgid.link/20260904165614.6abc075b5401.Ib90696e3fa49b1698c27d64db5360d51f6f187a9@changeid Signed-off-by: Johannes Berg --- net/wireless/reg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/reg.c b/net/wireless/reg.c index a8336baf85dc..9910b080b402 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2345,7 +2345,7 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) iftype = wdev->iftype; /* make sure the interface is active */ - if (!wdev->netdev || !netif_running(wdev->netdev)) + if (!wdev_running(wdev)) return true; /* NAN doesn't have links, handle it separately */ From f4e72e3758072d7b063e0d8b93419eb915b69c2c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:08 +0200 Subject: [PATCH 0632/1317] wifi: cfg80211: reduce RTNL holding in regulatory enforcement Regulatory enforcement in reg_check_chans_work() does all work with the RTNL held, which can block the RTNL for a long time, which syzbot can hit and report hung tasks. Except for NAN, we don't need the RTNL for the enforcement, and the list iteration can be done with RCU instead. Split the enforcement off into new work structs: for NAN, we have to have the RTNL to close dependent NAN_DATA interfaces, everything else can use cfg80211_leave_locked() in a wiphy work. It'd be doable to use just a single work with RTNL, but then the RTNL would end up being used all the time, and really it only needs to be used for NAN. Assisted-by: LLM Reported-by: syzbot+adeb8550754921fece20@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=adeb8550754921fece20 Reported-by: syzbot+101224300649c3eb8af4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=101224300649c3eb8af4 Link: https://patch.msgid.link/20260904165614.f65bd4d9fa35.I82dac71371d87f39e459fce931b0e5321e4f9767@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 3 +++ net/wireless/core.h | 2 ++ net/wireless/reg.c | 50 +++++++++++++++++++++++++++++++++++++-------- net/wireless/reg.h | 16 +++++++++++++++ 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 8bb2cbd66b48..668380deec7d 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -645,6 +645,8 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv, INIT_WORK(&rdev->destroy_work, cfg80211_destroy_iface_wk); wiphy_work_init(&rdev->sched_scan_stop_wk, cfg80211_sched_scan_stop_wk); INIT_WORK(&rdev->sched_scan_res_wk, cfg80211_sched_scan_results_wk); + wiphy_work_init(&rdev->reg_check_chans_wk, reg_leave_invalid_chans_wk); + INIT_WORK(&rdev->reg_leave_nan_wk, reg_leave_invalid_nan_wk); INIT_WORK(&rdev->propagate_radar_detect_wk, cfg80211_propagate_radar_detect_wk); INIT_WORK(&rdev->propagate_cac_done_wk, cfg80211_propagate_cac_done_wk); @@ -1344,6 +1346,7 @@ void wiphy_unregister(struct wiphy *wiphy) cancel_delayed_work_sync(&rdev->dfs_update_channels_wk); cancel_delayed_work_sync(&rdev->background_cac_done_wk); flush_work(&rdev->destroy_work); + flush_work(&rdev->reg_leave_nan_wk); flush_work(&rdev->propagate_radar_detect_wk); flush_work(&rdev->propagate_cac_done_wk); flush_work(&rdev->mgmt_registrations_update_wk); diff --git a/net/wireless/core.h b/net/wireless/core.h index 85dfb3ac803b..6138d207caf4 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -114,6 +114,8 @@ struct cfg80211_registered_device { struct work_struct destroy_work; struct wiphy_work sched_scan_stop_wk; struct work_struct sched_scan_res_wk; + struct wiphy_work reg_check_chans_wk; + struct work_struct reg_leave_nan_wk; struct cfg80211_chan_def radar_chandef; struct work_struct propagate_radar_detect_wk; diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 9910b080b402..11665e0a7efc 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2446,19 +2446,52 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) return true; } -static void reg_leave_invalid_chans(struct wiphy *wiphy) +void reg_leave_invalid_nan_wk(struct work_struct *work) { + struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; - struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + + rdev = container_of(work, struct cfg80211_registered_device, + reg_leave_nan_wk); + + /* stopping NAN closes its data interfaces, which needs the RTNL */ + rtnl_lock(); list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { bool valid; - scoped_guard(wiphy, wiphy) - valid = reg_wdev_chan_valid(wiphy, wdev); + if (wdev->iftype != NL80211_IFTYPE_NAN) + continue; + + scoped_guard(wiphy, &rdev->wiphy) + valid = reg_wdev_chan_valid(&rdev->wiphy, wdev); if (!valid) cfg80211_leave(rdev, wdev, -1); } + + rtnl_unlock(); +} + +void reg_leave_invalid_chans_wk(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + struct wireless_dev *wdev; + + lockdep_assert_held(&wiphy->mtx); + + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + if (reg_wdev_chan_valid(wiphy, wdev)) + continue; + + /* + * Tearing down NAN needs the RTNL for closing NAN_DATA + * interfaces, handle that separately. + */ + if (wdev->iftype == NL80211_IFTYPE_NAN) + schedule_work(&rdev->reg_leave_nan_wk); + else + cfg80211_leave_locked(rdev, wdev, -1); + } } static void reg_check_chans_work(struct work_struct *work) @@ -2466,12 +2499,13 @@ static void reg_check_chans_work(struct work_struct *work) struct cfg80211_registered_device *rdev; pr_debug("Verifying active interfaces after reg change\n"); - rtnl_lock(); - for_each_rdev(rdev) - reg_leave_invalid_chans(&rdev->wiphy); + rcu_read_lock(); - rtnl_unlock(); + list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) + wiphy_work_queue(&rdev->wiphy, &rdev->reg_check_chans_wk); + + rcu_read_unlock(); } void reg_check_channels(void) diff --git a/net/wireless/reg.h b/net/wireless/reg.h index fc31c5f9a61a..c587079ead8f 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h @@ -178,6 +178,22 @@ int reg_reload_regdb(void); */ void reg_check_channels(void); +/** + * reg_leave_invalid_chans_wk - check if channels are no longer usable and leave + * @wiphy: the wiphy to check + * @work: the work struct + */ +void reg_leave_invalid_chans_wk(struct wiphy *wiphy, struct wiphy_work *work); + +/** + * reg_leave_invalid_nan_wk - check channels and tear down NAN when unusable + * @work: the work struct + * + * Stopping a NAN interface needs the RTNL, so it cannot be done from + * reg_leave_invalid_chans_wk() which runs with the wiphy mutex held. + */ +void reg_leave_invalid_nan_wk(struct work_struct *work); + extern const u8 shipped_regdb_certs[]; extern unsigned int shipped_regdb_certs_len; extern const u8 extra_regdb_certs[]; From f5dd0626d2b07c6f5c8c763d75d13c6307393bca Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:05 +0200 Subject: [PATCH 0633/1317] wifi: mac80211: don't apply peer rates to off-channel frames All the off-channel frames (including scan) aren't really part of the connection, so don't apply the station rates even if they're being sent to the station in question (e.g. by accident). They don't use the rate mask via IEEE80211_TX_CTRL_DONT_USE_RATE_MASK, but the station might not have rates of them either, hitting the warning found by syzbot. Assisted-by: LLM Reported-by: syzbot+34463a129786910405dd@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=34463a129786910405dd Link: https://patch.msgid.link/20260904165722.ade6b07421b8.I59b7ea810eb021a7a68b3090828a757b6dd85e57@changeid Signed-off-by: Johannes Berg --- net/mac80211/rate.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c index 64768abb0a5f..e910f03af777 100644 --- a/net/mac80211/rate.c +++ b/net/mac80211/rate.c @@ -372,6 +372,14 @@ static void __rate_control_send_low(struct ieee80211_hw *hw, u32 rate_flags = 0; int i; + /* + * Frames that shouldn't use the rate mask could be anything, + * even on a different band, so don't take the sta into account + * to avoid ending up without rates. + */ + if (info->control.flags & IEEE80211_TX_CTRL_DONT_USE_RATE_MASK) + sta = NULL; + if (sband->band == NL80211_BAND_S1GHZ) { info->control.rates[0].flags |= IEEE80211_TX_RC_S1G_MCS; info->control.rates[0].idx = 0; From 23c68b4aaf5e18ab95532fc6714b737d5b7c701c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:06 +0200 Subject: [PATCH 0634/1317] wifi: mac80211: don't drop scan probe requests for lack of peer rates While software scanning, ieee80211_tx_h_rate_ctrl() warns and drops the frame if the target station has no usable bitrate on the band that's currently being scanned. But that's really meant for data frames, not if we happen to scan for the BSSID on the wrong band, which can be constructed easily. Skip the check for IEEE80211_TX_CTRL_DONT_USE_RATE_MASK, the previous commit also ignored the station rate mask for such frames as well. Assisted-by: LLM Reported-by: syzbot+0d516b33238bd97ee864@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0d516b33238bd97ee864 Link: https://patch.msgid.link/20260904165722.b57ea4ab82d3.Id6c9c42d5cef5901bfac88853647b03ba4077b3e@changeid Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3896c7b2c4e5..d155fb319a55 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -744,10 +744,12 @@ ieee80211_tx_h_rate_ctrl(struct ieee80211_tx_data *tx) assoc = test_sta_flag(tx->sta, WLAN_STA_ASSOC); /* - * Lets not bother rate control if we're associated and cannot - * talk to the sta. This should not happen. + * Lets not bother rate control if we're associated and cannot talk to + * the sta. This should not happen - except for frames that aren't + * really for the peer to start with and already ignore rates. */ - if (WARN(test_bit(SCAN_SW_SCANNING, &tx->local->scanning) && assoc && + if (!(info->control.flags & IEEE80211_TX_CTRL_DONT_USE_RATE_MASK) && + WARN(test_bit(SCAN_SW_SCANNING, &tx->local->scanning) && assoc && !rate_usable_index_exists(sband, &tx->sta->sta), "%s: Dropped data frame as no usable bitrate found while " "scanning and associated. Target station: " From 733f0fde95392ed5f61a4e36aee661ea8d0e8581 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:07 +0200 Subject: [PATCH 0635/1317] wifi: mac80211: don't start a ROC while scanning The ROC work can be pending when a scan starts (which requires ROC list to be empty, but that's possible), and then a new ROC can be added to the list and the work will pick it up. Avoid starting that ROC if a scan made it between things, as otherwise we'll hit a warning later: WARNING: net/mac80211/offchannel.c:404 at ieee80211_start_next_roc+0x256/0x2d0 Workqueue: events_unbound cfg80211_wiphy_work Call Trace: __ieee80211_scan_completed+0x4fd/0xe40 net/mac80211/scan.c:537 ieee80211_scan_work+0x472/0x1ff0 net/mac80211/scan.c:1193 cfg80211_wiphy_work+0x410/0x570 net/wireless/core.c:513 Assisted-by: LLM Fixes: aaa016ccd5df ("mac80211: rewrite remain-on-channel logic") Reported-by: syzbot+c3a167b5615df4ccd7fb@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c3a167b5615df4ccd7fb Link: https://patch.msgid.link/20260904165722.f9d5b150edd8.I61bc9de8c8d089096ad695213b9c85c7df38c3bd@changeid Signed-off-by: Johannes Berg --- net/mac80211/offchannel.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 7acef80d5f1f..e30767853c36 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -460,6 +460,13 @@ static void __ieee80211_roc_work(struct ieee80211_local *local) return; if (!roc->started) { + /* + * The work can be started by a previous ROC work, but a scan + * can get between things; scan finish will retrigger us. + */ + if (local->scanning) + return; + WARN_ON(!local->emulate_chanctx); _ieee80211_start_next_roc(local); } else { From a7491b7efbd9136b120a12ed72af9c12121dd134 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:08 +0200 Subject: [PATCH 0636/1317] wifi: mac80211: don't warn when an IBSS has no channel to scan ieee80211_request_ibss_scan() warns when regulatory leaves no allowed channel, but that can happen as the regdomain can change while IBSS is operating, and it can continue to operate briefly during the 60s grace period until it's shut down. Just remove the warning in this case. Assisted-by: LLM Fixes: 34bcf7150241 ("mac80211: fix ibss scanning") Reported-by: syzbot+1634c5399e29d8b66789@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1634c5399e29d8b66789 Link: https://patch.msgid.link/20260904165722.fe380c27fef4.I0e8bee2e12a40d240851a4bc724d47753af46159@changeid Signed-off-by: Johannes Berg --- net/mac80211/scan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index eeff230bd909..8e950ef6d1ea 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -1242,7 +1242,7 @@ int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata, } } - if (WARN_ON_ONCE(n_ch == 0)) + if (n_ch == 0) return -EINVAL; local->int_scan_req->n_channels = n_ch; From 362bd5bce29ed0f6fd3d39a7065567777d70606e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:09 +0200 Subject: [PATCH 0637/1317] wifi: mac80211: don't offload TC setup on AP_VLAN interfaces AP_VLAN interfaces are purely virtual, so don't try to offload TC setup to drivers. We can't really use the AP interface either since we may not know it all the time, and it could technically even change. Just reject the TC offload so things get done in software. Assisted-by: LLM Fixes: 61587f1556fe ("wifi: mac80211: add support for letting drivers register tc offload support") Reported-by: syzbot+f1ba58d6b55abd13239e@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f1ba58d6b55abd13239e Link: https://patch.msgid.link/20260904165722.726cc076cecb.Iccfd88b13635425e850ce031376eb60a4ce5f4f8@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 43460a705a6b..8c300e045fdf 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -935,6 +935,9 @@ static int ieee80211_netdev_setup_tc(struct net_device *dev, struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; + if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) + return -EOPNOTSUPP; + return drv_net_setup_tc(local, sdata, dev, type, type_data); } From bf29d085e0eba92388518719d044f4702a8c6644 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:10 +0200 Subject: [PATCH 0638/1317] wifi: mac80211: suppress chanctx warning for debugfs reset Before suspend all the channel contexts should removed, so the warning makes sense and should be there, but during reset the same code is called without first removing. Limit the check to the real suspend case. Assisted-by: LLM Fixes: 12e7f517029d ("mac80211: cleanup generic suspend/resume procedures") Reported-by: syzbot+56a1a45a9a2c04d425ff@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=56a1a45a9a2c04d425ff Link: https://patch.msgid.link/20260904165722.fe46395e310b.Ic4aaa95bd9d0ceb6a3cd7d84c425afee7d7d3dd7@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 2 +- net/mac80211/debugfs.c | 2 +- net/mac80211/ieee80211_i.h | 2 +- net/mac80211/pm.c | 8 +++++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 1f074799f85f..2b13c057312e 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -3475,7 +3475,7 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy, static int ieee80211_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wowlan) { - return __ieee80211_suspend(wiphy_priv(wiphy), wowlan); + return __ieee80211_suspend(wiphy_priv(wiphy), wowlan, false); } static int ieee80211_resume(struct wiphy *wiphy) diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 105653a16b68..e38631d7cfb4 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -384,7 +384,7 @@ static ssize_t reset_write(struct file *file, const char __user *user_buf, rtnl_lock(); wiphy_lock(local->hw.wiphy); - __ieee80211_suspend(&local->hw, NULL); + __ieee80211_suspend(&local->hw, NULL, true); ret = __ieee80211_resume(&local->hw); wiphy_unlock(local->hw.wiphy); diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 5761e9621491..d05f59467399 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2432,7 +2432,7 @@ int ieee80211_reconfig(struct ieee80211_local *local); void ieee80211_stop_device(struct ieee80211_local *local, bool suspend); int __ieee80211_suspend(struct ieee80211_hw *hw, - struct cfg80211_wowlan *wowlan); + struct cfg80211_wowlan *wowlan, bool reset); static inline int __ieee80211_resume(struct ieee80211_hw *hw) { diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c index 5a508d99e84f..f63676c44853 100644 --- a/net/mac80211/pm.c +++ b/net/mac80211/pm.c @@ -18,7 +18,8 @@ static void ieee80211_sched_scan_cancel(struct ieee80211_local *local) cfg80211_sched_scan_stopped_locked(local->hw.wiphy, 0); } -int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) +int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan, + bool reset) { struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_sub_if_data *sdata; @@ -166,9 +167,10 @@ int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) /* * We disconnected on all interfaces before suspend, all channel - * contexts should be released. + * contexts should be released, but on 'reset' debugfs that's + * not true so don't check there. */ - WARN_ON(!list_empty(&local->chanctx_list)); + WARN_ON(!reset && !list_empty(&local->chanctx_list)); /* stop hardware - this must stop RX */ ieee80211_stop_device(local, true); From ac7472a24bd433b81c06582835dd1d5547c10da9 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:11 +0200 Subject: [PATCH 0639/1317] wifi: mac80211: abort chanswitch when leaving a mesh The code in ieee80211_stop_mesh() leaves CSA active, but leaving the mesh released the channel context, so the CSA finalize work crashes: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000003 KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f] RIP: 0010:ieee80211_put_srates_elem+0x42/0x640 net/mac80211/util.c:3272 Call Trace: ieee80211_mesh_build_beacon+0xa83/0x1b50 net/mac80211/mesh.c:1093 ieee80211_mesh_rebuild_beacon+0xc7/0x170 net/mac80211/mesh.c:1147 ieee80211_mesh_finish_csa+0x131/0x210 net/mac80211/mesh.c:1542 ieee80211_set_after_csa_beacon net/mac80211/cfg.c:4085 [inline] __ieee80211_csa_finalize net/mac80211/cfg.c:4133 [inline] ieee80211_csa_finalize+0x633/0x1150 net/mac80211/cfg.c:4155 cfg80211_wiphy_work+0x2ab/0x450 net/wireless/core.c:438 Abort the channel switch properly. Assisted-by: LLM Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Reported-by: syzbot+81cd9dc1596563141d19@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=81cd9dc1596563141d19 Link: https://patch.msgid.link/20260904165722.d0b87eee08aa.I80550d6127e0bb26efb49a5fbe95be1aef1cd0cb@changeid Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index d4507e4e6ec1..bed7ac838250 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1204,6 +1204,10 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) netif_carrier_off(sdata->dev); + /* abort any running channel switch */ + sdata->vif.bss_conf.csa_active = false; + ieee80211_vif_unblock_queues_csa(sdata); + /* flush STAs and mpaths on this iface */ sta_info_flush(sdata, -1); ieee80211_free_keys(sdata, true); From 3f28551d0241254a75626d868041c6340285088b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:12 +0200 Subject: [PATCH 0640/1317] wifi: mac80211: reset state when starting AP fails ieee80211_start_ap() can set enable_beacon (and beacon_int) and fail later, leaving it set forever. Scanning can then attempt to restore beaconing on such an interface, leading to: Oops: divide error: 0000 [#1] SMP KASAN NOPTI RIP: 0010:mac80211_hwsim_link_info_changed+0xca7/0xf00 Call Trace: drv_link_info_changed+0x413/0x860 net/mac80211/driver-ops.c:495 ieee80211_link_info_change_notify+0x24b/0x3c0 net/mac80211/main.c:427 ieee80211_offchannel_return+0x381/0x580 net/mac80211/offchannel.c:160 __ieee80211_scan_completed+0x993/0xe30 net/mac80211/scan.c:519 ieee80211_scan_work+0x472/0x2010 net/mac80211/scan.c:1193 cfg80211_wiphy_work+0x2b7/0x550 net/wireless/core.c:538 in hwsim. Also, cfg80211 then allows changing the interface type, and the off-channel path getgs confused about beaconing as well, leading to another warning: WARNING: net/mac80211/driver-ops.c:468 at drv_link_info_changed+0x583/0x880 ieee80211_link_info_change_notify+0x24b/0x3c0 net/mac80211/main.c:427 ieee80211_offchannel_stop_vifs+0x328/0x5c0 net/mac80211/offchannel.c:122 ieee80211_start_sw_scan net/mac80211/scan.c:583 [inline] __ieee80211_start_scan+0xfb6/0x1af0 net/mac80211/scan.c:882 Reset the state on failures to always have it correct. Assisted-by: LLM Fixes: d6a83228823f ("mac80211: track enable_beacon explicitly") Reported-by: syzbot+ca7a2759caaa6cd4e3db@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ca7a2759caaa6cd4e3db Reported-by: syzbot+c4686c3eb8b64032618f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c4686c3eb8b64032618f Link: https://patch.msgid.link/20260904165722.9629429a5221.I7f599412bfe12a09d41ea4901be9ad165d07d133@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 2b13c057312e..2d5a0abe35db 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1929,6 +1929,9 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, return 0; error: + link_conf->enable_beacon = false; + link_conf->beacon_int = prev_beacon_int; + sdata->vif.cfg.ssid_len = 0; ieee80211_link_release_channel(link); return err; From 78183e8331958fda11cd2b6850bb424a9747c4b2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:13 +0200 Subject: [PATCH 0641/1317] wifi: mac80211: reset the LED state when ifup fails When the first interface comes up, the radio LED is turned on. This can start the TPT trigger timer, which continues running. But if bringing up the interface fails then the timer keeps running and won't be stopped by anything, eventually it can be freed: ODEBUG: free active (active state 0) object: ffff888127e12130 object type: timer_list hint: tpt_trig_timer+0x0/0x300 net/mac80211/led.c:145 WARNING: CPU: 0 PID: 5923 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0 debug_check_no_obj_freed+0x4b7/0x600 lib/debugobjects.c:1129 kfree+0x436/0x670 mm/slub.c:6818 ieee80211_led_exit+0x162/0x1c0 net/mac80211/led.c:210 ieee80211_unregister_hw+0x27e/0x3a0 net/mac80211/main.c:1706 rt2x00lib_remove_dev+0x55b/0x670 Undo the LED state in the error path. Assisted-by: LLM Fixes: 67408c8c7b9d ("mac80211: selective throughput LED trigger active") Reported-by: syzbot+e84ecca6d1fa09a9b3d9@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e84ecca6d1fa09a9b3d9 Link: https://patch.msgid.link/20260904165722.044aa432f873.I601a67a2cd558b8ef8416a07554ae7efe896e9d8@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 8c300e045fdf..4c34c3287eb4 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -1606,8 +1606,12 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) err_del_interface: drv_remove_interface(local, sdata); err_stop: - if (!local->open_count) + if (!local->open_count) { + ieee80211_led_radio(local, false); + ieee80211_mod_tpt_led_trig(local, 0, + IEEE80211_TPT_LEDTRIG_FL_RADIO); drv_stop(local, false); + } if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA) RCU_INIT_POINTER(sdata->u.nan_data.nmi, NULL); if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) From 6f0a100df8539ce90f37c14e1945f396ca2410bc Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:14 +0200 Subject: [PATCH 0642/1317] wifi: mac80211: only operate on TDLS peers in the TDLS code ieee80211_tdls_oper() can operate on the AP station, which then yields various warnings when the AP station is removed then or at a later point in time after being confused for a TDLS peer. Always check that the station is a TDLS peer. Assisted-by: LLM Fixes: dfe018bf9953 ("mac80211: handle TDLS high-level commands and frames") Fixes: 17e6a59a365a ("mac80211: cleanup TDLS state during failed setup") Reported-by: syzbot+a59b5291776979816910@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a59b5291776979816910 Link: https://patch.msgid.link/20260904165722.3bad8b79679b.I99618745e83cbe9b9804179387be15fcd3505ae3@changeid Signed-off-by: Johannes Berg --- net/mac80211/tdls.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index dc2f662fe4c4..7f40b1d62938 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1142,6 +1142,7 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_local *local = sdata->local; enum ieee80211_smps_mode smps_mode = sdata->deflink.u.mgd.driver_smps_mode; + struct sta_info *sta; int ret; /* don't support setup with forced SMPS mode that's not off */ @@ -1168,14 +1169,10 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev, * Allow error packets to be sent - sometimes we don't even add a STA * before failing the setup. */ - if (status_code == 0) { - rcu_read_lock(); - if (!sta_info_get(sdata, peer)) { - rcu_read_unlock(); - ret = -ENOLINK; - goto out_unlock; - } - rcu_read_unlock(); + sta = sta_info_get(sdata, peer); + if ((status_code == 0 && !sta) || (sta && !sta->sta.tdls)) { + ret = -ENOLINK; + goto out_unlock; } ieee80211_flush_queues(local, sdata, false); @@ -1442,6 +1439,10 @@ int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, */ tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer); + sta = sta_info_get(sdata, peer); + if (!sta || !sta->sta.tdls) + return -ENOLINK; + switch (oper) { case NL80211_TDLS_ENABLE_LINK: if (sdata->vif.bss_conf.csa_active) { @@ -1449,10 +1450,6 @@ int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, return -EBUSY; } - sta = sta_info_get(sdata, peer); - if (!sta || !sta->sta.tdls) - return -ENOLINK; - iee80211_tdls_recalc_chanctx(sdata, sta); iee80211_tdls_recalc_ht_protection(sdata, sta); From eeee52cfd1d639774c9812e8890631404a057dd2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:01:59 +0200 Subject: [PATCH 0643/1317] wifi: cfg80211: restore netns_immutable on failures Switching a wiphy's netns has to clear netns_immutable before moving interfaces, but then if any of the interfaces fails to move, it gets netns_immutable cleared forever. Then userspace can move it by itself, breaking the assumption that they all move together. Fix the order here and always reset netns_immutable after attempting the move. Assisted-by: LLM Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7ea88157dcbc.Id868585a790be8b9ece9b39b0db464a5963faaf3@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 668380deec7d..043bb57b0556 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -167,9 +167,9 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, continue; wdev->netdev->netns_immutable = false; err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); + wdev->netdev->netns_immutable = true; if (err) break; - wdev->netdev->netns_immutable = true; } if (err) { From a41bd1938a9bfe226d444172a7e20e4bd5097960 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:00 +0200 Subject: [PATCH 0644/1317] wifi: cfg80211: undo netns switch if renaming the wiphy fails Once all the interfaces have been moved, cfg80211_switch_netns() moves the wiphy itself by setting its network namespace and then renaming it, which makes sysfs move it. The rename can fail (but only on allocation failures), leaving things mixed up and hitting the warning there. Ignoring it isn't great, undo the move and let the change fail in this case. If undo fails then WARN, then things would again be stuck in two different network namespaces. Assisted-by: LLM Reported-by: syzbot+3515319a302224e081b4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3515319a302224e081b4 Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7966cc705e33.Ib398351113bbd3cab85302467060cab378564421@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 94 +++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 043bb57b0556..9ee1c36f1262 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -153,9 +153,25 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, return 0; } +static int cfg80211_switch_wdev_netns(struct wireless_dev *wdev, + struct net *net) +{ + int err; + + if (!wdev->netdev) + return 0; + + wdev->netdev->netns_immutable = false; + err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); + wdev->netdev->netns_immutable = true; + + return err; +} + int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, struct net *net) { + struct net *old_net = wiphy_net(&rdev->wiphy); struct wireless_dev *wdev; int err = 0; @@ -163,58 +179,54 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, return -EOPNOTSUPP; list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - wdev->netdev->netns_immutable = false; - err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); - wdev->netdev->netns_immutable = true; + err = cfg80211_switch_wdev_netns(wdev, net); if (err) - break; + goto undo; } - if (err) { - /* failed -- clean up to old netns */ - net = wiphy_net(&rdev->wiphy); - - list_for_each_entry_continue_reverse(wdev, - &rdev->wiphy.wdev_list, - list) { + scoped_guard(wiphy, &rdev->wiphy) { + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { if (!wdev->netdev) continue; - wdev->netdev->netns_immutable = false; - err = dev_change_net_namespace(wdev->netdev, net, - "wlan%d"); - WARN_ON(err); - wdev->netdev->netns_immutable = true; + nl80211_notify_iface(rdev, wdev, + NL80211_CMD_DEL_INTERFACE); } - return err; + nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY); + + wiphy_net_set(&rdev->wiphy, net); + + /* this only fails on allocation failure */ + err = device_rename(&rdev->wiphy.dev, + dev_name(&rdev->wiphy.dev)); + if (err) + wiphy_net_set(&rdev->wiphy, old_net); + + nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); + + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + if (!wdev->netdev) + continue; + nl80211_notify_iface(rdev, wdev, + NL80211_CMD_NEW_INTERFACE); + } } - guard(wiphy)(&rdev->wiphy); + if (!err) + return 0; - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE); - } + /* set to the last one to undo all of them */ + wdev = list_entry(&rdev->wiphy.wdev_list, typeof(*wdev), list); +undo: + /* + * Move back everything, if this fails again (allocation failures) + * then things get stuck in different network namespaces. + */ + list_for_each_entry_continue_reverse(wdev, &rdev->wiphy.wdev_list, + list) + WARN_ON(cfg80211_switch_wdev_netns(wdev, old_net)); - nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY); - - wiphy_net_set(&rdev->wiphy, net); - - err = device_rename(&rdev->wiphy.dev, dev_name(&rdev->wiphy.dev)); - WARN_ON(err); - - nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); - - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - nl80211_notify_iface(rdev, wdev, NL80211_CMD_NEW_INTERFACE); - } - - return 0; + return err; } static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) From eee2efd82867b623982ac51925b5a1812a74c50d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:01 +0200 Subject: [PATCH 0645/1317] wifi: mac80211: unlist vifs when their netdev is unregistered mac80211 only removes vifs from the local->interfaces list when an interface is removed via ieee80211_if_remove(), before it unregisters the netdev. However, it's possible for a netdev to be unregistered without going through that: When the netns that holds the wiphy is destroyed, the wiphy is supposed to move to the init_ns, but that can run into allocation failures. Then, mac80211 has an interface listed that doesn't exist, and will eventually hit BUG: failure at net/wireless/core.h:141/wiphy_to_rdev()! ... _cfg80211_unregister_wdev+0x24/0x36a [cfg80211] cfg80211_unregister_wdev+0x15/0x1d [cfg80211] ieee80211_remove_interfaces+0x1ff/0x257 [mac80211] ieee80211_unregister_hw+0x73/0x1d1 [mac80211] mac80211_hwsim_del_radio+0x114/0x166 [mac80211_hwsim] Remove the interface from the list in ->ndo_uninit if it's still around to avoid this. Assisted-by: LLM Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.038ad73e6c04.I990abca78483e058746b6f42b4796717c3028164@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 4c34c3287eb4..842bfb4a7cb6 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -924,9 +924,33 @@ static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata) } } +/* + * The netdev can be unregistered without mac80211 doing it, e.g. by the netdev + * core when cfg80211 couldn't move it out of a network namespace that's being + * destroyed. Drop it from the interface list either way. + */ +static void ieee80211_unlist_sdata(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_sub_if_data *iter; + + ASSERT_RTNL(); + + list_for_each_entry(iter, &local->interfaces, list) { + if (iter != sdata) + continue; + guard(mutex)(&local->iflist_mtx); + list_del_rcu(&sdata->list); + return; + } +} + static void ieee80211_uninit(struct net_device *dev) { - ieee80211_teardown_sdata(IEEE80211_DEV_TO_SUB_IF(dev)); + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + ieee80211_unlist_sdata(sdata); + ieee80211_teardown_sdata(sdata); } static int ieee80211_netdev_setup_tc(struct net_device *dev, From 4635b1a1c1d693178a537446a6e09963f0fdae52 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:02 +0200 Subject: [PATCH 0646/1317] wifi: cfg80211: get the wiphy out of a dying network namespace When a network namespace is destroyed, cfg80211_pernet_exit() moves any wiphy back to the initial namespace, and just warns if that fails. But moving an interface can fail (due to allocation failures), and then the wiphy is left behind with a garbage netns pointer: Kernel mode fault at addr 0x30 genlmsg_multicast_netns.constprop.0+0x46/0xcf [cfg80211] nl80211_notify_wiphy+0xcd/0xe8 [cfg80211] wiphy_unregister+0x169/0x3fc [cfg80211] Note that commit debac3a20dec ("net: Remove conflicting altnames for dying netns in __dev_change_net_namespace().") fixed another path that could reach it without allocation failures. Remove interfaces that cannot be moved instead of failing the switch, so that the wiphy always ends up in the initial namespace. In this case the netdev core will unregister the interfaces anyway. Assisted-by: LLM Reported-by: syzbot+c5f8a81e794d4a4f2014@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c5f8a81e794d4a4f2014 Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7f3edc6d9992.I5e57921011244d3d8ef14d89e738aa19a5d972a0@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 9ee1c36f1262..25dd1a4d6b4e 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -168,20 +168,24 @@ static int cfg80211_switch_wdev_netns(struct wireless_dev *wdev, return err; } -int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, - struct net *net) +static int __cfg80211_switch_netns(struct cfg80211_registered_device *rdev, + struct net *net, bool force) { struct net *old_net = wiphy_net(&rdev->wiphy); - struct wireless_dev *wdev; + struct wireless_dev *wdev, *tmp; int err = 0; - if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK)) - return -EOPNOTSUPP; - - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + list_for_each_entry_safe(wdev, tmp, &rdev->wiphy.wdev_list, list) { err = cfg80211_switch_wdev_netns(wdev, net); - if (err) + if (!err) + continue; + if (!force) goto undo; + /* remove interfaces that fail to allow wiphy switching */ + dev_close(wdev->netdev); + scoped_guard(wiphy, &rdev->wiphy) + cfg80211_unregister_wdev(wdev); + err = 0; } scoped_guard(wiphy, &rdev->wiphy) { @@ -199,7 +203,7 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, /* this only fails on allocation failure */ err = device_rename(&rdev->wiphy.dev, dev_name(&rdev->wiphy.dev)); - if (err) + if (err && !force) wiphy_net_set(&rdev->wiphy, old_net); nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); @@ -212,8 +216,8 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, } } - if (!err) - return 0; + if (!err || force) + return err; /* set to the last one to undo all of them */ wdev = list_entry(&rdev->wiphy.wdev_list, typeof(*wdev), list); @@ -229,6 +233,15 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, return err; } +int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, + struct net *net) +{ + if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK)) + return -EOPNOTSUPP; + + return __cfg80211_switch_netns(rdev, net, false); +} + static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) { struct cfg80211_registered_device *rdev = data; @@ -1882,7 +1895,7 @@ static void __net_exit cfg80211_pernet_exit(struct net *net) rtnl_lock(); for_each_rdev(rdev) { if (net_eq(wiphy_net(&rdev->wiphy), net)) - WARN_ON(cfg80211_switch_netns(rdev, &init_net)); + WARN_ON(__cfg80211_switch_netns(rdev, &init_net, true)); } rtnl_unlock(); } From 87840d4a3a21b1c19b867a80e16ba69dff284de2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:01:35 +0200 Subject: [PATCH 0647/1317] wifi: mac80211_hwsim: don't hand frames to mac80211 while stopping The code checks ->started for frames coming from wmediumd, but the radio can be stopped after the check and before frame delivery, causing mac80211 to hit the WARN_ON(!local->started). Expand the mutex for this case and synchronise against it when the radio is stopped to avoid being able to hit the warning with hwsim. Drop the error print that would've complicated the error path, it only triggers for allocation failures (already noisy) and malformed frames anyway. Assisted-by: LLM Fixes: 7882513bacb1 ("mac80211_hwsim driver support userspace frame tx/rx") Reported-by: syzbot+b4aa2b672b18f1d4dc5f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b4aa2b672b18f1d4dc5f Link: https://patch.msgid.link/20260904170140.5f69a10d606b.I4a7921d00643f69e439c7a3b221d104f66a3dcdc@changeid Signed-off-by: Johannes Berg --- .../wireless/virtual/mac80211_hwsim_main.c | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 02b6d81cccd1..b9446577ff49 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -2327,7 +2327,12 @@ static void mac80211_hwsim_stop(struct ieee80211_hw *hw, bool suspend) struct sk_buff *skb; int i; - data->started = false; + /* + * Serialise against wmediumd userspace, so no more frames + * can be handed to mac80211 after this returns. + */ + scoped_guard(mutex, &data->mutex) + data->started = false; for (i = 0; i < ARRAY_SIZE(data->link_data); i++) hrtimer_cancel(&data->link_data[i].beacon_timer); @@ -6505,12 +6510,12 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, if (frame_data_len < sizeof(struct ieee80211_hdr_3addr) || frame_data_len > IEEE80211_MAX_DATA_LEN) - goto err; + goto out; /* Allocate new skb here */ skb = alloc_skb(frame_data_len, GFP_KERNEL); if (skb == NULL) - goto err; + goto out; /* Copy the data */ skb_put_data(skb, frame_data, frame_data_len); @@ -6535,10 +6540,17 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, goto out; } + /* + * Serialise against mac80211_hwsim_stop() - mac80211 doesn't allow + * frames reported while the HW is down, hence the ->started check + * must be under mutex. + */ + mutex_lock(&data2->mutex); + /* check if radio is configured properly */ if ((data2->idle && !data2->tmp_chan) || !data2->started) - goto out; + goto out_unlock; /* A frame is received from user space */ memset(&rx_status, 0, sizeof(rx_status)); @@ -6557,22 +6569,18 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, iter_data.channel = ieee80211_get_channel(data2->hw->wiphy, rx_status.freq); if (!iter_data.channel) - goto out; + goto out_unlock; rx_status.band = iter_data.channel->band; - mutex_lock(&data2->mutex); if (!hwsim_chans_compat(iter_data.channel, channel)) { ieee80211_iterate_active_interfaces_atomic( data2->hw, IEEE80211_IFACE_ITER_NORMAL, mac80211_hwsim_tx_iter, &iter_data); - if (!iter_data.receive) { - mutex_unlock(&data2->mutex); - goto out; - } + if (!iter_data.receive) + goto out_unlock; } - mutex_unlock(&data2->mutex); } else if (!channel) { - goto out; + goto out_unlock; } else { rx_status.freq = channel->center_freq; rx_status.band = channel->band; @@ -6580,7 +6588,7 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, rx_status.rate_idx = nla_get_u32(info->attrs[HWSIM_ATTR_RX_RATE]); if (rx_status.rate_idx >= data2->hw->wiphy->bands[rx_status.band]->n_bitrates) - goto out; + goto out_unlock; rx_status.signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]); hdr = (void *)skb->data; @@ -6590,10 +6598,11 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, rx_status.boottime_ns = ktime_get_boottime_ns(); mac80211_hwsim_rx(data2, &rx_status, skb); + mutex_unlock(&data2->mutex); return 0; -err: - pr_debug("mac80211_hwsim: error occurred in %s\n", __func__); +out_unlock: + mutex_unlock(&data2->mutex); out: dev_kfree_skb(skb); return -EINVAL; From e6c5ed7a98d7bc8b0f7918246f1c90ddb3f79dfa Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 4 Sep 2026 14:24:45 +0000 Subject: [PATCH 0648/1317] wifi: rsi: fix heap OOB write on key removal When a key is removed (data == NULL), rsi_hal_load_key() runs: memset(&set_key[FRAME_DESC_SZ], 0, frame_len - FRAME_DESC_SZ); set_key is a struct rsi_set_key *, so the subscript is scaled by sizeof(struct rsi_set_key) (160 bytes): &set_key[FRAME_DESC_SZ] is skb->data + 2560, and the memset writes 144 zero bytes starting 2.4KB past the end of the 160-byte skb data buffer, corrupting unrelated heap objects. The intended byte offset would have been (u8 *)set_key + FRAME_DESC_SZ. The write fires on every DISABLE_KEY callback, so plain disconnects, roams and interface teardowns trigger it on real networks. The memset is redundant: the whole buffer is zeroed right after allocation, so the frame sent to the device is byte-identical without it. Drop the else branch; normal operation is unaffected. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: dad0d04fa7ba ("rsi: Add RS9113 wireless driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/90bb2b07007942064c04aa3729cedd9eb1e930b1@linux.dev Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_mgmt.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/rsi/rsi_91x_mgmt.c b/drivers/net/wireless/rsi/rsi_91x_mgmt.c index bb167f03367b..d9dcbb255317 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mgmt.c +++ b/drivers/net/wireless/rsi/rsi_91x_mgmt.c @@ -852,8 +852,6 @@ int rsi_hal_load_key(struct rsi_common *common, memcpy(set_key->tx_mic_key, &data[16], 8); memcpy(set_key->rx_mic_key, &data[24], 8); } - } else { - memset(&set_key[FRAME_DESC_SZ], 0, frame_len - FRAME_DESC_SZ); } skb_put(skb, frame_len); From c1ba7f7f18465e259cf1b4d9c73fc73853d7f790 Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 4 Sep 2026 13:39:34 +0000 Subject: [PATCH 0649/1317] wifi: wilc1000: fix RX buffer OOB-write in wilc_wlan_handle_isr_ext() wilc_wlan_handle_isr_ext() takes the RX transfer size from the device-reported interrupt status register (a 15-bit field shifted left by 2, up to 131068 bytes) and reads that many bytes from the device into rx_buffer, which is only WILC_RX_BUFF_SIZE (96K) large. The wrap check only handles the current offset; the size itself is never compared against the buffer, so a bogus SDIO device can make the driver OOB-write rx_buffer by up to ~32K with data it controls. The oversized transfer also leaves rx_buffer_offset past the end of the buffer, after which the unsigned wrap check stops working and the overflow can repeat. Drop any transfer whose size exceeds the RX buffer, acknowledging the data interrupt and re-arming the RX engine so the bogus frame is discarded and reception can continue. This also restores the rx_buffer_offset <= WILC_RX_BUFF_SIZE invariant the wrap check relies on. This is not expected to change driver behavior in most cases: without this check, an oversized transfer would most likely corrupt neighboring kernel memory instead of completing anyway, and the drop path performs the same interrupt acknowledgment and RX engine re-arming as the normal path, so subsequent transfers are received unaffected. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: c5c77ba18ea6 ("staging: wilc1000: Add SDIO/SPI 802.11 driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/7c971924c6bdccf6c2f75704a5a746e9303aaf64@linux.dev Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/wlan.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/wireless/microchip/wilc1000/wlan.c b/drivers/net/wireless/microchip/wilc1000/wlan.c index 4b116fe6f9ea..55a77a2e3288 100644 --- a/drivers/net/wireless/microchip/wilc1000/wlan.c +++ b/drivers/net/wireless/microchip/wilc1000/wlan.c @@ -1197,6 +1197,15 @@ static void wilc_wlan_handle_isr_ext(struct wilc *wilc, u32 int_status) if (size <= 0) return; + /* A size exceeding the RX buffer is bogus; drop the transfer + * instead of overflowing the buffer. + */ + if (size > WILC_RX_BUFF_SIZE) { + wilc->hif_func->hif_clear_int_ext(wilc, + DATA_INT_CLR | ENABLE_RX_VMM); + return; + } + if (WILC_RX_BUFF_SIZE - offset < size) offset = 0; From e14bf37bb2b3853012ff160131d1c6233f7a9cc9 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:12 +0200 Subject: [PATCH 0650/1317] wifi: mac80211: don't allow injecting frames wider than the chanctx Frames injected on a monitor interface can carry a radiotap field requesting a bandwidth, which mac80211 passes down to the driver regardless of the the actual operational bandwidth. If the bandwidth requested is too wide, that triggers a warning in hwsim: WARN_ON(hwsim_get_chanwidth(bw) > hwsim_get_chanwidth(confbw)) Drop such frames entirely instead since they cannot be sent. Assisted-by: LLM Fixes: 646e76bb5daf ("mac80211: parse VHT info in injected frames") Reported-by: syzbot+435fdb053cf98bfa5778@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=435fdb053cf98bfa5778 Link: https://patch.msgid.link/20260908122838.201719-13-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- include/net/mac80211.h | 5 ++++- net/mac80211/iface.c | 2 +- net/mac80211/tx.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 9d1fac6e8082..ed6a5874ff96 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7638,11 +7638,14 @@ bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw, * * @skb: packet injected by userspace * @dev: the &struct device of this 802.11 device + * @chandef: the channel definition the frame will be transmitted on, or + * %NULL to skip the bandwidth checks * * Return: %true if the radiotap header was parsed, %false otherwise */ bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, - struct net_device *dev); + struct net_device *dev, + const struct cfg80211_chan_def *chandef); /** * struct ieee80211_noa_data - holds temporary data for tracking P2P NoA state diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 842bfb4a7cb6..ca66eb493ac7 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -991,7 +991,7 @@ static u16 ieee80211_monitor_select_queue(struct net_device *dev, /* reset flags and info before parsing radiotap header */ memset(info, 0, sizeof(*info)); - if (!ieee80211_parse_tx_radiotap(skb, dev)) + if (!ieee80211_parse_tx_radiotap(skb, dev, NULL)) return 0; /* doesn't matter, frame will be dropped */ len_rthdr = ieee80211_get_radiotap_len(skb->data); diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index d155fb319a55..c343ed56506a 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2105,8 +2105,29 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb) return true; } +static bool ieee80211_rate_bw_usable(u16 rate_flags, + const struct cfg80211_chan_def *chandef) +{ + int width; + + if (!chandef) + return true; + + if (rate_flags & IEEE80211_TX_RC_160_MHZ_WIDTH) + width = 160; + else if (rate_flags & IEEE80211_TX_RC_80_MHZ_WIDTH) + width = 80; + else if (rate_flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + width = 40; + else + return true; + + return width <= cfg80211_chandef_get_width(chandef); +} + bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, - struct net_device *dev) + struct net_device *dev, + const struct cfg80211_chan_def *chandef) { struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct ieee80211_radiotap_iterator iterator; @@ -2280,6 +2301,9 @@ bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, struct ieee80211_supported_band *sband = local->hw.wiphy->bands[info->band]; + if (!ieee80211_rate_bw_usable(rate_flags, chandef)) + return false; + info->control.flags |= IEEE80211_TX_CTRL_RATE_INJECT; for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { @@ -2479,7 +2503,7 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, * selected chandef above to accurately set injection rates and * retransmissions. */ - if (!ieee80211_parse_tx_radiotap(skb, dev)) + if (!ieee80211_parse_tx_radiotap(skb, dev, chandef)) goto fail_rcu; /* remove the injection radiotap header */ From 4504f3960dc4501c73be9f99eabda2e26e9db41e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:13 +0200 Subject: [PATCH 0651/1317] wifi: mac80211: reset the AP_VLAN tailroom counter on ifdown On ifup, AP_VLAN interfaces get crypto_tx_tailroom_needed_cnt from the AP interface, but it's never decremented again unless the AP is also brought down. Thus, bringing the same AP_VLAN up again will increment the counter again and eventually hit the sanity check: WARN_ON_ONCE(sdata->crypto_tx_tailroom_needed_cnt != master->crypto_tx_tailroom_needed_cnt); Reset it on ifdown to avoid that. Assisted-by: LLM Fixes: f9dca80b98ca ("mac80211: fix AP_VLAN crypto tailroom calculation") Reported-by: syzbot+de3ee5362db09487ea37@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=de3ee5362db09487ea37 Link: https://patch.msgid.link/20260908122838.201719-14-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index ca66eb493ac7..889c32fd8de1 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -616,6 +616,8 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do RCU_INIT_POINTER(sdata->vif.bss_conf.chanctx_conf, NULL); /* see comment in the default case below */ ieee80211_free_keys(sdata, true); + /* increased by AP value on ifup, so reset on ifdown */ + sdata->crypto_tx_tailroom_needed_cnt = 0; /* no need to tell driver */ break; case NL80211_IFTYPE_MONITOR: From 038e1d126304fd25d507fd4e671232df57bd1799 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:14 +0200 Subject: [PATCH 0652/1317] wifi: mac80211: require a peer station for TDLS setup confirm It's nonsense for the setup confirm to go to station that doesn't even exist, and it hits a warning when building the frame: WARN_ON_ONCE(!sta || !ap_sta) Only accept WLAN_TDLS_SETUP_CONFIRM when the station is already there as a TDLS station. Need to copy the call to ieee80211_tdls_prep_mgmt_packet() since the existing WLAN_TDLS_DISCOVERY_REQUEST already falls through to it. Assisted-by: LLM Fixes: 6f7eaa47e1de ("mac80211: add TDLS QoS param IE on setup-confirm") Reported-by: syzbot+e55106f8389651870be0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e55106f8389651870be0 Link: https://patch.msgid.link/20260908122838.201719-15-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tdls.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index 7f40b1d62938..f663d28d9209 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1281,6 +1281,24 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, peer_capability, initiator, extra_ies, extra_ies_len); break; + case WLAN_TDLS_SETUP_CONFIRM: { + struct sta_info *sta; + + sta = sta_info_get(sdata, peer); + if (!sta || !sta->sta.tdls) { + ret = -ENOLINK; + break; + } + + ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, + link_id, action_code, + dialog_token, + status_code, + peer_capability, + initiator, extra_ies, + extra_ies_len, 0, NULL); + break; + } case WLAN_TDLS_DISCOVERY_REQUEST: /* * Protect the discovery so we can hear the TDLS discovery @@ -1289,7 +1307,6 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, */ drv_mgd_protect_tdls_discover(sdata->local, sdata, link_id); fallthrough; - case WLAN_TDLS_SETUP_CONFIRM: case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: /* no special handling */ ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, From 370872d30349d81dec519e15ea2949fd63511cf7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:15 +0200 Subject: [PATCH 0653/1317] wifi: mac80211: don't allow link changes when iface is down ieee80211_set_active_links() only checks that the interface is running in the inner __ieee80211_set_active_links(), after drv_can_activate_links() was already called, so using active_links on an interface that's down triggers the check-sdata-in-driver warning. Add the missing check in the debugfs file. Assisted-by: LLM Fixes: 3d9011029227 ("wifi: mac80211: implement link switching") Reported-by: syzbot+582469b3a9ef5f13606b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=582469b3a9ef5f13606b Link: https://patch.msgid.link/20260908122838.201719-16-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/debugfs_netdev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index f3c6a41e4911..8346d3eb1143 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -729,6 +729,9 @@ static ssize_t ieee80211_if_parse_active_links(struct ieee80211_sub_if_data *sda if (kstrtou16(buf, 0, &active_links) || !active_links) return -EINVAL; + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + return ieee80211_set_active_links(&sdata->vif, active_links) ?: buflen; } IEEE80211_IF_FILE_RW(active_links); From b481e64e4498e2c053d5954f546ee02338f6ab63 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:16 +0200 Subject: [PATCH 0654/1317] wifi: mac80211: don't RCU-dereference the mesh CSA settings we just set In the error path of ieee80211_mesh_csa_beacon() the settings that were just assigned are read back with rcu_dereference(), which lockdep then complains about. There's no need to read the pointer at all, tmp_csa_settings still is the right value anyway. Assisted-by: LLM Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Reported-by: syzbot+b59873f5699e941717ca@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b59873f5699e941717ca Link: https://patch.msgid.link/20260908122838.201719-17-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index bed7ac838250..a35e2d5870b6 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1559,7 +1559,6 @@ int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata, ret = ieee80211_mesh_rebuild_beacon(sdata); if (ret) { - tmp_csa_settings = rcu_dereference(ifmsh->csa); RCU_INIT_POINTER(ifmsh->csa, NULL); kfree_rcu(tmp_csa_settings, rcu_head); return ret; From 0b1de9feeb8651f7a3bb53ed7c9006e3b5298c01 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:17 +0200 Subject: [PATCH 0655/1317] wifi: mac80211: don't access the TSF of a down interface The tsf debugfs files call the driver even if the interface isn't up, tgriggering check-sdata-in-driver warnings. Reject the access in that case. Assisted-by: LLM Fixes: 37a41b4affa3 ("mac80211: add ieee80211_vif param to tsf functions") Reported-by: syzbot+1c8c45017f784e646b47@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1c8c45017f784e646b47 Link: https://patch.msgid.link/20260908122838.201719-18-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/debugfs_netdev.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index 8346d3eb1143..6aba22493670 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -657,6 +657,9 @@ static ssize_t ieee80211_if_fmt_tsf( struct ieee80211_local *local = sdata->local; u64 tsf; + if (!ieee80211_sdata_running((struct ieee80211_sub_if_data *)sdata)) + return -ENETDOWN; + tsf = drv_get_tsf(local, (struct ieee80211_sub_if_data *)sdata); return scnprintf(buf, buflen, "0x%016llx\n", (unsigned long long) tsf); @@ -670,6 +673,9 @@ static ssize_t ieee80211_if_parse_tsf( int ret; int tsf_is_delta = 0; + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + if (strncmp(buf, "reset", 5) == 0) { if (local->ops->reset_tsf) { drv_reset_tsf(local, sdata); From cd54bf333f5631d3630bab0a832e9ae648f73515 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:18 +0200 Subject: [PATCH 0656/1317] wifi: mac80211: add HE 6 GHz capability in the scan elems len The HE 6 GHz Band Capability element is in the probe request for every band if 6 GHz is supported, so add the size to scan_ies_len. Otherwise, building probe request elements can fail, triggering the WARN_ON in __ieee80211_start_scan(). Assisted-by: LLM Fixes: 2ad2274c58ee ("mac80211: Add HE 6GHz capabilities element to probe request") Reported-by: syzbot+f961b9f94edbc266f1f8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f961b9f94edbc266f1f8 Link: https://patch.msgid.link/20260908122838.201719-19-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/main.c b/net/mac80211/main.c index a59837b9f480..6408e8464338 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -1453,6 +1453,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) sizeof(struct ieee80211_he_mcs_nss_supp) + IEEE80211_HE_PPE_THRES_MAX_LEN; + if (local->hw.wiphy->bands[NL80211_BAND_6GHZ]) + local->scan_ies_len += + 3 + sizeof(struct ieee80211_he_6ghz_capa); + if (supp_eht) local->scan_ies_len += 3 + sizeof(struct ieee80211_eht_cap_elem) + From 860134b3af77970e006feab7e5decb8c84771c7f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:19 +0200 Subject: [PATCH 0657/1317] wifi: mac80211: mesh: reset the CSA state when leaving ifmsh->csa is allocated in ieee80211_mesh_csa_beacon() and only freed in ieee80211_mesh_finish_csa(), i.e. when the channel switch completes. Leaving the mesh while a switch is still pending therefore leaks it. Additionally, ifmsh->csa_role and ifmsh->chsw_ttl have their state leak in this case, so things can get mixed up in addition to the memory leak. Refactor the reset and call it in ieee80211_stop_mesh() to fix it all. Assisted-by: LLM Reported-by: syzbot+f5752cd6b94fe38be666@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f5752cd6b94fe38be666 Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Link: https://patch.msgid.link/20260908122838.201719-20-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index a35e2d5870b6..8f8814125375 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1196,6 +1196,21 @@ int ieee80211_start_mesh(struct ieee80211_sub_if_data *sdata) return 0; } +static void ieee80211_mesh_reset_csa(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct mesh_csa_settings *csa; + + /* Reset the TTL value and Initiator flag */ + ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; + ifmsh->chsw_ttl = 0; + + /* Remove the CSA and MCSP elements from the beacon */ + csa = sdata_dereference(ifmsh->csa, sdata); + RCU_INIT_POINTER(ifmsh->csa, NULL); + kfree_rcu(csa, rcu_head); +} + void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) { struct ieee80211_local *local = sdata->local; @@ -1206,6 +1221,7 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) /* abort any running channel switch */ sdata->vif.bss_conf.csa_active = false; + ieee80211_mesh_reset_csa(sdata); ieee80211_vif_unblock_queues_csa(sdata); /* flush STAs and mpaths on this iface */ @@ -1514,19 +1530,10 @@ static void ieee80211_mesh_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata, u64 *changed) { - struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; - struct mesh_csa_settings *tmp_csa_settings; - int ret = 0; + int ret; - /* Reset the TTL value and Initiator flag */ - ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; - ifmsh->chsw_ttl = 0; + ieee80211_mesh_reset_csa(sdata); - /* Remove the CSA and MCSP elements from the beacon */ - tmp_csa_settings = sdata_dereference(ifmsh->csa, sdata); - RCU_INIT_POINTER(ifmsh->csa, NULL); - if (tmp_csa_settings) - kfree_rcu(tmp_csa_settings, rcu_head); ret = ieee80211_mesh_rebuild_beacon(sdata); if (ret) return -EINVAL; From ae97fff6495a8764bc0ef281cfe5444f701e527f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:20 +0200 Subject: [PATCH 0658/1317] wifi: mac80211: mesh: release the channel if start fails ieee80211_join_mesh() acquires a channel context and then calls ieee80211_start_mesh(), which can fail. In that case, the chanctx isn't released then interface removal will attempt to unassign it after it's removed from the driver, hitting: wlan0: Failed check-sdata-in-driver check, flags: 0x0 WARNING: net/mac80211/driver-ops.c:366 at drv_unassign_vif_chanctx ieee80211_assign_link_chanctx __ieee80211_link_release_channel ieee80211_link_release_channel ieee80211_teardown_sdata unregister_netdevice_many_notify _cfg80211_unregister_wdev ieee80211_remove_interfaces ieee80211_unregister_hw mac80211_hwsim_del_radio hwsim_exit_net Correctly release the channel on start failures. Assisted-by: LLM Reported-by: syzbot+63a84ea9c0f57d6133fa@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=63a84ea9c0f57d6133fa Fixes: 2b5e19677592 ("mac80211: cache mesh beacon") Link: https://patch.msgid.link/20260908122838.201719-21-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 2d5a0abe35db..d3558f0c7550 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -3323,7 +3323,11 @@ static int ieee80211_join_mesh(struct wiphy *wiphy, struct net_device *dev, if (err) return err; - return ieee80211_start_mesh(sdata); + err = ieee80211_start_mesh(sdata); + if (err) + ieee80211_link_release_channel(&sdata->deflink); + + return err; } static int ieee80211_leave_mesh(struct wiphy *wiphy, struct net_device *dev) From 50d3d79dc0743b616afb00d01a626c76758721f7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:21 +0200 Subject: [PATCH 0659/1317] wifi: mac80211: set up the TX info early to fix failure paths The previous commit 2c51457d930f ("wifi: mac80211: free ack status frame on TX header build failure") cleaned up the leak, but still left the code a bit messy and the failed SKB didn't get reported to userspace. Fix this up by initialising skb->cb[] earlier, which allows using ieee80211_free_txskb() and therefore reports it for the failure in ieee80211_build_hdr(), and unifies the ieee80211_skb_resize() failure path with it. Assisted-by: LLM Fixes: c3e7724b6bc2 ("mac80211: use ieee80211_free_txskb to fix possible skb leaks") Link: https://patch.msgid.link/20260908122838.201719-22-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c343ed56506a..814399989b5e 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2981,10 +2981,23 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, */ skb = skb_share_check(skb, GFP_ATOMIC); if (unlikely(!skb)) { - ret = -ENOMEM; - goto free; + /* skb_share_check() already freed the skb */ + if (info_id) + ieee80211_remove_ack_skb(local, info_id); + return ERR_PTR(-ENOMEM); } + /* set this up so failure paths can clean up ack skb */ + info = IEEE80211_SKB_CB(skb); + memset(info, 0, sizeof(*info)); + + info->flags = info_flags; + if (info_id) { + info->status_data = info_id; + info->status_data_idr = 1; + } + info->band = band; + hdr.frame_control = fc; hdr.duration_id = 0; hdr.seq_ctrl = 0; @@ -3023,10 +3036,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, head_need += local->tx_headroom; head_need = max_t(int, 0, head_need); if (ieee80211_skb_resize(sdata, skb, head_need, ENCRYPT_DATA)) { - ieee80211_free_txskb(&local->hw, skb); - skb = NULL; ret = -ENOMEM; - goto free; + goto free_txskb; } } @@ -3053,16 +3064,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, skb_reset_mac_header(skb); - info = IEEE80211_SKB_CB(skb); - memset(info, 0, sizeof(*info)); - - info->flags = info_flags; - if (info_id) { - info->status_data = info_id; - info->status_data_idr = 1; - } - info->band = band; - if (likely(!cookie)) { ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); @@ -3086,16 +3087,17 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, pre_conf_link_id, link_id); #endif ret = -EINVAL; - goto free; + goto free_txskb; } } info->control.flags = ctrl_flags; return skb; + free_txskb: + ieee80211_free_txskb(&local->hw, skb); + return ERR_PTR(ret); free: - if (info_id) - ieee80211_remove_ack_skb(local, info_id); kfree_skb(skb); return ERR_PTR(ret); } From d144a494d81fcf2d1c5cf58b01c655bb8bafc701 Mon Sep 17 00:00:00 2001 From: Gabriel Windlin Date: Tue, 8 Sep 2026 22:34:11 +0200 Subject: [PATCH 0660/1317] MAINTAINERS: fix sysfs-platform-ayaneo-ec documentation path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation file for the AYANEO platform EC driver was added as Documentation/ABI/testing/sysfs-platform-ayaneo-ec, but MAINTAINERS references it without the '-ec' suffix, causing 'make refcheckdocs' to report a broken reference. Update the file entry to point to the correct file name. Signed-off-by: Gabriel Windlin Link: https://patch.msgid.link/20260908203412.608606-1-gawindlin@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..0a0dbb9cb126 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4512,7 +4512,7 @@ AYANEO PLATFORM EC DRIVER M: Antheas Kapenekakis L: platform-driver-x86@vger.kernel.org S: Maintained -F: Documentation/ABI/testing/sysfs-platform-ayaneo +F: Documentation/ABI/testing/sysfs-platform-ayaneo-ec F: drivers/platform/x86/ayaneo-ec.c AZ6007 DVB DRIVER From 0ee150794c75bcd0be0e24ff3394f433cbae18cc Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Tue, 8 Sep 2026 16:10:00 +0000 Subject: [PATCH 0661/1317] smb: client: fix heap overflow in DACL owner/group rewrite When id_mode_to_cifs_acl rewrites an existing DACL, it allocates a buffer sized according to the on-disk DACL length reported by dacl_ptr->size. However, replace_sids_and_copy_aces may rewrite each ACE with a new owner/group SID obtained from the cifs.idmap upcall. Those SIDs can have up to SID_MAX_SUB_AUTHORITIES (15) sub-authorities, making each ACE up to 76 bytes (sizeof(struct smb_ace)). If the original DACL contains short SIDs (e.g., 1 sub-authority) while the replacement SIDs are long, the rewritten ACEs overflow the allocation. Fix this by always budgeting for worst-case SID expansion: allocate sizeof(struct smb_acl) plus num_aces * sizeof(struct smb_ace), which covers the smb_acl header and room for every ACE at maximum SID size. This replaces the previous split logic that used dacl_ptr->size for cifsacl mounts but num_aces * sizeof(struct smb_ace) for mode_from_sid mounts: both paths can trigger the same rewrite and need the same headroom. KASAN reports this as: BUG: KASAN: slab-out-of-bounds in build_sec_desc+0x1e8a/0x2680 [cifs] Write of size 4 at addr ffff8881a5e25374 by task chown/5298 ... The buggy address is located 0 bytes to the right of allocated 884-byte region [ffff8881a5e25000, ffff8881a5e25374) Cc: stable@vger.kernel.org Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.") Assisted-by: Kiro:claude-opus-4.6 Signed-off-by: Bjoern Doebel Reviewed-by: Namjae Jeon Fixes: 5c3564852c58 ("cifs: Minimize the number of cifs_acl memory allocations") Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsacl.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index def8908dd7e9..3e96e151df35 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1837,11 +1837,13 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode, cifs_put_tlink(tlink); return rc; } - if (mode_from_sid) - nsecdesclen += - le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace); - else /* cifsacl */ - nsecdesclen += le16_to_cpu(dacl_ptr->size); + /* + * Worst case: every ACE is rewritten with a new SID of + * SID_MAX_SUB_AUTHORITIES sub-auths -> sizeof(smb_ace) each, + * plus the smb_acl header replace_sids_and_copy_aces() emits. + */ + nsecdesclen += sizeof(struct smb_acl) + + le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace); } } From d05045177a855386bca5e1909e08d06290e6e3b3 Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Tue, 8 Sep 2026 16:10:01 +0000 Subject: [PATCH 0662/1317] smb: client: fail DACL rewrite when the new DACL exceeds 64K replace_sids_and_copy_aces() and set_chmod_dacl() accumulate the size of the DACL they build in a u16. That accumulator can wrap. validate_dacl() caps num_aces at (dacl_size - sizeof(struct smb_acl)) / 20, i.e. 3276 for a maximally sized DACL, while each rewritten ACE can grow to sizeof(struct smb_ace) (76 bytes) once its SID is replaced with one carrying SID_MAX_SUB_AUTHORITIES sub-authorities. The worst case is therefore sizeof(struct smb_acl) + 3276 * 76 = 248984 bytes, far beyond what a u16 can hold. A wraparound is reached with 863 ACEs. After the wraparound, ndacl_ptr->size becomes meaningless and the offset will point anywhere in the ACE array. As a result, we will see corruption of the DACL, which then gets sent to the server. This is not an out-of-bounds write as the allocation now covers the worst-case expansion, so writes will always go into the buffer. Adjust the code to use a u32 internally and return -EOVERFLOW in the overflow case. The operation must be refused, because a DACL can only hold 2^16-1 bytes on the wire and larger DACLs cannot be represented. set_chmod_dacl() carries the same pattern and is fixed the same way. It only wraps once the source DACL comes within roughly 380 bytes of the 64K ceiling, but the failure mode is identical. Suggested-by: Namjae Jeon Cc: stable@vger.kernel.org Fixes: f5065508897a ("cifs: Retain old ACEs when converting between mode bits and ACL.") Assisted-by: Kiro:claude-opus-5 Signed-off-by: Bjoern Doebel Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsacl.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 3e96e151df35..c5e47a835f99 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1096,13 +1096,13 @@ unsigned int setup_special_user_owner_ACE(struct smb_ace *pntace) static void populate_new_aces(char *nacl_base, struct smb_sid *pownersid, struct smb_sid *pgrpsid, - __u64 *pnmode, u16 *pnum_aces, u16 *pnsize, + __u64 *pnmode, u16 *pnum_aces, u32 *pnsize, bool modefromsid, bool posix) { __u64 nmode; u16 num_aces = 0; - u16 nsize = 0; + u32 nsize = 0; __u64 user_mode; __u64 group_mode; __u64 other_mode; @@ -1201,17 +1201,17 @@ static void populate_new_aces(char *nacl_base, *pnsize = nsize; } -static __u16 replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *pndacl, - struct smb_sid *pownersid, struct smb_sid *pgrpsid, - struct smb_sid *pnownersid, struct smb_sid *pngrpsid, - int *aclflag) +static int replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *pndacl, + struct smb_sid *pownersid, struct smb_sid *pgrpsid, + struct smb_sid *pnownersid, struct smb_sid *pngrpsid, + int *aclflag, u16 *pnsize) { int i; u16 size = 0; struct smb_ace *pntace = NULL; char *acl_base = NULL; u16 src_num_aces = 0; - u16 nsize = 0; + u32 nsize = 0; struct smb_ace *pnntace = NULL; char *nacl_base = NULL; u16 ace_size = 0; @@ -1240,9 +1240,12 @@ static __u16 replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *p size += le16_to_cpu(pntace->size); nsize += ace_size; + if (nsize > U16_MAX) + return -EOVERFLOW; } - return nsize; + *pnsize = nsize; + return 0; } static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl, @@ -1254,7 +1257,7 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl, struct smb_ace *pntace = NULL; char *acl_base = NULL; u16 src_num_aces = 0; - u16 nsize = 0; + u32 nsize = 0; struct smb_ace *pnntace = NULL; char *nacl_base = NULL; u16 num_aces = 0; @@ -1305,6 +1308,8 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl, nsize += cifs_copy_ace(pnntace, pntace, NULL); num_aces++; + if (nsize > U16_MAX) + return -EOVERFLOW; next_ace: size += le16_to_cpu(pntace->size); @@ -1321,6 +1326,10 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl, } finalize_dacl: + /* The DACL size field is 16-bit on the wire, see MS-DTYP 2.4.5 */ + if (nsize > U16_MAX) + return -EOVERFLOW; + pndacl->num_aces = cpu_to_le16(num_aces); pndacl->size = cpu_to_le16(nsize); @@ -1473,6 +1482,8 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd, rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, pnmode, mode_from_sid, posix); + if (rc) + return rc; sidsoffset = ndacloffset + le16_to_cpu(ndacl_ptr->size); /* copy the non-dacl portion of secdesc */ @@ -1548,10 +1559,12 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd, if (dacloffset) { /* Replace ACEs for old owner with new one */ - size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, - owner_sid_ptr, group_sid_ptr, - nowner_sid_ptr, ngroup_sid_ptr, - aclflag); + rc = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, + owner_sid_ptr, group_sid_ptr, + nowner_sid_ptr, ngroup_sid_ptr, + aclflag, &size); + if (rc) + goto chown_chgrp_exit; ndacl_ptr->size = cpu_to_le16(size); } From 4600b4d1a9ee730d03ddac5ce409cd2730ce8c0c Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 31 Aug 2026 14:21:32 +0200 Subject: [PATCH 0663/1317] drm/bridge: ti-sn65dsi83: Fix error handling in sn65dsi83_reset_work() The error handling of sn65dsi83_reset_pipe() in sn65dsi83_reset_work() has seen a couple of changes that seems to cause a bit of confusion. While sn65dsi83_reset_work() has implemented an early exit if sn65dsi83_reset_pipe() fails since it was added, when a commit from Maxime Ripard switched to use drm_bridge_helper_reset_crtc() [1] the sn65dsi83_reset_pipe() function would no longer return an error code, so the early exit was then a no-op, and even on sn65dsi83_reset_pipe() failure, enable_irq() has been called. When drm_bridge_enter()/drm_bridge_exit() resource protection was added, the drm_bridge_exit() incidentally was always called, which is the correct approach. But only because the early exit in sn65dsi83_reset_pipe() was never hit because sn65dsi83_reset_pipe() always returns 0. In order get back to a situation where enable_irq() is not called on sn65dsi83_reset_pipe() failure, which should help protect against irq storms, we need to reintroduce a non-zero return value from sn65dsi83_reset_pipe() on error, and fix sn65dsi83_reset_work() so that we always exit the DRM bridge critical section with drm_bridge_exit(). [1] commit e17fadff7ab9 ("drm/bridge: ti-sn65dsi83: Switch to drm_bridge_helper_reset_crtc") [2] commit d2e8d1bc840b ("drm/bridge: ti-sn65dsi83: protect device resources on unplug") Fixes: e17fadff7ab9 ("drm/bridge: ti-sn65dsi83: Switch to drm_bridge_helper_reset_crtc") Cc: stable@vger.kernel.org Signed-off-by: Esben Haabendal Reviewed-by: Herve Codina Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260831-ti-sn65dsi83-fixes-v5-1-e712765d6c4f@geanix.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index f9fdbf48c6b3..526826ba4524 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -403,7 +403,7 @@ static int sn65dsi83_reset_pipe(struct sn65dsi83 *sn65dsi83) drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); - return 0; + return err; } static void sn65dsi83_reset_work(struct work_struct *ws) @@ -419,11 +419,13 @@ static void sn65dsi83_reset_work(struct work_struct *ws) ret = sn65dsi83_reset_pipe(ctx); if (ret) { dev_err(ctx->dev, "reset pipe failed %pe\n", ERR_PTR(ret)); - return; + goto bridge_exit; } + if (ctx->irq) enable_irq(ctx->irq); +bridge_exit: drm_bridge_exit(idx); } From 20fce5b34b21a995839743b4917a1edd2fd503ba Mon Sep 17 00:00:00 2001 From: Varun Gupta Date: Mon, 7 Sep 2026 10:30:12 +0530 Subject: [PATCH 0664/1317] drm/xe: Guard page-fault worker with runtime PM check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During VM teardown, the VM's runtime PM reference is dropped asynchronously, allowing the device to autosuspend while stale page faults belonging to the now-dead VM are still queued. When the page-fault worker later tries to ack one of these, it calls into guc_ct_send_locked() on an already-suspended device, tripping:   Assertion `!xe_pm_runtime_suspended(xe)` failed!   WARNING at xe_device.c:1267 xe_device_assert_mem_access+0x11c/0x140 [xe] A live VM/exec queue always holds a PM reference while it has outstanding work, so if the device is suspended at ack time, the owning context is already gone and the fault is stale. Take a runtime PM reference across the entire pagefault queue worker to safely deliver acks for torn-down VMs. v3: - Move PM ref to the generic xe_pagefault_queue_work using guard(xe_pm_runtime)(xe) instead of tracking it in the GuC backend(Matt Brost). v2: - Hold PM ref across the entire batch (begin/end) instead of per-ack. This prevents the device from autosuspending mid-batch, which would leave write_only acks written but the end flush skipped, and skip counter++, desyncing the cadence check.(Himal) - Add a comment explaining stale faults.(Himal) Fixes: f289f7807119 ("drm/xe: Add xe_guc_pagefault layer") Signed-off-by: Varun Gupta Reviewed-by: Matthew Brost Reviewed-by: Tejas Upadhyay Link: https://patch.msgid.link/20260907050011.497181-2-varun.gupta@intel.com Signed-off-by: Tejas Upadhyay (cherry picked from commit fcc2431d2213dc4d04250c4f1ae87d9c3ae0d455) Signed-off-by: Rodrigo Vivi [Rodrigo: Added xe_device struct for compatibility while cherry-picking] --- drivers/gpu/drm/xe/xe_pagefault.c | 10 ++++++++++ drivers/gpu/drm/xe/xe_pagefault_types.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_pagefault.c b/drivers/gpu/drm/xe/xe_pagefault.c index dbf8f71d3328..a4986df8328d 100644 --- a/drivers/gpu/drm/xe/xe_pagefault.c +++ b/drivers/gpu/drm/xe/xe_pagefault.c @@ -16,6 +16,7 @@ #include "xe_hw_engine.h" #include "xe_pagefault.h" #include "xe_pagefault_types.h" +#include "xe_pm.h" #include "xe_svm.h" #include "xe_trace_bo.h" #include "xe_vm.h" @@ -292,9 +293,17 @@ static void xe_pagefault_queue_work(struct work_struct *w) { struct xe_pagefault_queue *pf_queue = container_of(w, typeof(*pf_queue), worker); + struct xe_device *xe = pf_queue->xe; struct xe_pagefault pf; unsigned long threshold; + /* + * A live VM holds a PM reference, but a torn-down VM does not. + * Guard the entire worker loop to safely drain stale faults and + * prevent autosuspends from desyncing batched CT flushes. + */ + guard(xe_pm_runtime)(xe); + #define USM_QUEUE_MAX_RUNTIME_MS 20 threshold = jiffies + msecs_to_jiffies(USM_QUEUE_MAX_RUNTIME_MS); @@ -365,6 +374,7 @@ static int xe_pagefault_queue_init(struct xe_device *xe, drm_dbg(&xe->drm, "xe_pagefault_entry_size=%d, total_num_eus=%d, pf_queue->size=%u", xe_pagefault_entry_size(), total_num_eus, pf_queue->size); + pf_queue->xe = xe; spin_lock_init(&pf_queue->lock); INIT_WORK(&pf_queue->worker, xe_pagefault_queue_work); diff --git a/drivers/gpu/drm/xe/xe_pagefault_types.h b/drivers/gpu/drm/xe/xe_pagefault_types.h index c4ee625b93dd..f63a12aa0d4f 100644 --- a/drivers/gpu/drm/xe/xe_pagefault_types.h +++ b/drivers/gpu/drm/xe/xe_pagefault_types.h @@ -8,6 +8,7 @@ #include +struct xe_device; struct xe_gt; struct xe_pagefault; @@ -118,6 +119,8 @@ struct xe_pagefault { * queue to absorb the device’s worst-case number of outstanding faults. */ struct xe_pagefault_queue { + /** @xe: Back-pointer to the Xe device */ + struct xe_device *xe; /** * @data: Data in queue containing struct xe_pagefault, protected by * @lock From f5fcf7e638b904397ec0f66d3ea6766ef0cfe25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Thu, 3 Sep 2026 13:45:52 +0200 Subject: [PATCH 0665/1317] drm/xe: Flush LSC untyped L1 dataport cache after rcs/ccs batches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit emit_render_cache_flush() sets PIPE_CONTROL0_HDC_PIPELINE_FLUSH to flush the L2/HDC data cache before fence signalling, but it never requests a flush of the LSC untyped L1 data cache via the 'Untyped Data-Port Cache Flush Enable' bit in PIPE_CONTROL DWord0[11]. Per the Bspec, in 3D pipeline mode HDC Pipeline Flush is documented to also flush/invalidate the untyped L1 cache, but only depending on how HDC_CHICKEN0[13:11] is programmed. Starting with MTL, this coupling between HDC Pipeline Flush and the untyped L1 cache flush no longer holds in practice, regardless of how HDC_CHICKEN0 is programmed, so relying on it is not safe on newer platforms such as BMG. Mesa's Vulkan driver (anv) has been assuming the kernel flushes both caches between submissions, and hit user-visible corruption in apps such as Llama.cpp because of this gap; it now works around it by flushing both caches again from userspace at the end of every command buffer. Correctness between submissions on the same queue is userspace's responsibility and belongs in Mesa, not the kernel. However, for security we must ensure stale data can't leak through the untyped L1 dataport cache once memory is reclaimed or evicted, which requires the KMD to flush it before releasing memory for reuse. Prior to MTL, HDC_CHICKEN0 could be programmed (as already done for DG2 via Wa_22010960976/Wa_14013347512) to reliably keep HDC Pipeline Flush coupled to the untyped L1 cache flush, so those platforms are unaffected. Mesa's own anv driver found that on MTL the HW disconnected the two independently of how HDC_CHICKEN0 is programmed, and could not bring the old behavior back even by writing the register by hand; see Mesa commit 7c2ff46a4fc3 ("anv: don't prevent L1 untyped cache flush in 3D mode"). The kernel can't reliably request the flush from the CS on MTL either, so restrict the new PIPE_CONTROL bit to GRAPHICS_VERx100 >= 2000 (Xe2 and later), where it can be relied on. Explicitly set PIPE_CONTROL0_UNTYPED_DATAPORT_CACHE_FLUSH together with PIPE_CONTROL0_HDC_PIPELINE_FLUSH in emit_render_cache_flush() on Xe2 and later, so the L1 data cache is known clean before memory is released for reuse, without depending on undocumented platform-specific HDC_CHICKEN0 behavior. Bspec: 56551 Link: https://gitlab.freedesktop.org/mesa/mesa/-/commit/7c2ff46a4fc3e537573ac9503057e0cd29b6fff3 Fixes: 9f8f93bee3ef ("drm/xe: Emit a render cache flush after each rcs/ccs batch") Reported-by: Lionel Landwerlin Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/8909 Cc: José Roberto de Souza Cc: intel-xe@lists.freedesktop.org Cc: # v6.8+ Assisted-by: GitHub_Copilot:claude-sonnet-5 Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260903114552.48634-1-thomas.hellstrom@linux.intel.com (cherry picked from commit 434514b6fe731e873808297c268fc52cdf4a1ce6) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/instructions/xe_gpu_commands.h | 1 + drivers/gpu/drm/xe/xe_ring_ops.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/gpu/drm/xe/instructions/xe_gpu_commands.h b/drivers/gpu/drm/xe/instructions/xe_gpu_commands.h index 18d0fde8c98f..faf8d7e2c5c1 100644 --- a/drivers/gpu/drm/xe/instructions/xe_gpu_commands.h +++ b/drivers/gpu/drm/xe/instructions/xe_gpu_commands.h @@ -46,6 +46,7 @@ #define GFX_OP_PIPE_CONTROL(len) ((0x3<<29)|(0x3<<27)|(0x2<<24)|((len)-2)) #define PIPE_CONTROL0_QUEUE_DRAIN_MODE BIT(12) +#define PIPE_CONTROL0_UNTYPED_DATAPORT_CACHE_FLUSH BIT(11) /* gen12 */ #define PIPE_CONTROL0_L3_READ_ONLY_CACHE_INVALIDATE BIT(10) /* gen12 */ #define PIPE_CONTROL0_HDC_PIPELINE_FLUSH BIT(9) /* gen12 */ diff --git a/drivers/gpu/drm/xe/xe_ring_ops.c b/drivers/gpu/drm/xe/xe_ring_ops.c index 39a670e91ba7..08b4a4283e96 100644 --- a/drivers/gpu/drm/xe/xe_ring_ops.c +++ b/drivers/gpu/drm/xe/xe_ring_ops.c @@ -212,6 +212,7 @@ static int emit_render_cache_flush(struct xe_sched_job *job, u32 *dw, int i) { struct xe_exec_queue *q = job->q; struct xe_gt *gt = q->gt; + struct xe_device *xe = gt_to_xe(gt); bool lacks_render = !(gt->info.engine_mask & XE_HW_ENGINE_RCS_MASK); u32 flags0, flags1; @@ -220,6 +221,16 @@ static int emit_render_cache_flush(struct xe_sched_job *job, u32 *dw, int i) LRC_PPHWSP_FLUSH_INVAL_SCRATCH_ADDR, 0); flags0 = PIPE_CONTROL0_HDC_PIPELINE_FLUSH; + /* + * Prior to MTL, HDC Pipeline Flush reliably also flushes the LSC + * untyped L1 dataport cache, provided HDC_CHICKEN0 is programmed + * correctly. Starting with MTL that coupling no longer holds + * regardless of how HDC_CHICKEN0 is programmed, but explicitly + * requesting the flush via PIPE_CONTROL is itself only reliable + * from Xe2 onward, so only gate it in on Xe2+. + */ + if (GRAPHICS_VERx100(xe) >= 2000) + flags0 |= PIPE_CONTROL0_UNTYPED_DATAPORT_CACHE_FLUSH; flags1 = (PIPE_CONTROL_TILE_CACHE_FLUSH | PIPE_CONTROL_RENDER_TARGET_CACHE_FLUSH | PIPE_CONTROL_DEPTH_CACHE_FLUSH | From 6028b543884f8735e057ec9eea4908cd61cab230 Mon Sep 17 00:00:00 2001 From: Gabriel Krisman Bertazi Date: Wed, 2 Sep 2026 20:00:41 -0300 Subject: [PATCH 0666/1317] io_uring/net: don't overconsume buffers when using MSG_TRUNC When a recv/recvmsg is issued with MSG_TRUNC and the incoming packet is larger than the provided buffer, the net layer returns the full length of the packet rather than the number of bytes actually copied into the buffer. As a result, io_uring advances more of the provided buffer ring than was actually filled. Use the actual filled region size to consume the buffer, but still return the full size to preserve MSG_TRUNC semantics. Take care with multishot, because that seems to already truncate the consumption based on the available payload size. This was reported in https://github.com/axboe/liburing/issues/1619. Fixes: ae98dbf43d75 ("io_uring/kbuf: add support for incremental buffer consumption") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260728191454.1850326-1-krisman@suse.de Signed-off-by: Gabriel Krisman Bertazi Link: https://patch.msgid.link/20260902230041.1320658-3-krisman@suse.de [axboe: fold in size_t unsigned fix] Signed-off-by: Jens Axboe --- io_uring/net.c | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/io_uring/net.c b/io_uring/net.c index 647156c9331a..050ed274170a 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -853,7 +853,7 @@ int io_recvmsg_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) static inline bool io_recv_finish(struct io_kiocb *req, struct io_async_msghdr *kmsg, struct io_br_sel *sel, bool mshot_finished, - unsigned issue_flags) + unsigned issue_flags, int consumed) { struct io_sr_msg *sr = io_kiocb_to_cmd(req, struct io_sr_msg); unsigned int cflags = 0; @@ -877,7 +877,7 @@ static inline bool io_recv_finish(struct io_kiocb *req, if (sr->flags & IORING_RECVSEND_BUNDLE) { size_t this_ret = sel->val - sr->done_io; - cflags |= io_put_kbufs(req, this_ret, sel->buf_list, io_bundle_nbufs(kmsg, this_ret)); + cflags |= io_put_kbufs(req, consumed, sel->buf_list, io_bundle_nbufs(kmsg, consumed)); if (sr->flags & IORING_RECV_RETRY) cflags = req->cqe.flags | (cflags & CQE_F_MASK); if (sr->mshot_len && sel->val >= sr->mshot_len) @@ -899,7 +899,7 @@ static inline bool io_recv_finish(struct io_kiocb *req, return false; } } else { - cflags |= io_put_kbuf(req, sel->val, sel->buf_list); + cflags |= io_put_kbuf(req, consumed, sel->buf_list); } /* @@ -1027,6 +1027,8 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) int ret, min_ret = 0; bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK; bool mshot_finished = true; + int consumed = 0; + size_t len; sock = sock_from_file(req->file); if (unlikely(!sock)) @@ -1042,9 +1044,8 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) retry_multishot: sel.buf_list = NULL; + len = sr->len; if (io_do_buffer_select(req)) { - size_t len = sr->len; - sel = io_buffer_select(req, &len, sr->buf_group, issue_flags); if (!sel.addr) return -ENOBUFS; @@ -1065,6 +1066,7 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) if (req->flags & REQ_F_APOLL_MULTISHOT) { ret = io_recvmsg_multishot(sock, sr, kmsg, flags, &mshot_finished); + consumed = ret; } else { /* disable partial retry for recvmsg with cmsg attached */ if (flags & MSG_WAITALL && !kmsg->msg.msg_controllen) @@ -1072,6 +1074,15 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) ret = __sys_recvmsg_sock(sock, &kmsg->msg, sr->umsg, kmsg->uaddr, flags); + /* + * With MSG_TRUNC, the net layer will return the full size of + * the packet, even if we only filled part of it in the buffers. + * Adjust the returned size to consume only the real part of the + * buffer. + */ + consumed = ret; + if (ret > 0) + consumed = min_t(size_t, ret, len); } if (ret < min_ret) { @@ -1098,7 +1109,7 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) io_kbuf_recycle(req, sel.buf_list, issue_flags); sel.val = ret; - if (!io_recv_finish(req, kmsg, &sel, mshot_finished, issue_flags)) + if (!io_recv_finish(req, kmsg, &sel, mshot_finished, issue_flags, consumed)) goto retry_multishot; return sel.val; @@ -1185,9 +1196,10 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) struct io_br_sel sel; struct socket *sock; unsigned flags; - int ret, min_ret = 0; + int ret, min_ret = 0, consumed = 0; bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK; bool mshot_finished; + size_t len = 0; sock = sock_from_file(req->file); if (unlikely(!sock)) @@ -1215,6 +1227,7 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) retry_multishot: sel.buf_list = NULL; + len = sr->len; if (io_do_buffer_select(req)) { sel.val = sr->len; ret = io_recv_buf_select(req, kmsg, &sel, issue_flags); @@ -1222,6 +1235,7 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) kmsg->msg.msg_inq = -1; goto out_free; } + len = ret; sr->buf = NULL; } @@ -1252,6 +1266,17 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) } mshot_finished = ret <= 0; + + /* + * With MSG_TRUNC, the net layer will return the full size of + * the packet, even if we only filled part of it in the buffers. + * Adjust the returned size to consume only the real part of the + * buffer. + */ + consumed = ret; + if (ret > 0) + consumed = min_t(size_t, ret, len); + if (ret > 0) ret += sr->done_io; else if (sr->done_io) @@ -1260,7 +1285,7 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) io_kbuf_recycle(req, sel.buf_list, issue_flags); sel.val = ret; - if (!io_recv_finish(req, kmsg, &sel, mshot_finished, issue_flags)) + if (!io_recv_finish(req, kmsg, &sel, mshot_finished, issue_flags, consumed)) goto retry_multishot; return sel.val; From 47ccc3f1c615a46c25cbf7f3ae60df30b40eb2e6 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Wed, 2 Sep 2026 15:01:59 -0600 Subject: [PATCH 0667/1317] io_uring/rw: keep CQE flags on iopoll requests when adding kbuf flags io_do_iopoll() assigns the result of io_put_kbuf() to the request's CQE flags upon completion. This overwrites any CQE flags that may have been set by the opcode-specific layer. (For example, if __io_uring_cmd_done() had set IORING_CQE_F_32, it would be cleared.) Switch the = to an |= so the kbuf flags are added to the existing CQE flags rather than replacing them. io_req_rw_complete() does the same with the io_put_kbuf() result. Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED") Reported-by: sashiko-bot@kernel.org Link: https://sashiko.dev/#/message/20260827191705.D53C91F000E9%40smtp.kernel.org Signed-off-by: Caleb Sander Mateos Reviewed-by: Anuj Gupta Link: https://patch.msgid.link/20260902210200.2336720-1-csander@purestorage.com Signed-off-by: Jens Axboe --- io_uring/rw.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io_uring/rw.c b/io_uring/rw.c index 3e22f294bdf2..432820f86251 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -880,6 +880,7 @@ static int io_rw_init_file(struct io_kiocb *req, fmode_t mode, int rw_type) kiocb->private = NULL; kiocb->ki_flags |= IOCB_HIPRI; req->iopoll_completed = 0; + req->cqe.flags = 0; if (ctx->flags & IORING_SETUP_HYBRID_IOPOLL) { /* make sure every req only blocks once*/ req->flags &= ~REQ_F_IOPOLL_STATE; @@ -1382,7 +1383,7 @@ int io_do_iopoll(struct io_ring_ctx *ctx, bool force_nonspin) list_del(&req->iopoll_node); wq_list_add_tail(&req->comp_list, &ctx->submit_state.compl_reqs); nr_events++; - req->cqe.flags = io_put_kbuf(req, max(req->cqe.res, 0), NULL); + req->cqe.flags |= io_put_kbuf(req, max(req->cqe.res, 0), NULL); if (!io_is_uring_cmd(req)) io_req_rw_cleanup(req, 0); } From 3fc9f0558c95152b14d31badd5b27e5978bfb204 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 9 Sep 2026 07:41:54 -0700 Subject: [PATCH 0668/1317] drm/ci: Update xfails for kms_cursor_legacy regression These four started failing with some change not merged thru the msm tree. Update xfails to reflect reality. Signed-off-by: Rob Clark --- drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt b/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt index 4546363447ff..0f3d85e4845e 100644 --- a/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt +++ b/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt @@ -7,3 +7,10 @@ kms_hdmi_inject@inject-4k,Fail kms_lease@lease-uevent,Fail msm/msm_mapping@memptrs,Fail msm/msm_mapping@ring,Fail + +# Started failing with v7.3-rc2 backmerge +# https://gitlab.freedesktop.org/drm/msm/-/work_items/104 +kms_cursor_legacy@single-move,Fail +kms_cursor_legacy@torture-bo,Fail +kms_cursor_legacy@forked-bo,Fail +kms_cursor_legacy@torture-move,Fail From e2d5b01f878d76bd1142e512a0b979a1d3cd0abf Mon Sep 17 00:00:00 2001 From: Meijing Zhao Date: Wed, 2 Sep 2026 15:59:44 +0800 Subject: [PATCH 0669/1317] mm: memblock: show all region flags in debugfs Commit 493f349e38d0 ("memblock: Add flags and nid info in memblock debugfs") made memblock_debug_show() stop after finding the first set flag. A memblock region can carry multiple flags, so the remaining flags are hidden from debugfs. Walk all bits in the region flags and print every set flag separated by "|". Keep walking beyond flagname[] so that a set flag without a known name is reported as UNKNOWN rather than silently ignored. Fixes: 493f349e38d0 ("memblock: Add flags and nid info in memblock debugfs") Signed-off-by: Meijing Zhao Link: https://patch.msgid.link/20260902075944.3742866-1-zhaomeijing100@gmail.com Signed-off-by: Mike Rapoport (Microsoft) --- mm/memblock.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mm/memblock.c b/mm/memblock.c index 9ce86349a29f..021db49eb7fc 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -2908,14 +2908,18 @@ static int memblock_debug_show(struct seq_file *m, void *private) else seq_printf(m, "%4c ", 'x'); if (reg->flags) { - for (j = 0; j < count; j++) { - if (reg->flags & (1U << j)) { - seq_printf(m, "%s\n", flagname[j]); - break; - } + unsigned int flags = reg->flags; + bool first = true; + + for (j = 0; flags; j++, flags >>= 1) { + if (!(flags & 1)) + continue; + if (!first) + seq_putc(m, '|'); + seq_puts(m, j < count ? flagname[j] : "UNKNOWN"); + first = false; } - if (j == count) - seq_printf(m, "%s\n", "UNKNOWN"); + seq_putc(m, '\n'); } else { seq_printf(m, "%s\n", "NONE"); } From 6e33dc90df108c76899dc544a42d17adcba61668 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Mon, 31 Aug 2026 13:21:43 +0300 Subject: [PATCH 0670/1317] MAINTAINERS: update memblock tree URLs memblock tree moved into mm/ namespace at git.kernel.org. Update the T: entries for memblock to match it. Link: https://patch.msgid.link/20260831102143.69265-1-rppt@kernel.org Signed-off-by: Mike Rapoport (Microsoft) --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..1731d197c33c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17036,8 +17036,8 @@ MEMBLOCK AND MEMORY MANAGEMENT INITIALIZATION M: Mike Rapoport L: linux-mm@kvack.org S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git for-next -T: git git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git fixes +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mm/memblock.git for-next +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mm/memblock.git fixes F: Documentation/core-api/boot-time-mm.rst F: include/linux/kho/abi/memblock.h F: include/linux/memblock.h From e4a62833adff6ef0fe7c0b90393204fe3c26b5c5 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 9 Sep 2026 12:08:08 +0800 Subject: [PATCH 0671/1317] bpf: Disallow bpf_skb_pull_data() for LWT_SEG6LOCAL An LWT_SEG6LOCAL program can invalidate its cached SRH with bpf_lwt_seg6_adjust_srh() and then call bpf_skb_pull_data(). The latter may reallocate skb->head, leaving the per-CPU SRH pointer dangling. Post-program SRH validation then writes through that pointer. Disallow bpf_skb_pull_data() for LWT_SEG6LOCAL programs so the verifier rejects this unsafe helper combination. Other LWT program types continue to expose the helper through lwt_out_func_proto(). Fixes: 004d4b274e2a ("ipv6: sr: Add seg6local action End.BPF") Reported-by: co+adfca3e91be95776@bugs.sh Suggested-by: Alexei Starovoitov Signed-off-by: Weiming Shi Signed-off-by: Daniel Borkmann Reviewed-by: Emil Tsalapatis Closes: https://lore.kernel.org/all/GCy0KRM2IcQGoJQTjJEU9D0maBxXzEDHuQpq@bugs.sh/ Link: https://lore.kernel.org/bpf/DL9COXZQXX4V.1FN45QO2Q77ZH@gmail.com/ Link: https://lore.kernel.org/bpf/20260909040807.3885815-2-bestswngs@gmail.com --- net/core/filter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/filter.c b/net/core/filter.c index 8513167a858a..2a84f9d01131 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -9044,6 +9044,8 @@ static const struct bpf_func_proto * lwt_seg6local_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { switch (func_id) { + case BPF_FUNC_skb_pull_data: + return NULL; #if IS_ENABLED(CONFIG_IPV6_SEG6_BPF) case BPF_FUNC_lwt_seg6_store_bytes: return &bpf_lwt_seg6_store_bytes_proto; From 5be081b83abd3f17d908953b4bb77279f5a149e3 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 9 Sep 2026 00:50:47 +0800 Subject: [PATCH 0672/1317] net: dsa: tag_brcm: legacy FCS: request needed tailroom The legacy FCS tagger calculates the CRC over skb->len bytes starting at skb->data. When a nonlinear skb reaches the tagger, this reads past the linear head into unrelated slab memory. The tagger appends an Ethernet FCS but does not declare that tailroom. As a result, DSA leaves NETIF_F_SG and NETIF_F_FRAGLIST enabled on the user port, and nonlinear skbs can reach the CRC calculation. Declare the required tailroom. DSA will then clear those features and the networking core will linearize skbs before the tagger runs. A KASAN-enabled dsa_loop test using this tagger reports: BUG: KASAN: slab-out-of-bounds in crc32_le Read of size 1 at addr ffff8880397086c0 by task exp/135 Call Trace: crc32_le (lib/crc/crc32-main.c:38) brcm_leg_fcs_tag_xmit (net/dsa/tag_brcm.c:343) dsa_user_xmit (net/dsa/user.c:942) dev_hard_start_xmit (net/core/dev.c:3937) __dev_queue_xmit (net/core/dev.c:4926) packet_sendmsg (net/packet/af_packet.c:3110) __sys_sendto (net/socket.c:2281) The buggy address belongs to the object at ffff888039708400 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 0 bytes to the right of allocated 704-byte region [ffff888039708400, ffff8880397086c0) Fixes: ef07df397a62 ("net: dsa: tag_brcm: add support for legacy FCS tags") Cc: stable@vger.kernel.org Reported-by: co+28eef7d8af9428e6@bugs.sh Closes: https://lore.kernel.org/all/jH6u350kaBRuqklDjd3k3BW4nWzp0tYRjq3p%40bugs.sh/ Signed-off-by: Weiming Shi Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260908165047.2786340-1-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- net/dsa/tag_brcm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/dsa/tag_brcm.c b/net/dsa/tag_brcm.c index 411e3b57d16a..b7c49822ca88 100644 --- a/net/dsa/tag_brcm.c +++ b/net/dsa/tag_brcm.c @@ -373,6 +373,7 @@ static const struct dsa_device_ops brcm_legacy_fcs_netdev_ops = { .xmit = brcm_leg_fcs_tag_xmit, .rcv = brcm_leg_tag_rcv, .needed_headroom = BRCM_LEG_TAG_LEN, + .needed_tailroom = ETH_FCS_LEN, }; DSA_TAG_DRIVER(brcm_legacy_fcs_netdev_ops); From e2ab913f68c7d11e2561b8a8ad0b87ffefcad667 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:06 +0200 Subject: [PATCH 0673/1317] mptcp: do not reschedule the RTX timer for fallback sockets On fallback socket the retrans timer is a quite convoluted no-op, but currently nothing prevents the MPTCP core to keep rescheduling it. Additionally gate RTX timer reset to the msk not being fallen back to TCP yet. To avoid adding multiple tests in fast-path, use a new flags bit for such condition. The RTX enable bit is clear at close time and set before the msk could start retransmitting, with a couple of caveats: - passive sockets inherit the bit from the listener msk; set the bit on such socket to avoid flipping it in the fast-path, even if the listener will obviously never retransmit. - while fastopening (MPTFO), mptcp_sendmsg_fastopen still ends-up calling mptcp_connect via tcp_sendmsg_fastopen -> __inet_stream_connect(ssk->sk_socket), and the first subflow's sk_socket points to the msk one. Fixes: b51f9b80c032 ("mptcp: introduce MPTCP retransmission timer") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-1-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 13 ++++++++++--- net/mptcp/protocol.h | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index e1f08f71cdb1..be59651e708e 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -95,6 +95,7 @@ bool __mptcp_try_fallback(struct mptcp_sock *msk, int fb_mib) msk->allow_subflows = false; set_bit(MPTCP_FALLBACK_DONE, &msk->flags); + clear_bit(MPTCP_RTX_ENABLED, &msk->flags); __MPTCP_INC_STATS(net, fb_mib); spin_unlock_bh(&msk->fallback_lock); return true; @@ -1084,13 +1085,14 @@ static bool mptcp_rtx_timer_pending(struct sock *sk) static void mptcp_reset_rtx_timer(struct sock *sk) { + struct mptcp_sock *msk = mptcp_sk(sk); unsigned long tout; - /* prevent rescheduling on close */ - if (unlikely(inet_sk_state_load(sk) == TCP_CLOSE)) + /* Prevent rescheduling on close and in case of fallback. */ + if (!test_bit(MPTCP_RTX_ENABLED, &msk->flags)) return; - tout = mptcp_sk(sk)->timer_ival; + tout = msk->timer_ival; sk_reset_timer(sk, &sk->mptcp_retransmit_timer, jiffies + tout); } @@ -3323,6 +3325,9 @@ void mptcp_set_state(struct sock *sk, int state) * transition from TCP_SYN_RECV to TCP_CLOSE_WAIT. */ break; + case TCP_CLOSE: + clear_bit(MPTCP_RTX_ENABLED, &mptcp_sk(sk)->flags); + fallthrough; default: if (oldstate == TCP_ESTABLISHED || oldstate == TCP_CLOSE_WAIT) MPTCP_DEC_STATS(sock_net(sk), MPTCP_MIB_CURRESTAB); @@ -4141,6 +4146,7 @@ static int mptcp_connect(struct sock *sk, struct sockaddr_unsized *uaddr, if (IS_ERR(ssk)) return PTR_ERR(ssk); + set_bit(MPTCP_RTX_ENABLED, &msk->flags); mptcp_set_state(sk, TCP_SYN_SENT); subflow = mptcp_subflow_ctx(ssk); #ifdef CONFIG_TCP_MD5SIG @@ -4288,6 +4294,7 @@ static int mptcp_listen(struct socket *sock, int backlog) goto unlock; } + set_bit(MPTCP_RTX_ENABLED, &msk->flags); mptcp_set_state(sk, TCP_LISTEN); sock_set_flag(sk, SOCK_RCU_FREE); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 87ccb84e9927..2b4c27426477 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -116,6 +116,7 @@ #define MPTCP_WORK_RTX 1 #define MPTCP_FALLBACK_DONE 2 #define MPTCP_WORK_CLOSE_SUBFLOW 3 +#define MPTCP_RTX_ENABLED 4 /* MPTCP socket release cb flags */ #define MPTCP_PUSH_PENDING 1 From 29f641951be0d91036d77edf677807f1447dbe65 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:07 +0200 Subject: [PATCH 0674/1317] mptcp: subflow: no need to copy thmac during ulp_clone 'thmac' is not used after that point. Indeed, subflow_ulp_clone() is called when the request on the passive side is over, so when the truncated HMAC is no longer needed. Note that in case of SYN cookies, thmac will not be initialised. So better to remove it to avoid a warning from debug tools like KMSAN for reading uninitialised data. Fixes: f296234c98a8 ("mptcp: Add handling of incoming MP_JOIN requests") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-2-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/subflow.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index af81ad5e699d..01db7edce18a 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -2084,7 +2084,6 @@ static void subflow_ulp_clone(const struct request_sock *req, new_ctx->request_bkup = subflow_req->request_bkup; WRITE_ONCE(new_ctx->remote_id, subflow_req->remote_id); new_ctx->token = subflow_req->token; - new_ctx->thmac = subflow_req->thmac; /* the subflow req id is valid, fetched via subflow_check_req() * and subflow_token_join_request() From b76c0e28b392620dfbaf92cdeedbf115820b44cb Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:08 +0200 Subject: [PATCH 0675/1317] mptcp: syncookies: remember the request backup flag Instead of using an uninitialised bit when copying the info in subflow_ulp_clone(). To fix this, no need to extend the join_entry structure: backup is coming from struct mptcp_subflow_request_sock, only one bit. Do the same here by using one bit for both. Fixes: efd340bf3d77 ("mptcp: distinguish rcv vs sent backup flag in requests") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-3-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/syncookies.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/mptcp/syncookies.c b/net/mptcp/syncookies.c index b5cac5701122..9474706641c1 100644 --- a/net/mptcp/syncookies.c +++ b/net/mptcp/syncookies.c @@ -26,7 +26,8 @@ struct join_entry { u32 local_nonce; u8 join_id; u8 local_id; - u8 backup; + u8 backup:1, + request_bkup:1; u8 valid; }; @@ -63,6 +64,7 @@ static void mptcp_join_store_state(struct join_entry *entry, entry->remote_nonce = subflow_req->remote_nonce; entry->local_nonce = subflow_req->local_nonce; entry->backup = subflow_req->backup; + entry->request_bkup = subflow_req->request_bkup; entry->join_id = subflow_req->remote_id; entry->local_id = subflow_req->local_id; entry->valid = 1; @@ -117,6 +119,7 @@ bool mptcp_token_join_cookie_init_state(struct mptcp_subflow_request_sock *subfl subflow_req->remote_nonce = e->remote_nonce; subflow_req->local_nonce = e->local_nonce; subflow_req->backup = e->backup; + subflow_req->request_bkup = e->request_bkup; subflow_req->remote_id = e->join_id; subflow_req->local_id = e->local_id; subflow_req->token = e->token; From 2ac7d6e620764f1fc79eb4edd3610a7a661981ca Mon Sep 17 00:00:00 2001 From: Kalpan Jani Date: Tue, 8 Sep 2026 16:07:09 +0200 Subject: [PATCH 0676/1317] mptcp: pm: kernel: drop pending ADD_ADDR when removing ID0 The in-kernel MPTCP path manager can leave a stale ADD_ADDR announcement entry alive when removing the id 0 endpoint. This happens because the id 0 removal path does not tear down pending announcements, unlike the non-zero id path. When the PM later reselects id 0 after adding another signal endpoint, it finds the stale anno_list entry and hits WARN_ON_ONCE(mptcp_pm_is_kernel()) in mptcp_pm_announced_alloc(). Root cause: asymmetry between removal paths. - Non-zero id path: mptcp_nl_remove_subflow_and_signal_addr() calls mptcp_pm_remove_announced() to clean up. - Id 0 path: mptcp_nl_remove_id_zero_address() skips cleanup entirely. Fix by making the id 0 path symmetric: call mptcp_pm_announced_remove() and decrement add_addr_signaled before queuing the RM_ADDR. Subtle detail: signal endpoints are stored in anno_list with port 0, but msk_local carries the connection's local port. In other words, entries linked to ID0 paths should have port == 0. A follow-up patch will ensure that. mptcp_pm_announced_remove() uses use_port=true for comparison. So clear the port before the lookup. Fixes: 740d798e8767 ("mptcp: remove id 0 address") Cc: stable@vger.kernel.org Reported-by: syzbot+55c2a5c871441261ed14@syzkaller.appspotmail.com Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/620 Suggested-by: Tao Cui Signed-off-by: Kalpan Jani Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-4-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm_kernel.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/mptcp/pm_kernel.c b/net/mptcp/pm_kernel.c index 424f1a7f9248..1a7750813235 100644 --- a/net/mptcp/pm_kernel.c +++ b/net/mptcp/pm_kernel.c @@ -1137,6 +1137,8 @@ static int mptcp_nl_remove_id_zero_address(struct net *net, while ((msk = mptcp_token_iter_next(net, &s_slot, &s_num)) != NULL) { struct sock *sk = (struct sock *)msk; struct mptcp_addr_info msk_local; + struct mptcp_addr_info anno_addr; + bool announced; if (list_empty(&msk->conn_list) || mptcp_pm_is_userspace(msk)) goto next; @@ -1146,7 +1148,13 @@ static int mptcp_nl_remove_id_zero_address(struct net *net, goto next; lock_sock(sk); + /* Drop a possibly pending ADD_ADDR for this address. */ + anno_addr = msk_local; + anno_addr.port = 0; + announced = mptcp_pm_announced_remove(msk, &anno_addr); spin_lock_bh(&msk->pm.lock); + if (announced) + msk->pm.add_addr_signaled--; mptcp_pm_remove_addr(msk, &list); mptcp_pm_rm_subflow(msk, &list); __mark_subflow_endp_available(msk, 0); From ab36b1a80942c78ddb04d006ff38aa7ed3ec0e5e Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:10 +0200 Subject: [PATCH 0677/1317] mptcp: options: handle MPC data + csum reqd + no csum Before this modification, a remote peer could send an MP_CAPABLE with data, with the checksum flag set, but without adding the actual 2 bytes of checksum. As a result, uninitialised bytes could be used for the 'csum' field. That was not a critical issue, because this 'csum' field is only used to compare with the expected one, if previously negotiated in the 3WHS. Worst case, the checksum is likely wrong, a fallback is done without a reject if the negotiation was done earlier. That's OK. Yet, better to take the expected path with this case: only look at the checksum flag for MP_CAPABLEs not carrying a data-len. Such packet can be seen as a 3rd or 4th ACK. The RFC8684 mentions [1] that the 3rd packet should have the checksum flag set. When an MPC + ACK contains data, the checksum flag is redundant with the checksum field. It is not clear what should be done for the 4th ACK, nor if the flag has to be set if the checksum field is set. Therefore, it seems fine to only look at the presence of the checksum field, not to break the interaction with stacks that were not setting both. Note that linked to this checksum flag on the 3rd ACK, with the current implementation, we can have a situation where the SYN packets have no checksum flag, but the 3rd ACK has one, and this is the one that will be taken into account. First, that's clearly not directly linked to this patch, but Clashiko forced us to look at that. At the end, that seems fine to act like that: yes that's not how the negotiation should work, but being flexible without introducing side effects is also fine: fixing this would mean increasing the complexity, and that's not worth it. Fixes: 208e8f66926c ("mptcp: receive checksum for MP_CAPABLE with data") Cc: stable@vger.kernel.org Link: https://datatracker.ietf.org/doc/html/rfc8684#section-3.1-23 [1] Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-0-b8f496d71664%40kernel.org?part=1 Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-5-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index b8318e030138..92f27b9e087a 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -93,7 +93,8 @@ static void mptcp_parse_option(const struct sk_buff *skb, * In other words, the only way for checksums not to be used * is if both hosts in their SYNs set A=0." */ - if (flags & MPTCP_CAP_CHECKSUM_REQD) + if ((flags & MPTCP_CAP_CHECKSUM_REQD) && + opsize < TCPOLEN_MPTCP_MPC_ACK_DATA) mp_opt->suboptions |= OPTION_MPTCP_CSUMREQD; mp_opt->deny_join_id0 = !!(flags & MPTCP_CAP_DENY_JOIN_ID0); From 85c580b0d8590520ae00a15c29e9fb9c99427a3e Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:11 +0200 Subject: [PATCH 0678/1317] mptcp: prevent race between disconnect() and rtx Sashiko noted that the two event can race, leading to inconsistent status. Prevent the race using the synchronous timer stop operation. Cc: stable@vger.kernel.org Fixes: b29fcfb54cd7 ("mptcp: full disconnect implementation") Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-6-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index be59651e708e..d611af2eb74f 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3588,6 +3588,7 @@ static void mptcp_destroy_common(struct mptcp_sock *msk) static int mptcp_disconnect(struct sock *sk, int flags) { + struct inet_connection_sock *icsk = inet_csk(sk); struct mptcp_sock *msk = mptcp_sk(sk); /* We are on the fastopen error path. We can't call straight into the @@ -3600,8 +3601,13 @@ static int mptcp_disconnect(struct sock *sk, int flags) mptcp_check_listen_stop(sk); mptcp_set_state(sk, TCP_CLOSE); - mptcp_stop_rtx_timer(sk); - mptcp_stop_tout_timer(sk); + /* The later subflow close can not kick again the tout timer, + * as the msk is already in closed status. + */ + msk->timer_ival = icsk->icsk_rto_min; + sk_stop_timer_sync(sk, &sk->mptcp_retransmit_timer); + icsk->icsk_mtup.probe_timestamp = 0; + sk_stop_timer_sync(sk, &icsk->mptcp_tout_timer); mptcp_pm_connection_closed(msk); From 730444f094b12052916ebd7e14fe57bc3d47bf38 Mon Sep 17 00:00:00 2001 From: Gang Yan Date: Tue, 8 Sep 2026 16:07:12 +0200 Subject: [PATCH 0679/1317] selftests: mptcp: fix an UAF in mptcp_connect.c At the end of 'sock_connect_mptcp()', it calls 'freeaddrinfo(addr)', the 'peer' pointer (which points into 'addr') remains. Later, the main loop uses this peer pointer for reconnection attempts. If the memory has been freed and reused, the address data could be overwritten, resulting in an invalid remote address. This patch keeps the addrinfo list allocated for the whole process lifetime so "peer" remains valid across reconnects; the memory will be released at exit() time. Fixes: 05be5e273c84 ("selftests: mptcp: add disconnect tests") Cc: stable@vger.kernel.org Suggested-by: Paolo Abeni Signed-off-by: Gang Yan Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-7-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_connect.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.c b/tools/testing/selftests/net/mptcp/mptcp_connect.c index ea4cb6c1bd5e..178d98d91fea 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.c +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.c @@ -381,6 +381,9 @@ static int sock_connect_mptcp(const char * const remoteaddr, hints.ai_family = pf; + /* Keep the resolved address alive for the whole execution: it is + * used again when reconnecting, and will be released at exit time. + */ xgetaddrinfo(remoteaddr, port, &hints, &addr); for (a = addr; a; a = a->ai_next) { sock = socket(a->ai_family, a->ai_socktype, proto); @@ -421,7 +424,6 @@ static int sock_connect_mptcp(const char * const remoteaddr, sock = -1; } - freeaddrinfo(addr); if (sock != -1) SOCK_TEST_TCPULP(sock, proto); return sock; From f9f0068e8813d8c10d016b030fc3a320d0b6767c Mon Sep 17 00:00:00 2001 From: Qing Luo Date: Tue, 8 Sep 2026 16:07:13 +0200 Subject: [PATCH 0680/1317] mptcp: pm: userspace: fix address ID overflow When all MPTCP address IDs (1-255) are exhausted in the userspace PM, find_next_zero_bit() returns MPTCP_PM_MAX_ADDR_ID + 1 (256). This value overflows when stored in the u8 field e->addr.id, resulting in ID 0 being stored and the entry being incorrectly added to the list. ID 0 is reserved for the initial connection in MPTCP, so this overflow can cause address conflicts. Note: the in-kernel PM already has an 'endpoints == MPTCP_PM_MAX_ADDR_ID' check in mptcp_pm_nl_append_new_local_addr() that returns -ERANGE before reaching find_next_zero_bit(), preventing this overflow. So this fix only addresses the userspace PM path. Check the find_next_zero_bit() result against MPTCP_PM_MAX_ADDR_ID and return -ENOSPC if all IDs are truly exhausted. Move the ID allocation check before the memory allocation so that the error path does not need to free the allocated entry. Fixes: 4638de5aefe5 ("mptcp: handle local addrs announced by userspace PMs") Cc: stable@vger.kernel.org Signed-off-by: Qing Luo Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-8-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm_userspace.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/net/mptcp/pm_userspace.c b/net/mptcp/pm_userspace.c index b94fbb483bf9..fab16d953dbf 100644 --- a/net/mptcp/pm_userspace.c +++ b/net/mptcp/pm_userspace.c @@ -69,6 +69,19 @@ static int mptcp_userspace_pm_append_new_local_addr(struct mptcp_sock *msk, } if (!addr_match && !id_match) { + unsigned int id; + + if (!entry->addr.id && needs_id) { + id = find_next_zero_bit(id_bitmap, + MPTCP_PM_MAX_ADDR_ID + 1, 1); + if (id > MPTCP_PM_MAX_ADDR_ID) { + ret = -ENOSPC; + goto append_err; + } + } else { + id = entry->addr.id; + } + /* Memory for the entry is allocated from the * sock option buffer. */ @@ -78,10 +91,7 @@ static int mptcp_userspace_pm_append_new_local_addr(struct mptcp_sock *msk, goto append_err; } - if (!e->addr.id && needs_id) - e->addr.id = find_next_zero_bit(id_bitmap, - MPTCP_PM_MAX_ADDR_ID + 1, - 1); + e->addr.id = id; list_add_tail_rcu(&e->list, &msk->pm.userspace_pm_local_addr_list); msk->pm.local_addr_used++; ret = e->addr.id; From f968190c0b42ea2004dc1426359a53ec365a7a37 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:14 +0200 Subject: [PATCH 0681/1317] mptcp: pm: reset retrans_time when ADD_ADDR entry is reused When an ADD_ADDR entry is reused, the timer is re-armed, because the goal is to re-announce an ADD_ADDR, and eventually retransmit it if needed. In this case, the retransmission counter should be reset as well, so the re-announced address gets its retransmissions back instead of relying on what was left before, and possibly not being able to retransmit it. Fixes: 304ab97f4c7c ("mptcp: allow ADD_ADDR reissuance by userspace PMs") Cc: stable@vger.kernel.org Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-0-b8f496d71664%40kernel.org?part=4 Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-9-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index 8b68868255c5..b0b71adefb8f 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -462,10 +462,10 @@ bool mptcp_pm_announced_alloc(struct mptcp_sock *msk, add_entry->addr = *addr; add_entry->sock = msk; - add_entry->retrans_times = 0; timer_setup(&add_entry->timer, mptcp_pm_add_addr_timer, 0); reset_timer: + add_entry->retrans_times = 0; add_entry->timer_done = false; timeout = mptcp_adjust_add_addr_timeout(msk); if (timeout) From caa4a79f74f32084ce28aee8653bc04df745970d Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:15 +0200 Subject: [PATCH 0682/1317] mptcp: remove unneeded READ_ONCE() annotation The subflow->fully_established flag is always written under the subflow socket lock. Reading such value under the same lock does not require any ONCE annotation. Fixes: 581c8cbfa934 ("mptcp: annotate data-races around subflow->fully_established") Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-10-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 4 ++-- net/mptcp/protocol.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 92f27b9e087a..196a46e7467d 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -530,7 +530,7 @@ static bool mptcp_established_options_mp(struct sock *sk, struct sk_buff *skb, return false; /* MPC/MPJ needed only on 3rd ack packet, DATA_FIN and TCP shutdown take precedence */ - if (READ_ONCE(subflow->fully_established) || snd_data_fin_enable || + if (subflow->fully_established || snd_data_fin_enable || subflow->snd_isn != TCP_SKB_CB(skb)->seq || sk->sk_state != TCP_ESTABLISHED) return false; @@ -981,7 +981,7 @@ static bool check_fully_established(struct mptcp_sock *msk, struct sock *ssk, /* here we can process OoO, in-window pkts, only in-sequence 4th ack * will make the subflow fully established */ - if (likely(READ_ONCE(subflow->fully_established))) { + if (likely(subflow->fully_established)) { /* on passive sockets, check for 3rd ack retransmission * note that msk is always set by subflow_syn_recv_sock() * for mp_join subflows diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index d611af2eb74f..302936ff456a 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3886,7 +3886,7 @@ static void schedule_3rdack_retransmission(struct sock *ssk) struct tcp_sock *tp = tcp_sk(ssk); unsigned long timeout; - if (READ_ONCE(mptcp_subflow_ctx(ssk)->fully_established)) + if (mptcp_subflow_ctx(ssk)->fully_established) return; /* reschedule with a timeout above RTT, as we must look only for drop */ From e1a56368eac18b3b4b956b794526e8713c48a0ec Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:16 +0200 Subject: [PATCH 0683/1317] selftests: mptcp: lib: dump nstat for the right test In case of errors, mptcp_lib_pr_nstat is called to dump the nstat counters, but for some tests, it was dumping the counters for all subtests, not just the current one. That's an issue for tests that don't recreate the netns for each subtest, e.g. mptcp_connect.sh. In this case, 'nstat -a' will look at the absolute counters since the creation of the netns, making debugging harder. Instead, it should dump the counters for the current test, by using the history recorded in /tmp/.nstat if available, and not using '-a' which was dumping the absolute values instead of calculating increments. While at it, rename the previous 'hist' variable to 'cache' as it was used to look at the cache, not the nstat history. Fixes: 658e53141780 ("selftests: mptcp: join: dump stats from history") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-11-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_lib.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_lib.sh b/tools/testing/selftests/net/mptcp/mptcp_lib.sh index 5ef6033775c8..da1da414c30f 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_lib.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_lib.sh @@ -108,12 +108,14 @@ mptcp_lib_pr_info() { mptcp_lib_pr_nstat() { local ns="${1}" - local hist="/tmp/${ns}.out" + local cache="/tmp/${ns}.out" + local hist="/tmp/${ns}.nstat" - if [ -f "${hist}" ]; then - awk '$2 != 0 { print " "$0 }' "${hist}" + if [ -f "${cache}" ]; then + awk '$2 != 0 { print " "$0 }' "${cache}" else - ip netns exec "${ns}" nstat -as | grep Tcp + NSTAT_HISTORY="${hist}" ip netns exec "${ns}" nstat -s | + grep Tcp fi } From d23c41366e85f149b48323d66adc36c4a9f18cbd Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:17 +0200 Subject: [PATCH 0684/1317] selftests: mptcp: lib: get counters for the right test When the value for a MIB counter is required, mptcp_lib_get_counter is called. It tries to use the cache, if available. If not it falls back to calling 'nstat' directly by looking at the absolute counters. That's an issue for tests that don't recreate the netns for each subtest. In this case, 'nstat -a' will look at the counters for the netns. Instead, it should look at the increment for the current test, by using the history recorded in /tmp/.nstat, if available, and not using '-a' which was dumping the absolute values. While at it, rename the previous 'hist' variable to 'cache' as it was used to look at the cache, not the nstat history. Fixes: 71388a9f331d ("selftests: mptcp: lib: get counters from nstat history") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-12-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_lib.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_lib.sh b/tools/testing/selftests/net/mptcp/mptcp_lib.sh index da1da414c30f..b9d14647f401 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_lib.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_lib.sh @@ -416,19 +416,21 @@ mptcp_lib_nstat_get() { } # $1: ns, $2: MIB counter -# Get the counter from the history (mptcp_lib_nstat_{init,get}()) if available. -# If not, get the counter from nstat ignoring any history. +# Get the counter from the cache (mptcp_lib_nstat_{init,get}()) if available. +# If not, get the counter from nstat ignoring any cache, but using the history. mptcp_lib_get_counter() { local ns="${1}" local counter="${2}" - local hist="/tmp/${ns}.out" + local cache="/tmp/${ns}.out" + local hist="/tmp/${ns}.nstat" local count - if [[ -s "${hist}" && "${counter}" == *"Tcp"* ]]; then - count=$(awk "/^${counter} / {print \$2; exit}" "${hist}") + if [[ -s "${cache}" && "${counter}" == *"Tcp"* ]]; then + count=$(awk "/^${counter} / {print \$2; exit}" "${cache}") else - count=$(ip netns exec "${ns}" nstat -asz "${counter}" | - awk 'NR==1 {next} {print $2}') + count=$(NSTAT_HISTORY="${hist}" ip netns exec "${ns}" \ + nstat -sz "${counter}" | + awk 'NR==1 {next} {print $2}') fi if [ -z "${count}" ]; then mptcp_lib_fail_if_expected_feature "${counter} counter" From b110f1dd6cb6a9930503354a01a315e0a821eaa7 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:18 +0200 Subject: [PATCH 0685/1317] mptcp: options: fix uninit-value in mptcp_write_data_fin When sending a DATA_FIN without data, and because the DATA_FIN occupies 1 octet of the connection-level sequence space [1], it is then required to add a DSS mapping with specific values. If the checksum has been negotiated, it also needs to be computed, and included in the outgoing packet, and thus the initial csum data needs to be reset to 0 as well. This is no longer the case since commit cfcceb7a39fc ("tcp: shrink per-packet memset in __tcp_transmit_skb()"), because the whole ext_copy structure is no longer zeroed by default. This seems to be the only case where use_map is changed and set afterwards, so initialising the csum field only in this case, along with other fields for this specific case. Fixes: cfcceb7a39fc ("tcp: shrink per-packet memset in __tcp_transmit_skb()") Cc: stable@vger.kernel.org Link: https://datatracker.ietf.org/doc/html/rfc8684#section-3.3.3 [1] Link: https://sashiko.dev/#/patchset/20260812-net-next-mptcp-misc-feat-7-3-v1-0-1905a818f6cb%40kernel.org?part=2 Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-13-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 196a46e7467d..ce0de02f5a3a 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -612,6 +612,7 @@ static void mptcp_write_data_fin(struct mptcp_subflow_context *subflow, ext->data_seq = data_fin_tx_seq; ext->subflow_seq = 0; ext->data_len = 1; + ext->csum = 0; } else if (ext->data_seq + ext->data_len == data_fin_tx_seq) { /* If there's an existing DSS mapping and it is the * final mapping, DATA_FIN consumes 1 additional byte of From a4257a91af7a77a8347d33413ec9e54106f7ff48 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:19 +0200 Subject: [PATCH 0686/1317] mptcp: being below memory limit is a likely() condition The current compiler hint annotation is wrong, due to inverted logic in the previous revision of the relevant code. Fixes: e468d371180d ("mptcp: implemented OoO queue pruning") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-14-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 302936ff456a..4309fca6b119 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -289,8 +289,8 @@ static void mptcp_prune_ofo_queue(struct sock *sk, */ static bool mptcp_can_ingest(const struct sock *sk) { - return unlikely(sk_rmem_alloc_get(sk) <= READ_ONCE(sk->sk_rcvbuf)) || - __mptcp_check_fallback(mptcp_sk(sk)); + return likely(sk_rmem_alloc_get(sk) <= READ_ONCE(sk->sk_rcvbuf)) || + __mptcp_check_fallback(mptcp_sk(sk)); } static bool mptcp_try_rmem_schedule(struct sock *sk, const struct sk_buff *skb) From f01b8275745efe611284f6c3628099a81a421f0d Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:20 +0200 Subject: [PATCH 0687/1317] mptcp: avoid pruning for OoW data Pruning is expansive and destructive, do it only when we expect to accept the skb triggering the cleanup. Fixes: e468d371180d ("mptcp: implemented OoO queue pruning") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-15-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 4309fca6b119..0098e2830931 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -313,12 +313,6 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb) u64 seq, end_seq, max_seq; struct sk_buff *skb1; - if (!mptcp_try_rmem_schedule(sk, skb)) { - MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); - mptcp_drop(sk, skb); - return; - } - seq = MPTCP_SKB_CB(skb)->map_seq; end_seq = MPTCP_SKB_CB(skb)->end_seq; max_seq = atomic64_read(&msk->rcv_wnd_sent); @@ -335,6 +329,12 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb) return; } + if (!mptcp_try_rmem_schedule(sk, skb)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); + mptcp_drop(sk, skb); + return; + } + p = &msk->out_of_order_queue.rb_node; MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUE); if (RB_EMPTY_ROOT(&msk->out_of_order_queue)) { From 0fa37512eb747e4ffdcf367274f9e72845f1bca4 Mon Sep 17 00:00:00 2001 From: David Arcari Date: Thu, 3 Sep 2026 14:20:29 -0400 Subject: [PATCH 0688/1317] watchdog: fix hrtimer start when pretimeout is zero Per the watchdog API, a pretimeout value of 0 disables the feature. However, watchdog_hrtimer_pretimeout_start() fails to verify if the pretimeout is non-zero before arming the timer. This omission inadvertently starts the software pretimeout timer, which could result in the pretimeout handler executing incorrectly when the watchdog timeout is reached. Fix this by adding a check for wdd->pretimeout before calling hrtimer_start(), ensuring the disabled state is respected. Fixes: 7b7d2fdc8c3e ("watchdog: Add hrtimer-based pretimeout feature") Signed-off-by: David Arcari Link: https://patch.msgid.link/20260903182029.936030-1-darcari@redhat.com Signed-off-by: Guenter Roeck --- drivers/watchdog/watchdog_hrtimer_pretimeout.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/watchdog_hrtimer_pretimeout.c b/drivers/watchdog/watchdog_hrtimer_pretimeout.c index fbc7eecd8b20..49a05ea60c97 100644 --- a/drivers/watchdog/watchdog_hrtimer_pretimeout.c +++ b/drivers/watchdog/watchdog_hrtimer_pretimeout.c @@ -30,6 +30,7 @@ void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd) void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd) { if (!(wdd->info->options & WDIOF_PRETIMEOUT) && + wdd->pretimeout && !watchdog_pretimeout_invalid(wdd, wdd->pretimeout)) hrtimer_start(&wdd->wd_data->pretimeout_timer, ktime_set(wdd->timeout - wdd->pretimeout, 0), From 3c73a37f5e40972ce26d8eeb98e8b938d719b069 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:41 +0800 Subject: [PATCH 0689/1317] watchdog: msc313e: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-3-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index f69d66971c41..c3018b970164 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -97,6 +97,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct msc313e_wdt_priv *priv; + unsigned long rate; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -116,7 +117,10 @@ static int msc313e_wdt_probe(struct platform_device *pdev) priv->wdev.ops = &msc313e_wdt_ops, priv->wdev.parent = dev; priv->wdev.min_timeout = MSC313E_WDT_MIN_TIMEOUT; - priv->wdev.max_timeout = U32_MAX / clk_get_rate(priv->clk); + rate = clk_get_rate(priv->clk); + if (!rate) + return -EINVAL; + priv->wdev.max_timeout = U32_MAX / rate; priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT; /* If the period is non-zero the WDT is running */ From 3db30f315935c2fb0d95f46b7a593b5b4d3ec3d0 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:42 +0800 Subject: [PATCH 0690/1317] watchdog: msc313e: Fix clock leak and spurious timer in settimeout() msc313e_wdt_settimeout() unconditionally calls msc313e_wdt_start() which introduces two severe bugs: 1. If the watchdog is already active, calling start() again will increase the reference count of the clock again. However stop() is only called once, the reference count is unbalance. 2. If the watchdog is stopped, calling settimeout() will start the hardware timer accidentally. Factor out the register-writing logic into a helper function. Only call it in settimeout() if the watchdog is running. Otherwise, simply update `wdev->timeout`. Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-4-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index c3018b970164..8ce24df8e338 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -31,20 +31,26 @@ struct msc313e_wdt_priv { struct clk *clk; }; +static void msc313e_wdt_set_hw_timeout(struct msc313e_wdt_priv *priv, + unsigned int timeout) +{ + u32 t = timeout * clk_get_rate(priv->clk); + + writew(t & 0xffff, priv->base + REG_WDT_MAX_PRD_L); + writew((t >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H); + writew(1, priv->base + REG_WDT_CLR); +} + static int msc313e_wdt_start(struct watchdog_device *wdev) { struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); - u32 timeout; int err; err = clk_prepare_enable(priv->clk); if (err) return err; - timeout = wdev->timeout * clk_get_rate(priv->clk); - writew(timeout & 0xffff, priv->base + REG_WDT_MAX_PRD_L); - writew((timeout >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H); - writew(1, priv->base + REG_WDT_CLR); + msc313e_wdt_set_hw_timeout(priv, wdev->timeout); return 0; } @@ -69,9 +75,13 @@ static int msc313e_wdt_stop(struct watchdog_device *wdev) static int msc313e_wdt_settimeout(struct watchdog_device *wdev, unsigned int new_time) { + struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); + wdev->timeout = new_time; - return msc313e_wdt_start(wdev); + if (watchdog_hw_running(wdev) || watchdog_active(wdev)) + msc313e_wdt_set_hw_timeout(priv, wdev->timeout); + return 0; } static const struct watchdog_info msc313e_wdt_ident = { From 3db2df24e7f11fb117718f6abe326628d91bc500 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:43 +0800 Subject: [PATCH 0691/1317] watchdog: msc313e: Enable clock before accessing hardware registers msc313e_wdt_probe() reads from hardware registers without ensuring the required clock is enabled. Furthermore, if the bootloader leaves the watchdog running, msc313e_wdt_probe() sets WDOG_HW_RUNNING without increasing the clock's reference count. While the clock is currently supplied as a fixed clock by the device tree (`xtal_div2` in arch/arm/boot/dts/sigmastar/mstar-v7.dtsi) which masks the physical issue, this still violates the API usage. Call clk_prepare_enable() before reading WDT registers. If the WDT is running, leave the clock enabled so the CCF reference counter is balanced. Fixes: ffd264bd152c ("watchdog: msc313e: Check if the WDT was running at boot") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-5-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 8ce24df8e338..7c4593566781 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -108,6 +108,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct msc313e_wdt_priv *priv; unsigned long rate; + int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -133,9 +134,21 @@ static int msc313e_wdt_probe(struct platform_device *pdev) priv->wdev.max_timeout = U32_MAX / rate; priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT; + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + /* If the period is non-zero the WDT is running */ - if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16)) + if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16)) { set_bit(WDOG_HW_RUNNING, &priv->wdev.status); + /* + * Keep the clock enabled. The watchdog core will skip the next + * start() and a future stop() will balance the CCF reference + * count. + */ + } else { + clk_disable_unprepare(priv->clk); + } watchdog_set_drvdata(&priv->wdev, priv); platform_set_drvdata(pdev, priv); @@ -144,7 +157,13 @@ static int msc313e_wdt_probe(struct platform_device *pdev) watchdog_stop_on_reboot(&priv->wdev); watchdog_stop_on_unregister(&priv->wdev); - return devm_watchdog_register_device(dev, &priv->wdev); + ret = devm_watchdog_register_device(dev, &priv->wdev); + + /* If the WDT is running and anything goes wrong, disable the clock. */ + if (ret && test_bit(WDOG_HW_RUNNING, &priv->wdev.status)) + clk_disable_unprepare(priv->clk); + + return ret; } static int __maybe_unused msc313e_wdt_suspend(struct device *dev) From 4f6817c9eff4aa1078e16652d82e4b7ef4ffae3e Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:44 +0800 Subject: [PATCH 0692/1317] watchdog: msc313e: Fix spurious reset on suspend If the hardware watchdog was started by the bootloader and the device is suspended before userspace opens it, the ping worker (from watchdog core) is frozen and the active hardware timer continues running. This leads to a spurious system reset. Check both watchdog_active() and watchdog_hw_running() when deciding whether to start or stop the watchdog during suspend and resume. Additionally, call watchdog_stop_ping_on_suspend() to ensure the ping worker be correctly paused and restarted during suspend and resume. Fixes: ffd264bd152c ("watchdog: msc313e: Check if the WDT was running at boot") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-6-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 7c4593566781..c7d558fefc86 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -156,6 +156,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(&priv->wdev, timeout, dev); watchdog_stop_on_reboot(&priv->wdev); watchdog_stop_on_unregister(&priv->wdev); + watchdog_stop_ping_on_suspend(&priv->wdev); ret = devm_watchdog_register_device(dev, &priv->wdev); @@ -170,7 +171,7 @@ static int __maybe_unused msc313e_wdt_suspend(struct device *dev) { struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); - if (watchdog_active(&priv->wdev)) + if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) msc313e_wdt_stop(&priv->wdev); return 0; @@ -180,7 +181,7 @@ static int __maybe_unused msc313e_wdt_resume(struct device *dev) { struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); - if (watchdog_active(&priv->wdev)) + if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) msc313e_wdt_start(&priv->wdev); return 0; From ab390021b2a3bb4cc875f28a6f76d13de90d7457 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:45 +0800 Subject: [PATCH 0693/1317] watchdog: msc313e: Fix undefined behavior readw() returns a u16. Left shifting a u16 by 16 bits yields undefined behavior. Cast to u32 explicitly before the shift. Fixes: ffd264bd152c ("watchdog: msc313e: Check if the WDT was running at boot") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-7-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index c7d558fefc86..e28261c7a8d4 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -31,6 +31,16 @@ struct msc313e_wdt_priv { struct clk *clk; }; +static u32 msc313e_wdt_get_hw_timeout(struct msc313e_wdt_priv *priv) +{ + u16 low, high; + + low = readw(priv->base + REG_WDT_MAX_PRD_L); + high = readw(priv->base + REG_WDT_MAX_PRD_H); + + return ((u32)high << 16) | low; +} + static void msc313e_wdt_set_hw_timeout(struct msc313e_wdt_priv *priv, unsigned int timeout) { @@ -139,7 +149,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) return ret; /* If the period is non-zero the WDT is running */ - if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16)) { + if (msc313e_wdt_get_hw_timeout(priv)) { set_bit(WDOG_HW_RUNNING, &priv->wdev.status); /* * Keep the clock enabled. The watchdog core will skip the next From 01504d14e47b34779911250dd308a03f6ef681c2 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:46 +0800 Subject: [PATCH 0694/1317] watchdog: msc313e: Sync timeout value if WDT was running at boot If WDT was running at boot, the hardware timeout might be set to values other than the final software timeout. To be consistent, set the hardware timeout to match the final software timeout (i.e., after watchdog_init_timeout()) if WDT was running. Fixes: ffd264bd152c ("watchdog: msc313e: Check if the WDT was running at boot") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-8-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index e28261c7a8d4..4a5cce2a16b1 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -144,12 +144,21 @@ static int msc313e_wdt_probe(struct platform_device *pdev) priv->wdev.max_timeout = U32_MAX / rate; priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT; + watchdog_set_drvdata(&priv->wdev, priv); + platform_set_drvdata(pdev, priv); + + watchdog_init_timeout(&priv->wdev, timeout, dev); + watchdog_stop_on_reboot(&priv->wdev); + watchdog_stop_on_unregister(&priv->wdev); + watchdog_stop_ping_on_suspend(&priv->wdev); + ret = clk_prepare_enable(priv->clk); if (ret) return ret; /* If the period is non-zero the WDT is running */ if (msc313e_wdt_get_hw_timeout(priv)) { + msc313e_wdt_set_hw_timeout(priv, priv->wdev.timeout); set_bit(WDOG_HW_RUNNING, &priv->wdev.status); /* * Keep the clock enabled. The watchdog core will skip the next @@ -160,14 +169,6 @@ static int msc313e_wdt_probe(struct platform_device *pdev) clk_disable_unprepare(priv->clk); } - watchdog_set_drvdata(&priv->wdev, priv); - platform_set_drvdata(pdev, priv); - - watchdog_init_timeout(&priv->wdev, timeout, dev); - watchdog_stop_on_reboot(&priv->wdev); - watchdog_stop_on_unregister(&priv->wdev); - watchdog_stop_ping_on_suspend(&priv->wdev); - ret = devm_watchdog_register_device(dev, &priv->wdev); /* If the WDT is running and anything goes wrong, disable the clock. */ From b824476c56a153934c67c9e0f873e1fd967743d6 Mon Sep 17 00:00:00 2001 From: Yilin Zhang Date: Sat, 5 Sep 2026 00:28:00 +0800 Subject: [PATCH 0695/1317] inet: frags: invalidate queues before flushing them fqdir_pre_exit() flushes the skbs from incomplete queues without changing their completion state. A fragment which found a queue before high_thresh was cleared can then acquire the queue lock and reuse stale reassembly metadata. A queue concurrently killed after fqdir->dead is set can instead become INET_FRAG_COMPLETE|INET_FRAG_HASH_DEAD while still holding its old skbs; skipping it because it is complete leaves those references behind until asynchronous fqdir teardown. For IPv6, stale metadata can make ip6_frag_reasm() use the old nhoffset with a new skb and access memory out of bounds. The resulting heap corruption can be leveraged for local privilege escalation when unprivileged network namespaces are available. Unflushed fragments can also keep conntrack references alive after the conntrack per-net cleanup point. Kill each incomplete queue, then flush every queue still owned by the dying rhashtable. HASH_DEAD identifies that ownership, while complete queues without it are already owned by another destroy path and must be left alone. Releasing a timer reference removed by inet_frag_kill() is deferred to inet_frag_putn(), after the queue lock is dropped. KASAN report: BUG: KASAN: slab-out-of-bounds in ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2)) Write of size 1 at addr ff110001039c6e00 by task poc/771 Call Trace: ? ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2)) ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2)) ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:479 (discriminator 5)) ip6_input_finish (net/ipv6/ip6_input.c:534) ipv6_rcv (include/net/dst.h:480 (discriminator 3) net/ipv6/ip6_input.c:119 (discriminator 3) net/ipv6/ip6_input.c:109 (discriminator 3) include/linux/netfilter.h:325 (discriminator 3) include/linux/netfilter.h:319 (discriminator 3) net/ipv6/ip6_input.c:351 (discriminator 3)) packet_sendmsg (net/packet/af_packet.c:3110 net/packet/af_packet.c:3142) __x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880) The buggy address belongs to the object at ff110001039c6b40 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 0 bytes to the right of allocated 704-byte region [ff110001039c6b40, ff110001039c6e00) BUG: KASAN: slab-out-of-bounds in ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1)) Read of size 1 at addr ff110001039c6e08 by task poc/771 Call Trace: ? ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1)) ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1)) ip6_input_finish (net/ipv6/ip6_input.c:534) ipv6_rcv (include/net/dst.h:480 (discriminator 3) net/ipv6/ip6_input.c:119 (discriminator 3) net/ipv6/ip6_input.c:109 (discriminator 3) include/linux/netfilter.h:325 (discriminator 3) include/linux/netfilter.h:319 (discriminator 3) net/ipv6/ip6_input.c:351 (discriminator 3)) packet_sendmsg (net/packet/af_packet.c:3110 net/packet/af_packet.c:3142) __x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880) packet_sendmsg (net/packet/af_packet.c:2959 net/packet/af_packet.c:3053 net/packet/af_packet.c:3142) __x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880) The buggy address belongs to the object at ff110001039c6b40 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 8 bytes to the right of allocated 704-byte region [ff110001039c6b40, ff110001039c6e00) Fixes: 006a5035b495 ("inet: frags: flush pending skbs in fqdir_pre_exit()") Cc: stable@vger.kernel.org Reported-by: Kimi Security Team Tested-by: Weiming Shi Reviewed-by: Eric Dumazet Signed-off-by: Yilin Zhang Link: https://patch.msgid.link/20260904162800.1095662-1-yilinzhang@moonshot.ai Signed-off-by: Jakub Kicinski --- net/ipv4/inet_fragment.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index c17e57ec7d5c..b286ee429da8 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -235,6 +235,8 @@ void fqdir_pre_exit(struct fqdir *fqdir) rhashtable_walk_start(&hti); while ((fq = rhashtable_walk_next(&hti))) { + int refs = 0; + if (IS_ERR(fq)) { if (PTR_ERR(fq) != -EAGAIN) break; @@ -242,8 +244,12 @@ void fqdir_pre_exit(struct fqdir *fqdir) } spin_lock_bh(&fq->lock); if (!(fq->flags & INET_FRAG_COMPLETE)) + inet_frag_kill(fq, &refs); + + if (fq->flags & INET_FRAG_HASH_DEAD) inet_frag_queue_flush(fq, 0); spin_unlock_bh(&fq->lock); + inet_frag_putn(fq, refs); } rhashtable_walk_stop(&hti); From 59fb389ad6bf50916189e56dafcd225ab977f874 Mon Sep 17 00:00:00 2001 From: "Jan Havran (Advantech Czech)" Date: Mon, 7 Sep 2026 15:48:18 +0200 Subject: [PATCH 0696/1317] net: dsa: lantiq_gswip: fix GSWIP_MDIO_PHY_FCONTX_EN value Per the GSW145 data sheet, the FCONTX (bits 8:7) and FCONRX (bits 6:5) flow-control fields of the PHY_ADDR_n register both encode 00 = AUTO, 01 = EN, 10 = reserved, 11 = DIS. GSWIP_MDIO_PHY_FCONTX_EN was 0x0100, i.e. field value 10 (the reserved encoding), instead of 0x0080 (01 = EN); FCONRX_EN is already 0x0020 (01). Enabling tx flow control therefore wrote the reserved value. Set FCONTX_EN to 0x0080. The register is shared by all supported parts. Fixes: 14fceff4771e ("net: dsa: Add Lantiq / Intel DSA driver for vrx200") Signed-off-by: Jan Havran (Advantech Czech) Reviewed-by: Daniel Golle Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260907134818.16670-4-havran.jan@email.cz Signed-off-by: Jakub Kicinski --- drivers/net/dsa/lantiq/lantiq_gswip.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index bc3686faad0d..0b75be14dc10 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -42,7 +42,7 @@ #define GSWIP_MDIO_PHY_FDUP_DIS 0x0600 #define GSWIP_MDIO_PHY_FCONTX_MASK 0x0180 #define GSWIP_MDIO_PHY_FCONTX_AUTO 0x0000 -#define GSWIP_MDIO_PHY_FCONTX_EN 0x0100 +#define GSWIP_MDIO_PHY_FCONTX_EN 0x0080 #define GSWIP_MDIO_PHY_FCONTX_DIS 0x0180 #define GSWIP_MDIO_PHY_FCONRX_MASK 0x0060 #define GSWIP_MDIO_PHY_FCONRX_AUTO 0x0000 From 66ef5adb75446627f8b6c26cd04f2adc86d4de56 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sat, 5 Sep 2026 15:02:32 +0200 Subject: [PATCH 0697/1317] net: ks8851: Fix receiver error in 100BASE-TX mode following software power-down KSZ8851 errata sheet DS80000716D-page 4 Module 3 [1] states that, when issuing a software power-down (PMECR[1:0] = 10) followed by a power-on (PMECR[1:0] = 00), the receiver circuit can fail to start properly preventing communication. The Transmitter will still send data, but no data will be received. The errata sheet also includes a workaround, which states that, it is recommended that the software power-down feature not be used. Implement that workaround and drop the entry into software power-down mode. The ks8851_write_mac_addr() calls entry into normal power-on mode at the very beginning of the function, therefore dropping the second call to enter software power-down mode is sufficient here. The ks8851_net_stop() can only be called after ks8851_net_start() was already called, and ks8851_net_start() also makes the MAC enter normal power-on mode, therefore it is also fine to drop the call to enter software power-down mode from ks8851_net_stop(). This will lead to a slight increase in power consumption, but it also fixes a sporadic reliability problem on at least KSZ8851-16MLL, which is where the problem was reported and this fix was tested. [1] https://ww1.microchip.com/downloads/en/DeviceDoc/80000716D.pdf Fixes: 3ba81f3ece3c ("net: Micrel KS8851 SPI network driver") Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260905130327.203851-1-marex@nabladev.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/micrel/ks8851_common.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/net/ethernet/micrel/ks8851_common.c b/drivers/net/ethernet/micrel/ks8851_common.c index 4afbb40bc0e4..d49f281c7867 100644 --- a/drivers/net/ethernet/micrel/ks8851_common.c +++ b/drivers/net/ethernet/micrel/ks8851_common.c @@ -143,9 +143,6 @@ static int ks8851_write_mac_addr(struct net_device *dev) ks8851_wrreg16(ks, KS_MAR(i), val); } - if (!netif_running(dev)) - ks8851_set_powermode(ks, PMECR_PM_SOFTDOWN); - ks8851_unlock(ks); return 0; @@ -478,8 +475,7 @@ static int ks8851_net_open(struct net_device *dev) * @dev: The device being closed. * * Called to close down a network device which has been active. Cancel any - * work, shutdown the RX and TX process and then place the chip into a low - * power state whilst it is not being used. + * work and shutdown the RX and TX process. */ static int ks8851_net_stop(struct net_device *dev) { @@ -506,8 +502,6 @@ static int ks8851_net_stop(struct net_device *dev) /* shutdown TX process */ ks8851_wrreg16(ks, KS_TXCR, 0x0000); - /* set powermode to soft power down to save power */ - ks8851_set_powermode(ks, PMECR_PM_SOFTDOWN); ks8851_unlock(ks); /* ensure any queued tx buffers are dumped */ From cb26524ef4ac28fcfa554c0656e8dc412c38a8ff Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 9 Sep 2026 17:02:40 -0300 Subject: [PATCH 0698/1317] smb: client: fix one-byte OOB read in smb2_parse_native_symlink() When parsing a share-root relative native symlink, memcpy copies smb_target+1 (skipping the leading separator) but uses strlen(smb_target)+1 as the length, reading one byte past the allocated buffer. This fixes the following KASAN splat when accessing an SMB symlink with a target of '\a\b': BUG: KASAN: slab-out-of-bounds in smb2_parse_native_symlink+0x4f5/0xca0 Read of size 5 at addr ffff88800878fe21 by task netfsfuzz-execu/1 CPU: 1 UID: 0 PID: 1 Comm: netfsfuzz-execu Tainted: G N 7.2.0-11943-g2709dd5ae32f-dirty #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996) Call Trace: dump_stack_lvl+0x7b/0xa0 print_report+0xd0/0x630 kasan_report+0xe5/0x120 kasan_check_range+0x105/0x1b0 __asan_memcpy+0x23/0x60 smb2_parse_native_symlink+0x4f5/0xca0 parse_reparse_point+0x68a/0x1530 reparse_info_to_fattr+0x752/0xa20 cifs_get_fattr+0x873/0x15b0 cifs_get_inode_info+0xc0/0x310 cifs_lookup+0x308/0xa70 __lookup_slow+0x122/0x2b0 lookup_slow+0x50/0x70 path_lookupat+0x525/0xaf0 filename_lookup+0x1f2/0x550 vfs_statx+0xd1/0x1a0 vfs_fstatat+0x65/0xc0 __do_sys_newfstatat+0x9a/0x120 do_syscall_64+0xdd/0x4a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Reported-by: Yuanfu Xie Fixes: 723f4ef90452 ("cifs: Fix parsing native symlinks relative to the export") Suggested-by: Pali Rohar Reviewed-by: Pali Rohar Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index b6bded042e78..8a1b9e8be5ba 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -971,7 +971,8 @@ int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, linux_target[i*3 + 1] = '.'; linux_target[i*3 + 2] = sep; } - memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ + /* +1 to skip leading sep */ + memcpy(linux_target + levels*3, smb_target+1, smb_target_len-1); } else { /* * This is either an absolute symlink in POSIX-style format From 286b175bb03893949f24621f356abd9d20368b0a Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 23 Aug 2026 19:59:01 +0200 Subject: [PATCH 0699/1317] hwmon: (chipcap2) fix channels in humidity alarm notifications hwmon_notify_event() expects the channel number as its last argument, taken into account with the type parameter that it is a humidity sensor type. Given that this device only provides one humidity channel, 0 must be passed. The custom construct to enumerate the channels makes wrong assumptions by listing all types together (temperature and humidity). Remove the custom channel enumeration and pass the right channel to hwmon_notify_event() for hwmon_humidity_min_alarm and hwmon_humidity_max_alarm. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20260823-chipcap2_locks-v2-1-6a26c8e9e2fc@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 086571d556b7..9bef767b589e 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -92,11 +92,6 @@ struct cc2_data { bool process_irqs; }; -enum cc2_chan_addr { - CC2_CHAN_TEMP = 0, - CC2_CHAN_HUMIDITY, -}; - /* %RH as a per cent mille from a register value */ static long cc2_rh_convert(u16 data) { @@ -499,7 +494,7 @@ static irqreturn_t cc2_low_interrupt(int irq, void *data) if (cc2->process_irqs) { hwmon_notify_event(cc2->hwmon, hwmon_humidity, - hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY); + hwmon_humidity_min_alarm, 0); cc2->rh_alarm.low_alarm = true; } @@ -512,7 +507,7 @@ static irqreturn_t cc2_high_interrupt(int irq, void *data) if (cc2->process_irqs) { hwmon_notify_event(cc2->hwmon, hwmon_humidity, - hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY); + hwmon_humidity_max_alarm, 0); cc2->rh_alarm.high_alarm = true; } From 6d760f8b41aed74de4402440e4db663d261478bd Mon Sep 17 00:00:00 2001 From: Vishnu Razdan Date: Mon, 24 Aug 2026 23:58:00 -0700 Subject: [PATCH 0700/1317] hwmon: (pmbus) Clear generic status alarms with CLEAR_FAULTS Some hwmon alarms fall back to STATUS_WORD summary bits when no individual limit alarm is available. On PMBus 1.2 and newer devices, pmbus_get_boolean() acknowledges these alarms with the same byte-data write used for detailed status registers. For example, PB_STATUS_INPUT is 0x2000, so it is truncated to zero when passed to _pmbus_write_byte_data(). The resulting write cannot acknowledge the input alarm. PMBus 1.3 Part II, sections 10.2.4 and 10.2.5, excludes ordinary STATUS_BYTE and STATUS_WORD summary bits from individual clearing. Their summary bits clear when the underlying status bits clear, so changing this to a word-data write would not fix the generic input alarm either. Use the existing page CLEAR_FAULTS path for generic STATUS_WORD alarms, including devices whose status accessor uses STATUS_BYTE. Keep individual byte writes for detailed status registers on PMBus 1.2 and newer devices. As with the existing older-device fallback, CLEAR_FAULTS can clear other latched status; an active condition can reassert its status. Fixes: 35f165f08950 ("hwmon: (pmbus) Clear pmbus fault/warning bits after read") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Vishnu Razdan Link: https://patch.msgid.link/20260824-vrazdan-pmbus-status-word-b4-v1-1-2606ecd0c029@openai.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 806c9a4913bb..5f69c1420b4e 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -1275,7 +1275,9 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, regval = status & mask; if (regval) { - if (data->revision >= PMBUS_REV_12) { + /* Generic STATUS_WORD alarms are not individually clearable. */ + if (data->revision >= PMBUS_REV_12 && + reg != PMBUS_STATUS_WORD) { ret = _pmbus_write_byte_data(client, page, reg, regval); if (ret) return ret; From 508baf1713f32f287bfb4f85d759403ec8ba35a3 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 31 Aug 2026 09:45:09 +0800 Subject: [PATCH 0701/1317] hwmon: (corsair-cpro) Create debugfs entries after hwmon registration ccp_debugfs_init() registers debugfs files whose private data is the devm allocated ccp. It runs before hwmon_device_register_with_info(), so when that registration fails, ccp_probe() returns with the files still in place. The HID core then frees ccp, and ccp_remove() is not called for a failed probe, so nothing removes them later either. Reading one of the files dereferences the freed pointer. Create the debugfs entries only after the hwmon device has been registered, so no failing path can leave them behind. The two version queries stay where they are. They send USB commands without holding ccp->mutex, which is only safe as long as nothing else can call send_usb_cmd(); once the hwmon device is registered its callbacks can do so concurrently. Only the debugfs creation moves, and it is told which queries succeeded. Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hwmon/20260708031612.BD7E61F000E9@smtp.kernel.org/ Suggested-by: Guenter Roeck Fixes: 5997eb60f896 ("hwmon: (corsair-cpro) Add firmware and bootloader information") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260831014509.3352442-1-lilinmao@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-cpro.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/corsair-cpro.c b/drivers/hwmon/corsair-cpro.c index 8354a002f4c5..56de0fe0f544 100644 --- a/drivers/hwmon/corsair-cpro.c +++ b/drivers/hwmon/corsair-cpro.c @@ -566,21 +566,18 @@ static int bootloader_show(struct seq_file *seqf, void *unused) } DEFINE_SHOW_ATTRIBUTE(bootloader); -static void ccp_debugfs_init(struct ccp_device *ccp) +static void ccp_debugfs_init(struct ccp_device *ccp, bool fw_valid, bool bl_valid) { char name[32]; - int ret; scnprintf(name, sizeof(name), "corsaircpro-%s", dev_name(&ccp->hdev->dev)); ccp->debugfs = debugfs_create_dir(name, NULL); - ret = get_fw_version(ccp); - if (!ret) + if (fw_valid) debugfs_create_file("firmware_version", 0444, ccp->debugfs, ccp, &firmware_fops); - ret = get_bl_version(ccp); - if (!ret) + if (bl_valid) debugfs_create_file("bootloader_version", 0444, ccp->debugfs, ccp, &bootloader_fops); } @@ -588,6 +585,7 @@ static void ccp_debugfs_init(struct ccp_device *ccp) static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct ccp_device *ccp; + bool fw_valid, bl_valid; int ret; ccp = devm_kzalloc(&hdev->dev, sizeof(*ccp), GFP_KERNEL); @@ -632,7 +630,13 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto out_hw_close; - ccp_debugfs_init(ccp); + /* + * Query the versions before registering the hwmon device: they send + * USB commands without holding ccp->mutex, which is only safe while + * nothing else can call send_usb_cmd(). + */ + fw_valid = !get_fw_version(ccp); + bl_valid = !get_bl_version(ccp); ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro", ccp, &ccp_chip_info, NULL); @@ -641,6 +645,8 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) goto out_hw_close; } + ccp_debugfs_init(ccp, fw_valid, bl_valid); + return 0; out_hw_close: From bb2424c3502cc72292eedade46960c331d5f28fb Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Tue, 1 Sep 2026 22:54:04 +0700 Subject: [PATCH 0702/1317] hwmon: (gpio-fan) take fan_data->lock in gpio_fan_shutdown() set_fan_speed() writes the control GPIOs one bit at a time. Every other caller locks around it; gpio_fan_shutdown() doesn't. If it races a locked caller, the GPIO writes can interleave and leave the fan at a speed neither caller asked for. Fixes: b95579cd8795 ("hwmon: (gpio-fan) Add a shutdown handler to poweroff the fans") Reported-by: Sashiko AI review Link: https://lore.kernel.org/r/20260830152150.27F5F1F000E9@smtp.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://patch.msgid.link/20260901155404.1532092-1-congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index 7f36e5f6f223..df8bd9707605 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -612,8 +612,11 @@ static void gpio_fan_shutdown(struct platform_device *pdev) { struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); - if (fan_data->gpios) + if (fan_data->gpios) { + mutex_lock(&fan_data->lock); set_fan_speed(fan_data, 0); + mutex_unlock(&fan_data->lock); + } } static int gpio_fan_runtime_suspend(struct device *dev) From 09a9e1746a87845d7d8e2b4e23bb613306effdff Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 20:50:44 +0800 Subject: [PATCH 0703/1317] hwmon: (aspeed-pwm-tacho) Propagate reset deassert errors aspeed_pwm_tacho_probe() installs its reset cleanup action and configures the controller after an unchecked reset deassertion. Stop probing when the reset controller rejects the transition, before the hwmon device becomes visible. Fixes: 18c514cc0e02 ("hwmon: (aspeed-pwm-tacho) Deassert reset in probe") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830125044.97718-1-pengpeng@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/aspeed-pwm-tacho.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c index 1c5945d4ba37..bfce589c3fb1 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -934,7 +934,9 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev) "missing or invalid reset controller device tree entry"); return PTR_ERR(priv->rst); } - reset_control_deassert(priv->rst); + ret = reset_control_deassert(priv->rst); + if (ret) + return ret; ret = devm_add_action_or_reset(dev, aspeed_pwm_tacho_remove, priv); if (ret) From 4ee875c423c66c45d7ef7bbff403cd0e3971e0a2 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 28 Aug 2026 14:19:49 +0800 Subject: [PATCH 0704/1317] hwmon: (corsair-cpro) Remove debugfs entries when probe fails ccp_debugfs_init() registers debugfs files whose private data is the devm allocated ccp. If hwmon_device_register_with_info() fails right after it, ccp_probe() returns without removing them: the HID core then frees ccp, and ccp_remove() is not called for a failed probe, so the files stay behind. Reading one of them dereferences the freed pointer. Remove the debugfs entries on that error path. debugfs_remove_recursive() waits for readers already inside the show callbacks, so ccp is no longer reachable through debugfs by the time probe returns. Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hwmon/20260708031612.BD7E61F000E9@smtp.kernel.org/ Fixes: 5997eb60f896 ("hwmon: (corsair-cpro) Add firmware and bootloader information") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260828061949.3151191-1-lilinmao@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-cpro.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/corsair-cpro.c b/drivers/hwmon/corsair-cpro.c index 56de0fe0f544..c09645152613 100644 --- a/drivers/hwmon/corsair-cpro.c +++ b/drivers/hwmon/corsair-cpro.c @@ -642,13 +642,15 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) ccp, &ccp_chip_info, NULL); if (IS_ERR(ccp->hwmon_dev)) { ret = PTR_ERR(ccp->hwmon_dev); - goto out_hw_close; + goto out_debugfs_remove; } ccp_debugfs_init(ccp, fw_valid, bl_valid); return 0; +out_debugfs_remove: + debugfs_remove_recursive(ccp->debugfs); out_hw_close: hid_hw_close(hdev); hid_device_io_stop(hdev); From 8042312e73c50de82634ce63eae7cf219464b481 Mon Sep 17 00:00:00 2001 From: Arie Miller Date: Thu, 3 Sep 2026 22:21:28 -0400 Subject: [PATCH 0705/1317] hwmon: (asus_rog_ryujin) Validate HID report lengths rog_ryujin_raw_event() parses response headers and payload fields without first checking that they are present in the received report. A short report can therefore make the driver consume uninitialized bytes from the HID transport buffer and expose them as sensor values through sysfs. Validate the response header and the fields used by each response type before parsing them. Fixes: ed3e03790c5c ("hwmon: Add driver for ASUS ROG RYUJIN II 360 AIO cooler") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hwmon/20260812104617.858D01F000E9@smtp.kernel.org/ Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol sparse Signed-off-by: Arie Miller Link: https://patch.msgid.link/20260904022129.97896-2-renari@arimil.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_rog_ryujin.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index 702edb831394..f4d99c510369 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -422,10 +422,15 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo { struct rog_ryujin_data *priv = hid_get_drvdata(hdev); - if (data[0] != RYUJIN_CMD_PREFIX) + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) return 0; if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) + return 0; + /* Received coolant temp and speeds of pump and internal fan */ priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + data[priv->info->temp_offset + 1] * 100; @@ -437,6 +442,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo if (!completion_done(&priv->cooler_status_received)) complete_all(&priv->cooler_status_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) + return 0; + /* Received speeds of four fans attached to the controller */ priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2); @@ -446,6 +454,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo if (!completion_done(&priv->controller_status_received)) complete_all(&priv->controller_status_received); } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) + return 0; + /* Received report for pump and internal fan duties (in %) */ if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { /* @@ -472,6 +483,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo if (!completion_done(&priv->cooler_duty_received)) complete_all(&priv->cooler_duty_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) + return 0; + /* Received report for controller duty for fans (in PWM) */ if (data[RYUJIN_CONTROLLER_DUTY] == 0) { /* From 06d48355bf41028c1321acda6a4391cd70098be8 Mon Sep 17 00:00:00 2001 From: Arie Miller Date: Thu, 3 Sep 2026 22:21:29 -0400 Subject: [PATCH 0706/1317] hwmon: (asus_rog_ryujin) Synchronize HID command and report handling rog_ryujin_execute_cmd() holds status_report_request_lock while reinitializing a completion, intending to exclude raw-event handling. However, rog_ryujin_raw_event() does not acquire the lock when it updates the completion. A response can therefore race with reinit_completion() and be lost, leaving the command to time out. Hold the lock while parsing reports and updating their completions. Use the irqsave variants in both paths because raw-event handling may run in interrupt context. Fixes: ed3e03790c5c ("hwmon: Add driver for ASUS ROG RYUJIN II 360 AIO cooler") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hwmon/20260812104617.858D01F000E9@smtp.kernel.org/ Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol sparse Signed-off-by: Arie Miller Link: https://patch.msgid.link/20260904022129.97896-3-renari@arimil.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_rog_ryujin.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index f4d99c510369..e297557ca346 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -184,6 +184,7 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, struct completion *status_completion) { + unsigned long flags; int ret; /* @@ -191,9 +192,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i * completion. Reinit is done because hidraw could have triggered * the raw event parsing and marked the passed in completion as done. */ - spin_lock_bh(&priv->status_report_request_lock); + spin_lock_irqsave(&priv->status_report_request_lock, flags); reinit_completion(status_completion); - spin_unlock_bh(&priv->status_report_request_lock); + spin_unlock_irqrestore(&priv->status_report_request_lock, flags); /* Send command for getting data */ ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); @@ -421,15 +422,18 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo int size) { struct rog_ryujin_data *priv = hid_get_drvdata(hdev); + unsigned long flags; if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) return 0; + spin_lock_irqsave(&priv->status_report_request_lock, flags); + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { if (size <= priv->info->temp_offset + 1 || size <= priv->info->pump_speed_offset + 1 || size <= priv->info->fan_speed_offset + 1) - return 0; + goto unlock; /* Received coolant temp and speeds of pump and internal fan */ priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + @@ -443,7 +447,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->cooler_status_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) - return 0; + goto unlock; /* Received speeds of four fans attached to the controller */ priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); @@ -455,7 +459,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->controller_status_received); } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { if (size <= RYUJIN_INTERNAL_FAN_DUTY) - return 0; + goto unlock; /* Received report for pump and internal fan duties (in %) */ if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { @@ -474,7 +478,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo * We're expecting a report, so parse it. */ goto read_cooler_duty; - return 0; + goto unlock; } read_cooler_duty: priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); @@ -484,7 +488,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->cooler_duty_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { if (size <= RYUJIN_CONTROLLER_DUTY) - return 0; + goto unlock; /* Received report for controller duty for fans (in PWM) */ if (data[RYUJIN_CONTROLLER_DUTY] == 0) { @@ -503,7 +507,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo * We're expecting a report, so parse it. */ goto read_controller_duty; - return 0; + goto unlock; } read_controller_duty: priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; @@ -512,6 +516,8 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->controller_duty_received); } +unlock: + spin_unlock_irqrestore(&priv->status_report_request_lock, flags); return 0; } From c88a6338ae485e4d6210cc74cdb7664d6476c925 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Mon, 3 Aug 2026 10:21:48 +0000 Subject: [PATCH 0707/1317] hwmon: (nct6694) do not expose enable on DTIN temperature channels The driver registers 26 temperature channels, all advertising HWMON_T_ENABLE, and indexes the enable bitmap with the raw channel: data->hwmon_en.tin_en[channel / 8] |= BIT(channel % 8); tin_en is two bytes and only covers the 5 THR and 5 TDP channels (index 0-9). The 16 DTIN channels (index 10-25) are enabled by the firmware and were never meant to carry an enable bit. Because the control structure is packed, writing temp17_enable and above indexes past tin_en into the fin_en bytes that follow it, so it toggles fan enable state instead; nct6694_hwmon_init() then sends the whole structure back to the device, and reads report fan state as temperature state. It stays within the structure, so this is not a memory safety problem, but on a board that uses the fan channels it is not harmless. Give the DTIN channels a temperature config without HWMON_T_ENABLE so the core never creates their enable attribute. The enable path is then reachable only for the first 10 channels, which stay within tin_en, and fin_en is left alone. The DTIN input and limit attributes are unchanged. Fixes: 197e779d29d8 ("hwmon: Add Nuvoton NCT6694 HWMON support") Suggested-by: Ming Yu Link: https://lore.kernel.org/all/20260802124730.20387-1-ali@iusegentoo.com/ Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260803102148.14196-1-ali@iusegentoo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6694-hwmon.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c index 6dcf22ca5018..9a9a4db434c4 100644 --- a/drivers/hwmon/nct6694-hwmon.c +++ b/drivers/hwmon/nct6694-hwmon.c @@ -159,6 +159,9 @@ static inline s8 temp_to_reg(long val) #define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE | \ HWMON_T_MAX | HWMON_T_MAX_HYST | \ HWMON_T_MAX_ALARM) +#define NCT6694_HWMON_DTIN_CONFIG (HWMON_T_INPUT | \ + HWMON_T_MAX | HWMON_T_MAX_HYST | \ + HWMON_T_MAX_ALARM) #define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE | \ HWMON_F_MIN | HWMON_F_MIN_ALARM) #define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE | \ @@ -193,22 +196,22 @@ static const struct hwmon_channel_info *nct6694_info[] = { NCT6694_HWMON_TEMP_CONFIG, /* TDP2 */ NCT6694_HWMON_TEMP_CONFIG, /* TDP3 */ NCT6694_HWMON_TEMP_CONFIG, /* TDP4 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN0 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN1 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN2 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN3 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN4 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN5 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN6 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN7 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN8 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN9 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN10 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN11 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN12 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN13 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN14 */ - NCT6694_HWMON_TEMP_CONFIG), /* DTIN15 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN0 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN1 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN2 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN3 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN4 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN5 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN6 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN7 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN8 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN9 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN10 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN11 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN12 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN13 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN14 */ + NCT6694_HWMON_DTIN_CONFIG), /* DTIN15 */ HWMON_CHANNEL_INFO(fan, NCT6694_HWMON_FAN_CONFIG, /* FIN0 */ From ef39fca8508597fa565cf2be72a884a712fb98af Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Thu, 3 Sep 2026 07:35:33 +0530 Subject: [PATCH 0708/1317] octeontx2-pf: reset HTB scheduler topology before freeing queues HTB offload programs NIX_AF_TLxX_TOPOLOGY on QoS-allocated scheduler queues via otx2_qos_txschq_set_parent_topology(), but teardown freed those queues without clearing TOPOLOGY. The AF only restores PARENT and SCHEDULE on free, so PRIO_ANCHOR/RR_PRIO settings can survive in the shared scheduler pool and affect later allocations. Add otx2_qos_reset_schq_topology() and otx2_qos_free_hw_schq() to zero TL4 through TL2 TOPOLOGY before each schq is returned to the AF during hierarchy teardown and cfg rollback. Skip the aggregation level (TL1): it is a per-tx-link queue shared by the PF, default Tx hierarchy and VFs, and is not freed back to the AF by nix_txschq_free_one(). Fixes: 5e6808b4c68d ("octeontx2-pf: Add support for HTB offload") Signed-off-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260903020533.3068041-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/marvell/octeontx2/nic/qos.c | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/qos.c b/drivers/net/ethernet/marvell/octeontx2/nic/qos.c index 69c0911e28e9..f160b1618efa 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/qos.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/qos.c @@ -235,13 +235,63 @@ static int otx2_qos_txschq_set_parent_topology(struct otx2_nic *pfvf, return rc; } +static int otx2_qos_reset_schq_topology(struct otx2_nic *pfvf, u16 lvl, + u16 schq) +{ + struct mbox *mbox = &pfvf->mbox; + struct nix_txschq_config *cfg; + int rc; + + if (lvl < NIX_TXSCH_LVL_TL4 || lvl >= NIX_TXSCH_LVL_TL1) + return 0; + + mutex_lock(&mbox->lock); + + cfg = otx2_mbox_alloc_msg_nix_txschq_cfg(mbox); + if (!cfg) { + mutex_unlock(&mbox->lock); + return -ENOMEM; + } + + cfg->lvl = lvl; + cfg->num_regs = 1; + + if (lvl == NIX_TXSCH_LVL_TL4) + cfg->reg[0] = NIX_AF_TL4X_TOPOLOGY(schq); + else if (lvl == NIX_TXSCH_LVL_TL3) + cfg->reg[0] = NIX_AF_TL3X_TOPOLOGY(schq); + else if (lvl == NIX_TXSCH_LVL_TL2) + cfg->reg[0] = NIX_AF_TL2X_TOPOLOGY(schq); + + cfg->regval[0] = 0; + + rc = otx2_sync_mbox_msg(mbox); + + mutex_unlock(&mbox->lock); + + return rc; +} + +static void otx2_qos_free_hw_schq(struct otx2_nic *pfvf, u16 lvl, u16 schq) +{ + int err; + + err = otx2_qos_reset_schq_topology(pfvf, lvl, schq); + if (err) + netdev_warn(pfvf->netdev, + "QoS: failed to reset topology for schq %u at level %u: %d\n", + schq, lvl, err); + + otx2_txschq_free_one(pfvf, lvl, schq); +} + static void otx2_qos_free_hw_node_schq(struct otx2_nic *pfvf, struct otx2_qos_node *parent) { struct otx2_qos_node *node; list_for_each_entry_reverse(node, &parent->child_schq_list, list) - otx2_txschq_free_one(pfvf, node->level, node->schq); + otx2_qos_free_hw_schq(pfvf, node->level, node->schq); } static void otx2_qos_free_hw_node(struct otx2_nic *pfvf, @@ -252,7 +302,7 @@ static void otx2_qos_free_hw_node(struct otx2_nic *pfvf, list_for_each_entry_safe(node, tmp, &parent->child_list, list) { otx2_qos_free_hw_node(pfvf, node); otx2_qos_free_hw_node_schq(pfvf, node); - otx2_txschq_free_one(pfvf, node->level, node->schq); + otx2_qos_free_hw_schq(pfvf, node->level, node->schq); } } @@ -266,7 +316,7 @@ static void otx2_qos_free_hw_cfg(struct otx2_nic *pfvf, otx2_qos_free_hw_node_schq(pfvf, node); /* free node hw mappings */ - otx2_txschq_free_one(pfvf, node->level, node->schq); + otx2_qos_free_hw_schq(pfvf, node->level, node->schq); mutex_unlock(&pfvf->qos.qos_lock); } @@ -913,7 +963,7 @@ static void otx2_qos_free_cfg(struct otx2_nic *pfvf, struct otx2_qos_cfg *cfg) for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) { for (idx = 0; idx < cfg->schq[lvl]; idx++) { schq = cfg->schq_list[lvl][idx]; - otx2_txschq_free_one(pfvf, lvl, schq); + otx2_qos_free_hw_schq(pfvf, lvl, schq); } } @@ -921,7 +971,7 @@ static void otx2_qos_free_cfg(struct otx2_nic *pfvf, struct otx2_qos_cfg *cfg) for (idx = 0; idx < cfg->schq_contig[lvl]; idx++) { if (cfg->schq_index_used[lvl][idx]) { schq = cfg->schq_contig_list[lvl][idx]; - otx2_txschq_free_one(pfvf, lvl, schq); + otx2_qos_free_hw_schq(pfvf, lvl, schq); } } } From 264bf9655c3d067d775a46f05eb8c871c488a864 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:09:25 +0200 Subject: [PATCH 0709/1317] scsi: pm80xx: Fix the use_msix, use_tasklet and read_wwn parameter descriptions The MODULE_PARM_DESC() lines of use_msix, use_tasklet and read_wwn all name a parameter zoned, which does not exist, and the use_tasklet one repeats the use_msix text. modinfo shows three "zoned" entries and no description for the real parameters. Name the right parameters and describe use_tasklet. Fixes: efa1fca45082 ("scsi: pm8001: Remove PM8001_USE_MSIX") Fixes: 205430290ad0 ("scsi: pm8001: Remove PM8001_USE_TASKLET") Fixes: 80975adc79dd ("scsi: pm8001: Remove PM8001_READ_VPD") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260906170925.2524-1-kmehltretter@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/pm8001/pm8001_init.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c index 54b35893261a..5af81c73a8f8 100644 --- a/drivers/scsi/pm8001/pm8001_init.c +++ b/drivers/scsi/pm8001/pm8001_init.c @@ -58,15 +58,15 @@ MODULE_PARM_DESC(link_rate, "Enable link rate.\n" bool pm8001_use_msix = true; module_param_named(use_msix, pm8001_use_msix, bool, 0444); -MODULE_PARM_DESC(zoned, "Use MSIX interrupts. Default: true"); +MODULE_PARM_DESC(use_msix, "Use MSIX interrupts. Default: true"); static bool pm8001_use_tasklet = true; module_param_named(use_tasklet, pm8001_use_tasklet, bool, 0444); -MODULE_PARM_DESC(zoned, "Use MSIX interrupts. Default: true"); +MODULE_PARM_DESC(use_tasklet, "Use tasklets for interrupt handling. Default: true"); static bool pm8001_read_wwn = true; module_param_named(read_wwn, pm8001_read_wwn, bool, 0444); -MODULE_PARM_DESC(zoned, "Get WWN from the controller. Default: true"); +MODULE_PARM_DESC(read_wwn, "Get WWN from the controller. Default: true"); uint pcs_event_log_severity = 0x03; module_param(pcs_event_log_severity, int, 0644); From 779f202a92ef10a426efc07d0f4267918cb07ca3 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:10:09 +0200 Subject: [PATCH 0710/1317] scsi: qla2xxx: Fix the ql2xfc2target parameter description The module parameter is ql2xfc2target, but its MODULE_PARM_DESC() names qla2xfc2target, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 877b03795fcf ("scsi: qla2xxx: Add option to disable FC2 Target support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260906171009.2560-1-kmehltretter@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index c0efdbff5da7..3c412c7fb6fe 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -352,7 +352,7 @@ MODULE_PARM_DESC(ql2xnvme_queues, int ql2xfc2target = 1; module_param(ql2xfc2target, int, 0444); -MODULE_PARM_DESC(qla2xfc2target, +MODULE_PARM_DESC(ql2xfc2target, "Enables FC2 Target support. " "0 - FC2 Target support is disabled. " "1 - FC2 Target support is enabled (default)."); From be83178bfc44588f6e3adb827ed874c683193466 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 6 Sep 2026 18:01:04 +0000 Subject: [PATCH 0711/1317] vxlan: initialize _md in vxlan_xmit_one() If a VXLAN device is configured with both VXLAN_F_COLLECT_METADATA and VXLAN_F_GBP, and a packet is transmitted through it using an external ip_tunnel_info that lacks the IP_TUNNEL_VXLAN_OPT_BIT flag, md is left pointing to the uninitialized _md stack variable: if (test_bit(IP_TUNNEL_VXLAN_OPT_BIT, info->key.tun_flags)) { if (info->options_len < sizeof(*md)) goto drop; md = ip_tunnel_info_opts(info); } Because IP_TUNNEL_VXLAN_OPT_BIT is not set, md is not updated and remains pointing to _md. Later, vxlan_build_skb() is called with md, which eventually calls vxlan_build_gbp_hdr(): if (vxflags & VXLAN_F_GBP) vxlan_build_gbp_hdr(vxh, md); Inside vxlan_build_gbp_hdr(), md->gbp is read: if (!md->gbp) return; gbp = (struct vxlanhdr_gbp *)vxh; ... if (md->gbp & VXLAN_GBP_DONT_LEARN) gbp->dont_learn = 1; If the stack contains garbage, this causes: 1) VXLAN_HF_GBP flag to be spuriously set in the VXLAN header. 2) gbp->dont_learn and gbp->policy_applied to be set from stack bits. 3) gbp->policy_id to receive 16 bits of uninitialized kernel stack data, leaking it onto the wire. Fix this by zero-initializing _md. If IP_TUNNEL_VXLAN_OPT_BIT is not present, md->gbp remains 0, and vxlan_build_gbp_hdr() returns early without modifying the VXLAN header. Fixes: ee122c79d422 ("vxlan: Flow based tunneling") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260906180111.1973188-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index be95af64a1f5..c1d54339fa2b 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2373,7 +2373,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, struct ip_tunnel_key key; struct vxlan_dev *vxlan = netdev_priv(dev); const struct iphdr *old_iph; - struct vxlan_metadata _md; + struct vxlan_metadata _md = {}; struct vxlan_metadata *md = &_md; unsigned int pkt_len = skb->len; __be16 src_port = 0, dst_port; From 1274045b0eda1df5a5cdd3e63ed48cf013b3b4ea Mon Sep 17 00:00:00 2001 From: Venkat Rao Bagalkote Date: Thu, 3 Sep 2026 13:13:29 +0530 Subject: [PATCH 0712/1317] scsi: ibmvfc: Add Kconfig dependency to fix link failure when NVME_FC=m Building with CONFIG_SCSI_IBMVFC=y and CONFIG_NVME_FC=m results in a link failure: ibmvfc-nvme.o: undefined reference to `nvme_fc_register_localport' ibmvfc-nvme.o: undefined reference to `nvme_fc_register_remoteport' ibmvfc-nvme.o: undefined reference to `nvme_fc_unregister_localport' ibmvfc-nvme.o: undefined reference to `nvme_fc_unregister_remoteport' ibmvfc-core.o: undefined reference to `nvme_fc_rescan_remoteport' IS_ENABLED() evaluates to 1 for both =y and =m, so the nvme_fc_* call sites are kept in the object file. When SCSI_IBMVFC=y (built-in) but NVME_FC=m (loadable module), the linker cannot resolve these symbols at vmlinux link time. Add the same "depends on NVME_FC || NVME_FC=n" constraint already used by SCSI_LPFC to prevent this configuration. Reported-by: Pavithra Closes: https://lore.kernel.org/all/327877a29337aa526cc50ac88fbddb86@linux.ibm.com/ Signed-off-by: Venkat Rao Bagalkote Tested-by: Pavithra Acked-by: Tyrel Datwyler Link: https://patch.msgid.link/20260903074329.6705-1-venkat88@linux.ibm.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 4a2af0f702e1..1eec66195cf4 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -753,6 +753,7 @@ config SCSI_IBMVFC tristate "IBM Virtual FC support" depends on PPC_PSERIES && SCSI depends on SCSI_FC_ATTRS + depends on NVME_FC || NVME_FC=n help This is the IBM POWER Virtual FC Client From 8aaeb56aff2a557a88f83ae866da2c91ad247e59 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Tue, 8 Sep 2026 15:21:31 +0800 Subject: [PATCH 0713/1317] ppp_synctty: ensure a writeable skb header ppp_sync_txmunge() checks headroom before prepending the address and control bytes, but does not ensure that the skb header is writable. A received skb can reach this function through PPP channel bridging without passing through ppp_start_xmit(), which calls skb_cow_head(). For example, a PPPoE frame may share its buffer with a clone queued to an AF_PACKET socket. If it is bridged to a synchronous tty channel, the address/control bytes can overwrite data still visible to that socket. Use skb_cow_head() to ensure both sufficient headroom and a writable header. Fixes: 4cf476ced45d ("ppp: add PPPIOCBRIDGECHAN and PPPIOCUNBRIDGECHAN ioctls") Signed-off-by: Qingfang Deng Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260908072135.877364-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_synctty.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c index f87d43faeeab..ebd62a7ab54b 100644 --- a/drivers/net/ppp/ppp_synctty.c +++ b/drivers/net/ppp/ppp_synctty.c @@ -455,17 +455,9 @@ ppp_sync_txmunge(struct syncppp *ap, struct sk_buff *skb) /* prepend address/control fields if necessary */ if ((ap->flags & SC_COMP_AC) == 0 || islcp) { - if (skb_headroom(skb) < 2) { - struct sk_buff *npkt = dev_alloc_skb(skb->len + 2); - if (npkt == NULL) { - kfree_skb(skb); - return NULL; - } - skb_reserve(npkt,2); - skb_copy_from_linear_data(skb, - skb_put(npkt, skb->len), skb->len); - consume_skb(skb); - skb = npkt; + if (skb_cow_head(skb, 2)) { + kfree_skb(skb); + return NULL; } skb_push(skb,2); skb->data[0] = PPP_ALLSTATIONS; From 0cb1fd924126f1f581621a5e804df98a02be9dff Mon Sep 17 00:00:00 2001 From: Arun Easi Date: Thu, 3 Sep 2026 10:55:47 -0700 Subject: [PATCH 0714/1317] scsi: fnic: Fix missed link-up when critical IRQ targets offline CPU When CPU Hyper Threading is disabled, sibling CPUs remain present but are reported offline. Managed MSI-X IRQs can still receive affinity masks that include those offline CPUs. If a driver-critical vector is managed, it can be parked on an offline CPU and the driver may miss critical events such as link-up. Keep driver-critical vectors unmanaged so they can be migrated by the IRQ core when their target CPU is offlined. Since HWQ-0 is unmanaged now, in some queue combinations there can be no mappings to it in mq_map. So without the blk-mq fix mentioned below, system may crash during cpu offline/online tests. Fixes: 8a8449ca5e33 ("scsi: fnic: Modify ISRs to support multiqueue (MQ)") Cc: stable@vger.kernel.org Depends-on: commit 10845a105bbc ("blk-mq: skip CPU offline notify on unmapped hctx") Reviewed-by: Sesidhar Baddela Reviewed-by: Arulprabhu Ponnusamy Reviewed-by: Gian Carlo Boffa Reviewed-by: Karan Tilak Kumar Signed-off-by: Arun Easi Reviewed-by: Laurence Oberman Link: https://patch.msgid.link/20260903175547.57971-1-aeasi@cisco.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/fnic/fnic.h | 2 +- drivers/scsi/fnic/fnic_isr.c | 13 ++++++++--- drivers/scsi/fnic/fnic_main.c | 41 ++++++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/fnic/fnic.h b/drivers/scsi/fnic/fnic.h index c576a7f5083e..3ba1592940ca 100644 --- a/drivers/scsi/fnic/fnic.h +++ b/drivers/scsi/fnic/fnic.h @@ -31,7 +31,7 @@ #define DRV_NAME "fnic" #define DRV_DESCRIPTION "Cisco FCoE HBA Driver" -#define DRV_VERSION "1.9.0.0" +#define DRV_VERSION "1.9.0.1" #define PFX DRV_NAME ": " #define DFX DRV_NAME "%d: " diff --git a/drivers/scsi/fnic/fnic_isr.c b/drivers/scsi/fnic/fnic_isr.c index 02856745580f..43149d8312ec 100644 --- a/drivers/scsi/fnic/fnic_isr.c +++ b/drivers/scsi/fnic/fnic_isr.c @@ -245,7 +245,14 @@ int fnic_set_intr_mode_msix(struct fnic *fnic) unsigned int m = ARRAY_SIZE(fnic->wq); unsigned int o = ARRAY_SIZE(fnic->hw_copy_wq); unsigned int min_irqs = n + m + 1 + 1; /*rq, raw wq, wq, err*/ - + /* + * Make driver critical vectors unmanaged, or else it can get tied + * to an offline CPU. This can happen when hyper-threading is off. + */ + struct irq_affinity affd = { + .pre_vectors = n + m + 1, /* rq, raw wq, 1 ioq */ + .post_vectors = 1, /* err */ + }; /* * We need n RQs, m WQs, o Copy WQs, n+m+o CQs, and n+m+o+1 INTRs * (last INTR is used for WQ/RQ errors and notification area) @@ -263,8 +270,8 @@ int fnic_set_intr_mode_msix(struct fnic *fnic) int vec_count = 0; int vecs = fnic->rq_count + fnic->raw_wq_count + fnic->wq_copy_count + 1; - vec_count = pci_alloc_irq_vectors(fnic->pdev, min_irqs, vecs, - PCI_IRQ_MSIX | PCI_IRQ_AFFINITY); + vec_count = pci_alloc_irq_vectors_affinity(fnic->pdev, min_irqs, + vecs, PCI_IRQ_MSIX|PCI_IRQ_AFFINITY, &affd); FNIC_ISR_DBG(KERN_INFO, fnic, "allocated %d MSI-X vectors\n", vec_count); diff --git a/drivers/scsi/fnic/fnic_main.c b/drivers/scsi/fnic/fnic_main.c index 9b3025007075..f13c381a66d7 100644 --- a/drivers/scsi/fnic/fnic_main.c +++ b/drivers/scsi/fnic/fnic_main.c @@ -744,8 +744,19 @@ static int fnic_nvme_drv_init(struct fnic *fnic) return ret; } +static void fnic_mq_init_queue_map(struct fnic *fnic, + struct blk_mq_queue_map *qmap) +{ + unsigned int cpu; + + for_each_possible_cpu(cpu) + qmap->mq_map[cpu] = 0; +} + void fnic_mq_map_queues_cpus(struct Scsi_Host *host) { + const struct cpumask *mask; + unsigned int queue, cpu; struct fnic *fnic = *((struct fnic **) shost_priv(host)); struct pci_dev *l_pdev = fnic->pdev; int intr_mode = fnic->config.intr_mode; @@ -766,7 +777,35 @@ void fnic_mq_map_queues_cpus(struct Scsi_Host *host) return; } - blk_mq_map_hw_queues(qmap, &l_pdev->dev, FNIC_PCI_OFFSET); + fnic_mq_init_queue_map(fnic, qmap); + + /* + * Setup CPU to Queue mapping for all managed MSI-X IRQs. + * Q0 is driver critical and non-managed, hence start from Q1. + */ + for (queue = 1; queue < qmap->nr_queues; queue++) { + int irq_num = pci_irq_vector(fnic->pdev, + queue + FNIC_PCI_OFFSET); + + if (irq_num < 0) + continue; + + mask = pci_irq_get_affinity(fnic->pdev, + queue + FNIC_PCI_OFFSET); + if (!mask) { + shost_printk(KERN_ERR, host, + "failed to get irq_affinity map for queue:%d\n", irq_num); + continue; + } + FNIC_MAIN_DBG(KERN_INFO, fnic, + "got irq_affinity map for %d:\n", irq_num); + for_each_cpu(cpu, mask) { + qmap->mq_map[cpu] = qmap->queue_offset + queue; + FNIC_MAIN_DBG(KERN_INFO, fnic, + "[Q%d] cpu:%d <=> irq:%d\n", + queue, cpu, irq_num); + } + } } static int fnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) From 113998aa372f4869bf62cfc75c28a2849e8487be Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 4 Sep 2026 18:55:40 +0000 Subject: [PATCH 0715/1317] net: phylink: initialise link_state before a forced major config phylink_resolve() leaves link_state on the stack unpopulated on its disable and link-failed branches, which set only link_state.link. phylink_apply_manual_flow() then reads the struct's advertising on every mode but MLO_AN_FIXED, and has done so since long before force_major_config existed. force_major_config turns that into a write to the hardware. It is the only trigger for the major-config block that does not require mac_config, so phylink_major_config() programs the MAC for whatever the stack held, a zeroed interface is PHY_INTERFACE_MODE_NA, and the write-back stores it in pl->link_config.interface. phylink_replay_link_end() is the only in-tree setter, and sja1105_static_config_reload() calls it for every port that has a phylink instance, regardless of admin state. On a stopped port phylink_run_resolve() no-ops, so the flag outlives the call. The next resolve consumes it whatever branch it takes; an unpopulated branch is where that does damage. Found while developing a series that attaches a late PHY from a delayed work item and sets this flag there, so the PHY attached after its port was already up. The link stayed down until the port was cycled 29 minutes later. With this patch on the same board the same attach programs the MAC for 2500base-x rather than unknown, and the PHY's interrupt fires without a port bounce where it had stayed at zero throughout the failure. Fixes: 96969b132bf1 ("net: phylink: introduce helpers for replaying link callbacks") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260904185540.2844261-1-f@lex.la Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 3ec3bb439109..a1458da8111b 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -1630,8 +1630,10 @@ static void phylink_resolve(struct work_struct *w) if (pl->phylink_disable_state) { pl->link_failed = false; + link_state = pl->link_config; link_state.link = false; } else if (pl->link_failed) { + link_state = pl->link_config; link_state.link = false; retrigger = true; } else if (pl->act_link_an_mode == MLO_AN_FIXED) { From 0338c68e22abd2ee509ec2e32508a50896618c32 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 4 Sep 2026 12:32:55 +0200 Subject: [PATCH 0716/1317] net: stmmac: initialize ptp_lock at probe time priv->ptp_lock is only initialized in stmmac_ptp_register(), which runs during __stmmac_open(). However, the lock is also used while the interface is down and has never been opened: tc_taprio_configure() invokes the PTP gettime64() callback to compute the EST base time when offloading a TAPRIO schedule, and stmmac_get_time() takes priv->ptp_lock. Using an uninitialized rwlock is undefined behaviour. Move the rwlock_init() to __stmmac_dvr_probe(), together with the other private locks, so that ptp_lock is always valid regardless of the interface state. Fixes: b60189e0392f ("net: stmmac: Integrate EST with TAPRIO scheduler API") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260904-stmmac-fix-ptp-clock-init-v1-1-df70eb1eb04d@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 24656b35350b..5fe7e95fdd34 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -8025,6 +8025,7 @@ static int __stmmac_dvr_probe(struct device *device, stmmac_napi_add(ndev); mutex_init(&priv->lock); + rwlock_init(&priv->ptp_lock); stmmac_fpe_init(priv); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 960249960004..3bfcc9760dce 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -365,7 +365,6 @@ void stmmac_ptp_register(struct stmmac_priv *priv) if (priv->plat->crosststamp) priv->ptp_clock_ops.getcrosststamp = stmmac_getcrosststamp; - rwlock_init(&priv->ptp_lock); mutex_init(&priv->aux_ts_lock); priv->ptp_clock = ptp_clock_register(&priv->ptp_clock_ops, From 3d676e458fe0c566f5a62753dc696b6a862fc412 Mon Sep 17 00:00:00 2001 From: Alberto Carboneri Date: Fri, 4 Sep 2026 13:54:37 +0000 Subject: [PATCH 0717/1317] scsi: core: Validate MODE SENSE lengths in scsi_cdl_enable() scsi_cdl_enable() uses length fields returned by MODE SENSE to locate the ATA feature mode page in a 64-byte stack buffer. A target can report a total length shorter than its mode header and block descriptors. The unsigned subtraction used for the MODE SELECT length can wrap, and the separately computed buf_data can point beyond buf. During automatic scan, enable is false, so the read-modify-write of buf_data[4] can clear the low two bits of a target-selected out-of-bounds stack byte. scsi_mode_select() can then copy up to 64 bytes from outside the buffer into the outgoing MODE SELECT payload, disclosing stack contents to the target. This is reachable while scanning a USB storage device that identifies as an ATA device and advertises CDL support. No filesystem mount or userspace access to the block device is required. On upstream commit cee9395acd80 ("Linux 7.3-rc1"), a build-specific, one-vCPU QEMU/Raw Gadget proof using QEMU-only multi-UDC allocator sampling executed a fixed proof command inside the guest and created a UID-0-owned marker during automatic enumeration, with KASLR and NX enabled. The issue was independently found during security research at Drivesec S.r.l. Cap the available length to the buffer size. Validate and consume the mode header and block descriptor lengths before using the page, and require the five bytes needed to access the CDL field. Fixes: 1b22cfb14142 ("scsi: core: Allow enabling and disabling command duration limits") Reported-by: Sashiko AI Review Closes: https://lore.kernel.org/linux-scsi/20260717192313.93D791F000E9@smtp.kernel.org/ Link: https://lore.kernel.org/linux-scsi/20260717222931.AC4EE1F000E9@smtp.kernel.org/ Link: https://lore.kernel.org/linux-scsi/df13ec87ac9b28e3b0a2d9eb26477e276ff0278a.camel@HansenPartnership.com/ Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Pimen Flavian Dei (Drivesec S.r.l.) Signed-off-by: Pimen Flavian Dei (Drivesec S.r.l.) Signed-off-by: Alberto Carboneri (Drivesec S.r.l.) Link: https://lore.kernel.org/linux-scsi/20260717192313.93D791F000E9@smtp.kernel.org/ Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260904135410.360314-1-acarboneri@drivesec.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c index 76cdad063f7b..f285521d9de6 100644 --- a/drivers/scsi/scsi.c +++ b/drivers/scsi/scsi.c @@ -727,6 +727,7 @@ int scsi_cdl_enable(struct scsi_device *sdev, bool enable) struct scsi_mode_data data; struct scsi_sense_hdr sshdr; char *buf_data; + size_t avail, offset; int len; ret = scsi_mode_sense(sdev, 0x08, 0x0a, 0xf2, buf, sizeof(buf), @@ -735,11 +736,24 @@ int scsi_cdl_enable(struct scsi_device *sdev, bool enable) return -EINVAL; /* Enable or disable CDL using the ATA feature page */ - len = min_t(size_t, sizeof(buf), - data.length - data.header_length - - data.block_descriptor_length); - buf_data = buf + data.header_length + - data.block_descriptor_length; + avail = min_t(size_t, data.length, sizeof(buf)); + if (data.header_length > avail) + return -EINVAL; + + offset = data.header_length; + avail -= data.header_length; + + if (data.block_descriptor_length > avail) + return -EINVAL; + + offset += data.block_descriptor_length; + avail -= data.block_descriptor_length; + + if (avail < 5) + return -EINVAL; + + buf_data = buf + offset; + len = avail; /* * If we want to enable CDL and CDL is already enabled on the From 7f26a5e8040b4957ef4dbdfcde6cc7ba2db53937 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Sun, 6 Sep 2026 12:07:00 +0300 Subject: [PATCH 0718/1317] net/mlx5e: Move representor vnic reporter to eswitch devlink port The representor vnic devlink health reporter is created and destroyed along the representor netdev (un)load path, which is not serialized by the devlink instance lock. Destroying the reporter from there triggers a devl_assert_locked() splat on driver unbind: WARNING: net/devlink/core.c:259 at devl_assert_locked+0x54/0x70, CPU#2: bash/3758 Modules linked in: mlx5_vdpa vringh vdpa mlx5_ib mlx5_fwctl mlx5_core ... CPU: 2 UID: 0 PID: 3758 Comm: bash Tainted: G W 6.19.0+ #1 PREEMPT Tainted: [W]=WARN Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), ... RIP: 0010:devl_assert_locked+0x54/0x70 Call Trace: devl_health_reporter_destroy+0x3a/0x1b0 mlx5e_vport_rep_unload+0x12d/0x2b0 [mlx5_core] mlx5_eswitch_unregister_vport_reps+0x1b8/0x220 [mlx5_core] ? __esw_offloads_unload_rep+0x190/0x190 [mlx5_core] ? kernfs_remove_by_name_ns+0xc3/0xf0 device_release_driver_internal+0x3b2/0x560 unbind_store+0xce/0xf0 Move the reporter's lifecycle to the eswitch devlink port (un)register paths, which are already serialized by the devlink instance lock, and store the handle on mlx5_devlink_port. Use the port's mlx5_vport as the reporter priv since the diagnose callback only needs a device handle and a vport number, and mlx5_vport carries both and is initialized before any representor driver probes. Fixes: cf14af140a5a ("net/mlx5e: Add vnic devlink health reporter to representors") Signed-off-by: Carolina Jubran Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260906090700.3761260-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/en_rep.c | 52 +------------------ .../net/ethernet/mellanox/mlx5/core/en_rep.h | 1 - .../mellanox/mlx5/core/esw/devlink_port.c | 37 +++++++++++++ .../net/ethernet/mellanox/mlx5/core/eswitch.h | 1 + 4 files changed, 39 insertions(+), 52 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index ce765692fd19..88a170e40bd9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -56,7 +56,6 @@ #include "lib/vxlan.h" #define CREATE_TRACE_POINTS #include "diag/en_rep_tracepoint.h" -#include "diag/reporter_vnic.h" #include "en_accel/ipsec.h" #include "en/tc/int_port.h" #include "en/ptp.h" @@ -1439,51 +1438,6 @@ static unsigned int mlx5e_ul_rep_stats_grps_num(struct mlx5e_priv *priv) return ARRAY_SIZE(mlx5e_ul_rep_stats_grps); } -static int -mlx5e_rep_vnic_reporter_diagnose(struct devlink_health_reporter *reporter, - struct devlink_fmsg *fmsg, - struct netlink_ext_ack *extack) -{ - struct mlx5e_rep_priv *rpriv = devlink_health_reporter_priv(reporter); - struct mlx5_eswitch_rep *rep = rpriv->rep; - - mlx5_reporter_vnic_diagnose_counters(rep->esw->dev, fmsg, rep->vport, - true); - return 0; -} - -static const struct devlink_health_reporter_ops mlx5_rep_vnic_reporter_ops = { - .name = "vnic", - .diagnose = mlx5e_rep_vnic_reporter_diagnose, -}; - -static void mlx5e_rep_vnic_reporter_create(struct mlx5e_priv *priv, - struct devlink_port *dl_port) -{ - struct mlx5e_rep_priv *rpriv = priv->ppriv; - struct devlink_health_reporter *reporter; - - reporter = devl_port_health_reporter_create(dl_port, - &mlx5_rep_vnic_reporter_ops, - rpriv); - if (IS_ERR(reporter)) { - mlx5_core_err(priv->mdev, - "Failed to create representor vnic reporter, err = %pe\n", - reporter); - return; - } - - rpriv->rep_vnic_reporter = reporter; -} - -static void mlx5e_rep_vnic_reporter_destroy(struct mlx5e_priv *priv) -{ - struct mlx5e_rep_priv *rpriv = priv->ppriv; - - if (!IS_ERR_OR_NULL(rpriv->rep_vnic_reporter)) - devl_health_reporter_destroy(rpriv->rep_vnic_reporter); -} - static const struct mlx5e_profile mlx5e_rep_profile = { .init = mlx5e_init_rep, .cleanup = mlx5e_cleanup_rep, @@ -1607,10 +1561,8 @@ mlx5e_vport_vf_rep_load(struct mlx5_core_dev *dev, struct mlx5_eswitch_rep *rep) dl_port = mlx5_esw_offloads_devlink_port(dev->priv.eswitch, rpriv->rep->vport); - if (!IS_ERR(dl_port)) { + if (!IS_ERR(dl_port)) SET_NETDEV_DEVLINK_PORT(netdev, dl_port); - mlx5e_rep_vnic_reporter_create(priv, dl_port); - } err = register_netdev(netdev); if (err) { @@ -1623,7 +1575,6 @@ mlx5e_vport_vf_rep_load(struct mlx5_core_dev *dev, struct mlx5_eswitch_rep *rep) return 0; err_detach_netdev: - mlx5e_rep_vnic_reporter_destroy(priv); mlx5e_detach_netdev(netdev_priv(netdev)); err_cleanup_profile: priv->profile->cleanup(priv); @@ -1681,7 +1632,6 @@ mlx5e_vport_rep_unload(struct mlx5_eswitch_rep *rep) } unregister_netdev(netdev); - mlx5e_rep_vnic_reporter_destroy(priv); mlx5e_detach_netdev(priv); priv->profile->cleanup(priv); mlx5e_destroy_netdev(netdev); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h index 70640fa1ad7b..bcd7b4e814d0 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h @@ -118,7 +118,6 @@ struct mlx5e_rep_priv { struct rtnl_link_stats64 prev_vf_vport_stats; struct mlx5_flow_handle *send_to_vport_meta_rule; struct rhashtable tc_ht; - struct devlink_health_reporter *rep_vnic_reporter; }; static inline diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c index 36b00a856bc2..fdc960ea5331 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c @@ -4,6 +4,26 @@ #include #include "eswitch.h" #include "devlink.h" +#include "diag/reporter_vnic.h" + +static int +mlx5_esw_rep_vnic_reporter_diagnose(struct devlink_health_reporter *reporter, + struct devlink_fmsg *fmsg, + struct netlink_ext_ack *extack) +{ + struct mlx5_vport *vport = devlink_health_reporter_priv(reporter); + + mlx5_reporter_vnic_diagnose_counters(vport->dev, fmsg, vport->vport, + true); + + return 0; +} + +static const +struct devlink_health_reporter_ops mlx5_esw_rep_vnic_reporter_ops = { + .name = "vnic", + .diagnose = mlx5_esw_rep_vnic_reporter_diagnose, +}; static void mlx5_esw_get_port_parent_id(struct mlx5_core_dev *dev, struct netdev_phys_item_id *ppid) @@ -220,6 +240,7 @@ static void mlx5_esw_devlink_port_res_unregister(struct devlink_port *dl_port) int mlx5_esw_offloads_devlink_port_register(struct mlx5_eswitch *esw, struct mlx5_vport *vport) { + struct devlink_health_reporter *reporter; struct mlx5_core_dev *dev = esw->dev; const struct devlink_port_ops *ops; struct mlx5_devlink_port *dl_port; @@ -255,6 +276,16 @@ int mlx5_esw_offloads_devlink_port_register(struct mlx5_eswitch *esw, struct mlx mlx5_core_dbg(dev, "Failed to register port resources: %d\n", err); + reporter = devl_port_health_reporter_create( + &dl_port->dl_port, &mlx5_esw_rep_vnic_reporter_ops, + vport); + if (IS_ERR(reporter)) + mlx5_core_err(dev, + "Failed to create vnic health reporter for vport %d: %pe\n", + vport_num, reporter); + else + dl_port->vnic_reporter = reporter; + return 0; rate_err: @@ -269,6 +300,12 @@ void mlx5_esw_offloads_devlink_port_unregister(struct mlx5_vport *vport) if (!vport->dl_port) return; dl_port = vport->dl_port; + + if (dl_port->vnic_reporter) { + devl_health_reporter_destroy(dl_port->vnic_reporter); + dl_port->vnic_reporter = NULL; + } + mlx5_esw_devlink_port_res_unregister(&dl_port->dl_port); devl_rate_leaf_destroy(&dl_port->dl_port); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index c655f6e8da1c..8b1f93b13ea9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -189,6 +189,7 @@ struct mlx5_vport; struct mlx5_devlink_port { struct devlink_port dl_port; struct mlx5_vport *vport; + struct devlink_health_reporter *vnic_reporter; }; static inline void mlx5_devlink_port_init(struct mlx5_devlink_port *dl_port, From 11ae2e1dc58304a48816fc8ca4afa8f2ef9d1bdf Mon Sep 17 00:00:00 2001 From: Aboorva Devarajan Date: Fri, 4 Sep 2026 08:28:30 +0530 Subject: [PATCH 0719/1317] powerpc/entry: Fix double accounting of user time on interrupt entry Since the switch to generic entry, an interrupt from user mode accounts user time twice: once in arch_interrupt_enter_prepare() and again in arch_enter_from_user_mode(), which irqentry_enter() invokes for the same interrupt: arch_interrupt_enter_prepare() account_cpu_user_entry() /* first */ irqentry_enter() arch_enter_from_user_mode() account_cpu_user_entry() /* second */ The second call charges the same interval again, because account_cpu_user_entry() accumulates the time spent in user mode since the last return to user space. The two calls come from the GENERIC_ENTRY preparation series, where each step was a no-op on its own. Commit 09a9d3a8499d ("powerpc: introduce arch_enter_from_user_mode") added the hook with the user-time accounting in it, but nothing called it yet. Commit 893082ac769b ("powerpc: Prepare for IRQ entry exit") copied interrupt_enter_prepare() verbatim into entry-common.h as arch_interrupt_enter_prepare(); that copy was equally unused, as handlers still called interrupt_enter_prepare(). Commit bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") made both live. On the syscall side it did the full conversion: system_call_exception() now accounts once through the hook via syscall_enter_from_user_mode(), rather than calling account_cpu_user_entry() directly. On the interrupt side it switched the handler macros to arch_interrupt_enter_prepare() followed by irqentry_enter(), which also runs the hook, but the accounting in arch_interrupt_enter_prepare() was not removed to match. The double accounting starts with that commit. With CONFIG_VIRT_CPU_ACCOUNTING_NATIVE=y this roughly doubles the reported user time of any workload that takes interrupts. The other accounting modes compile account_cpu_user_entry() to an empty stub, so they are not affected. Remove the accounting from arch_interrupt_enter_prepare() and rely on arch_enter_from_user_mode(), which already runs for both syscalls and interrupts. The duplicate account_stolen_time() call is removed the same way. On a pseries LPAR a busy loop reports 6s user time in 3s elapsed (~210% CPU) before the fix, and 3s (~105% CPU) after it: $ python3 -c 'while True: pass' & $ sleep 3; ps -p $! -o etime,time,pcpu ELAPSED TIME %CPU Before 00:03 00:00:06 210 After 00:03 00:00:03 105 A 50% load reports ~70% usr / 30% idle before the fix, and ~49% usr / 51% idle after it: $ taskset -c 6 stress-ng --cpu 1 --cpu-load 50 & $ mpstat -P 6 1 CPU %usr %idle Before 6 69.74 30.26 After 6 48.51 50.50 Fixes: bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Aboorva Devarajan Tested-by: Venkat Rao Bagalkote Reviewed-by: Amit Machhiwal Reviewed-by: Ritesh Harjani (IBM) Reviewed-by: Christophe Leroy (CS GROUP) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260904025831.3439809-1-aboorvad@linux.ibm.com --- arch/powerpc/include/asm/entry-common.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/powerpc/include/asm/entry-common.h b/arch/powerpc/include/asm/entry-common.h index 80b07750b531..8e91489fdf2b 100644 --- a/arch/powerpc/include/asm/entry-common.h +++ b/arch/powerpc/include/asm/entry-common.h @@ -222,8 +222,6 @@ static inline void arch_interrupt_enter_prepare(struct pt_regs *regs) if (user_mode(regs)) { kuap_lock(); - account_cpu_user_entry(); - account_stolen_time(); } else { kuap_save_and_lock(regs); /* From ef17515a8ef88c246c342a6c532aa10f9ecdcfaf Mon Sep 17 00:00:00 2001 From: Athira Rajeev Date: Thu, 3 Sep 2026 13:10:36 +0530 Subject: [PATCH 0720/1317] selftests/powerpc/pmu/ebb: fix lost_exception_test hang with sched yield change commit 79104becf42b ("sched/fair: Forfeit vruntime on yield") changed yield_task_fair() to only bump the deadline when the entity is eligible (vruntime <= avg_vruntime). When the entity is ineligible the yield becomes a complete no-op from scheduling perspective. lost_exception_test calls sched_yield() 100,000 times per iteration to race the EBB exception delivery with a context switch to the eat_cpu companion process. After enough iterations the test process's vruntime races ahead of avg_vruntime (each eligible yield bumps vruntime to deadline, then advances deadline by one slice). Once ineligible, yield_task_fair() does nothing: so the scheduler won't pick the eat_cpu child. No context switch occurs, the PMAO race is never triggered, and ebb_count stays at 0 forever causing the test to hang until timeout. Fix by replacing sched_yield() with nanosleep(0, 1ns). nanosleep() goes through hrtimer_nanosleep() -> do_nanosleep(), which puts the task into TASK_INTERRUPTIBLE and removes it from the run queue entirely. This guarantees the scheduler picks the eat_cpu child, restoring the context-switch guarantee the test requires. The 1ns duration is enough to engage the hrtimer path while keeping the sleep effectively instantaneous; the same race window between PMU overflow and context switch is preserved. Reported-by: Venkat Rao Bagalkote Signed-off-by: Athira Rajeev Tested-by: Venkat Rao Bagalkote Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260903074036.63309-1-atrajeev@linux.ibm.com --- .../selftests/powerpc/pmu/ebb/lost_exception_test.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c b/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c index ba2681a12cc7..9be5945f3b1f 100644 --- a/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c +++ b/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "ebb.h" @@ -22,6 +23,7 @@ static int test_body(void) { int i, orig_period, max_period; struct event event; + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1 }; SKIP_IF(!ebb_is_supported()); @@ -57,10 +59,15 @@ static int test_body(void) * kernel to decide our timeslice is up and context switch to * the other thread. When we come back our EBB will have been * lost and we'll spin in this while loop forever. + * + * Use nanosleep(0) instead of sched_yield() to guarantee a + * context switch to the eat_cpu child regardless of the + * eligibility state. sched_yield() via yield_task_fair() may + * become a no-op when the task is ineligible (vruntime ahead + * of avg_vruntime), preventing the required context switch. */ - for (i = 0; i < 100000; i++) - sched_yield(); + nanosleep(&ts, NULL); /* Change the sample period slightly to try and hit the race */ if (sample_period >= (orig_period + 200)) From ed28b16eab705071d28edaace47189c2eb3aa108 Mon Sep 17 00:00:00 2001 From: Thibault Ferrante Date: Mon, 7 Sep 2026 23:54:20 +0200 Subject: [PATCH 0721/1317] selftests/powerpc/tm: Fix tcheck() reading uninitialised CR value tcheck() is used to check the current transaction state (active, suspended, doomed) via the "tcheck" instruction, which writes its result into CR field 0. The inline asm declared a GPR output operand for this result but never actually moved the CR into it. Every caller (tcheck_doomed(), tcheck_active(), tcheck_suspended(), tcheck_transactional()) has effectively been testing bits of an unrelated, arbitrary register value since this helper was introduced. The "& 4" mask discards the TDOOMED and TS_lsb (suspended) bits before they ever reach the callers, so tcheck_doomed() and tcheck_suspended() can never return true, and tcheck_transactional() degrades to being equivalent to tcheck_active(). Fix tcheck() to actually move CR into the output register with mfcr, and widen the mask from "& 4" to "& 0xf" so the full CR0 nibble (TDOOMED | TS_msb | TS_lsb | reserved) is preserved for the callers. This bug has been present since tcheck() was introduced. Link: https://bugs.launchpad.net/bugs/2107442 Fixes: 8e03bd4e70b6 ("selftests/powerpc: Add TM tcheck helpers in C") Signed-off-by: Thibault Ferrante Reported-by: Venkat Rao Bagalkote Tested-by: Venkat Rao Bagalkote Closes: https://lore.kernel.org/all/364996ce-aba2-4213-8d20-7dd481b43fe6@linux.ibm.com/ Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260907215420.1258678-1-thibault.ferrante@canonical.com --- tools/testing/selftests/powerpc/tm/tm.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/powerpc/tm/tm.h b/tools/testing/selftests/powerpc/tm/tm.h index c03c6e778876..6024ce4ba6ff 100644 --- a/tools/testing/selftests/powerpc/tm/tm.h +++ b/tools/testing/selftests/powerpc/tm/tm.h @@ -105,8 +105,12 @@ static inline bool failure_is_nesting(void) static inline int tcheck(void) { long cr; - asm volatile ("tcheck 0" : "=r"(cr) : : "cr0"); - return (cr >> 28) & 4; + asm volatile("tcheck 0;" + "mfcr %0;" + : "=r"(cr) + : + : "cr0"); + return (cr >> 28) & 0xf; } static inline bool tcheck_doomed(void) From 10557fe7fc9e09d273f8575274be2bbbe255dbf0 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Tue, 4 Aug 2026 22:55:23 +0300 Subject: [PATCH 0722/1317] powerpc/pseries/htmdump: Fix leak in htmdump_init_debugfs If any allocation fails during init all previous allocations are leaked and the debugfs directory is left. Fix by freeing the allocations that have already happened and also remove the directory that has been created. Signed-off-by: Michail Tatas Reviewed-by: Athira Rajeev Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/anJDq-JckR6j-6EJ@michalis-linux --- arch/powerpc/platforms/pseries/htmdump.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/arch/powerpc/platforms/pseries/htmdump.c b/arch/powerpc/platforms/pseries/htmdump.c index 489a80e87082..f33941b80ada 100644 --- a/arch/powerpc/platforms/pseries/htmdump.c +++ b/arch/powerpc/platforms/pseries/htmdump.c @@ -527,28 +527,28 @@ static int htmdump_init_debugfs(void) htm_status_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!htm_status_buf) { pr_err("Failed to allocate htmstatus buf\n"); - return -ENOMEM; + goto htm_status_buf_err; } /* Debugfs interface file to present System Processor Configuration */ htm_info_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!htm_info_buf) { pr_err("Failed to allocate htm info buf\n"); - return -ENOMEM; + goto htm_info_buf_err; } /* Debugfs interface file to present HTM capabilities */ htm_caps_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!htm_caps_buf) { pr_err("Failed to allocate htm caps buf\n"); - return -ENOMEM; + goto htm_caps_buf_err; } /* Memory to present HTM system memory configuration */ htm_mem_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!htm_mem_buf) { pr_err("Failed to allocate htm mem buf\n"); - return -ENOMEM; + goto htm_mem_buf_err; } debugfs_create_file("htmstatus", 0400, htmdump_debugfs_dir, htm_status_buf, &htmstatus_fops); @@ -557,6 +557,17 @@ static int htmdump_init_debugfs(void) debugfs_create_file("htmsystem_mem", 0400, htmdump_debugfs_dir, htm_mem_buf, &htmsystem_mem_fops); return 0; + +htm_mem_buf_err: + kfree(htm_caps_buf); +htm_caps_buf_err: + kfree(htm_info_buf); +htm_info_buf_err: + kfree(htm_status_buf); +htm_status_buf_err: + debugfs_remove_recursive(htmdump_debugfs_dir); + kfree(htm_buf); + return -ENOMEM; } static int __init htmdump_init(void) From 1144454ea22290d7c6998a2af6239e5995476afc Mon Sep 17 00:00:00 2001 From: leixiang Date: Thu, 9 Jul 2026 13:57:52 +0800 Subject: [PATCH 0723/1317] KVM: PPC: Book3S HV: Set irqfd->producer only on success Set irqfd->producer only after kvmppc_set_passthru_irq() succeeds to avoid leaving a dangling pointer on failure. The bypass manager does not register a failed producer, so the pointer is never cleared. Fixes: c57875f5f9be ("KVM: PPC: Book3S HV: Enable IRQ bypass") Suggested-by: Sean Christopherson Cc: stable@vger.kernel.org Signed-off-by: leixiang Reviewed-by: Amit Machhiwal Reviewed-by: Vaibhav Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260709055755.31297-1-leixiang@kylinos.cn --- arch/powerpc/kvm/book3s_hv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c index 0409ac9e7b31..dbac3573b2c8 100644 --- a/arch/powerpc/kvm/book3s_hv.c +++ b/arch/powerpc/kvm/book3s_hv.c @@ -6140,12 +6140,12 @@ static int kvmppc_irq_bypass_add_producer_hv(struct irq_bypass_consumer *cons, struct kvm_kernel_irqfd *irqfd = container_of(cons, struct kvm_kernel_irqfd, consumer); - irqfd->producer = prod; - ret = kvmppc_set_passthru_irq(irqfd->kvm, prod->irq, irqfd->gsi); if (ret) pr_info("kvmppc_set_passthru_irq (irq %d, gsi %d) fails: %d\n", prod->irq, irqfd->gsi, ret); + else + irqfd->producer = prod; return ret; } From e58b9d90973e09f1908d2cd00ca2336a8232b9f8 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 25 Jul 2025 18:14:38 +0530 Subject: [PATCH 0724/1317] powerpc/kexec_file: print configured kernel command line Kexec with the -d option prints extra logs about the kexec/kdump kernel that help debug kexec and kdump. For example, it shows what kexec segments are loaded, their locations, and sizes. One key piece of information still missing is the kernel command line configured for the kexec/kdump kernel. With this patch included, the kernel will print the kernel command line configured for the kexec/kdump kernel as shown below: kexec --initrd=./initrd ./kernel -lspd --command-line="test1 test2" Loaded elf core header at 0x22e30000, bufsz=0x2000 memsz=0x80000 kexec_elf: Command line: elfcorehdr=0x22e30000 test1 test2 <--- New kexec_elf: Loaded initrd at 0x22eb0000 Signed-off-by: Sourabh Jain Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20250725124438.327593-1-sourabhjain@linux.ibm.com --- arch/powerpc/kexec/elf_64.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/kexec/elf_64.c b/arch/powerpc/kexec/elf_64.c index ea50a072debf..d9a466cd602e 100644 --- a/arch/powerpc/kexec/elf_64.c +++ b/arch/powerpc/kexec/elf_64.c @@ -94,6 +94,8 @@ static void *elf64_load(struct kimage *image, char *kernel_buf, cmdline = modified_cmdline; } + kexec_dprintk("Command line: %s", cmdline ? cmdline : ""); + if (initrd != NULL) { kbuf.buffer = initrd; kbuf.bufsz = kbuf.memsz = initrd_len; From bf1d8287816194457c1a936056ad2d1e1e478944 Mon Sep 17 00:00:00 2001 From: longlong yan Date: Wed, 22 Jul 2026 10:34:28 +0800 Subject: [PATCH 0725/1317] selftests/powerpc: use MAP_FAILED instead of (void *)-1 in tm-signal-context-force-tm mmap() is documented to return MAP_FAILED on error, but tm-signal-context-force-tm.c compares the return value against (void *)-1. Replace these with the standard MAP_FAILED macro for better readability and type safety. Signed-off-by: longlong yan Tested-by: Venkat Rao Bagalkote Reviewed-by: Amit Machhiwal Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260722023428.932-1-yanlonglong@kylinos.cn --- .../testing/selftests/powerpc/tm/tm-signal-context-force-tm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c b/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c index 0a4bc479ae39..5dc0f12f467d 100644 --- a/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c +++ b/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c @@ -60,7 +60,7 @@ void usr_signal_handler(int signo, siginfo_t *si, void *uc) ucp->uc_link = mmap(NULL, sizeof(ucontext_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - if (ucp->uc_link == (void *)-1) { + if (ucp->uc_link == MAP_FAILED) { perror("Mmap failed"); exit(-1); } @@ -129,7 +129,7 @@ void tm_trap_test(void) ss.ss_size = SIGSTKSZ; ss.ss_flags = 0; - if (ss.ss_sp == (void *)-1) { + if (ss.ss_sp == MAP_FAILED) { perror("mmap error\n"); exit(-1); } From 15f3ce3aa218f6d4cece9101f1af4332e8712e39 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Sat, 18 Apr 2026 14:42:50 +0530 Subject: [PATCH 0726/1317] powerpc/prom: Remove redundant early_init_dt_scan_root() call Commit 554b66233623 ("of/fdt: Scan the root node properties earlier") moved the invocation of early_init_dt_scan_root() into early_init_dt_verify(). early_init_devtree() already calls early_init_dt_verify(), so the root node properties are parsed before reaching the explicit call in this function. Keeping the call here results in scanning the root node twice. Remove the redundant call and rely on the invocation from early_init_dt_verify(). This change keeps the behavior the same and removes an unnecessary duplicate call. Signed-off-by: Sourabh Jain Tested-by: Shivang Upadhyay Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260418091250.134111-1-sourabhjain@linux.ibm.com --- arch/powerpc/kernel/prom.c | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/powerpc/kernel/prom.c b/arch/powerpc/kernel/prom.c index 9ed9dde7d231..d218c8cc1f73 100644 --- a/arch/powerpc/kernel/prom.c +++ b/arch/powerpc/kernel/prom.c @@ -824,7 +824,6 @@ void __init early_init_devtree(void *params) fadump_append_bootargs(); /* Scan memory nodes and rebuild MEMBLOCKs */ - early_init_dt_scan_root(); early_init_dt_scan_memory_ppc(); /* From 9e5c53d75c560a058abef0e4338c5e3e52cb432a Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Wed, 17 Jan 2024 17:17:06 +0800 Subject: [PATCH 0727/1317] powerpc/pasemi: Add a null pointer check to the pas_setup_mce_regs kasprintf() returns a pointer to dynamically allocated memory which can be NULL upon failure. Ensure the allocation was successful by checking the pointer validity. Signed-off-by: Kunwu Chan Reviewed-by: Christophe Leroy Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20240117091706.153431-1-chentao@kylinos.cn --- arch/powerpc/platforms/pasemi/setup.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/platforms/pasemi/setup.c b/arch/powerpc/platforms/pasemi/setup.c index d03b41336901..45792ecd5dfd 100644 --- a/arch/powerpc/platforms/pasemi/setup.c +++ b/arch/powerpc/platforms/pasemi/setup.c @@ -165,6 +165,8 @@ static int __init pas_setup_mce_regs(void) while (dev && reg < MAX_MCE_REGS) { mce_regs[reg].name = kasprintf(GFP_KERNEL, "mc%d_mcdebug_errsta", reg); + if (!mce_regs[reg].name) + return -ENOMEM; mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x730); dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa00a, dev); reg++; From a894f97318366d12102c15937aa6b63c21aa82b5 Mon Sep 17 00:00:00 2001 From: Mitul Golani Date: Tue, 25 Aug 2026 13:02:04 +0530 Subject: [PATCH 0728/1317] drm/i915/dp: Gate UHBR SST SDP splitting on sink capability SDP splitting for 128b/132b (UHBR) SST audio must only be enabled when the sink advertises support for it. Previously sdp_split_enable was set for every UHBR SST stream carrying audio, regardless of sink capability. In MST mode SDP splitting is inherently supported, so the sink capability check (DP_SST_SPLIT_SDP_CAP) is applied only to the SST path. Fixes: 8853750dbad8 ("drm/i915: Enable SDP split for DP2.0") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Mitul Golani Reviewed-by: Suraj Kandpal Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260825073204.872441-1-mitulkumar.ajitkumar.golani@intel.com (cherry picked from commit b37921c9f533ca936c5b5a484c1299680c570a7e) Signed-off-by: Jani Nikula --- .../drm/i915/display/intel_display_types.h | 2 + drivers/gpu/drm/i915/display/intel_dp.c | 44 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index b7cc361fd955..43d53a98dae7 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1946,6 +1946,8 @@ struct intel_dp { bool colorimetry_support; + bool sst_split_sdp_support; + struct { enum transcoder transcoder; struct mutex lock; diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 113d767e62e9..3152122e6aef 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -3409,12 +3409,22 @@ intel_dp_audio_compute_config(struct intel_encoder *encoder, struct intel_crtc_state *pipe_config, struct drm_connector_state *conn_state) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + pipe_config->has_audio = intel_dp_has_audio(encoder, conn_state) && intel_audio_compute_config(encoder, pipe_config, conn_state); pipe_config->sdp_split_enable = pipe_config->has_audio && intel_dp_is_uhbr(pipe_config); + + /* + * SDP splitting for UHBR audio requires explicit sink capability in + * SST mode, whereas in MST mode it is inherently supported. + */ + if (pipe_config->sdp_split_enable && + !intel_crtc_has_type(pipe_config, INTEL_OUTPUT_DP_MST)) + pipe_config->sdp_split_enable = intel_dp->sst_split_sdp_support; } void @@ -4462,16 +4472,25 @@ void intel_dp_configure_protocol_converter(struct intel_dp *intel_dp, str_enable_disable(tmp)); } -static bool intel_dp_get_colorimetry_status(struct intel_dp *intel_dp) +static u8 intel_dp_read_dprx_feature_enum(struct intel_dp *intel_dp) { u8 dprx = 0; - if (drm_dp_dpcd_readb(&intel_dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, - &dprx) != 1) - return false; + drm_dp_dpcd_read_data(&intel_dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, + &dprx, sizeof(dprx)); + return dprx; +} + +static bool intel_dp_get_colorimetry_status(u8 dprx) +{ return dprx & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED; } +static bool intel_dp_get_sst_split_sdp_status(u8 dprx) +{ + return dprx & DP_SST_SPLIT_SDP_CAP; +} + static int intel_dp_read_dsc_dpcd(struct drm_dp_aux *aux, u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) { @@ -4771,6 +4790,7 @@ intel_edp_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *connector { struct intel_display *display = to_intel_display(intel_dp); int ret; + u8 dprx; /* this function is meant to be called only once */ drm_WARN_ON(display->drm, intel_dp->dpcd[DP_DPCD_REV] != 0); @@ -4782,8 +4802,13 @@ intel_edp_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *connector drm_dp_is_branch(intel_dp->dpcd)); intel_init_dpcd_quirks(intel_dp, &intel_dp->desc.ident); + dprx = intel_dp_read_dprx_feature_enum(intel_dp); + intel_dp->colorimetry_support = - intel_dp_get_colorimetry_status(intel_dp); + intel_dp_get_colorimetry_status(dprx); + + intel_dp->sst_split_sdp_support = + intel_dp_get_sst_split_sdp_status(dprx); /* * Read the eDP display control registers. @@ -4874,13 +4899,20 @@ intel_dp_get_dpcd(struct intel_dp *intel_dp) * the OUI/ID since we know it won't change. */ if (!intel_dp_is_edp(intel_dp)) { + u8 dprx; + drm_dp_read_desc(&intel_dp->aux, &intel_dp->desc, drm_dp_is_branch(intel_dp->dpcd)); intel_init_dpcd_quirks(intel_dp, &intel_dp->desc.ident); + dprx = intel_dp_read_dprx_feature_enum(intel_dp); + intel_dp->colorimetry_support = - intel_dp_get_colorimetry_status(intel_dp); + intel_dp_get_colorimetry_status(dprx); + + intel_dp->sst_split_sdp_support = + intel_dp_get_sst_split_sdp_status(dprx); intel_dp_update_sink_caps(intel_dp); } From cbd3dafc2003db679ccd2f6c6a2551db79657049 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 23 Aug 2026 22:50:28 +0200 Subject: [PATCH 0729/1317] drm/i915: Fix memory leak in query_perf_config_list() When krealloc() fails, free the original oa_config_ids before returning to avoid a memory leak. Fixes: 4f6ccc74a85c ("drm/i915: add support for perf configuration queries") Signed-off-by: Thorsten Blum Cc: # v5.5+ Reviewed-by: Andi Shyti Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260823205028.178597-2-thorsten.blum@linux.dev (cherry picked from commit 9977e9d84f46d4f12ad35fbbc0ec4638554bce87) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/i915_query.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/i915_query.c b/drivers/gpu/drm/i915/i915_query.c index 0c55fb6e9727..11157fb14db3 100644 --- a/drivers/gpu/drm/i915/i915_query.c +++ b/drivers/gpu/drm/i915/i915_query.c @@ -403,8 +403,10 @@ static int query_perf_config_list(struct drm_i915_private *i915, ids = krealloc(oa_config_ids, n_configs * sizeof(*oa_config_ids), GFP_KERNEL); - if (!ids) + if (!ids) { + kfree(oa_config_ids); return -ENOMEM; + } alloc = fetch_and_zero(&n_configs); From 59e63416f5153e7d58652c616fbdcb7d5e01fff7 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 8 Sep 2026 12:56:37 +0200 Subject: [PATCH 0730/1317] perf/core: Allow list_del during perf_event_overflow() A PMU might use perf_sched_cb_inc() and perf_sched_cb_dec() interface to get the PMU call back function pmu::sched_task invoked at schedule in and schedule out. This is achieved by walking along the list anchored by sched_cb_list. The following scenario might lead to a list corruption. perf_pmu_sched_task() for_each_list_entry(..., &sched_cb_list) +--> __perf_pmu_sched_task() +--> event->pmu->sched_task()) +--> PMU_push_sample() +--> perf_event_overflow() +--> __perf_event_overflow() +--> pmu->stop() +--> perf_sched_cb_dec() remove entry from sched_cb_list while list node in use. This happens when ioctl(fd, PERF_EVENT_IOC_REFRESH, xxx) has been invoked and perf_event::event_limit hits zero. Prevent the list corruption and convert for_each_list_entry() to for_each_list_entry_safe(). Fixes: bd2756811766 ("perf: Rewrite core context handling") Signed-off-by: Thomas Richter Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260908105637.627004-1-tmricht@linux.ibm.com --- kernel/events/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 33210aff3ee6..fe33fe15689d 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3925,13 +3925,13 @@ static void perf_pmu_sched_task(struct task_struct *prev, bool sched_in) { struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); - struct perf_cpu_pmu_context *cpc; + struct perf_cpu_pmu_context *cpc, *cpc2; /* cpuctx->task_ctx will be handled in perf_event_context_sched_in/out */ if (prev == next || cpuctx->task_ctx) return; - list_for_each_entry(cpc, this_cpu_ptr(&sched_cb_list), sched_cb_entry) + list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) __perf_pmu_sched_task(cpc, sched_in ? next : prev, sched_in); } From 88aa1223bfffb1a0a98c639e9e1f71058f0d9178 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 8 Sep 2026 15:51:01 +0800 Subject: [PATCH 0731/1317] perf/x86/intel: Correct pt_regs->flags update for PEBS path pt_regs->flags holds the saved CPU FLAGS register. In the PEBS path, it was incorrectly set to PERF_EFLAGS_EXACT instead of being populated from the PEBS flags snapshot. Update pt_regs->flags from PEBS GPR flags if GPRs group is present. Fixes: c22497f5838c ("perf/x86/intel: Support adaptive PEBS v4") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260908075102.540715-1-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 8940f0292229..d0bb767a0fef 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -2432,7 +2432,7 @@ static inline void __setup_pebs_basic_group(struct perf_event *event, { /* The ip in basic is EventingIP */ set_linear_ip(regs, ip); - regs->flags = PERF_EFLAGS_EXACT; + regs->flags |= PERF_EFLAGS_EXACT; setup_pebs_time(event, data, tsc); if (sample_type & PERF_SAMPLE_WEIGHT_STRUCT) @@ -2444,9 +2444,17 @@ static inline void __setup_pebs_gpr_group(struct perf_event *event, struct pebs_gprs *gprs, u64 sample_type) { + /* + * Update flags with PEBS data. PERF_EFLAGS_EXACT must be set + * in previous basic group handling. + */ + regs->flags = gprs->flags | PERF_EFLAGS_EXACT; + if (event->attr.precise_ip < 2) { set_linear_ip(regs, gprs->ip); regs->flags &= ~PERF_EFLAGS_EXACT; + } else if (regs->flags & X86_VM_MASK) { + regs->flags ^= (PERF_EFLAGS_VM | X86_VM_MASK); } if (sample_type & (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER)) From a56c03a397e2cd0c4cf8da96dcd6214f7d0e7d8c Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 13 Aug 2026 14:43:46 +0800 Subject: [PATCH 0732/1317] perf/x86/intel: Prevent drain_pebs() reentry The PEBS buffer is shared by all events on a CPU, so drain_pebs() must not be reentered. If so, one instance may observe stale buffer state and potentially access out-of-bound memory. Most invocations happen in NMI context, which naturally prevents reentry. However, drain_pebs() is also reachable from process context via intel_pmu_drain_pebs_buffer(). In those paths, the PMU is often already disabled, but not guaranteed. For example, __intel_pmu_pebs_disable() only disables the target counter, so other active counters can still raise a PMI and interrupt an in-flight drain_pebs(). Here is an example, __perf_addr_filters_adjust() perf_event_stop() __perf_event_stop() x86_pmu_stop() (event->pmu->stop) intel_pmu_disable_event() intel_pmu_pebs_disable() __intel_pmu_pebs_disable() intel_pmu_drain_large_pebs() intel_pmu_drain_pebs_buffer() Introduce __intel_pmu_quiesce() and __intel_pmu_resume() helpers and use them in intel_pmu_drain_large_pebs() to disable the full PMU around the intel_pmu_drain_pebs_buffer() call, preventing reentry. Also add a warning in intel_pmu_drain_pebs_buffer() when the full PMU is not disabled. Fixes: b752ea0c28e3 ("perf/x86/intel/ds: Flush PEBS DS when changing PEBS_DATA_CFG") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260813064346.335458-1-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 33 ++++++++++++++++++++++++--------- arch/x86/events/intel/ds.c | 8 +++++++- arch/x86/events/perf_event.h | 3 +++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index cc13164d948f..1ac2ca35db53 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3125,6 +3125,27 @@ static void intel_pmu_del_event(struct perf_event *event) this_cpu_ptr(&cpu_hw_events)->n_late_setup--; } +int __intel_pmu_quiesce(void) +{ + struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); + int pmu_enabled = cpuc->enabled; + + cpuc->enabled = 0; + if (pmu_enabled) + intel_pmu_disable_all(); + + return pmu_enabled; +} + +void __intel_pmu_resume(int pmu_enabled) +{ + struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); + + cpuc->enabled = pmu_enabled; + if (pmu_enabled) + intel_pmu_enable_all(0); +} + static int icl_set_topdown_event_period(struct perf_event *event) { struct hw_perf_event *hwc = &event->hw; @@ -3316,16 +3337,13 @@ static void intel_pmu_read_event(struct perf_event *event) if (event->hw.flags & (PERF_X86_EVENT_AUTO_RELOAD | PERF_X86_EVENT_TOPDOWN) || is_pebs_counter_event_group(event)) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); - bool pmu_enabled = cpuc->enabled; + int pmu_enabled; /* Only need to call update_topdown_event() once for group read. */ if (is_metric_event(event) && (cpuc->txn_flags & PERF_PMU_TXN_READ)) return; - cpuc->enabled = 0; - if (pmu_enabled) - intel_pmu_disable_all(); - + pmu_enabled = __intel_pmu_quiesce(); /* * If the PEBS counters snapshotting is enabled, * the topdown event is available in PEBS records. @@ -3334,10 +3352,7 @@ static void intel_pmu_read_event(struct perf_event *event) static_call(intel_pmu_update_topdown_event)(event, NULL); else intel_pmu_drain_pebs_buffer(); - - cpuc->enabled = pmu_enabled; - if (pmu_enabled) - intel_pmu_enable_all(0); + __intel_pmu_resume(pmu_enabled); return; } diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index d0bb767a0fef..b98029b44052 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1242,8 +1242,11 @@ int intel_pmu_drain_bts_buffer(void) void intel_pmu_drain_pebs_buffer(void) { + struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct perf_sample_data data; + WARN_ON_ONCE(cpuc->enabled); + static_call(x86_pmu_drain_pebs)(NULL, &data); } @@ -1864,8 +1867,11 @@ static void intel_pmu_pebs_via_pt_enable(struct perf_event *event) static inline void intel_pmu_drain_large_pebs(struct cpu_hw_events *cpuc) { if (cpuc->n_pebs == cpuc->n_large_pebs && - cpuc->n_pebs != cpuc->n_pebs_via_pt) + cpuc->n_pebs != cpuc->n_pebs_via_pt) { + int enabled = __intel_pmu_quiesce(); intel_pmu_drain_pebs_buffer(); + __intel_pmu_resume(enabled); + } } static void __intel_pmu_pebs_enable(struct perf_event *event) diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 71ed5b2acea2..4680cba91340 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1638,6 +1638,9 @@ static __always_inline void __intel_pmu_lbr_disable(void) wrmsrq(MSR_IA32_DEBUGCTLMSR, debugctl); } +extern int __intel_pmu_quiesce(void); +extern void __intel_pmu_resume(int pmu_enabled); + int intel_pmu_save_and_restart(struct perf_event *event); struct event_constraint * From 9a8bc9bb4c3fb3218b4f151f98a722fbeb5b5c34 Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Mon, 7 Sep 2026 14:38:55 +0200 Subject: [PATCH 0733/1317] sched/eevdf: Fix augmented max_slice Similarly to se->min_slice, init se->max_slice with se->slice before enqueueing the entity so the augmented callback computes it correctly at parent level. Fixes: 6e3c0a4e1ad1 ("sched/fair: Fix lag clamp") Signed-off-by: Vincent Guittot Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: K Prateek Nayak Link: https://patch.msgid.link/20260907123855.1297976-1-vincent.guittot@linaro.org --- kernel/sched/fair.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index ade1eceb39b8..5b944f9a8a00 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1073,6 +1073,8 @@ static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) sum_w_vruntime_add(cfs_rq, se); se->min_vruntime = se->vruntime; se->min_slice = se->slice; + se->max_slice = se->slice; + rb_add_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, __entity_less, &min_vruntime_cb); } From 51b0e68cfa0ac69e3c3ea9d6753af7e15dfaab22 Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Wed, 9 Sep 2026 17:05:22 +0200 Subject: [PATCH 0734/1317] sched/eevdf: Fix rb augmented with multi fields The eevdf rb tree maintains 3 augmented fields but only one is currently copied when balancing the tree. Add a more generic define that can be used when there are several augmented fields. In this case, we provide a function that takes care of copying all fields. Fixes: aef6987d8954 ("sched/eevdf: Propagate min_slice up the cgroup hierarchy") Signed-off-by: Vincent Guittot Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Link: https://patch.msgid.link/20260909150522.858312-1-vincent.guittot@linaro.org --- include/linux/rbtree_augmented.h | 35 +++++++++++++++++++++++++------- kernel/sched/fair.c | 12 +++++++++-- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/include/linux/rbtree_augmented.h b/include/linux/rbtree_augmented.h index 6dbc5a1bf6a8..d2fa1c41bfd2 100644 --- a/include/linux/rbtree_augmented.h +++ b/include/linux/rbtree_augmented.h @@ -87,18 +87,18 @@ rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree, } /* - * Template for declaring augmented rbtree callbacks (generic case) + * Template for declaring augmented rbtree callbacks (generic multi fields) * * RBSTATIC: 'static' or empty * RBNAME: name of the rb_augment_callbacks structure * RBSTRUCT: struct type of the tree nodes * RBFIELD: name of struct rb_node field within RBSTRUCT - * RBAUGMENTED: name of field within RBSTRUCT holding data for subtree - * RBCOMPUTE: name of function that recomputes the RBAUGMENTED data + * RBCOPY: name of function that copies the RBAUGMENTED datas + * RBCOMPUTE: name of function that recomputes the RBAUGMENTED datas */ -#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ +#define RB_DECLARE_CALLBACKS_MULTI(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBCOPY, RBCOMPUTE) \ static inline void \ RBNAME ## _propagate(struct rb_node *rb, struct rb_node *stop) \ { \ @@ -114,14 +114,14 @@ RBNAME ## _copy(struct rb_node *rb_old, struct rb_node *rb_new) \ { \ RBSTRUCT *old = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ RBSTRUCT *new = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - new->RBAUGMENTED = old->RBAUGMENTED; \ + RBCOPY(new, old); \ } \ static void \ RBNAME ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ { \ RBSTRUCT *old = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ RBSTRUCT *new = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - new->RBAUGMENTED = old->RBAUGMENTED; \ + RBCOPY(new, old); \ RBCOMPUTE(old, false); \ } \ RBSTATIC const struct rb_augment_callbacks RBNAME = { \ @@ -130,6 +130,27 @@ RBSTATIC const struct rb_augment_callbacks RBNAME = { \ .rotate = RBNAME ## _rotate \ }; +/* + * Template for declaring augmented rbtree callbacks (generic single field) + * + * RBSTATIC: 'static' or empty + * RBNAME: name of the rb_augment_callbacks structure + * RBSTRUCT: struct type of the tree nodes + * RBFIELD: name of struct rb_node field within RBSTRUCT + * RBAUGMENTED: name of field within RBSTRUCT holding data for subtree + * RBCOMPUTE: name of function that recomputes the RBAUGMENTED data + */ + +#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ +static inline void \ +RBNAME ## _copy_single(RBSTRUCT *new, RBSTRUCT *old) \ +{ \ + new->RBAUGMENTED = old->RBAUGMENTED; \ +} \ +RB_DECLARE_CALLBACKS_MULTI(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBNAME ## _copy_single, RBCOMPUTE) + /* * Template for declaring augmented rbtree callbacks, * computing RBAUGMENTED scalar as max(RBCOMPUTE(node)) for all subtree nodes. diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 5b944f9a8a00..944833e8056f 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1032,6 +1032,13 @@ static inline void __max_slice_update(struct sched_entity *se, struct rb_node *n } } +static inline void min_vruntime_copy(struct sched_entity *new, struct sched_entity *old) +{ + new->min_vruntime = old->min_vruntime; + new->min_slice = old->min_slice; + new->max_slice = old->max_slice; +} + /* * se->min_vruntime = min(se->vruntime, {left,right}->min_vruntime) */ @@ -1059,8 +1066,9 @@ static inline bool min_vruntime_update(struct sched_entity *se, bool exit) se->max_slice == old_max_slice; } -RB_DECLARE_CALLBACKS(static, min_vruntime_cb, struct sched_entity, - run_node, min_vruntime, min_vruntime_update); + +RB_DECLARE_CALLBACKS_MULTI(static, min_vruntime_cb, struct sched_entity, + run_node, min_vruntime_copy, min_vruntime_update); /* * Enqueue an entity into the rb-tree: From c23810313bdf6b02f39a1f2a1464c4b18bd39e31 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Fri, 4 Sep 2026 11:47:07 +0800 Subject: [PATCH 0735/1317] sched: Account cgroup CPU time to the execution context Proxy execution separates the scheduling context from the execution context. Commit aa4f74dfd42b ("sched: Fix runtime accounting w/ split exec & sched contexts") made per-task and thread-group runtime accounting follow the task that actually executes, while cgroup CPU usage is charged to the donor. When the donor and execution task belong to different cgroups, this makes a task's execution time count against a different cgroup from the one the task belongs to. Cgroup CPU usage should follow the execution context, matching the per-task, thread-group, and cgroup user/system accounting. Keep scheduling state associated with the donor, but charge cgroup CPU usage to rq->curr. A reproducer with the donor and execution task in separate cgroups showed the execution task accumulating runtime while cgroup CPU usage was charged to the donor's cgroup. With this change, the execution task's cgroup accumulates the CPU usage instead. The same behavior was verified with an RT donor and with legacy cpuacct accounting. Fixes: aa4f74dfd42b ("sched: Fix runtime accounting w/ split exec & sched contexts") Suggested-by: Tejun Heo Signed-off-by: Hui Su Signed-off-by: Peter Zijlstra (Intel) Acked-by: Tejun Heo Acked-by: John Stultz Link: https://patch.msgid.link/20260904034707.268416-1-sh_def@163.com --- kernel/sched/fair.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 944833e8056f..7455a83a6a99 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1414,7 +1414,6 @@ static s64 update_se(struct rq *rq, struct sched_entity *se) se->exec_start = now; if (entity_is_task(se)) { - struct task_struct *donor = task_of(se); struct task_struct *running = rq->curr; /* * If se is a task, we account the time against the running @@ -1427,8 +1426,7 @@ static s64 update_se(struct rq *rq, struct sched_entity *se) account_group_exec_runtime(running, delta_exec); account_mm_sched(rq, running, delta_exec); - /* cgroup time is always accounted against the donor */ - cgroup_account_cputime(donor, delta_exec); + cgroup_account_cputime(running, delta_exec); } else { /* If not task, account the time against donor se */ se->sum_exec_runtime += delta_exec; From f5741d2b34519d387edf6e9798fc7030c20a35f3 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Wed, 2 Sep 2026 23:02:09 +0800 Subject: [PATCH 0736/1317] sched/core: Call wq_worker_tick() for the execution context wq_worker_tick() accounts CPU time and detects CPU-intensive work for the kworker that is actually running. With proxy execution, rq->donor is the scheduling context while rq->curr is the execution context. Calling the hook with rq->donor can skip workqueue accounting when a kworker is executing on behalf of a donor task. It can also account a blocked kworker when the donor is a worker but rq->curr is the task actually executing. The former can delay WORKER_CPU_INTENSIVE handling and pool concurrency management, which can delay pending kernel work and userspace operations depending on it. Use rq->curr for the workqueue tick hook while retaining rq->donor for scheduler accounting. Fixes: af0c8b2bf67b ("sched: Split scheduler and execution contexts") Signed-off-by: Hui Su Signed-off-by: Peter Zijlstra (Intel) Acked-by: Tejun Heo Link: https://patch.msgid.link/20260902150208.1209922-2-sh_def@163.com --- kernel/sched/core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index b998ef6b87af..7885ff76e69f 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5776,8 +5776,8 @@ void sched_tick(void) { int cpu = smp_processor_id(); struct rq *rq = cpu_rq(cpu); - /* accounting goes to the donor task */ - struct task_struct *donor; + /* scheduler accounting goes to the donor task */ + struct task_struct *curr, *donor; struct rq_flags rf; unsigned long hw_pressure; u64 resched_latency; @@ -5788,6 +5788,7 @@ void sched_tick(void) sched_clock_tick(); rq_lock(rq, &rf); + curr = rq->curr; donor = rq->donor; psi_account_irqtime(rq, donor, NULL); @@ -5813,8 +5814,8 @@ void sched_tick(void) perf_event_task_tick(); - if (donor->flags & PF_WQ_WORKER) - wq_worker_tick(donor); + if (curr->flags & PF_WQ_WORKER) + wq_worker_tick(curr); if (!scx_switched_all()) { rq->idle_balance = idle_cpu(cpu); From 5a5d26f2cfe13467166219f6bf58099326912ddb Mon Sep 17 00:00:00 2001 From: Soheil Hassas Yeganeh Date: Mon, 31 Aug 2026 14:48:44 +0000 Subject: [PATCH 0737/1317] x86/cfi: Fix FineIBT hash offset in cfi_get_func_hash() The switch of the FineIBT preamble from "subl $hash, %r10d" to the shorter "subl $hash, %eax" moved the hash immediate from offset 7 to offset 5 of the preamble. fineibt_preamble_hash was updated to match, but the open-coded offset in cfi_get_func_hash() was missed and it still reads the hash at offset 7. cfi_get_func_hash() is used by the BPF JIT to give a struct_ops trampoline the CFI hash of the stub function it stands in for. With FineIBT the trampoline now gets the upper half of the real hash followed by the first two bytes of the next instruction, so the first indirect call from the kernel into a struct_ops program, tcp_init_congestion_control() calling ->init() of a BPF congestion control for example, fails the FineIBT check and the kernel dies with a CFI failure. Move the FineIBT preamble template and its offset defines above cfi_get_func_hash() and use fineibt_preamble_hash there, so every reader of the preamble shares one definition of its layout. The CFI_FINEIBT arm is only built with CONFIG_FINEIBT, the only configuration in which cfi_mode can take that value. cfi_get_func_arity() does not need the same treatment: the __bhi_args call whose displacement it reads still ends at the function address. Fixes: 85a2d4a890dc ("x86,ibt: Use UDB instead of 0xEA") Assisted-by: LLM Signed-off-by: Soheil Hassas Yeganeh Signed-off-by: Peter Zijlstra (Intel) Cc: stable@vger.kernel.org # 6.18+ Link: https://patch.msgid.link/20260831-b4-x86-cfi-fineibt-func-hash-v1-1-6ffc0af5c4ec@gmail.com --- arch/x86/kernel/alternative.c | 72 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index add62db3e82c..741d8767ddf8 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -1201,6 +1201,41 @@ static bool cfi_debug __ro_after_init; bool cfi_bhi __ro_after_init = false; #endif +#ifdef CONFIG_FINEIBT +/* + * : + * 0: f3 0f 1e fa endbr64 + * 4: 2d 78 56 34 12 sub $0x12345678, %eax + * 9: 2e 0f 85 03 00 00 00 jne,pn 13 + * 10: 0f 1f 40 d6 nopl -0x2a(%rax) + * + * Note that the JNE target is the 0xD6 byte inside the NOPL, this decodes as + * UDB on x86_64 and raises #UD. + */ +asm( ".pushsection .rodata \n" + "fineibt_preamble_start: \n" + " endbr64 \n" + " subl $0x12345678, %eax \n" + "fineibt_preamble_bhi: \n" + " cs jne.d32 fineibt_preamble_start+0x13 \n" + "#fineibt_func: \n" + " nopl -42(%rax) \n" + "fineibt_preamble_end: \n" + ".popsection\n" +); + +extern u8 fineibt_preamble_start[]; +extern u8 fineibt_preamble_bhi[]; +extern u8 fineibt_preamble_end[]; + +#define fineibt_preamble_size (fineibt_preamble_end - fineibt_preamble_start) +#define fineibt_preamble_bhi (fineibt_preamble_bhi - fineibt_preamble_start) +#define fineibt_preamble_ud 0x13 +#define fineibt_preamble_hash 5 + +#define fineibt_prefix_size (fineibt_preamble_size - ENDBR_INSN_SIZE) +#endif /* CONFIG_FINEIBT */ + #ifdef CONFIG_CFI u32 cfi_get_func_hash(void *func) { @@ -1208,9 +1243,11 @@ u32 cfi_get_func_hash(void *func) func -= cfi_get_offset(); switch (cfi_mode) { +#ifdef CONFIG_FINEIBT case CFI_FINEIBT: - func += 7; + func += fineibt_preamble_hash; break; +#endif case CFI_KCFI: func += 1; break; @@ -1366,39 +1403,6 @@ early_param("cfi", cfi_parse_cmdline); * anyway. */ -/* - * : - * 0: f3 0f 1e fa endbr64 - * 4: 2d 78 56 34 12 sub $0x12345678, %eax - * 9: 2e 0f 85 03 00 00 00 jne,pn 13 - * 10: 0f 1f 40 d6 nopl -0x2a(%rax) - * - * Note that the JNE target is the 0xD6 byte inside the NOPL, this decodes as - * UDB on x86_64 and raises #UD. - */ -asm( ".pushsection .rodata \n" - "fineibt_preamble_start: \n" - " endbr64 \n" - " subl $0x12345678, %eax \n" - "fineibt_preamble_bhi: \n" - " cs jne.d32 fineibt_preamble_start+0x13 \n" - "#fineibt_func: \n" - " nopl -42(%rax) \n" - "fineibt_preamble_end: \n" - ".popsection\n" -); - -extern u8 fineibt_preamble_start[]; -extern u8 fineibt_preamble_bhi[]; -extern u8 fineibt_preamble_end[]; - -#define fineibt_preamble_size (fineibt_preamble_end - fineibt_preamble_start) -#define fineibt_preamble_bhi (fineibt_preamble_bhi - fineibt_preamble_start) -#define fineibt_preamble_ud 0x13 -#define fineibt_preamble_hash 5 - -#define fineibt_prefix_size (fineibt_preamble_size - ENDBR_INSN_SIZE) - /* * : * 0: b8 78 56 34 12 mov $0x12345678, %eax From 00f9fbc12320253bfc576fb7539d860029c82d0f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 7 Sep 2026 08:52:35 +0200 Subject: [PATCH 0738/1317] net: hso: fix TIOCMIWAIT race The task state must be updated before checking the wakeup condition to avoid missing a racing modem status update. Fixes: 542f54823614 ("tty: Modem functions for the HSO driver") Cc: stable@vger.kernel.org # 2.6.29 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260907065235.100848-1-johan@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/usb/hso.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index c1aec67688ae..71caa3764b23 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -1547,10 +1547,10 @@ hso_wait_modem_status(struct hso_serial *serial, unsigned long arg) spin_unlock_irq(&serial->serial_lock); add_wait_queue(&tiocmget->waitq, &wait); for (;;) { + set_current_state(TASK_INTERRUPTIBLE); spin_lock_irq(&serial->serial_lock); memcpy(&cnow, &tiocmget->icount, sizeof(struct uart_icount)); spin_unlock_irq(&serial->serial_lock); - set_current_state(TASK_INTERRUPTIBLE); if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd))) { From 1853f30cf5c84971f99788a76207c6f745380896 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 7 Sep 2026 16:21:30 -0300 Subject: [PATCH 0739/1317] net/sched: cls_route: free emptied bucket on filter move route4_change can move an existing filter to a different top-level bucket: route4_set_parms recomputes the handle from TCA_ROUTE4_TO/ FROM/IIF, and the handle-mismatch check is gated on the 'new' flag, so for an existing filter the new handle may differ from the old one and land in a different bucket. When this happens, the filter is unlinked from the old bucket, but the bucket itself is never freed once it goes empty. The stale empty bucket remains in head->table[], causing route4_delete to report *last=false even after the last live filter is gone. That pins the empty tcf_proto and causes a leak. Fix this by refcounting the filters linked to a bucket and freeing the bucket when the count drops to zero. The existing scan in route4_delete goes away with it. The count is updated at all sites that link or unlink a filter during add, change and delete, and the bucket is dropped from head->table[] as soon as it reaches zero. Conditions to recreate the bug: CONFIG_NET_CLS_ROUTE4=y, CONFIG_NET_SCH_INGRESS=y, CONFIG_NET_CLS_ACT=y. tc qdisc replace dev lo clsact tc filter add dev lo ingress protocol ip pref 100 route from 1 to 1 tc filter change dev lo ingress protocol ip pref 100 handle 0x10001 \ route from 1 to 2 tc filter del dev lo ingress protocol ip pref 100 handle 0x10002 \ route from 1 to 2 tc filter show dev lo ingress | grep -c 'pref 100 route chain 0 ' Fixes: 1e052be69d04 ("net_sched: destroy proto tp when all filters are gone") Reported-by: Vega Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260907192133.2639067-2-victor@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/cls_route.c | 45 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 0d1324c90583..17b0ebb76662 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ struct route4_head { struct route4_bucket { /* 16 FROM buckets + 16 IIF buckets + 1 wildcard bucket */ struct route4_filter __rcu *ht[16 + 16 + 1]; + refcount_t filters_ref; struct rcu_head rcu; }; @@ -336,7 +338,7 @@ static int route4_delete(struct tcf_proto *tp, void *arg, bool *last, struct route4_filter *nf; struct route4_bucket *b; unsigned int h = 0; - int i, h1; + int h1; if (!head || !f) return -EINVAL; @@ -362,23 +364,14 @@ static int route4_delete(struct tcf_proto *tp, void *arg, bool *last, tcf_exts_get_net(&f->exts); tcf_queue_work(&f->rwork, route4_delete_filter_work); - /* Strip RTNL protected tree */ - for (i = 0; i <= 32; i++) { - struct route4_filter *rt; - - rt = rtnl_dereference(b->ht[i]); - if (rt) - goto out; + if (refcount_dec_and_test(&b->filters_ref)) { + RCU_INIT_POINTER(head->table[to_hash(h)], NULL); + kfree_rcu(b, rcu); } - - /* OK, session has no flows */ - RCU_INIT_POINTER(head->table[to_hash(h)], NULL); - kfree_rcu(b, rcu); break; } } -out: *last = true; for (h1 = 0; h1 <= 256; h1++) { if (rcu_access_pointer(head->table[h1])) { @@ -459,6 +452,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, if (b == NULL) return -ENOBUFS; + refcount_set(&b->filters_ref, 1); rcu_assign_pointer(head->table[h1], b); } else { unsigned int h2 = from_hash(nhandle >> 16); @@ -468,6 +462,8 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, fp = rtnl_dereference(fp->next)) if (fp->handle == f->handle) return -EEXIST; + + refcount_inc(&b->filters_ref); } if (tb[TCA_ROUTE4_TO]) @@ -500,7 +496,7 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, struct route4_filter *fold, *f1, *pfp, *f = NULL; struct route4_bucket *b; struct nlattr *tb[TCA_ROUTE4_MAX + 1]; - unsigned int h, th; + unsigned int h; int err; bool new = true; @@ -560,17 +556,20 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, rcu_assign_pointer(*fp, f); if (fold) { - th = to_hash(fold->handle); + b = fold->bkt; h = from_hash(fold->handle >> 16); - b = rtnl_dereference(head->table[th]); - if (b) { - fp = &b->ht[h]; - for (pfp = rtnl_dereference(*fp); pfp; - fp = &pfp->next, pfp = rtnl_dereference(*fp)) { - if (pfp == fold) { - rcu_assign_pointer(*fp, fold->next); - break; + fp = &b->ht[h]; + for (pfp = rtnl_dereference(*fp); pfp; + fp = &pfp->next, pfp = rtnl_dereference(*fp)) { + if (pfp == fold) { + rcu_assign_pointer(*fp, fold->next); + if (refcount_dec_and_test(&b->filters_ref)) { + unsigned int th = to_hash(fold->handle); + + RCU_INIT_POINTER(head->table[th], NULL); + kfree_rcu(b, rcu); } + break; } } } From b74a8455a2f271f54695b6a8ec1f113824a46c0e Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 7 Sep 2026 16:21:31 -0300 Subject: [PATCH 0740/1317] net/sched: cls_route: Reject handle aliasing route4_set_parms() rejects a duplicate by scanning the destination chain for f->handle, but f->handle is the handle the filter has before the update, not the one it is about to be linked under. The comparison and the insertion therefore use different handles, which causes breakage. When a change moves the filter to a chain that already holds nhandle, the scan looks for the old handle instead, misses the collision and links a second filter with the same handle: tc filter add dev lo ingress protocol ip pref 100 \ route from 1 to 1 classid 1:1 action ok tc filter add dev lo ingress protocol ip pref 100 \ route from 2 to 2 classid 1:2 action drop tc filter change dev lo ingress protocol ip pref 100 handle 0x10001 \ route from 2 to 2 classid 1:1 action ok tc filter show dev lo ingress ... fh 0x00020002 flowid 1:2 to 2 from 2 ... fh 0x00020002 flowid 1:1 to 2 from 2 The newcomer is appended after the incumbent, and both end up with the same f->id. route4_get() returns the first match, so the second filter can no longer be addressed by handle, and route4_classify() stops at the first filter whose f->id matches. The second filter is dumped but is effectively dead. Fix this by comparing against nhandle. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260829205422.854785-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260907192133.2639067-3-victor@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/cls_route.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 17b0ebb76662..9710b77d379c 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -460,8 +460,12 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, for (fp = rtnl_dereference(b->ht[h2]); fp; fp = rtnl_dereference(fp->next)) - if (fp->handle == f->handle) + if (fp->handle == nhandle) { + NL_SET_ERR_MSG_FMT(extack, + "Handle %x is already in use", + nhandle); return -EEXIST; + } refcount_inc(&b->filters_ref); } From 41e85e54e5649a1617698438b0ce64c6f9d83d69 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 7 Sep 2026 16:21:32 -0300 Subject: [PATCH 0741/1317] net/sched: cls_route: Fix in-place replace Building on the previous patch, route4_set_parms rejects a duplicate by scanning the destination chain for nhandle, but the scan doesn't exclude the older version it is replacing, so an in-place replace will match the older version's handle and fail. Fix this by passing the older filter as a parameter to route4_set_parms (replacing "new") and skipping it in the scan. Excluding the older version is not enough on its own. nhandle is built out of TCA_ROUTE4_TO, TCA_ROUTE4_FROM and TCA_ROUTE4_IIF alone, while the 0x7F00 bits, which only tell apart filters sharing one key, are folded in on the create path. Letting the replace through would therefore rename the filter it replaces: replacing handle 0x10101 stored it back as 0x10001, and a sibling at 0x10201 could then no longer be replaced at all, since its own nhandle collided with the renamed filter. tc filter add ... handle 0x10101 route from 1 to 1 classid 1:1 tc filter add ... handle 0x10201 route from 1 to 1 classid 1:2 tc filter replace ... handle 0x10101 route from 1 to 1 classid 1:9 ... fh 0x00010001 flowid 1:9 to 1 from 1 ... fh 0x00010201 flowid 1:2 to 1 from 1 tc filter replace ... handle 0x10201 route from 1 to 1 classid 1:8 Error: Handle 10001 is already in use. So carry those bits over when the key the request builds is the key the older filter already has. An in-place replace then keeps the handle userspace named the filter by, while a request that does change the key still renames it, as it did before. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260829205422.854785-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260907192133.2639067-4-victor@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/cls_route.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 9710b77d379c..0f211f030fd9 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -393,8 +393,9 @@ static const struct nla_policy route4_policy[TCA_ROUTE4_MAX + 1] = { static int route4_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base, struct route4_filter *f, u32 handle, struct route4_head *head, - struct nlattr **tb, struct nlattr *est, int new, - u32 flags, struct netlink_ext_ack *extack) + struct nlattr **tb, struct nlattr *est, + struct route4_filter *fold, u32 flags, + struct netlink_ext_ack *extack) { u32 id = 0, to = 0, nhandle = 0x8000; struct route4_filter *fp; @@ -407,7 +408,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, return err; if (tb[TCA_ROUTE4_TO]) { - if (new && handle & 0x8000) { + if (!fold && handle & 0x8000) { NL_SET_ERR_MSG(extack, "Invalid handle"); return -EINVAL; } @@ -430,14 +431,14 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, } else nhandle |= 0xFFFF << 16; - if (handle && new) { + if (handle && (!fold || nhandle == (handle & ~0x7F00))) nhandle |= handle & 0x7F00; - if (nhandle != handle) { - NL_SET_ERR_MSG_FMT(extack, - "Handle mismatch constructed: %x (expected: %x)", - handle, nhandle); - return -EINVAL; - } + + if (handle && !fold && nhandle != handle) { + NL_SET_ERR_MSG_FMT(extack, + "Handle mismatch constructed: %x (expected: %x)", + handle, nhandle); + return -EINVAL; } if (!nhandle) { @@ -460,7 +461,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, for (fp = rtnl_dereference(b->ht[h2]); fp; fp = rtnl_dereference(fp->next)) - if (fp->handle == nhandle) { + if (fp != fold && fp->handle == nhandle) { NL_SET_ERR_MSG_FMT(extack, "Handle %x is already in use", nhandle); @@ -502,7 +503,6 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, struct nlattr *tb[TCA_ROUTE4_MAX + 1]; unsigned int h; int err; - bool new = true; if (!handle) { NL_SET_ERR_MSG(extack, "Creating with handle of 0 is invalid"); @@ -539,11 +539,10 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, f->tp = fold->tp; f->bkt = fold->bkt; - new = false; } err = route4_set_parms(net, tp, base, f, handle, head, tb, - tca[TCA_RATE], new, flags, extack); + tca[TCA_RATE], fold, flags, extack); if (err < 0) goto errout; From e190a7aabbea4fbfec0e74de134144cb4d040738 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 7 Sep 2026 16:21:33 -0300 Subject: [PATCH 0742/1317] selftests/tc-testing: Add cls_route bucket move and change tests Add 4 tdc tests for the cls_route bugs fixed earlier in this series: - Delete a route filter that was moved to another bucket (a7d2): Validates that deleting a filter, and making a bucket empty, does not leave a dangling empty bucket - Try to change a route filter onto an already used handle (c05a): Validates that attempting to change an existing filter's handle to an already taken one fails - Replace a route filter that shares its key with another filter (3f21): Validates that an in-place replace keeps the handle userspace named the filter by, rather than dropping the 0x7F00 bits from it - Replace both route filters sharing a key (9d0e): Validates that replacing one of the two does not make the other one unreplaceable Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260907192133.2639067-5-victor@mojatatu.com Signed-off-by: Paolo Abeni --- .../tc-testing/tc-tests/filters/route.json | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json index 05cedca67cca..2d5843aebd72 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json @@ -202,5 +202,215 @@ "teardown": [ "$TC qdisc del dev $DEV1 parent root drr" ] + }, + { + "id": "a7d2", + "name": "Delete a route filter that was moved to another bucket", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 1 to 1 classid 1:1", + "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10001 route from 1 to 2 classid 1:1", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 200 route from 5 to 5 classid 1:5" + ], + "cmdUnderTest": "$TC filter del dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10002 route from 1 to 2", + "expExitCode": "0", + "verifyCmd": "$TC -j filter show dev $DEV1 parent ffff:", + "matchJSON": [ + { + "protocol": "ip", + "pref": 200, + "kind": "route", + "chain": 0 + }, + { + "protocol": "ip", + "pref": 200, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x50005", + "flowid": "1:5" + } + } + ], + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "c05a", + "name": "Try to change a route filter onto an already used handle", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 1 to 1 classid 1:1 action ok", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 2 to 2 classid 1:2 action drop" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10001 route from 2 to 2 classid 1:1 action ok", + "expExitCode": "2", + "verifyCmd": "$TC -j filter show dev $DEV1 parent ffff:", + "matchJSON": [ + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0 + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10001", + "flowid": "1:1", + "actions": [ + { + "order": 1, + "kind": "gact", + "control_action": { + "type": "pass" + } + } + ] + } + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x20002", + "flowid": "1:2", + "actions": [ + { + "order": 1, + "kind": "gact", + "control_action": { + "type": "drop" + } + } + ] + } + } + ], + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "3f21", + "name": "Replace a route filter that shares its key with another filter", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10101 route from 1 to 1 classid 1:1", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10201 route from 1 to 1 classid 1:2" + ], + "cmdUnderTest": "$TC filter replace dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10101 route from 1 to 1 classid 1:9", + "expExitCode": "0", + "verifyCmd": "$TC -j filter show dev $DEV1 parent ffff:", + "matchJSON": [ + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0 + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10101", + "flowid": "1:9" + } + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10201", + "flowid": "1:2" + } + } + ], + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "9d0e", + "name": "Replace both route filters sharing a key", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10101 route from 1 to 1 classid 1:1", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10201 route from 1 to 1 classid 1:2", + "$TC filter replace dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10101 route from 1 to 1 classid 1:9" + ], + "cmdUnderTest": "$TC filter replace dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10201 route from 1 to 1 classid 1:8", + "expExitCode": "0", + "verifyCmd": "$TC -j filter show dev $DEV1 parent ffff:", + "matchJSON": [ + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0 + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10101", + "flowid": "1:9" + } + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10201", + "flowid": "1:8" + } + } + ], + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] From dff39930ad5e53d202bfdfb14687d1d2fd753b4d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 8 Sep 2026 20:55:25 +0000 Subject: [PATCH 0743/1317] net/sched: cls_api: Don't replay RTM_GETCHAIN in tc_ctl_chain(). If a netlink socket sends RTM_GETCHAIN requests repeatedly without recv()ing the responses, tc_ctl_chain() hogs CPU and triggers Hung Task splat. [0] As caught in the stack trace, netlink_attachskb() could confuse tc_ctl_chain() by returning -EAGAIN when the userspace netlink socket's receive buffer is full. The replay: label exists since commit 32a4f5ecd738 ("net: sched: introduce chain object to uapi") but was not used initially. Since commit 9f407f1768d3 ("net: sched: introduce chain templates"), the label is needed for RTM_NEWCHAIN because tcf_proto_lookup_ops() may release RTNL to call request_module(). However, the replay logic is unnecessary for RTM_GETCHAIN. Let's apply the replay logic only for RTM_NEWCHAIN. [0]: INFO: task repro:1018 is blocked on a mutex likely owned by task repro:1022. task:repro state:R running task stack:14096 pid:1022 tgid:1014 ppid:961 task_flags:0x400040 flags:0x00080000 Call Trace: ? clockevents_program_event (kernel/time/clockevents.c:372) ? pskb_expand_head (net/core/skbuff.c:615) ? skb_release_data (net/core/skbuff.c:1122) ? netlink_attachskb (./include/linux/skbuff.h:1323 ./include/linux/skbuff.h:1332 net/netlink/af_netlink.c:1232) ? __netlink_lookup (./include/linux/rcupdate.h:882 ./include/linux/rhashtable.h:711 net/netlink/af_netlink.c:499) ? tc_chain_notify (net/sched/cls_api.c:3045) ? tc_chain_notify (./include/linux/skbuff.h:1384 net/sched/cls_api.c:3041) ? netlink_unicast (net/netlink/af_netlink.c:1335) ? rtnl_unicast (./include/net/netlink.h:1198 net/core/rtnetlink.c:985) ? tc_ctl_chain (net/sched/cls_api.c:3242) ? rtnetlink_rcv_msg (net/core/rtnetlink.c:7146) ? netlink_unicast (net/netlink/af_netlink.c:1354) ? __pfx_rtnetlink_rcv_msg (net/core/rtnetlink.c:7177) ? netlink_rcv_skb (net/netlink/af_netlink.c:2556) ? netlink_unicast (net/netlink/af_netlink.c:1319) ? netlink_sendmsg (net/netlink/af_netlink.c:1900) ? __sock_sendmsg (net/socket.c:800) ? __sys_sendto (net/socket.c:2281) ? __x64_sys_sendto (net/socket.c:2288 net/socket.c:2284 net/socket.c:2284) ? do_syscall_64 (arch/x86/entry/syscall_64.c:61 arch/x86/entry/syscall_64.c:84) ? entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Fixes: 2ed9db3074fc ("net: sched: cls_api: fix dead code in switch") Reported-by: Taras Madan Signed-off-by: Kuniyuki Iwashima Reviewed-by: Jamal Hadi Salim Tested-by: hybris@mojatatu.ai Link: https://patch.msgid.link/20260908205537.863484-1-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/sched/cls_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index 9966766661d5..c47d2ee13641 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -3254,7 +3254,7 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n, tcf_chain_put(chain); errout_block: tcf_block_release(q, block, true); - if (err == -EAGAIN) + if (err == -EAGAIN && n->nlmsg_type == RTM_NEWCHAIN) /* Replay the request. */ goto replay; return err; From af406abfecad2f48d8f1fc646d3994f0982bac62 Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Fri, 4 Sep 2026 16:07:58 +0800 Subject: [PATCH 0744/1317] net: sun4i-emac: fix missing of_node_put() for phy_node of_parse_phandle() returns a node pointer with an elevated refcount. Add the missing of_node_put() on the probe error path after register_netdev() fails and in emac_remove(). Fixes: 492205050d77 ("net: Add EMAC ethernet driver found on Allwinner A10 SoC's") Signed-off-by: Li Youhong Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904080758.2432748-1-dayou5941@163.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/allwinner/sun4i-emac.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index 942454e29488..0ba67a55705d 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -1067,6 +1067,7 @@ static int emac_probe(struct platform_device *pdev) return 0; out_release_sram: + of_node_put(db->phy_node); sunxi_sram_release(&pdev->dev); out_clk_disable_unprepare: clk_disable_unprepare(db->clk); @@ -1094,6 +1095,7 @@ static void emac_remove(struct platform_device *pdev) } unregister_netdev(ndev); + of_node_put(db->phy_node); sunxi_sram_release(&pdev->dev); clk_disable_unprepare(db->clk); irq_dispose_mapping(ndev->irq); From 5d4d985957434867bbe85e4fa5e638f3e48ad522 Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Mon, 7 Sep 2026 02:42:34 +0000 Subject: [PATCH 0745/1317] net: hinic: fix mailbox segment buffer overflow check_mbox_seq_id_and_seg_len() validates that seq_id does not exceed SEQ_ID_MAX_VAL (42) and seg_len does not exceed MBOX_SEG_LEN (48). However, this allows the last segment (seq_id=42) to carry a full 48-byte payload, writing to offset 42*48=2016 for 48 bytes (ending at byte 2064). The receive buffer is only MBOX_MAX_BUF_SZ (2048) bytes, resulting in a 16-byte heap buffer overflow. The hinic3 driver already handles this correctly by defining MBOX_LAST_SEG_MAX_LEN and rejecting the last segment when it exceeds the remaining buffer space. Apply the same fix to the hinic driver. Fixes: a425b6e1c69b ("hinic: add mailbox function support") Signed-off-by: Aamir Ahmed Link: https://patch.msgid.link/AS8P251MB0001AE870B09020B46B5D7DBC8B22@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: Paolo Abeni --- drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c b/drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c index 2784127327e6..6e67a6c9578e 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c +++ b/drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c @@ -128,6 +128,7 @@ enum hinic_mbox_tx_status { #define SEQ_ID_START_VAL 0 #define SEQ_ID_MAX_VAL 42 +#define MBOX_LAST_SEG_MAX_LEN (MBOX_MAX_BUF_SZ - SEQ_ID_MAX_VAL * MBOX_SEG_LEN) #define NO_DMA_ATTRIBUTE_VAL 0 @@ -372,7 +373,8 @@ recv_pf_from_vf_mbox_handler(struct hinic_mbox_func_to_func *func_to_func, static bool check_mbox_seq_id_and_seg_len(struct hinic_recv_mbox *recv_mbox, u8 seq_id, u8 seg_len) { - if (seq_id > SEQ_ID_MAX_VAL || seg_len > MBOX_SEG_LEN) + if (seq_id > SEQ_ID_MAX_VAL || seg_len > MBOX_SEG_LEN || + (seq_id == SEQ_ID_MAX_VAL && seg_len > MBOX_LAST_SEG_MAX_LEN)) return false; if (seq_id == 0) { From 3c18e3c9a54e1239b72849502ca4737604bfbb46 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 3 Sep 2026 12:36:43 +0000 Subject: [PATCH 0746/1317] net: dsa: mt7530: populate lpi_interfaces to fix EEE support phylink_create() decides once and for all that a MAC supports managed EEE, and it requires the tx_lpi ops plus non-empty lpi_capabilities and lpi_interfaces. mt753x_phylink_get_caps() leaves lpi_interfaces empty. So ever since the conversion to phylink managed EEE, ethtool has answered "Not supported" on every mt753x port, and phy_disable_eee() has locked userspace out of turning EEE on. That undoes what commit 06dfcd4098cf ("net: dsa: mt7530: fix enabling EEE on MT7531 switch on all boards") arranged: EEE off by default, but reachable with ethtool. Leave the speeds above 1 Gbps out of both bitmaps. PMCR folds SPEED_2500 and SPEED_10000 onto PMCR_FORCE_SPEED_1000, so PMCR_FORCE_EEE1G would govern LPI on such a link, and that is unvalidated rather than known unsupported: MediaTek's SDK driver sets the EEE force bits for 100 Mbps and 1 Gbps only, and the unit of the wakeup timers is undocumented with the port clock at 2.5 times the rate. LPI stays off until userspace enables it, but the EEE advertisement of a PHY that advertises it out of reset comes back, since phylink stops force-clearing it. Fixes: 9cf21773f535 ("net: dsa: mt7530: convert to phylink managed EEE") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260903123644.23800-2-f@lex.la Signed-off-by: Paolo Abeni --- drivers/net/dsa/mt7530.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 2b7be091c056..3e61eb3c2b1e 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -3172,23 +3172,31 @@ static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port, config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE; + priv->info->mac_port_get_caps(ds, port, config); + /* The EN7528 GPHYs report EEE capability, but negotiating EEE with * common link partners (e.g. Realtek GbE NICs) results in an unstable * link with dropped frames. Leave the LPI capabilities empty so that * phylink disables EEE on these PHYs and refuses to enable it from * userspace. */ - if (priv->id != ID_EN7528) { + if (priv->id != ID_EN7528 && + config->mac_capabilities & (MAC_100FD | MAC_1000FD)) { u32 eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port)); - config->lpi_capabilities = MAC_100FD | MAC_1000FD | MAC_2500FD; + /* LPI above 1 Gbps is not supported */ + config->lpi_capabilities = config->mac_capabilities & + (MAC_100FD | MAC_1000FD); + phy_interface_copy(config->lpi_interfaces, + config->supported_interfaces); + __clear_bit(PHY_INTERFACE_MODE_2500BASEX, + config->lpi_interfaces); + /* tx_lpi_timer should be in microseconds. The time units for * LPI threshold are unspecified. */ config->lpi_timer_default = FIELD_GET(LPI_THRESH_MASK, eeecr); } - - priv->info->mac_port_get_caps(ds, port, config); } static int mt753x_pcs_validate(struct phylink_pcs *pcs, From d876c9cb2d16ed259449fe9da08c37a5cb81d724 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 3 Sep 2026 12:36:44 +0000 Subject: [PATCH 0747/1317] net: ethernet: mtk_eth_soc: populate lpi_interfaces to fix EEE support phylink_create() decides once and for all that a MAC supports managed EEE, and it requires the tx_lpi ops plus non-empty lpi_capabilities and lpi_interfaces. mtk_add_mac() leaves lpi_interfaces empty. So ever since EEE support was added, ethtool has answered "Not supported" on every MAC that uses mtk_phylink_ops, and phy_disable_eee() has locked userspace out of turning EEE on. MT7628 is unaffected, as rt5350_phylink_ops has no tx_lpi methods. Leave 2.5 Gbps out of both bitmaps, and the xGMII modes that mtk_mac_enable_tx_lpi() already refuses. MAC_MCR folds SPEED_2500 onto MAC_MCR_SPEED_1000, so MAC_MCR_EEE1G would govern LPI on such a link, and that is unvalidated rather than known unsupported: MediaTek's SDK driver sets the EEE force bits for 100 Mbps and 1 Gbps only, and the unit of the wakeup timers is undocumented with the port clock at 2.5 times the rate. mtk_mac_enable_tx_lpi() programs wake-up times taken from MT7531's reset values, and the SoC's own field has no reset value to fall back on. Only MT7981 has been seen to exit LPI cleanly with them, so the LPI interfaces sit behind a new MTK_GMAC_EEE capability that only MT7981 sets; every other SoC keeps the current behaviour until it has been confirmed. LPI stays off until userspace enables it, but the EEE advertisement of a PHY that advertises it out of reset comes back, since phylink stops force-clearing it. Fixes: 952d7325362f ("net: ethernet: mediatek: add EEE support") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260903123644.23800-3-f@lex.la Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mediatek/mtk_eth_soc.c | 18 +++++++++++++++--- drivers/net/ethernet/mediatek/mtk_eth_soc.h | 4 +++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index be3bd025c41a..fd7a49ae88d0 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -4828,7 +4828,7 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np) phy_interface_t phy_mode; struct phylink *phylink; struct mtk_mac *mac; - int id, err; + int id, err, i; int txqs = 1; u32 val; @@ -4907,8 +4907,8 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np) mac->phylink_config.type = PHYLINK_NETDEV; mac->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD; - mac->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD | - MAC_2500FD; + /* LPI above 1 Gbps is not supported */ + mac->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD; mac->phylink_config.lpi_timer_default = 1000; /* MT7623 gmac0 is now missing its speed-specific PLL configuration @@ -4966,6 +4966,18 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np) __set_bit(PHY_INTERFACE_MODE_INTERNAL, mac->phylink_config.supported_interfaces); + /* LPI wake-up timing is only verified on MTK_GMAC_EEE SoCs */ + if (MTK_HAS_CAPS(eth->soc->caps, MTK_GMAC_EEE)) { + phy_interface_copy(mac->phylink_config.lpi_interfaces, + mac->phylink_config.supported_interfaces); + __clear_bit(PHY_INTERFACE_MODE_2500BASEX, + mac->phylink_config.lpi_interfaces); + for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) + if (mtk_interface_mode_is_xgmii(eth, i)) + __clear_bit(i, + mac->phylink_config.lpi_interfaces); + } + phylink = phylink_create(&mac->phylink_config, of_fwnode_handle(mac->of_node), phy_mode, mac_ops); diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/drivers/net/ethernet/mediatek/mtk_eth_soc.h index 0168e2fbc619..88a9b3b23bea 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h @@ -994,6 +994,7 @@ enum mkt_eth_capabilities { MTK_U3_COPHY_V2_BIT, MTK_SRAM_BIT, MTK_36BIT_DMA_BIT, + MTK_GMAC_EEE_BIT, /* MUX BITS*/ MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT, @@ -1034,6 +1035,7 @@ enum mkt_eth_capabilities { #define MTK_U3_COPHY_V2 BIT_ULL(MTK_U3_COPHY_V2_BIT) #define MTK_SRAM BIT_ULL(MTK_SRAM_BIT) #define MTK_36BIT_DMA BIT_ULL(MTK_36BIT_DMA_BIT) +#define MTK_GMAC_EEE BIT_ULL(MTK_GMAC_EEE_BIT) #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW \ BIT_ULL(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT) @@ -1117,7 +1119,7 @@ enum mkt_eth_capabilities { #define MT7981_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | MTK_GMAC2_GEPHY | \ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \ MTK_MUX_U3_GMAC2_TO_QPHY | MTK_U3_COPHY_V2 | \ - MTK_RSTCTRL_PPE1 | MTK_SRAM) + MTK_RSTCTRL_PPE1 | MTK_SRAM | MTK_GMAC_EEE) #define MT7986_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | \ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \ From 9c182bc5d7817437a7d04ab96133f9191846d93d Mon Sep 17 00:00:00 2001 From: Florian Maillard Date: Mon, 24 Aug 2026 08:57:55 +0200 Subject: [PATCH 0748/1317] mmc: rtsx_pci_sdmmc: ignore broken write-protect on ThinkPad X260 The Realtek RTS522A card reader in the Lenovo ThinkPad X260 (subsystem 17aa:504a) incorrectly reports inserted SD cards as write-protected. This causes the MMC core to expose the card as read-only: mmcblk0: mmc0:aaaa SN256 238 GiB (ro) and /sys/block/mmcblk0/ro reports 1. Setting MMC_CAP2_NO_WRITE_PROTECT makes the card writable again. Limit the quirk to the affected Lenovo subsystem. Assisted-by: ChatGPT:GPT-5.6 Sol Signed-off-by: Florian Maillard Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/rtsx_pci_sdmmc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 8dfbc62f165b..d25d4cb59bc9 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -1425,6 +1425,11 @@ static void realtek_init_host(struct realtek_pci_sdmmc *host) mmc->caps = mmc->caps | MMC_CAP_AGGRESSIVE_PM; mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE | MMC_CAP2_NO_SDIO; + + if (pcr->pci->device == 0x522a && + pcr->pci->subsystem_vendor == PCI_VENDOR_ID_LENOVO && + pcr->pci->subsystem_device == 0x504a) + mmc->caps2 |= MMC_CAP2_NO_WRITE_PROTECT; mmc->max_current_330 = 400; mmc->max_current_180 = 800; mmc->ops = &realtek_pci_sdmmc_ops; From 08b54e16d547d5c1aa61bf7a3595bb1620975eeb Mon Sep 17 00:00:00 2001 From: Zhu Ling Date: Fri, 4 Sep 2026 17:07:46 +0800 Subject: [PATCH 0749/1317] mmc: core: Fix OF node reference leak on card add failure mmc_of_find_child_device() returns a device node with its reference count incremented. mmc_add_card() stores the reference before calling device_add(), while the card is marked present only after device_add() succeeds. If device_add() fails, the callers release the card through mmc_remove_card(). However, mmc_remove_card() only drops the OF node reference for a present card, leaking the reference on this error path. Move of_node_put() outside the present-card conditional so the reference is released for both registered cards and card-add failures. Fixes: 25185f3f31c9 ("mmc: Add SDIO function devicetree subnode parsing") Cc: stable@vger.kernel.org Signed-off-by: Zhu Ling Reviewed-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/core/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index be5cf338bdeb..92ad39c0cabc 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -417,8 +417,8 @@ void mmc_remove_card(struct mmc_card *card) mmc_hostname(card->host), card->rca); } device_del(&card->dev); - of_node_put(card->dev.of_node); } + of_node_put(card->dev.of_node); if (host->cqe_enabled) { host->cqe_ops->cqe_disable(host); From cee9c863ee68cb27d66745eb03f60e357f4f8ad2 Mon Sep 17 00:00:00 2001 From: Alex Bereza Date: Mon, 17 Aug 2026 11:23:55 +0200 Subject: [PATCH 0750/1317] dmaengine: xilinx_dma: Fix hardware buffer descriptor reuse order xilinx_dma_alloc_chan_resources() builds a static ring of hardware buffer descriptors once and the driver uses this ring throughout the lifetime of a channel. This requires the allocation order of hardware buffer descriptors from chan->free_seg_list to stay in sync with the hardware buffer descriptor ring built at channel allocation time by returning oldest descriptors to chan->free_seg_list first. When chan->pending_list is not empty e.g. during xilinx_dma_terminate_all() the chan->free_seg_list and the order of the static hardware buffer descriptor ring get out of sync. Descriptors age in this order: pending -> active -> done. So freeing pending_list first returns the newest buffer descriptors to the chan->free_seg_list first and thus breaks the order required by the static hardware buffer descriptor ring. Then when the channel is reused, after a wrap around of the free_seg_list the DMA will find a hardware buffer descriptor with a length field that is still zeroed and stop with something like this: xilinx-vdma 86000000.dma: Channel 000000003a21d7b8 has errors 10, cdr 6de4c000 tdr 6de4c000 After this no more descriptors are completed and a consumer potentially blocks and waits forever. The only way to get out of this error state is to rebuild the static hardware buffer descriptor ring and the free_seg_list by releasing and re-acquiring the channel. Fix the order in which hardware buffer descriptors are returned to free_seg_list to ensure the mentioned requirement holds. Fixes: 23059408b6a3 ("dmaengine: xilinx_dma: Fix race condition in the driver for multiple descriptor scenario") Signed-off-by: Alex Bereza Reviewed-by: Frank Li Reviewed-by: Suraj Gupta Link: https://patch.msgid.link/20260817-fix-hw-buf-desc-reuse-v1-1-d79827a844c7@bereza.email Signed-off-by: Vinod Koul --- drivers/dma/xilinx/xilinx_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c index bef2b031dba1..0817b74f7450 100644 --- a/drivers/dma/xilinx/xilinx_dma.c +++ b/drivers/dma/xilinx/xilinx_dma.c @@ -920,9 +920,9 @@ static void xilinx_dma_free_descriptors(struct xilinx_dma_chan *chan) spin_lock_irqsave(&chan->lock, flags); - xilinx_dma_free_desc_list(chan, &chan->pending_list); xilinx_dma_free_desc_list(chan, &chan->done_list); xilinx_dma_free_desc_list(chan, &chan->active_list); + xilinx_dma_free_desc_list(chan, &chan->pending_list); spin_unlock_irqrestore(&chan->lock, flags); } From c90b6973daa37f4c283342dff881ae001dea4fe6 Mon Sep 17 00:00:00 2001 From: Christian Lugnberg Date: Mon, 17 Aug 2026 15:51:22 +0200 Subject: [PATCH 0751/1317] dmaengine: sun6i: fix non-atomic read of DMA position registers sun6i_get_chan_size() reads DMA_CHAN_LLI_ADDR and DMA_CHAN_CUR_CNT in two separate readl() calls with no synchronisation between them: pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); DMA_CHAN_LLI_ADDR holds the physical address of the *next* descriptor the engine will load once the current one completes. DMA_CHAN_CUR_CNT holds the remaining byte count for the *current* descriptor. If the DMA engine advances to the next LLI entry between the two reads, pos becomes stale: it still points to what was the next descriptor at the time of the first read, but that descriptor is now the current one and CUR_CNT reflects its initial (full) byte count. The subsequent virtual-chain walk starts one entry too early and accumulates an extra full period's worth of bytes into the residue estimate. Fix this by re-reading DMA_CHAN_LLI_ADDR after DMA_CHAN_CUR_CNT and retrying if the value changed. This double-read pattern guarantees that both registers were sampled during the same descriptor interval. The cost is at most one extra readl() pair per call in the racy case, which occurs only at descriptor boundaries (~every 2 ms) and is negligible. Fixes: a90e173f3faf ("dmaengine: sun6i: Add cyclic capability") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Christian Lugnberg Reviewed-by: Frank Li Link: https://patch.msgid.link/20260817135723.12807-2-christian.lugnberg@soundtrack.io Signed-off-by: Vinod Koul --- drivers/dma/sun6i-dma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c index f47a326dd7ff..04fe1f5042e9 100644 --- a/drivers/dma/sun6i-dma.c +++ b/drivers/dma/sun6i-dma.c @@ -354,8 +354,10 @@ static size_t sun6i_get_chan_size(struct sun6i_pchan *pchan) size_t bytes; dma_addr_t pos; - pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); - bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); + do { + pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); + bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); + } while (pos != readl(pchan->base + DMA_CHAN_LLI_ADDR)); if (pos == LLI_LAST_ITEM) return bytes; From 9096bdc8d930147f7c39a493a859acbd3a8485d8 Mon Sep 17 00:00:00 2001 From: Christian Lugnberg Date: Mon, 17 Aug 2026 15:51:23 +0200 Subject: [PATCH 0752/1317] dmaengine: sun6i: fix undefined behaviour in sun6i_dma_tx_status sun6i_dma_tx_status() calls vchan_find_desc() to look up the virtual descriptor for a given cookie, before checking whether the pointer vd is NULL: vd = vchan_find_desc(&vchan->vc, cookie); txd = to_sun6i_desc(&vd->tx); /* vd may be NULL here */ if (vd) { for (lli = txd->v_lli; ...) vchan_find_desc() returns NULL when the descriptor has already been completed or is in-flight on a physical channel and no longer present in the virtual channel's descriptor list. When vd is NULL, to_sun6i_desc() is called unconditionally on &vd->tx before the NULL check, which is undefined behaviour. Move the call inside the if (vd) guard to ensure it is only reached with a valid pointer. vd = vchan_find_desc(&vchan->vc, cookie); if (vd) { struct sun6i_desc *txd = to_sun6i_desc(&vd->tx); for (lli = txd->v_lli; ...) Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Christian Lugnberg Reviewed-by: Frank Li Link: https://patch.msgid.link/20260817135723.12807-3-christian.lugnberg@soundtrack.io Signed-off-by: Vinod Koul --- drivers/dma/sun6i-dma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c index 04fe1f5042e9..7704b016aed8 100644 --- a/drivers/dma/sun6i-dma.c +++ b/drivers/dma/sun6i-dma.c @@ -981,7 +981,6 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan, struct sun6i_pchan *pchan = vchan->phy; struct sun6i_dma_lli *lli; struct virt_dma_desc *vd; - struct sun6i_desc *txd; enum dma_status ret; unsigned long flags; size_t bytes = 0; @@ -993,9 +992,9 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan, spin_lock_irqsave(&vchan->vc.lock, flags); vd = vchan_find_desc(&vchan->vc, cookie); - txd = to_sun6i_desc(&vd->tx); if (vd) { + struct sun6i_desc *txd = to_sun6i_desc(&vd->tx); for (lli = txd->v_lli; lli != NULL; lli = lli->v_lli_next) bytes += lli->len; } else if (!pchan || !pchan->desc) { From f6504be006aa4bb4bd26285f410a885c17920d65 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Mon, 17 Aug 2026 22:44:33 +0200 Subject: [PATCH 0753/1317] dmaengine: pxa: fix double counting of the hw descriptors pxad_alloc_desc() was converted from kzalloc(struct_size(sw_desc, hw_desc, nb_hw_desc), GFP_NOWAIT) to kzalloc_flex(), which sets the __counted_by() counter sw_desc->nb_desc itself - but only where the compiler has __builtin_counted_by_ref(), so from gcc 15.1 or clang 22.1 on. The loop below it still increments nb_desc, which makes it come out doubled there and correct elsewhere. nb_desc is what pxad_free_desc() iterates over and what set_updater_desc() indexes from, so set it explicitly and drop the increment. The error path has to lower it to the number of descriptors allocated so far, otherwise pxad_free_desc() would free entries that were never allocated. Fixes: 69050f8d6d075 ("treewide: Replace kmalloc with kmalloc_obj for non-scalar types") Assisted-by: Claude:claude-opus-5 Signed-off-by: Sascha Hauer Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20260817-dmaengine-pxa-v1-1-850c215c1196@pengutronix.de Link: https://patch.msgid.link/20260817-dmaengine-pxa-v2-1-f42ab0569a48@pengutronix.de Signed-off-by: Vinod Koul --- drivers/dma/pxa_dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c index fa2ee0b3e09f..fc43124fefa8 100644 --- a/drivers/dma/pxa_dma.c +++ b/drivers/dma/pxa_dma.c @@ -744,6 +744,7 @@ pxad_alloc_desc(struct pxad_chan *chan, unsigned int nb_hw_desc) sw_desc = kzalloc_flex(*sw_desc, hw_desc, nb_hw_desc, GFP_NOWAIT); if (!sw_desc) return NULL; + sw_desc->nb_desc = nb_hw_desc; sw_desc->desc_pool = chan->desc_pool; for (i = 0; i < nb_hw_desc; i++) { @@ -752,10 +753,10 @@ pxad_alloc_desc(struct pxad_chan *chan, unsigned int nb_hw_desc) dev_err(&chan->vc.chan.dev->device, "%s(): Couldn't allocate the %dth hw_desc from dma_pool %p\n", __func__, i, sw_desc->desc_pool); + sw_desc->nb_desc = i; goto err; } - sw_desc->nb_desc++; sw_desc->hw_desc[i] = desc; if (i == 0) From 125755776bc6d4dd53eaf551c87e3d460625d638 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Fri, 4 Sep 2026 14:43:07 +0100 Subject: [PATCH 0754/1317] tcp: reject non zerocopy devmem tx Devmem tcp tx doesn't work without zero-copy, however it's not currently enforced if NETIF_F_SG isn't present. In this case, tcp_sendmsg_locked() will try the copy path and try to copy data from an iovec which consists of offsets into the dma-buf and would normally fail. Moreover, d9c56501c72fd ("net: tcp: block mixing readable and unreadable frags") relies on that and assumes that the devmem binding is present IFF we're using the zero-copy path, which can be used to mix net-iov and pages in a single skb, and break invariants. Let's reject devmem tx without zero-copy. Note, the parameter check the patch is modifying is too loose, we can create an io_uring request with dmabuf_id and all ZC flags, but which won't have the binding. We replace it with stricter validation. Fixes: bd61848900bff ("net: devmem: Implement TX path") Fixes: d9c56501c72fd ("net: tcp: block mixing readable and unreadable frags") Signed-off-by: Pavel Begunkov Reviewed-by: Mina Almasry Link: https://patch.msgid.link/fdc2478d8f21268d7078556409887d8e6ba0ad32.1788529053.git.asml.silence@gmail.com Signed-off-by: Paolo Abeni --- net/ipv4/tcp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 1c867a302444..562752352afe 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1169,8 +1169,7 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) zc = MSG_SPLICE_PAGES; } - if (!sockc_err && sockc.dmabuf_id && - (!(flags & MSG_ZEROCOPY) || !sock_flag(sk, SOCK_ZEROCOPY))) { + if (!sockc_err && sockc.dmabuf_id && (zc != MSG_ZEROCOPY || !binding)) { err = -EINVAL; goto out_err; } From ccbe7540e4aad0d1c3acc249697350b93ccb8025 Mon Sep 17 00:00:00 2001 From: Vladislav Karmanov Date: Tue, 8 Sep 2026 17:52:13 +0300 Subject: [PATCH 0755/1317] net: phy: mediatek-ge: disable EEE on the MT7530 PHY The MT7530 internal GE PHY advertises EEE by hardware default, but its EEE support is defective: with EEE advertised, some link partners fail to establish a stable link. On a 2-pair (4-wire) cable where both ends advertise gigabit, 1000BASE-T training cannot succeed, and instead of falling back to 100 Mbps the port loops, so no link or DHCP lease is ever obtained. MediaTek confirms the hardware is the root cause (Landen Chao, 2021): "EEE of the 10-year-old MT7530 internal gephy has many IOT problems, so it is recommended to disable its EEE." mtk_gephy_config_init() used to clear the EEE advertisement early, but commit af3b4b0e59de ("net: phy: mediatek-ge: do not disable EEE advertisement") removed that on the rationale that the DSA subdriver already performs an early disable. That holds for MT7531, whose mt7531_setup() clears MDIO_AN_EEE_ADV on each switch PHY, but not for the MT7530 PHY: neither the MT7621 integrated switch nor the dedicated MT7530 IC ever had such a loop, so removing it left those boards without any working early EEE disable and the link flapping came back. Since the broken hardware is the PHY, fix it in the PHY driver so it covers all users of this PHY, integrated in a switch or standalone: - clear MDIO_AN_EEE_ADV in probe(), as early as possible, before anything can negotiate EEE with the link partner; - clear it again in config_init() and call phy_disable_eee() there. config_init() is what phy_init_hw() replays after a PHY reset, when the register is back at its EEE-advertising hardware default, and it runs after of_set_phy_eee_broken() in phy_probe(), so the eee_disabled_modes mask survives and neither phylib nor userspace can re-enable EEE. dp83867 disables broken EEE from config_init() the same way. Auto-negotiation then falls back to a stable 100 Mbps link instead of looping at gigabit. Tested on ASUS RT-AX53U (MT7621): with a 2-pair cable on the WAN port, a single clean 100 Mbps link comes up and a DHCP lease is obtained, where the unpatched driver loops. Fixes: af3b4b0e59de ("net: phy: mediatek-ge: do not disable EEE advertisement") Suggested-by: Andrew Lunn Signed-off-by: Vladislav Karmanov Link: https://patch.msgid.link/20260908145213.3976508-1-vladislav.karmanov.dev@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/phy/mediatek/mtk-ge.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/net/phy/mediatek/mtk-ge.c b/drivers/net/phy/mediatek/mtk-ge.c index 73d9b72f9d9e..96d8ac5154e5 100644 --- a/drivers/net/phy/mediatek/mtk-ge.c +++ b/drivers/net/phy/mediatek/mtk-ge.c @@ -62,10 +62,38 @@ static void mtk_gephy_config_init(struct phy_device *phydev) FIELD_PREP(MTK_MCC_NEARECHO_OFFSET_MASK, 0x3)); } +static int mt7530_phy_probe(struct phy_device *phydev) +{ + /* The MT7530 internal GE PHY has broken EEE: with EEE advertised, + * some link partners fail to establish a stable link (on a 2-pair + * cable, 1000BASE-T training fails and the port loops instead of + * falling back). MediaTek recommends disabling EEE on this PHY. + * Clear the advertisement as early as possible, before anything + * can negotiate EEE with the link partner. + */ + return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); +} + static int mt7530_phy_config_init(struct phy_device *phydev) { + int ret; + mtk_gephy_config_init(phydev); + /* The probe() clear alone is not durable: phy_init_hw() replays only + * ->config_init after a PHY reset, with the register back at its + * EEE-advertising hardware default, and phy_probe() zeroes + * eee_disabled_modes (of_set_phy_eee_broken()) after ->probe already + * ran. Clear the advertisement again and mark EEE disabled, so that + * neither phylib nor userspace can re-enable it; dp83867 disables + * broken EEE from config_init() the same way. + */ + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); + if (ret) + return ret; + + phy_disable_eee(phydev); + /* Increase post_update_timer */ phy_write_paged(phydev, MTK_PHY_PAGE_EXTENDED_3, MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG11, 0x4b); @@ -100,6 +128,7 @@ static struct phy_driver mtk_gephy_driver[] = { { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530), .name = "MediaTek MT7530 PHY", + .probe = mt7530_phy_probe, .config_init = mt7530_phy_config_init, /* Interrupts are handled by the switch, not the PHY * itself. From 7ed1e3070c9b4bbd67d5519e14711038dd53ab13 Mon Sep 17 00:00:00 2001 From: Alex Bereza Date: Tue, 18 Aug 2026 09:36:29 +0200 Subject: [PATCH 0756/1317] dmaengine: xilinx_dma: Fix hardware buffer descriptor chain after cyclic DMA Using the DMA in cyclic mode modifies the hardware buffer descriptor chain in xilinx_dma_prep_dma_cyclic so that the last descriptor used by the cyclic transfer points back to the first descriptor, but it never restores the original descriptor ring. This breaks using non-cyclic mode after cyclic mode with an error like: xilinx-vdma 86000000.dma: Channel 00000000354d5c8d has errors 100, cdr 6de40000 tdr 6de40400 The only way to get out of this error state is to rebuild the hardware buffer descriptor ring by releasing and re-acquiring the channel. Fix using non-cyclic mode after cyclic mode by always restoring the original buffer descriptor ring in the same manner as it is set up by xilinx_dma_alloc_chan_resources(). Fixes: 23059408b6a3 ("dmaengine: xilinx_dma: Fix race condition in the driver for multiple descriptor scenario") Signed-off-by: Alex Bereza Reviewed-by: Frank Li Reviewed-by: Suraj Gupta Link: https://patch.msgid.link/20260817-fix-hw-buf-desc-after-cyclic-mode-v1-1-1fe47e701d6c@bereza.email Link: https://patch.msgid.link/20260818-fix-hw-buf-desc-after-cyclic-mode-v2-1-530ff44c6a81@bereza.email Signed-off-by: Vinod Koul --- drivers/dma/xilinx/xilinx_dma.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c index 0817b74f7450..cffe7c6fa640 100644 --- a/drivers/dma/xilinx/xilinx_dma.c +++ b/drivers/dma/xilinx/xilinx_dma.c @@ -756,15 +756,25 @@ xilinx_aximcdma_alloc_tx_segment(struct xilinx_dma_chan *chan) return segment; } -static void xilinx_dma_clean_hw_desc(struct xilinx_axidma_desc_hw *hw) +static void xilinx_dma_clean_hw_desc(struct xilinx_dma_chan *chan, + struct xilinx_axidma_tx_segment *segment) { - u32 next_desc = hw->next_desc; - u32 next_desc_msb = hw->next_desc_msb; + dma_addr_t next; + u32 i; - memset(hw, 0, sizeof(struct xilinx_axidma_desc_hw)); + /* + * Restore the buffer descriptor's next descriptor pointer to the value + * set up in xilinx_dma_alloc_chan_resources(). Otherwise using the DMA + * in cyclic mode leaves the next descriptor pointer altered and + * prevents subsequent non-cyclic transfers. + */ + i = segment - chan->seg_v; + next = chan->seg_p + + sizeof(*chan->seg_v) * ((i + 1) % XILINX_DMA_NUM_DESCS); - hw->next_desc = next_desc; - hw->next_desc_msb = next_desc_msb; + memset(&segment->hw, 0, sizeof(segment->hw)); + segment->hw.next_desc = lower_32_bits(next); + segment->hw.next_desc_msb = upper_32_bits(next); } static void xilinx_mcdma_clean_hw_desc(struct xilinx_aximcdma_desc_hw *hw) @@ -786,7 +796,7 @@ static void xilinx_mcdma_clean_hw_desc(struct xilinx_aximcdma_desc_hw *hw) static void xilinx_dma_free_tx_segment(struct xilinx_dma_chan *chan, struct xilinx_axidma_tx_segment *segment) { - xilinx_dma_clean_hw_desc(&segment->hw); + xilinx_dma_clean_hw_desc(chan, segment); list_add_tail(&segment->node, &chan->free_seg_list); } From 985a663bf00799c1daf1c5789efa6406958780c8 Mon Sep 17 00:00:00 2001 From: Faicker Mo Date: Tue, 8 Sep 2026 12:06:29 +0800 Subject: [PATCH 0757/1317] net: net_failover: Fix the deadlock in net_failover_slave_name_change() This is a sibling fix of commit b84c5632c7b3 ("net: net_failover: Fix the deadlock in slave register"). There is netdev_lock_ops() in the upper callers, so using netif_open() instead of dev_open(). Call Trace: __schedule+0x2bb/0x650 schedule+0x27/0xb0 schedule_preempt_disabled+0x15/0x30 __mutex_lock.constprop.0+0x550/0xaf0 __mutex_lock_slowpath+0x13/0x20 mutex_lock+0x3b/0x50 dev_open+0x3b/0xe0 net_failover_slave_name_change+0x22/0x40 failover_event+0xd4/0x1e0 notifier_call_chain+0x62/0xf0 raw_notifier_call_chain+0x16/0x30 call_netdevice_notifiers_info+0x50/0x80 netif_change_name+0x200/0x330 do_setlink.isra.0+0xb12/0xdf0 ? security_capable+0x9a/0x1e0 ? ns_capable+0x31/0x60 rtnl_setlink+0x302/0x670 ? netlink_recvmsg+0x296/0x340 ? security_capable+0x9a/0x1e0 ? __pfx_rtnl_setlink+0x10/0x10 rtnetlink_rcv_msg+0x384/0x460 ? __pfx_rtnetlink_rcv_msg+0x10/0x10 netlink_rcv_skb+0x61/0x120 rtnetlink_rcv+0x15/0x30 netlink_unicast+0x28f/0x3c0 netlink_sendmsg+0x216/0x450 __sys_sendto+0x222/0x230 __x64_sys_sendto+0x24/0x40 x64_sys_call+0x1d5d/0x2390 do_syscall_64+0x105/0x5a0 ? do_syscall_64+0x140/0x5a0 ? exc_page_fault+0x94/0x1e0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Fixes: 7e4d784f5810 ("net: hold netdev instance lock during rtnetlink operations") Signed-off-by: Faicker Mo Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260908040708.3972058-1-faicker.mo@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/net_failover.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/net_failover.c b/drivers/net/net_failover.c index 3f7d31033bae..1b5213e87070 100644 --- a/drivers/net/net_failover.c +++ b/drivers/net/net_failover.c @@ -675,7 +675,7 @@ static int net_failover_slave_name_change(struct net_device *slave_dev, /* We need to bring up the slave after the rename by udev in case * open failed with EBUSY when it was registered. */ - dev_open(slave_dev, NULL); + netif_open(slave_dev, NULL); return 0; } From e08aca85c02ff290f785f07acae758f0daf5f49e Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Thu, 10 Sep 2026 09:35:13 -0400 Subject: [PATCH 0758/1317] RDMA/efa: Keep admin queues alive while IRQ is registered The management IRQ handler accesses both the admin completion queue and the async event queue. The driver registered the IRQ before constructing these queues and destroyed them before freeing the IRQ, so the handler's lifetime was not contained by the resources it accesses. Initialize the queues with interrupts masked, request the IRQ, and then switch to interrupt mode. On removal, reset the device and free the IRQ before destroying the queues. Also reset the device before destroying the queues if IRQ registration fails, because the device already has their DMA addresses. Fixes: b7f5e880f377 ("RDMA/efa: Add the efa module") Link: https://patch.msgid.link/20260907-use-after-free-of-admin-queue-struct-v1-1-dd9d9267fbf4@nvidia.com Reviewed-by: Michael Margolin Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/efa/efa_com.c | 4 +--- drivers/infiniband/hw/efa/efa_main.c | 15 +++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/infiniband/hw/efa/efa_com.c b/drivers/infiniband/hw/efa/efa_com.c index 583b1cf0d721..cb2d448db2d7 100644 --- a/drivers/infiniband/hw/efa/efa_com.c +++ b/drivers/infiniband/hw/efa/efa_com.c @@ -850,7 +850,7 @@ int efa_com_admin_init(struct efa_com_dev *edev, aq->dmadev = edev->dmadev; aq->efa_dev = edev->efa_dev; - set_bit(EFA_AQ_STATE_POLLING_BIT, &aq->state); + efa_com_set_admin_polling_mode(edev, true); sema_init(&aq->avail_cmds, aq->depth); @@ -868,8 +868,6 @@ int efa_com_admin_init(struct efa_com_dev *edev, if (err) goto err_destroy_sq; - efa_com_set_admin_polling_mode(edev, false); - err = efa_com_admin_init_aenq(edev, aenq_handlers); if (err) goto err_destroy_cq; diff --git a/drivers/infiniband/hw/efa/efa_main.c b/drivers/infiniband/hw/efa/efa_main.c index 4cd80727dcd2..b1df9bb8fac2 100644 --- a/drivers/infiniband/hw/efa/efa_main.c +++ b/drivers/infiniband/hw/efa/efa_main.c @@ -619,18 +619,21 @@ static struct efa_dev *efa_probe_device(struct pci_dev *pdev) edev->aq.msix_vector_idx = dev->admin_msix_vector_idx; edev->aenq.msix_vector_idx = dev->admin_msix_vector_idx; - err = efa_set_mgmnt_irq(dev); + err = efa_com_admin_init(edev, &aenq_handlers); if (err) goto err_disable_msix; - err = efa_com_admin_init(edev, &aenq_handlers); + err = efa_set_mgmnt_irq(dev); if (err) - goto err_free_mgmnt_irq; + goto err_destroy_admin; + + efa_com_set_admin_polling_mode(edev, false); return dev; -err_free_mgmnt_irq: - efa_free_irq(dev, &dev->admin_irq); +err_destroy_admin: + efa_com_dev_reset(edev, EFA_REGS_RESET_INIT_ERR); + efa_com_admin_destroy(edev); err_disable_msix: efa_disable_msix(dev); err_reg_read_destroy: @@ -654,8 +657,8 @@ static void efa_remove_device(struct pci_dev *pdev, edev = &dev->edev; efa_com_dev_reset(edev, reset_reason); - efa_com_admin_destroy(edev); efa_free_irq(dev, &dev->admin_irq); + efa_com_admin_destroy(edev); efa_disable_msix(dev); efa_com_mmio_reg_read_destroy(edev); devm_iounmap(&pdev->dev, edev->reg_bar); From e22a3627b7151754f07f90ea3d1ab6e85f5d93f4 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Thu, 10 Sep 2026 09:35:13 -0400 Subject: [PATCH 0759/1317] RDMA/efa: Keep EQ resources alive while IRQ is registered The completion IRQ handler accesses the EQ state and DMA buffer. Its IRQ was registered before that state was initialized, while teardown released the buffer before free_irq() synchronized the handler. Initialize the EQ without arming it, register the IRQ, and then arm it. Reverse the resource order during teardown by freeing the IRQ before destroying the EQ. Fixes: 2a152512a155 ("RDMA/efa: CQ notifications") Link: https://patch.msgid.link/20260907-use-after-free-of-admin-queue-struct-v1-2-dd9d9267fbf4@nvidia.com Reviewed-by: Michael Margolin Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/efa/efa_com.c | 3 +-- drivers/infiniband/hw/efa/efa_com.h | 1 + drivers/infiniband/hw/efa/efa_main.c | 20 +++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/infiniband/hw/efa/efa_com.c b/drivers/infiniband/hw/efa/efa_com.c index cb2d448db2d7..04c6d63c449e 100644 --- a/drivers/infiniband/hw/efa/efa_com.c +++ b/drivers/infiniband/hw/efa/efa_com.c @@ -1252,7 +1252,7 @@ static void efa_com_destroy_eq(struct efa_com_dev *edev, err); } -static void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq) +void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq) { u32 val = 0; @@ -1341,7 +1341,6 @@ int efa_com_eq_init(struct efa_com_dev *edev, struct efa_com_eq *eeq, eeq->phase = 1; eeq->depth = params.depth; eeq->cb = cb; - efa_com_arm_eq(edev, eeq); return 0; diff --git a/drivers/infiniband/hw/efa/efa_com.h b/drivers/infiniband/hw/efa/efa_com.h index 0341704d0921..98fb6a42a6cb 100644 --- a/drivers/infiniband/hw/efa/efa_com.h +++ b/drivers/infiniband/hw/efa/efa_com.h @@ -169,6 +169,7 @@ int efa_com_admin_init(struct efa_com_dev *edev, void efa_com_admin_destroy(struct efa_com_dev *edev); int efa_com_eq_init(struct efa_com_dev *edev, struct efa_com_eq *eeq, efa_eqe_handler cb, u16 depth, u8 msix_vec); +void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq); void efa_com_eq_destroy(struct efa_com_dev *edev, struct efa_com_eq *eeq); int efa_com_dev_reset(struct efa_com_dev *edev, enum efa_regs_reset_reason_types reset_reason); diff --git a/drivers/infiniband/hw/efa/efa_main.c b/drivers/infiniband/hw/efa/efa_main.c index b1df9bb8fac2..753ed8430b69 100644 --- a/drivers/infiniband/hw/efa/efa_main.c +++ b/drivers/infiniband/hw/efa/efa_main.c @@ -302,28 +302,30 @@ static void efa_set_host_info(struct efa_dev *dev) static void efa_destroy_eq(struct efa_dev *dev, struct efa_eq *eq) { - efa_com_eq_destroy(&dev->edev, &eq->eeq); efa_free_irq(dev, &eq->irq); + efa_com_eq_destroy(&dev->edev, &eq->eeq); } static int efa_create_eq(struct efa_dev *dev, struct efa_eq *eq, u32 msix_vec) { int err; - efa_setup_comp_irq(dev, eq, msix_vec); - err = efa_request_irq(dev, &eq->irq); - if (err) - return err; - err = efa_com_eq_init(&dev->edev, &eq->eeq, efa_process_eqe, dev->dev_attr.max_eq_depth, msix_vec); if (err) - goto err_free_comp_irq; + return err; + + efa_setup_comp_irq(dev, eq, msix_vec); + err = efa_request_irq(dev, &eq->irq); + if (err) + goto err_destroy_eq; + + efa_com_arm_eq(&dev->edev, &eq->eeq); return 0; -err_free_comp_irq: - efa_free_irq(dev, &eq->irq); +err_destroy_eq: + efa_com_eq_destroy(&dev->edev, &eq->eeq); return err; } From 3db7d7d583419f7b1f2e141e36418802dbb25cf8 Mon Sep 17 00:00:00 2001 From: Vadim Nikitushkin Date: Wed, 9 Sep 2026 23:50:28 +0300 Subject: [PATCH 0760/1317] drm/ttm: fix swapped-out resources never leaving their bulk_move range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ttm_tt_swapout() returns the number of pages swapped out on success and a negative error code on failure; for a populated ttm it never returns zero. Commit b2ed01e7ad3d ("drm/ttm: Fix ttm_bo_swapout() infinite LRU walk on swapout failure") moved the bulk_move bookkeeping in ttm_bo_swapout_cb() under "if (!ret)", so the ttm_resource_del_bulk_move_unevictable() / ttm_resource_move_to_lru_tail() pair is now skipped on every successful swapout. The equivalent change for the shrinker in commit 1d59f36e95f7 ("drm/ttm: Fix ttm_bo_shrink() infinite LRU walk on backup failure") tests "lret > 0", which is what was intended here as well. Before b2ed01e7ad3d the resource was taken off the bulk_move before the swapout; since then a swapped-out resource stays inside its BO's bulk_move range (and on the manager LRU) although it is unevictable. When it is later freed or the BO leaves the bulk_move (ttm_resource_free(), ttm_bo_set_bulk_move() via amdgpu_vm_bo_del()), ttm_resource_del_bulk_move() skips it because of its !ttm_resource_unevictable() guard, so a range endpoint in pos->first / pos->last is left pointing at freed memory. The next ttm_lru_bulk_move_tail() or ttm_resource_add_bulk_move() on that cursor is a use-after-free, seen as the resv WARN in ttm_lru_bulk_move_add(), "list_del corruption" in ttm_resource_move_to_lru_tail() or a NULL dereference in ttm_resource_manager_next() -- minutes to hours after a hibernation, or at process exit / reboot following one. Samuel Ainsworth's analysis of drm/amd issue 5387 (see Link) identified the dangling cursor; the missing removal at swapout time is the reason it dangles. Testing the condition for success restores the removal. On an AMD Phoenix APU (ASUS UM3406GA, gfx1103) running suspend-then-hibernate on a 7.0.y stable kernel carrying the backport (Ubuntu 7.0.0-31) the bug crashed 5 of 18 hibernation cycles; a function profile of one hibernation showed 336 ttm_tt_swapout() calls and zero ttm_resource_del_bulk_move_unevictable() calls. With this change the removal happens for every swapped-out resource and 12 further cycles were clean. Fixes: b2ed01e7ad3d ("drm/ttm: Fix ttm_bo_swapout() infinite LRU walk on swapout failure") Cc: stable@vger.kernel.org # v7.1+ Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5387 Link: https://lore.kernel.org/dri-devel/CAHYiNPa6aVacJoLOje-qZ1GyYx-9p0tN4NuP8D_eSL+UJeevXw@mail.gmail.com/ Signed-off-by: Vadim Nikitushkin Reviewed-by: Thomas Hellström Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260909205028.13799-1-bub4z0r@gmail.com --- drivers/gpu/drm/ttm/ttm_bo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index ef56c18ded1b..a12af5b38a31 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -532,7 +532,7 @@ static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, force_space ? &alloc_state->limit_pool : NULL); - if (ret) { + if (ret > 0) { /* * -EAGAIN means the charge failed, which we treat * like an allocation failure. Therefore, return an From 568a1588b906780dc3e9be56a61217afb4f7800e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:03:02 -0700 Subject: [PATCH 0761/1317] xfs: snapshot scrub stats when rendering them LOLLM complains about concurrency problems in the scrub stats code because xchk_stats_format doesn't synchronize in any way with updates. These stats are only reported through debugfs so I don't think it really matters, but I guess I exist to make bots happy now. Note: We snapshot the entire stats object with a spinlock so that we don't have to worry about users seeing slightly weird numbers (e.g. invocations has incremented but none of the outcomes have been yet) if we race with xchk_stats_merge_one. This isn't a hot path. Cc: stable@vger.kernel.org # v6.6 Fixes: d7a74cad8f4513 ("xfs: track usage statistics of online fsck") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/stats.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index 76f2515188d1..f3f1fbfb6d99 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -99,25 +99,31 @@ xchk_stats_format( int ret = 0; for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) { + struct xchk_scrub_stats fss; + if (!name_map[i]) continue; + spin_lock(&css->css_lock); + memcpy(&fss, css, offsetof(struct xchk_scrub_stats, css_lock)); + spin_unlock(&css->css_lock); + ret = scnprintf(buf, remaining, "%s %u %u %u %u %u %u %u %u %u %llu %u %u %llu\n", name_map[i], - (unsigned int)css->invocations, - (unsigned int)css->clean, - (unsigned int)css->corrupt, - (unsigned int)css->preen, - (unsigned int)css->xfail, - (unsigned int)css->xcorrupt, - (unsigned int)css->incomplete, - (unsigned int)css->warning, - (unsigned int)css->retries, - (unsigned long long)css->checktime_us, - (unsigned int)css->repair_invocations, - (unsigned int)css->repair_success, - (unsigned long long)css->repairtime_us); + (unsigned int)fss.invocations, + (unsigned int)fss.clean, + (unsigned int)fss.corrupt, + (unsigned int)fss.preen, + (unsigned int)fss.xfail, + (unsigned int)fss.xcorrupt, + (unsigned int)fss.incomplete, + (unsigned int)fss.warning, + (unsigned int)fss.retries, + (unsigned long long)fss.checktime_us, + (unsigned int)fss.repair_invocations, + (unsigned int)fss.repair_success, + (unsigned long long)fss.repairtime_us); if (ret <= 0) break; From 0ae61c331ec552ad0c278c5c48a1c4ccb90b4bab Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:03:17 -0700 Subject: [PATCH 0762/1317] xfs: report healthy filesystem events in scrub stats LOLLM also notices that I forgot to expose the "clean bill of health" scrub stats. Fix that. Cc: stable@vger.kernel.org # v6.9 Fixes: a1f3e0cca41036 ("xfs: update health status if we get a clean bill of health") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/stats.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index f3f1fbfb6d99..da0c05ffe5cd 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -84,6 +84,7 @@ static const char *name_map[XFS_SCRUB_TYPE_NR] = { [XFS_SCRUB_TYPE_RGSUPER] = "rgsuper", [XFS_SCRUB_TYPE_RTRMAPBT] = "rtrmapbt", [XFS_SCRUB_TYPE_RTREFCBT] = "rtrefcountbt", + [XFS_SCRUB_TYPE_HEALTHY] = "healthy", }; /* Format the scrub stats into a text buffer, similar to pcp style. */ From d3dc979a49df6d48f8e137034d19d9b35afd07d8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:03:33 -0700 Subject: [PATCH 0763/1317] xfs: report runtime failures in scrub Add a new counter so that we can track the number of runtime failures encountered during scrubs. Signed-off-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/stats.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index da0c05ffe5cd..3339cae4b39d 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -29,6 +29,7 @@ struct xchk_scrub_stats { uint32_t incomplete; uint32_t warning; uint32_t retries; + uint32_t runtime_errors; /* repair stats */ uint32_t repair_invocations; @@ -110,7 +111,7 @@ xchk_stats_format( spin_unlock(&css->css_lock); ret = scnprintf(buf, remaining, - "%s %u %u %u %u %u %u %u %u %u %llu %u %u %llu\n", + "%s %u %u %u %u %u %u %u %u %u %llu %u %u %llu %u\n", name_map[i], (unsigned int)fss.invocations, (unsigned int)fss.clean, @@ -124,7 +125,8 @@ xchk_stats_format( (unsigned long long)fss.checktime_us, (unsigned int)fss.repair_invocations, (unsigned int)fss.repair_success, - (unsigned long long)fss.repairtime_us); + (unsigned long long)fss.repairtime_us, + (unsigned int)fss.runtime_errors); if (ret <= 0) break; @@ -207,13 +209,17 @@ xchk_stats_merge_one( } /* caller applies this same transformation after we return */ - if (error == -EFSCORRUPTED || error == -EFSBADCRC) + if (error == -EFSCORRUPTED || error == -EFSBADCRC) { sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + error = 0; + } css = &cs->cs_stats[sm->sm_type]; spin_lock(&css->css_lock); css->invocations++; - if (!(sm_flags & XFS_SCRUB_OFLAG_UNCLEAN)) + if (error) + css->runtime_errors++; + else if (!(sm_flags & XFS_SCRUB_OFLAG_UNCLEAN)) css->clean++; if (sm_flags & XFS_SCRUB_OFLAG_CORRUPT) css->corrupt++; From 341f03865d0793e7df22c4661e04047c092e5ac2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:03:48 -0700 Subject: [PATCH 0764/1317] xfs: remove redundant function declaration Remove this useless code. Signed-off-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dabtree.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/xfs/scrub/dabtree.h b/fs/xfs/scrub/dabtree.h index de291e3b77dd..d654c125feb4 100644 --- a/fs/xfs/scrub/dabtree.h +++ b/fs/xfs/scrub/dabtree.h @@ -37,8 +37,6 @@ bool xchk_da_process_error(struct xchk_da_btree *ds, int level, int *error); void xchk_da_set_corrupt(struct xchk_da_btree *ds, int level); void xchk_da_set_preen(struct xchk_da_btree *ds, int level); -void xchk_da_set_preen(struct xchk_da_btree *ds, int level); - int xchk_da_btree_hash(struct xchk_da_btree *ds, int level, __be32 *hashp); int xchk_da_btree(struct xfs_scrub *sc, int whichfork, xchk_da_btree_rec_fn scrub_fn, void *private); From 3466dfef0a20f842363958deea55be9f1d26818a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:04:04 -0700 Subject: [PATCH 0765/1317] xfs: snapshot old AGFL before rewriting it LOLLM complains that we can't undo an attempt at fixing the AGFL if anything goes wrong during the rewrite, so take a snapshot of the whole buffer so that we can restore it. Move the xrep_agfl_update_agf call so that we only update the AGF if the AGFL update is 100% successful. While we're at it, fix leaking the used_extents bitmap if the disunion operation fails. Cc: stable@vger.kernel.org # v4.19 Fixes: 0e93d3f43ec7d3 ("xfs: repair the AGFL") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 35 ++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 2104512f1ee1..46c95354ca64 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -668,14 +668,16 @@ xrep_agfl_init_header( struct xfs_scrub *sc, struct xfs_buf *agfl_bp, struct xagb_bitmap *agfl_extents, - xfs_agblock_t flcount) + xfs_agblock_t flcount, + struct xfs_agfl *old_agfl) { struct xrep_agfl_fill af = { .sc = sc, .flcount = flcount, }; struct xfs_mount *mp = sc->mp; - struct xfs_agfl *agfl; + struct xfs_agfl *agfl = XFS_BUF_TO_AGFL(agfl_bp); + const size_t agfl_sz = BBTOB(agfl_bp->b_length); int error; ASSERT(flcount <= xfs_agfl_size(mp)); @@ -684,8 +686,8 @@ xrep_agfl_init_header( * Start rewriting the header by setting the bno[] array to * NULLAGBLOCK, then setting AGFL header fields. */ - agfl = XFS_BUF_TO_AGFL(agfl_bp); - memset(agfl, 0xFF, BBTOB(agfl_bp->b_length)); + memcpy(old_agfl, agfl, agfl_sz); + memset(agfl, 0xFF, agfl_sz); agfl->agfl_magicnum = cpu_to_be32(XFS_AGFL_MAGIC); agfl->agfl_seqno = cpu_to_be32(pag_agno(sc->sa.pag)); uuid_copy(&agfl->agfl_uuid, &mp->m_sb.sb_meta_uuid); @@ -700,13 +702,18 @@ xrep_agfl_init_header( xagb_bitmap_walk(agfl_extents, xrep_agfl_fill, &af); error = xagb_bitmap_disunion(agfl_extents, &af.used_extents); if (error) - return error; + goto err_undo; /* Write new AGFL to disk. */ xfs_trans_buf_set_type(sc->tp, agfl_bp, XFS_BLFT_AGFL_BUF); - xfs_trans_log_buf(sc->tp, agfl_bp, 0, BBTOB(agfl_bp->b_length) - 1); + xfs_trans_log_buf(sc->tp, agfl_bp, 0, agfl_sz - 1); xagb_bitmap_destroy(&af.used_extents); return 0; + +err_undo: + xagb_bitmap_destroy(&af.used_extents); + memcpy(agfl, old_agfl, agfl_sz); + return error; } /* Repair the AGFL. */ @@ -718,6 +725,7 @@ xrep_agfl( struct xfs_mount *mp = sc->mp; struct xfs_buf *agf_bp; struct xfs_buf *agfl_bp; + struct xfs_agfl *old_agfl; xfs_agblock_t flcount; int error; @@ -725,6 +733,10 @@ xrep_agfl( if (!xfs_has_rmapbt(mp)) return -EOPNOTSUPP; + old_agfl = kzalloc(BBTOB(XFS_FSS_TO_BB(mp, 1)), XCHK_GFP_FLAGS); + if (!old_agfl) + return -ENOMEM; + xagb_bitmap_init(&agfl_extents); /* @@ -734,7 +746,7 @@ xrep_agfl( */ error = xfs_alloc_read_agf(sc->sa.pag, sc->tp, 0, &agf_bp); if (error) - return error; + goto err_old_agfl; /* * Make sure we have the AGFL buffer, as scrub might have decided it @@ -745,7 +757,7 @@ xrep_agfl( XFS_AGFL_DADDR(mp)), XFS_FSS_TO_BB(mp, 1), 0, &agfl_bp, NULL); if (error) - return error; + goto err_old_agfl; agfl_bp->b_ops = &xfs_agfl_buf_ops; /* Gather all the extents we're going to put on the new AGFL. */ @@ -762,10 +774,11 @@ xrep_agfl( * we adjust the AGF flcount (which can fail) so avoid updating any * buffers until we know that part works. */ - xrep_agfl_update_agf(sc, agf_bp, flcount); - error = xrep_agfl_init_header(sc, agfl_bp, &agfl_extents, flcount); + error = xrep_agfl_init_header(sc, agfl_bp, &agfl_extents, flcount, + old_agfl); if (error) goto err; + xrep_agfl_update_agf(sc, agf_bp, flcount); /* * Ok, the AGFL should be ready to go now. Roll the transaction to @@ -785,6 +798,8 @@ xrep_agfl( err: xagb_bitmap_destroy(&agfl_extents); +err_old_agfl: + kfree(old_agfl); return error; } From eaf580538eb1be3d162400d04c4b7dc4c627296b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:04:19 -0700 Subject: [PATCH 0766/1317] xfs: bail out on bitmap errors in xrep_agfl_fill LOLLM also points out that the xagb_bitmap_set call in xrep_agfl_fill can fail, but we don't check the result of xagb_bitmap_walk, so we silently drop the error and proceed with inconsistent incore data. That shouldn't be allowed. Cc: stable@vger.kernel.org # v6.6 Fixes: 014ad53732d2ba ("xfs: use per-AG bitmaps to reap unused AG metadata blocks during repair") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 46c95354ca64..a66b611588c4 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -699,7 +699,9 @@ xrep_agfl_init_header( */ xagb_bitmap_init(&af.used_extents); af.agfl_bno = xfs_buf_to_agfl_bno(agfl_bp); - xagb_bitmap_walk(agfl_extents, xrep_agfl_fill, &af); + error = xagb_bitmap_walk(agfl_extents, xrep_agfl_fill, &af); + if (error && error != -ECANCELED) + goto err_undo; error = xagb_bitmap_disunion(agfl_extents, &af.used_extents); if (error) goto err_undo; From ad0033e2dbd3ecc063dfe613060da5cbab9a4970 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:33:07 +0300 Subject: [PATCH 0767/1317] xfs: also flush the RT device cache in xlog_write_iclog The cache flush before writing the CIL start record no only needs to ensure any metadata covered by the overwritten part of the log is on stable storage, but also that any data pointed to by metadata logged is on stable storage, as otherwise log recovery could created allocated blocks that point to stale data. Fortunately the code already handles this right for the data device, but it also needs to flush the RT device for this to work for data on the RT device. Also update the comments to explicitly mention this case. This omission goes back to the first days of cache control in XFS. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log.c | 45 ++++++++++++++++++++++++++++++-------------- fs/xfs/xfs_log_cil.c | 7 ++++--- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c index 2a34611d81f6..f4f81d893e8c 100644 --- a/fs/xfs/xfs_log.c +++ b/fs/xfs/xfs_log.c @@ -1544,6 +1544,35 @@ xlog_bio_end_io( &iclog->ic_end_io_work); } +/* + * When using multiple devices, we also need to flush the data and RT device + * caches first to ensure that all metadata writeback covered by the LSN in + * this iclog is on stable storage. This is slow, but it *must* complete + * before we issue the external log IO. + * + * If the flush fails, we cannot conclude that past metadata writeback from + * the log succeeded. Repeating the flush is not possible, hence we must + * shut down with log IO error to avoid shutdown re-entering this path and + * erroring out again. + */ +static int +xlog_flush_data_caches( + struct xlog *log) +{ + struct xfs_mount *mp = log->l_mp; + + if (log->l_targ != mp->m_ddev_targp) { + if (blkdev_issue_flush(mp->m_ddev_targp->bt_bdev)) + return -EIO; + } + if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp) { + if (blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev)) + return -EIO; + } + + return 0; +} + STATIC void xlog_write_iclog( struct xlog *log, @@ -1588,21 +1617,9 @@ xlog_write_iclog( iclog->ic_bio.bi_private = iclog; if (iclog->ic_flags & XLOG_ICL_NEED_FLUSH) { - iclog->ic_bio.bi_opf |= REQ_PREFLUSH; - /* - * For external log devices, we also need to flush the data - * device cache first to ensure all metadata writeback covered - * by the LSN in this iclog is on stable storage. This is slow, - * but it *must* complete before we issue the external log IO. - * - * If the flush fails, we cannot conclude that past metadata - * writeback from the log succeeded. Repeating the flush is - * not possible, hence we must shut down with log IO error to - * avoid shutdown re-entering this path and erroring out again. - */ - if (log->l_targ != log->l_mp->m_ddev_targp && - blkdev_issue_flush(log->l_mp->m_ddev_targp->bt_bdev)) + if (xlog_flush_data_caches(log)) goto shutdown; + iclog->ic_bio.bi_opf |= REQ_PREFLUSH; } if (iclog->ic_flags & XLOG_ICL_NEED_FUA) iclog->ic_bio.bi_opf |= REQ_FUA; diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index 166531018ce4..f9e07a32f60f 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1055,9 +1055,10 @@ xlog_cil_set_ctx_write_state( spin_unlock(&cil->xc_push_lock); /* - * Make sure the metadata we are about to overwrite in the log - * has been flushed to stable storage before this iclog is - * issued. + * Flush the write cache before writing the start record so that + * the metadata we are about to overwrite in the log and the + * data that new allocations in this context refer to are + * persisted to stable storage before this iclog is written. */ spin_lock(&cil->xc_log->l_icloglock); iclog->ic_flags |= XLOG_ICL_NEED_FLUSH; From c84455c683eb0b0397b0f5c5f5ce5cd82572a23f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:33:08 +0300 Subject: [PATCH 0768/1317] xfs: don't continue on error in xfs_fsync As soon as we get an error from cache flushing or log forcing, there is no point in continuing as the data integrity is already impacted. Return the error instead of continuing to do more work. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 426a67b813a7..0d31fea67a2c 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -130,8 +130,8 @@ xfs_file_fsync( { struct xfs_inode *ip = XFS_I(file->f_mapping->host); struct xfs_mount *mp = ip->i_mount; - int error, err2; int log_flushed = 0; + int error; trace_xfs_file_fsync(ip); @@ -154,15 +154,17 @@ xfs_file_fsync( error = blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev); else if (mp->m_logdev_targp != mp->m_ddev_targp) error = blkdev_issue_flush(mp->m_ddev_targp->bt_bdev); + if (error) + return error; /* * If the inode has a inode log item attached, it may need the journal * flushed to persist any changes the log item might be tracking. */ if (ip->i_itemp) { - err2 = xfs_fsync_flush_log(ip, datasync, &log_flushed); - if (err2 && !error) - error = err2; + error = xfs_fsync_flush_log(ip, datasync, &log_flushed); + if (error) + return error; } /* @@ -178,14 +180,11 @@ xfs_file_fsync( if (!log_flushed) { struct xfs_buftarg *file_targp = xfs_inode_buftarg(ip); - if (mp->m_logdev_targp == file_targp) { - err2 = blkdev_issue_flush(file_targp->bt_bdev); - if (err2 && !error) - error = err2; - } + if (mp->m_logdev_targp == file_targp) + return blkdev_issue_flush(file_targp->bt_bdev); } - return error; + return 0; } static int From 761e015e5a54851043c3b5bb7cfb6f539b01a35e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:33:09 +0300 Subject: [PATCH 0769/1317] xfs: avoid extra cache flushes for multi-device file systems in xfs_fsync When xlog_force_lsn sets log_flushed, it has just called xlog_force_iclog through xlog_force_and_check_iclog, which sets XLOG_ICL_NEED_FLUSH before writing out the head iclog. This means that we already flushed the log, data, and (with the recent fix) RT devices before writing out the iclog start record and no extra cache flushed is required. This optimizes the external log case, and fixes a performance regression due to double RT dev flushes with "xfs: also flush the RT device cache in xlog_write_iclog". The explicit flush of the data that the device resides on when no iclog was written out is still required. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 0d31fea67a2c..d8202da15aca 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -129,7 +129,6 @@ xfs_file_fsync( int datasync) { struct xfs_inode *ip = XFS_I(file->f_mapping->host); - struct xfs_mount *mp = ip->i_mount; int log_flushed = 0; int error; @@ -139,27 +138,17 @@ xfs_file_fsync( if (error) return error; - if (xfs_is_shutdown(mp)) + if (xfs_is_shutdown(ip->i_mount)) return -EIO; xfs_iflags_clear(ip, XFS_ITRUNCATED); /* - * If we have an RT and/or log subvolume we need to make sure to flush - * the write cache the device used for file data first. This is to - * ensure newly written file data make it to disk before logging the new - * inode size in case of an extending write. - */ - if (XFS_IS_REALTIME_INODE(ip) && mp->m_rtdev_targp != mp->m_ddev_targp) - error = blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev); - else if (mp->m_logdev_targp != mp->m_ddev_targp) - error = blkdev_issue_flush(mp->m_ddev_targp->bt_bdev); - if (error) - return error; - - /* - * If the inode has a inode log item attached, it may need the journal - * flushed to persist any changes the log item might be tracking. + * If the inode has a log item attached, we must force the log up to the + * last LSN in which the inode was modified to ensure all metadata is + * persisted. The log force will flush the caches for all devices + * before writing the log records unless it is a no-op because there are + * no modifications to this inode that need to be pushed out. */ if (ip->i_itemp) { error = xfs_fsync_flush_log(ip, datasync, &log_flushed); @@ -173,17 +162,10 @@ xfs_file_fsync( * when no metadata needed to be committed. * * Use the inode's actual file data target rather than assuming the - * main data device. Realtime inodes with a separate realtime device - * are flushed before the log force, so this fallback only applies - * when the file data target is the same as the log target. + * main data device. */ - if (!log_flushed) { - struct xfs_buftarg *file_targp = xfs_inode_buftarg(ip); - - if (mp->m_logdev_targp == file_targp) - return blkdev_issue_flush(file_targp->bt_bdev); - } - + if (!log_flushed) + return blkdev_issue_flush(xfs_inode_buftarg(ip)->bt_bdev); return 0; } From 50ba24ccb9a94f61c707209442ca23d98c815052 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Mon, 7 Sep 2026 10:27:39 +0300 Subject: [PATCH 0770/1317] xfs: set IOMAP_F_INTEGRITY for zoned writes on integrity devices xfs_iomap_set_anon_write does not set IOMAP_F_INTEGRITY based on bdev_has_integrity_csum(), so file system PI generation is silently skipped for zoned writes on integrity-enabled devices, and left to the block layer PI generation. Fixes: 6bbb4d96f797 ("xfs: support T10 protection information") Signed-off-by: Anuj Gupta [hch: ported to the recently introduced xfs_iomap_set_anon_write()] Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_iomap.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h index c906c62d46f3..f2520a9b3a13 100644 --- a/fs/xfs/xfs_iomap.h +++ b/fs/xfs/xfs_iomap.h @@ -41,6 +41,8 @@ xfs_iomap_set_anon_write( iomap->offset = offset; iomap->length = length; iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; + if (bdev_has_integrity_csum(iomap->bdev)) + iomap->flags |= IOMAP_F_INTEGRITY; } static inline xfs_filblks_t From e240919ca727776f16a468d3d90686dc82cfe9cb Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sun, 6 Sep 2026 20:16:29 +0530 Subject: [PATCH 0771/1317] xfs: take hm->lock in xfs_ioc_health_monitor() before insert __xfs_healthmon_insert() asserts that hm->lock is held (lockdep_assert_held), but xfs_ioc_health_monitor() called it right after allocating hm, before ever taking the lock, triggering a lockdep warning. Take hm->lock around the call. Fixes: b3a289a2a9397 ("xfs: create event queuing, formatting, and discovery infrastructure") Reported-by: syzbot+ccdf3469f5f653bff7ac@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ccdf3469f5f653bff7ac Signed-off-by: Deepanshu Kartikey Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index b57fa033cec4..c3749675ef19 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -1223,7 +1223,9 @@ xfs_ioc_health_monitor( } running_event->type = XFS_HEALTHMON_RUNNING; running_event->domain = XFS_HEALTHMON_MOUNT; + mutex_lock(&hm->lock); __xfs_healthmon_insert(hm, INSERT_HEAD, running_event); + mutex_unlock(&hm->lock); /* * Preallocate the unmount event so that we can't fail to notify the From 4f4b743c2d2bbc336cb164d9d3d2ed6956ad8437 Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Fri, 4 Sep 2026 14:21:13 +0530 Subject: [PATCH 0772/1317] octeontx2-af: fix PF/CGX debugfs PCI bus lookup rvu_dbg_rvu_pf_cgx_map_display() locates each RVU PF PCI device via pci_get_domain_bus_and_slot() when printing the PF-to-CGX map. It assumed PF0 always sits on PCI bus 1 and derived other PF bus numbers as pf + 1, but the AF device can be enumerated on a different bus. Use rvu->pdev->bus->number as the base bus instead, so each PF lookup uses pf + start on systems where RVU functions are on contiguous buses but do not start at bus 1. Fixes: e2fb373038654 ("octeontx2-af: Display CGX, NIX and PF map in debugfs.") Signed-off-by: Subbaraya Sundeep Signed-off-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904085114.3385530-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/marvell/octeontx2/af/rvu_debugfs.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c index 22ee99676879..904374baae6f 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c @@ -829,19 +829,25 @@ static int rvu_dbg_rvu_pf_cgx_map_display(struct seq_file *filp, void *unused) int pf, domain, blkid; u8 cgx_id, lmac_id; u16 pcifunc; + u8 start; - domain = 2; + domain = pci_domain_nr(rvu->pdev->bus); mac_ops = get_mac_ops(rvu_first_cgx_pdata(rvu)); /* There can be no CGX devices at all */ if (!mac_ops) return 0; seq_printf(filp, "PCI dev\t\tRVU PF Func\tNIX block\t%s\tLMAC\tCHAN\n", mac_ops->name); + + /* All the PF devices are on contiguous PCI bus numbers, but the PF0(AF) + * may not start from 1 always. Hence get domain and bus from PCI device. + */ + start = rvu->pdev->bus->number; for (pf = 0; pf < rvu->hw->total_pfs; pf++) { if (!is_pf_cgxmapped(rvu, pf)) continue; - pdev = pci_get_domain_bus_and_slot(domain, pf + 1, 0); + pdev = pci_get_domain_bus_and_slot(domain, pf + start, 0); if (!pdev) continue; From 36a45facedd5c8e73bfb2403f8b0dbff05124c9c Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 3 Sep 2026 11:28:38 +0900 Subject: [PATCH 0773/1317] net: phy: dp83867: handle the active-high LED polarity mode Commit a274465cc3be ("net: phy: support 'active-high' property for PHY LEDs") added PHY_LED_ACTIVE_HIGH and made of_phy_led() set the matching bit in the modes mask when a LED node carries the 'active-high' property. dp83867 was not part of that series. dp83867_led_polarity_set() only recognizes PHY_LED_ACTIVE_LOW, so PHY_LED_ACTIVE_HIGH falls through to the default case and returns -EINVAL. of_phy_led() propagates the error, of_phy_leds() drops the LEDs registered so far and passes it on, and phy_probe() fails. A device tree marking a DP83867 LED as 'active-high', which leds/common.yaml allows and ethernet-phy.yaml references for led@N nodes, thus stops the PHY from probing. Active high is what the function programs when no polarity mode is requested at all, so the initial value of polarity already satisfies the request and only the case label is missing. The same series updated mxl-gpy in commit eb89c79c1b8f ("net: phy: mxl-gpy: correctly describe LED polarity") and aquantia in commit 9d55e68b19f2 ("net: phy: aquantia: correctly describe LED polarity override"). Fixes: a274465cc3be ("net: phy: support 'active-high' property for PHY LEDs") Signed-off-by: Donggeun Yoo Link: https://patch.msgid.link/20260903022839.4006614-1-donggeunyoo.kernel@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83867.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 88255e92b4cd..61a941aa02d9 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -1150,6 +1150,9 @@ static int dp83867_led_polarity_set(struct phy_device *phydev, int index, case PHY_LED_ACTIVE_LOW: polarity = 0; break; + case PHY_LED_ACTIVE_HIGH: + polarity = DP83867_LED_POLARITY(index); + break; default: return -EINVAL; } From 4c46beb807efcc93f5899ebe1f5958248eb296c6 Mon Sep 17 00:00:00 2001 From: Long Li Date: Fri, 4 Sep 2026 13:26:40 -0700 Subject: [PATCH 0774/1317] net: mana: restore the XDP program pointer when pre-allocation fails mana_xdp_set() publishes the new program into apc->bpf_prog before it allocates anything, because mana_pre_alloc_rxbufs() sizes the buffers from it via mana_get_rxbuf_cfg(). When that allocation fails the function returns the error directly, skipping the err_dealloc_rxbuffs label which is the only place that restores the previous pointer. The attach is reported as failed, so the BPF core drops the reference it held for the caller and the program can be freed, while apc->bpf_prog still points at it. The next consumer of mana_xdp_get() - typically mana_chn_setxdp() from mana_alloc_queues() on the following ifup, or after a TX timeout reset - then calls bpf_prog_add() on freed memory. This is reachable from an ordinary "ip link set dev ethX xdp obj ..." whenever the per-queue RX buffer pre-allocation cannot be satisfied. Restore the previous program on that error path. Fixes: 730ff06d3f5c ("net: mana: Use page pool fragments for RX buffers instead of full pages to improve memory efficiency.") Signed-off-by: Long Li Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904202640.3900685-1-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_bpf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c index 53308e139cbe..5c9961ee9747 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c +++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c @@ -208,6 +208,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, if (err) { NL_SET_ERR_MSG_MOD(extack, "XDP: Insufficient memory for tx/rx re-config"); + apc->bpf_prog = old_prog; return err; } From 9ff797e516dbc1ecb73701ec4c24055712d44411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 8 Sep 2026 08:55:20 +0000 Subject: [PATCH 0775/1317] RDMA/siw: Bound fragmented header copies by the remaining length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit siw_get_hdr() can receive an extended DDP/RDMAP header across more than one TCP callback. The first callback may receive most of the header, while the next one still limits the copy to hdrlen - MIN_DDP_HDR instead of the number of missing bytes. This makes the destination move past the end of the header and overwrite the receive state, including fpdu_part_rcvd. A later callback can then use a negative fpdu_part_rcvd value as a copy offset, which creates an OOB write. Use the number of header bytes already received when calculating the next copy length. Fixes: 754209850df8 ("RDMA/siw: Always consume all skbuf data in sk_data_ready() upcall.") Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260908085520.1746329-1-Jeremy.Jean@oss.cyber.gouv.fr Assisted-by: Codex:gpt-6 Acked-by: Bernard Metzler Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/siw/siw_qp_rx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/sw/siw/siw_qp_rx.c b/drivers/infiniband/sw/siw/siw_qp_rx.c index b566d163c5aa..e5b641c8d694 100644 --- a/drivers/infiniband/sw/siw/siw_qp_rx.c +++ b/drivers/infiniband/sw/siw/siw_qp_rx.c @@ -1079,7 +1079,7 @@ static int siw_get_hdr(struct siw_rx_stream *srx) if (iwarp_pktinfo[opcode].hdr_len > sizeof(struct iwarp_ctrl_tagged)) { int hdrlen = iwarp_pktinfo[opcode].hdr_len; - bytes = min_t(int, hdrlen - MIN_DDP_HDR, srx->skb_new); + bytes = min_t(int, hdrlen - srx->fpdu_part_rcvd, srx->skb_new); skb_copy_bits(skb, srx->skb_offset, (char *)c_hdr + srx->fpdu_part_rcvd, bytes); From 2ac09b5353fe6858411fdc8c6efa60d832e20f13 Mon Sep 17 00:00:00 2001 From: Greg Marsden Date: Sat, 5 Sep 2026 10:00:41 -0700 Subject: [PATCH 0776/1317] net/rds: fix tcp stream corruption with large pages rds_message_map_pages() assigns PAGE_SIZE bytes to every scatterlist entry, even when total_len ends in a partial page. The RDS congestion map is defined as 8192 bytes, so on systems with PAGE_SIZE greater than 8192 the scatterlist maps bytes beyond the end of the congestion map. RDS-TCP transmits the SG contents according to those lengths, so the extra bytes become part of the TCP RDS stream and are interpreted as subsequent RDS message headers, corrupting the stream. Limit the final scatterlist mapping to the number of bytes remaining. This has no effect on systems with a 4K page size and allows RDS-TCP to be used on systems with 16K and larger page sizes. The RDS selftest, which previously hung on 16K pages, now passes. Fixes: 7875e18e0996 ("RDS: Message parsing") Signed-off-by: Greg Marsden Reviewed-by: Allison Henderson Link: https://patch.msgid.link/apxJjxvStibPI0AS@oracle.com Signed-off-by: Jakub Kicinski --- net/rds/message.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/rds/message.c b/net/rds/message.c index f25f2592586f..47d5e9ab9b10 100644 --- a/net/rds/message.c +++ b/net/rds/message.c @@ -431,7 +431,9 @@ struct rds_message *rds_message_map_pages(unsigned long *page_addrs, unsigned in for (i = 0; i < rm->data.op_nents; ++i) { sg_set_page(&rm->data.op_sg[i], virt_to_page((void *)page_addrs[i]), - PAGE_SIZE, 0); + i == rm->data.op_nents - 1 + ? total_len - (i * PAGE_SIZE) + : PAGE_SIZE, 0); } return rm; From 5e38d732ec67a5b1f9a56e6c73add480c4b6030a Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 7 Sep 2026 23:46:45 +0200 Subject: [PATCH 0777/1317] net: stmmac: fix TX descriptor availability check for TSO traffic stmmac_tso_xmit() estimates the number of free TX descriptors required by a TSO skb as: (skb->len - proto_hdr_len) / TSO_MAX_BUFF_SIZE + 1 which assumes the payload is split into TSO_MAX_BUFF_SIZE chunks. This underestimates the descriptors actually consumed by stmmac_tso_allocator(), since each fragment is mapped individually and so it needs at least one descriptor regardless of its size. Moreover, one descriptor is used for the L2/L3/L4 headers and, when the MSS changes, one more is consumed for the MSS context descriptor. For a highly fragmented TSO skb the check can therefore pass even when the ring has too few free slots. stmmac_tso_allocator() then writes past the available descriptors, overwriting descriptors still owned by the DMA engine, corrupting the TX ring. Add stmmac_tso_get_num_desc() to compute the exact number of descriptors needed for the header, the linear payload and each fragment, plus the MSS context descriptor when required, and use it in the availability check. Fixes: f748be531d70 ("stmmac: support new GMAC4") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260907-stmmac-fix-tso-nfrags-check-v1-1-328459906cdb@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 5fe7e95fdd34..62c3441911e7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4454,6 +4454,26 @@ static bool stmmac_tso_valid_packet(struct sk_buff *skb) header_len + gso_size < 16383; } +static int stmmac_tso_get_num_desc(struct stmmac_tx_queue *tx_q, + struct sk_buff *skb, u32 pay_len) +{ + int i, ndesc = 1; + + /* head payload */ + ndesc += DIV_ROUND_UP(pay_len, TSO_MAX_BUFF_SIZE); + /* frag payload */ + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + + ndesc += DIV_ROUND_UP(skb_frag_size(frag), + TSO_MAX_BUFF_SIZE); + } + /* MSS update requires a new descriptor */ + ndesc += !!(skb_shinfo(skb)->gso_size != tx_q->mss); + + return ndesc; +} + /** * stmmac_tso_xmit - Tx entry point of the driver for oversized frames (TSO) * @skb : the socket buffer @@ -4497,10 +4517,10 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) struct stmmac_priv *priv = netdev_priv(dev); unsigned int first_entry, entry, tx_packets; struct stmmac_txq_stats *txq_stats; + int i, first_tx, nfrags, ndesc; struct stmmac_tx_queue *tx_q; bool set_ic, is_last_segment; u32 pay_len, mss, queue; - int i, first_tx, nfrags; u8 proto_hdr_len, hdr; dma_addr_t des; @@ -4513,14 +4533,15 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) /* Compute header lengths */ proto_hdr_len = stmmac_tso_header_size(skb); + pay_len = skb_headlen(skb) - proto_hdr_len; /* no frags */ + if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) hdr = sizeof(struct udphdr); else hdr = tcp_hdrlen(skb); - /* Desc availability based on threshold should be enough safe */ - if (unlikely(stmmac_tx_avail(priv, queue) < - (((skb->len - proto_hdr_len) / TSO_MAX_BUFF_SIZE + 1)))) { + ndesc = stmmac_tso_get_num_desc(tx_q, skb, pay_len); + if (unlikely(stmmac_tx_avail(priv, queue) < ndesc)) { if (!netif_tx_queue_stopped(netdev_get_tx_queue(dev, queue))) { netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, queue)); @@ -4532,8 +4553,6 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_BUSY; } - pay_len = skb_headlen(skb) - proto_hdr_len; /* no frags */ - mss = skb_shinfo(skb)->gso_size; /* set new MSS value if needed */ From 19b4ed644d68098cc62ab612727f40d30f43476c Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 8 Sep 2026 07:42:56 +0000 Subject: [PATCH 0778/1317] ipv6: fix fib6 walker UAF on seq stop ipv6_route_iter_active() treats a walker in FWS_U at the table root as already unlinked. fib6_del_route() can move a still-linked walker into that same state when the current leaf is the last route at the root, so ipv6_route_native_seq_stop() skips fib6_walker_unlink(). The seq private object can then be freed while it remains on net->ipv6.fib6_walkers. A later route deletion walks the dangling list and uses the freed walker. Use the list head as membership state and reinitialize it when unlinking. Keep the existing w->node check so a never-started iterator with a zeroed private object is not treated as linked. The same stop helper is used by /proc/net/ipv6_route and by the BPF ipv6_route iterator. The BPF show path only widens the race. Fixes: 8d2ca1d7b5c3 ("ipv6: avoid high order memory allocations for /proc/net/ipv6_route") Cc: stable@vger.kernel.org Reported-by: Vega Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/89699735763f6c297584d7c2ff106239cc1e8ce0.1788837093.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_fib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 3e382ba1573e..9ea75703b38d 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -85,7 +85,7 @@ static void fib6_walker_link(struct net *net, struct fib6_walker *w) static void fib6_walker_unlink(struct net *net, struct fib6_walker *w) { write_lock_bh(&net->ipv6.fib6_walker_lock); - list_del(&w->lh); + list_del_init(&w->lh); write_unlock_bh(&net->ipv6.fib6_walker_lock); } @@ -2760,7 +2760,7 @@ static void *ipv6_route_seq_start(struct seq_file *seq, loff_t *pos) static bool ipv6_route_iter_active(struct ipv6_route_iter *iter) { struct fib6_walker *w = &iter->w; - return w->node && !(w->state == FWS_U && w->node == w->root); + return w->node && !list_empty(&w->lh); } static void ipv6_route_native_seq_stop(struct seq_file *seq, void *v) From a2dc179481d18f6df7274522571b64dd50f31e81 Mon Sep 17 00:00:00 2001 From: MD Danish Anwar Date: Tue, 8 Sep 2026 14:38:56 +0530 Subject: [PATCH 0779/1317] net: hsr: enable promiscuous mode on interlink port with fwd offload hsr_portdev_setup() skips promiscuous mode on non-master ports when hsr->fwd_offloaded is set. fwd_offloaded is derived only from the ring slaves' NETIF_F_HW_HSR_FWD bit, so this also skips it for the interlink port, which never gets forwarding offload. Without promiscuous mode, the interlink NIC drops unicast frames addressed to hsr_dev's MAC (e.g. SAN traffic to the RedBox), breaking RedBox whenever the ring is HW-offloaded. Fixes: 5055cccfc2d1 ("net: hsr: Provide RedBox support (HSR-SAN)") Signed-off-by: MD Danish Anwar Reviewed-by: Simon Horman Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260908090856.2876114-1-danishanwar@ti.com Signed-off-by: Jakub Kicinski --- net/hsr/hsr_slave.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c index 01c73b4b50dd..a546f70f9cc8 100644 --- a/net/hsr/hsr_slave.c +++ b/net/hsr/hsr_slave.c @@ -149,9 +149,12 @@ static int hsr_portdev_setup(struct hsr_priv *hsr, struct net_device *dev, int res; /* Don't use promiscuous mode for offload since L2 frame forward - * happens at the offloaded hardware. + * happens at the offloaded hardware. The interlink port never + * gets forwarding offload (RedBox forwarding to/from it is done + * by this driver), so it still needs promiscuous mode to receive + * frames addressed to hsr_dev's MAC rather than its own. */ - if (!port->hsr->fwd_offloaded) { + if (!port->hsr->fwd_offloaded || port->type == HSR_PT_INTERLINK) { res = dev_set_promiscuity(dev, 1); if (res) return res; @@ -176,7 +179,7 @@ static int hsr_portdev_setup(struct hsr_priv *hsr, struct net_device *dev, fail_rx_handler: netdev_upper_dev_unlink(dev, hsr_dev); fail_upper_dev_link: - if (!port->hsr->fwd_offloaded) + if (!port->hsr->fwd_offloaded || port->type == HSR_PT_INTERLINK) dev_set_promiscuity(dev, -1); return res; @@ -240,7 +243,7 @@ void hsr_del_port(struct hsr_port *port) netdev_update_features(master->dev); dev_set_mtu(master->dev, hsr_get_max_mtu(hsr)); netdev_rx_handler_unregister(port->dev); - if (!port->hsr->fwd_offloaded) + if (!port->hsr->fwd_offloaded || port->type == HSR_PT_INTERLINK) dev_set_promiscuity(port->dev, -1); if (port->type == HSR_PT_SLAVE_A || port->type == HSR_PT_SLAVE_B) vlan_vids_del_by_dev(port->dev, master->dev); From e1406330d70e56dd44fa6fbafc86e77e5c80c122 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 8 Sep 2026 18:39:24 +0800 Subject: [PATCH 0780/1317] net: macb: initialize PTP state before registering clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gem_ptp_init() registers the PTP clock before initializing bp->tsu_clk_lock and the TSU hardware. Since ptp_clock_register() publishes the PTP character device, userspace may invoke PTP callbacks before the lock and hardware are ready. In addition, gem_ptp_init() is called from both the interface open and resume paths. Reinitializing tsu_clk_lock there can reset the lock while timestamp processing is using it. This race is theoretical and has not been observed in practice. Initialize tsu_clk_lock once during probe and initialize the TSU before registering the PTP clock. Fixes: ab91f0a9b5f4 ("net: macb: Add hardware PTP support") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/netdev/20260904030439.3994047-1-runyu.xiao@seu.edu.cn/ Reviewed-by: Théo Lebrun Reviewed-by: Vadim Fedorenko Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260908103924.607033-1-runyu.xiao@seu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 1 + drivers/net/ethernet/cadence/macb_ptp.c | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 77dec2d6e3fb..4cb5d7088d43 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -5883,6 +5883,7 @@ static int macb_probe(struct platform_device *pdev) } spin_lock_init(&bp->lock); spin_lock_init(&bp->stats_lock); + spin_lock_init(&bp->tsu_clk_lock); /* setup capabilities */ macb_configure_caps(bp, macb_config); diff --git a/drivers/net/ethernet/cadence/macb_ptp.c b/drivers/net/ethernet/cadence/macb_ptp.c index e5195d7dac1d..6d9166389988 100644 --- a/drivers/net/ethernet/cadence/macb_ptp.c +++ b/drivers/net/ethernet/cadence/macb_ptp.c @@ -334,6 +334,7 @@ void gem_ptp_init(struct net_device *netdev) bp->tsu_rate = bp->ptp_info->get_tsu_rate(bp); bp->ptp_clock_info.max_adj = bp->ptp_info->get_ptp_max_adj(); gem_ptp_init_timer(bp); + gem_ptp_init_tsu(bp); bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &netdev->dev); if (IS_ERR(bp->ptp_clock)) { pr_err("ptp clock register failed: %ld\n", @@ -345,10 +346,6 @@ void gem_ptp_init(struct net_device *netdev) return; } - spin_lock_init(&bp->tsu_clk_lock); - - gem_ptp_init_tsu(bp); - dev_info(&bp->pdev->dev, "%s ptp clock registered.\n", GEM_PTP_TIMER_NAME); } From 6ca81bbc31cdc964e4b74d17b86215d4a810a56f Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Tue, 8 Sep 2026 19:59:58 +0900 Subject: [PATCH 0781/1317] net: phy: dp83td510: handle the active-high LED polarity mode dp83td510_led_polarity_set() only recognizes PHY_LED_ACTIVE_LOW, so PHY_LED_ACTIVE_HIGH falls through to the default case and returns -EINVAL. of_phy_led() propagates the error, of_phy_leds() drops the LEDs registered so far and passes it on, and phy_probe() returns it. A device tree marking a DP83TD510 LED as 'active-high', which leds/common.yaml allows and ethernet-phy.yaml references for led@N nodes, thus leaves the mdio device unbound, so phy_attach_direct() falls back to the genphy driver, which cannot drive this 10BASE-T1L single-mode PHY, so the interface has no usable link. The callback initializes polarity to DP83TD510E_LED_POLARITY(index), which is the active-high setting, so the request is already satisfied and only the case label is missing. Cc: stable@vger.kernel.org Fixes: 5b281fe7e396 ("net: phy: dp83td510: introduce LED framework support") Signed-off-by: Donggeun Yoo Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260908105959.70453-3-donggeunyoo.kernel@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83td510.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/phy/dp83td510.c b/drivers/net/phy/dp83td510.c index d75dae6071ad..9e9a41bf6457 100644 --- a/drivers/net/phy/dp83td510.c +++ b/drivers/net/phy/dp83td510.c @@ -439,6 +439,9 @@ static int dp83td510_led_polarity_set(struct phy_device *phydev, int index, case PHY_LED_ACTIVE_LOW: polarity = 0; break; + case PHY_LED_ACTIVE_HIGH: + polarity = DP83TD510E_LED_POLARITY(index); + break; default: return -EINVAL; } From b7ee18725f2292ab554aa96a101ae42d45f008bd Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 8 Sep 2026 11:58:39 +0000 Subject: [PATCH 0782/1317] ipmr: account multicast table and route memory A netadmin in a user+net namespace can create many IPv4 and IPv6 multicast routing tables with MRT_TABLE and MRT6_TABLE. Each unseen id allocates an mr_table via the shared mr_table_alloc(), links it into the per-net list, and leaves it until netns teardown. Those objects were not charged to memcg, so the host unreclaimable slab grows with the table count. Account mr_table allocations with GFP_KERNEL_ACCOUNT and mark the IPv4/IPv6 MFC caches SLAB_ACCOUNT. This matches the established handling of IP addresses, routes and alternate interface names. Unresolved MFC entries are still allocated from softIRQ with GFP_ATOMIC and are not charged. They expire after 10 seconds and are bounded by the socket receive queue; see commit 0079ad8e8dc3 ("ipmr: remove hard code cache_resolve_queue_len limit"). Fixes: f0ad0860d01e ("ipv4: ipmr: support multiple tables") Fixes: d1db275dd3f6 ("ipv6: ip6mr: support multiple tables") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zihan Xi Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/050b58f7fc6b45da0fb12768ebb62d18fa46133d.1788784801.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/ipmr.c | 3 ++- net/ipv4/ipmr_base.c | 2 +- net/ipv6/ip6mr.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index e5f2b1c6150d..b9c544d48c45 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -3376,7 +3376,8 @@ int __init ip_mr_init(void) { int err; - mrt_cachep = KMEM_CACHE(mfc_cache, SLAB_HWCACHE_ALIGN | SLAB_PANIC); + mrt_cachep = KMEM_CACHE(mfc_cache, + SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT); err = register_pernet_subsys(&ipmr_net_ops); if (err) diff --git a/net/ipv4/ipmr_base.c b/net/ipv4/ipmr_base.c index 867b24beded1..a0ec6d19a237 100644 --- a/net/ipv4/ipmr_base.c +++ b/net/ipv4/ipmr_base.c @@ -52,7 +52,7 @@ mr_table_alloc(struct net *net, u32 id, struct mr_table *mrt; int err; - mrt = kzalloc_obj(*mrt); + mrt = kzalloc_obj(*mrt, GFP_KERNEL_ACCOUNT); if (!mrt) return ERR_PTR(-ENOMEM); mrt->id = id; diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 3f2ed9b77deb..9d8116b5edb1 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -1427,7 +1427,7 @@ int __init ip6_mr_init(void) { int err; - mrt_cachep = KMEM_CACHE(mfc6_cache, SLAB_HWCACHE_ALIGN); + mrt_cachep = KMEM_CACHE(mfc6_cache, SLAB_HWCACHE_ALIGN | SLAB_ACCOUNT); if (!mrt_cachep) return -ENOMEM; From e184a4a6f423550a25adce867036cdb1ff471745 Mon Sep 17 00:00:00 2001 From: Eelco Chaudron Date: Tue, 8 Sep 2026 16:15:17 +0200 Subject: [PATCH 0783/1317] openvswitch: fix wrong flag value in get_ipv6_ext_hdrs() The ESP and AH cases in get_ipv6_ext_hdrs() used IPPROTO_FRAGMENT instead of OFPIEH12_FRAG when checking for out-of-order extension headers, causing the fragment header to not be recognised as a valid predecessor. The original code used IPPROTO_FRAGMENT (44) as a bitmask constant where OFPIEH12_FRAG (1 << 4 = 16) was intended. IPPROTO_FRAGMENT encodes bits 2, 3 and 5 (OFPIEH12_AUTH | OFPIEH12_DEST | OFPIEH12_ROUTER), but not bit 4 (OFPIEH12_FRAG). This caused incorrect OFPIEH12_UNSEQ verdicts in both the ESP and AH arms: the ESP arm failed to whitelist OFPIEH12_FRAG, while the AH arm accidentally whitelisted OFPIEH12_AUTH. With the fix, a packet with two AH headers now also gets OFPIEH12_UNSEQ in addition to OFPIEH12_UNREP, matching the ESP arm which already sets UNSEQ on a repeat, which is the intended behavior. Fixes: 28a3f0601727 ("net: openvswitch: IPv6: Add IPv6 extension header support") Reported-by: Paolo Abeni Reviewed-by: Aaron Conole Reviewed-by: Ilya Maximets Signed-off-by: Eelco Chaudron Link: https://patch.msgid.link/1b1582eb07550d71f3cbe210e5cb31eeb8d0ad86.1788876917.git.echaudro@redhat.com Signed-off-by: Jakub Kicinski --- net/openvswitch/flow.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c index 46c1d66aad8c..868d9fdf3afd 100644 --- a/net/openvswitch/flow.c +++ b/net/openvswitch/flow.c @@ -288,7 +288,7 @@ static void get_ipv6_ext_hdrs(struct sk_buff *skb, struct ipv6hdr *nh, if (*ext_hdrs & OFPIEH12_ESP) *ext_hdrs |= OFPIEH12_UNREP; if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST | - OFPIEH12_ROUTER | IPPROTO_FRAGMENT | + OFPIEH12_ROUTER | OFPIEH12_FRAG | OFPIEH12_AUTH | OFPIEH12_UNREP)) || dest_options_header_count >= 2) { *ext_hdrs |= OFPIEH12_UNSEQ; @@ -301,7 +301,7 @@ static void get_ipv6_ext_hdrs(struct sk_buff *skb, struct ipv6hdr *nh, *ext_hdrs |= OFPIEH12_UNREP; if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST | OFPIEH12_ROUTER | - IPPROTO_FRAGMENT | OFPIEH12_UNREP)) || + OFPIEH12_FRAG | OFPIEH12_UNREP)) || dest_options_header_count >= 2) { *ext_hdrs |= OFPIEH12_UNSEQ; } From e10f2b7e28be3e1ce42a4be8fa9b0684d1d354ac Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 10 Sep 2026 16:41:50 +0200 Subject: [PATCH 0784/1317] ALSA: hdspm: Add a new PCI device ID (1d18:3fc6) for RME HDSPe AIO PCI express audio The RME HDSPe AIO PCI express audio card has a new PCI vendor ID (1d18) while keeping the same device ID (3fc6). The card seems working fine by just adding the new ID. While we're at it, use the standard macro to cleann up the existing PCI ID entry, too. Reported-by: AtmanActive Closes: https://lore.kernel.org/178674392532.7.10140952469564861620.1550442282@slmail.me Tested-by: AtmanActive Link: https://patch.msgid.link/20260910144204.973359-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/pci/rme9652/hdspm.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index d8bbedbc8ff6..95fd82bb1c6d 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -1071,14 +1071,8 @@ struct hdspm { static const struct pci_device_id snd_hdspm_ids[] = { - { - .vendor = PCI_VENDOR_ID_XILINX, - .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID, - .class = 0, - .class_mask = 0, - .driver_data = 0}, + { PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI) }, + { PCI_DEVICE(0x1d18, 0x3fc6) }, /* RME HDSPe AIO PCI express audio */ {0,} }; From fcfe64715b425262af1b36f498f9197f3537ceed Mon Sep 17 00:00:00 2001 From: Vadim Nikitushkin Date: Thu, 10 Sep 2026 17:34:51 +0300 Subject: [PATCH 0785/1317] drm/ttm: apply the swapout bulk_move fix to the intended condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 3db7d7d58341 ("drm/ttm: fix swapped-out resources never leaving their bulk_move range") landed in drm-misc-fixes with its one-line change applied to the wrong "if": the "if (ret)" after ttm_resource_try_charge() in ttm_bo_alloc_at_place() became "if (ret > 0)", while the "if (!ret)" after ttm_tt_swapout() in ttm_bo_swapout_cb() that the patch targeted was left untouched. ttm_resource_try_charge() returns 0 or a negative error code, so with "ret > 0" a failed dmem cgroup charge no longer fails the allocation. Restore that check and apply the intended change: ttm_tt_swapout() returns the number of pages swapped out on success, so the bulk_move removal must run for ret > 0. Fixes: 3db7d7d58341 ("drm/ttm: fix swapped-out resources never leaving their bulk_move range") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Vadim Nikitushkin Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260910143451.65853-1-bub4z0r@gmail.com --- drivers/gpu/drm/ttm/ttm_bo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index a12af5b38a31..9b85b5f388d4 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -532,7 +532,7 @@ static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, force_space ? &alloc_state->limit_pool : NULL); - if (ret > 0) { + if (ret) { /* * -EAGAIN means the charge failed, which we treat * like an allocation failure. Therefore, return an @@ -1434,7 +1434,7 @@ ttm_bo_swapout_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) if (ttm_tt_is_populated(tt)) { ret = ttm_tt_swapout(bdev, tt, swapout_walk->gfp_flags); - if (!ret) { + if (ret > 0) { spin_lock(&bdev->lru_lock); ttm_resource_del_bulk_move_unevictable(bo->resource, bo); ttm_resource_move_to_lru_tail(bo->resource); From 478eb5abb51931a152abab068f8a717b7ff480fd Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Wed, 9 Sep 2026 15:03:35 +0800 Subject: [PATCH 0786/1317] net/sched: act_api: release all action references on NEWACTION failure When a batched RTM_NEWACTION request replaces an existing action, tcf_idr_check_alloc() takes a temporary reference on it. If a later action fails to initialize, tcf_action_destroy() uses strict release semantics to clean up the actions initialized so far. For an action bound to a filter, the strict check returns -EPERM without dropping the temporary reference. This error also makes tcf_action_destroy() return before releasing subsequent entries. Any new action initialized between the bound action and the failing entry is leaked together with its reserved IDR slot, preventing reuse of its index. Use tcf_idr_release() to drop each reference held by the batch without rejecting bound actions. This allows cleanup to continue through all initialized entries and preserves the module reference release when an action is destroyed. Explicit action deletion and flushing retain their separate bind-count checks. Fixes: 55334a5db5cd ("net_sched: act: refuse to remove bound action outside") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260909070336.32979-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 37eced84dfa5..19501dc99464 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1200,18 +1200,13 @@ EXPORT_SYMBOL(tcf_action_exec); int tcf_action_destroy(struct tc_action *actions[], int bind) { - const struct tc_action_ops *ops; struct tc_action *a; int ret = 0, i; tcf_act_for_each_action(i, a, actions) { actions[i] = NULL; - ops = a->ops; - ret = __tcf_idr_release(a, bind, true); - if (ret == ACT_P_DELETED) - module_put(ops->owner); - else if (ret < 0) - return ret; + /* Drop our reference even if the action is still bound to a filter. */ + ret = tcf_idr_release(a, bind); } return ret; } From 2a86bbed9f60702e97a8194e40f90f4db22d7795 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Wed, 9 Sep 2026 15:03:36 +0800 Subject: [PATCH 0787/1317] selftests: tc-testing: test action batch failure cleanup Add tests for cleanup after a batched RTM_NEWACTION request fails. Replace an existing gact action bound to a filter, then fail a later entry by requesting goto chain without a classifier context. Check that the bound action's reference count returns to its original value. Also cover a successfully initialized new action between the bound action and the failing entry, verifying that its reserved index can be reused. Repeat the bound action in another batch to check that each temporary reference to the same action is released. Signed-off-by: Xuanqiang Luo Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260909070336.32979-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- .../tc-tests/actions/gact-rollback.json | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/actions/gact-rollback.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/gact-rollback.json b/tools/testing/selftests/tc-testing/tc-tests/actions/gact-rollback.json new file mode 100644 index 000000000000..e92a4180db68 --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/actions/gact-rollback.json @@ -0,0 +1,78 @@ +[ + { + "id": "e3b1", + "name": "Failed action batch releases a bound action reference", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC filter add dev $DEV1 protocol all ingress prio 1 matchall action gact index 1" + ], + "cmdUnderTest": "$TC actions replace action pass index 1 action goto chain 42 index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 1.*index 1 ref 2 bind 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "e3b2", + "name": "Failed action batch releases entries after a bound action", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC filter add dev $DEV1 protocol all ingress prio 1 matchall action gact index 1", + [ + "$TC actions replace action pass index 1 action pass index 2 action goto chain 42 index 3", + 255 + ] + ], + "cmdUnderTest": "$TC actions add action pass index 2", + "expExitCode": "0", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 2.*index 1 ref 2 bind 1.*index 2 ref 1 bind 0", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "e3b3", + "name": "Failed action batch releases repeated references to a bound action", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC filter add dev $DEV1 protocol all ingress prio 1 matchall action gact index 1" + ], + "cmdUnderTest": "$TC actions replace action pass index 1 action pass index 1 action goto chain 42 index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 1.*index 1 ref 2 bind 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + } +] From cfdcf5571c3107bf636002fc0c16ce93c19bd671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20D=C3=A4nzer?= Date: Mon, 18 May 2026 17:48:09 +0200 Subject: [PATCH 0788/1317] drm/amd/display: Consult MCCS FreeSync cap only if requested & supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the do_mccs parameter is false, we don't call dm_helpers_read_mccs_caps, so sink->mccs_caps.freesync_supported is unlikely to be true. Fixes: 6f71d5dd3206 ("drm/amd/display: Read sink freesync support via mccs") Bug: https://gitlab.freedesktop.org/drm/amd/-/work_items/5286 Signed-off-by: Michel Dänzer Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit ac3aea794fb4156467b4b3b92c3155d95bf435c9) Cc: stable@vger.kernel.org --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index c8a1ab8c3b16..d03773887214 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -3937,17 +3937,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, } /* Handle MCCS */ - if (do_mccs) + if (do_mccs) { dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); - if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || - as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && - (!sink->edid_caps.freesync_vcp_code || - (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported))) - freesync_capable = false; + if (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported) + freesync_capable = false; - if (do_mccs && sink->mccs_caps.freesync_supported && freesync_capable) - dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + if (sink->mccs_caps.freesync_supported && freesync_capable) + dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + } update: if (dm_con_state) From 87ceb8cba73d0b3c4025ff42495bccd8164acaed Mon Sep 17 00:00:00 2001 From: Arunpravin Paneer Selvam Date: Wed, 2 Sep 2026 18:33:48 +0530 Subject: [PATCH 0789/1317] drm/amdgpu: skip the VMID 0 flush for VRAM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clear-on-release only runs on VRAM, which amdgpu_ttm_map_buffer() reaches via its direct MC address without programming a GART window, yet the wipe still forces a VMID 0 flush. On GFX11 (e.g. Navi33) that spurious SDMA flush can wedge the engine; only flush when a GART window is actually used. v2: Let amdgpu_ttm_map_buffer() return whether the VMID 0 flush is needed, and drive the clear and copy paths from that. (Christian) v3: Make the vm_needs_flush output parameter mandatory instead of allowing NULL. (Christian) Fixes: a68c7eaa7a8f ("drm/amdgpu: Enable clear page functionality") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5413 Cc: Christian König Signed-off-by: Arunpravin Paneer Selvam Reviewed-by: Christian König Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit a306e406e570b74318ff7d80e5b07b540ca1d3a9) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 05abf4c31dce..016957cac1f2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -191,6 +191,8 @@ amdgpu_ttm_job_submit(struct amdgpu_device *adev, struct amdgpu_ttm_buffer_entit * @tmz: if we should setup a TMZ enabled mapping * @size: in number of bytes to map, out number of bytes mapped * @addr: resulting address inside the MC address space + * @vm_needs_flush: out, set true if a GART window was programmed (VMID 0 flush + * needed) or false for a direct address * * Setup one of the GART windows to access a specific piece of memory or return * the physical address for local memory. @@ -200,7 +202,8 @@ static int amdgpu_ttm_map_buffer(struct amdgpu_ttm_buffer_entity *entity, struct ttm_resource *mem, struct amdgpu_res_cursor *mm_cur, unsigned int window, - bool tmz, uint64_t *size, uint64_t *addr) + bool tmz, uint64_t *size, uint64_t *addr, + bool *vm_needs_flush) { struct amdgpu_device *adev = amdgpu_ttm_adev(bo->bdev); unsigned int offset, num_pages, num_dw, num_bytes; @@ -221,9 +224,12 @@ static int amdgpu_ttm_map_buffer(struct amdgpu_ttm_buffer_entity *entity, if (!tmz && mem->start != AMDGPU_BO_INVALID_OFFSET) { *addr = amdgpu_ttm_domain_start(adev, mem->mem_type) + mm_cur->start; + *vm_needs_flush = false; return 0; } + /* A GART window is programmed below, so its VMID 0 TLB needs a flush */ + *vm_needs_flush = true; /* * If start begins at an offset inside the page, then adjust the size @@ -324,6 +330,7 @@ static int amdgpu_ttm_copy_mem_to_mem(struct amdgpu_device *adev, while (src_mm.remaining) { uint64_t from, to, cur_size, tiling_flags; uint32_t num_type, data_format, max_com, write_compress_disable; + bool src_vm_flush, dst_vm_flush; struct dma_fence *next; /* Never copy more than 256MiB at once to avoid a timeout */ @@ -331,12 +338,12 @@ static int amdgpu_ttm_copy_mem_to_mem(struct amdgpu_device *adev, /* Map src to window 0 and dst to window 1. */ r = amdgpu_ttm_map_buffer(entity, src->bo, src->mem, &src_mm, - 0, tmz, &cur_size, &from); + 0, tmz, &cur_size, &from, &src_vm_flush); if (r) goto error; r = amdgpu_ttm_map_buffer(entity, dst->bo, dst->mem, &dst_mm, - 1, tmz, &cur_size, &to); + 1, tmz, &cur_size, &to, &dst_vm_flush); if (r) goto error; @@ -364,7 +371,7 @@ static int amdgpu_ttm_copy_mem_to_mem(struct amdgpu_device *adev, } r = amdgpu_copy_buffer(adev, entity, from, to, cur_size, resv, - &next, true, copy_flags); + &next, src_vm_flush || dst_vm_flush, copy_flags); if (r) goto error; @@ -2624,6 +2631,7 @@ int amdgpu_ttm_clear_buffer(struct amdgpu_ttm_buffer_entity *entity, struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); struct dma_fence *fence = NULL; struct amdgpu_res_cursor dst; + bool vm_needs_flush = false; int r; if (!entity) @@ -2645,13 +2653,13 @@ int amdgpu_ttm_clear_buffer(struct amdgpu_ttm_buffer_entity *entity, cur_size = min(dst.size, 256ULL << 20); r = amdgpu_ttm_map_buffer(entity, &bo->tbo, bo->tbo.resource, &dst, - 0, false, &cur_size, &to); + 0, false, &cur_size, &to, &vm_needs_flush); if (r) goto error; r = amdgpu_ttm_fill_mem(adev, entity, 0, to, cur_size, resv, - &next, true, k_job_id); + &next, vm_needs_flush, k_job_id); if (r) goto error; From 622b4e8505aa7453a53d17fa3a288871f270fc8b Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 16 Jun 2026 13:39:21 -0400 Subject: [PATCH 0790/1317] dm/amdgpu: fix malformed link_settings debugfs output [Why] dp_link_settings_read() passed strlen() of each format string as the size argument to snprintf() and then advanced rd_buf_ptr by that same fixed amount. The format-string length has no relation to the formatted output length, so snprintf() truncated each field at a NUL it wrote inside the buffer while the pointer was advanced past it. The result is a buffer peppered with embedded NUL bytes and fields that are silently cut short, so the data read back from the debugfs node does not reflect the actual link settings. [How] Use scnprintf() with the real remaining buffer size (rd_buf_size - (rd_buf_ptr - rd_buf)) and advance rd_buf_ptr by its return value, which is the number of characters actually written. This both bounds each write to the space left in rd_buf and keeps the output a single, properly terminated string. The now-unused str_len local is removed. Fixes: 41db5f1931ec ("drm/amd/display: set-read link rate and lane count through debugfs") Assisted-by: Copilot:claude-opus-4.8 Signed-off-by: Harry Wentland Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 43b9f0f18693c7f7b75613f3aeae25fa2b4e2f76) Cc: stable@vger.kernel.org --- .../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index c4b2fc690fd7..2a6b48e24869 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -196,7 +196,6 @@ static ssize_t dp_link_settings_read(struct file *f, char __user *buf, char *rd_buf_ptr = NULL; const uint32_t rd_buf_size = 100; uint32_t result = 0; - uint8_t str_len = 0; int r; if (*pos & 3 || size & 3) @@ -208,29 +207,26 @@ static ssize_t dp_link_settings_read(struct file *f, char __user *buf, rd_buf_ptr = rd_buf; - str_len = strlen("Current: %d 0x%x %d "); - snprintf(rd_buf_ptr, str_len, "Current: %d 0x%x %d ", + rd_buf_ptr += scnprintf(rd_buf_ptr, rd_buf_size - (rd_buf_ptr - rd_buf), + "Current: %d 0x%x %d ", link->cur_link_settings.lane_count, link->cur_link_settings.link_rate, link->cur_link_settings.link_spread); - rd_buf_ptr += str_len; - str_len = strlen("Verified: %d 0x%x %d "); - snprintf(rd_buf_ptr, str_len, "Verified: %d 0x%x %d ", + rd_buf_ptr += scnprintf(rd_buf_ptr, rd_buf_size - (rd_buf_ptr - rd_buf), + "Verified: %d 0x%x %d ", link->verified_link_cap.lane_count, link->verified_link_cap.link_rate, link->verified_link_cap.link_spread); - rd_buf_ptr += str_len; - str_len = strlen("Reported: %d 0x%x %d "); - snprintf(rd_buf_ptr, str_len, "Reported: %d 0x%x %d ", + rd_buf_ptr += scnprintf(rd_buf_ptr, rd_buf_size - (rd_buf_ptr - rd_buf), + "Reported: %d 0x%x %d ", link->reported_link_cap.lane_count, link->reported_link_cap.link_rate, link->reported_link_cap.link_spread); - rd_buf_ptr += str_len; - str_len = strlen("Preferred: %d 0x%x %d "); - snprintf(rd_buf_ptr, str_len, "Preferred: %d 0x%x %d\n", + rd_buf_ptr += scnprintf(rd_buf_ptr, rd_buf_size - (rd_buf_ptr - rd_buf), + "Preferred: %d 0x%x %d\n", link->preferred_link_setting.lane_count, link->preferred_link_setting.link_rate, link->preferred_link_setting.link_spread); From 2e8ff3ac79eb09dccbd8f00ee5da1b61e53246be Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Thu, 18 Jun 2026 09:52:14 -0700 Subject: [PATCH 0791/1317] drm/amd/display: Shorten hdmi_frl_status_polling_workqueue There is a warning when creating the hdmi_frl_status_polling_wq workqueue because "hdmi_frl_status_polling_workqueue" excceds WQ_NAME_LEN: workqueue: name exceeds WQ_NAME_LEN. Truncating to: hdmi_frl_status_polling_workque Shorten the workqueue name to "hdmi_frl_status_polling_wq" like the structure member to avoid the warning. Fixes: 5c9b8b27a883 ("drm/amd/display: Tie FRL support into amdgpu_dm") Reviewed-by: Alex Hung Signed-off-by: Nathan Chancellor Reviewed-by: Tvrtko Ursulin Link: https://patch.msgid.link/20260618-amdgpu-fix-wq_name_len-warning-v2-1-ef0e2e6f5be7@kernel.org Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 767ae341b68193fda5fdbc510b2d77e3e8938039) --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 2fe934036e36..a95243656f54 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -747,9 +747,9 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) } if (adev->dm.dc->caps.max_links > 0) { adev->dm.hdmi_frl_status_polling_wq = - create_singlethread_workqueue("hdmi_frl_status_polling_workqueue"); + create_singlethread_workqueue("hdmi_frl_status_polling_wq"); if (!adev->dm.hdmi_frl_status_polling_wq) - drm_err(adev_to_drm(adev), "failed to initialize hdmi_frl_status_polling_workqueue\n"); + drm_err(adev_to_drm(adev), "failed to initialize hdmi_frl_status_polling_wq\n"); } if (dc_is_dmub_outbox_supported(adev->dm.dc)) { init_completion(&adev->dm.dmub_aux_transfer_done); From 3001d2073d6542a9e51fa5bca3a39a078094d3c6 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Thu, 27 Aug 2026 13:12:46 -0400 Subject: [PATCH 0792/1317] drm/amd/display: Exit IPS before connector detection on resume [Why & How] On resume, dm_resume() walks the connector list and, for each connector, calls dc_link_detect_connection_type() at the top of the loop iteration before the per-connector dc_exit_ips_for_hw_access() that sits in the detection branch. There is no dc_exit_ips_for_hw_access() before the loop, so the very first HW access relies on an earlier connector having already taken the display out of IPS. Commit d1d51519bc3b ("drm/amd/display: Skip eDP detection when no sink") skips the eDP connector when no panel is present. On a DCN3.5 APU whose eDP link has no sink, the eDP iteration - which used to bring the HW out of IPS first - is now skipped, so a downstream DP connector becomes the first one processed. Its initial DDC/AUX access then runs while the HW is still idle, the AUX transfers time out (-ETIMEDOUT), and the EDID read fails: [drm:dm_helpers_read_local_edid [amdgpu]] *ERROR* EDID err: 2, on connector: DP-1 amdgpu: [drm] *ERROR* No EDID read. Take the display out of IPS once before the detection loop so the first connector processed no longer touches the AUX/DDC engine while the HW is still in idle power state. This keeps the eDP-skip boot/resume optimization while fixing the DP EDID read failure. Fixes: d1d51519bc3b ("drm/amd/display: Skip eDP detection when no sink") Reviewed-by: Roman Li Signed-off-by: Fangzhi Zuo Signed-off-by: Ray Wu Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 86420fe3093161971b4064e05be11ffff1df76aa) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index a95243656f54..08b8605029ab 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1972,6 +1972,10 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) /* On resume we need to rewrite the MSTM control bits to enable MST*/ s3_handle_mst(ddev, false); + /* Exit IPS before the detection loop's first AUX/DDC access. */ + scoped_guard(mutex, &dm->dc_lock) + dc_exit_ips_for_hw_access(dm->dc); + /* Do detection*/ drm_connector_list_iter_begin(ddev, &iter); drm_for_each_connector_iter(connector, &iter) { From 5d7e0cc4afda0cb25c0829dc7c1bb4bedd7886a5 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Wed, 26 Aug 2026 17:47:41 -0400 Subject: [PATCH 0793/1317] drm/amd/display: Fix HF-VSDB DSC bpc detection to be cumulative [Why & How] The HDMI Forum VSDB reports the maximum DSC color depth a sink supports. This maximum is cumulative: a sink that reports 12 bpc also supports 10 and 8 bpc. The previous code used exact "== 10" and "== 12" comparisons chained with else-if, so a 12 bpc sink only set frl_dsc_12bpc and never set frl_dsc_10bpc, incorrectly narrowing the DSC bpc range usable with that sink. Use ">= 10" and a separate ">= 12" check so a sink advertising a higher maximum also enables the lower DSC bit depths it supports. Reviewed-by: Alex Hung Signed-off-by: Fangzhi Zuo Signed-off-by: Ray Wu Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 4523adbf4dca157aea96a6f28b4e7b7ebd4d5eda) --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 5 +++-- .../drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index d451082552e8..249e0cd995c6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1202,9 +1202,10 @@ void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hd edid_caps->max_frl_rate = get_max_frl_rate(hdmi->max_lanes, hdmi->max_frl_rate_per_lane); edid_caps->frl_dsc_support = hdmi->dsc_cap.v_1p2; if (edid_caps->frl_dsc_support) { - if (hdmi->dsc_cap.bpc_supported == 10) + /* HF-VSDB DSC max bpc is cumulative: >=12 implies 10 and 8. */ + if (hdmi->dsc_cap.bpc_supported >= 10) edid_caps->frl_dsc_10bpc = true; - else if (hdmi->dsc_cap.bpc_supported == 12) + if (hdmi->dsc_cap.bpc_supported >= 12) edid_caps->frl_dsc_12bpc = true; edid_caps->frl_dsc_all_bpp = hdmi->dsc_cap.all_bpp; edid_caps->frl_dsc_native_420 = hdmi->dsc_cap.native_420; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 058e1ad15dfe..0c4517fb9d75 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -909,7 +909,7 @@ static void dm_test_populate_hdmi_frl_dsc_12bpc(struct kunit *test) KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 2); KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support); - KUNIT_EXPECT_FALSE(test, caps->frl_dsc_10bpc); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_10bpc); KUNIT_EXPECT_TRUE(test, caps->frl_dsc_12bpc); KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 7); KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 1); From 1f1d43418d61c8511779e0f63a954a78b9433b43 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Mon, 31 Aug 2026 10:51:04 +0800 Subject: [PATCH 0794/1317] drm/amdgpu: skip gfx switch_power_profile during GPU reset During resume from GPU reset, the gfx idle work may invoke switch_power_profile before the reset completes. This causes the following assert error because the register access occurs without first releasing the GPU reset semaphore: [ 1576.768935] CR2: 0000559ea133ead0 CR3: 00000002e6c42000 CR4: 0000000000350ef0 [ 1576.768940] Call Trace: [ 1576.768944] [ 1576.768953] amdgpu_device_rreg+0x21/0x50 [amdgpu] [ 1576.769158] smu_msg_v1_send_msg+0x1a4/0x6e0 [amdgpu] [ 1576.769437] smu_cmn_send_smc_msg_with_params_ext+0xba/0x120 [amdgpu] [ 1576.769721] smu_cmn_send_smc_msg_with_param+0x33/0x40 [amdgpu] [ 1576.769993] smu_v13_0_0_set_power_profile_mode+0x192/0x2b0 [amdgpu] [ 1576.770267] smu_bump_power_profile_mode+0x5d/0x80 [amdgpu] [ 1576.770538] smu_switch_power_profile+0xa4/0xf0 [amdgpu] [ 1576.770839] amdgpu_dpm_switch_power_profile+0x6f/0x90 [amdgpu] [ 1576.771210] amdgpu_gfx_profile_idle_work_handler+0xe9/0x130 [amdgpu] [ 1576.771460] process_one_work+0x23e/0x6f0 [ 1576.771491] worker_thread+0x1c4/0x380 [ 1576.771506] kthread+0x10c/0x150 [ 1576.771512] ? __pfx_worker_thread+0x10/0x10 [ 1576.771518] ? __pfx_kthread+0x10/0x10 [ 1576.771530] ret_from_fork+0x314/0x390 [ 1576.771537] ? __pfx_kthread+0x10/0x10 [ 1576.771546] ret_from_fork_asm+0x1a/0x30 Signed-off-by: Prike Liang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit d93b1ff538ce9750c01e0dd0aa62575579c0fc08) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/amdgpu_dpm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index ce526db4d24a..808be6c425bf 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -348,7 +348,8 @@ int amdgpu_dpm_switch_power_profile(struct amdgpu_device *adev, const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; int ret = 0; - if (amdgpu_sriov_vf(adev)) + if (amdgpu_sriov_vf(adev) || + amdgpu_in_reset(adev)) return 0; if (pp_funcs && pp_funcs->switch_power_profile) { @@ -367,7 +368,8 @@ int amdgpu_dpm_pause_power_profile(struct amdgpu_device *adev, const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; int ret = 0; - if (amdgpu_sriov_vf(adev)) + if (amdgpu_sriov_vf(adev) || + amdgpu_in_reset(adev)) return 0; if (pp_funcs && pp_funcs->pause_power_profile) { From 829157e762ed043e28ecb2e9f3c7d22b54e5989e Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Tue, 8 Sep 2026 16:59:51 -0300 Subject: [PATCH 0795/1317] Revert "drm/amdgpu: debugfs: avoid extra EOLs in amdgpu_gem_info" This reverts commit c119d05a36a884482decc67e55944648f8cba97e. It removes the newline even when there are no fences attached to a struct dma_resv, leading to multiple BOs being output on the same line, making the debug file less readable, not more as the commit intended. Signed-off-by: Thadeu Lima de Souza Cascardo Signed-off-by: Alex Deucher (cherry picked from commit a2aafaeb2be13ed3c893e6a44a3a5d26b251ae6a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_object.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c index 5d9d137209b6..1b6c32a177fb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -1701,9 +1701,8 @@ u64 amdgpu_bo_print_info(int id, struct amdgpu_bo *bo, struct seq_file *m) if (dma_resv_trylock(bo->tbo.base.resv)) { dma_resv_describe(bo->tbo.base.resv, m); dma_resv_unlock(bo->tbo.base.resv); - } else { - seq_puts(m, "\n"); } + seq_puts(m, "\n"); return size; } From bdcd0411d7d186225a52458fd42bb70d54ca917a Mon Sep 17 00:00:00 2001 From: Satyajit Roy Date: Sun, 30 Aug 2026 03:51:58 +0000 Subject: [PATCH 0796/1317] drm/amd/display: Propagate HDMI RGB quantization selectability DC uses dc_edid_caps.qs_bit when constructing the HDMI AVI InfoFrame quantization-range field. Although DRM parses the sink capability into drm_display_info, DM never copies it into the DC EDID capabilities. The field therefore remains zero and the AVI quantization range stays at its default value. Copy rgb_quant_range_selectable for HDMI sinks and extend the existing EDID-capability KUnit test to cover it. Fixes: 6eb4c13a3845 ("drm/amd/display: Support "Broadcast RGB" drm property") Signed-off-by: Satyajit Roy Reviewed-by: Alex Hung Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 892659399f64642e33072562a11ec1b2e7bd2263) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 1 + .../drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 249e0cd995c6..298de7b75ca8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -200,6 +200,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps( edid_caps->edid_hdmi = connector->display_info.is_hdmi; if (edid_caps->edid_hdmi) { + edid_caps->qs_bit = connector->display_info.rgb_quant_range_selectable; populate_hdmi_info_from_connector(link->dc->config.enable_frl, &connector->display_info.hdmi, edid_caps); drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] max_frl_rate %d\n", __func__, connector->name, edid_caps->max_frl_rate); if (edid_caps->frl_dsc_support) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 0c4517fb9d75..82e0c984693c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -358,12 +358,14 @@ static void dm_test_parse_edid_caps_hdmi_frl(struct kunit *test) /* Drive the HDMI/FRL branch */ connector->display_info.is_hdmi = true; + connector->display_info.rgb_quant_range_selectable = true; connector->display_info.hdmi.scdc.supported = true; connector->display_info.hdmi.max_lanes = 4; connector->display_info.hdmi.max_frl_rate_per_lane = 12; KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); KUNIT_EXPECT_TRUE(test, edid_caps->edid_hdmi); + KUNIT_EXPECT_EQ(test, edid_caps->qs_bit, 1); KUNIT_EXPECT_TRUE(test, edid_caps->scdc_present); /* max_lanes 4 + max_frl_rate_per_lane 12 -> rate index 6 */ KUNIT_EXPECT_EQ(test, edid_caps->max_frl_rate, 6); From 7fca7acd60a228b62b4e9efa5f184738041e9564 Mon Sep 17 00:00:00 2001 From: Satyajit Roy Date: Sun, 30 Aug 2026 03:52:06 +0000 Subject: [PATCH 0797/1317] drm/amd/display: Honor Broadcast RGB for BT.2020 RGB output amdgpu_dm_get_output_color_space() applies the Broadcast RGB connector property to default RGB output, but always selects full-range output for BT.2020 RGB. Consequently, explicitly selecting Limited has no effect on the output CSC or AVI InfoFrame when HDR uses BT.2020 RGB. Select COLOR_SPACE_2020_RGB_LIMITEDRANGE when the output encoding is RGB and Broadcast RGB is Limited. Keep Automatic and Full at full range, and leave YCbCr output unchanged. Add KUnit coverage for limited-range RGB output through both BT.2020 connector colorspace values. Fixes: 6eb4c13a3845 ("drm/amd/display: Support "Broadcast RGB" drm property") Signed-off-by: Satyajit Roy Reviewed-by: Alex Hung Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 022236eaa63bbf65761aa8aec43f661451a94654) Cc: stable@vger.kernel.org --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 10 +++-- .../tests/amdgpu_dm_connector_test.c | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index d03773887214..154e1f35dcb1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -756,10 +756,14 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, break; case DRM_MODE_COLORIMETRY_BT2020_RGB: case DRM_MODE_COLORIMETRY_BT2020_YCC: - if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) - color_space = COLOR_SPACE_2020_RGB_FULLRANGE; - else + if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { + if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + color_space = COLOR_SPACE_2020_RGB_LIMITEDRANGE; + else + color_space = COLOR_SPACE_2020_RGB_FULLRANGE; + } else { color_space = COLOR_SPACE_2020_YCBCR_LIMITED; + } break; case DRM_MODE_COLORIMETRY_DEFAULT: /* ITU601 */ default: diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 0d2f9dbce0a9..212a7536e65b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -567,6 +567,23 @@ static void dm_test_output_color_space_bt2020_rgb(struct kunit *test) (int)COLOR_SPACE_2020_RGB_FULLRANGE); } +/** + * dm_test_output_color_space_bt2020_rgb_limited - Test limited BT.2020 RGB + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb_limited(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); +} + /** * dm_test_output_color_space_bt2020_ycc - Test Output color space bt2020 ycc * @test: The KUnit test context @@ -638,6 +655,24 @@ static void dm_test_output_color_space_bt2020_ycc_rgb_encoding(struct kunit *tes (int)COLOR_SPACE_2020_RGB_FULLRANGE); } +/** + * dm_test_output_color_space_bt2020_ycc_rgb_encoding_limited - Test limited + * BT.2020 RGB output selected through the BT.2020 YCC connector colorspace + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_ycc_rgb_encoding_limited(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); +} + /** * dm_test_output_color_space_bt2020_rgb_ycc_encoding - Test Output color space * bt2020 rgb with non-rgb pixel encoding falls back to limited ycbcr @@ -5422,10 +5457,12 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_output_color_space_bt709_y_only), KUNIT_CASE(dm_test_output_color_space_oprgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_limited), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), KUNIT_CASE(dm_test_output_color_space_default_ycbcr709_y_only), KUNIT_CASE(dm_test_output_color_space_default_ycbcr601), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc_rgb_encoding), + KUNIT_CASE(dm_test_output_color_space_bt2020_ycc_rgb_encoding_limited), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_ycc_encoding), /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc */ KUNIT_CASE(dm_test_convert_color_depth_bpc_mappings), From 8cfd9e22eb5c04b15b82985ff913944f84673d4f Mon Sep 17 00:00:00 2001 From: Satyajit Roy Date: Sun, 30 Aug 2026 03:52:13 +0000 Subject: [PATCH 0798/1317] drm/amd/display: Rebuild InfoFrames on output color space changes resource_build_info_frame() derives colorimetry and RGB quantization from stream->output_color_space. A Broadcast RGB-only atomic commit updates that field and reprograms the output CSC, but none of the InfoFrame update predicates include output_color_space. The sink can therefore retain the previous AVI InfoFrame range while the source starts transmitting a different pixel range. Treat an output color space change as an InfoFrame change in update classification and in both stream programming paths. Hardware testing on an HDMI 2.1 television confirmed that its automatic black-level selection follows Full to Limited and Limited to Full transitions in SDR, HDR, and HDR with VRR active, without a modeset or visible link blank. Fixes: 6eb4c13a3845 ("drm/amd/display: Support "Broadcast RGB" drm property") Signed-off-by: Satyajit Roy Reviewed-by: Alex Hung Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit d6faca79f5720893843e649e70aeb19147ee0578) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/core/dc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index a98ed4617a03..519ac878ada1 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -3198,6 +3198,7 @@ static struct dc_update_descriptor check_update_surfaces_for_stream( } if ((stream_update->hdr_static_metadata && !stream_update->stream->use_dynamic_meta) || + stream_update->output_color_space || stream_update->vrr_infopacket || stream_update->vsc_infopacket || stream_update->vsp_infopacket || @@ -4188,6 +4189,7 @@ static void commit_planes_do_stream_update_sequence(struct dc *dc, hwss_add_setup_periodic_interrupt(&seq_state, dc, pipe_ctx); if ((stream_update->hdr_static_metadata && !stream->use_dynamic_meta) || + stream_update->output_color_space || stream_update->vrr_infopacket || stream_update->vsc_infopacket || stream_update->vsp_infopacket || @@ -4370,6 +4372,7 @@ static void commit_planes_do_stream_update(struct dc *dc, dc->hwss.setup_periodic_interrupt(dc, pipe_ctx); if ((stream_update->hdr_static_metadata && !stream->use_dynamic_meta) || + stream_update->output_color_space || stream_update->vrr_infopacket || stream_update->vsc_infopacket || stream_update->vsp_infopacket || From 13ddcc7acb9adbed7627e952a61d1d62cd9546fc Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 26 Aug 2026 09:51:55 +0800 Subject: [PATCH 0799/1317] drm/amd/pm: fix gpu metrics energy accumulator for smu 13.0.0/13.0.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GPU metrics v1.3 defines energy_accumulator as a 64‑bit field. The unsupported‑firmware code path assigns UINT_MAX, which is neither the full‑width invalid value for this field nor its default value. Fixes: 8de9edb35976 ("drm/amd/pm: remove invalid gpu_metrics.energy_accumulator on smu v13.0.x") Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit c2b948c4fe16eb13d98ff5d1371956cb2f55cdc6) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 2 -- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c index 6e741ec4a71e..87b80b6f83b5 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c @@ -2095,8 +2095,6 @@ static ssize_t smu_v13_0_0_get_gpu_metrics(struct smu_context *smu, if ((mp1_ver == IP_VERSION(13, 0, 0) && smu->smc_fw_version <= 0x004e1e00) || (mp1_ver == IP_VERSION(13, 0, 10) && smu->smc_fw_version <= 0x00500800)) gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; - else - gpu_metrics->energy_accumulator = UINT_MAX; if (metrics->AverageGfxActivity <= SMU_13_0_0_BUSY_THRESHOLD) gpu_metrics->average_gfxclk_frequency = metrics->AverageGfxclkFrequencyPostDs; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c index b94ae43586df..5fe409a23772 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c @@ -2097,8 +2097,8 @@ static ssize_t smu_v13_0_7_get_gpu_metrics(struct smu_context *smu, metrics->Vcn1ActivityPercentage); gpu_metrics->average_socket_power = metrics->AverageSocketPower; - gpu_metrics->energy_accumulator = smu->smc_fw_version <= 0x00521400 ? - metrics->EnergyAccumulator : UINT_MAX; + if (smu->smc_fw_version <= 0x00521400) + gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; if (metrics->AverageGfxActivity <= SMU_13_0_7_BUSY_THRESHOLD) gpu_metrics->average_gfxclk_frequency = metrics->AverageGfxclkFrequencyPostDs; From f3c6a8ae601abf2d8476d3f899283cc9a1001f7d Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 8 Sep 2026 18:15:41 +0800 Subject: [PATCH 0800/1317] drm/amd/pm: report energy accumulator for smu 13.0.0 add energy accumulator on pmfw 0x004e8600 and above version. Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit 3a804a5b15c22e4d7a3906ff09035e539785813e) --- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c index 87b80b6f83b5..a29e76b03476 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c @@ -2092,8 +2092,10 @@ static ssize_t smu_v13_0_0_get_gpu_metrics(struct smu_context *smu, gpu_metrics->average_socket_power = metrics->AverageSocketPower; - if ((mp1_ver == IP_VERSION(13, 0, 0) && smu->smc_fw_version <= 0x004e1e00) || - (mp1_ver == IP_VERSION(13, 0, 10) && smu->smc_fw_version <= 0x00500800)) + if ((mp1_ver == IP_VERSION(13, 0, 0) && + (smu->smc_fw_version <= 0x004e1e00 || smu->smc_fw_version >= 0x004e8600)) || + (mp1_ver == IP_VERSION(13, 0, 10) && + smu->smc_fw_version <= 0x00500800)) gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; if (metrics->AverageGfxActivity <= SMU_13_0_0_BUSY_THRESHOLD) From a19d4f9b8befdcfcd5a87bab91312fe64af3bbb8 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sat, 6 Jun 2026 23:40:49 -0700 Subject: [PATCH 0801/1317] ata: pata_legacy: remove documentation for removed module parameters Commit 3c4d783f6922 ("ata: pata_legacy: remove VLB support") removed several module parameters from the pata_legacy driver, but neglected to remove their documentation. Remove it. Fixes: 3c4d783f6922 ("ata: pata_legacy: remove VLB support") Cc: stable@vger.kernel.org # 7.0+ Signed-off-by: Ethan Nelson-Moore Reviewed-by: Karl Mehltretter Reviewed-by: Damien Le Moal Reviewed-by: Randy Dunlap Link: https://lore.kernel.org/r/20260607064053.195166-1-enelsonmoore@gmail.com Signed-off-by: Niklas Cassel --- .../admin-guide/kernel-parameters.txt | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd2..33cd30996e47 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4987,18 +4987,6 @@ Kernel parameters Set to non-zero if a chip is present that snoops speed changes. Disabled by default. - pata_legacy.ht6560a= [HW,LIBATA] - Format: - Set to 1, 2, or 3 for HT 6560A on the primary channel, - the secondary channel, or both channels respectively. - Disabled by default. - - pata_legacy.ht6560b= [HW,LIBATA] - Format: - Set to 1, 2, or 3 for HT 6560B on the primary channel, - the secondary channel, or both channels respectively. - Disabled by default. - pata_legacy.iordy_mask= [HW,LIBATA] Format: IORDY enable mask. Set individual bits to allow IORDY @@ -5011,18 +4999,6 @@ Kernel parameters with the sequence. By default IORDY is allowed across all channels. - pata_legacy.opti82c46x= [HW,LIBATA] - Format: - Set to 1, 2, or 3 for Opti 82c611A on the primary - channel, the secondary channel, or both channels - respectively. Disabled by default. - - pata_legacy.opti82c611a= [HW,LIBATA] - Format: - Set to 1, 2, or 3 for Opti 82c465MV on the primary - channel, the secondary channel, or both channels - respectively. Disabled by default. - pata_legacy.pio_mask= [HW,LIBATA] Format: PIO mode mask for autospeed devices. Set individual @@ -5046,19 +5022,6 @@ Kernel parameters the first port in the list above (0x1f0), and so on. By default all supported ports are probed. - pata_legacy.qdi= [HW,LIBATA] - Format: - Set to non-zero to probe QDI controllers. By default - set to 1 if CONFIG_PATA_QDI_MODULE, 0 otherwise. - - pata_legacy.winbond= [HW,LIBATA] - Format: - Set to non-zero to probe Winbond controllers. Use - the standard I/O port (0x130) if 1, otherwise the - value given is the I/O port to use (typically 0x1b0). - By default set to 1 if CONFIG_PATA_WINBOND_VLB_MODULE, - 0 otherwise. - pata_platform.pio_mask= [HW,LIBATA] Format: Supported PIO mode mask. Set individual bits to allow From f65d38155aef069c897a64643a03db237dd1e0c8 Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 3 Aug 2026 10:47:01 +0100 Subject: [PATCH 0802/1317] x86/div64: Fix addition of large constants in mul_u64_add_u64_div_u64() Adding constants over 2^31 fails to compile because the ADD instruction only supports 32bit signed immediates. Replace the "irm" constraint with "erm" so that the compiler loads large constants into a register. Found by a patch to drivers/iio/frequency/ad9910.c [ bp: Massage commit message. ] Fixes: 6480241f31f5 ("lib: add mul_u64_add_u64_div_u64() and mul_u64_u64_div_u64_roundup()") Signed-off-by: David Laight Signed-off-by: Borislav Petkov (AMD) Reviewed-by: H. Peter Anvin Link: https://patch.msgid.link/20260803094702.3852-2-david.laight.linux@gmail.com --- arch/x86/include/asm/div64.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/asm/div64.h b/arch/x86/include/asm/div64.h index 30fd06ede751..8a2d343f977e 100644 --- a/arch/x86/include/asm/div64.h +++ b/arch/x86/include/asm/div64.h @@ -111,7 +111,7 @@ static inline u64 mul_u64_add_u64_div_u64(u64 rax, u64 mul, u64 add, u64 div) if (!statically_true(!add)) asm ("addq %[add], %[lo]; adcq $0, %[hi]" : - [lo] "+r" (rax), [hi] "+r" (rdx) : [add] "irm" (add)); + [lo] "+r" (rax), [hi] "+r" (rdx) : [add] "erm" (add)); asm ("divq %[div]" : "+a" (rax), "+d" (rdx) : [div] "rm" (div)); From adf5967331318bcb436fc80069915231ec039352 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:03:47 +0200 Subject: [PATCH 0803/1317] drm/msm: Fix the separate_gpu_kms parameter description The module parameter is separate_gpu_kms, but its MODULE_PARM_DESC() names separate_gpu_drm, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 217ed15bd399 ("drm/msm: enable separate binding of GPU and display devices") Assisted-by: LLM Signed-off-by: Karl Mehltretter Patchwork: https://patchwork.freedesktop.org/patch/751407/ Message-ID: <20260906170347.2427-1-kmehltretter@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index db1b655dd055..f3d2eaa04f14 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -55,7 +55,7 @@ MODULE_PARM_DESC(modeset, "Use kernel modesetting [KMS] (1=on (default), 0=disab module_param(modeset, bool, 0600); static bool separate_gpu_kms; -MODULE_PARM_DESC(separate_gpu_drm, "Use separate DRM device for the GPU (0=single DRM device for both GPU and display (default), 1=two DRM devices)"); +MODULE_PARM_DESC(separate_gpu_kms, "Use separate DRM device for the GPU (0=single DRM device for both GPU and display (default), 1=two DRM devices)"); module_param(separate_gpu_kms, bool, 0400); DECLARE_FAULT_ATTR(fail_gem_alloc); From 8061ee61b9426fe38350fa9eead2d9c50b03deb6 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:03:01 +0200 Subject: [PATCH 0804/1317] drm/msm/adreno: Fix the skip_gpu parameter description The module parameter is skip_gpu, but its MODULE_PARM_DESC() names no_gpu, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 3f17991488af ("drm/msm/adreno: Add a modparam to skip GPU") Assisted-by: LLM Signed-off-by: Karl Mehltretter Patchwork: https://patchwork.freedesktop.org/patch/751406/ Message-ID: <20260906170301.2393-1-kmehltretter@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_device.c b/drivers/gpu/drm/msm/adreno/adreno_device.c index 7f20320ef66a..05c77fe27e62 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_device.c +++ b/drivers/gpu/drm/msm/adreno/adreno_device.c @@ -25,7 +25,7 @@ MODULE_PARM_DESC(disable_acd, "Forcefully disable GPU ACD"); module_param_unsafe(disable_acd, bool, 0400); static bool skip_gpu; -MODULE_PARM_DESC(no_gpu, "Disable GPU driver register (0=enable GPU driver register (default), 1=skip GPU driver register"); +MODULE_PARM_DESC(skip_gpu, "Disable GPU driver register (0=enable GPU driver register (default), 1=skip GPU driver register"); module_param(skip_gpu, bool, 0400); extern const struct adreno_gpulist a2xx_gpulist; From 01c8d1f385f788f1bbbbb7687c4386d614281218 Mon Sep 17 00:00:00 2001 From: "Jonghyuk Kim(MalHyuk)" Date: Wed, 2 Sep 2026 10:27:20 +0900 Subject: [PATCH 0805/1317] drm/msm: RCU-free the scheduler-containing ring and VM objects Both struct msm_ringbuffer and struct msm_gem_vm embed a struct drm_gpu_scheduler. msm_ringbuffer_destroy() and the VM free callback msm_gem_vm_free() call drm_sched_fini() on the embedded scheduler and then free the containing object with plain kfree(). drm_sched_fence_get_timeline_name() returns fence->sched->name, and the scheduler fence keeps a .release callback so it is not ops-detached on signalling. A finished fence exported to userspace (the submit out-fence, or a VM_BIND fence, via sync_file / drm_syncobj) keeps pointing at the embedded scheduler after the ring/VM is freed, so a later get_timeline_name() -- reachable unprivileged through SYNC_IOC_FILE_INFO -- dereferences freed slab memory (KASAN slab-use-after-free read). Per the dma-fence lifetime contract the exporter must keep the data backing a signalled fence alive for an RCU grace period. Free the scheduler-containing objects with kfree_rcu() instead of kfree(). Fixes: 1d8a5ca436ee ("drm/msm: Conversion to drm scheduler") Fixes: 92395af63a99 ("drm/msm: Add VM_BIND submitqueue") Cc: stable@vger.kernel.org Signed-off-by: Jonghyuk Kim(MalHyuk) Patchwork: https://patchwork.freedesktop.org/patch/750234/ Message-ID: <20260902012720.880783-1-malhyuk97@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gem.h | 3 +++ drivers/gpu/drm/msm/msm_gem_vma.c | 2 +- drivers/gpu/drm/msm/msm_ringbuffer.c | 2 +- drivers/gpu/drm/msm/msm_ringbuffer.h | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index dff60cbc9d95..7c6a8c01f910 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -68,6 +68,9 @@ struct msm_gem_vm { /** @base: Inherit from drm_gpuvm. */ struct drm_gpuvm base; + /** @rcu: RCU-delayed free so an exported sched fence->sched stays valid. */ + struct rcu_head rcu; + /** * @sched: Scheduler used for asynchronous VM_BIND request. * diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index c11d021581e0..1badec3caa7b 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -166,7 +166,7 @@ msm_gem_vm_free(struct drm_gpuvm *gpuvm) dma_fence_put(vm->last_fence); put_pid(vm->pid); kfree(vm->log); - kfree(vm); + kfree_rcu(vm, rcu); } /** diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.c b/drivers/gpu/drm/msm/msm_ringbuffer.c index 59c69aa75649..38e1e6866301 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.c +++ b/drivers/gpu/drm/msm/msm_ringbuffer.c @@ -140,5 +140,5 @@ void msm_ringbuffer_destroy(struct msm_ringbuffer *ring) msm_gem_kernel_put(ring->bo, ring->gpu->vm); - kfree(ring); + kfree_rcu(ring, rcu); } diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.h b/drivers/gpu/drm/msm/msm_ringbuffer.h index 3631ec283c6e..0bfb6b0f42fa 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.h +++ b/drivers/gpu/drm/msm/msm_ringbuffer.h @@ -55,6 +55,7 @@ struct msm_ringbuffer { /* * The job scheduler for this ring. */ + struct rcu_head rcu; struct drm_gpu_scheduler sched; bool sched_initialized; From 3fb13d29cf8eb4502837bff06b2873c5435f6ffd Mon Sep 17 00:00:00 2001 From: Yang Zi <2959243019@qq.com> Date: Tue, 25 Aug 2026 16:58:15 +0800 Subject: [PATCH 0806/1317] fbdev: ssd1307fb: fix NULL pointer dereference on missing match data device_get_match_data() can return NULL, e.g. when the device is matched through the I2C device ID table rather than the OF match table. The returned value is stored in par->device_info and later dereferenced when initializing par->vcomh, causing a NULL pointer dereference. Check the return value right after the assignment and bail out with -ENODEV (releasing the already allocated framebuffer) before any dereference. Signed-off-by: Yang Zi <2959243019@qq.com> Signed-off-by: Helge Deller --- drivers/video/fbdev/ssd1307fb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index c4fdecafd856..4d185c754284 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -665,6 +665,10 @@ static int ssd1307fb_probe(struct i2c_client *client) spin_lock_init(&par->damage_lock); par->device_info = device_get_match_data(dev); + if (!par->device_info) { + ret = -ENODEV; + goto fb_alloc_error; + } par->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(par->reset)) { From 3934185ba63feca6e80cc8b90f7d7f01cde78223 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 27 Aug 2026 17:39:48 +0800 Subject: [PATCH 0807/1317] fbdev: atafb: Restrict SuperBlitter to supported formats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SuperBlitter operations derive an integer byte count per pixel. The accelerated fill path handles only one-, two- and four-byte pixels. However, the operations are currently installed for every external framebuffer in SuperVidel RAM, including planar 1/2/4/8-bpp and 24-bpp truecolor modes accepted by the external video parser. For 1/2/4-bpp modes, the byte count becomes zero, so accelerated copies do nothing and fills fall through to 32-bit stores. Planar 8-bpp uses an incompatible memory layout. For 24-bpp modes, fills also use 32-bit stores despite advancing addresses by three bytes per pixel. These cases can corrupt the framebuffer beyond the requested rectangle. Enable the SuperBlitter operations only for the layouts they implement: 8-bpp packed pixels and 16/32-bpp truecolor. Keep the existing software operations for all other external formats. Fixes: d463633d63e6 ("fbdev: atafb: Add support for SuperVidel's SuperBlitter") Signed-off-by: Linmao Li Tested-by: Miro Kropáček Reviewed-by: Michael Schmitz Signed-off-by: Helge Deller --- drivers/video/fbdev/atafb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/atafb.c b/drivers/video/fbdev/atafb.c index 5bca34c45cef..c3011b61a94b 100644 --- a/drivers/video/fbdev/atafb.c +++ b/drivers/video/fbdev/atafb.c @@ -3360,7 +3360,11 @@ static int __init atafb_probe(struct platform_device *pdev) memset (screen_base, 0, external_len); /* framebuffer in SV RAM: enable the SuperBlitter */ - if (external_addr >= 0xa0000000) { + if (external_addr >= 0xa0000000 && + ((external_pmode == FB_TYPE_PACKED_PIXELS && + external_depth == 8) || + (external_pmode == -1 && + (external_depth == 16 || external_depth == 32)))) { svblit_regs = ioremap(SVBLIT_REGS_PHYS, 0x100); if (svblit_regs) { svblit_fw = svblit_rd(SVBLIT_VERSION) & 0x1ff; From c4fa55f85c47cd5d54d717fb8170746edb10292e Mon Sep 17 00:00:00 2001 From: Mahmoud Nagy Adam Date: Wed, 9 Sep 2026 15:26:03 +0200 Subject: [PATCH 0808/1317] selftests: ublk: install test_common.sh and trace/ scripts Every ublk test script sources test_common.sh from its own directory: . "$(cd "$(dirname "$0")" && pwd)"/test_common.sh and test_generic_02/12 additionally run bpftrace against the scripts in trace/. Neither test_common.sh nor trace/ is listed in TEST_FILES, so "make install" does not copy them into the install directory and every ublk test fails when run from there: ./test_generic_02.sh: line 4: .../kselftest_install/ublk/test_common.sh: No such file or directory ./test_generic_02.sh: line 8: _have_program: command not found The bpftrace tests are affected even when bpftrace is installed: the missing trace/*.bt makes bpftrace exit immediately, and the tests then report a skip rather than a failure, which hides the problem. Add both to TEST_FILES, matching how other selftests ship their sourced helpers (see kexec/kexec_common_lib.sh and zram/zram_lib.sh). Fixes: 6aecda00b7d1e1 ("selftests: ublk: add kernel selftests for ublk") Fixes: 723977cab4c0fd ("selftests: ublk: add generic_01 for verifying sequential IO order") Cc: stable@vger.kernel.org # v6.15+ Assisted-by: Kiro:claude-opus-5 Signed-off-by: Mahmoud Nagy Adam Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260909132602.68852-2-mngyadam@amazon.de Signed-off-by: Jens Axboe --- tools/testing/selftests/ublk/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/ublk/Makefile b/tools/testing/selftests/ublk/Makefile index 5daf36c6c36c..37883e9d50ec 100644 --- a/tools/testing/selftests/ublk/Makefile +++ b/tools/testing/selftests/ublk/Makefile @@ -73,6 +73,8 @@ TEST_PROGS += test_stress_08.sh TEST_PROGS += test_stress_09.sh TEST_FILES := settings +TEST_FILES += test_common.sh +TEST_FILES += trace TEST_GEN_PROGS_EXTENDED = kublk metadata_size STANDALONE_UTILS := metadata_size.c From b0d8d56b7c93ed767eb4f2be9988e7b9dc023566 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 09:06:31 +0100 Subject: [PATCH 0809/1317] block: Fix start and length check added to iov_iter_extract_bvecs() Commit 14b007e17881 added an address check using iter_iov_addr() and a length check using iter_iov_len() to iov_iter_extract_bvecs(), but these cannot be used so and are unsafe in this circumstance as the functions have hardwired assumptions about the iterator type. They should only be used with ITER_UBUF or ITER_IOVEC-type iterators; they shouldn't be used with ITER_BVEC, ITER_KVEC, ITER_FOLIOQ, ITER_XARRAY or ITER_DISCARD iterators. This proves to be a problem for cachefiles as an iterator of type ITER_FOLIOQ is passed and iter_iov_addr() and iter_iov_len() both malfunction because iter->__iov in iter_iov() is not pointing to an iovec array. Fix this by using iov_iter_alignment() instead. Fixes: 14b007e17881 ("block: validate user space vectors during extraction") Signed-off-by: David Howells Reviewed-by: Keith Busch cc: Hannes Reinecke cc: Christoph Hellwig cc: Jens Axboe cc: Alexander Viro cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-block@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/1667275.1788941191@warthog.procyon.org.uk Signed-off-by: Jens Axboe --- lib/iov_iter.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 6665372ecf71..2072c04e99d0 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -1921,15 +1921,29 @@ ssize_t iov_iter_extract_bvecs(struct iov_iter *iter, struct bio_vec *bv, unsigned short max_vecs, unsigned mem_align_mask, iov_iter_extraction_t extraction_flags) { - unsigned long start = (unsigned long)iter_iov_addr(iter); unsigned short entries_left = max_vecs - *nr_vecs; unsigned short nr_pages, i = 0; size_t left, offset, len; struct page **pages; ssize_t size; - if ((start | iter_iov_len(iter)) & mem_align_mask) + /* + * DMA engines typically have both memory address and length alignment + * requirements, so check these against the alignment mask. For UBUF, + * IOVEC and KVEC, only the current segment will be extracted from; for + * everything else we might extract from multiple segments, so we need + * to check those too. + */ + if (likely(iter_is_ubuf(iter) || + iter_is_iovec(iter) || + iov_iter_is_kvec(iter))) { + unsigned long start = (unsigned long)iter_iov_addr(iter); + + if ((start | iter_iov_len(iter)) & mem_align_mask) + return -EINVAL; + } else if (iov_iter_alignment(iter) & mem_align_mask) { return -EINVAL; + } /* * Move page array up in the allocated memory for the bio vecs as far as From 0c6da21fa35e03fc74f09895433ccd6d4a9c3530 Mon Sep 17 00:00:00 2001 From: Stian Halseth Date: Tue, 1 Sep 2026 19:39:45 +0200 Subject: [PATCH 0810/1317] sunvdc: unmap LDC cookies when the descriptor send fails __send_request() maps the request's pages into the LDC channel's map table (ldc_map_sg()), fills in the descriptor and marks it VIO_DESC_READY before ringing the doorbell via __vdc_tx_trigger(). When the trigger fails, the error path only prints a message: the descriptor stays READY and the cookies are never unmapped. The mapping is normally released in vdc_end_one() when the peer completes the descriptor - but a descriptor whose doorbell was never sent will never complete, and since dr->prod is not advanced on failure, the reset path (vdc_requeue_inflight(), which walks [cons, prod)) never visits it either. The map table entries are leaked permanently. Since commit a11f6ca9aef9 ("sunvdc: Do not spin in an infinite loop when vio_ldc_send() returns EAGAIN") trigger failures occur in practice under load, so every resulting I/O error also leaks one request's worth of entries from the fixed-size (8192 entries per channel) map table. Because the allocator hands out contiguous ranges, fragmentation makes large multi-segment requests fail first as the table drains, until ldc_map_sg() fails permanently and the disk is dead until reboot. It also makes any retry-based recovery unusable: requeuing the request on -EAGAIN remaps the pages on every attempt, overwriting desc->cookies and orphaning the previous mapping, so the table drains at the retry rate. This is the memory exhaustion observed when the requeue approach was first tested in October 2025. Roll back on failure: unmap the cookies, mark the descriptor FREE again and clear the request entry. If the trigger failed with -ENOTCONN, __vdc_tx_trigger() has already reset the port, which tears down and reallocates both the dring and the LDC channel including its map table - nothing to roll back, and the stale descriptor must not be touched. Fixes: a11f6ca9aef9 ("sunvdc: Do not spin in an infinite loop when vio_ldc_send() returns EAGAIN") Reported-by: John Paul Adrian Glaubitz Link: https://github.com/sparclinux/issues/issues/2 Signed-off-by: Stian Halseth Link: https://patch.msgid.link/20260901173947.3292110-2-stian@itx.no Signed-off-by: Jens Axboe --- drivers/block/sunvdc.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c index 020bd9f1a7b6..24ad56536ed6 100644 --- a/drivers/block/sunvdc.c +++ b/drivers/block/sunvdc.c @@ -525,6 +525,23 @@ static int __send_request(struct request *req) err = __vdc_tx_trigger(port); if (err < 0) { printk(KERN_ERR PFX "vdc_tx_trigger() failure, err=%d\n", err); + /* + * If the port was reset (-ENOTCONN), the dring and the + * LDC channel including all of its mappings are already + * torn down and reallocated - there is nothing to undo + * and @desc must not be touched. + * + * For any other failure the descriptor was never handed + * to the peer: unmap the cookies and free the descriptor + * again, so that a later retry of the request does not + * leak LDC map table entries. + */ + if (err != -ENOTCONN) { + ldc_unmap(port->vio.lp, desc->cookies, + desc->ncookies); + desc->hdr.state = VIO_DESC_FREE; + rqe->req = NULL; + } } else { port->req_id++; dr->prod = vio_dring_next(dr, dr->prod); From 5067d4ba713961d8ccea1e06cd4c453793f3121e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 1 Sep 2026 19:39:46 +0200 Subject: [PATCH 0811/1317] sunvdc: fix -EIO issue due to lack of retries John reports that since commit: a11f6ca9aef9 ("sunvdc: Do not spin in an infinite loop when vio_ldc_send() returns EAGAIN") users of Linux inside Solaris ldom see occasional -EIO errors because the request send loop now times out. The current loop does 10 retries, and inside vio_ldc_send() a further 1000 1usec retries are done as well. Even with 10.5 msec of busy loop retries that's apparently not enough to always succeed. Rather than introduce continued busy looping, requeue the request and have the delayed queue kicking retry the request after another 10ms. This obviously isn't ideal, but there's seemingly no way to wait for this type of event. And if 10ms of busy looping was not enough to make progress, then presumably this is an edge condition and we just need to guarantee to make forward progress at some later point in time. That's more suitably done through letting the CPU tend to other work, rather than sitting in a tight loop retrying. [stian: rebased on top of the cookie-unmap fix, without which every requeued attempt leaks LDC map table entries; tested on an UltraSPARC T4 LDOM where the vdc_tx_trigger failure condition was reproduced and absorbed by the requeue with no I/O error] Reported-by: John Paul Adrian Glaubitz Link: https://lore.kernel.org/all/20251006100226.4246-2-glaubitz@physik.fu-berlin.de/ Link: https://lore.kernel.org/all/418310b3-2b77-4534-b2fd-27dcc11e333c@kernel.dk/ Signed-off-by: Stian Halseth Link: https://patch.msgid.link/20260901173947.3292110-3-stian@itx.no Signed-off-by: Jens Axboe --- drivers/block/sunvdc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c index 24ad56536ed6..2be8231dcd5b 100644 --- a/drivers/block/sunvdc.c +++ b/drivers/block/sunvdc.c @@ -556,6 +556,7 @@ static blk_status_t vdc_queue_rq(struct blk_mq_hw_ctx *hctx, struct vdc_port *port = hctx->queue->queuedata; struct vio_dring_state *dr; unsigned long flags; + int ret; dr = &port->vio.drings[VIO_DRIVER_TX_RING]; @@ -577,7 +578,13 @@ static blk_status_t vdc_queue_rq(struct blk_mq_hw_ctx *hctx, return BLK_STS_DEV_RESOURCE; } - if (__send_request(bd->rq) < 0) { + ret = __send_request(bd->rq); + if (ret == -EAGAIN) { + spin_unlock_irqrestore(&port->vio.lock, flags); + /* already spun for 10msec, defer 10msec and retry */ + blk_mq_delay_kick_requeue_list(hctx->queue, 10); + return BLK_STS_DEV_RESOURCE; + } else if (ret < 0) { spin_unlock_irqrestore(&port->vio.lock, flags); return BLK_STS_IOERR; } From a0a34a40ed299c9c7cff6af163a5b883ee9d6d73 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Thu, 10 Sep 2026 03:10:23 +0800 Subject: [PATCH 0812/1317] fbdev: vfb: defer cleanup until the last reference FBIOGETCMAP takes a shallow snapshot of info->cmap and performs the usercopy after dropping info->lock. vfb_remove() frees the colormap immediately after unregistering the framebuffer, even when an open file still holds a reference to fb_info. A concurrent driver unbind can therefore free the colormap while the ioctl copies it to userspace. KASAN reports: BUG: KASAN: slab-use-after-free in _copy_to_user Read of size 512 by task poc/125 _copy_to_user (./include/linux/instrumented.h:129 ./include/linux/uaccess.h:201 lib/usercopy.c:24) fb_cmap_to_user (./include/linux/uaccess.h:230 drivers/video/fbdev/core/fbcmap.c:211) do_fb_ioctl (drivers/video/fbdev/core/fb_chrdev.c:114) Allocated by task 1: fb_alloc_cmap_gfp (./include/linux/slab.h:973 ./include/linux/slab.h:1290 drivers/video/fbdev/core/fbcmap.c:108) vfb_probe (drivers/video/fbdev/vfb.c:459) Freed by task 124: fb_dealloc_cmap (drivers/video/fbdev/core/fbcmap.c:151) vfb_remove (drivers/video/fbdev/vfb.c:489) unregister_framebuffer() drops the registration reference, and fbdev calls fb_destroy after the last put_fb_info(). Move the registered framebuffer's cleanup into an fb_destroy callback so its colormap and screen buffer stay alive until all file references have been released. Fixes: 5e266e2e0e19 ("vfb: fix memory leaks in removal path") Reported-by: co+c25629c98ba36ebe@bugs.sh Cc: stable@kernel.org Closes: https://lore.kernel.org/linux-fbdev/f2Kf9GYn1lKR5S1dbvGVtykMxK1RlgP5z8sW@bugs.sh/ Assisted-by: Codex:gpt-5 Signed-off-by: Weiming Shi Link: https://lore.kernel.org/linux-fbdev/f2Kf9GYn1lKR5S1dbvGVtykMxK1RlgP5z8sW@bugs.sh/ Signed-off-by: Helge Deller --- drivers/video/fbdev/vfb.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/vfb.c b/drivers/video/fbdev/vfb.c index 5b7965f36c5e..de137b2bdaed 100644 --- a/drivers/video/fbdev/vfb.c +++ b/drivers/video/fbdev/vfb.c @@ -78,6 +78,13 @@ static int vfb_pan_display(struct fb_var_screeninfo *var, static int vfb_mmap(struct fb_info *info, struct vm_area_struct *vma); +static void vfb_destroy(struct fb_info *info) +{ + vfree(info->screen_buffer); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + static const struct fb_ops vfb_ops = { .owner = THIS_MODULE, __FB_DEFAULT_SYSMEM_OPS_RDWR, @@ -87,6 +94,7 @@ static const struct fb_ops vfb_ops = { .fb_pan_display = vfb_pan_display, __FB_DEFAULT_SYSMEM_OPS_DRAW, .fb_mmap = vfb_mmap, + .fb_destroy = vfb_destroy, }; /* @@ -485,9 +493,6 @@ static void vfb_remove(struct platform_device *dev) if (info) { unregister_framebuffer(info); - vfree(videomemory); - fb_dealloc_cmap(&info->cmap); - framebuffer_release(info); } } From 88ce88e933e40f4ab4b81a5e9d0a10328583f593 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:07 +0200 Subject: [PATCH 0813/1317] bpf: Add KF_PERFMON kfunc flag Tracing related BPF helpers e.g. under bpf_base_func_proto() are gated behind CAP_PERFMON. However, the same is currently not true for kfuncs and they are accessible via plain CAP_BPF. Add a new KF_PERFMON flag which can be used such that check_kfunc_call() ensures env->allow_ptr_leaks is permitted. This follows similar pattern to existing KF_DESTRUCTIVE flag. The rejection returns -EPERM to match the other CAP_PERFMON gates in the verifier, that is, check_ptr_to_btf_access() and check_ptr_to_map_access(), which report the very same policy to user space. Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- Documentation/bpf/kfuncs.rst | 10 ++++++++++ include/linux/btf.h | 1 + kernel/bpf/verifier.c | 14 ++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst index 85f73e0bbd0f..6691fe8a32c3 100644 --- a/Documentation/bpf/kfuncs.rst +++ b/Documentation/bpf/kfuncs.rst @@ -486,6 +486,16 @@ Example usage in BPF program: /* note that the last argument is omitted */ bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback); +2.5.10 KF_PERFMON flag +---------------------- + +The KF_PERFMON flag is used for kfuncs that can expose kernel memory or kernel +addresses to the BPF program, for example by reading through a pointer that the +verifier does not check. Calling such a kfunc requires CAP_PERFMON, or +CAP_SYS_ADMIN, in the same way that the equivalent BPF helpers are gated in +bpf_base_func_proto(). A program loaded with CAP_BPF alone is rejected at load +time. + 2.6 Registering the kfuncs -------------------------- diff --git a/include/linux/btf.h b/include/linux/btf.h index 89d5a5c4f117..7c62ea17b116 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -80,6 +80,7 @@ #define KF_ARENA_ARG2 (1 << 15) /* kfunc takes an arena pointer as its second argument */ #define KF_IMPLICIT_ARGS (1 << 16) /* kfunc has implicit arguments supplied by the verifier */ #define KF_SPINLOCK_SAFE (1 << 17) /* kfunc is allowed inside bpf_spin_lock-ed region */ +#define KF_PERFMON (1 << 18) /* kfunc requires CAP_PERFMON */ /* * Tag marking a kernel function as a kfunc. This is meant to minimize the diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 72a3f5998dd2..939e535a3442 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11356,6 +11356,11 @@ static bool is_kfunc_destructive(struct bpf_call_arg_meta *meta) return meta->kfunc_flags & KF_DESTRUCTIVE; } +static bool is_kfunc_perfmon(struct bpf_call_arg_meta *meta) +{ + return meta->kfunc_flags & KF_PERFMON; +} + static bool is_kfunc_rcu(struct bpf_call_arg_meta *meta) { return meta->kfunc_flags & KF_RCU; @@ -13834,6 +13839,15 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, return -EACCES; } + if (is_kfunc_perfmon(&meta) && !env->allow_ptr_leaks) { + verbose(env, "%s is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n", + func_name); + operation = bpf_diag_fmt(env, "kfunc %s", func_name); + bpf_diag_policy(env, insn_idx, operation, "the kfunc requires CAP_PERFMON", + "Load the program with CAP_PERFMON, or avoid the kfunc."); + return -EPERM; + } + sleepable = bpf_is_kfunc_sleepable(&meta); if (sleepable && !in_sleepable(env)) { verbose(env, "program must be sleepable to call sleepable kfunc %s\n", func_name); From 81c975aae375d2053fa2926ee3730f02e713140c Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:08 +0200 Subject: [PATCH 0814/1317] bpf: Require CAP_PERFMON for kfuncs reading memory Mark fault-safe probe reading kfuncs as KF_PERFMON, similarly as we do for the old-style BPF helper equivalents. bpf_rdonly_cast() is included in this list as well as it returns PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED for an unchecked object and is using fault-safe BPF_PROBE_MEM. Note that only the void form of bpf_rdonly_cast() produced a register that was readable without CAP_PERFMON. For a struct type id the kfunc returns PTR_TO_BTF_ID | PTR_UNTRUSTED, whose dereference has always been gated in check_ptr_to_btf_access(). The flag is not conditional on the type id, so for the latter it only moves the rejection from the dereference to the call itself, which is the better place to report it anyway. The bpf_stream_vprintk() and bpf_stream_print_stack() kfuncs are marked as well. The former ends up in the same bpf_bprintf_prepare() as the bpf_snprintf() helper, where %pks, %pus and %pI4 read through a program- supplied address and %pB resolves one into a symbol. The latter walks the stack and prints each instruction pointer via %pS. Lastly, bpf_get_kmem_cache() takes a raw scalar address that the verifier does not constrain and dereferences the page/slab metadata for it. Field reads on the returned pointer are still blocked (PTR_TO_BTF_ID | PTR_UNTRUSTED -> check_ptr_to_btf_access() results in -EPERM without the CAP), but the NULL/non-NULL result is observable. Reported-by: STAR Labs SG Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-2-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/helpers.c | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index b3cc5c8fc875..712dca5a2c5b 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -4883,7 +4883,7 @@ BTF_ID(func, bpf_cgroup_release_dtor) BTF_KFUNCS_START(common_btf_ids) BTF_ID_FLAGS(func, bpf_cast_to_kern_ctx, KF_FASTCALL) -BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL) +BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL | KF_PERFMON) BTF_ID_FLAGS(func, bpf_rcu_read_lock) BTF_ID_FLAGS(func, bpf_rcu_read_unlock) BTF_ID_FLAGS(func, bpf_dynptr_slice, KF_RET_NULL) @@ -4920,26 +4920,26 @@ BTF_ID_FLAGS(func, bpf_wq_set_callback, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_wq_start) BTF_ID_FLAGS(func, bpf_preempt_disable) BTF_ID_FLAGS(func, bpf_preempt_enable) -BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW) +BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW | KF_PERFMON) BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY) -BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_get_kmem_cache) +BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_get_kmem_cache, KF_PERFMON) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_local_irq_save) BTF_ID_FLAGS(func, bpf_local_irq_restore) #ifdef CONFIG_BPF_EVENTS -BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr) -BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE | KF_PERFMON) #endif #ifdef CONFIG_DMA_SHARED_BUFFER BTF_ID_FLAGS(func, bpf_iter_dmabuf_new, KF_ITER_NEW | KF_SLEEPABLE) @@ -4947,26 +4947,26 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPAB BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) #endif BTF_ID_FLAGS(func, __bpf_trap) -BTF_ID_FLAGS(func, bpf_strcmp); -BTF_ID_FLAGS(func, bpf_strcasecmp); -BTF_ID_FLAGS(func, bpf_strncasecmp); -BTF_ID_FLAGS(func, bpf_strchr); -BTF_ID_FLAGS(func, bpf_strchrnul); -BTF_ID_FLAGS(func, bpf_strnchr); -BTF_ID_FLAGS(func, bpf_strrchr); -BTF_ID_FLAGS(func, bpf_strlen); -BTF_ID_FLAGS(func, bpf_strnlen); -BTF_ID_FLAGS(func, bpf_strspn); -BTF_ID_FLAGS(func, bpf_strcspn); -BTF_ID_FLAGS(func, bpf_strstr); -BTF_ID_FLAGS(func, bpf_strcasestr); -BTF_ID_FLAGS(func, bpf_strnstr); -BTF_ID_FLAGS(func, bpf_strncasestr); +BTF_ID_FLAGS(func, bpf_strcmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcasecmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strncasecmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strchrnul, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strrchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strlen, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnlen, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strspn, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcspn, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strstr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcasestr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnstr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strncasestr, KF_PERFMON); #if defined(CONFIG_BPF_LSM) && defined(CONFIG_CGROUPS) BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU) #endif -BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE) -BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE) +BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON) BTF_ID_FLAGS(func, bpf_task_work_schedule_signal, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_task_work_schedule_resume, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_dynptr_from_file) From f9191460cd805e84eace0884c75c67ec719fa1d8 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:09 +0200 Subject: [PATCH 0815/1317] bpf: Require CAP_PERFMON for untrusted read-only memory reads Marking bpf_rdonly_cast() KF_PERFMON CAP-limits one producer of PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED, but not the type itself. A global subprogram argument tagged __arg_untrusted results in the same register with no kfunc call. Reported-by: STAR Labs SG Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-3-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 939e535a3442..8058f684a9ea 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -6453,6 +6453,15 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b return -EACCES; } + if (rdonly_untrusted && !env->allow_ptr_leaks) { + verbose(env, "%s access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n", + reg_type_str(env, reg->type)); + bpf_diag_policy(env, insn_idx, "read from untrusted read-only memory", + "the access requires CAP_PERFMON", + "Load the program with CAP_PERFMON, or avoid dereferencing untrusted pointers."); + return -EPERM; + } + /* * Accesses to untrusted PTR_TO_MEM are done through probe * instructions, hence no need to check bounds in that case. From a903f145a8914d9ecba009b398d135154e4a5d94 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:10 +0200 Subject: [PATCH 0816/1317] selftests/bpf: Add tests for the KF_PERFMON gates Add test cases where each one loads with CAP_BPF alone and checks that the program is correctly rejected. # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_kfunc_perfmon [...] #627/1 verifier_kfunc_perfmon/rdonly_cast_noperfmon:OK #627/2 verifier_kfunc_perfmon/rdonly_cast_noperfmon @unpriv:OK #627/3 verifier_kfunc_perfmon/probe_read_kernel_dynptr_noperfmon:OK #627/4 verifier_kfunc_perfmon/probe_read_kernel_dynptr_noperfmon @unpriv:OK #627/5 verifier_kfunc_perfmon/stream_vprintk_noperfmon:OK #627/6 verifier_kfunc_perfmon/stream_vprintk_noperfmon @unpriv:OK #627/7 verifier_kfunc_perfmon/get_kmem_cache_noperfmon:OK #627/8 verifier_kfunc_perfmon/get_kmem_cache_noperfmon @unpriv:OK #627/9 verifier_kfunc_perfmon/arg_untrusted_read_noperfmon:OK #627/10 verifier_kfunc_perfmon/arg_untrusted_read_noperfmon @unpriv:OK #627 verifier_kfunc_perfmon:OK Summary: 1/10 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-4-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/verifier.c | 2 + .../bpf/progs/verifier_kfunc_perfmon.c | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 64ac49ad67e6..ec9e3907c75d 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -53,6 +53,7 @@ #include "verifier_iterating_callbacks.skel.h" #include "verifier_jeq_infer_not_null.skel.h" #include "verifier_jit_convergence.skel.h" +#include "verifier_kfunc_perfmon.skel.h" #include "verifier_ld_ind.skel.h" #include "verifier_ldsx.skel.h" #include "verifier_leak_ptr.skel.h" @@ -216,6 +217,7 @@ void test_verifier_int_ptr(void) { RUN(verifier_int_ptr); } void test_verifier_iterating_callbacks(void) { RUN(verifier_iterating_callbacks); } void test_verifier_jeq_infer_not_null(void) { RUN(verifier_jeq_infer_not_null); } void test_verifier_jit_convergence(void) { RUN(verifier_jit_convergence); } +void test_verifier_kfunc_perfmon(void) { RUN(verifier_kfunc_perfmon); } void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); } void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); } void test_verifier_ldsx(void) { RUN(verifier_ldsx); } diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c new file mode 100644 index 000000000000..76c39ef30e96 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include "bpf_misc.h" + +void *user_ptr; +char dynptr_buf[8]; +u64 kaddr; + +extern struct kmem_cache *bpf_get_kmem_cache(u64 addr) __ksym; + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_rdonly_cast is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int rdonly_cast_noperfmon(void *ctx) +{ + char *p = bpf_rdonly_cast(0, 0); + + return p[0x7fff]; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_probe_read_kernel_dynptr is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int probe_read_kernel_dynptr_noperfmon(void *ctx) +{ + struct bpf_dynptr dptr; + + bpf_dynptr_from_mem(dynptr_buf, sizeof(dynptr_buf), 0, &dptr); + bpf_probe_read_kernel_dynptr(&dptr, 0, sizeof(dynptr_buf), user_ptr); + return 0; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_stream_vprintk is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int stream_vprintk_noperfmon(void *ctx) +{ + bpf_stream_printk(BPF_STDOUT, "%pB", (void *)kaddr); + return 0; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_get_kmem_cache is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int get_kmem_cache_noperfmon(void *ctx) +{ + return !!bpf_get_kmem_cache(kaddr); +} + +__weak int subprog_untrusted_read(void *p __arg_untrusted) +{ + return *(char *)p; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("rdonly_untrusted_mem access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int arg_untrusted_read_noperfmon(void *ctx) +{ + return subprog_untrusted_read(0); +} + +char _license[] SEC("license") = "GPL"; From 2936aed9b02162df7fbe08fcd6bfbb3270ad9f95 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 20:56:04 +0200 Subject: [PATCH 0817/1317] bpf: Clear scalar delta on narrowing stack spill check_stack_write_fixed_off() breaks the scalar link on a narrowing spill by zeroing the id of the destination stack slot, but leaves the delta in place. save_register_state() has just copied the source register verbatim, so the slot keeps the BPF_ADD_CONST delta of a register it is no longer linked to. This is the same shape as the case addressed in 1b327732c846 ("bpf: Clear delta when clearing reg id for non-{add,sub} ops"). Unlike the latter, no miscomputation seems reachable, so mainly consistency. Signed-off-by: Daniel Borkmann Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260910185604.44949-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 8058f684a9ea..1b0b1fb62878 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3582,7 +3582,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, save_register_state(env, state, spi, reg, size); /* Break the relation on a narrowing spill. */ if (!reg_value_fits) - state->stack[spi].spilled_ptr.id = 0; + clear_scalar_id(&state->stack[spi].spilled_ptr); } else if (!reg && !(off % BPF_REG_SIZE) && is_bpf_st_mem(insn) && env->bpf_capable) { struct bpf_reg_state *tmp_reg = &env->fake_reg[0]; From b0b3dc66529676228cb938cbcad66920f735c223 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 20:22:55 +0800 Subject: [PATCH 0818/1317] bpf: Fix divide-by-zero in btf_struct_walk() When an access goes past the struct and the last member is a flexible array, btf_struct_walk() folds the offset back into a single element with (off - moff) % t->size, but never checks that the element type has a size. BTF takes an empty struct, so this in program BTF /* event could be empty */ struct event { #ifdef HAVE_TIMESTAMP __u64 ts; #endif }; struct batch { int nr; struct event events[]; }; divides by zero at prog load time. Getting there needs a PTR_TO_BTF_ID that is not MEM_ALLOC, e.g. a plain read of a local kptr stashed in a map from a sleepable program. Oops: divide error: 0000 [#1] SMP KASAN PTI RIP: 0010:btf_struct_walk+0x53f/0x1570 Call Trace: btf_struct_access+0x42a/0xcd0 check_ptr_to_btf_access+0x4dc/0x1160 check_mem_access+0x3a45/0x8740 check_load_mem+0x36a/0xd10 do_check_common+0x3ef0/0xb210 bpf_check+0x6d3b/0x8580 bpf_prog_load+0xf7c/0x2720 __sys_bpf+0xa83/0x3690 __x64_sys_bpf+0xc7/0x150 x64_sys_call+0x1f3f/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e Reject a zero-sized element type. The fixed array path in the same function already bails out on the same thing: btf_struct_walk() ... /* skip empty array */ if (moff == mtrue_end) continue; msize /= total_nelems; Fixes: 9c5f8a1008a1 ("bpf: Support variable length array in tracing programs") Signed-off-by: Jiayuan Chen Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260910122316.186384-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- kernel/bpf/btf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 9f33e95d5741..d67a169ba2bb 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -7187,7 +7187,7 @@ static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf, if (btf_type_is_int(t)) return WALK_SCALAR; - if (!btf_type_is_struct(t)) + if (!btf_type_is_struct(t) || !t->size) goto error; off = (off - moff) % t->size; From f77d21245710690f3fb02d19d8dd4dc17ee58c2c Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 20:22:56 +0800 Subject: [PATCH 0819/1317] selftests/bpf: Test BTF walk into a flexible array of zero-sized elements The program stashes a bpf_obj_new() object whose type ends with a flexible array of empty structs, then reads it back as an untrusted kptr. Without the previous patch this divides by zero in btf_struct_walk() instead of being rejected. # ./test_progs -t verifier_btf_flex_array ... #602 verifier_btf_flex_array:OK Summary: 1/1 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910122316.186384-2-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/verifier.c | 2 + .../bpf/progs/verifier_btf_flex_array.c | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index ec9e3907c75d..8b439e194bcc 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -23,6 +23,7 @@ #include "verifier_bpf_trap.skel.h" #include "verifier_bswap.skel.h" #include "verifier_btf_ctx_access.skel.h" +#include "verifier_btf_flex_array.skel.h" #include "verifier_btf_unreliable_prog.skel.h" #include "verifier_call_large_imm.skel.h" #include "verifier_cfg.skel.h" @@ -187,6 +188,7 @@ void test_verifier_bpf_get_stack(void) { RUN(verifier_bpf_get_stack); } void test_verifier_bpf_trap(void) { RUN(verifier_bpf_trap); } void test_verifier_bswap(void) { RUN(verifier_bswap); } void test_verifier_btf_ctx_access(void) { RUN(verifier_btf_ctx_access); } +void test_verifier_btf_flex_array(void) { RUN(verifier_btf_flex_array); } void test_verifier_btf_unreliable_prog(void) { RUN(verifier_btf_unreliable_prog); } void test_verifier_call_large_imm(void) { RUN(verifier_call_large_imm); } void test_verifier_cfg(void) { RUN(verifier_cfg); } diff --git a/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c new file mode 100644 index 000000000000..59b84261f622 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include "bpf_experimental.h" +#include "bpf_misc.h" + +struct test_empty_event {}; + +struct test_flex_batch { + int nr; + struct test_empty_event events[]; +}; + +struct map_value { + struct test_flex_batch __kptr *batch; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct map_value); + __uint(max_entries, 1); +} batches SEC(".maps"); + +SEC("syscall") +__description("btf walk into flexible array of zero-sized elements") +__failure __msg("access beyond struct test_flex_batch at off 4 size 1") +int stash_and_peek(void *ctx) +{ + struct test_flex_batch *b, *old; + struct map_value *v; + int key = 0; + + v = bpf_map_lookup_elem(&batches, &key); + if (!v) + return 0; + + b = bpf_obj_new(struct test_flex_batch); + if (!b) + return 0; + b->nr = 1; + + old = bpf_kptr_xchg(&v->batch, b); + if (old) + bpf_obj_drop(old); + + b = v->batch; + if (!b) + return 0; + + return b->nr + *(char *)&b->events[0]; +} + +char _license[] SEC("license") = "GPL"; From 01b245ba016d44861690594e10f67e026ce8552f Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:26:26 +0800 Subject: [PATCH 0820/1317] bpf: Fix out-of-bounds read of sk_protocol in bpf_sock_destroy() sk_protocol lives in struct sock, not in struct sock_common. A timewait or request sock handed to bpf_sock_destroy() by the tcp iterator is neither, so reading sk->sk_protocol runs past the object: ================================================================== BUG: KASAN: slab-out-of-bounds in bpf_sock_destroy+0xc7/0xe0 Read of size 2 at addr ffff8881047d11b4 by task test_progs/428 Tainted: [W]=WARN Call Trace: dump_stack_lvl+0x91/0xf0 print_report+0xd1/0x630 kasan_report+0xf3/0x130 __asan_report_load2_noabort+0x14/0x30 bpf_sock_destroy+0xc7/0xe0 bpf_prog_c3dd61f9d9cd9f37_iter_tcp6_timewait+0x9f/0xb7 bpf_iter_run_prog+0x538/0xde0 bpf_iter_tcp_seq_show+0x26b/0x4b0 bpf_seq_read+0x424/0x1210 vfs_read+0x197/0xe40 ksys_read+0x119/0x240 __x64_sys_read+0x72/0xc0 x64_sys_call+0x647/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e Only check sk_protocol on full socks. tcp_abort() already knows how to deal with TIME_WAIT and NEW_SYN_RECV socks. Also fix the comment, it never matched the code. Fixes: 4ddbcb886268 ("bpf: Add bpf_sock_destroy kfunc") Reported-by: Xiang Mei (Microsoft) Closes: https://lore.kernel.org/bpf/20260702224519.800135-1-xmei5@asu.edu/ Signed-off-by: Jiayuan Chen Reviewed-by: Kuniyuki Iwashima Link: https://lore.kernel.org/r/20260910112634.152195-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 2a84f9d01131..cae43b999162 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -12913,8 +12913,9 @@ __bpf_kfunc_start_defs(); * @sock: Pointer to socket to be destroyed * * Return: - * On error, may return EPROTONOSUPPORT, EINVAL. - * EPROTONOSUPPORT if protocol specific destroy handler is not supported. + * On error, may return EOPNOTSUPP, or whatever the protocol specific + * destroy handler returns. + * EOPNOTSUPP if protocol specific destroy handler is not supported. * 0 otherwise */ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock) @@ -12926,8 +12927,12 @@ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock) * Supporting protocols will need to acquire sock lock in the BPF context * prior to invoking this kfunc. */ - if (!sk->sk_prot->diag_destroy || (sk->sk_protocol != IPPROTO_TCP && - sk->sk_protocol != IPPROTO_UDP)) + if (!sk->sk_prot->diag_destroy) + return -EOPNOTSUPP; + + if (sk_fullsock(sk) && + sk->sk_protocol != IPPROTO_TCP && + sk->sk_protocol != IPPROTO_UDP) return -EOPNOTSUPP; return sk->sk_prot->diag_destroy(sk, ECONNABORTED); From eaab8cab451b9502ce224cd202550375b894a467 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:27:28 +0800 Subject: [PATCH 0821/1317] tcp: Skip cond_resched() in inet_csk_listen_stop() under BPF context bpf_sock_destroy() runs from the tcp iterator, under rcu_read_lock(). If the sock is a listener that still has children in its accept queue, tcp_abort() ends up in inet_csk_listen_stop() and the cond_resched() there trips the debug check: BUG: sleeping function called from invalid context at net/ipv4/inet_connection_sock.c:1523 in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid: 628, name: test_progs preempt_count: 0, expected: 0 RCU nest depth: 1, expected: 0 locks held by test_progs/628: 3, last CPU#3: #0: ffff8881158cee18 (&p->lock){+.+.}-{4:4}, at: bpf_seq_read+0x56/0x1210 #1: ffff8881106bb858 (sk_lock-AF_INET6){+.+.}-{0:0}, at: bpf_iter_tcp_seq_show+0x32b/0x4b0 #2: ffffffffb435af20 (rcu_read_lock){....}-{1:3}, at: bpf_iter_run_prog+0x46b/0xde0 CPU: 3 UID: 0 PID: 628 Comm: test_progs Tainted: G W 7.2.0+ #65 PREEMPT Tainted: [W]=WARN Call Trace: dump_stack_lvl+0xc1/0xf0 dump_stack+0x10/0x20 __might_resched+0x3d2/0x610 inet_csk_listen_stop+0x7b/0xbf0 tcp_abort+0x23b/0x3b0 bpf_sock_destroy+0xfc/0x140 bpf_prog_448133d24601754f_iter_tcp6_server+0x81/0x8a bpf_iter_run_prog+0x538/0xde0 bpf_iter_tcp_seq_show+0x26b/0x4b0 bpf_seq_read+0x424/0x1210 vfs_read+0x197/0xe40 ksys_read+0x119/0x240 __x64_sys_read+0x72/0xc0 x64_sys_call+0x647/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e RIP: 0033:0x7fad39b28aca RSP: 002b:00007ffc381c61c0 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 RAX: ffffffffffffffda RBX: 00007ffc381c6a88 RCX: 00007fad39b28aca RDX: 0000000000000032 RSI: 00007ffc381c6250 RDI: 0000000000000014 RBP: 00007ffc381c61e0 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000003 R13: 0000000000000000 R14: 000055f077c1bbb0 R15: 00007fad3a0f3000 The commit that added the kfunc already guards lock_sock() in tcp_abort() and udp_abort() with has_current_bpf_ctx(), but missed the listener path. Do the same for the cond_resched(). The loop runs inside the iterator's rcu_read_lock(), it must not reschedule or report a quiescent state there. Fixes: 4ddbcb886268 ("bpf: Add bpf_sock_destroy kfunc") Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910112736.153710-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/ipv4/inet_connection_sock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 6257459bcee2..6a30f1138454 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -1520,7 +1520,8 @@ void inet_csk_listen_stop(struct sock *sk) local_bh_enable(); sock_put(child); - cond_resched(); + if (!has_current_bpf_ctx()) + cond_resched(); } if (queue->fastopenq.rskq_rst_head) { /* Free all the reqs queued in rskq_rst_head. */ From 8036d3a5a6589ef0721d15e7c03976bc3996f7a8 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:28:02 +0800 Subject: [PATCH 0822/1317] selftests/bpf: Test bpf_sock_destroy() on TIME_WAIT and listener socks Add two subtests. tcp_timewait: the client shuts down first and the server closes after it, so the client sock ends up in TIME_WAIT. A tcp iterator then finds the timewait sock by the cookie it inherited from the client sock and destroys it. Iterate once more to make sure it is gone. Without the first fix bpf_sock_destroy() reads past the timewait sock and KASAN complains. tcp_listen_pending: connect to a listener but never accept, so the child sits in the accept queue, then destroy the listener. Without the second fix the cond_resched() in inet_csk_listen_stop() trips the might_sleep check under rcu_read_lock(). ./test_progs -a sock_destroy #444/1 sock_destroy/tcp_client:OK #444/2 sock_destroy/tcp_server:OK #444/3 sock_destroy/tcp_listen_pending:OK #444/4 sock_destroy/tcp_timewait:OK #444/5 sock_destroy/udp_client:OK #444/6 sock_destroy/udp_server:OK #444/7 sock_destroy/trace_tcp_destroy_sock:OK #444 sock_destroy:OK Summary: 1/7 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910112810.153801-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/sock_destroy.c | 121 ++++++++++++++++++ .../selftests/bpf/progs/sock_destroy_prog.c | 30 +++++ 2 files changed, 151 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c index 9c11938fe597..78d642a02bdb 100644 --- a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c +++ b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include @@ -110,6 +111,122 @@ static void test_tcp_server(struct sock_destroy_prog *skel) close(serv); } +static void test_tcp_listen_pending(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n, serv_port; + struct pollfd pfd = { .events = POLLIN }; + char buf[1]; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + serv_port = get_socket_local_port(serv); + if (!ASSERT_GE(serv_port, 0, "get_sock_local_port")) + goto cleanup; + skel->bss->serv_port = (__be16)serv_port; + + /* + * Connect but never accept, so the child sits in the accept queue + * of the listener. Wait until it's actually there. + */ + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + pfd.fd = serv; + if (!ASSERT_EQ(poll(&pfd, 1, -1), 1, "poll listener")) + goto cleanup; + + /* Run iterator program that destroys server sockets. */ + start_iter_sockets(skel->progs.iter_tcp6_server); + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_LT(accept_serv, 0, "accept on destroyed listener")) + goto cleanup; + ASSERT_EQ(errno, EINVAL, "error code on destroyed listener"); + + /* The unaccepted child was reset along with the listener. */ + n = recv(clien, buf, sizeof(buf), 0); + if (!ASSERT_LT(n, 0, "client recv on reset child")) + goto cleanup; + ASSERT_EQ(errno, ECONNRESET, "error code on reset child"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + +static void test_tcp_timewait(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n; + struct timeval tv = {}; + char buf[1]; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_GE(accept_serv, 0, "serv accept")) + goto cleanup; + + /* + * Active close from the client, then close the server side. Once + * recv() sees EOF the server FIN has been processed and the client + * sock is in TIME_WAIT. Block without timeout so a loaded CI box + * can't race us. + */ + if (!ASSERT_OK(setsockopt(clien, SOL_SOCKET, SO_RCVTIMEO, &tv, + sizeof(tv)), "clear rcvtimeo")) + goto cleanup; + if (!ASSERT_OK(shutdown(clien, SHUT_WR), "client shutdown")) + goto cleanup; + + /* + * Make sure the server has seen the client FIN before it closes, + * so the two FINs never cross. + */ + n = recv(accept_serv, buf, sizeof(buf), 0); + if (!ASSERT_EQ(n, 0, "server recv EOF")) + goto cleanup; + + close(accept_serv); + accept_serv = -1; + + /* block until return EOF */ + n = recv(clien, buf, sizeof(buf), 0); + if (!ASSERT_EQ(n, 0, "client recv EOF")) + goto cleanup; + + /* Run iterator program that destroys the timewait client sock. */ + skel->bss->tw_found = 0; + start_iter_sockets(skel->progs.iter_tcp6_timewait); + if (!ASSERT_EQ(skel->bss->tw_found, 1, "timewait sock found")) + goto cleanup; + + ASSERT_OK(skel->bss->tw_destroy_err, "destroy timewait sock"); + + /* The destroyed timewait sock must be gone. */ + skel->bss->tw_found = 0; + start_iter_sockets(skel->progs.iter_tcp6_timewait); + ASSERT_EQ(skel->bss->tw_found, 0, "timewait sock destroyed"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + static void test_udp_client(struct sock_destroy_prog *skel) { int serv = -1, clien = -1, n = 0; @@ -204,6 +321,10 @@ void test_sock_destroy(void) test_tcp_client(skel); if (test__start_subtest("tcp_server")) test_tcp_server(skel); + if (test__start_subtest("tcp_listen_pending")) + test_tcp_listen_pending(skel); + if (test__start_subtest("tcp_timewait")) + test_tcp_timewait(skel); if (test__start_subtest("udp_client")) test_udp_client(skel); if (test__start_subtest("udp_server")) diff --git a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c index 9e0bf7a54cec..0a8887543218 100644 --- a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c +++ b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c @@ -7,6 +7,8 @@ #include "bpf_tracing_net.h" __be16 serv_port = 0; +int tw_found = 0; +int tw_destroy_err = 0; int bpf_sock_destroy(struct sock_common *sk) __ksym; @@ -100,6 +102,34 @@ int iter_tcp6_server(struct bpf_iter__tcp *ctx) return 0; } +SEC("iter/tcp") +int iter_tcp6_timewait(struct bpf_iter__tcp *ctx) +{ + struct sock_common *sk_common = ctx->sk_common; + __u64 *val; + int key = 0; + + if (!sk_common) + return 0; + + if (sk_common->skc_family != AF_INET6) + return 0; + + if (!bpf_skc_to_tcp_timewait_sock(sk_common)) + return 0; + + val = bpf_map_lookup_elem(&tcp_conn_sockets, &key); + if (!val) + return 0; + /* The timewait sock inherits the cookie of the closed client sock. */ + if (bpf_get_socket_cookie(sk_common) != *val) + return 0; + + tw_found++; + tw_destroy_err = bpf_sock_destroy(sk_common); + + return 0; +} SEC("iter/udp") int iter_udp6_client(struct bpf_iter__udp *ctx) From 27600805e62f800bacf990354632eae4e487d34c Mon Sep 17 00:00:00 2001 From: Yazen Ghannam Date: Thu, 3 Sep 2026 10:43:25 -0500 Subject: [PATCH 0823/1317] x86/amd_node: Fix PCI device reference counting in amd_smn_init() The local "root" pointer is a temporary variable used during the device search. Therefore, refcount related to the search iterators should be cleaned up after the search is complete. Use the __free() cleanup macro to ensure the refcount is decremented when the temporary pointer goes out of scope. Additionally, increment the refcount when caching a root pointer. This ensures the in-use refcount is separate from the temporary search refcounting. Finally, drop the redundant "root = NULL" before the second search loop. The pci_get_class() iterator always decrements the refcount of its "from" argument, so the first loop can only fall through with "root" already NULL. Fixes: 0a4b61d9c2e4 ("x86/amd_node: Fix AMD root device caching") Closes: https://sashiko.dev/#/patchset/20260806160159.230453-1-jason.andryuk%40amd.com Reported-by: Sashiko Assisted-by: LLM Signed-off-by: Yazen Ghannam Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Mario Limonciello (AMD) Cc: Link: https://patch.msgid.link/20260903154325.74343-1-yazen.ghannam@amd.com --- arch/x86/kernel/amd_node.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c index 762585775b5a..b7926ba3610a 100644 --- a/arch/x86/kernel/amd_node.c +++ b/arch/x86/kernel/amd_node.c @@ -251,7 +251,7 @@ __setup("amd_smn_debugfs_enable", amd_smn_enable_dfs); static int __init amd_smn_init(void) { u16 count, num_roots, roots_per_node, node, num_nodes; - struct pci_dev *root; + struct pci_dev *root __free(pci_dev_put) = NULL; if (!cpu_feature_enabled(X86_FEATURE_ZEN)) return 0; @@ -262,7 +262,6 @@ static int __init amd_smn_init(void) return 0; num_roots = 0; - root = NULL; while ((root = get_next_root(root))) { pci_dbg(root, "Reserving PCI config space\n"); @@ -299,14 +298,13 @@ static int __init amd_smn_init(void) count = 0; node = 0; - root = NULL; while (node < num_nodes && (root = get_next_root(root))) { /* Use one root for each node and skip the rest. */ if (count++ % roots_per_node) continue; pci_dbg(root, "is root for AMD node %u\n", node); - amd_roots[node++] = root; + amd_roots[node++] = pci_dev_get(root); } if (enable_dfs) { From 8a14be55bdc6d5a25cd7b0ac5d4d884fcc727b49 Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Fri, 21 Aug 2026 18:30:46 +0800 Subject: [PATCH 0824/1317] ublk: clear force_abort in ublk_queue_reset_io_flags() Quiesce sets ubq->force_abort for batch I/O. Recovery never clears it, so batch fetch keeps failing with -ENODEV and the device stays QUIESCED. Fixes: a4d883755399 ("ublk: add UBLK_U_IO_FETCH_IO_CMDS for batch I/O processing") Signed-off-by: Yang Xiuwei Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260821103047.369522-2-yangxiuwei@kylinos.cn Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index e5ba07d8d281..44d10cb36bf1 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -3030,6 +3030,7 @@ static void ublk_queue_reset_io_flags(struct ublk_queue *ubq) ubq->canceling = false; spin_unlock(&ubq->cancel_lock); ubq->fail_io = false; + ubq->force_abort = false; } /* device can only be started after all IOs are ready */ From 94b1a3ca9b8db3151f1416263704c159a9470da5 Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Fri, 21 Aug 2026 18:30:47 +0800 Subject: [PATCH 0825/1317] selftests: ublk: add batch IO cases to recover_03 Add -b coverage for quiesce recover. Signed-off-by: Yang Xiuwei Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260821103047.369522-3-yangxiuwei@kylinos.cn Signed-off-by: Jens Axboe --- tools/testing/selftests/ublk/test_recover_03.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/testing/selftests/ublk/test_recover_03.sh b/tools/testing/selftests/ublk/test_recover_03.sh index 2554805e5b02..92f4012178f0 100755 --- a/tools/testing/selftests/ublk/test_recover_03.sh +++ b/tools/testing/selftests/ublk/test_recover_03.sh @@ -29,6 +29,11 @@ _create_backfile 0 256M _create_backfile 1 128M _create_backfile 2 128M +ublk_run_quiesce_recover -t null -q 2 -r 1 -b & +ublk_run_quiesce_recover -t loop -q 2 -r 1 -b "${UBLK_BACKFILES[0]}" & +ublk_run_quiesce_recover -t stripe -q 2 -r 1 -b "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" & +wait + ublk_run_quiesce_recover -t null -q 2 -r 1 & ublk_run_quiesce_recover -t loop -q 2 -r 1 "${UBLK_BACKFILES[0]}" & ublk_run_quiesce_recover -t stripe -q 2 -r 1 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" & From 135d84c66f85426299db01a09d93a79a87af18ba Mon Sep 17 00:00:00 2001 From: Binglei Wang Date: Fri, 11 Sep 2026 12:11:33 +0800 Subject: [PATCH 0826/1317] erofs: add missing buf->off in erofs_bread() erofs_bread() locates the target folio with index = (buf->off + offset) >> PAGE_SHIFT; but computes the in-folio offset without taking buf->off into account: return buf->base + (offset & ~PAGE_MASK); If buf->off is not page-aligned, the returned pointer misses the in-page component of buf->off, so callers end up fetching data from a wrong offset. buf->off is set to sbi->dif0.fsoff in erofs_init_metabuf(), and fsoff can be specified via the "fsoffset=" mount option, which only requires block-size alignment. Therefore, on an image with a sub-page block size (e.g. 512 bytes), a non-page-aligned fsoff (e.g. 512) triggers the issue, since 512 is a multiple of the block size but not of PAGE_SIZE. It can be reproduced by mounting an image that is placed at a non-page-aligned offset: mkfs.erofs -b512 -zlz4hc sub.erofs src/ # prepend 512 bytes of padding to the image mount -t erofs -o loop,fsoffset=512 padded.erofs /mnt which fails with erofs (device loop0): cannot find valid erofs superblock because the on-disk superblock (at offset 1024 within the image, i.e. 1536 within the padded file) is read from a wrong in-folio offset. With this fixed, the very same image mounts successfully and its file contents match those read from the unpadded image. Fix it by including buf->off in the in-folio offset calculation, so that it is consistent with the folio index calculation. Fixes: c36ec00d7f67 ("erofs: add 'fsoffset' mount option to specify filesystem offset") Signed-off-by: Binglei Wang Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang --- fs/erofs/data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/erofs/data.c b/fs/erofs/data.c index 0885b1f2fc92..be63b89f0862 100644 --- a/fs/erofs/data.c +++ b/fs/erofs/data.c @@ -48,7 +48,7 @@ void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap) return NULL; if (!buf->base) buf->base = kmap_local_page(buf->page); - return buf->base + (offset & ~PAGE_MASK); + return buf->base + ((buf->off + offset) & ~PAGE_MASK); } int erofs_init_metabuf(struct erofs_buf *buf, struct super_block *sb, From 4d3c07591534517c633945c8d8e6526f10e3fabc Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 21:42:28 -0700 Subject: [PATCH 0827/1317] xfs: fix under-reservation of blocks when repairing sf directories Whilst running QA on XFS for-next as of 7.3-rc2 with MKFS_OPTIONS="-n size=8192", I observed the following (trimmed) dmesg splat: XFS: Assertion failed: args->total >= dp->i_nblocks - nblks, file: fs/xfs/libxfs/xfs_da_btree.c, line: 2387 WARNING: fs/xfs/xfs_message.c:104 at assfail+0x46/0x4a [xfs], CPU#0: xfs_scrub/1426511 CPU: 0 UID: 0 PID: 1426511 Comm: xfs_scrub Tainted: G W 7.3.0-rc2-djwx #rc2 PREEMPT(lazy) 6e418570b606a39783b0e7e7b30dc407b965f9e8 Tainted: [W]=WARN RIP: 0010:assfail+0x46/0x4a [xfs] RSP: 0018:ffffc900010d7890 EFLAGS: 00010246 RAX: 0000000000000000 RBX: 0000000000000000 RCX: 00000000ffffffd1 RDX: 0000000000000000 RSI: 0000000000000021 RDI: ffffffffa059fd38 RBP: 0000000000000002 R08: 0000000000000000 R09: 0000000000000000 R10: 000000000000000a R11: 000000007fffffff R12: ffffc900010d7940 R13: ffff888368d8f980 R14: ffffc900010d7a48 R15: ffffc900010d78d0 FS: 00007f445c5ce680(0000) GS:ffff8884a97ea000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f443803b9a8 CR3: 0000000107a4b000 CR4: 00000000003506f0 Call Trace: xfs_da_grow_inode_int+0x2e0/0x300 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_dir2_grow_inode+0x6e/0x150 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_dir2_sf_to_block+0x149/0x870 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_dir_swap_prep+0xe2/0x110 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_dir_swap+0xfb/0x2f0 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_dir_rebuild_tree+0x99/0x100 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_directory+0x83/0x1c0 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_attempt+0x4f/0x1e0 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_scrub_metadata+0x393/0x5b0 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_ioc_scrubv_metadata+0x306/0x570 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_file_ioctl+0xa4f/0x1150 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] __x64_sys_ioctl+0x76/0xc0 do_syscall_64+0x7a/0x3b0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 This is a consequence of commit 0fe77e57588b98, which added the following assertion to xfs_da_grow_inode_int: ASSERT(args->total >= dp->i_nblocks - nblks); Tracing this back to xrep_dir_swap_prep, I noticed that the xfs_da_args object that's passed to xfs_dir2_sf_to_block sets args->total to 1. This is incorrect because mkfs set the directory block size to 8k and the filesystem block size to 4k. In other words, args->total should be 2 here, not 1. Dave Chinner tripped over the same problem with the same branch through a different channel -- his test setup set the fs block size to 1k, in which case the directory block size is still set to 4k. Here, args->total should be 4. Changing the assignment of args->total to sc->mp->m_dir_geo->fsbcount makes the assertion go away, but that isn't a complete fix. In xrep_tempexch_estimate, we also incorrectly assume that a shortform conversion requires 1 fsblock when it should be m_dir_geo->fsbcount. Without that, we can under-reserve space in the transaction and cause a filesystem shutdown. Note that the xfs_dabuf_nfsb helper will compute the correct value for directories and xattr, so we use that instead of open-coding the logic. Also fix xrep_xattr_swap_prep to assign args->total via xfs_dabuf_nfsb to avoid one logic bomb if we ever support multi-fsblock attrs. Cc: stable@vger.kernel.org # v6.10 Cc: floss@jetm.me Reported-by: dgc@kernel.org Fixes: 629fdaf5f5b1b7 ("xfs: use atomic extent swapping to fix user file fork data") Tripped-by: 0fe77e57588b98 ("xfs: assert the reservation covers each da fork growth") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_da_btree.c | 2 +- fs/xfs/libxfs/xfs_da_btree.h | 2 ++ fs/xfs/scrub/attr_repair.c | 2 +- fs/xfs/scrub/dir_repair.c | 2 +- fs/xfs/scrub/tempfile.c | 29 ++++++++++++++++++++++------- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c index 8cbdd6574755..3d02a0d7ba44 100644 --- a/fs/xfs/libxfs/xfs_da_btree.c +++ b/fs/xfs/libxfs/xfs_da_btree.c @@ -130,7 +130,7 @@ xfs_da_state_reset( state->mp = state->args->dp->i_mount; } -static inline int xfs_dabuf_nfsb(struct xfs_mount *mp, int whichfork) +inline int xfs_dabuf_nfsb(struct xfs_mount *mp, int whichfork) { if (whichfork == XFS_DATA_FORK) return mp->m_dir_geo->fsbcount; diff --git a/fs/xfs/libxfs/xfs_da_btree.h b/fs/xfs/libxfs/xfs_da_btree.h index afcf2d3c7a21..a718b1ceb0aa 100644 --- a/fs/xfs/libxfs/xfs_da_btree.h +++ b/fs/xfs/libxfs/xfs_da_btree.h @@ -244,4 +244,6 @@ xfs_failaddr_t xfs_da3_node_header_check(struct xfs_buf *bp, xfs_ino_t owner); extern struct kmem_cache *xfs_da_state_cache; +int xfs_dabuf_nfsb(struct xfs_mount *mp, int whichfork); + #endif /* __XFS_DA_BTREE_H__ */ diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index 6e6af142f1fb..28f92e9ba72b 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -1294,7 +1294,7 @@ xrep_xattr_swap_prep( .geo = sc->mp->m_attr_geo, .whichfork = XFS_ATTR_FORK, .trans = sc->tp, - .total = 1, + .total = xfs_dabuf_nfsb(sc->mp, XFS_ATTR_FORK), .owner = I_INO(sc->ip), }; diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 31a23c5f386a..d9d6b7e2abda 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -1488,7 +1488,7 @@ xrep_dir_swap_prep( .geo = sc->mp->m_dir_geo, .whichfork = XFS_DATA_FORK, .trans = sc->tp, - .total = 1, + .total = xfs_dabuf_nfsb(sc->mp, XFS_DATA_FORK), .owner = I_INO(sc->ip), }; diff --git a/fs/xfs/scrub/tempfile.c b/fs/xfs/scrub/tempfile.c index 98820003b929..59a9213a3c7d 100644 --- a/fs/xfs/scrub/tempfile.c +++ b/fs/xfs/scrub/tempfile.c @@ -649,6 +649,19 @@ xrep_tempexch_prep_request( return 0; } +static inline unsigned int +xrep_tempexch_estimate_sf_resblks( + struct xfs_scrub *sc, + int whichfork) +{ + /* repairing a symlink target */ + if (S_ISLNK(VFS_I(sc->ip)->i_mode) && whichfork == XFS_DATA_FORK) + return 1; + + /* everything else is a directory or an xattr structure */ + return xfs_dabuf_nfsb(sc->mp, whichfork); +} + /* * Fill out the mapping exchange resource estimation structures in preparation * for exchanging the contents of a metadata file that we've rebuilt in the @@ -663,6 +676,8 @@ xrep_tempexch_estimate( struct xfs_ifork *ifp; struct xfs_ifork *tifp; int whichfork = xfs_exchmaps_reqfork(req); + unsigned int sf_resblks = + xrep_tempexch_estimate_sf_resblks(sc, whichfork); int state = 0; /* @@ -693,9 +708,9 @@ xrep_tempexch_estimate( * plus the block we converted. */ req->ip1_bcount = sc->tempip->i_nblocks; - req->ip2_bcount = 1; + req->ip2_bcount = sf_resblks; req->nr_exchanges = 1 + tifp->if_nextents; - req->resblks = 1; + req->resblks = sf_resblks; break; case 2: /* @@ -707,10 +722,10 @@ xrep_tempexch_estimate( * is (worst case) the extent count of the file being repaired * plus the block we converted. */ - req->ip1_bcount = 1; + req->ip1_bcount = sf_resblks; req->ip2_bcount = sc->ip->i_nblocks; req->nr_exchanges = 1 + ifp->if_nextents; - req->resblks = 1; + req->resblks = sf_resblks; break; case 3: /* @@ -722,10 +737,10 @@ xrep_tempexch_estimate( * fileoff 0. Presumably, the caller could not exchange the * two inode fork areas directly. */ - req->ip1_bcount = 1; - req->ip2_bcount = 1; + req->ip1_bcount = sf_resblks; + req->ip2_bcount = sf_resblks; req->nr_exchanges = 1; - req->resblks = 2; + req->resblks = 2 * sf_resblks; break; } From 1ee2ce797c360785a3813fef62c90f427f3aed34 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:04:50 -0700 Subject: [PATCH 0828/1317] xfs: actually check internal-rtdev fields in the superblock LOLLM points out that the superblock scrubber doesn't check the new fields that were added for internal realtime volumes when we added zoned device support. Cc: stable@vger.kernel.org # v6.15 Fixes: 2167eaabe2fadd ("xfs: define the zoned on-disk format") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index 1fa66aa68e16..fa5d32ec020a 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -418,6 +418,13 @@ xchk_superblock( xchk_block_set_corrupt(sc, bp); } + if (xfs_has_zoned(mp)) { + if (sb->sb_rtstart != cpu_to_be64(mp->m_sb.sb_rtstart)) + xchk_block_set_corrupt(sc, bp); + if (sb->sb_rtreserved != cpu_to_be64(mp->m_sb.sb_rtreserved)) + xchk_block_set_corrupt(sc, bp); + } + /* Everything else must be zero. */ sblen = xchk_superblock_ondisk_size(mp); if (memchr_inv((char *)sb + sblen, 0, BBTOB(bp->b_length) - sblen)) From 3bdbf472a608aeb7e8e4dc70ee86738ad5256356 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:05:06 -0700 Subject: [PATCH 0829/1317] xfs: fix rtrmap cross-referencing elision logic LOLLM points out that xchk_bmap_xref_rmap_cow skips the cross-reference if the data-section rmapbt cursor is not present. However, this is broken for realtime file data fork scanning, because they will have an rtrmapbt cursor and not an rmapbt cursor. Fix the behavior by removing the cursor checks because xchk_bmap_get_rmap already accounts for that. Cc: stable@vger.kernel.org # v6.14 Fixes: 037a44d8277adf ("xfs: cross-reference the realtime rmapbt") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 401c278725d2..3b0f1dbd9147 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -274,7 +274,7 @@ xchk_bmap_xref_rmap_cow( unsigned long long rmap_end; uint64_t owner = XFS_RMAP_OWN_COW; - if (!info->sc->sa.rmap_cur || xchk_skip_xref(info->sc->sm)) + if (xchk_skip_xref(info->sc->sm)) return; /* Find the rmap record for this irec. */ From d3a6a35a220615c4f4578aedf3b1626b91d3acae Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:05:21 -0700 Subject: [PATCH 0830/1317] xfs: fix termination logic in xchk_bmap xchk_should_terminate can turn its @error argument into -EINTR if the user is sitting on ^C. Unfortunately, this code here turns that into a 0 return, which isn't quite correct. LOLLM complains about this, though I think it's a very minor matter because the only way -EINTR happens is if there's a fatal signal. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 3b0f1dbd9147..4f3c7f681bd9 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -1103,8 +1103,9 @@ xchk_bmap( * the rmap must match the combined mapping exactly. */ while (xchk_bmap_iext_iter(&info, &irec)) { - if (xchk_should_terminate(sc, &error) || - (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) + if (xchk_should_terminate(sc, &error)) + return error; + if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) return 0; if (irec.br_startoff >= endoff) { From e854f9a28b1fa08dfa5bf18ee4184fae90106180 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:05:37 -0700 Subject: [PATCH 0831/1317] xfs: fix replaying dirent removals into the temporary directory xrep_dir_replay_removename is the function that replays a directory entry removal from sc->ip into the temporary directory so that when we swap the contents of sc->tempip and sc->ip, the directory is correct. LOLLM noticed that we were passing the wrong inode pointer into xrep_dir_init_args. It doesn't make sense to set rd->args.dp to rd->args.dp so let's fix this. Cc: stable@vger.kernel.org # v6.10 Fixes: 8559b21a64d983 ("xfs: implement live updates for directory repairs") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index d9d6b7e2abda..2cfcf1c35679 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -727,7 +727,7 @@ xrep_dir_replay_removename( const struct xfs_name *name, xfs_extlen_t total) { - struct xfs_inode *dp = rd->args.dp; + struct xfs_inode *dp = rd->sc->tempip; ASSERT(S_ISDIR(VFS_I(dp)->i_mode)); From 69e10c2b4a51b4ff3c88a70e90f5180ad58c758f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:05:52 -0700 Subject: [PATCH 0832/1317] xfs: reset parent pointer args before each dir tree unlink repair LOLLM noticed that xfs_parent_removename only partially initializes the passed-in parent pointer arguments object. In the directory tree repair code, we could decide to remove multiple links to a file, so we don't want state from one call to bleed into the next one. Zero the whole thing explicitly. Cc: stable@vger.kernel.org # v6.10 Fixes: 3f31406aef493b ("xfs: fix corruptions in the directory tree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree_repair.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index 8acd55b8c769..1d1eafcf6eb5 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -479,6 +479,7 @@ xrep_dirtree_unlink( } if (xfs_has_parent(sc->mp)) { + memset(&dl->ppargs, 0, sizeof(dl->ppargs)); error = xfs_parent_removename(sc->tp, &dl->ppargs, dp, &dl->xname, sc->ip); if (error) From ad4497a92caba4630f75c80d49cb947026213280 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:06:08 -0700 Subject: [PATCH 0833/1317] xfs: advance the findparent inode scan cursor while holding ILOCK LOLLM pointed out a race condition in xrep_findparent_scan -- the directory live update hook holds the directory ILOCK when it calls the xchk_iscan_want_live_update predicate to figure out if it needs to remember the live update, but xrep_findparent_scan drops the directory ILOCK before advancing the cursor. Therefore, it's possible for a live update to check the scan cursor after the scan drops the ILOCK but before the scan updates its cursor. If this happens, we'll fail to record the live update. Fix this by moving the cursor update logic inside xrep_findparent_walk_directory. Note that for non-directories it's ok to advance the cursor without holding any ILOCK because the findparent scan only cares about directory parents, not the children. Cc: stable@vger.kernel.org # v6.10 Fixes: a07b45576264e7 ("xfs: scan the filesystem to repair a directory dotdot entry") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/findparent.c | 56 ++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/fs/xfs/scrub/findparent.c b/fs/xfs/scrub/findparent.c index 04b6b96b0a30..eab3ac2704be 100644 --- a/fs/xfs/scrub/findparent.c +++ b/fs/xfs/scrub/findparent.c @@ -139,12 +139,40 @@ xrep_findparent_dirent( return 0; } +static inline bool +xrep_findparent_want_scan_file( + const struct xrep_findparent_info *fpi) +{ + const struct xfs_scrub *sc = fpi->sc; + const struct xfs_inode *dp = fpi->dp; + + /* Only directories can be parents */ + if (!S_ISDIR(VFS_IC(dp)->i_mode)) + return false; + + /* + * The inode being scanned cannot be its own parent, nor can any + * temporary directory we created to stage this repair. + */ + if (dp == sc->ip || dp == sc->tempip) + return false; + + /* + * Similarly, temporary files created to stage a repair cannot be the + * parent of this inode. + */ + if (xrep_is_tempfile(dp)) + return false; + + return true; +} + /* * If this is a directory, walk the dirents looking for any that point to the * scrub target inode. */ STATIC int -xrep_findparent_walk_directory( +xrep_findparent_walk_file( struct xrep_findparent_info *fpi) { struct xfs_scrub *sc = fpi->sc; @@ -152,19 +180,11 @@ xrep_findparent_walk_directory( unsigned int lock_mode; int error = 0; - /* - * The inode being scanned cannot be its own parent, nor can any - * temporary directory we created to stage this repair. - */ - if (dp == sc->ip || dp == sc->tempip) - return 0; - - /* - * Similarly, temporary files created to stage a repair cannot be the - * parent of this inode. - */ - if (xrep_is_tempfile(dp)) + if (!xrep_findparent_want_scan_file(fpi)) { + if (fpi->parent_scan) + xchk_iscan_mark_visited(&fpi->parent_scan->iscan, dp); return 0; + } /* * Scan the directory to see if there it contains an entry pointing to @@ -201,6 +221,8 @@ xrep_findparent_walk_directory( goto out_unlock; out_unlock: + if (fpi->parent_scan) + xchk_iscan_mark_visited(&fpi->parent_scan->iscan, dp); xfs_iunlock(dp, lock_mode); return error; } @@ -308,11 +330,7 @@ xrep_findparent_scan( ASSERT(S_ISDIR(VFS_IC(sc->ip)->i_mode)); while ((ret = xchk_iscan_iter(&pscan->iscan, &fpi.dp)) == 1) { - if (S_ISDIR(VFS_I(fpi.dp)->i_mode)) - ret = xrep_findparent_walk_directory(&fpi); - else - ret = 0; - xchk_iscan_mark_visited(&pscan->iscan, fpi.dp); + ret = xrep_findparent_walk_file(&fpi); xchk_irele(sc, fpi.dp); if (ret) break; @@ -401,7 +419,7 @@ xrep_findparent_confirm( goto out_rele; } - error = xrep_findparent_walk_directory(&fpi); + error = xrep_findparent_walk_file(&fpi); if (error) goto out_rele; From c5dcb3aadc18d7b82ba64790721b005d18193d35 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 10 Sep 2026 16:34:42 +0200 Subject: [PATCH 0834/1317] hrtimer: Use hard expiry when updating timers on the same base Rearming a queued timer with nonzero slack can leave the timerqueue out of order. remove_and_enqueue_same_base() checks the new soft expiry against its neighbours' hard expiries, then stores the new hard expiry in the node without requeueing it. For example, with A at 10 and B at 20, rearming A at 11 with slack 30 passes the neighbour check but leaves A's hard expiry of 41 before B's 20. The same function also caches the soft expiry in base->expires_next when updating or inserting the first timer, giving next-event selection an earlier deadline than the queue head's hard expiry. Set the timer expiry before handling the queue. Use its stored hard expiry for the in-place ordering check and both updates to base->expires_next. The early update is safe because remove_and_enqueue_same_base() runs with base->cpu_base->lock held. The lock keeps the queue stable while hrtimer_can_update_in_place() checks the new expiry against both neighbours. If the check fails, timerqueue_linked_del() removes the node without comparing expiry values before it is reinserted. Fixes: eddffab8282e3 ("hrtimer: Keep track of first expiring timer per clock base") Fixes: 343f2f4dc5425 ("hrtimer: Try to modify timers in place") Signed-off-by: Andrea Parri Signed-off-by: Thomas Gleixner Assisted-by: LLM Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260910143442.2018-1-parri.andrea@gmail.com --- kernel/time/hrtimer.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 530d61257b9a..cbf1693c86b3 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -1263,13 +1263,23 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b { bool was_first = false; + /* + * Updating the sort key while @timer is queued can temporarily + * make the tree inconsistent. This is safe under cpu_base->lock: + * no other queue operation can observe that state. + * hrtimer_can_update_in_place() either confirms that the new expiry + * fits between the neighbours or timerqueue_linked_del() removes the + * timer without consulting the expiry. + */ + hrtimer_set_expires_range_ns(timer, expires, delta_ns); + expires = hrtimer_get_expires(timer); + /* Remove it from the timer queue if active */ if (timer->is_queued) { was_first = !timerqueue_linked_prev(&timer->node); /* Try to update in place to avoid the de/enqueue dance */ if (hrtimer_can_update_in_place(timer, base, expires)) { - hrtimer_set_expires_range_ns(timer, expires, delta_ns); trace_hrtimer_start(timer, mode, true); if (was_first) base->expires_next = expires; @@ -1280,9 +1290,6 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b timerqueue_linked_del(&base->active, &timer->node); } - /* Set the new expiry time */ - hrtimer_set_expires_range_ns(timer, expires, delta_ns); - debug_activate(timer, mode, timer->is_queued); base->cpu_base->active_bases |= 1 << base->index; From 55a8e1451869233db837a97e8f3cbf9983bc8678 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Tue, 8 Sep 2026 17:02:32 +0530 Subject: [PATCH 0835/1317] x86/mm: Don't force unencrypted DMA for IOMMU-backed devices Commit 8277a12d0d60 ("dma-pool: track decrypted atomic pools and select them via attrs") exposed an issue with force_dma_unencrypted() on systems using host memory encryption. force_dma_unencrypted() checks whether the device DMA mask can address the encryption bit and, if not, requires DMA allocations to use unencrypted memory. However, this check is not applicable when the device is using the IOMMU. In that case, the device DMA mask constrains the IOVA seen by the device, not the backing physical address, so it does not need to cover the C-bit. This currently causes dma_alloc_attrs() to set __DMA_ATTR_ALLOC_CC_SHARED for such devices. iommu_dma_alloc() does not support that attribute and rejects the allocation, causing DMA allocations to fail. Do not force DMA allocations to be unencrypted when the device is using the IOMMU. This allows the IOMMU to map the encrypted physical pages as before and avoids incorrectly requesting CC_SHARED allocations. Fixes: 8277a12d0d60 ("dma-pool: track decrypted atomic pools and select them via attrs") Reported-by: Timo Witte Closes: https://lore.kernel.org/all/CANB4YXS=Nf-co3t8eMHtqrW4sn=1BDcP6o=EoTrgZm0WYMYTyw@mail.gmail.com/ Signed-off-by: Aneesh Kumar K.V (Arm) Link: https://lore.kernel.org/r/20260908113232.247457-1-aneesh.kumar@kernel.org [mszyprow: adjusted url and changed 'link' tag to the 'closes' one] Signed-off-by: Marek Szyprowski --- arch/x86/mm/mem_encrypt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/x86/mm/mem_encrypt.c b/arch/x86/mm/mem_encrypt.c index 95bae74fdab2..3aefdef5bcb0 100644 --- a/arch/x86/mm/mem_encrypt.c +++ b/arch/x86/mm/mem_encrypt.c @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -30,7 +31,7 @@ bool force_dma_unencrypted(struct device *dev) * device does not support DMA to addresses that include the * encryption mask. */ - if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT)) { + if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT) && !use_dma_iommu(dev)) { u64 dma_enc_mask = DMA_BIT_MASK(__ffs64(sme_me_mask)); u64 dma_dev_mask = min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit); From 143755bdabaa96776c24f878014608e9cb44f930 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 7 Sep 2026 04:00:15 +0200 Subject: [PATCH 0836/1317] dma-buf: Make DMABUF_DEBUG default to y on DEBUG_KERNEL kernels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 646013f513f3 ("dma-buf: enable DMABUF_DEBUG by default on DEBUG kernels") changed the default of DMABUF_DEBUG to "y if DEBUG", but no Kconfig symbol DEBUG exists, so the option has had no default since. Use DEBUG_KERNEL, the Kconfig symbol for a debug kernel. Fixes: 646013f513f3 ("dma-buf: enable DMABUF_DEBUG by default on DEBUG kernels") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260907020015.24719-1-kmehltretter@gmail.com --- drivers/dma-buf/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma-buf/Kconfig b/drivers/dma-buf/Kconfig index 7efc0f0d0712..e4f078a326a4 100644 --- a/drivers/dma-buf/Kconfig +++ b/drivers/dma-buf/Kconfig @@ -43,7 +43,7 @@ config UDMABUF config DMABUF_DEBUG bool "DMA-BUF debug checks" depends on DMA_SHARED_BUFFER - default y if DEBUG + default y if DEBUG_KERNEL help This option enables additional checks for DMA-BUF importers and exporters. Specifically it validates that importers do not peek at the From b344ca94e8cc85796f16ea25e2e5a8e0303fe813 Mon Sep 17 00:00:00 2001 From: David Hu Date: Tue, 1 Sep 2026 17:08:48 +0000 Subject: [PATCH 0837/1317] dma-buf: Fix silent overflow for phys vec to sgt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In case MMIO size is bigger than 4G and peer2peer DMA goes through host bridge, we trigger a code path that assigns the total linked IOVA (which is greater than 4G) to mapped_len. Previously, `mapped_len` was declared as 32-bit `unsigned int`. When accumulating `size_t` lengths, this leads to a silent wrap-around. This truncation causes truncated lengths to be passed to functions like `fill_sg_entry()`. Fix this by changing `mapped_len` to `size_t` (64-bit). While at it, fix similar potential overflow issues in `calc_sg_nents` by using `check_add_overflow()` for `nents` and using `unsigned int` for the loop iterator in `fill_sg_entry` to match. Fixes: 3aa31a8bb11e ("dma-buf: provide phys_vec to scatter-gather mapping routine") Cc: stable@vger.kernel.org Cc: iommu@lists.linux.dev Reviewed-by: Pranjal Shrivastava Reviewed-by: Kevin Tian Reviewed-by: Leon Romanovsky Signed-off-by: David Hu Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260901170849.4052816-2-dhu@x6u.co --- drivers/dma-buf/dma-buf-mapping.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/dma-buf/dma-buf-mapping.c b/drivers/dma-buf/dma-buf-mapping.c index 794acff2546a..80f6ab2f4809 100644 --- a/drivers/dma-buf/dma-buf-mapping.c +++ b/drivers/dma-buf/dma-buf-mapping.c @@ -5,12 +5,13 @@ */ #include #include +#include static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, dma_addr_t addr) { unsigned int len, nents; - int i; + unsigned int i; nents = DIV_ROUND_UP(length, UINT_MAX); for (i = 0; i < nents; i++) { @@ -40,8 +41,12 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, size_t i; if (!state || !dma_use_iova(state)) { - for (i = 0; i < nr_ranges; i++) - nents += DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + for (i = 0; i < nr_ranges; i++) { + unsigned int added = DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + + if (check_add_overflow(nents, added, &nents)) + return 0; + } } else { /* * In IOVA case, there is only one SG entry which spans @@ -95,9 +100,10 @@ struct sg_table *dma_buf_phys_vec_to_sgt(struct dma_buf_attachment *attach, size_t nr_ranges, size_t size, enum dma_data_direction dir) { - unsigned int nents, mapped_len = 0; struct dma_buf_dma *dma; struct scatterlist *sgl; + size_t mapped_len = 0; + unsigned int nents; dma_addr_t addr; size_t i; int ret; @@ -133,6 +139,8 @@ struct sg_table *dma_buf_phys_vec_to_sgt(struct dma_buf_attachment *attach, } nents = calc_sg_nents(dma->state, phys_vec, nr_ranges, size); + + /* sg_alloc_table will cleanly fail and return -EINVAL if nents == 0 */ ret = sg_alloc_table(&dma->sgt, nents, GFP_KERNEL | __GFP_ZERO); if (ret) goto err_free_state; From 06dd5e1ae8ce4e129791087c8c66594950f0ba03 Mon Sep 17 00:00:00 2001 From: David Hu Date: Tue, 1 Sep 2026 17:08:49 +0000 Subject: [PATCH 0838/1317] dma-buf: Split sgl by largest page-aligned chunk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, `fill_sg_entry()` splits the scatterlist using `UINT_MAX`. This creates a non-page-aligned DMA length (`0xFFFFFFFF`) for the first entry, resulting in non-page-aligned DMA addresses for all subsequent entries. While the underlying IOMMU mapping may be contiguous, hardware DMA engines often require explicit address alignment (e.g., page, cacheline, or storage sector boundaries). Passing unaligned addresses and lengths can cause explicit failures in DMA descriptor creation or silent data corruption if lower unaligned bits are truncated. In addition, a non-page-aligned sgl length will trigger an edge case in `ib_umem_find_best_pgsz()`. In case of a discontinuity in later buffers, we will have a `va` with lowest bit set to 1. That will lead to `ib_umem_find_best_pgsz()` always return 0, and break the promise to find best page size for the mapping on the NIC side. Fix this by splitting the scatterlist by the largest possible page aligned chunk within `UINT_MAX` (`ALIGN_DOWN(UINT_MAX, PAGE_SIZE)`). This ensures all scatterlist DMA addresses and lengths remain page aligned, while minimizing the total number of sgl entries. Page-aligned entries allow the system to cleanly chunk payloads into PCIe MaxPayloadSize (MPS) (e.g., 128 bytes, 256 bytes, 512 bytes). As a result, this may help reduce TLP fragmentation in P2P transfers and alleviate potential congestion within a logical PCIe switch partition, especially when Relaxed Ordering is not possible due to hardware constraints. Reported-by: sashiko-bot Closes: https://lore.kernel.org/all/20260609165431.778061F00893@smtp.kernel.org/ Fixes: 3aa31a8bb11e ("dma-buf: provide phys_vec to scatter-gather mapping routine") Cc: stable@vger.kernel.org Reviewed-by: Leon Romanovsky Signed-off-by: David Hu Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260901170849.4052816-3-dhu@x6u.co --- drivers/dma-buf/dma-buf-mapping.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/dma-buf/dma-buf-mapping.c b/drivers/dma-buf/dma-buf-mapping.c index 80f6ab2f4809..833be519e1e6 100644 --- a/drivers/dma-buf/dma-buf-mapping.c +++ b/drivers/dma-buf/dma-buf-mapping.c @@ -6,16 +6,17 @@ #include #include #include +#include + +#define MAX_SG_ENT_SZ ALIGN_DOWN(UINT_MAX, PAGE_SIZE) static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, dma_addr_t addr) { - unsigned int len, nents; - unsigned int i; + size_t len; - nents = DIV_ROUND_UP(length, UINT_MAX); - for (i = 0; i < nents; i++) { - len = min_t(size_t, length, UINT_MAX); + while (length) { + len = min(length, MAX_SG_ENT_SZ); length -= len; /* * DMABUF abuses scatterlist to create a scatterlist @@ -25,8 +26,10 @@ static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, * does not require the CPU list for mapping or unmapping. */ sg_set_page(sgl, NULL, 0, 0); - sg_dma_address(sgl) = addr + (dma_addr_t)i * UINT_MAX; + sg_dma_address(sgl) = addr; sg_dma_len(sgl) = len; + addr += len; + /* Unconditionally advance. On last segment, this becomes NULL */ sgl = sg_next(sgl); } @@ -42,7 +45,7 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, if (!state || !dma_use_iova(state)) { for (i = 0; i < nr_ranges; i++) { - unsigned int added = DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + unsigned int added = DIV_ROUND_UP(phys_vec[i].len, MAX_SG_ENT_SZ); if (check_add_overflow(nents, added, &nents)) return 0; @@ -53,7 +56,7 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, * for whole IOVA address space, but we need to make sure * that it fits sg->length, maybe we need more. */ - nents = DIV_ROUND_UP(size, UINT_MAX); + nents = DIV_ROUND_UP(size, MAX_SG_ENT_SZ); } return nents; From d313499df66159b4b7971d760d16729598ab7e5a Mon Sep 17 00:00:00 2001 From: Theodor Arsenij Larionov Trichkine Date: Tue, 25 Aug 2026 12:10:56 +0300 Subject: [PATCH 0839/1317] netfilter: nft_nat: fully initialise new_addr in netmap setup nft_nat_setup_netmap() builds the mapped address in an on-stack union nf_inet_addr. For an IPv4 mapping it writes only the 4-byte .ip member and the loop runs a single 32-bit iteration, but it then copies the whole 16-byte union into range->min_addr and range->max_addr, so the upper 12 bytes reach nf_nat_setup_info() uninitialised. KMSAN reports an uninit-value in nf_nat_setup_info() reached from nft_nat_eval(). The IPv6 path fills all 16 bytes and is not affected. Zero-initialise new_addr. Fixes: 3ff7ddb1353d ("netfilter: nft_nat: add netmap support") Signed-off-by: Theodor Arsenij Larionov Trichkine Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_nat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nft_nat.c b/net/netfilter/nft_nat.c index e32cd9fbc7c2..cdbd800cac96 100644 --- a/net/netfilter/nft_nat.c +++ b/net/netfilter/nft_nat.c @@ -64,8 +64,8 @@ static void nft_nat_setup_netmap(struct nf_nat_range2 *range, const struct nft_pktinfo *pkt, const struct nft_nat *priv) { + union nf_inet_addr new_addr = {}; struct sk_buff *skb = pkt->skb; - union nf_inet_addr new_addr; __be32 netmask; int i, len = 0; From 444e4c88c9c62a3d823069006563513fe7d5aa66 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Thu, 27 Aug 2026 12:32:56 +0200 Subject: [PATCH 0840/1317] netfilter: nf_tables: fix device name and prefix match in hook lookup Currently, a netdev chain or flowtable hooked to a device prefix can be unintentionally deleted by a control-plane request targeting an exact device name or even a shorter one due to the usage of min() to calculate the length to match. Fix this by making sure an exact device match never matches a prefix and that both the target and the candidate have the same length during delete operation. The add and update paths retain the existing overlap matching to prevent a single device from matching multiple hooks. Reported-by: Wei Fang Closes: https://lore.kernel.org/netfilter-devel/CANE+tVrDeNCHQVmsqkV2ozeBqyE3GtRDMhZgsg1bhw10yGNTRQ@mail.gmail.com/ Fixes: 6d07a289504a ("netfilter: nf_tables: Support wildcard netdev hook specs") Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 31fbd5a28937..c0b754a2d45b 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -2440,11 +2440,14 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net, } static struct nft_hook *nft_hook_list_find(struct list_head *hook_list, - const struct nft_hook *this) + const struct nft_hook *this, + bool strict) { struct nft_hook *hook; list_for_each_entry(hook, hook_list, list) { + if (strict && hook->ifnamelen != this->ifnamelen) + continue; if (!strncmp(hook->ifname, this->ifname, min(hook->ifnamelen, this->ifnamelen))) { if (hook->flags & NFT_HOOK_REMOVE) @@ -2486,7 +2489,7 @@ static int nf_tables_parse_netdev_hooks(struct net *net, err = PTR_ERR(hook); goto err_hook; } - if (nft_hook_list_find(hook_list, hook)) { + if (nft_hook_list_find(hook_list, hook, false)) { NL_SET_BAD_ATTR(extack, tmp); nft_netdev_hook_free(hook); err = -EEXIST; @@ -2943,7 +2946,7 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy, ops->hook = basechain->ops.hook; } - if (nft_hook_list_find(&basechain->hook_list, h)) { + if (nft_hook_list_find(&basechain->hook_list, h, false)) { list_del(&h->list); nft_netdev_hook_free(h); continue; @@ -2956,7 +2959,8 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy, !nft_trans_chain_update(trans)) continue; - if (nft_hook_list_find(&nft_trans_chain_hooks(trans), h)) { + if (nft_hook_list_find(&nft_trans_chain_hooks(trans), + h, false)) { nft_chain_release_hook(&hook); return -EEXIST; } @@ -3257,7 +3261,7 @@ static int nft_delchain_hook(struct nft_ctx *ctx, return err; list_for_each_entry(this, &chain_hook.list, list) { - hook = nft_hook_list_find(&basechain->hook_list, this); + hook = nft_hook_list_find(&basechain->hook_list, this, true); if (!hook) { err = -ENOENT; goto err_chain_del_hook; @@ -9053,7 +9057,7 @@ static int nft_register_flowtable_net_hooks(struct net *net, if (!nft_is_active_next(net, ft)) continue; - if (nft_hook_list_find(&ft->hook_list, hook)) { + if (nft_hook_list_find(&ft->hook_list, hook, false)) { err = -EEXIST; goto err_unregister_net_hooks; } @@ -9130,7 +9134,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, return err; list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) { - if (nft_hook_list_find(&flowtable->hook_list, hook)) { + if (nft_hook_list_find(&flowtable->hook_list, hook, false)) { list_del(&hook->list); nft_netdev_hook_free(hook); continue; @@ -9143,7 +9147,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, !nft_trans_flowtable_update(trans)) continue; - if (nft_hook_list_find(&nft_trans_flowtable_hooks(trans), hook)) { + if (nft_hook_list_find(&nft_trans_flowtable_hooks(trans), hook, false)) { err = -EEXIST; goto err_flowtable_update_hook; } @@ -9363,7 +9367,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx, return err; list_for_each_entry(this, &flowtable_hook.list, list) { - hook = nft_hook_list_find(&flowtable->hook_list, this); + hook = nft_hook_list_find(&flowtable->hook_list, this, true); if (!hook) { err = -ENOENT; goto err_flowtable_del_hook; From cbdd39ce42530a193c56beb206a3356cb6d01016 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 3 Sep 2026 01:28:56 +0200 Subject: [PATCH 0841/1317] netfilter: nf_nat: unregister and release hooks on error If nf_hook_entries_insert_raw() fails, the NAT hooks get never released, resulting in a memleak. Postpone setting nat_proto_net->nat_hook_ops when the hooks are registered to simplify the error path to decide whether the nat hooks need unwinding. Fixes: 1cd472bf036c ("netfilter: nf_nat: add nat hook register functions to nf_nat") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_nat_core.c | 46 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index 8ac326e1eb5b..a4858c2b2d65 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -1224,31 +1224,45 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops, } ret = nf_register_net_hooks(net, nat_ops, ops_count); - if (ret < 0) { - mutex_unlock(&nf_nat_proto_mutex); - for (i = 0; i < ops_count; i++) { - priv = nat_ops[i].priv; - kfree_rcu(priv, rcu_head); - } - kfree_rcu(nat_ops, rcu); - return ret; - } - - nat_proto_net->nat_hook_ops = nat_ops; + if (ret < 0) + goto err_free_hooks; + } else { + nat_ops = nat_proto_net->nat_hook_ops; } - nat_ops = nat_proto_net->nat_hook_ops; priv = nat_ops[hooknum].priv; if (WARN_ON_ONCE(!priv)) { - mutex_unlock(&nf_nat_proto_mutex); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto err_unregister_hooks; } ret = nf_hook_entries_insert_raw(&priv->entries, ops); - if (ret == 0) - nat_proto_net->users++; + if (ret) + goto err_unregister_hooks; + + if (!nat_proto_net->nat_hook_ops) + nat_proto_net->nat_hook_ops = nat_ops; + + nat_proto_net->users++; mutex_unlock(&nf_nat_proto_mutex); + + return 0; + +err_unregister_hooks: + if (nat_proto_net->nat_hook_ops) { + mutex_unlock(&nf_nat_proto_mutex); + return ret; + } + nf_unregister_net_hooks(net, nat_ops, ops_count); +err_free_hooks: + mutex_unlock(&nf_nat_proto_mutex); + for (i = 0; i < ops_count; i++) { + priv = nat_ops[i].priv; + kfree_rcu(priv, rcu_head); + } + kfree_rcu(nat_ops, rcu); + return ret; } From e75a9fa1d44bcbd66ea02e8781bcca6ea4076e0d Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 7 Sep 2026 21:04:05 +0200 Subject: [PATCH 0842/1317] netfilter: flowtable: hold reference on ct until flow is released nf_ct_put() releases the ct->ext area inmediately, the rcu typesafe semantics also allow to refer to the wrong conntrack from the flowtable datapath. Hold reference on ct until flow is released after rcu grace period. Add rcu_barrier() on module exit path, to ensure pending flow entries are release before module goes away. Fixes: 0ff90b6c2034 ("netfilter: nf_flow_offload: fix use-after-free and a resource leak") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_core.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 03241d4bfd5e..934c6151f558 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -258,6 +258,14 @@ static void flow_offload_route_release(struct flow_offload *flow) nft_flow_dst_release(flow, FLOW_OFFLOAD_DIR_REPLY); } +static void flow_offload_free_rcu(struct rcu_head *rcu_head) +{ + struct flow_offload *flow = container_of(rcu_head, struct flow_offload, rcu_head); + + nf_ct_put(flow->ct); + kfree(flow); +} + void flow_offload_free(struct flow_offload *flow) { switch (flow->type) { @@ -267,8 +275,7 @@ void flow_offload_free(struct flow_offload *flow) default: break; } - nf_ct_put(flow->ct); - kfree_rcu(flow, rcu_head); + call_rcu(&flow->rcu_head, flow_offload_free_rcu); } EXPORT_SYMBOL_GPL(flow_offload_free); @@ -854,6 +861,7 @@ static int __init nf_flow_table_module_init(void) static void __exit nf_flow_table_module_exit(void) { + rcu_barrier(); nf_flow_table_offload_exit(); unregister_pernet_subsys(&nf_flow_table_net_ops); kmem_cache_destroy(flow_offload_cachep); From 49daa3d668b69a5454b5aba0078848a479f79f1c Mon Sep 17 00:00:00 2001 From: Shouping Wang Date: Thu, 10 Sep 2026 19:46:01 +0800 Subject: [PATCH 0843/1317] perf/arm-cmn: Fix wp_dev_sel2 setting for multi-DTM configurations When MXP_MULTIPLE_DTM_EN is TRUE, each DTM will monitor at most two device ports. In this case, {wp_dev_sel2, wp_dev_sel} will only use values 2'b00 and 2'b01 per DTM. Previously the setting allowed values beyond the supported range per DTM, which could cause each DTM to select invalid ports when MXP_MULTIPLE_DTM_EN is TRUE. Fix this by only setting CMN_DTM_WPn_CONFIG_WP_DEV_SEL2 when !multi_dtm. Fixes: 60d1504070c2 ("perf/arm-cmn: Support new IP features") Signed-off-by: Shouping Wang Reviewed-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/perf/arm-cmn.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index b162de3d9d16..33ee2be9b386 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -1582,13 +1582,14 @@ static void arm_cmn_claim_wp_idx(struct arm_cmn_dtm *dtm, static u32 arm_cmn_wp_config(struct perf_event *event, int wp_idx) { + struct arm_cmn *cmn = to_cmn(event->pmu); u32 config; u32 dev = CMN_EVENT_WP_DEV_SEL(event); u32 chn = CMN_EVENT_WP_CHN_SEL(event); u32 grp = CMN_EVENT_WP_GRP(event); u32 exc = CMN_EVENT_WP_EXCLUSIVE(event); u32 combine = CMN_EVENT_WP_COMBINE(event); - bool is_cmn600 = to_cmn(event->pmu)->part == PART_CMN600; + bool is_cmn600 = cmn->part == PART_CMN600; /* CMN-600 supports only primary and secondary matching groups */ if (is_cmn600) @@ -1596,8 +1597,11 @@ static u32 arm_cmn_wp_config(struct perf_event *event, int wp_idx) config = FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL, dev) | FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_CHN_SEL, chn) | - FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_GRP, grp) | - FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL2, dev >> 1); + FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_GRP, grp); + + if (!cmn->multi_dtm) + config |= FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL2, dev >> 1); + if (exc) config |= is_cmn600 ? CMN600_WPn_CONFIG_WP_EXCLUSIVE : CMN_DTM_WPn_CONFIG_WP_EXCLUSIVE; From e4a6f57d22e079e23fafac51057fad534160b269 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 10 Sep 2026 06:53:27 -0700 Subject: [PATCH 0844/1317] arm64: hibernate: clone only the linear map that exists at runtime This is similar to commit 1537e55728ec2 ("arm64: trans_pgd: clone only the linear map that exists at runtime"), but in a different place. swsusp_arch_resume() clones the kernel linear map with trans_pgd_create_copy(..., PAGE_OFFSET, PAGE_END). PAGE_OFFSET comes from the compile-time VA_BITS, so a CONFIG_ARM64_VA_BITS_52 kernel booting on hardware without LPA2 -- vabits_actual is 48 and the fifth level is folded -- hands the walk a 3.9PB window while its linear map only spans the top 128TB. On a VA_BITS_52 4k kernel with CONFIG_KASAN_GENERIC in a 4GB VM, I see: swapper/0: page allocation failure: order:0, mode:0x920(GFP_ATOMIC|__GFP_ZERO) hibernate_page_alloc+0x10/0x1c swsusp_arch_resume+0x70/0x320 hibernation_restore+0xa4/0x138 software_resume+0x15c/0x270 PM: hibernation: Failed to load image, recovering. PM: hibernation: resume failed (-12) Fix it by copying the linear map that is the actual one, not the compiled one. Fixes: a6bbf5d4d9d1 ("arm64: mm: Add definitions to support 5 levels of paging") Signed-off-by: Breno Leitao Reviewed-by: Ard Biesheuvel Signed-off-by: Will Deacon --- arch/arm64/kernel/hibernate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/hibernate.c b/arch/arm64/kernel/hibernate.c index 7bf117427777..424291c547f0 100644 --- a/arch/arm64/kernel/hibernate.c +++ b/arch/arm64/kernel/hibernate.c @@ -423,8 +423,8 @@ int __nocfi swsusp_arch_resume(void) * Create a second copy of just the linear map, and use this when * restoring. */ - rc = trans_pgd_create_copy(&trans_info, &tmp_pg_dir, PAGE_OFFSET, - PAGE_END); + rc = trans_pgd_create_copy(&trans_info, &tmp_pg_dir, + _PAGE_OFFSET(vabits_actual), PAGE_END); if (rc) return rc; From 885bff055a0f251a51a0d4fd4f0a7b525582a3de Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:21 +0100 Subject: [PATCH 0845/1317] arm64: percpu: Fix this_cpu_write() casting The arm64 implementation of this_cpu_write() casts 'val' to unsigned long. This is necessary to handle cases where 'val' is a pointer type, and to avoid spurious compiler warnings for the (unreachable!) cases where the pointer type would be cast to a smaller integer type. Unfortunately, the cast is applied to 'val' rather than '(val)', which won't always generate the expected value when 'val' is an expression. For example, for this_cpu_write(pcp, zero - 1), where 'pcp' is a u64 and 'zero' is a u32: * 'zero' ===> (u32) 0x00000000 * 'zero - 1' ===> (u32) 0xffffffff * '(unsigned long)zero - 1' ===> (u64) 0xffffffffffffffff * '(unsigned long)(zero - 1)' ===> (u64) 0x00000000ffffffff Fix this by adding brackets around 'val'. Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Reported-by: David Laight Signed-off-by: Mark Rutland Reviewed-by: David Laight Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Acked-by: Christopher Lameter (Ampere) Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Reviewed-by: Lorenzo Stoakes (ARM) Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index b57b2bb00967..63bbfd4944a3 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -179,13 +179,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_protect_return(__percpu_read_64, pcp) #define this_cpu_write_1(pcp, val) \ - _pcp_protect(__percpu_write_8, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_8, pcp, (unsigned long)(val)) #define this_cpu_write_2(pcp, val) \ - _pcp_protect(__percpu_write_16, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_16, pcp, (unsigned long)(val)) #define this_cpu_write_4(pcp, val) \ - _pcp_protect(__percpu_write_32, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_32, pcp, (unsigned long)(val)) #define this_cpu_write_8(pcp, val) \ - _pcp_protect(__percpu_write_64, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_64, pcp, (unsigned long)(val)) #define this_cpu_add_1(pcp, val) \ _pcp_protect(__percpu_add_case_8, pcp, val) From 44274c657256b4911de82f8104e9e22f054cf742 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:22 +0100 Subject: [PATCH 0846/1317] arm64: percpu: Fix this_cpu_and() mask generation The arm64 implementation of this_cpu_and(pcp, val) is built in terms of ANDNOT operations, which requires the 'val' argument to be bitwise negated. The bitwise negation is not implemented correctly, with two bugs described below. (1) The bitwise negation is performed as '~val' rather than '~(val)'. This won't always generate the expected value when 'val' is an expression. For example, for this_cpu_and(pcp, 1 - 1): * 'val' is '1 - 1' ===> (int) 0x00000000 * '~val' is '~1 - 1' ===> (int) 0xfffffffd * '~(val)' is '~(1 - 1)' ===> (int) 0xffffffff ... and thus bit[1] of 'pcp' would be preserved unexpectedly by the ANDNOT operation. (2) The bitwise negation is performed on 'val' before it has been cast to (at least) the width of 'pcp'. This won't always generate the expected value for the upper bits. For example, for this_cpu_and(pcp, zero), where 'pcp' is a u64 and 'zero' is a u32: * 'zero' ===> (u32) 0x00000000 * '~(zero)' ===> (u32) 0xffffffff * '(u64)~(zero)' ===> (u64) 0x00000000ffffffff * '~((u64)(zero))' ===> (u64) 0xffffffffffffffff ... and thus bits[63:32] of 'pcp' would be preserved unexpectedly by the ANDNOT operation. Fix these issues by adding brackets around 'val', and by casting 'val' to an appropriately-sized type before bitwise negation. Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Acked-by: Christopher Lameter (Ampere) Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 63bbfd4944a3..31193bcf89a2 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -206,13 +206,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_protect_return(__percpu_add_return_case_64, pcp, val) #define this_cpu_and_1(pcp, val) \ - _pcp_protect(__percpu_andnot_case_8, pcp, ~val) + _pcp_protect(__percpu_andnot_case_8, pcp, ~(u8)(val)) #define this_cpu_and_2(pcp, val) \ - _pcp_protect(__percpu_andnot_case_16, pcp, ~val) + _pcp_protect(__percpu_andnot_case_16, pcp, ~(u16)(val)) #define this_cpu_and_4(pcp, val) \ - _pcp_protect(__percpu_andnot_case_32, pcp, ~val) + _pcp_protect(__percpu_andnot_case_32, pcp, ~(u32)(val)) #define this_cpu_and_8(pcp, val) \ - _pcp_protect(__percpu_andnot_case_64, pcp, ~val) + _pcp_protect(__percpu_andnot_case_64, pcp, ~(u64)(val)) #define this_cpu_or_1(pcp, val) \ _pcp_protect(__percpu_or_case_8, pcp, val) From 8cf2093f5372952a9ebc805c418d45df7112cd14 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:23 +0100 Subject: [PATCH 0847/1317] arm64: percpu: Fix LSE operations on {8,16}-bit types The assembly for __percpu_##name##_case_##sz() and __percpu_##name##_return_case_##sz() doesn't use the 'sfx' macro argument to form the LSE instruction. Without 'sfx', a W register argument will imply a 32-bit memory location, and consequently {8,16}-bit ops will erroneously read and write 32 bits of memory when the LSE instruction is used. Fix this by appending 'sfx' to 'op_lse' to LSE instruction. It is not necessary (and not valid) to append 'sfx' to 'op_llsc', as 'op_llsc' is a register-register operation which does not access memory (and does not take a size suffix). Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Reviewed-by: Vladimir Murzin Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 31193bcf89a2..8cf4068ce1b5 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -77,7 +77,7 @@ __percpu_##name##_case_##sz(void *ptr, unsigned long val) \ " stxr" #sfx "\t%w[loop], %" #w "[tmp], %[ptr]\n" \ " cbnz %w[loop], 1b", \ /* LSE atomics */ \ - #op_lse "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \ + #op_lse #sfx "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \ __nops(3)) \ : [loop] "=&r" (loop), [tmp] "=&r" (tmp), \ [ptr] "+Q"(*(u##sz *)ptr) \ @@ -98,7 +98,7 @@ __percpu_##name##_return_case_##sz(void *ptr, unsigned long val) \ " stxr" #sfx "\t%w[loop], %" #w "[ret], %[ptr]\n" \ " cbnz %w[loop], 1b", \ /* LSE atomics */ \ - #op_lse "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \ + #op_lse #sfx "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \ #op_llsc "\t%" #w "[ret], %" #w "[ret], %" #w "[val]\n" \ __nops(2)) \ : [loop] "=&r" (loop), [ret] "=&r" (ret), \ From 3d1ba5cbfb622025690c218d8f20da92a9ecb383 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 9 Sep 2026 17:57:07 +0200 Subject: [PATCH 0848/1317] kselftest/arm64: Fix size of thread_data values for pthread_join() pthread_join() stores the thread's return value (a "void *", i.e. 8 bytes on 64 bit computers) into the address that is passed as second parameter. However, the entries of thread_data are only normal "int"s, i.e. only 4 bytes. The additional 4 bytes of the return value clobber whatever is adjacent on the stack, i.e. other members of the thread_data array (which will be re-written in the next iteration of the for-loop, so that nobody noticed this problem), or another other local variable on the stack for the last iteration. Use "intptr_t" to declare the thread_data array entries with the correct size. Fixes: 29f080881601c ("kselftest/arm64: check GCR_EL1 after context switch") Cc: stable@vger.kernel.org Signed-off-by: Thomas Huth Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c index d23f154d3288..5d9dc8bcfbf5 100644 --- a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c +++ b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c @@ -69,7 +69,7 @@ void *execute_thread(void *x) int execute_test(pid_t pid) { pthread_t thread_id[MAX_THREADS]; - int thread_data[MAX_THREADS]; + intptr_t thread_data[MAX_THREADS]; for (int i = 0; i < MAX_THREADS; i++) pthread_create(&thread_id[i], NULL, From fdb9ebc7fb7788b8371fbef6c17dc5c8291e1429 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Mon, 7 Sep 2026 12:30:25 +0100 Subject: [PATCH 0849/1317] arm64: mte: Fix PTRACE_{PEEK,POKE}MTETAGS error documentation PTRACE_{PEEK,POKE}MTETAGS return -EIO rather than -EOPNOTSUPP (as documented) when no tags are copied from/to a mapping without PROT_MTE. This has been the behaviour since the interface was introduced, though the original intent was to distinguish between address not being accessible and mapped as untagged. Update the documentation to match the implementation (de-facto ABI). Since -EOPNOTSUPP was never returned, change the error assignment to -EIO as well to avoid confusion. Fixes: df9d7a22dd21 ("arm64: mte: Add Memory Tagging Extension documentation") Fixes: 18ddbaa02b7a ("arm64: mte: ptrace: Add PTRACE_{PEEK,POKE}MTETAGS support") Reported-by: Yury Khrustalev Cc: Will Deacon Cc: Mark Rutland Signed-off-by: Catalin Marinas Signed-off-by: Will Deacon --- Documentation/arch/arm64/memory-tagging-extension.rst | 5 ++--- arch/arm64/kernel/mte.c | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/arch/arm64/memory-tagging-extension.rst b/Documentation/arch/arm64/memory-tagging-extension.rst index e6fe428f0e2a..1d32fc5df183 100644 --- a/Documentation/arch/arm64/memory-tagging-extension.rst +++ b/Documentation/arch/arm64/memory-tagging-extension.rst @@ -208,11 +208,10 @@ will use the corresponding aligned address. tracer's space cannot be accessed or does not have valid tags. - ``-EPERM`` - the specified process cannot be traced. - ``-EIO`` - the tracee's address range cannot be accessed (e.g. invalid - address) and no tags copied. ``iov_len`` not updated. + address) or does not have valid tags (not mapped with the ``PROT_MTE`` + flag) and no tags copied. ``iov_len`` not updated. - ``-EFAULT`` - fault on accessing the tracer's memory (``struct iovec`` or ``iov_base`` buffer) and no tags copied. ``iov_len`` not updated. -- ``-EOPNOTSUPP`` - the tracee's address does not have valid tags (never - mapped with the ``PROT_MTE`` flag). ``iov_len`` not updated. **Note**: There are no transient errors for the requests above, so user programs should not retry in case of a non-zero system call return. diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c index 1a9aad6ef22a..31f5c6b0510e 100644 --- a/arch/arm64/kernel/mte.c +++ b/arch/arm64/kernel/mte.c @@ -476,7 +476,7 @@ static int __access_remote_tags(struct mm_struct *mm, unsigned long addr, * was never mapped with PROT_MTE. */ if (!(vma->vm_flags & VM_MTE)) { - err = -EOPNOTSUPP; + err = -EIO; put_page(page); break; } From 9aa237cf66495b2426ddde8532e9b08a0ed83aaa Mon Sep 17 00:00:00 2001 From: Wei Jie LAW <98lawweijie@gmail.com> Date: Wed, 9 Sep 2026 09:15:13 +0800 Subject: [PATCH 0850/1317] HID: wacom: fix OOB read in wacom_wac_pen_serial_enforce() The 'wacom_wac_pen_serial_enforce()' function may calculate and pass an invalid offset to hid_field_extract(), resulting in memory reads at incorrect addresses -- possibly beyond the end of the report. If a field in the HID descriptor lists more usages than its Report Count actually reserves space for, the function's inner 'j' will walk past the end of the field: for (i = 0; i < report->maxfield; i++) { for (j = 0; j < report->field[i]->maxusage; j++) { ... value = hid_field_extract(hdev, raw_data + 1, offset + j * size, size); A descriptor listing 12288 usages against Report Count 1 has the loop extract the usage at index 12287 from bit offset 98296 -- about 12 KB past a 2-byte received report. The value is stored in wacom_wac->serial[0] and can reach userspace as an MSC_SERIAL event, making this an information disclosure. Clamp the loop to field->report_count, the number of value slots the report holds. Value slots past the last declared usage are still scanned; they reuse that usage (HID 1.11, 6.2.2.8). Verified on v6.12.105 with a UHID reproducer: a 2-byte report from such a descriptor trips KASAN before the patch and not after it. Fixes: 83417206427b ("HID: wacom: Queue events with missing type/serial data for later processing") Suggested-by: Jason Gerecke Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Assisted-by: GLM:glm-5.3 Signed-off-by: Wei Jie Law <98lawweijie@gmail.com> Reviewed-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 0eafa483b7f7..40770affdbde 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -113,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, /* Queue events which have invalid tool type or serial number */ for (i = 0; i < report->maxfield; i++) { - for (j = 0; j < report->field[i]->maxusage; j++) { - struct hid_field *field = report->field[i]; + struct hid_field *field = report->field[i]; + + for (j = 0; j < field->report_count; j++) { struct hid_usage *usage = &field->usage[j]; unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); unsigned int offset; From 462d0b066b613103f579793031429db2ca23abc0 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 11 Sep 2026 00:15:24 +0900 Subject: [PATCH 0851/1317] tools/bootconfig: Fix integer overflow and truncation in size checks Sashiko reported that on 32-bit systems, if an attacker crafts size in the bootconfig footer such that adding BOOTCONFIG_FOOTER_SIZE wraps around (for instance, if size is 0xFFFFFFFF), the size check in load_xbc_from_initrd() can be bypassed: if (stat.st_size < size + BOOTCONFIG_FOOTER_SIZE) { pr_err("bootconfig size is too big\n"); return -E2BIG; } Furthermore, on 64-bit systems with an initrd > 4.29 GB, comparing a corrupted 32-bit size (e.g. 0xFFFFFFFF) against stat.st_size - BOOTCONFIG_FOOTER_SIZE can also bypass the check if size is not bounded. Similarly, load_xbc_file() passes 64-bit stat.st_size directly into the 32-bit int size parameter of load_xbc_fd(), truncating large standalone files (>= 2GB). In both cases, passing 0xFFFFFFFF to load_xbc_fd() truncates to -1, resulting in malloc(0), an integer overflow in read(), and an out-of-bounds null-byte write. Fix this by: 1. Rejecting size > XBC_DATA_MAX or size > stat.st_size - BOOTCONFIG_FOOTER_SIZE in load_xbc_from_initrd(). 2. Rejecting stat.st_size > XBC_DATA_MAX in load_xbc_file() before passing it to load_xbc_fd(). 3. Checking size < 0 || size > XBC_DATA_MAX defensively in load_xbc_fd(). Link: https://lore.kernel.org/all/178905332413.213925.3179977110281463499.stgit@devnote2/ Fixes: 950313ebf79c ("tools: bootconfig: Add bootconfig command") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260909161113.16C691F00A3A@smtp.kernel.org/ Closes: https://lore.kernel.org/all/20260910010137.EE0431F000FF@smtp.kernel.org/ Assisted-by: Antigravity:gemini-3.8-flash Signed-off-by: Masami Hiramatsu (Google) Reviewed-by: Sang-Heon Jeon --- tools/bootconfig/main.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/bootconfig/main.c b/tools/bootconfig/main.c index 7dc9fff9b637..17d971d47f87 100644 --- a/tools/bootconfig/main.c +++ b/tools/bootconfig/main.c @@ -140,6 +140,9 @@ static int load_xbc_fd(int fd, char **buf, int size) { int ret; + if (size < 0 || size > XBC_DATA_MAX) + return -EINVAL; + *buf = malloc(size + 1); if (!*buf) return -ENOMEM; @@ -168,6 +171,13 @@ static int load_xbc_file(const char *path, char **buf) return ret; } + if (stat.st_size > XBC_DATA_MAX) { + pr_err("%s size is too big\n", path); + ret = -E2BIG; + close(fd); + return ret; + } + ret = load_xbc_fd(fd, buf, stat.st_size); close(fd); @@ -218,7 +228,8 @@ static int load_xbc_from_initrd(int fd, char **buf) csum = le32toh(csum); /* Wrong size error */ - if (stat.st_size < size + BOOTCONFIG_FOOTER_SIZE) { + if (size > XBC_DATA_MAX || + size > stat.st_size - BOOTCONFIG_FOOTER_SIZE) { pr_err("bootconfig size is too big\n"); return -E2BIG; } From 7812d6dab0698001e50e8c2f901e17da3eb6f429 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 11 Sep 2026 00:15:34 +0900 Subject: [PATCH 0852/1317] bootconfig: Fix integer overflow in initrd size check Sashiko reported that in get_boot_config_from_initrd(), a crafted initrd with a huge bootconfig size (such as 0xFFFFFFFF) can cause the pointer arithmetic: data = ((void *)hdr) - size; to wrap around on 32-bit systems (or when pointer subtraction overflows). Because data wraps around, the subsequent bounds check: if ((unsigned long)data < initrd_start) evaluates to false, bypassing the check. The kernel then calls xbc_calc_checksum(data, size), which attempts to read 4GB of memory, hitting unmapped pages and triggering a fatal kernel page fault during early boot. Furthermore, on 64-bit systems with an initrd > 4.29 GB, an unbounded 32-bit size can similarly bypass the initrd_start check. Fix this by: 1. Ensuring the initrd is at least large enough to contain the bootconfig footer and verifying hdr is within the initrd bounds. 2. Checking that size does not exceed XBC_DATA_MAX and does not exceed the available space between initrd_start and hdr before performing pointer subtraction. Link: https://lore.kernel.org/all/178905333479.213925.1358412668943562406.stgit@devnote2/ Fixes: de462e5f1071 ("bootconfig: Fix to remove bootconfig data from initrd while boot") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260910010137.EE0431F000FF@smtp.kernel.org/ Assisted-by: Antigravity:gemini-3.8-flash Signed-off-by: Masami Hiramatsu (Google) Reviewed-by: Sang-Heon Jeon --- init/main.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/init/main.c b/init/main.c index 2613d3f9b3ce..16749bb7a219 100644 --- a/init/main.c +++ b/init/main.c @@ -277,7 +277,8 @@ static void * __init get_boot_config_from_initrd(size_t *_size) u8 *hdr; int i; - if (!initrd_end) + if (!initrd_end || initrd_end < initrd_start || + initrd_end - initrd_start < BOOTCONFIG_MAGIC_LEN + 8) return NULL; data = (char *)initrd_end - BOOTCONFIG_MAGIC_LEN; @@ -294,16 +295,26 @@ static void * __init get_boot_config_from_initrd(size_t *_size) found: hdr = (u8 *)(data - 8); + if ((unsigned long)hdr < initrd_start) + return NULL; + size = get_unaligned_le32(hdr); csum = get_unaligned_le32(hdr + 4); - data = ((void *)hdr) - size; - if ((unsigned long)data < initrd_start) { - pr_err("bootconfig size %d is greater than initrd size %ld\n", + if (size > XBC_DATA_MAX) { + pr_err("bootconfig size %u is greater than max size %d\n", + size, XBC_DATA_MAX); + return NULL; + } + + if (size > ((unsigned long)hdr - initrd_start)) { + pr_err("bootconfig size %u is greater than initrd size %lu\n", size, initrd_end - initrd_start); return NULL; } + data = ((void *)hdr) - size; + if (xbc_calc_checksum(data, size) != csum) { pr_err("bootconfig checksum failed\n"); return NULL; @@ -394,12 +405,6 @@ static void __init setup_boot_config(void) return; } - if (size >= XBC_DATA_MAX) { - pr_err("bootconfig size %ld greater than max size %d\n", - (long)size, XBC_DATA_MAX); - return; - } - ret = xbc_init(data, size, &msg, &pos); if (ret < 0) { if (pos < 0) From aaaea79efba5a27cb9e0a5a628046d829d3f2cbb Mon Sep 17 00:00:00 2001 From: Lovekesh Solanki Date: Wed, 5 Aug 2026 01:50:31 +0530 Subject: [PATCH 0853/1317] HID: multitouch: Add report ID mismatch quirk for ASUS ROG Z13 Folio Commit e716edafedad ("HID: multitouch: Check to ensure report responses match the request") introduced validating GET_FEATURE responses return the requested report ID. ASUS ROG Z13 Flow (2025) GZ302EA touchpad (USB 0b05:1a30) returns a different report ID for Win8 feature request. Before this check, the response was still processed and allowed device to switch into its full Touchpad Precision mode. After the validation, the response is discarded before hid_report_raw_event() processes it and device remains in fallback mode and no longer exposes ABS_MT_SLOT, ABS_MT_TOOL_TYPE or the multi-finger BTN_TOOL_* capabilities for palm rejection. Add a device quirk to allow the known firmware behavior for this device while preserving report ID validation for all other devices. The device previously matched the generic MT_CLS_WIN_8 entry, so base the new class on MT_CLS_WIN_8 to keep it on the same quirk set as before the regression. MT_QUIRK_CONFIDENCE must be set explicitly: it is normally enabled by the class name check in mt_touch_input_mapping(), which only matches the MT_CLS_WIN_8* names, and it is what makes ABS_MT_TOOL_TYPE available for touchpads. Fixes: e716edafedad ("HID: multitouch: Check to ensure report responses match the request") Signed-off-by: Lovekesh Solanki Reported-by: mayhemandcoffee Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221774 Tested-by: mayhemandcoffee Link: https://bugzilla.kernel.org/show_bug.cgi?id=221774 Signed-off-by: Jiri Kosina --- drivers/hid/hid-multitouch.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 451c7324e6a0..ab4ac261fe76 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_YOGABOOK9I BIT(24) #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) +#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -235,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_YOGABOOK9I 0x0115 #define MT_CLS_EGALAX_P80H84 0x0116 +#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -405,6 +407,16 @@ static const struct mt_class mt_classes[] = { .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_ASUS_CUSTOM_UP }, + { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS | + MT_QUIRK_CONFIDENCE | + MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, + .export_all_inputs = true }, { .name = MT_CLS_VTL, .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | @@ -507,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) { + struct mt_device *td = hid_get_drvdata(hdev); int ret; u32 size = hid_report_len(report); u8 *buf; @@ -528,8 +541,14 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) dev_warn(&hdev->dev, "failed to fetch feature %d\n", report->id); } else { - /* The report ID in the request and the response should match */ - if (report->id != buf[0]) { + /* + * The report ID in the request and the response should match. + * Some firmware (e.g. the ASUS ROG Z13 Folio + * touchpad) returns a mismatched ID on this specific fetch; + * tolerate it only for devices explicitly flagged as such. + */ + if (report->id != buf[0] && + !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { hid_err(hdev, "Returned feature report did not match the request\n"); goto free; } @@ -2715,6 +2734,12 @@ static const struct hid_device_id mt_devices[] = { HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, + /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ + { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, + HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, + /* Generic MT device */ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, From 9eb1a393c89a79c4210230d23e7d88d239c61d7b Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Wed, 26 Aug 2026 08:46:58 -0300 Subject: [PATCH 0854/1317] drm: Fix drm_pending_vblank_event leak in error path for out_fence_ptr When an out_fence_ptr is provided but DRM_MODE_PAGE_FLIP_EVENT is not set, a drm_pending_vblank_event will be allocated. If later, there is an allocation failure or another failure at setup_out_fence(), that event will not have base.fence set and it will not be released at complete_signaling(). Release the event and set crtc_state->event to NULL just like in the DRM_MODE_PAGE_FLIP_EVENT case when there is a failure at drm_event_reserve_init(). That is, prepare_signaling() releases the event and there is nothing to be done at complete_signaling(). Use drm_event_cancel_free() as that will also undo drm_event_reserve_init() in case it has been called. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260727-drm_crtc_atomic_commit_leak-v1-1-23d9948a9d7c@igalia.com?part=1 Fixes: 92c715fca907 ("drm/atomic: Fix double free in drm_atomic_state_default_clear") Signed-off-by: Thadeu Lima de Souza Cascardo Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826-drm_pending_vblank_event_leak-v4-1-f8de8b996b9d@igalia.com --- drivers/gpu/drm/drm_atomic_uapi.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index 5ea593b3a98e..68d59e17ffc2 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -1462,10 +1462,12 @@ static int prepare_signaling(struct drm_device *dev, struct dma_fence *fence; struct drm_out_fence_state *f; + ret = -ENOMEM; + f = krealloc(*fence_state, sizeof(**fence_state) * (*num_fences + 1), GFP_KERNEL); if (!f) - return -ENOMEM; + goto err_free_event; memset(&f[*num_fences], 0, sizeof(*f)); @@ -1474,12 +1476,12 @@ static int prepare_signaling(struct drm_device *dev, fence = drm_crtc_create_fence(crtc); if (!fence) - return -ENOMEM; + goto err_free_event; ret = setup_out_fence(&f[(*num_fences)++], fence); if (ret) { dma_fence_put(fence); - return ret; + goto err_free_event; } crtc_state->event->base.fence = fence; @@ -1535,6 +1537,11 @@ static int prepare_signaling(struct drm_device *dev, } return 0; + +err_free_event: + drm_event_cancel_free(dev, &crtc_state->event->base); + crtc_state->event = NULL; + return ret; } static void complete_signaling(struct drm_device *dev, From a1a5ad37e50ceb192c07ac7e2d7143638cd4d110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Onier?= Date: Wed, 12 Aug 2026 13:13:59 -0400 Subject: [PATCH 0855/1317] HID: winwing: fix use-after-free in force feedback teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit winwing_init_ff() passes the driver's private data, allocated with devm_kzalloc() in winwing_probe(), as the effect context to input_ff_create_memless(). The memoryless force-feedback core takes ownership of that pointer and frees it with kfree() from input_ff_destroy() (ml_ff_destroy()) when the input device is destroyed. Freeing a devm-managed allocation with kfree() is an invalid free, and the same object is then released again by devres when the HID device is torn down, a double free. As the allocation also embeds the LED class devices, their timers and work item live on freed memory and the slab gets corrupted. This triggers on unbind, rmmod, hot-unplug and on system suspend, where the firmware cache walks the now-corrupt devres list. KASAN reports: BUG: KASAN: invalid-free in input_ff_destroy Allocated by task N: winwing_probe Pass NULL as the memless context instead and fetch the driver data from the input device in winwing_play_effect(): the HID core already stores the hid_device as the input device's drvdata. The force-feedback core then owns nothing that it must not free. Fixes: 42d020b54edc ("HID: winwing: Enable rumble effects") Signed-off-by: René Onier Signed-off-by: Jiri Kosina --- drivers/hid/hid-winwing.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c index 9cd25a77999e..19b92c2c6579 100644 --- a/drivers/hid/hid-winwing.c +++ b/drivers/hid/hid-winwing.c @@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) static int winwing_play_effect(struct input_dev *dev, void *context, struct ff_effect *effect) { - struct winwing_drv_data *data = (struct winwing_drv_data *) context; + struct hid_device *hdev = input_get_drvdata(dev); + struct winwing_drv_data *data = hid_get_drvdata(hdev); if (effect->type != FF_RUMBLE) return 0; @@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); - return input_ff_create_memless(hidinput->input, data, + /* + * input_ff_create_memless() takes ownership of the context pointer + * and frees it on teardown; do not hand it the devm-managed drvdata. + * winwing_play_effect() fetches it from the input device instead. + */ + return input_ff_create_memless(hidinput->input, NULL, winwing_play_effect); } From aa9dde93e05a837645fdfd577eea71f0733f0694 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Fri, 14 Aug 2026 15:06:20 +0800 Subject: [PATCH 0856/1317] HID: alps: unregister DualPoint Stick input device on remove alps_input_configured() allocates a second input device ("DualPoint Stick") with input_allocate_device() and registers it, but the alps_driver struct has no .remove handler and input2 is not tracked in hdev->inputs. The default remove path (hid_hw_stop -> hidinput_disconnect) only iterates hdev->inputs, so input2 is never unregistered and leaks on every device removal. Add a .remove handler that stops the device first (preventing URB callbacks from touching input2 during teardown) and then unregisters input2. Fixes: 2562756dde55 ("HID: add Alps I2C HID Touchpad-Stick support") Cc: stable@vger.kernel.org Signed-off-by: Chen Changcheng Signed-off-by: Jiri Kosina --- drivers/hid/hid-alps.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 67179e3fe39b..370635f5b704 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -823,6 +823,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) return 0; } +static void alps_remove(struct hid_device *hdev) +{ + struct alps_dev *data = hid_get_drvdata(hdev); + + /* + * input2 ("DualPoint Stick") is allocated separately and is not + * tracked in hdev->inputs, so the default remove path + * (hid_hw_stop -> hidinput_disconnect) does not unregister it. + * + * Stop the device first so that no URB callback can touch input2 + * while it is being unregistered, then drop it explicitly. + */ + hid_hw_stop(hdev); + + if (data->input2) + input_unregister_device(data->input2); +} + static const struct hid_device_id alps_id[] = { { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, @@ -845,6 +863,7 @@ static struct hid_driver alps_driver = { .input_configured = alps_input_configured, .resume = pm_ptr(alps_post_resume), .reset_resume = pm_ptr(alps_post_reset), + .remove = alps_remove, }; module_hid_driver(alps_driver); From d3aba3442798ce4a4c8ce3104d7b286d61e605f9 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Fri, 14 Aug 2026 15:06:21 +0800 Subject: [PATCH 0857/1317] HID: alps: fix use-after-free on input2 registration failure alps_input_configured() stores data->input2 before calling input_register_device(). If registration fails, input_free_device() frees the input device but data->input2 still points to the freed memory. alps_input_configured() calls hid_hw_open() before allocating input2, so URBs are already active and raw_event can fire during the failure window. A U1_SP_ABSOLUTE_REPORT_ID report arriving then causes u1_raw_event() to dereference the freed data->input2 -> use-after-free. Fix by only storing input2 into drvdata after successful registration and adding a NULL guard in the raw_event path. Fixes: 2562756dde55 ("HID: add Alps I2C HID Touchpad-Stick support") Cc: stable@vger.kernel.org Signed-off-by: Chen Changcheng Signed-off-by: Jiri Kosina --- drivers/hid/hid-alps.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 370635f5b704..0556cb5645eb 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) return 1; case U1_SP_ABSOLUTE_REPORT_ID: + if (!hdata->input2) + return 0; sp_x = get_unaligned_le16(data+2); sp_y = get_unaligned_le16(data+4); @@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) goto exit; } - data->input2 = input2; input2->phys = input->phys; input2->name = "DualPoint Stick"; input2->id.bustype = BUS_I2C; @@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) __set_bit(INPUT_PROP_POINTER, input2->propbit); __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); - if (input_register_device(data->input2)) { + if (input_register_device(input2)) { input_free_device(input2); ret = -ENOENT; goto exit; } + data->input2 = input2; } exit: From 31fe2cb51133f00e1138c617fef8ee808a37714d Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 27 Aug 2026 13:29:49 +0200 Subject: [PATCH 0858/1317] HID: fix semantic patch and improve its performance Replace "expression" with "identifier" in the declaration of hdev. This is necessary because hdev is used as the name of a function parameter. Move the two uses of @p2 to the relevant function names. Convert <... ...>, meaning that the contained pattern is optional, to use ..., when any, and exists. This requires that the function contain calls to hid_hw_start, etc, which reduces the set of files that are considered for matching against this pattern. Reported-by: Ricardo Ribalda Signed-off-by: Julia Lawall Signed-off-by: Jiri Kosina --- scripts/coccinelle/hid/ff_race.cocci | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/coccinelle/hid/ff_race.cocci b/scripts/coccinelle/hid/ff_race.cocci index 479f5d1e3184..e861de00cb10 100644 --- a/scripts/coccinelle/hid/ff_race.cocci +++ b/scripts/coccinelle/hid/ff_race.cocci @@ -7,18 +7,19 @@ virtual report -@r@ +@r exists@ identifier probe_fn; -expression hdev, flags; +identifier hdev; +expression flags; position p1, p2; @@ probe_fn(struct hid_device *hdev, ...) { - <... + ... when any hid_hw_start@p1(hdev, flags) ... - \(input_ff_create\|input_ff_create_memless\)@p2(...) - ...> + \(input_ff_create@p2\|input_ff_create_memless@p2\)(...) + ... when any } @script:python depends on report@ From d5ea0d226e8f0801d78702142a124d78c317d822 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 22:09:26 +0800 Subject: [PATCH 0859/1317] mmc: sh_mmcif: initialize IRQ-thread mutex before requesting interrupt The threaded IRQ handler can run before devm_request_threaded_irq() returns, but thread_lock was initialized afterwards. Initialize it before requesting either interrupt. Fixes: 8047310ee984 ("mmc: sh_mmcif: fix a race, causing an Oops on SMP") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Signed-off-by: Ulf Hansson --- drivers/mmc/host/sh_mmcif.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 7706d01b149e..8bd9cdedafa4 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1461,6 +1461,7 @@ static int sh_mmcif_probe(struct platform_device *pdev) host->pd = pdev; spin_lock_init(&host->lock); + mutex_init(&host->thread_lock); mmc->ops = &sh_mmcif_ops; sh_mmcif_init_ocr(host); @@ -1515,8 +1516,6 @@ static int sh_mmcif_probe(struct platform_device *pdev) goto err_clk; } - mutex_init(&host->thread_lock); - ret = mmc_add_host(mmc); if (ret < 0) goto err_clk; From fce551616dfa4523c15fe50c27aa3baf8afda955 Mon Sep 17 00:00:00 2001 From: Andres Diaz Date: Mon, 31 Aug 2026 23:01:47 -0600 Subject: [PATCH 0860/1317] HID: logitech-hidpp: Add support for G502 X Lightspeed USB mouse The G502 X Lightspeed enumerates as 046d:c098 when connected with its cable. Add the id so the driver handles the wired device. Signed-off-by: Andres Diaz Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 1504de32b1c8..493763a12518 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4924,6 +4924,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, { /* Logitech G502 X Plus Wireless Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC095) }, + { /* Logitech G502 X Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, { /* Logitech G703 Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, { /* Logitech G703 Hero Gaming Mouse over USB */ From 7e749a7972829a53fd1e41568cf2018c180627d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20H=C3=A5kansson?= Date: Tue, 1 Sep 2026 22:30:44 +0200 Subject: [PATCH 0861/1317] HID: steelseries: Add support for Arctis 7 (2018) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The headset reports connection and battery status on HID interface 5. When the device is disconnected, ignore incoming battery reports as they will incorrectly report battery level 0. Clamp overreported battery values to 100% and add USB ID 1038:12ad to the driver's device tables. Signed-off-by: Erik Håkansson Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 3 +- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + drivers/hid/hid-steelseries-arctis.c | 44 ++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a81bf51cbcf1..c43e824428f0 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1210,7 +1210,8 @@ config HID_STEELSERIES depends on USB_HID help Support for Steelseries SRW-S1 steering wheel, and the Steelseries - Arctis 1 Wireless for XBox headset. + Arctis 1 Wireless for XBox, Arctis 7 (2018), Arctis 9, and Arctis + Nova headsets. config HID_SUNPLUS tristate "Sunplus wireless desktop" diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8b4f4b02aec0..874ed8e49302 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1398,6 +1398,7 @@ #define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018 0x12ad #define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c8c6b29fc04d..9ac90b7efcb1 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -752,6 +752,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_STEELSERIES) { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) }, diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c index 23fb0cebd72a..fa33fbd94889 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -101,6 +101,19 @@ static int steelseries_arctis_1_request_status(struct hid_device *hdev) return steelseries_send_output_report(hdev, data, sizeof(data)); } +static int steelseries_arctis_7_2018_request_status(struct hid_device *hdev) +{ + const u8 connection[] = { 0x06, 0x14 }; + const u8 battery[] = { 0x06, 0x18 }; + int ret; + + ret = steelseries_send_output_report(hdev, connection, sizeof(connection)); + if (ret) + return ret; + + return steelseries_send_output_report(hdev, battery, sizeof(battery)); +} + static int steelseries_arctis_9_request_status(struct hid_device *hdev) { const u8 data[] = { 0x00, 0x20 }; @@ -152,6 +165,27 @@ static void steelseries_arctis_1_parse_status(struct steelseries_device *sd, sd->battery_capacity = data[3]; } +static void steelseries_arctis_7_2018_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + if (size < 3 || data[0] != 0x06) + return; + + switch (data[1]) { + case 0x14: + /* 0x03 means that the headset is connected to the transmitter. */ + sd->headset_connected = data[2] == 0x03; + break; + case 0x18: + if (!sd->headset_connected) + break; + + /* The Arctis 7 sometimes overreports battery. Cap to 100. */ + sd->battery_capacity = steelseries_map_capacity(data[2], 0, 100); + break; + } +} + static void steelseries_arctis_9_parse_status(struct steelseries_device *sd, u8 *data, int size) { @@ -232,6 +266,13 @@ static const struct steelseries_device_info arctis_1_info = { .parse_status = steelseries_arctis_1_parse_status, }; +static const struct steelseries_device_info arctis_7_2018_info = { + .sync_interface = 5, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_7_2018_request_status, + .parse_status = steelseries_arctis_7_2018_parse_status, +}; + static const struct steelseries_device_info arctis_9_info = { .sync_interface = 0, .capabilities = SS_CAP_BATTERY, @@ -653,6 +694,9 @@ static const struct hid_device_id steelseries_arctis_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X), .driver_data = (unsigned long)&arctis_1_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018), + .driver_data = (unsigned long)&arctis_7_2018_info }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9), .driver_data = (unsigned long)&arctis_9_info }, From 798514a25544d6978d0bd7fe7071c9bdb5503076 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 11 Sep 2026 08:33:50 +0000 Subject: [PATCH 0862/1317] iommu/amd: Make iommu_sva_set_dev_pasid as static Its used inside pasid.c only. No functional changes. Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 3 --- drivers/iommu/amd/pasid.c | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index a2fe804b038b..71113e860859 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -54,9 +54,6 @@ struct protection_domain *protection_domain_alloc(void); struct iommu_domain *amd_iommu_domain_alloc_sva(struct device *dev, struct mm_struct *mm); void amd_iommu_domain_free(struct iommu_domain *dom); -int iommu_sva_set_dev_pasid(struct iommu_domain *domain, - struct device *dev, ioasid_t pasid, - struct iommu_domain *old); void amd_iommu_remove_dev_pasid(struct device *dev, ioasid_t pasid, struct iommu_domain *domain); diff --git a/drivers/iommu/amd/pasid.c b/drivers/iommu/amd/pasid.c index d708c6532480..40be5902087c 100644 --- a/drivers/iommu/amd/pasid.c +++ b/drivers/iommu/amd/pasid.c @@ -99,9 +99,9 @@ static const struct mmu_notifier_ops sva_mn = { .release = sva_mn_release, }; -int iommu_sva_set_dev_pasid(struct iommu_domain *domain, - struct device *dev, ioasid_t pasid, - struct iommu_domain *old) +static int iommu_sva_set_dev_pasid(struct iommu_domain *domain, + struct device *dev, ioasid_t pasid, + struct iommu_domain *old) { struct pdom_dev_data *pdom_dev_data; struct protection_domain *sva_pdom = to_pdomain(domain); From 5e1afd4ea1d6a9bbaecf3e28707dac9c8b56bd45 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 11 Sep 2026 08:33:51 +0000 Subject: [PATCH 0863/1317] iommu/amd: Remove redundant check in irq_remapping_select() The amd_iommu_irq_remap flag is already validated during irq remapping domain creation (before calling amd_iommu_create_irq_domain()). The duplicate check in irq_remapping_select() is unnecessary and can be removed. Additionally, mark amd_iommu_irq_remap as static. No functional changes. Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu_types.h | 3 --- drivers/iommu/amd/init.c | 2 +- drivers/iommu/amd/iommu.c | 3 --- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index 3dbe20023456..bce5027388b9 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -434,9 +434,6 @@ struct irq_remap_table { u32 *table; }; -/* Interrupt remapping feature used? */ -extern bool amd_iommu_irq_remap; - extern const struct iommu_ops amd_iommu_ops; /* IVRS indicates that pre-boot remapping was enabled */ diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index edcc187b8f14..8a410d4aa370 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -152,7 +152,7 @@ struct ivmd_header { } __attribute__((packed)); bool amd_iommu_dump; -bool amd_iommu_irq_remap __read_mostly; +static bool amd_iommu_irq_remap __read_mostly; enum protection_domain_mode amd_iommu_pgtable = PD_MODE_V1; /* Virtual address size */ diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 4dc306a4b5c6..67a86b9e1eca 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -3976,9 +3976,6 @@ static int irq_remapping_select(struct irq_domain *d, struct irq_fwspec *fwspec, struct amd_iommu *iommu; int devid = -1; - if (!amd_iommu_irq_remap) - return 0; - if (x86_fwspec_is_ioapic(fwspec)) devid = get_ioapic_devid(fwspec->param[0]); else if (x86_fwspec_is_hpet(fwspec)) From 80a4e3ad8daba66915a9bdf0fcae5831cc8dbd5f Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 11 Sep 2026 08:33:52 +0000 Subject: [PATCH 0864/1317] iommu/amd: Remove redundant checks from interrupt handler path PPR and GAlog interrupt is enabled only if buffer is allocated. (See amd_iommu_enable_ppr_log() and iommu_ga_log_enable()). The duplicate check in interrupt hanlder path is unnecessary and can be removed. No functional changes. Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 3 --- drivers/iommu/amd/ppr.c | 3 --- 2 files changed, 6 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 67a86b9e1eca..56262f6b1f70 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1076,9 +1076,6 @@ static void iommu_poll_ga_log(struct amd_iommu *iommu) { u32 head, tail; - if (iommu->ga_log == NULL) - return; - head = readl(iommu->mmio_base + MMIO_GA_HEAD_OFFSET); tail = readl(iommu->mmio_base + MMIO_GA_TAIL_OFFSET); diff --git a/drivers/iommu/amd/ppr.c b/drivers/iommu/amd/ppr.c index 76296079bb8b..2039a9dd71ac 100644 --- a/drivers/iommu/amd/ppr.c +++ b/drivers/iommu/amd/ppr.c @@ -165,9 +165,6 @@ void amd_iommu_poll_ppr_log(struct amd_iommu *iommu) { u32 head, tail; - if (iommu->ppr_log == NULL) - return; - head = readl(iommu->mmio_base + MMIO_PPR_HEAD_OFFSET); tail = readl(iommu->mmio_base + MMIO_PPR_TAIL_OFFSET); From b63c3c26726576e2a87baeee80bc202a5a43c9e5 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 11 Sep 2026 08:33:53 +0000 Subject: [PATCH 0865/1317] iommu/amd: Remove unused macro Remove unsed device range capability related macros. No functional changes. Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu_types.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index bce5027388b9..8241ef922519 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -39,18 +39,6 @@ #define MMIO_RANGE_OFFSET 0x0c #define MMIO_MISC_OFFSET 0x10 -/* Masks, shifts and macros to parse the device range capability */ -#define MMIO_RANGE_LD_MASK 0xff000000 -#define MMIO_RANGE_FD_MASK 0x00ff0000 -#define MMIO_RANGE_BUS_MASK 0x0000ff00 -#define MMIO_RANGE_LD_SHIFT 24 -#define MMIO_RANGE_FD_SHIFT 16 -#define MMIO_RANGE_BUS_SHIFT 8 -#define MMIO_GET_LD(x) (((x) & MMIO_RANGE_LD_MASK) >> MMIO_RANGE_LD_SHIFT) -#define MMIO_GET_FD(x) (((x) & MMIO_RANGE_FD_MASK) >> MMIO_RANGE_FD_SHIFT) -#define MMIO_GET_BUS(x) (((x) & MMIO_RANGE_BUS_MASK) >> MMIO_RANGE_BUS_SHIFT) -#define MMIO_MSI_NUM(x) ((x) & 0x1f) - /* Used offsets into the MMIO space */ #define MMIO_DEV_TABLE_OFFSET 0x0000 #define MMIO_CMD_BUF_OFFSET 0x0008 @@ -247,7 +235,6 @@ /* constants to configure the command buffer */ #define CMD_BUFFER_SIZE 8192 -#define CMD_BUFFER_UNINITIALIZED 1 #define CMD_BUFFER_ENTRIES 512 #define MMIO_CMD_SIZE_SHIFT 56 #define MMIO_CMD_SIZE_512 (0x9ULL << MMIO_CMD_SIZE_SHIFT) From d76994443eed0af297cb82bd038ae9d76327ef90 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Wed, 2 Sep 2026 12:45:49 +0300 Subject: [PATCH 0866/1317] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Extend critical section in roccat_connect() to ensure that partially initialized 'struct roccat_device' is never exposed in 'devices' list, and do the same in roccat_disconnect() to avoid racy 'devices' access against roccat_release(). Signed-off-by: Dmitry Antipov Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index 4f15eb951039..5deb6da8d4f7 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report return temp; } - mutex_unlock(&devices_lock); - init_waitqueue_head(&device->wait); INIT_LIST_HEAD(&device->readers); mutex_init(&device->readers_lock); @@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report device->cbuf_end = 0; device->report_size = report_size; + mutex_unlock(&devices_lock); return minor; } EXPORT_SYMBOL_GPL(roccat_connect); @@ -369,15 +368,12 @@ void roccat_disconnect(int minor) mutex_lock(&devices_lock); device = devices[minor]; - mutex_unlock(&devices_lock); device->exist = 0; /* TODO exist maybe not needed */ device_destroy(device->dev->class, MKDEV(roccat_major, minor)); - mutex_lock(&devices_lock); devices[minor] = NULL; - mutex_unlock(&devices_lock); if (device->open) { hid_hw_close(device->hid); @@ -385,6 +381,8 @@ void roccat_disconnect(int minor) } else { roccat_free_device(device); } + + mutex_unlock(&devices_lock); } EXPORT_SYMBOL_GPL(roccat_disconnect); From 39e8e085710a2d36d9ceade11739fb7b87f5aeb0 Mon Sep 17 00:00:00 2001 From: Oleg Keri Date: Thu, 10 Sep 2026 08:30:50 +0200 Subject: [PATCH 0867/1317] HID: i2c-hid: add reset quirk for Lenovo Yoga Slim 7x Gen 11 keyboard The ITE controller behind the keyboard of the Lenovo Yoga Slim 7x Gen 11 (048d:83db) carries out a reset but never raises the interrupt that acknowledges it. i2c_hid_finish_hwreset() therefore waits out its full one second timeout and logs "device did not ack reset within 1000 ms" on every probe and every resume, before the keyboard comes up regardless. Set I2C_HID_QUIRK_NO_IRQ_AFTER_RESET for it, as is already done for several other ITE parts, so the reset is followed by a fixed 100 ms sleep instead. Signed-off-by: Oleg Keri Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/i2c-hid/i2c-hid-core.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 874ed8e49302..06a1309589e3 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -765,6 +765,7 @@ #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 +#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 0ff07fdab442..ad8d9f329404 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -126,6 +126,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, From abd24922c2a9797d6184be0561dd85e7bcdfd091 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Fri, 4 Sep 2026 10:58:00 +0000 Subject: [PATCH 0868/1317] HID: hid-oxp: use cancel_delayed_work_sync() in remove oxp_hid_remove() uses cancel_delayed_work() for all three delayed work items. cancel_delayed_work() only dequeues a pending work item without waiting for a currently executing callback to finish. If any of the work callbacks (oxp_rgb_queue_fn, oxp_btn_queue_fn, oxp_mcu_init_fn) is running at the time of removal, the callback continues executing concurrently with hid_hw_close() and hid_hw_stop(), accessing the HID device after it has been closed and stopped. Use cancel_delayed_work_sync() instead to ensure that any in-progress work callback completes before device teardown proceeds. Fixes: 84910c459d65 ("HID: hid-oxp: Add OneXPlayer configuration driver") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Reviewed-by: Derek J. Clark Link: https://lore.kernel.org/r/20260804-oxp-fix-v2-1-b2d56e4c8a2c@cherr.cc Link: https://lore.kernel.org/r/20260804-oxp-fix-v1-1-51a4fe787167@cherr.cc Signed-off-by: Jiri Kosina --- drivers/hid/hid-oxp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index d2ded6b08ce9..1e691ebc1199 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev, static void oxp_hid_remove(struct hid_device *hdev) { - cancel_delayed_work(&drvdata.oxp_rgb_queue); - cancel_delayed_work(&drvdata.oxp_btn_queue); - cancel_delayed_work(&drvdata.oxp_mcu_init); + cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); + cancel_delayed_work_sync(&drvdata.oxp_btn_queue); + cancel_delayed_work_sync(&drvdata.oxp_mcu_init); hid_hw_close(hdev); hid_hw_stop(hdev); } From 58d97b45f3f43af0afd9c74e8480d22d36fd3f72 Mon Sep 17 00:00:00 2001 From: Youth Cao Date: Fri, 4 Sep 2026 00:14:44 +0800 Subject: [PATCH 0869/1317] HID: i2c-hid: Add i2c-hid-quirk-bad-input-size quirk for 0911:5288 device I have recently acquired a cheap Apollo Lake-based laptop that uses a Hynitron CST128-A touchpad controller. While booting from a Debian LiveCD, the kernel log is flooded with the following error (though the touchpad works well): i2c_hid_acpi i2c-ALPS0001:00: i2c_hid_get_input: incomplete report (27/42405) The CST128-A was identified via ACPI as ALPS0001:00, and the I2C HID device ID (0911:5288) was shared with the Hantick 5288. Add the I2C_HID_QUIRK_BAD_INPUT_SIZE quirk option to the existing Hantick 5288 quirk entry to suppress the kernel log flood. Signed-off-by: Youth Cao Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index ad8d9f329404..a61fd1de5203 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -123,7 +123,7 @@ static const struct i2c_hid_quirks { __u32 quirks; } i2c_hid_quirks[] = { { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, - I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, From 1d00442cc4699accfaa2317fb18259ddd7556f9a Mon Sep 17 00:00:00 2001 From: Stuart Hayhurst Date: Tue, 18 Aug 2026 14:00:53 +0100 Subject: [PATCH 0870/1317] HID: corsair-void: Fix firmware event packet description The size was incorrectly stated as 4 bytes since the ID was missed out. Add the ID in and correct the indices for the firmware versions. Signed-off-by: Stuart Hayhurst Signed-off-by: Jiri Kosina --- drivers/hid/hid-corsair-void.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c index 071a663a6c26..7be8c974d11e 100644 --- a/drivers/hid/hid-corsair-void.c +++ b/drivers/hid/hid-corsair-void.c @@ -51,20 +51,23 @@ /* Receiver report information: (ID 102) */ /* -------------------------------------------------------------------------- */ /* - * When queried, the recevier responds with 4 bytes to describe the firmware - * The first 2 bytes are for the receiver, the second 2 are the headset + * When queried, the receiver responds with 5 bytes to describe the firmware + * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset * The headset firmware version will be 0 if no headset is connected * - * 0: Recevier firmware major version + * 0: Report ID + * 102 for the firmware packet + * + * 1: Receiver firmware major version * Major version of the receiver's firmware * - * 1: Recevier firmware minor version + * 2: Receiver firmware minor version * Minor version of the receiver's firmware * - * 2: Headset firmware major version + * 3: Headset firmware major version * Major version of the headset's firmware * - * 3: Headset firmware minor version + * 4: Headset firmware minor version * Minor version of the headset's firmware */ /* -------------------------------------------------------------------------- */ From f3c2b266b8fd7cf816d36168d19f67e716f2c080 Mon Sep 17 00:00:00 2001 From: Berke Durak Date: Mon, 7 Sep 2026 11:47:19 -0700 Subject: [PATCH 0871/1317] HID: elecom: Add support for ELECOM M-XT4DRBK (018E) The 2025 revision of the left-handed EX-G wireless trackball (M-XT4DRBK-G) reports USB ID 056e:018e instead of 056e:00fd. Its report descriptor declares an 8-bit button field (Report Count 8) but only five usages (Usage Maximum 5), so the sixth (Fn) button ends up as a duplicate of button 5 and is unusable. The report descriptor has the same layout as the M-XT3DRBK 018C (button usage maximum at offset 16, button report count at 22, button report size at 24, padding report size at 30), so reuse that fixup. Rename the existing M_XT4DRBK define to M_XT4DRBK_00FD to match the convention used for the other EX-G revisions. Tested on a Raspberry Pi 5 with the same fixup in an out-of-tree module on 6.12 (6.12.96+rpt-rpi-2712); all six buttons are reported after the fix, and they work (tested with xev as well). Report descriptor as sent by the device, pre-fix (056e:018e, 215 bytes): 05 01 09 02 A1 01 09 01 A1 00 85 01 05 09 19 01 29 05 15 00 25 01 95 08 75 01 81 02 95 01 75 00 81 01 05 01 09 30 09 31 16 00 80 26 FF 7F 75 10 95 02 81 06 C0 A1 00 05 01 09 38 15 81 25 7F 75 08 95 01 81 06 C0 A1 00 05 0C 0A 38 02 95 01 75 08 15 81 25 7F 81 06 C0 C0 06 01 FF 09 00 A1 01 85 02 09 00 15 00 26 FF 00 75 08 95 07 81 02 C0 05 0C 09 01 A1 01 85 05 15 00 26 3C 02 19 00 2A 3C 02 75 10 95 01 81 00 C0 05 01 09 80 A1 01 85 03 19 81 29 83 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 C0 06 BC FF 09 88 A1 01 85 04 95 01 75 08 15 00 26 FF 00 19 00 2A FF 00 81 00 C0 06 02 FF 09 02 A1 01 85 06 09 02 15 00 26 FF 00 75 08 95 07 B1 02 C0 Assisted-by: LLM Signed-off-by: Berke Durak Signed-off-by: Jiri Kosina --- drivers/hid/hid-elecom.c | 6 ++++-- drivers/hid/hid-ids.h | 3 ++- drivers/hid/hid-quirks.c | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 37d88ce57f67..d21ead40bb07 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: - case USB_DEVICE_ID_ELECOM_M_XT4DRBK: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: /* * Report descriptor format: * 12: button bit count @@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); break; case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: /* * Report descriptor format: * 22: button bit count @@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 06a1309589e3..9a473f72c8cb 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -477,7 +477,8 @@ #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c -#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 9ac90b7efcb1..8c4b7580a6ee 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -432,7 +432,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, From 8e2a4b458ad25e13422bb059758c30a6562aa9cf Mon Sep 17 00:00:00 2001 From: Oscar Priego Verdugo Date: Mon, 17 Aug 2026 06:05:46 -0600 Subject: [PATCH 0872/1317] HID: elecom: fix bus type for M-XGL20DLBK The M-XGL20DLBK is matched as a USB device by hid-elecom, but its entry in hid_have_special_driver[] uses HID_BLUETOOTH_DEVICE. This prevents the special-driver quirk entry from matching the USB device handled by hid-elecom. Use HID_USB_DEVICE there as well. Fixes: 55633e681afb ("HID: elecom: add support for EX-G M-XGL20DLBK wireless mouse") Signed-off-by: Oscar Priego Verdugo Signed-off-by: Jiri Kosina --- drivers/hid/hid-quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 8c4b7580a6ee..96a36c5ba04f 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -426,7 +426,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #endif #if IS_ENABLED(CONFIG_HID_ELECOM) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, From a5c41fa7f925fda2db394329fa0b26243fa63a81 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:48 -0400 Subject: [PATCH 0873/1317] Revert "nouveau/gsp: fix suspend/resume regression on r570 firmware" This reverts commit 8302d0afeaec0bc57d951dd085e0cffe997d4d18. It turns out this looked like the right fix on some systems, but it's not - as this causes runtime PM to actually fail on many a laptop. Fixes: 8302d0afeaec ("nouveau/gsp: fix suspend/resume regression on r570 firmware") Cc: # v6.19+ Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-2-lyude@redhat.com (cherry picked from commit 94097122bfd701976bc1a62ccd434c13f3f67cde) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 8 ++++---- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c index f128330f30d7..40bf83ea33ac 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c @@ -208,7 +208,7 @@ r535_fbsr_resume(struct nvkm_gsp *gsp) } static int -r535_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) +r535_fbsr_suspend(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index f544afa12b6b..4a3b771ded25 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1749,7 +1749,7 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) sr->sysmemAddrOfSuspendResumeData = gsp->sr.radix3.lvl0.addr; sr->sizeOfSuspendResumeData = len; - ret = rm->api->fbsr->suspend(gsp, suspend == NVKM_RUNTIME_SUSPEND); + ret = rm->api->fbsr->suspend(gsp); if (ret) { nvkm_gsp_mem_dtor(&gsp->sr.meta); nvkm_gsp_radix3_dtor(gsp, &gsp->sr.radix3); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 8ef8b4f65588..2945d5b4e570 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -62,7 +62,7 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) } static int -r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtime) +r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) { NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS *ctrl; struct nvkm_gsp_object memlist; @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtim ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = runtime ? 1 : 0; + ctrl->bEnteringGcoffState = 1; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) @@ -92,7 +92,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtim } static int -r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) +r570_fbsr_suspend(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; @@ -133,7 +133,7 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) return ret; /* Initialise FBSR on RM. */ - ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size, runtime); + ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size); if (ret) { nvkm_gsp_sg_free(device, &gsp->sr.fbsr); return ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index fcd0221dcea1..e9ac47d86b69 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -79,7 +79,7 @@ struct nvkm_rm_api { } *device; const struct nvkm_rm_api_fbsr { - int (*suspend)(struct nvkm_gsp *, bool runtime); + int (*suspend)(struct nvkm_gsp *); void (*resume)(struct nvkm_gsp *); } *fbsr; From 12f6eff11ccf9cad3b2dfcdd94184fdb9ffface2 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:49 -0400 Subject: [PATCH 0874/1317] drm/nouveau/gsp/r570: Set GcOff = 0 in fbsr Previously, it looked as if we were able to fix suspend/resume on some desktops by setting Gcoff based on whether or not we were entering runtime PM. This was a mistake though - the only time suspend/resume would end up actually working was if Gcoff = 0. It seems like it's likely the main reason for this is the FBSR GcOff argument actually controls GSP's behavior with regards to which buffers it decides to save across suspend/resume. When GcOff = 1, RM reserved regions are saved unless they are marked as LOST_ON_SUSPEND, and RM channel-context and kernel-client buffers are also saved -including- when they are LOST_ON_SUSPEND. This means with GcOff = 1, we end up having GSP save and restore buffers that actually need to be reinitialized on resume - causing the failures we're setting. Thanks to John Hubbard from Nvidia for providing some background on what these options do in the GSP firmware do! Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-3-lyude@redhat.com (cherry picked from commit c7abe771e013848970421e5ca29c6b2f05c31965) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 2945d5b4e570..af5aa5065c3d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = 1; + ctrl->bEnteringGcoffState = 0; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) From 24fbd6d4bcf3363ef13ebe0d36dea93f30396c6d Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:50 -0400 Subject: [PATCH 0875/1317] drm/nouveau/gsp/r570: Enable S/R Display workaround in GSP There's two flags that we've never been setting when asking GSP to suspend the GPU, which OpenRM does set: GPU_STATE_FLAGS_PRESERVING GPU_STATE_FLAGS_PM_TRANSITION These flags aren't -supposed- to do much in GSP, they're mostly used by OpenRM itself for state tracking. The only thing they do from GSP's side is control whether or not a single display related workaround is applied during suspend. But as it turns out, that single workaround is actually quite crucial for getting runtime PM working with nouveau - and without it set we end up seeing a lot more failures with runtime PM resume. So, let's start setting it. Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-4-lyude@redhat.com (cherry picked from commit ca57629b3eb912c77bc4357178a2130ea6c2d6df) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 3 ++- .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 1488771c63fc..b45781cd0dfd 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -207,7 +207,8 @@ r570_gsp_set_rmargs(struct nvkm_gsp *gsp, bool resume) args->srInitArguments.bInPMTransition = 0; } else { args->srInitArguments.oldLevel = NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3; - args->srInitArguments.flags = 0; + args->srInitArguments.flags = + GPU_STATE_FLAGS_PRESERVING | GPU_STATE_FLAGS_PM_TRANSITION; args->srInitArguments.bInPMTransition = 1; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h index b6075021e74f..c458569af9d7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h @@ -523,6 +523,14 @@ typedef struct #define NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3 (0x00000003U) +#define GPU_STATE_FLAGS_PRESERVING BIT(0) // GPU state is preserved +#define GPU_STATE_FLAGS_VGA_TRANSITION BIT(1) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_PM_TRANSITION BIT(2) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_PM_SUSPEND BIT(3) +#define GPU_STATE_FLAGS_PM_HIBERNATE BIT(4) +#define GPU_STATE_FLAGS_GC6_TRANSITION BIT(5) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_FAST_UNLOAD BIT(6) // Used during windows restart, skips stateDestroy steps + typedef struct { // Magic for verification by secure ucode From bbb9293c9bb792f3f16c842f223b8c97bdbaf227 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:51 -0400 Subject: [PATCH 0876/1317] drm/nouveau/gsp: Increase delay for magic sleep in r535_gsp_fini() As it turns out, Turing isn't the only architecture that needs this. On this Dell Precision 7780 with an AD103 GPU, along with pretty much every other laptop I tested, runtime PM is still somewhat unreliable. At first glance it seems as if it's fixed, but lowering the autosuspend delay to 500ms and then doing a stress test of suspend/resume cycles on the GPU ends up causing everything to start timing out. After quite a lot of digging, I eventually landed back on this magic timeout in r535_gsp_fini(). As it turns out, increasing the timeout ends up fixing the runtime PM issues as far as I can tell, even during intense stress testing. Unfortunately after spending quite a bit of time trying to dig through OpenRM to figure out what this magic sleep is actually doing, I've also come up short with any reasonable explanation. In lieu of that, I'm going to include the observations I did make while trying to figure this out in hopes someone eventually does figure this out: * The magic sleep has to occur after fbsr is initialized. Performing it at any time before that doesn't appear to work. * In situations where runtime PM starts getting flaky, some rather interesting visual effects end up happening on occasion before the GPU fully falls over. In particular, squares that look like the result of an incomplete blitting operation to a tiled buffer end up showing up on applications like vkcube. Interestingly enough, they remain in precisely the same place between runtime PM cycles until the GPU falls over - even when restarting vkcube multiple times, and even when vkcube is actively updating the screen. Even more interestingly, they're not limited to a specific framebuffer - you can see the squares changing as the cube rotates around. We cannot however, say that this is likely to be a incomplete fbsr operation. The magic sleep happens before fbsr is actually saved (which happens on the GSP unload), so it's something else. * During a short bit of testing with a desktop that I have, the magic sleep seemed to make no difference to whether or not suspend/resume works. It seems to generally work almost always. So we can assume this is likely exclusive to runtime PM, not S3. As well, here's a list of the things I tried before settling on the magic sleep: * Hooking up NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE and then blocking runtime PM until OpenRM signals that GC6/GCOFF is ready appears to make no difference. * Hooking up some (maybe not all, unsure about that part) bits of comptag saving including: * Fetching static memsys information from GSP * Adding the size of the comptag storage to the fbsr data * Adding a GA103+ workaround for disabling raw compression mode during fbsr (it doesn't seem like it applies for any systems I tried it on anyhow) * Setting bPreserveVideoMemoryAllocations=1 in GspSystemInfo So, until we can figure this out properly - just sleep for longer. Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-5-lyude@redhat.com (cherry picked from commit 09b47186a4164f3aaa3591313f80794443117342) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 4a3b771ded25..94925f1590ea 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1761,8 +1761,12 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) * TODO: Debug the GSP firmware / RPC handling to find out why * without this Turing (but none of the other architectures) * ends up resetting all channels after resume. + * Additionally, runtime suspend on other architectures quickly + * becomes unreliable without this sleep. If you're experiencing + * issues with runtime suspend, try bumping this delay up and + * sending a patch if it fixes your GPU. */ - msleep(50); + msleep(200); } ret = r535_gsp_rpc_unloading_guest_driver(gsp, suspend); From 2deb753127d7b7035e893955c5e91875e767d1f8 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Fri, 4 Sep 2026 19:52:23 +0800 Subject: [PATCH 0877/1317] tracing/user_events: Don't destroy fields when event removal fails destroy_user_event() destroys the event's fields before attempting to remove the trace event call. If user_event_set_call_visible() fails, e.g. because the event is still enabled and trace_remove_event_call() returns -EBUSY, the event is left registered with an irreversibly destroyed field list. Any subsequent interaction with the event then operates on an empty field list while it is still fully visible in tracefs. Move the field destruction after the call removal, and splice the field list back onto the event when the removal fails so the event remains in a consistent state. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260904115223.2976446-1-bsdhenrymartin@gmail.com Fixes: 7f5a08c79df35 ("user_events: Add minimal support for trace_event into ftrace") Signed-off-by: Henry Martin Reviewed-by: Beau Belgrave Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_user.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 93cda2f6f269..f658c3a77aa7 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -1122,10 +1122,9 @@ static void user_event_destroy_validators(struct user_event *user) } } -static void user_event_destroy_fields(struct user_event *user) +static void user_event_destroy_fields(struct list_head *head) { struct ftrace_event_field *field, *next; - struct list_head *head = &user->fields; list_for_each_entry_safe(field, next, head, link) { list_del(&field->link); @@ -1502,17 +1501,32 @@ static int user_event_set_call_visible(struct user_event *user, bool visible) static int destroy_user_event(struct user_event *user) { + LIST_HEAD(fields); int ret = 0; lockdep_assert_held(&event_mutex); - /* Must destroy fields before call removal */ - user_event_destroy_fields(user); + /* + * Detach the fields before removing the call. Removing the event + * frees the field list memory (trace_destroy_fields() is run on + * successful removal and kmem_cache_free()s the fields), but the + * fields here are allocated and owned by user_events. Destroy + * them separately once removal has succeeded. + */ + list_splice_init(&user->fields, &fields); ret = user_event_set_call_visible(user, false); - if (ret) + if (ret) { + /* + * Removal failed and the event stays registered, recover + * the fields so it is left in a consistent state. + */ + list_splice(&fields, &user->fields); return ret; + } + + user_event_destroy_fields(&fields); dyn_event_remove(&user->devent); hash_del(&user->node); @@ -2212,7 +2226,7 @@ static int user_event_parse(struct user_event_group *group, char *name, put_user_lock: mutex_unlock(&event_mutex); put_user: - user_event_destroy_fields(user); + user_event_destroy_fields(&user->fields); user_event_destroy_validators(user); kfree(user->call.print_fmt); From 08cacffeef8f64f1a222c93467ca84f24a46c953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sat, 22 Aug 2026 19:53:22 +0000 Subject: [PATCH 0878/1317] ftrace: fork: Initialize function graph state before copy_exec_state() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dup_task_struct() copies the parent's task_struct, including ret_stack. ftrace_graph_init_task() clears the copied function graph state, but it currently runs after copy_exec_state(). For non-CLONE_VM forks, copy_exec_state() allocates a new task_exec_state. If that allocation fails, copy_process() reaches bad_fork_free and free_task() calls ftrace_graph_exit_task(). Since the child still carries the parent's ret_stack pointer, the unwind frees the parent's active function graph return stack. The parent subsequently accesses freed memory from function_graph_enter_regs(). KASAN reports: [ 22.190920] ================================================================== [ 22.195899] BUG: KASAN: slab-use-after-free in function_graph_enter_regs+0xa76/0xb90 [ 22.200747] Write of size 8 at addr ff110000054dc0a8 by task repro/1 [ 22.205134] [ 22.210770] CPU: 0 UID: 0 PID: 1 Comm: repro Not tainted 7.2.0-07732-g9328b3b03bdc-dirty #3 PREEMPT(lazy) [ 22.212576] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 22.213750] Call Trace: [ 22.215271] [ 22.216242] ? ftrace_stub_direct_tramp+0x10/0x10 [ 22.217774] dump_stack_lvl+0x4e/0x70 [ 22.220531] print_report+0x157/0x4b4 [ 22.223202] ? fixup_red_left+0x9/0x30 [ 22.224407] ? complete_report_info+0x83/0x110 [ 22.226679] ? function_graph_enter_regs+0xa76/0xb90 [ 22.228084] kasan_report+0xce/0x100 [ 22.230109] ? function_graph_enter_regs+0xa76/0xb90 [ 22.232860] ? stack_trace_save+0x4/0xd0 [ 22.234156] function_graph_enter_regs+0xa76/0xb90 [ 22.236090] ? kasan_save_stack+0x30/0x50 [ 22.237752] ? __pfx_function_graph_enter_regs+0x10/0x10 [ 22.238694] ? ring_buffer_lock_reserve+0x345/0xf80 [ 22.239628] ? stack_trace_save+0x4/0xd0 [ 22.242121] ? stack_trace_save+0x4/0xd0 [ 22.243588] ftrace_graph_func+0xda/0x160 [ 22.245362] ? ftrace_stub_direct_tramp+0x10/0x10 [ 22.246520] 0xffffffffa0000095 [ 22.250528] ? stack_trace_save+0x9/0xd0 [ 22.251757] ? ring_buffer_unlock_commit+0x11d/0x5c0 [ 22.253152] stack_trace_save+0x9/0xd0 [ 22.254264] kasan_save_stack+0x30/0x50 [ 22.273631] kasan_save_track+0x14/0x30 [ 22.276763] kasan_save_free_info+0x3b/0x70 [ 22.278296] __kasan_slab_free+0x43/0x70 [ 22.280157] kmem_cache_free+0xbf/0x3b0 [ 22.282963] ? ftrace_stub_direct_tramp+0x10/0x10 [ 22.284001] free_task+0xa2/0x160 [ 22.285699] ? ftrace_stub_direct_tramp+0x10/0x10 [ 22.286752] copy_process+0x2aae/0x7bc0 Initialize the child function graph state immediately after dup_task_struct(), before the first fallible operation. Cc: stable@vger.kernel.org Fixes: 6b1c66c9cca9 ("exec_state: relocate dumpable information") Reviewed-by: Bradley Morgan Link: https://patch.msgid.link/20260822195321.962383-2-Jeremy.Jean@oss.cyber.gouv.fr Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Steven Rostedt --- kernel/fork.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index 416758c8a3d4..a5934a317634 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2133,6 +2133,11 @@ __latent_entropy struct task_struct *copy_process( p = dup_task_struct(current, node); if (!p) goto fork_out; + /* + * Must run before the first fallible op, so error paths never + * free the parent's ret_stack. + */ + ftrace_graph_init_task(p); retval = copy_exec_state(clone_flags, p); if (retval) goto bad_fork_free; @@ -2159,8 +2164,6 @@ __latent_entropy struct task_struct *copy_process( */ p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? args->child_tid : NULL; - ftrace_graph_init_task(p); - rt_mutex_init_task(p); raw_spin_lock_init(&p->blocked_lock); From b22845487096247f5370c412376a472304627847 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sun, 6 Sep 2026 06:19:22 +0900 Subject: [PATCH 0879/1317] fgraph: Remove unused FGRAPH_MAX_INDEX FGRAPH_MAX_INDEX has no user, and it expands to FGRAPH_INDEX_SIZE and FGRAPH_RET_INDEX, neither of which is defined anywhere in the tree. It was added in that form by commit 91c46b0aa917 ("function_graph: Implement fgraph_reserve_data() and fgraph_retrieve_data()"), which introduced the current data word layout under new names, so anything referencing it would have failed to build ever since. Remove it. Link: https://patch.msgid.link/20260905211922.1196366-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/fgraph.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/kernel/trace/fgraph.c b/kernel/trace/fgraph.c index 40d373d65f9b..ed455b53513b 100644 --- a/kernel/trace/fgraph.c +++ b/kernel/trace/fgraph.c @@ -143,9 +143,6 @@ enum { #define FGRAPH_DATA_INDEX_MASK GENMASK(FGRAPH_DATA_INDEX_BITS - 1, 0) #define FGRAPH_DATA_INDEX_SHIFT (FGRAPH_DATA_SHIFT + FGRAPH_DATA_BITS) -#define FGRAPH_MAX_INDEX \ - ((FGRAPH_INDEX_SIZE << FGRAPH_DATA_BITS) + FGRAPH_RET_INDEX) - #define FGRAPH_ARRAY_SIZE FGRAPH_INDEX_BITS /* From 0701995aaf8fc2281154db829ca85e231951e51d Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sun, 6 Sep 2026 12:44:06 +0900 Subject: [PATCH 0880/1317] function_graph: Use the saved entry's size when reprinting it When a graph entry does not fit in the trace_seq, print_graph_entry() saves it in the iterator's fgraph_data and reprints it on the next read. The entry has already been consumed from the ring buffer by then, so the copy is all that is left of it. The copy is sized with iter->ent_size, which no longer describes the saved entry but whatever entry the iterator has moved on to. The argument count is derived from the same field, so a 72 byte entry saved and then reprinted ahead of a 48 byte return entry loses its arguments. Record the size next to the failure flag, so that the two are always set together, and restore it before reprinting. Cc: stable@vger.kernel.org Fixes: ff5c9c576e75 ("ftrace: Add support for function argument to graph tracer") Link: https://patch.msgid.link/20260906034406.1335316-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_functions_graph.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/trace/trace_functions_graph.c b/kernel/trace/trace_functions_graph.c index ff7cb1a76b95..c5befd0c5b9a 100644 --- a/kernel/trace/trace_functions_graph.c +++ b/kernel/trace/trace_functions_graph.c @@ -52,6 +52,7 @@ struct fgraph_data { }; struct ftrace_graph_ret_entry ret; int failed; + int ent_size; int cpu; }; @@ -1274,6 +1275,7 @@ print_graph_entry(struct ftrace_graph_ent_entry *field, struct trace_seq *s, if (s->full) { data->failed = 1; data->cpu = cpu; + data->ent_size = iter->ent_size; } else data->failed = 0; } @@ -1457,6 +1459,7 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags) if (data && data->failed) { field = &data->ent.ent; iter->cpu = data->cpu; + iter->ent_size = data->ent_size; ret = print_graph_entry(field, s, iter, flags); if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) { per_cpu_ptr(data->cpu_data, iter->cpu)->ignore = 1; From 4bddcb346a6cf4615ca77f69a589623b877ca267 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sun, 6 Sep 2026 21:40:25 +0900 Subject: [PATCH 0881/1317] tracing: Free histogram var refs regardless of how often they are referenced Using the same variable three or more times in one hist trigger leaks the variable reference and its strings when the trigger is removed. commit 656fe2ba85e8 ("tracing: Use hist trigger's var_ref array to destroy var_refs") made a trigger's var_refs[] array the only owner of a var ref: destroy_hist_field() returns early for HIST_FIELD_FL_VAR_REF, so the field expressions never destroy one. One entry, freed once, no count needed. commit 8bcebc77e85f ("tracing: Fix histogram code when expression has same var as value") then made repeated references share one object and added a count of them. Only the increment side exists, since those expressions still return early and never drop a reference, so __destroy_hist_field() sees how many references were created rather than how many are left. It frees when the decremented count is 0 or 1, so two references work and three or more leak. Sharing kept one array entry per object, and create_var_ref() searches and appends within a single trigger, so nothing outside it holds the object. Removing a trigger whose variables are still referenced is already refused by check_var_refs() with -EBUSY. Drop the count and free unconditionally. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260906124025.3550596-1-donggeunyoo.kernel@gmail.com Fixes: 8bcebc77e85f ("tracing: Fix histogram code when expression has same var as value") Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 963e0d6b61fd..f90680b33a37 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -169,7 +169,6 @@ struct hist_field { struct hist_field *operands[HIST_FIELD_OPERANDS_MAX]; struct hist_trigger_data *hist_data; enum hist_field_fn fn_num; - unsigned int ref; unsigned int size; unsigned int offset; unsigned int is_signed; @@ -1913,16 +1912,8 @@ static int contains_operator(char *str, char **sep) return field_op; } -static void get_hist_field(struct hist_field *hist_field) -{ - hist_field->ref++; -} - static void __destroy_hist_field(struct hist_field *hist_field) { - if (--hist_field->ref > 1) - return; - kfree(hist_field->var.name); kfree(hist_field->name); @@ -1969,8 +1960,6 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, if (!hist_field) return NULL; - hist_field->ref = 1; - hist_field->hist_data = hist_data; if (flags & HIST_FIELD_FL_EXPR || flags & HIST_FIELD_FL_ALIAS) @@ -2223,10 +2212,8 @@ static struct hist_field *create_var_ref(struct hist_trigger_data *hist_data, for (i = 0; i < hist_data->n_var_refs; i++) { ref_field = hist_data->var_refs[i]; if (ref_field->var.idx == var_field->var.idx && - ref_field->var.hist_data == var_field->hist_data) { - get_hist_field(ref_field); + ref_field->var.hist_data == var_field->hist_data) return ref_field; - } } /* Sanity check to avoid out-of-bound write on 'hist_data->var_refs' */ if (hist_data->n_var_refs >= TRACING_MAP_VARS_MAX) @@ -3276,7 +3263,6 @@ static struct hist_field *create_var(struct hist_trigger_data *hist_data, goto out; } - var->ref = 1; var->flags = HIST_FIELD_FL_VAR; var->var.idx = idx; var->var.hist_data = var->hist_data = hist_data; From 516001d53e6b2ea95a251ee2ef54a1a689a3fd58 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sun, 6 Sep 2026 22:33:52 +0900 Subject: [PATCH 0882/1317] tracing: Free histogram the var ref when its initialization fails create_var_ref() allocates a VAR_REF hist_field and then calls init_var_ref() to fill it in. When that fails the field is leaked. commit 656fe2ba85e8 ("tracing: Use hist trigger's var_ref array to destroy var_refs") made destroy_hist_field() return early for HIST_FIELD_FL_VAR_REF, since var refs are freed by walking the trigger's var_refs[] array instead. create_var_ref() adds the field to that array only after init_var_ref() has succeeded, so on this path the field is in neither place and nothing frees it. The call was correct when it was written, before var refs were taken out of destroy_hist_field(). init_var_ref() cannot free it either. The caller owns the field, so init_var_ref() undoes only its own string allocations and leaves the field alone. Freeing it there would leave create_var_ref() passing freed memory to destroy_hist_field(), which reads its flags. Call __destroy_hist_field(), which frees the field without consulting the flag. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260906133352.3815019-1-donggeunyoo.kernel@gmail.com Fixes: 656fe2ba85e8 ("tracing: Use hist trigger's var_ref array to destroy var_refs") Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index f90680b33a37..bbdd56208eff 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -2221,7 +2221,7 @@ static struct hist_field *create_var_ref(struct hist_trigger_data *hist_data, ref_field = create_hist_field(var_field->hist_data, NULL, flags, NULL); if (ref_field) { if (init_var_ref(ref_field, var_field, system, event_name)) { - destroy_hist_field(ref_field, 0); + __destroy_hist_field(ref_field); return NULL; } From 230234d12ce42ab04132a32c3a848f07a5d27a71 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 12:49:48 +0900 Subject: [PATCH 0883/1317] tracing: Free histogram the field rejected for a bad modifier Writing a hist trigger whose value or variable carries a modifier that is not allowed there leaks the fields that were built for it. __create_val_field() takes the field from parse_expr() and stores it in hist_data->fields[] only after the modifier checks have run: hist_field = parse_expr(hist_data, file, field_str, flags, var_name, &n_subexprs); ... if (hist_field->flags & HIST_FIELD_FL_VAR) { if (hist_field->flags & (...)) goto err; } else { if (hist_field->flags & (...)) goto err; } hist_data->fields[val_idx] = hist_field; Both checks jump past that store, and the err label returns without freeing anything. The error unwinds to create_hist_data(), which calls destroy_hist_data() -> destroy_hist_fields(), and that reaches a field only by walking fields[]. A field that never got there is unreachable. commit e0213434fe3e ("tracing: Do not let histogram values have some modifiers") set ret to -EINVAL and fell through to the store, which left the field owned by fields[] and freed along with the rest of hist_data. Splitting the check into a value case and a variable case replaced that fall-through with a goto that skips it. With CONFIG_DEBUG_KMEMLEAK, 200 writes of # echo 'hist:keys=prev_pid:vals=next_pid.log2' > \ events/sched/sched_switch/trigger each correctly rejected with -EINVAL, leave 332 unreferenced objects (63744 bytes) reported at create_hist_field(); 200 install and remove cycles of a valid trigger leave none. A '.log2' field is two allocations, since create_hist_field() puts the plain field in operands[0] of the log2 field, and both are reported. Use destroy_hist_field() rather than __destroy_hist_field() so that operands[0] is freed as well. It returns early for HIST_FIELD_FL_VAR_REF, which is what an operand owned by hist_data->var_refs[] needs; the rejected field itself is never a var ref, because a var ref never carries a modifier flag. Cc: stable@vger.kernel.org Fixes: e30fbc618e97 ("tracing/histograms: Allow variables to have some modifiers") Link: https://patch.msgid.link/20260907034948.240387-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index bbdd56208eff..8cad99a8d01e 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -4317,6 +4317,7 @@ static int __create_val_field(struct hist_trigger_data *hist_data, return ret; err: hist_err(file->tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str)); + destroy_hist_field(hist_field, 0); return -EINVAL; } From 06f5634ec5584954177f9a22e36b3bfb398a971b Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 15:03:23 +0900 Subject: [PATCH 0884/1317] tracing: Keep the entry count when the histogram stats allocation fails print_entries() uses n_entries both as the number of sort entries and as its own return value, so the -ENOMEM it stores when the stats allocation fails overwrites the count that the cleanup still needs: n_entries = tracing_map_sort_entries(map, ...); if (n_entries < 0) return n_entries; ... if (!stats) { n_entries = -ENOMEM; goto out; } ... out: tracing_map_destroy_sort_entries(sort_entries, n_entries); tracing_map_destroy_sort_entries() takes an unsigned int and loops up to it, so -ENOMEM arrives as 4294967284. It walks an array of at most map->max_elts pointers and calls destroy_sort_entry(), which dereferences and frees, on whatever lies past the end. Reading the hist file of a trigger with a .percent value, with that allocation forced to fail: BUG: KASAN: vmalloc-out-of-bounds in tracing_map_destroy_sort_entries+0xa0/0xb0 Read of size 8 at addr ffffc90000045000 by task init/1 tracing_map_destroy_sort_entries+0xa0/0xb0 hist_show+0x6f7/0x1df0 seq_read_iter+0x2b8/0x1190 vfs_read+0x176/0xa40 The buggy address belongs to a 4-page vmalloc region starting at ffffc90000041000 allocated at tracing_map_sort_entries+0x5c/0xd50 A few pages further the fault is fatal. The registers at the oops confirm the bound: the loop's end pointer less the array start, over the pointer size, is 4294967284. Return the error in a separate variable and leave n_entries holding the count, the way tracing_map_sort_entries() does on its own error path. The stats block is only entered for a value carrying .percent or .graph, which __create_val_field() has rejected since v6.3, so this cannot be reached in mainline as it stands. It becomes reachable again with "tracing: hist: let values keep the percent and graph modifiers", so it should be applied first. Cc: stable@vger.kernel.org Fixes: abaa5258ce5e ("tracing: Add .percent suffix option to histogram values") Link: https://patch.msgid.link/20260907060323.480728-1-donggeunyoo.kernel@gmail.com Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260907053113.1CED91F00A3A@smtp.kernel.org/ Signed-off-by: Donggeun Yoo Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 8cad99a8d01e..8d80562fb502 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -5677,7 +5677,7 @@ static int print_entries(struct seq_file *m, { struct tracing_map_sort_entry **sort_entries = NULL; struct tracing_map *map = hist_data->map; - int i, j, n_entries; + int i, j, n_entries, ret; struct hist_val_stat *stats = NULL; u64 val; @@ -5687,6 +5687,8 @@ static int print_entries(struct seq_file *m, if (n_entries < 0) return n_entries; + ret = n_entries; + /* Calculate the max and the total for each field if needed. */ for (j = 0; j < hist_data->n_vals; j++) { if (!(hist_data->fields[j]->flags & @@ -5695,7 +5697,7 @@ static int print_entries(struct seq_file *m, if (!stats) { stats = kzalloc_objs(*stats, hist_data->n_vals); if (!stats) { - n_entries = -ENOMEM; + ret = -ENOMEM; goto out; } } @@ -5716,7 +5718,7 @@ static int print_entries(struct seq_file *m, out: tracing_map_destroy_sort_entries(sort_entries, n_entries); - return n_entries; + return ret; } static void hist_trigger_show(struct seq_file *m, From 3d617bfd79330ae3acf94862c18bb3ccf5f5a0f9 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 14:21:13 +0900 Subject: [PATCH 0885/1317] tracing: Let histogram values keep the percent and graph modifiers The .percent and .graph modifiers exist only for histogram values, but a value carrying either of them has been rejected since v6.3. The example in Documentation/trace/histogram.rst, # echo 'hist:keys=prev_comm:vals=hitcount.percent:nohitcount' > \ events/sched/sched_switch/trigger returns -EINVAL. parse_field() sets the two flags only when the field is neither a key nor a variable, that is, only on a value: } else if (strncmp(modifier, "percent", 7) == 0) { if (*flags & (HIST_FIELD_FL_VAR | HIST_FIELD_FL_KEY)) goto error; *flags |= HIST_FIELD_FL_PERCENT; __create_val_field() then rejects a value for carrying them, so no field can reach hist_trigger_print_val(), where both are implemented. commit e0213434fe3e ("tracing: Do not let histogram values have some modifiers") added the check after a value with .buckets oopsed in hist_field_name(). That happens because .buckets and .log2 make create_hist_field() build a nested field in operands[0] which hist_field_name() then walks into. The percent and graph flags do not create an operand and are not read by hist_field_name(); they are only used when printing a value. Stop rejecting the two flags on a value. The check for variables is left alone, where they are unreachable anyway because parse_field() rejects a variable carrying them first. With the two flags removed, the trigger above installs and prints as documented: { prev_comm: rcu_preempt } hitcount (%): 0.00 { prev_comm: init } hitcount (%): 99.98 Totals: Hits: 237896 Cc: stable@vger.kernel.org Fixes: e0213434fe3e ("tracing: Do not let histogram values have some modifiers") Link: https://patch.msgid.link/20260907052113.430818-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 8d80562fb502..5e00da2d5b1a 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -4299,8 +4299,7 @@ static int __create_val_field(struct hist_trigger_data *hist_data, goto err; } else { /* Value */ - if (hist_field->flags & (HIST_FIELD_FL_GRAPH | HIST_FIELD_FL_PERCENT | - HIST_FIELD_FL_BUCKET | HIST_FIELD_FL_LOG2 | + if (hist_field->flags & (HIST_FIELD_FL_BUCKET | HIST_FIELD_FL_LOG2 | HIST_FIELD_FL_SYM | HIST_FIELD_FL_SYM_OFFSET | HIST_FIELD_FL_SYSCALL | HIST_FIELD_FL_STACKTRACE)) goto err; From 89b000ba0796593aa61f6eec24d369337594588b Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 11:56:08 +0530 Subject: [PATCH 0886/1317] tracing: Fix typo "availabe" in comment Correct "availabe" to "available", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Link: https://patch.msgid.link/20260907062608.13924-1-hemanth.selam@gmail.com Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Steven Rostedt --- kernel/trace/rethook.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/rethook.c b/kernel/trace/rethook.c index 5a8bdf88999a..87a27f3aa4a6 100644 --- a/kernel/trace/rethook.c +++ b/kernel/trace/rethook.c @@ -171,7 +171,7 @@ struct rethook_node *rethook_try_get(struct rethook *rh) * This expects the caller will set up a rethook on a function entry. * When the function returns, the rethook will eventually be reclaimed * or released in the rethook_recycle() with call_rcu(). - * This means the caller must be run in the RCU-availabe context. + * This means the caller must be run in the RCU-available context. */ if (unlikely(!rcu_is_watching())) return NULL; From 0999d3e16d13b6299fd7cc7a7fb2825c18e90dd0 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:26:07 +0530 Subject: [PATCH 0887/1317] tracing: Fix typo "preceeded" in comment Correct "preceeded" to "Preceded", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Link: https://patch.msgid.link/20260907065607.36615-1-hemanth.selam@gmail.com Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Steven Rostedt --- include/trace/events/timer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/trace/events/timer.h b/include/trace/events/timer.h index ca82fd62dc30..3aa0608c6361 100644 --- a/include/trace/events/timer.h +++ b/include/trace/events/timer.h @@ -302,7 +302,7 @@ DECLARE_EVENT_CLASS(hrtimer_class, * hrtimer_start_expired - Invoked when a expired timer was started * @hrtimer: pointer to struct hrtimer * - * Preceeded by a hrtimer_start tracepoint. + * Preceded by a hrtimer_start tracepoint. */ DEFINE_EVENT(hrtimer_class, hrtimer_start_expired, From 6ede78d0563a2a3ae3e46f9c07cedb5d79645429 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 18:14:15 +0900 Subject: [PATCH 0888/1317] tracing: Set the trace clock before registering the histogram trigger hist_register_trigger() puts the trigger on the global named_triggers list in cmd_ops->init(), and only then sets the trace clock: if (data->cmd_ops->init) { ret = data->cmd_ops->init(data); if (ret < 0) goto out; } if (hist_data->enable_timestamps) { ret = tracing_set_clock(file->tr, hist_data->attrs->clock); if (ret) { hist_err(tr, HIST_ERR_SET_CLOCK_FAIL, errpos(clock)); goto out; } The clock string is not checked anywhere before that call, so a named trigger using common_timestamp with an unknown clock fails after it has already become findable. event_hist_trigger_parse() then frees it without taking it off the list, and the next lookup by name reads the freed object: ~# cd /sys/kernel/tracing/events/sched/sched_switch ~# echo 'hist:name=foo:keys=common_pid:ts=common_timestamp:clock=bogus' > trigger bash: echo: write error: Invalid argument ~# echo 'hist:name=foo:keys=common_pid' > trigger BUG: KASAN: slab-use-after-free in find_named_trigger+0xac/0xc0 Read of size 8 at addr ffff88800915d760 by task init/1 find_named_trigger+0xac/0xc0 hist_register_trigger+0xc1/0x900 event_hist_trigger_parse+0x3146/0x6af0 event_trigger_write+0xce/0x160 Freed by task 63: kfree+0x154/0x420 trigger_kthread_fn+0xfd/0x160 Set the clock before the trigger is registered, so that nothing which can fail runs after it is published, the way commit 6f86bdeab633 ("tracing: Fix bad hist from corrupting named_triggers list") moved the registration below the rest of the setup. tracing_set_filter_buffering() is reference counted, so the init failure path has to drop the reference that the clock block now takes first. Cc: stable@vger.kernel.org Fixes: a4072fe85ba3 ("tracing: Add a clock attribute for hist triggers") Link: https://patch.msgid.link/20260907091415.554535-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 5e00da2d5b1a..1889e310b73c 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6631,12 +6631,6 @@ static int hist_register_trigger(char *glob, data->cmd_ops = cmd_ops; } - if (data->cmd_ops->init) { - ret = data->cmd_ops->init(data); - if (ret < 0) - goto out; - } - if (hist_data->enable_timestamps) { char *clock = hist_data->attrs->clock; @@ -6649,6 +6643,15 @@ static int hist_register_trigger(char *glob, tracing_set_filter_buffering(file->tr, true); } + if (data->cmd_ops->init) { + ret = data->cmd_ops->init(data); + if (ret < 0) { + if (hist_data->enable_timestamps) + tracing_set_filter_buffering(file->tr, false); + goto out; + } + } + if (named_data) { remove_hist_vars(hist_data); destroy_hist_data(hist_data); From 0fe23b8eaba0d3372c66b7b31204408da0715edc Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 21:44:19 +0900 Subject: [PATCH 0889/1317] tracing: Take the reference before publishing the named histogram trigger event_hist_trigger_named_init() puts the trigger on the global named_triggers list and only then takes the reference on the trigger it shares its histogram with: data->ref++; save_named_trigger(data->named_data->name, data); ret = event_hist_trigger_init(data->named_data); if (ret < 0) { kfree(data->cmd_ops); data->cmd_ops = &trigger_hist_cmd; } return ret; event_hist_trigger_init() fails when alloc_hist_pad() cannot allocate, and nothing takes the trigger back off the list on the way out. event_hist_trigger_parse() frees it, and the next lookup by name reads the freed object: BUG: KASAN: slab-use-after-free in find_named_trigger+0xac/0xc0 Read of size 8 at addr ffff888009346860 by task init/1 find_named_trigger+0xac/0xc0 hist_register_trigger+0xc1/0xa00 event_hist_trigger_parse+0x3146/0x6af0 event_trigger_write+0xce/0x160 Freed by task 67: kfree+0x154/0x420 trigger_kthread_fn+0xfd/0x160 Do the reference first and publish once it has succeeded, so that nothing which can fail runs after the trigger becomes findable. Cc: stable@vger.kernel.org Fixes: 7ab0fc61ce73 ("tracing: Move histogram trigger variables from stack to per CPU structure") Reported-by: Sashiko AI Closes: https://lore.kernel.org/linux-trace-kernel/20260907092944.3950E1F00A3D@smtp.kernel.org/ Link: https://patch.msgid.link/20260907124420.607097-2-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Acked-by: Tom Zanussi Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 1889e310b73c..54c95f9bd0a3 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6371,17 +6371,18 @@ static int event_hist_trigger_named_init(struct event_trigger_data *data) { int ret; - data->ref++; - - save_named_trigger(data->named_data->name, data); - ret = event_hist_trigger_init(data->named_data); if (ret < 0) { kfree(data->cmd_ops); data->cmd_ops = &trigger_hist_cmd; + return ret; } - return ret; + data->ref++; + + save_named_trigger(data->named_data->name, data); + + return 0; } static void event_hist_trigger_named_free(struct event_trigger_data *data) From 92383cef66791a0c63a2f27755cadbdb2fbf270b Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 21:44:20 +0900 Subject: [PATCH 0890/1317] tracing: Undo the registration when enabling the histogram trigger fails Commit 6f86bdeab633 ("tracing: Fix bad hist from corrupting named_triggers list") described how a trigger that is registered but not on file->triggers ends up freed while still on the global named_triggers list, and moved the registration down so that hist_trigger_enable() follows it immediately. One path still gets there. hist_trigger_enable() adds the trigger and takes it straight back out when the event cannot be enabled: list_add_tail_rcu(&data->list, &file->triggers); update_cond_flag(file); if (trace_event_trigger_enable_disable(file, 1) < 0) { list_del_rcu(&data->list); update_cond_flag(file); ret--; } so the list walk in hist_unregister_trigger() matches nothing, test stays NULL, and the ->free() that would call del_named_trigger() is skipped. out_unreg falls through to out_free, which frees the trigger anyway: BUG: KASAN: slab-use-after-free in find_named_trigger+0xac/0xc0 Read of size 8 at addr ffff8880091d3160 by task init/1 find_named_trigger+0xac/0xc0 hist_register_trigger+0xc1/0xa00 event_hist_trigger_parse+0x3146/0x6af0 event_trigger_write+0xce/0x160 Freed by task 69: kfree+0x154/0x420 trigger_kthread_fn+0xfd/0x160 Leave the trigger where hist_unregister_trigger() can find it and let that undo the registration, which is the only code that knows all of what cmd_ops->init() took: the named list entry, the hist_pad reference, the reference on the trigger a named histogram is shared with, and the copied cmd_ops. It also pairs the failed trace_event_trigger_enable_disable(), whose sm_ref and buffered event reference are otherwise left behind. Since ->free() releases trigger_data and, for a trigger that does not share its histogram, hist_data with it, out_unreg can no longer fall through to out_free. For a trigger that does share, hist_register_trigger() has already destroyed the caller's hist_data, so the fall-through was reading freed memory there as well. Move the enable_timestamps check in hist_unregister_trigger() above the ->free() call for the same reason: hist_data does not outlive it once the trigger being removed is the one that owns it. Cc: stable@vger.kernel.org Fixes: 067fe038e70f ("tracing: Add variable reference handling to hist triggers") Reported-by: Sashiko AI Closes: https://lore.kernel.org/linux-trace-kernel/20260907092944.3950E1F00A3D@smtp.kernel.org/ Link: https://patch.msgid.link/20260907124420.607097-3-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 54c95f9bd0a3..53100287466f 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6670,11 +6670,12 @@ static int hist_trigger_enable(struct event_trigger_data *data, update_cond_flag(file); - if (trace_event_trigger_enable_disable(file, 1) < 0) { - list_del_rcu(&data->list); - update_cond_flag(file); + /* + * On failure the caller undoes the registration, and + * hist_unregister_trigger() can only find the trigger here. + */ + if (trace_event_trigger_enable_disable(file, 1) < 0) ret--; - } return ret; } @@ -6752,13 +6753,13 @@ static void hist_unregister_trigger(char *glob, } } - if (test && test->cmd_ops->free) - test->cmd_ops->free(test); - if (hist_data->enable_timestamps) { if (!hist_data->remove || test) tracing_set_filter_buffering(file->tr, false); } + + if (test && test->cmd_ops->free) + test->cmd_ops->free(test); } static bool hist_file_check_refs(struct trace_event_file *file) @@ -6963,6 +6964,8 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops, return ret; out_unreg: event_trigger_unregister(cmd_ops, file, glob+1, trigger_data); + /* The unregister frees trigger_data, skip out_free */ + goto out; out_free: remove_hist_vars(hist_data); From a5e70ba87ca8ebc79b4e63de302d03b0625fe153 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Tue, 8 Sep 2026 00:50:44 +0900 Subject: [PATCH 0891/1317] tracing: Fix memory corruption from the histogram stacktrace modifier parse_field() sets HIST_FIELD_FL_STACKTRACE from the ".stacktrace" modifier before it looks the field name up, and nothing afterwards checks that the name resolved to a field which holds a stacktrace. create_hist_field() picks HIST_FIELD_FN_STACK on the strength of the field pointer alone, which reads a __data_loc word from the record and follows its low 16 bits as an offset into the same record. event_hist_trigger() takes the first word there as an entry count and copies that many longs into a 31 entry array: n_entries = *stack; memcpy(entries, ++stack, n_entries * sizeof(unsigned long)); Neither end of that copy is bounded, and the count is whatever the event holds at the offset, so any field will do: # cd /sys/kernel/tracing/events/sched/sched_process_fork # echo 'hist:keys=parent_pid.stacktrace' > trigger # (true) BUG: kernel NULL pointer dereference, address: 0000000000000008 RIP: 0010:rb_insert_color+0x18/0x130 timerqueue_linked_add+0x7e/0xd0 enqueue_hrtimer+0x39/0xb0 __hrtimer_run_queues+0x10f/0x1f0 RIP: 0010:memcpy+0xc/0x30 event_hist_trigger+0x165/0x690 The timer interrupt landed on the rbtree the copy had already run over. No debug options are needed for this; KASAN reports the same write as an out-of-bounds read of 13835058055416381440 bytes. Documentation/trace/histogram.rst already states the rule, "must be a long[] type", so enforce it once the name has been resolved. Names which resolve to no field at all, "hitcount.stacktrace" and the common_* pseudo-fields, are refused for the same reason: they hold no stacktrace to read. Cc: stable@vger.kernel.org Fixes: cc5fc8bfc961 ("tracing/histogram: Add stacktrace type") Link: https://patch.msgid.link/20260907155045.692664-2-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 53100287466f..9bc829c1e876 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -2317,6 +2317,7 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, struct ftrace_event_field *field = NULL; char *field_name, *modifier, *str; struct trace_array *tr = file->tr; + bool stack_modifier = false; modifier = str = kstrdup(field_str, GFP_KERNEL); if (!modifier) @@ -2339,9 +2340,10 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, *flags |= HIST_FIELD_FL_EXECNAME; else if (strcmp(modifier, "syscall") == 0) *flags |= HIST_FIELD_FL_SYSCALL; - else if (strcmp(modifier, "stacktrace") == 0) + else if (strcmp(modifier, "stacktrace") == 0) { *flags |= HIST_FIELD_FL_STACKTRACE; - else if (strcmp(modifier, "log2") == 0) + stack_modifier = true; + } else if (strcmp(modifier, "log2") == 0) *flags |= HIST_FIELD_FL_LOG2; else if (strcmp(modifier, "usecs") == 0) *flags |= HIST_FIELD_FL_TIMESTAMP_USECS; @@ -2412,6 +2414,12 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, } } } + + if (stack_modifier && + (!field || field->filter_type != FILTER_STACKTRACE)) { + hist_err(tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str)); + field = ERR_PTR(-EINVAL); + } out: kfree(str); From 7f711e62355bb3123a2ca2f97a2facbfebc678c6 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Tue, 8 Sep 2026 00:50:45 +0900 Subject: [PATCH 0892/1317] tracing: Fix memory corruption from a "STACKTRACE" histogram key "cpu", "CPU", "stacktrace" and "STACKTRACE" are generic fields, defined with an offset and a size of zero so that the filter code can match them by name. parse_field() maps them onto their common_* equivalents for backward compatibility, but unlike the common_* names it hands the placeholder back to the caller instead of NULL. create_hist_field() takes a non-NULL field as a promise that the record carries a stacktrace and picks HIST_FIELD_FN_STACK, so the __data_loc word is read from offset 0, that is from common_type, and its low 16 bits are followed as an offset into the record. What is found there becomes the length of an unbounded memcpy. Pick an event whose id is small enough that the offset stays inside its own record and the length is a kernel text address: # cd /sys/kernel/tracing # echo 'hist:keys=STACKTRACE' > events/ftrace/print/trigger # echo hello > trace_marker Oops: general protection fault, probably for non-canonical address RIP: 0010:rb_next+0x23/0x60 RIP: 0010:memcpy+0xc/0x30 event_hist_trigger+0x2e7/0x12c0 Kernel panic - not syncing: Fatal exception in interrupt Leave the field NULL, which is what the comment above the branch says the code does and what common_stacktrace already does. FILTER_CPU and FILTER_COMM are left alone, their create_hist_field() branches never look at the field. Cc: stable@vger.kernel.org Fixes: 4b512860bdbd ("tracing: Rename stacktrace field to common_stacktrace") Link: https://patch.msgid.link/20260907155045.692664-3-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 9bc829c1e876..8af97fd4ee2d 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -2404,6 +2404,7 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, *flags |= HIST_FIELD_FL_CPU; } else if (field && field->filter_type == FILTER_STACKTRACE) { *flags |= HIST_FIELD_FL_STACKTRACE; + field = NULL; } else if (field && field->filter_type == FILTER_COMM) { *flags |= HIST_FIELD_FL_COMM | HIST_FIELD_FL_STRING; } else { From 911002e99e15f640f1fdc6d276206beaef59e790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 8 Sep 2026 08:22:15 +0200 Subject: [PATCH 0893/1317] tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While ftrace_set_clr_event() modifies its input buffer during parsing, before returning to the caller the buffer is supposed to be restored to its original state. This works correctly for the colon between the subsystem and event but not the colon at the beginning of :mod:. Restore the colon, so the :mod: trailer is not stripped after ftrace_set_clr_event(). Cc: stable@vger.kernel.org Fixes: 4c86bc531e60 ("tracing: Add :mod: command to enabled module events") Link: https://patch.msgid.link/20260908-tracing-cli-event-filter-v2-1-05396a3fb663@linutronix.de Signed-off-by: Thomas Weißschuh Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 9dbc2441763b..30c0ddf90887 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -1489,6 +1489,8 @@ int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set) /* Put back the colon to allow this to be called again */ if (buf) *(buf - 1) = ':'; + if (mod) + *(mod - 5) = ':'; return ret; } From 7e645147dfba67edb3ed3090a1ed1d89df77fc27 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 9 Sep 2026 08:29:17 +0200 Subject: [PATCH 0894/1317] tracing: Fix ring_buffer_read_page_size() kernel-doc ring_buffer_read_page_size() takes a parameter named rpage, but its kernel-doc describes page. As a result, kernel-doc reports rpage as undescribed and page as an excess parameter description. Rename the documentation entry to match the function. Link: https://patch.msgid.link/20260909062917.89482-1-kmehltretter@gmail.com Fixes: dae8dda341d2 ("tracing: Fix subbuf resize races with trace_pipe_raw readers") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 9c03a555a6ba..0e30c7bd6045 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -7384,7 +7384,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_read_page_data); /** * ring_buffer_read_page_size - get size of the read page. - * @page: the page to get the size from + * @rpage: the page to get the size from * * Returns size of the page in bytes. */ From ed0aff60f83a9bdc2f6556376ac79c96b3ce7e80 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 10 Sep 2026 22:12:09 -0400 Subject: [PATCH 0895/1317] tracing: Take trace_array reference when opening a tracer options file When a tracer option file is opened, it is passed a descriptor that points to an element on the trace_array's topts array. This element has information to find the trace array and other information. It uses this element to take a reference of the trace_array so that the trace_array does not get removed while this file is opened. Unfortunately, there's a race condition where the element itself could be freed by the removal of the instance the trace_array represents causing a use-after-free as this element that is used to find the trace_array to increment its reference counter is also freed when the instance is removed. To solve this, add a trace_array_tracer_options_get() helper function that will take the address of the element that is passed to the open function by the inode->i_private pointer and search all the trace_arrays under a lock to find the one that the element's address is in the range of the trace_arrays topts array elements. When a match happens, that trace_array's reference would be increased. Note, there's a race where if an admin was deleting and creating trace instances at the same time and the memory of the old trace_array's array matched the memory of the new trace_array that it could in theory open the option from the wrong trace array. But we do not care because it would be stupid to perform that kind of action. As long as the only thing that can happen is that the option from the wrong trace array is used and doesn't crash the kernel it will only make the user confused. But if they are doing something stupid like this, they are already confused, so no harm done. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260910221209.62dad8d3@robin Fixes: 7e2cfbd2d3c86 ("tracing: Have option files inc the trace array ref count") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-trace-kernel/20260902121918.5a9e9d1b@gandalf.local.home/ Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 46 +++++++++++++++++++++++++++++++++++++++++++- kernel/trace/trace.h | 1 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 8658cad53cb5..e4a490d3d08c 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -7717,12 +7717,55 @@ trace_options_write(struct file *filp, const char __user *ubuf, size_t cnt, return cnt; } +static bool tr_option_match(struct trace_array *tr, void *topt) +{ + for (int i = 0; i < tr->nr_topts; i++) { + struct trace_options *tr_topts = &tr->topts[i]; + + if (topt >= (void *)&tr_topts->topts[0] && + topt < (void *)&tr_topts->topts[tr_topts->nr_topts]) + return true; + } + return false; +} + +/* + * The topt is the address of a trace_array->topts[] element that holds the + * the tracer options descriptor. But since the trace_array reference has not + * been taken yet, it cannot be dereferenced as it could have been freed by + * a rmdir of the instance the trace_array represents. + * + * Search the list of trace_arrays and compare the topt to the address of + * the entire trace_array topts array for each trace_array in the list. + * If one is matched, then take the reference and return it. If not, the + * trace_array no longer exits. + */ +static int trace_array_tracer_options_get(void *topt) +{ + struct trace_array *tr; + int ret; + + ret = security_locked_down(LOCKDOWN_TRACEFS); + if (ret) + return ret; + + if (tracing_disabled) + return -ENODEV; + + guard(mutex)(&trace_types_lock); + list_for_each_entry(tr, &ftrace_trace_arrays, list) { + if (tr_option_match(tr, topt)) + return __trace_array_get(tr); + } + return -ENODEV; +} + static int tracing_open_options(struct inode *inode, struct file *filp) { struct trace_option_dentry *topt = inode->i_private; int ret; - ret = tracing_check_open_get_tr(topt->tr); + ret = trace_array_tracer_options_get(topt); if (ret) return ret; @@ -7984,6 +8027,7 @@ create_trace_option_files(struct trace_array *tr, struct tracer *tracer, tr->topts = tr_topts; tr->topts[tr->nr_topts].tracer = tracer; tr->topts[tr->nr_topts].topts = topts; + tr->topts[tr->nr_topts].nr_topts = cnt; tr->nr_topts++; for (cnt = 0; opts[cnt].name; cnt++) { diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 5e76f94e7a80..bd3c8f80300f 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -227,6 +227,7 @@ struct array_buffer { struct trace_options { struct tracer *tracer; struct trace_option_dentry *topts; + int nr_topts; }; struct trace_pid_list *trace_pid_list_alloc(void); From 23c240d9509e15f72e4112fc95f0160ab32ec430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 10 Sep 2026 18:37:43 +0000 Subject: [PATCH 0896/1317] smb: client: validate absolute native symlink targets before NT fixups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With symlinkroot unset, an absolute target is copied without conversion to an NT drive path. Later code still assumes an NT prefix is present when modifying the target and calculating the print name length. For "/ab", this causes two failures: sym[5] and path[5] are written past their allocations, and plen -= 2 * poff subtracts an assumed 8-byte prefix from a 6-byte UTF-16 target, wrapping u16 plen to 65534. That underflow causes another overflow: memcpy() copies 65534 bytes into a 24-byte buffer. A user with write access to a mounted share can trigger these bugs with default settings. Validate the NT drive prefix, including an ASCII drive letter, before accessing fixed offsets or subtracting the prefix length. Fixes: 3363da82e02f ("smb: client: fix native SMB symlink traversal") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/reparse.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 8a1b9e8be5ba..9e31fce7e0a5 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -3,6 +3,7 @@ * Copyright (c) 2024 Paulo Alcantara */ +#include #include #include #include @@ -159,15 +160,24 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode, convert_delimiter(sym, sep); /* - * For absolute NT symlinks it is required to pass also leading - * backslash and to not mangle NT object prefix "\\??\\" and not to - * mangle colon in drive letter. But cifs_convert_path_to_utf16() - * removes leading backslash and replaces '?' and ':'. So temporary - * mask these characters in NT object prefix by '_' and then change - * them back. + * Absolute NT symlinks must retain the leading backslash, "\\??\\" + * prefix and drive-letter colon. cifs_convert_path_to_utf16() strips + * the leading backslash and maps '?' and ':', so temporarily mask + * these characters with '_' and restore them after conversion. + * + * When symlinkroot is unset, sym comes directly from the caller. + * Validate the complete "\\??\\X:" prefix before using fixed offsets + * or subtracting the NT prefix length below. Require an ASCII drive + * letter so the prefix occupies six characters in UTF-16 too. */ - if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') + if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') { + if (!strstarts(sym, "\\??\\") || !isascii(sym[4]) || + !isalpha(sym[4]) || sym[5] != ':') { + rc = -EINVAL; + goto out; + } sym[0] = sym[1] = sym[2] = sym[5] = '_'; + } /* * On a POSIX paths mount the symlink target is stored verbatim, so From 815e07c8fe885a87751c2496a30ae0dcd4118210 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 11 Sep 2026 12:21:52 +0200 Subject: [PATCH 0897/1317] ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters() rb_wake_up_waiters() is a irq_work callback which is initialized with init_irq_work(). As such it will be invoked in thread context on PREEMPT_RT. Invoking the callback in IRQ context on PREEMPT_RT is not an option due its usage of wake_up_all(). Since this callback may run in thread context, it needs to acquire ring_buffer_per_cpu::reader_lock with disabling interrupts and may not assume that they are disabled. Use raw_spinlock_irqsave() to acquire ring_buffer_per_cpu::reader_lock. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260911102152.YEtwkBj9@linutronix.de Fixes: 68282dd930ea3 ("ring-buffer: Fix resetting of shortest_full") Reviewed-by: Vincent Donnefort Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 0e30c7bd6045..9bc8ce8c5676 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -904,14 +904,13 @@ static void rb_wake_up_waiters(struct irq_work *work) struct ring_buffer_per_cpu *cpu_buffer = container_of(rbwork, struct ring_buffer_per_cpu, irq_work); - /* Called from interrupt context */ - raw_spin_lock(&cpu_buffer->reader_lock); - rbwork->wakeup_full = false; - rbwork->full_waiters_pending = false; + scoped_guard(raw_spinlock_irqsave, &cpu_buffer->reader_lock) { + rbwork->wakeup_full = false; + rbwork->full_waiters_pending = false; - /* Waking up all waiters, they will reset the shortest full */ - cpu_buffer->shortest_full = 0; - raw_spin_unlock(&cpu_buffer->reader_lock); + /* Waking up all waiters, they will reset the shortest full */ + cpu_buffer->shortest_full = 0; + } wake_up_all(&rbwork->full_waiters); } From 3163cd7253432f262c2fa22edb51474d7afc76a0 Mon Sep 17 00:00:00 2001 From: George Emmanuel Thomas Date: Sat, 15 Aug 2026 22:13:35 +0530 Subject: [PATCH 0898/1317] drm/msm: remove stale perf counter XML TODO The adreno_perfcntrs macro already takes the XML file stem as its second argument, allowing each perf counter JSON file to select the appropriate register XML. The a2xx and a5xx entries already pass their respective XML file stems. Remove the stale TODO comment. Signed-off-by: George Emmanuel Thomas Patchwork: https://patchwork.freedesktop.org/patch/746745/ Message-ID: <20260815164335.158958-1-georgeemmanuelthomas@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index d0c3a4c6703b..0b8f2cafed19 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -177,7 +177,6 @@ quiet_cmd_headergen = GENHDR $@ cmd_headergen = mkdir -p $(obj)/generated && $(PYTHON3) $(src)/registers/gen_header.py \ $(headergen-opts) --rnn $(src)/registers --xml $< c-defines > $@ -# TODO how to do this for a2xx/a5xx which have different .xml arg? quiet_cmd_headergen_json = GENHDRJSN $@ cmd_headergen_json = mkdir -p $(obj)/generated && $(PYTHON3) $(src)/registers/gen_header.py \ $(headergen-opts) --rnn $(src)/registers --xml $(filter %.xml,$^) perfcntrs --json $< > $@ From ed9ad3d4183053d4a8c43960a3d23ea8db71c30d Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Wed, 9 Sep 2026 15:07:37 +0200 Subject: [PATCH 0899/1317] drm/msm/a6xx: Add CX AO Counter registers used for a750 GPUs The a750 uses the CX AO Counters instead of the GMU_ALWAYS_ON_COUNTER register on A6xx and other A7xx GPUs. Signed-off-by: Neil Armstrong Patchwork: https://patchwork.freedesktop.org/patch/752234/ Message-ID: <20260909-topic-sm8650-gmu-a750-timestamp-reg-v2-1-091d74958951@linaro.org> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml b/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml index 33404eb18fd0..b3082738c99d 100644 --- a/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml +++ b/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml @@ -141,6 +141,8 @@ xsi:schemaLocation="https://gitlab.freedesktop.org/freedreno/ rules-fd.xsd"> + + From a2b65837980a513cf24825bc4a50b0430e3391e7 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Wed, 9 Sep 2026 15:07:38 +0200 Subject: [PATCH 0900/1317] drm/msm/a6xx: Use CX AO Counter register for timestamp on a750 GPUs The a750 uses the GMU CX AO Counters instead of the GMU_ALWAYS_ON_COUNTER register on A6xx and other A7xx GPUs, use it when running a A750 GPU. The GMU_ALWAYS_ON_COUNTER at offset 0x1f888 doesn't seem to exist on the SM8650 A750 GMU and returns 0, but the CX AO counter at offset 0x1f880 returns some proper timestamp data. Signed-off-by: Neil Armstrong Patchwork: https://patchwork.freedesktop.org/patch/752235/ Message-ID: <20260909-topic-sm8650-gmu-a750-timestamp-reg-v2-2-091d74958951@linaro.org> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index f9de9329dee3..081e79ea4652 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -23,9 +23,15 @@ static u64 a6xx_gmu_get_timestamp(struct msm_gpu *gpu) u64 count_hi, count_lo, temp; do { - count_hi = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); - count_lo = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_L); - temp = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + if (adreno_is_a750_family(adreno_gpu)) { + count_hi = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_H); + count_lo = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_L); + temp = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_H); + } else { + count_hi = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + count_lo = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_L); + temp = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + } } while (unlikely(count_hi != temp)); return (count_hi << 32) | count_lo; From ebc5660132ddd244b57f03ed324922013a3d7363 Mon Sep 17 00:00:00 2001 From: April Cardenas Date: Thu, 10 Sep 2026 00:16:49 -0500 Subject: [PATCH 0901/1317] smb/client: send lease break ACKs thru correct session for multiuser mounts Currently, when cifs_oplock_break handles a break request from the server it searches for the appropriate tlink to handle the request but incorrectly uses the current fsuid as the search key, eventually causing read errors for users with multiuser mounts on NetApp. Fix this by using the tlink from the cfile struct instead to respond through the correct session. As breaks are handled in a worker thread, the current fsuid isn't guaranteed to match the session that the break is intended for. This means that cifs_sb_tlink may search the rbtree using the wrong fsuid, and return a tlink with an incorrect session than the lease break was intended for. As a result, the breaks may be ACKed through an incorrect session. While it seems that Samba/Windows Servers 2016-2025 ignore this as long as the lease key is correct, we ran into a case where if you're using NetApp ONTAP or Azure NetApp Files they will reject the ACK and return `STATUS_LOCK_NOT_GRANTED` errors on any future read requests a user may initiate through their still held open file handle, and the server will eventually close the file. In the dmesg logs, the user may see errors like these: CIFS: Status code returned 0xc0000128 STATUS_FILE_CLOSED CIFS: VFS: Send error in read = -9 With a multiuser mount using NetApp, this issue is really easy for users to hit on a wide variety of kernel versions by attempting to copy a file from the share to the local machine through GNOME Files/Nautilus. This copy will always result in Nautilus throwing a `Bad File Descriptor` error to the user and fail. With this fix, you can copy files through Nautilus without issue. >From looking at the traces, it seems that glib will open the file first, and call listxattr before actually attempting to copy the file data. The listxattr call always triggers a break, causing the copy to fail. The proposed fix returns to the way the client grabbed the tlink before commit e8f5f849ffce2 ("cifs: fix potential oops in cifs_oplock_break"). The bulk of that commit (checking for list empty) remains untouched, and I think the change to using cifs_sb_tlink was intended to avoid a NULL/ERR deference on the tlink as well as update the reference count. I believe this fix should preserve those safety properties, but of course I'd appreciate any corrections here. Fixes: e8f5f849ffce2 ("cifs: fix potential oops in cifs_oplock_break") Cc: stable@vger.kernel.org Signed-off-by: April Cardenas Reviewed-by: Namjae Jeon Reviewed-by: Bharath S M Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 1aa4844f8b8a..0d428517f454 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -3354,8 +3354,8 @@ void cifs_oplock_break(struct work_struct *work) wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS, TASK_UNINTERRUPTIBLE); - tlink = cifs_sb_tlink(cifs_sb); - if (IS_ERR(tlink)) { + tlink = cifs_get_tlink(cfile->tlink); + if (IS_ERR_OR_NULL(tlink)) { /* drop the reference taken when the break was queued */ _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false); goto out; From 2725ab3f5ad1c5f375c7c9fee4af02a9b138f701 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 21 Aug 2026 04:53:27 +0200 Subject: [PATCH 0902/1317] keys: fix lost wakeup when reaping a dead key type clear_bit() is atomic with respect to the word it modifies, but it is an unordered operation: it implies no memory barrier on either side (Documentation/atomic_bitops.txt). key_garbage_collector() clears KEY_GC_REAPING_KEYTYPE with clear_bit() and calls wake_up_bit() after reaping a dead key type. wake_up_bit() uses a lockless waitqueue check and requires a full barrier after the clear. The existing smp_mb() is before clear_bit(), so nothing orders the clear against that check. The GC can see an empty waitqueue while unregister_key_type() still sees the bit set. The final wakeup is then lost, leaving module unload stuck in wait_on_bit(). Use clear_and_wake_up_bit(). Its clear_bit_unlock() has RELEASE semantics, so the completed GC work stays ordered before the clear, and its smp_mb__after_atomic() orders the clear before the waitqueue check. Fixes: 0c061b5707ab ("KEYS: Correctly destroy key payloads when their keytype is removed") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://lore.kernel.org/r/20260821025327.61488-1-kmehltretter@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- security/keys/gc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/security/keys/gc.c b/security/keys/gc.c index 748e83818a76..eda445f815d4 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -318,9 +318,7 @@ static void key_garbage_collector(struct work_struct *work) if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) { kdebug("dead wake"); - smp_mb(); - clear_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); - wake_up_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE); + clear_and_wake_up_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); } if (gc_state & KEY_GC_REAP_AGAIN) From 0d6a4268b06084baafd8ee5d66955c7e1c2e053b Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Fri, 21 Aug 2026 17:59:35 +0800 Subject: [PATCH 0903/1317] keys: translate request_key_auth pid for the reading procfs instance request_key_auth_describe() prints rka->pid into /proc/keys as a raw pid_t in the initial pid namespace. A reader can open /proc/keys through a mount in another pid namespace. That reader sees a number with no meaning there. The number can even name an unrelated task. The line needs VIEW on the key. So the reader either shares the key owner's uid or possesses the key. The fix keeps a struct pid. Commit 4f82f45730c6 ("net ip6 flowlabel: Make owner a union of struct pid * and kuid_t") gave /proc/net/ip6_flowlabel the same storage. The print goes through pid_nr_ns(). It renders against the pid namespace of the procfs instance the line is read through. Commit ad08978ab41c ("ipv6/flowlabel: simplify pid namespace lookup") moved that print to the same anchor. Output through an initial namespace /proc does not change. The line shows 0 for a requestor with no number in that namespace. Translating at read time was the alternative. find_pid_ns() can resolve a recycled number. The line would then name a live task with no connection to the key. A stored struct pid gives 0 instead when the requestor has no number there. Link: https://lore.kernel.org/keyrings/20260809110202.2180410-1-maoyixie.tju@gmail.com/ Fixes: 78b7280cce23 ("KEYS: Improve /proc/keys") Cc: stable@vger.kernel.org # v5.10+ Assisted-by: Claude:claude-opus-5 codeql Signed-off-by: Maoyi Xie Link: https://lore.kernel.org/r/20260821095935.1864998-1-maoyixie.tju@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- include/keys/request_key_auth-type.h | 2 +- security/keys/request_key_auth.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/keys/request_key_auth-type.h b/include/keys/request_key_auth-type.h index 01e42ee5f409..464636278c4f 100644 --- a/include/keys/request_key_auth-type.h +++ b/include/keys/request_key_auth-type.h @@ -22,7 +22,7 @@ struct request_key_auth { const struct cred *cred; void *callout_info; size_t callout_len; - pid_t pid; + struct pid *pid; char op[8]; } __randomize_layout; diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 282e09d8fa46..ed6f55b9cdd9 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -73,7 +75,10 @@ static void request_key_auth_describe(const struct key *key, seq_puts(m, "key:"); seq_puts(m, key->description); if (key_is_positive(key)) - seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len); + seq_printf(m, " pid:%d ci:%zu", + pid_nr_ns(rka->pid, + proc_pid_ns(file_inode(m->file)->i_sb)), + rka->callout_len); } /* @@ -113,6 +118,7 @@ static void free_request_key_auth(struct request_key_auth *rka) if (rka->cred) put_cred(rka->cred); kfree(rka->callout_info); + put_pid(rka->pid); kfree(rka); } @@ -226,14 +232,14 @@ struct key *request_key_auth_new(struct key *target, const char *op, irka = cred->request_key_auth->payload.data[0]; rka->cred = get_cred(irka->cred); - rka->pid = irka->pid; + rka->pid = get_pid(irka->pid); up_read(&cred->request_key_auth->sem); } else { /* it isn't - use this process as the context */ rka->cred = get_cred(cred); - rka->pid = current->pid; + rka->pid = get_pid(task_pid(current)); } rka->target_key = key_get(target); From 114f00d738f15dd8c7318369edcdc53dd6d08763 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Tue, 1 Sep 2026 23:58:06 +0300 Subject: [PATCH 0904/1317] KEYS: trusted: Fix tpm2_load_cmd() boundary check tpm2_load_cmd() does boundary checks against the ASN.1 size i.e., payload->blob_len. Address this by passing the decoded blob size to tpm2_load_cmd(), and use it for the boundary checks. Cc: stable@vger.kernel.org # v5.13+ Fixes: f2219745250f ("security: keys: trusted: use ASN.1 TPM2 key format for the blobs") Reported-by: co+6a581c4284f721d4@bugs.sh Closes: https://bugs.sh/b/6a581c4284f721d4/ Reviewed-by: Stefano Garzarella Tested-by: Srish Srinivasan Link: https://lore.kernel.org/r/20260901205809.2028454-1-jarkko@kernel.org Signed-off-by: Jarkko Sakkinen --- security/keys/trusted-keys/trusted_tpm2.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 67225dd562a9..01f18bb37047 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -99,7 +99,7 @@ struct tpm2_key_context { static int tpm2_key_decode(struct trusted_key_payload *payload, struct trusted_key_options *options, - u8 **buf) + u8 **buf, unsigned int *blob_len) { int ret; struct tpm2_key_context ctx; @@ -120,6 +120,7 @@ static int tpm2_key_decode(struct trusted_key_payload *payload, return -ENOMEM; *buf = blob; + *blob_len = ctx.priv_len + ctx.pub_len; options->keyhandle = ctx.parent; memcpy(blob, ctx.priv, ctx.priv_len); @@ -384,10 +385,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip, int rc; u32 attrs; - rc = tpm2_key_decode(payload, options, &blob); + rc = tpm2_key_decode(payload, options, &blob, &blob_len); if (rc) { /* old form */ blob = payload->blob; + blob_len = payload->blob_len; payload->old_format = 1; } else { /* Bind for cleanup: */ @@ -399,17 +401,17 @@ static int tpm2_load_cmd(struct tpm_chip *chip, return -EINVAL; /* must be big enough for at least the two be16 size counts */ - if (payload->blob_len < 4) + if (blob_len < 4) return -EINVAL; private_len = get_unaligned_be16(blob); /* must be big enough for following public_len */ - if (private_len + 2 + 2 > (payload->blob_len)) + if (private_len + 2 + 2 > blob_len) return -E2BIG; public_len = get_unaligned_be16(blob + 2 + private_len); - if (private_len + 2 + public_len + 2 > payload->blob_len) + if (private_len + 2 + public_len + 2 > blob_len) return -E2BIG; pub = blob + 2 + private_len + 2; From 8697c431e297eb0d0ab13dda6bc172b48a34f05c Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 12 Sep 2026 00:31:16 +0300 Subject: [PATCH 0905/1317] KEYS: encrypted: fix integer overflow of datablob_len encrypted_key_alloc() stores datablob_len in a u16. It is computed from multiple string and payload lengths. If the result exceeds U16_MAX, the assignment truncates the allocation size. KASAN reports a 32760-byte slab-out-of-bounds write when __ekey_init() copies the master key description into the undersized buffer. The total payload length stored in key->datalen is also a u16. Use check_add_overflow() to reject values that do not fit either destination, and use kzalloc_flex() for the flexible-array allocation. Fixes: 7e70cb497850 ("keys: add new key-type encrypted") Cc: stable@vger.kernel.org Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Cen Zhang Signed-off-by: Francis Perron Reviewed-by: Jarkko Sakkinen Tested-by: R Nageswara Sastry Link: https://lore.kernel.org/r/20260909153433.83117-1-cenzhang@linux.microsoft.com Signed-off-by: Jarkko Sakkinen --- security/keys/encrypted-keys/encrypted.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 59cb77b237b3..e07092ea301a 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -579,6 +580,7 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, { struct encrypted_key_payload *epayload = NULL; unsigned short datablob_len; + unsigned short payload_totallen; unsigned short decrypted_datalen; unsigned short payload_datalen; unsigned int encrypted_datalen; @@ -632,16 +634,22 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, encrypted_datalen = roundup(decrypted_datalen, blksize); - datablob_len = format_len + 1 + strlen(master_desc) + 1 - + strlen(datalen) + 1 + ivsize + 1 + encrypted_datalen; + if (check_add_overflow(format_len + 1 + strlen(master_desc) + 1 + + strlen(datalen) + 1 + ivsize + 1, + encrypted_datalen, &datablob_len)) + return ERR_PTR(-EINVAL); - ret = key_payload_reserve(key, payload_datalen + datablob_len - + HASH_SIZE + 1); + if (check_add_overflow(datablob_len, + payload_datalen + HASH_SIZE + 1, + &payload_totallen)) + return ERR_PTR(-EINVAL); + + ret = key_payload_reserve(key, payload_totallen); if (ret < 0) return ERR_PTR(ret); - epayload = kzalloc(sizeof(*epayload) + payload_datalen + - datablob_len + HASH_SIZE + 1, GFP_KERNEL); + epayload = kzalloc_flex(*epayload, payload_data, payload_totallen, + GFP_KERNEL); if (!epayload) return ERR_PTR(-ENOMEM); From 5225b8eec4c9bb21aecff6295fab6346a3c3738e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 11 Sep 2026 15:45:37 -0600 Subject: [PATCH 0906/1317] mailmap: update entry for Jens Axboe I recently changed jobs, let's update the .mailmap entry so that patches are attributed to the right (current) company. Signed-off-by: Jens Axboe Signed-off-by: Linus Torvalds --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 7c02242d4ed7..29c556cf597b 100644 --- a/.mailmap +++ b/.mailmap @@ -421,6 +421,7 @@ Jens Axboe Jens Axboe Jens Axboe Jens Axboe +Jens Axboe Jens Osterkamp Jens Wiklander Jernej Skrabec From 764dcebb033764633700a036c7351a7c6350eec6 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:23 +0000 Subject: [PATCH 0907/1317] neighbour: Add missing RCU annotation for neightbl_dump_info(). neightbl_dump_info() fetches the first non-default neigh_parms with list_next_entry(&tbl->parms, ...) and iterates through the list with list_for_each_entry_from_rcu(). However, list_next_entry() does not use RCU helper. Let's use list_for_each_entry_rcu() and skip the default parms. Fixes: 4ae34be50064 ("neighbour: Convert RTM_GETNEIGHTBL to RCU.") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 1349c0eedb64..49dd7df149ef 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2611,11 +2611,14 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) break; nidx = 0; - p = list_next_entry(&tbl->parms, list); - list_for_each_entry_from_rcu(p, &tbl->parms_list, list) { + + list_for_each_entry_rcu(p, &tbl->parms_list, list) { if (!net_eq(neigh_parms_net(p), net)) continue; + if (!p->dev) + continue; + if (nidx < neigh_skip) goto next; From 6d79b223ec44ada58ad37db42f539b60985a7722 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:24 +0000 Subject: [PATCH 0908/1317] neighbour: Enforce min/max to NDTPA_INTERVAL_PROBE_TIME_MS. NDTPA_INTERVAL_PROBE_TIME_MS sets .type and .min but misses .validation_type, so no validation is applied: # ynl --family rt-neigh --do setneightbl \ --json '{"name": "arp_cache", "parms": {"interval-probe-time-ms": 0}}' # ynl --family rt-neigh --dump getneightbl --output-json | \ jq '.[] | select(.name == "arp_cache" and has("config")) | .parms["interval-probe-time-ms"]' 0 Moreover, nla_get_msecs() uses msecs_to_jiffies(), and u64 is silently cast to u32, so a larger value can bypass the min check: e.g. 4294967296 == 0x100000000 # ynl --family rt-neigh --do setneightbl \ --json '{"name": "arp_cache", "parms": {"interval-probe-time-ms": 4294967296}}' # ynl --family rt-neigh --dump getneightbl --output-json | \ jq '.[] | select(.name == "arp_cache" and has("config")) | .parms["interval-probe-time-ms"]' 0 msecs_to_jiffies() returns MAX_JIFFY_OFFSET if the value is larger than INT_MAX. Also, INT_MAX ms overflows int NEIGH_VAR() when HZ > 1000 (Alpha, MIPS), and passing a negative integer to queue_delayed_work(unsigned long delay) causes sign extension, which wraps around the expiry time to the past, resulting in it being handled as 0 delay in the timer wheel. Let's use NLA_POLICY_FULL_RANGE() and limit the max to 1 day. The same max check is applied to sysctl as well. Note that this controls the probe interval for NTF_MANAGED entries, so the max of 1 day is unlikely to break any deployments. Fixes: 211da42eaa45 ("net, neigh: introduce interval_probe_time_ms for periodic probe") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-neigh.yaml | 3 +++ Documentation/networking/ip-sysctl.rst | 2 +- net/core/neighbour.c | 17 +++++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Documentation/netlink/specs/rt-neigh.yaml b/Documentation/netlink/specs/rt-neigh.yaml index 0f46ef313590..c8e55c98d564 100644 --- a/Documentation/netlink/specs/rt-neigh.yaml +++ b/Documentation/netlink/specs/rt-neigh.yaml @@ -341,6 +341,9 @@ attribute-sets: - name: interval-probe-time-ms type: u64 + checks: + min: 1 + max: 86400000 operations: enum-model: directional diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index 208f46967ee5..b05829e44d8f 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -248,7 +248,7 @@ neigh/default/unres_qlen - INTEGER neigh/default/interval_probe_time_ms - INTEGER The probe interval for neighbor entries with NTF_MANAGED flag, - the min value is 1. + the min value is 1, and the max value is 86400000 (1 day). Default: 5000 diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 49dd7df149ef..0db78a0dfb51 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2359,6 +2359,13 @@ static const struct nla_policy nl_neightbl_policy[NDTA_MAX+1] = { [NDTA_PARMS] = { .type = NLA_NESTED }, }; +#define NTBL_PARM_MS_MAX (24 * 60 * 60 * MSEC_PER_SEC) + +static const struct netlink_range_validation nl_ntbl_parm_ms_range = { + .min = 1, + .max = NTBL_PARM_MS_MAX, +}; + static const struct nla_policy nl_ntbl_parm_policy[NDTPA_MAX+1] = { [NDTPA_IFINDEX] = { .type = NLA_U32 }, [NDTPA_QUEUE_LEN] = { .type = NLA_U32 }, @@ -2375,7 +2382,8 @@ static const struct nla_policy nl_ntbl_parm_policy[NDTPA_MAX+1] = { [NDTPA_ANYCAST_DELAY] = { .type = NLA_U64 }, [NDTPA_PROXY_DELAY] = { .type = NLA_U64 }, [NDTPA_LOCKTIME] = { .type = NLA_U64 }, - [NDTPA_INTERVAL_PROBE_TIME_MS] = { .type = NLA_U64, .min = 1 }, + [NDTPA_INTERVAL_PROBE_TIME_MS] = NLA_POLICY_FULL_RANGE(NLA_U64, + &nl_ntbl_parm_ms_range), }; static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh, @@ -3672,12 +3680,13 @@ static int neigh_proc_dointvec_ms_jiffies_positive(const struct ctl_table *ctl, void *buffer, size_t *lenp, loff_t *ppos) { struct ctl_table tmp = *ctl; - int ret; + int ret, min, max; - int min = msecs_to_jiffies(1); + min = msecs_to_jiffies(1); + max = msecs_to_jiffies(NTBL_PARM_MS_MAX); tmp.extra1 = &min; - tmp.extra2 = NULL; + tmp.extra2 = &max; ret = proc_dointvec_ms_jiffies_minmax(&tmp, write, buffer, lenp, ppos); neigh_proc_update(ctl, write); From 7b430fcfc972f61b09cc19ca95997586af4a147d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:25 +0000 Subject: [PATCH 0909/1317] neighbour: Don't render blackhole_netdev via RTM_GETNEIGHTBL. The cited commits started to initialise blackhole_netdev with neigh_parms_alloc(). This is visible in init_net as the ifindex==0 entries via RTM_GETNEIGHTBL: # ynl --family rt-neigh --dump getneightbl --output-json \ | jq '.[] | select(.parms.ifindex == 0) | {name: .name, ifindex: .parms.ifindex}' { "name": "arp_cache", "ifindex": 0 } { "name": "ndisc_cache", "ifindex": 0 } For RTM_SETNEIGHTBL, ifindex being 0 means wildcard. Let's skip blackhole_netdev's parms in neightbl_dump_info(). Note that lookup_neigh_parms() does not need the same change because the default parms is always the first entry and matches with ifindex == 0. Fixes: e5f80fcf869a ("ipv6: give an IPv6 dev to blackhole_netdev") Fixes: 22600596b675 ("ipv4: give an IPv4 dev to blackhole_netdev") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-4-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 0db78a0dfb51..02bf940ce00a 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2624,7 +2624,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) if (!net_eq(neigh_parms_net(p), net)) continue; - if (!p->dev) + if (!p->dev || p->dev == blackhole_netdev) continue; if (nidx < neigh_skip) From 979aabdad8dd03394467ee484a1a70f3d40b19ba Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:26 +0000 Subject: [PATCH 0910/1317] neighbour: Skip default parms when resumed in neightbl_dump_info(). neightbl_dump_info() calls neightbl_fill_info() in each loop to render the default parms. If there are many devices and neightbl_fill_param_info() failed, neightbl_fill_info() is called again when the dump resumes: # ynl --family rt-neigh --dump getneightbl --output-json | jq '.[] | {name: .name, ifindex: .parms.ifindex}' ... { "name": "ndisc_cache", "ifindex": null } ... { "name": "ndisc_cache", "ifindex": 6 } { "name": "ndisc_cache", "ifindex": null } { "name": "ndisc_cache", "ifindex": 5 } Let's skip neightbl_fill_info() if it is already called in neightbl_dump_info(). Note that we cannot use !neigh_skip instead of !default_skip because default_skip == 1 && neigh_skip == 0 could be true if the first neightbl_fill_param_info() fails. Also, nidx must be cleared at the end of each table loop; otherwise, if neightbl_fill_info() for a subsequent table fails, the leftover nidx from the previous table would be saved in cb->args[1], resulting in erroneously skipping parms of the subsequent table in the next dump. Fixes: c7fb64db001f ("[NETLINK]: Neighbour table configuration and statistics via rtnetlink") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-5-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 02bf940ce00a..7448320f7ad5 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2587,9 +2587,10 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) { const struct nlmsghdr *nlh = cb->nlh; struct net *net = sock_net(skb->sk); + int default_skip = cb->args[2]; + int neigh_skip = cb->args[1]; int family, tidx, nidx = 0; int tbl_skip = cb->args[0]; - int neigh_skip = cb->args[1]; struct neigh_table *tbl; if (cb->strict_check) { @@ -2613,12 +2614,13 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) if (tidx < tbl_skip || (family && tbl->family != family)) continue; - if (neightbl_fill_info(skb, tbl, NETLINK_CB(cb->skb).portid, + if (!default_skip && + neightbl_fill_info(skb, tbl, NETLINK_CB(cb->skb).portid, nlh->nlmsg_seq, RTM_NEWNEIGHTBL, NLM_F_MULTI) < 0) break; - nidx = 0; + default_skip = 1; list_for_each_entry_rcu(p, &tbl->parms_list, list) { if (!net_eq(neigh_parms_net(p), net)) @@ -2641,12 +2643,15 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) } neigh_skip = 0; + nidx = 0; + default_skip = 0; } out: rcu_read_unlock(); cb->args[0] = tidx; cb->args[1] = nidx; + cb->args[2] = default_skip; return skb->len; } From c60ae98c5aa64021751b38ab1313b19d620bf640 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 11 Sep 2026 10:25:04 +0100 Subject: [PATCH 0911/1317] 9p: Fix v9fs_issue_write() to update i_size and remote_i_size Fix v9fs_issue_write() to update i_size and remote_i_size to the new size of the server file if we made it larger, using the start fpos and the count returned by p9_client_write() to calculate the new minimum file size. This assumes that if the 9P server makes a short write (say it hits ENOSPC), a reduced count is returned. Fixes: 5fb70e7275a6 ("netfs, 9p: Implement helpers for new write code") Reported-by: Michael Mulqueen Closes: https://lore.kernel.org/r/fbb9e395-1e07-4212-8f70-23f3cd498074@method-b.uk/ Cc: stable@vger.kernel.org Signed-off-by: David Howells Message-ID: <2226525.1789118704@warthog.procyon.org.uk> Signed-off-by: Dominique Martinet --- fs/9p/vfs_addr.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 1ac0b3dcc077..13cf87a5f90c 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -54,11 +54,37 @@ static void v9fs_begin_writeback(struct netfs_io_request *wreq) static void v9fs_issue_write(struct netfs_io_subrequest *subreq) { struct p9_fid *fid = subreq->rreq->netfs_priv; + struct inode *inode = subreq->rreq->inode; + struct netfs_inode *ictx = netfs_inode(inode); int err, len; len = p9_client_write(fid, subreq->start, &subreq->io_iter, &err); - if (len > 0) + if (len > 0) { + uoff_t end = subreq->start + len, i_size, remote, zp; + bool set = false; + + spin_lock(&inode->i_lock); + + /* We can read the sizes directly as we hold i_lock. */ + i_size = inode->i_size; + remote = ictx->_remote_i_size; + zp = ictx->_zero_point; + + if (end > i_size) { + i_size = end; + set = true; + } + if (end > remote) { + remote = end; + set = true; + } + + if (set) + netfs_write_sizes(inode, i_size, remote, zp); + spin_unlock(&inode->i_lock); + __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + } netfs_write_subrequest_terminated(subreq, len ?: err); } From 5cddf63367ef894ba0026dfa2d524346f7048de9 Mon Sep 17 00:00:00 2001 From: Krish Gulati Date: Sat, 12 Sep 2026 12:36:13 +0530 Subject: [PATCH 0912/1317] ALSA: hda/realtek: Add quirk for HP Victus 15-fa1xxx (MB 8BB1) mute LED The mute LED on this board does not respond to mute state changes because no fixup is matched for SSID 103c:8bb1. The reporter verified the LED can be toggled manually via COEF index 0x0B. Reported-by: Mazen Ahmed Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221982 Tested-by: Mazen Ahmed Signed-off-by: Krish Gulati Link: https://patch.msgid.link/20260912070618.21272-1-krishgulati7@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 3abee617e86e..dc73fa95b00a 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7568,6 +7568,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), From 221253723dc58bb901c3f27a7659823e63fc598c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 10 Sep 2026 17:52:23 +0200 Subject: [PATCH 0913/1317] ALSA: bcd2000: Fix race between rawmidi and disconnect Although we tried to fix the potential UAF issues at USB disconnect on bcd2000 driver, there is still an overlooked case -- namely, when a rawmidi trigger callback has been already running at USB disconnect handling, the in-flight function (e.g. bcd2000_midi_send()) could still access the URB, because the previous URB NULL-check & clearance was considered only for the URB complete callbacks, but not about the parallel rawmidi operations. For addressing the race, this patch introduced a new spinlock that covers each rawmidi operation as well as the rawmidi handling in the complete callback. The URB is cleared with the lock, so it guarantees that the pending rawmidi task already finished or a NULL check is effective. Fixes: 459d3a64766f ("ALSA: bcd2000: clear the URB pointers on disconnect") Link: https://patch.msgid.link/20260910155227.996210-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/bcd2000/bcd2000.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c index c5c542d17ccc..2bd49bf82748 100644 --- a/sound/usb/bcd2000/bcd2000.c +++ b/sound/usb/bcd2000/bcd2000.c @@ -43,6 +43,7 @@ struct bcd2000 { struct usb_interface *intf; int card_index; + spinlock_t midi_lock; int midi_out_active; struct snd_rawmidi *rmidi; struct snd_rawmidi_substream *midi_receive_substream; @@ -90,6 +91,8 @@ static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) { struct bcd2000 *bcd2k = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&bcd2k->midi_lock); bcd2k->midi_receive_substream = up ? substream : NULL; } @@ -195,6 +198,8 @@ static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream, { struct bcd2000 *bcd2k = substream->rmidi->private_data; + guard(spinlock_irqsave)(&bcd2k->midi_lock); + if (up) { bcd2k->midi_out_substream = substream; /* check if there is data userspace wants to send */ @@ -219,6 +224,7 @@ static void bcd2000_output_complete(struct urb *urb) return; /* check if there is more data userspace wants to send */ + guard(spinlock_irqsave)(&bcd2k->midi_lock); bcd2000_midi_send(bcd2k); } @@ -234,6 +240,8 @@ static void bcd2000_input_complete(struct urb *urb) if (!bcd2k || urb->status == -ESHUTDOWN) return; + guard(spinlock_irqsave)(&bcd2k->midi_lock); + if (urb->actual_length > 0) bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer, urb->actual_length); @@ -348,16 +356,26 @@ static int bcd2000_init_midi(struct bcd2000 *bcd2k) return 0; } +static void bcd2000_midi_free(struct bcd2000 *bcd2k, + struct urb **urb_p) +{ + struct urb *urb = *urb_p; + + if (!urb) + return; + + usb_poison_urb(urb); + scoped_guard(spinlock_irq, &bcd2k->midi_lock) + *urb_p = NULL; + + usb_free_urb(urb); +} + static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k, struct usb_interface *interface) { - usb_poison_urb(bcd2k->midi_out_urb); - usb_poison_urb(bcd2k->midi_in_urb); - - usb_free_urb(bcd2k->midi_out_urb); - usb_free_urb(bcd2k->midi_in_urb); - bcd2k->midi_out_urb = NULL; - bcd2k->midi_in_urb = NULL; + bcd2000_midi_free(bcd2k, &bcd2k->midi_out_urb); + bcd2000_midi_free(bcd2k, &bcd2k->midi_in_urb); if (bcd2k->intf) { usb_set_intfdata(bcd2k->intf, NULL); @@ -393,6 +411,7 @@ static int bcd2000_probe(struct usb_interface *interface, bcd2k->card = card; bcd2k->card_index = card_index; bcd2k->intf = interface; + spin_lock_init(&bcd2k->midi_lock); snd_card_set_dev(card, &interface->dev); From effce1cb87ee0d8b3a8cbe7722968f4ea7efd360 Mon Sep 17 00:00:00 2001 From: Sophie D Date: Wed, 9 Sep 2026 21:49:10 -0400 Subject: [PATCH 0914/1317] drm/gud: Ignore damage clips in full update mode When running in full update mode, previously small updates (such as moving the mouse across the screen) would cause many full frames to be generated. This would bog down the bus and lower the effective framerate significantly - I was seeing a drop from 60 FPS to 2 FPS. Set ignore_damage_clips in full update mode so the damage iterator yields a single full-plane rectangle instead of one per clip. Fixes: 73cfd166e045 ("drm/gud: Replace simple display pipe with DRM atomic helpers") Cc: # 6.18.x Signed-off-by: Sophie D Reviewed-by: Thomas Zimmermann Acked-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260910014910.8564-1-patches@scd31.com --- drivers/gpu/drm/gud/gud_pipe.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index 5ef887d8485a..3388fdc8ea7b 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -482,6 +482,9 @@ int gud_plane_atomic_check(struct drm_plane *plane, if (!new_plane_state->visible) return 0; + if (gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE) + new_plane_state->ignore_damage_clips = true; + if (old_plane_state->rotation != new_plane_state->rotation) crtc_state->mode_changed = true; From 59ced288fcba9e91bd38e61a972ad782c4edb7d0 Mon Sep 17 00:00:00 2001 From: Sajal Gupta Date: Wed, 2 Sep 2026 18:00:57 +0530 Subject: [PATCH 0915/1317] drm/gud: fix out-of-bounds write in gud_plane_atomic_check() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plane property loop uses req->properties[num_properties + i] as write index while simultaneously incrementing `num_properties` inside the loop. At iteration i, num_properties has also incremented by i, so the write is done at `initial_num_properties + 2*i`, skipping every other index and advancing by 2 per iteration. With just 2 connector and 32 plane properties the last write happens at index 64, one slot past the end of the 64-slot (indices 0–63) allocation. A USB device can trigger OOB by advertising the maximum number of properties. Fix by dropping the redundant `+ i`; num_properties is already the correct running index, as gud_connector_fill_properties() fills the preceding slots. Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260821071812.16500-1-sajal2005gupta%40gmail.com?part=1 Signed-off-by: Sajal Gupta Cc: Acked-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260902123254.36987-1-sajal2005gupta@gmail.com --- drivers/gpu/drm/gud/gud_pipe.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index 3388fdc8ea7b..aa7792966287 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -565,8 +565,8 @@ int gud_plane_atomic_check(struct drm_plane *plane, goto out; } - req->properties[num_properties + i].prop = cpu_to_le16(prop); - req->properties[num_properties + i].val = cpu_to_le64(val); + req->properties[num_properties].prop = cpu_to_le16(prop); + req->properties[num_properties].val = cpu_to_le64(val); num_properties++; } From ba970587a0e1203b6a0934b5b9174886b4c4d24c Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sat, 12 Sep 2026 08:09:14 -0700 Subject: [PATCH 0916/1317] drm/msm/a6xx+: Increase GMU FW init timeout We were using 10ms, kgsl uses 100ms. In practice it is usually takes less than 10ms, but very occasionally goes a bit above 10ms, leading to a "GMU firmware inialization timed out", from which point things go south. There are probably some things that could be done to speed up init, like increasing GMU freq. But to be safe, increase the timeout to match kgsl. Signed-off-by: Rob Clark Reviewed-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/753014/ Message-ID: <20260912150915.28700-1-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index 27cac853975f..6d49c51df1a2 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -320,7 +320,7 @@ static int a6xx_gmu_start(struct a6xx_gmu *gmu) gmu_write(gmu, REG_A6XX_GMU_CM3_SYSRESET, 0); ret = gmu_poll_timeout(gmu, REG_A6XX_GMU_CM3_FW_INIT_RESULT, val, - (val & mask) == reset_val, 100, 10000); + (val & mask) == reset_val, 100, 100000); if (ret) DRM_DEV_ERROR(gmu->dev, "GMU firmware initialization timed out\n"); From 0d7823cd4cda35f2060a685fa276ff7709915abc Mon Sep 17 00:00:00 2001 From: Siddharth Chintamaneni Date: Wed, 2 Sep 2026 17:14:13 +0000 Subject: [PATCH 0917/1317] bpf: Allow terminal gotox instructions check_subprogs() treats gotox as a direct jump and validates its reserved zero offset. When gotox is the final instruction, this produces a synthetic successor one instruction past the end of the subprogram and rejects an otherwise valid program. Skip direct-offset validation for gotox and accept it as a non-fallthrough terminal instruction. Its actual targets remain validated from the instruction-array jump table during CFG construction. Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps") Signed-off-by: Siddharth Chintamaneni Reviewed-by: Anton Protopopov Link: https://lore.kernel.org/r/20260902171414.96165-1-sidchintamaneni@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1b0b1fb62878..ddba53eaa333 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3042,6 +3042,8 @@ static int check_subprogs(struct bpf_verifier_env *env) subprog[cur_subprog].exit_idx = i; goto next; } + if (insn_is_gotox(&insn[i])) + goto next; off = i + bpf_jmp_offset(&insn[i]) + 1; if (off < subprog_start || off >= subprog_end) { verbose(env, "jump out of range from insn %d to %d\n", i, off); @@ -3061,7 +3063,8 @@ static int check_subprogs(struct bpf_verifier_env *env) */ if (code != (BPF_JMP | BPF_EXIT) && code != (BPF_JMP32 | BPF_JA) && - code != (BPF_JMP | BPF_JA)) { + code != (BPF_JMP | BPF_JA) && + !insn_is_gotox(&insn[i])) { verbose(env, "last insn is not an exit or jmp\n"); bpf_diag_program_structure( env, i, "subprogram can fall through", From ac781acaef4904b9be64a7b5755c778e5d76ede6 Mon Sep 17 00:00:00 2001 From: Siddharth Chintamaneni Date: Wed, 2 Sep 2026 17:14:14 +0000 Subject: [PATCH 0918/1317] selftests/bpf: Test terminal gotox instructions Add tests that place gotox at the end of the main program and a subprogram, with each jump-table target preceding the gotox instruction. This tests gotox as a valid non-fallthrough terminal instruction. Signed-off-by: Siddharth Chintamaneni Reviewed-by: Anton Protopopov Link: https://lore.kernel.org/r/20260902171414.96165-2-sidchintamaneni@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/verifier_gotox.c | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_gotox.c b/tools/testing/selftests/bpf/progs/verifier_gotox.c index 5b18c9a27717..0e27c2c79c57 100644 --- a/tools/testing/selftests/bpf/progs/verifier_gotox.c +++ b/tools/testing/selftests/bpf/progs/verifier_gotox.c @@ -47,6 +47,54 @@ DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_src_reg, BPF_REG_1, 0, 0, __fa DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_off, BPF_REG_0, 1, 0, __failure __msg("BPF_JA|BPF_X uses reserved fields")) DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_imm, BPF_REG_0, 0, 1, __failure __msg("BPF_JA|BPF_X uses reserved fields")) +#define DEFINE_TERMINAL_GOTOX_PROG(NAME, BASE) \ + __naked void NAME(void) \ + { \ + asm volatile (" \ + .pushsection .jumptables,\"\",@progbits; \ +jt0_%=: \ + .quad ret0_%= - " BASE "; \ + .size jt0_%=, 8; \ + .global jt0_%=; \ + .popsection; \ + \ + r0 = jt0_%= ll; \ + r0 = *(u64 *)(r0 + 0); \ + goto end_%=; \ +ret0_%=: \ + r0 = 0; \ + exit; \ +end_%=: \ + .8byte %[gotox_r0]; \ +" : \ + : __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, \ + BPF_REG_0, 0, 0, 0)) \ + : __clobber_all); \ + } + +SEC("socket") +__success __retval(0) +DEFINE_TERMINAL_GOTOX_PROG(jump_table_terminal_gotox, "socket") + +static __noinline __used +DEFINE_TERMINAL_GOTOX_PROG(terminal_gotox_subprog1, ".text") + +static __noinline __used int terminal_gotox_subprog2(void) +{ + return 0; +} + +SEC("socket") +__success __retval(0) +__naked void jump_table_terminal_gotox_subprog(void) +{ + asm volatile (" \ + call terminal_gotox_subprog1; \ + call terminal_gotox_subprog2; \ + exit; \ +" ::: __clobber_all); +} + /* * Gotox is forbidden when there is no jump table loaded * which points to the sub-function where the gotox is used From b4dcc18b97913888e8d009624e07c8014ce41b84 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Fri, 11 Sep 2026 22:25:12 +0800 Subject: [PATCH 0919/1317] ftrace: Use rcu_assign_pointer() for tmp_ops filter hash tmp_ops.func_hash->filter_hash is annotated __rcu, but update_ftrace_direct_mod() assigns hash to it directly. Sparse reports an address-space mismatch. Use rcu_assign_pointer() for the assignment. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260911142512.19344-1-leon.hwang@linux.dev Fixes: 50b35c9e50a8 ("ftrace: Use hash argument for tmp_ops in update_ftrace_direct_mod") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609110704.Q3M5vCDV-lkp@intel.com/ Signed-off-by: Leon Hwang Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 53d5db60bfa5..673a54fdf392 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -6675,7 +6675,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b /* Enable the tmp_ops to have the same functions as the hash object. */ ftrace_ops_init(&tmp_ops); - tmp_ops.func_hash->filter_hash = hash; + rcu_assign_pointer(tmp_ops.func_hash->filter_hash, hash); err = register_ftrace_function_nolock(&tmp_ops); if (err) From bcfe2816e6ec46c3f4c58aa4264476665ddb3f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Ahmet=20Memi=C5=9F?= Date: Fri, 11 Sep 2026 18:56:47 +0300 Subject: [PATCH 0920/1317] tracing: Don't dereference trace_event_file in deferred trigger free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The enable_event trigger defers trace_event_put_ref() to the trigger free kthread, but the trace_event_file can already be freed when the instance is removed. Keep the trace_event_call directly in enable_trigger_data so the deferred free does not access the freed trace_event_file. Cc: stable@vger.kernel.org Fixes: e091351b3881 ("tracing: Delay module ref count for "enable_event" trigger") Reported-by: Alexander Gordeev Closes: https://lore.kernel.org/all/20260828134340.2501683A24-agordeev@linux.ibm.com/ Link: https://patch.msgid.link/20260911155650.354844-1-aliamemis@disroot.org Signed-off-by: Ali Ahmet Memiş Signed-off-by: Steven Rostedt --- kernel/trace/trace.h | 1 + kernel/trace/trace_events_trigger.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index bd3c8f80300f..3749485a7d85 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -1953,6 +1953,7 @@ struct event_trigger_data { struct enable_trigger_data { struct trace_event_file *file; + struct trace_event_call *call; bool enable; bool hist; }; diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c index 149300cc5e8a..4d2fde996c0f 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c @@ -1728,7 +1728,8 @@ static void enable_trigger_private_data_free(struct event_trigger_data *data) { struct enable_trigger_data *enable_data = data->private_data; - trace_event_put_ref(enable_data->file->event_call); + /* The file may already be freed here, only the call is kept alive */ + trace_event_put_ref(enable_data->call); kfree(enable_data); } @@ -1801,6 +1802,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops, enable_data->hist = hist; enable_data->enable = enable; enable_data->file = event_enable_file; + enable_data->call = event_enable_file->event_call; trigger_data = trigger_data_alloc(cmd_ops, cmd, param, enable_data); if (!trigger_data) From 85136bf22404474a815fc0ed26ec0d1cbc1bc3f9 Mon Sep 17 00:00:00 2001 From: "Jose Fernandez (Anthropic)" Date: Wed, 9 Sep 2026 17:51:04 +0000 Subject: [PATCH 0921/1317] bpf: Avoid soft lockup in __htab_map_lookup_and_delete_batch() __htab_map_lookup_and_delete_batch() has no rescheduling point. The batch count bounds how many entries are copied out, not how many buckets are visited, so one BPF_MAP_LOOKUP_BATCH call can walk the map end to end. The empty-bucket fast path is worse: it stays inside a single rcu_read_lock() / bpf_disable_instrumentation() section for any run of consecutive empty buckets. That holds up on small maps, but it falls apart at scale. On a 144-CPU arm64 host running a CONFIG_PREEMPT_NONE kernel, periodic BPF_MAP_LOOKUP_BATCH calls against an LRU hash map with 16,777,216 buckets held a CPU inside the batch op for 77+ seconds and triggered the soft lockup watchdog. Commit 75134f16e7dd ("bpf: Add schedule points in batch ops") fixed this same problem in the generic batch ops, but not in this htab-native path, which every htab-based hash map variant uses for its lookup[_and_delete] batch ops. Complete that fix here. Leave the critical section after 64 consecutive empty buckets, call cond_resched_tasks_rcu_qs(), and resume at the saved bucket cursor. No locks are held at that point, and resuming from the cursor is already the function's behavior for non-empty buckets. Add the same call to the per-bucket loop after copy_to_user(), where every lock has been dropped. cond_resched_rcu() is not enough here: sleeping with bpf_prog_active elevated makes tracing programs on that CPU silently skip their invocations. Plain cond_resched() is not enough either. It is a no-op under PREEMPT and PREEMPT_LAZY, the only models arm64 and x86 have offered since commit 7dadeaa6e851 ("sched: Further restrict the preemption modes"). It is also never a Tasks RCU quiescent state, in any model: the reschedule counts as a preemption. The walking task stays a holdout and stalls every synchronize_rcu_tasks() caller, ftrace and BPF trampoline teardown included, until the syscall returns [1]. cond_resched_tasks_rcu_qs() is the usual tool for that [2]. It reports the quiescent state at each yield and still reschedules as cond_resched() does on PREEMPT_NONE and PREEMPT_VOLUNTARY kernels. Fixes: 057996380a42 ("bpf: Add batch ops to all htab bpf map") Cc: "Paul E. McKenney" Cc: Rik van Riel Link: https://lore.kernel.org/bpf/20260715215314.44423f47@fangorn/ [1] Link: https://lore.kernel.org/bpf/9d444098-7c03-4163-af12-bd0a79a51443@paulmck-laptop/ [2] Assisted-by: LLM Signed-off-by: Jose Fernandez (Anthropic) Signed-off-by: Josef Bacik Reviewed-by: Rik van Riel Link: https://lore.kernel.org/r/20260909-b4-htab-batch-resched-v2-1-0cb529d8f95a@toxicpanda.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 6f331c80130d..a72dc5b9f184 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1772,6 +1772,12 @@ static int htab_lru_percpu_map_lookup_and_delete_elem(struct bpf_map *map, flags); } +/* + * Max consecutive empty buckets to walk in one RCU + + * instrumentation-disabled section before rescheduling. + */ +#define HTAB_BATCH_EMPTY_RESCHED 64 + static int __htab_map_lookup_and_delete_batch(struct bpf_map *map, const union bpf_attr *attr, @@ -1793,6 +1799,7 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, unsigned long flags = 0; bool locked = false; struct htab_elem *l; + u32 empty_cnt = 0; struct bucket *b; int ret = 0; @@ -1971,12 +1978,21 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, } next_batch: - /* If we are not copying data, we can go to next bucket and avoid - * unlocking the rcu. + /* + * If we are not copying data, we can go to next bucket and avoid + * unlocking the rcu. Bound the walk though: after + * HTAB_BATCH_EMPTY_RESCHED consecutive empty buckets, fully exit + * the critical section (no locks are held here) and reschedule. */ if (!bucket_cnt && (batch + 1 < htab->n_buckets)) { batch++; - goto again_nocopy; + if (++empty_cnt < HTAB_BATCH_EMPTY_RESCHED) + goto again_nocopy; + empty_cnt = 0; + rcu_read_unlock(); + bpf_enable_instrumentation(); + cond_resched_tasks_rcu_qs(); + goto again; } rcu_read_unlock(); @@ -1990,11 +2006,13 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, } total += bucket_cnt; + empty_cnt = 0; batch++; if (batch >= htab->n_buckets) { ret = -ENOENT; goto after_loop; } + cond_resched_tasks_rcu_qs(); goto again; after_loop: From 06bb43d8c79762fa3452f292cd080e54bef5d431 Mon Sep 17 00:00:00 2001 From: Vlad Poenaru Date: Wed, 2 Sep 2026 09:13:47 -0700 Subject: [PATCH 0922/1317] kbuild: don't delete in-flight filechk temporaries in asm-headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 2d69b891e646 ("kbuild: Support generated asm-headers in subdirectories") switched the stale-wrapper sweep in scripts/Makefile.asm-headers from $(wildcard $(obj)/*.h) to a find(1) invocation, so that generated headers in subdirectories are considered. The two do not match the same set of files. Make's $(wildcard) uses glob semantics, where a leading '.' has to be matched explicitly, whereas find's -name uses fnmatch() without FNM_PERIOD, so '*.h' matches dotfiles as well. filechk writes its output to $(dir $@).tmp_$(notdir $@) before renaming it into place, so such a scratch file, if it happens to exist in $(obj) when the sub-make is parsed, is now picked up in old-headers. It appears in neither generic-y, generated-y nor syscall-y, is therefore classified as unwanted, and cmd_remove deletes it. On x86 this races with archprepare, which lists both asm-generic and arch/x86/include/generated/asm/cpufeaturemasks.h as prerequisites. Under -j they run concurrently against the same directory, and the build fails intermittently: mv: cannot stat 'arch/x86/include/generated/asm/.tmp_cpufeaturemasks.h': No such file or directory make[1]: *** [arch/x86/Makefile:269: arch/x86/include/generated/asm/cpufeaturemasks.h] Error 1 The same commit also converted the generic wrapper rule to filechk, so those wrappers now create .tmp_*.h in $(obj) too and can race among themselves. Restore the previous behaviour by excluding dotfiles from the sweep. Subdirectories, which is what the find(1) conversion was for, keep being descended into. While at it, quote the -name argument: it is currently expanded by the shell against the build directory before find sees it. Fixes: 2d69b891e646 ("kbuild: Support generated asm-headers in subdirectories") Signed-off-by: Vlad Poenaru Reviewed-by: Nathan Chancellor Reviewed-by: Thomas Weißschuh Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260902161347.4163577-1-vlad.wing@gmail.com Signed-off-by: Nicolas Schier --- scripts/Makefile.asm-headers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Makefile.asm-headers b/scripts/Makefile.asm-headers index b38931314ad7..f1c3d287c14b 100644 --- a/scripts/Makefile.asm-headers +++ b/scripts/Makefile.asm-headers @@ -48,7 +48,7 @@ syscall-y := $(addprefix $(obj)/, $(syscall-y)) generated-y := $(addprefix $(obj)/, $(generated-y)) # Remove stale wrappers when the corresponding files are removed from generic-y -old-headers := $(shell test -d $(obj) && find $(obj) -name *.h) +old-headers := $(shell test -d $(obj) && find $(obj) -name '*.h' ! -name '.*') unwanted := $(filter-out $(generic-y) $(generated-y) $(syscall-y),$(old-headers)) filechk_wrap = echo "\#include " From 4f73462856576797b8f3c55564a9be99f76dc67b Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 31 Aug 2026 18:46:31 -0700 Subject: [PATCH 0923/1317] scripts/sorttable: Mark long_size as __maybe_unused When building in a kernel tree prior to commit b055f4c431e3 ("sorttable: Move ELF parsing into scripts/elf-parse.[ch]") with clang-23 or newer, which implements a new warning under -Wunused-but-set-variable for static global variable, there is a warning from sorttable because long_size is unused when MCOUNT_SORT_ENABLED is not set: scripts/sorttable.c:452:12: error: variable 'long_size' set but not used [-Werror,-Wunused-but-set-global] 452 | static int long_size; | ^ Mark long_size as __maybe_unused to avoid inserting more ugly #ifdef directives while insuring the warning does not reappear, as the aforementioned change does not alter the uses of long_size, so it appears to be coincidence that the warning disappears after this refactoring. Cc: stable@vger.kernel.org Signed-off-by: Nathan Chancellor Tested-by: Nicolas Schier Link: https://patch.msgid.link/20260831-sorttable-long_size-unused-but-set-global-v1-1-8a96b88697e5@kernel.org Signed-off-by: Nicolas Schier --- scripts/sorttable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sorttable.c b/scripts/sorttable.c index d8dc2a1b7c31..d7b50581c732 100644 --- a/scripts/sorttable.c +++ b/scripts/sorttable.c @@ -116,7 +116,7 @@ static inline void *get_index(void *start, int entsize, int index) } static int extable_ent_size; -static int long_size; +static int long_size __maybe_unused; #define ERRSTR_MAXSZ 256 From 281b61d408d4c39544583e393c6707af0ef5ee50 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 8 Sep 2026 21:55:01 +0100 Subject: [PATCH 0924/1317] scripts/mksysmap: drop the MODULE_INFO() symbols from kallsyms Commit 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") keeps .modinfo symbols out of System.map and kallsyms, which assumes unique IDs have a format like '__UNIQUE_ID_modinfo123'. However, commit afb026b6d35c ("compiler: Tweak __UNIQUE_ID() naming"), sent in the same cycle, changes this to '__UNIQUE_ID_modinfo_123'. As a result this regexp has never matched and every kernel since v6.18 has carried one kallsyms entries for every MODULE_INFO() declaration in the kernel whether the modules are compiled or not. That's 5,810 entries for an x86 defconfig build and 15,200 for arm64. On x86 defconfig that is 113 KiB of kallsyms tables and 32 KiB of bzImage, and every lookup walks past them. Fix the pattern. Fixes: 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Nicolas Schier Reviewed-by: Nathan Chancellor Link: https://patch.msgid.link/20260908-build-speedup-v1-1-5dc1ac01672d@kernel.org Signed-off-by: Nicolas Schier --- scripts/mksysmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mksysmap b/scripts/mksysmap index c4531eacde20..56a8b8bbdb37 100755 --- a/scripts/mksysmap +++ b/scripts/mksysmap @@ -83,7 +83,7 @@ / _SDA2_BASE_$/d # MODULE_INFO() -/ __UNIQUE_ID_modinfo[0-9]*$/d +/ __UNIQUE_ID_modinfo_[0-9]*$/d # --------------------------------------------------------------------------- # Ignored patterns From 59351365ac271b5e0eb180f211c531476a36221f Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 8 Sep 2026 21:55:02 +0100 Subject: [PATCH 0925/1317] scripts/mksysmap: fix escape of '$' in the __pi_ pattern Commit b18b047002b7 ("kbuild: change scripts/mksysmap into sed script") converted scripts/mksysmap from a shell script to a sed script. However an error was made - escaping of '$' required \\ escaping in shell but only \ in a sed script. This was mostly corrected in commit 7a6c355b55c0 ("scripts/mksysmap: Fix escape chars '$'"), but this fix missed arm64 PIE namespace local symbols like __pi_$x and __pi_$d which appear in System.map and /proc/kallsyms: $ grep __pi_\\$ /proc/kallsyms | sort -u 0000000000000000 d __pi_$d 0000000000000000 t __pi_$x Fix the escaping properly. Fixes: b18b047002b7 ("kbuild: change scripts/mksysmap into sed script") Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Nathan Chancellor Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260908-build-speedup-v1-2-5dc1ac01672d@kernel.org Signed-off-by: Nicolas Schier --- scripts/mksysmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mksysmap b/scripts/mksysmap index 56a8b8bbdb37..856b26ba2ac0 100755 --- a/scripts/mksysmap +++ b/scripts/mksysmap @@ -35,7 +35,7 @@ / __efistub_/d # arm64 local symbols in PIE namespace -/ __pi_\\$/d +/ __pi_\$/d / __pi_\.L/d # arm64 local symbols in non-VHE KVM namespace From 75f8cf22463d82bb1fb0239a3d485fc8f4c8ef03 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 3 Sep 2026 18:09:20 +0800 Subject: [PATCH 0926/1317] bpf: Fix out-of-bounds read of rtt_min in sock_ops A sockops prog reading skops->rtt_min never checks the sk type: on the tcp_conn_request() path sock_ops->sk is a request_sock (non-full), and the ctx rewrite casts it to a tcp_sock (full) and reads rtt_min past the end of the request_sock, returning dirty adjacent memory. SEC("sockops") int prog(struct bpf_sock_ops *skops) { switch (skops->op) { case BPF_SOCK_OPS_RWND_INIT: leak = skops->rtt_min; /* reads the request_sock OOB */ ... } } For instance one such read returned rtt_min=0xffff8881, the high half of a leaked kernel pointer. Guarding that cast is exactly what SOCK_OPS_GET_FIELD() does -- it checks is_locked_tcp_sock and returns 0 when sock_ops->sk is not a locked full socket. Every other tcp_sock field in sock_ops goes through it; rtt_min is the only one open-coded, so it skips the check. Read rtt_min through SOCK_OPS_GET_FIELD() too. rtt_min is a bit special: it is a struct minmax and we only want the current min, so pass rtt_min.s[0].v. That is equivalent to the old hand-computed offset offsetof(struct tcp_sock, rtt_min) + sizeof_field(struct minmax_sample, t) (s[0] sits at rtt_min + 0 and .v at + sizeof(.t), i.e. what minmax_get() returns), so the loaded field is unchanged and only the full-sock guard is added. The two BUILD_BUG_ON()s that protected the hand-computed offset are no longer needed. Before patch: 0: r1 = *(u64 *)(r1 +0) ; r1 = skops->sk 1: r1 = *(u32 *)(r1 +2324) ; ((tcp_sock *)sk)->rtt_min.s[0].v After patch: 0: *(u64 *)(r1 +56) = r9 1: r9 = *(u8 *)(r1 +50) ; is_locked_tcp_sock 2: if r9 == 0 goto pc+4 ; not a locked full sock -> 0 3: r9 = *(u64 *)(r1 +56) 4: r1 = *(u64 *)(r1 +0) ; r1 = skops->sk 5: r1 = *(u32 *)(r1 +2324) ; rtt_min.s[0].v 6: goto pc+2 7: r9 = *(u64 *)(r1 +56) 8: r1 = 0 Fixes: 44f0e43037d3 ("bpf: Add support for reading sk_state and more") Reported-by: VEGA Signed-off-by: Jiayuan Chen Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260903100921.113374-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index cae43b999162..532405988fd9 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -11106,18 +11106,7 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type, break; case offsetof(struct bpf_sock_ops, rtt_min): - BUILD_BUG_ON(sizeof_field(struct tcp_sock, rtt_min) != - sizeof(struct minmax)); - BUILD_BUG_ON(sizeof(struct minmax) < - sizeof(struct minmax_sample)); - - *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF( - struct bpf_sock_ops_kern, sk), - si->dst_reg, si->src_reg, - offsetof(struct bpf_sock_ops_kern, sk)); - *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg, - offsetof(struct tcp_sock, rtt_min) + - sizeof_field(struct minmax_sample, t)); + SOCK_OPS_GET_FIELD(rtt_min, rtt_min.s[0].v, struct tcp_sock); break; case offsetof(struct bpf_sock_ops, bpf_sock_ops_cb_flags): From 7d70a0b02d262971201fdd1e221586bdd95c2910 Mon Sep 17 00:00:00 2001 From: Zhixing Chen Date: Thu, 3 Sep 2026 18:43:58 +0800 Subject: [PATCH 0927/1317] bpf: Use kvfree() in xdp_test_run_teardown() xdp_test_run_setup() allocates xdp->frames and xdp->skbs with kvmalloc_array(). The setup error path already releases both arrays with kvfree(), while the normal teardown path still uses kfree(). Use kvfree() in xdp_test_run_teardown() as well, so the release helper matches the allocator on both paths. Signed-off-by: Zhixing Chen Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260903104358.29228-1-running910@gmail.com Signed-off-by: Alexei Starovoitov --- net/bpf/test_run.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 5d51f6cb7d15..513354e928cb 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -205,8 +205,8 @@ static void xdp_test_run_teardown(struct xdp_test_data *xdp) { xdp_unreg_mem_model(&xdp->mem); page_pool_destroy(xdp->pp); - kfree(xdp->frames); - kfree(xdp->skbs); + kvfree(xdp->frames); + kvfree(xdp->skbs); } static bool frame_was_changed(const struct xdp_page_head *head) From 6d8c197c9992659a65525a07de4368c8401fdda7 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 8 Sep 2026 15:52:46 +0900 Subject: [PATCH 0928/1317] ntfs: use dynamic MFT tail reservation The ntfs MFT allocator historically treated records below 64 as a permanent extension area and stopped searching for $MFT extent records after record 400. Windows and ntfs3 do not maintain that on-disk layout, so an NTFS volume can have free MFT records while ntfs returns -ENOSPC when $MFT:$DATA needs another mapping-pairs extent. Use record 24 as the first normal record and maintain an in-memory tail reserve of up to four initialized records. Normal allocations skip the reserve, while $MFT metadata extent allocations consume it. When a new tail is initialized, allocate at least two records and reserve the following records to avoid recursive allocation during MFT extension. Existing free runs can seed the reserve on volumes mounted without one. For $MFT/$DATA, constrain an extent record to a record whose byte offset is below the new extent lowest VCN byte offset. This preserves bootstrap reachability without an arbitrary record 400 limit. If no safe record is available, validate and use reserved records 15, 12, 13, and 14 as bootstrap candidates while keeping their MFT bitmap entries in use. This allows existing Windows volumes to extend $MFT using their actual free records and prevents normal file allocation from consuming the metadata reserve. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 11 +- fs/ntfs/mft.c | 418 +++++++++++++++++++++++++++++++++++++---------- fs/ntfs/mft.h | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs/volume.h | 6 + 5 files changed, 348 insertions(+), 91 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 848a0d338b89..81b9e0971b3e 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -2917,7 +2917,7 @@ int ntfs_attr_add(struct ntfs_inode *ni, __le32 type, attr_ni = NULL; /* Allocate new extent. */ - err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL); + err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL, -1); if (err) { ntfs_error(sb, "Failed to allocate extent record"); goto err_out; @@ -3550,7 +3550,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) * new extent and move attribute to it. */ ni = NULL; - err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL); + err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL, -1); if (err) { ntfs_error(sb, "Couldn't allocate MFT record, err : %d", err); return err; @@ -3976,7 +3976,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) unsigned int de_cnt = 0; /* Allocate new mft record. */ - err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL); + err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL, + base_ni->mft_no == FILE_MFT && + ni->type == AT_DATA && + ni->name == AT_UNNAMED ? stop_vcn : -1); if (err) { ntfs_error(sb, "Failed to allocate extent record"); goto put_err_out; @@ -4836,7 +4839,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi } /* Allocate new mft record. */ - err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL); + err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL, -1); if (err) { ntfs_error(sb, "Couldn't allocate MFT record"); goto put_err_out; diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 7e58c99f1728..61d4ee3a57fd 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -841,12 +841,126 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk."; -#define RESERVED_MFT_RECORDS 64 +#define FIRST_NORMAL_MFT_RECORD 24 +#define MFT_RECORD_RESERVE 4 /* - * ntfs_mft_bitmap_find_and_alloc_free_rec_nolock - see name + * Records 12-15 are marked in use by Windows but normally have no name + * and no links. Keep them as the last bootstrap option when a volume + * mounted without an in-memory tail reserve needs its first $MFT metadata + * extent. + */ +static bool mft_reserved_is_free(struct ntfs_volume *vol, + struct ntfs_inode *mft_ni, s64 mft_no) +{ + struct attr_record *a; + struct mft_record *m; + struct folio *folio; + void *mapped; + pgoff_t index = NTFS_MFT_NR_TO_PIDX(vol, mft_no); + unsigned int ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no); + u32 attrs_offset, bytes_in_use; + bool available = false, have_std = false; + int i; + + for (i = 0; i < mft_ni->nr_extents; i++) { + if (mft_ni->ext.extent_ntfs_inos[i] && + mft_ni->ext.extent_ntfs_inos[i]->mft_no == mft_no) + return false; + } + m = kmalloc(vol->mft_record_size, GFP_NOFS); + if (!m) + return false; + + folio = read_mapping_folio(vol->mft_ino->i_mapping, index, NULL); + if (IS_ERR(folio)) + goto free_m; + + folio_lock(folio); + mapped = kmap_local_folio(folio, 0); + memcpy(m, (u8 *)mapped + ofs, vol->mft_record_size); + kunmap_local(mapped); + folio_unlock(folio); + folio_put(folio); + if (post_read_mst_fixup((struct ntfs_record *)m, vol->mft_record_size)) + goto free_m; + + if (!ntfs_is_mft_record(m->magic) || + !(m->flags & MFT_RECORD_IN_USE) || m->base_mft_record || + m->link_count) + goto out; + + attrs_offset = le16_to_cpu(m->attrs_offset); + bytes_in_use = le32_to_cpu(m->bytes_in_use); + if (attrs_offset > bytes_in_use || bytes_in_use > vol->mft_record_size || + bytes_in_use - attrs_offset < sizeof(a->type)) + goto out; + + for (a = (struct attr_record *)((u8 *)m + attrs_offset); + (u8 *)a + sizeof(a->type) <= (u8 *)m + bytes_in_use;) { + u32 len; + + if (a->type == AT_END) { + if ((u8 *)a + sizeof(a->type) + sizeof(a->length) > + (u8 *)m + bytes_in_use) + break; + /* Also accept a record emptied by an earlier bootstrap. */ + available = have_std || + (u8 *)a == (u8 *)m + attrs_offset; + break; + } + if (a->type == AT_FILE_NAME) + break; + len = le32_to_cpu(a->length); + if (len < offsetof(struct attr_record, data) || + (u8 *)a + len > (u8 *)m + bytes_in_use) + break; + if (a->type == AT_STANDARD_INFORMATION) { + u32 value_len, value_ofs; + + if (have_std || a->non_resident || + len < offsetof(struct attr_record, + data.resident.reserved) + 1) + break; + value_len = le32_to_cpu(a->data.resident.value_length); + value_ofs = le16_to_cpu(a->data.resident.value_offset); + if (value_ofs > len || value_len > len - value_ofs) + break; + have_std = true; + } + a = (struct attr_record *)((u8 *)a + len); + } +out: + kfree(m); + return available; +free_m: + kfree(m); + return false; +} + +static s64 mft_reserve_end(const u8 *buf, s64 buf_start, s64 buf_end, + s64 start, s64 pass_end, s64 initialized_mft_records) +{ + s64 end = start + 1; + s64 limit = min_t(s64, start + MFT_RECORD_RESERVE, pass_end); + + if (limit > initialized_mft_records) + limit = initialized_mft_records; + if (limit > buf_end) + limit = buf_end; + while (end < limit && + !(buf[(end - buf_start) >> 3] & + (1 << ((end - buf_start) & 7)))) + end++; + return end; +} + +/* + * mft_bitmap_alloc_free_rec - find and allocate a free MFT record * @vol: volume on which to search for a free mft record * @base_ni: open base inode if allocating an extent mft record or NULL + * @max_mft_no: first record which must not be allocated, or -1 + * @new_reserve_end: if not NULL, end of a free run starting after the result * * Search for a free mft record in the mft bitmap attribute on the ntfs volume * @vol. @@ -862,10 +976,12 @@ static const char *es = " Leaving inconsistent metadata. Unmount and run chkds * * Locking: Caller must hold vol->mftbmp_lock for writing. */ -static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vol, - struct ntfs_inode *base_ni) +static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol, + struct ntfs_inode *base_ni, + s64 max_mft_no, s64 *new_reserve_end) { s64 pass_end, ll, data_pos, pass_start, ofs, bit; + s64 initialized_mft_records; unsigned long flags; struct address_space *mftbmp_mapping; u8 *buf = NULL, *byte; @@ -882,30 +998,36 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo read_lock_irqsave(&NTFS_I(vol->mft_ino)->size_lock, flags); pass_end = NTFS_I(vol->mft_ino)->allocated_size >> vol->mft_record_size_bits; + initialized_mft_records = NTFS_I(vol->mft_ino)->initialized_size >> + vol->mft_record_size_bits; read_unlock_irqrestore(&NTFS_I(vol->mft_ino)->size_lock, flags); read_lock_irqsave(&NTFS_I(vol->mftbmp_ino)->size_lock, flags); ll = NTFS_I(vol->mftbmp_ino)->initialized_size << 3; read_unlock_irqrestore(&NTFS_I(vol->mftbmp_ino)->size_lock, flags); if (pass_end > ll) pass_end = ll; - pass = 1; - if (!base_ni) - data_pos = vol->mft_data_pos; - else - data_pos = base_ni->mft_no + 1; - if (data_pos < RESERVED_MFT_RECORDS) - data_pos = RESERVED_MFT_RECORDS; - if (data_pos >= pass_end) { - data_pos = RESERVED_MFT_RECORDS; + if (max_mft_no >= 0 && pass_end > max_mft_no) + pass_end = max_mft_no; + if (base_ni && base_ni->mft_no == FILE_MFT) { + data_pos = FILE_first_user; pass = 2; - /* This happens on a freshly formatted volume. */ if (data_pos >= pass_end) return -ENOSPC; - } - - if (base_ni && base_ni->mft_no == FILE_MFT) { - data_pos = 0; - pass = 2; + } else { + pass = 1; + if (!base_ni) + data_pos = vol->mft_data_pos; + else + data_pos = base_ni->mft_no + 1; + if (data_pos < FIRST_NORMAL_MFT_RECORD) + data_pos = FIRST_NORMAL_MFT_RECORD; + if (data_pos >= pass_end) { + data_pos = FIRST_NORMAL_MFT_RECORD; + pass = 2; + /* This happens on a freshly formatted volume. */ + if (data_pos >= pass_end) + return -ENOSPC; + } } pass_start = data_pos; @@ -940,38 +1062,28 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo size, data_pos, bit); for (; bit < size && data_pos + bit < pass_end; bit &= ~7ull, bit += 8) { - /* - * If we're extending $MFT and running out of the first - * mft record (base record) then give up searching since - * no guarantee that the found record will be accessible. - */ - if (base_ni && base_ni->mft_no == FILE_MFT && bit > 400) { - folio_unlock(folio); - kunmap_local(buf); - folio_put(folio); - return -ENOSPC; - } - byte = buf + (bit >> 3); if (*byte == 0xff) continue; - b = ffz((unsigned long)*byte); - if (b < 8 && b >= (bit & 7)) { + b = bit & 7; + for (; b < 8; b++) { + if (*byte & (1 << b)) + continue; ll = data_pos + (bit & ~7ull) + b; + if (ll >= pass_end) + break; + /* Keep the dynamic tail reserve for $MFT metadata. */ + if ((!base_ni || base_ni->mft_no != FILE_MFT) && + ll >= vol->mft_record_reserve_pos && + ll < vol->mft_record_reserve_end) + continue; if (unlikely(ll >= (1ll << 32))) { folio_unlock(folio); kunmap_local(buf); folio_put(folio); return -ENOSPC; } - *byte |= 1 << b; - folio_mark_dirty(folio); - folio_unlock(folio); - kunmap_local(buf); - folio_put(folio); - ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", - ll); - return ll; + goto found; } } ntfs_debug("After inner for loop: size 0x%x, data_pos 0x%llx, bit 0x%llx", @@ -994,7 +1106,8 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo * part of the zone which we omitted earlier. */ pass_end = pass_start; - data_pos = pass_start = RESERVED_MFT_RECORDS; + data_pos = FIRST_NORMAL_MFT_RECORD; + pass_start = FIRST_NORMAL_MFT_RECORD; ntfs_debug("pass %i, pass_start 0x%llx, pass_end 0x%llx.", pass, pass_start, pass_end); if (data_pos >= pass_end) @@ -1004,6 +1117,18 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo /* No free mft records in currently initialized mft bitmap. */ ntfs_debug("Done. (No free mft records left in currently initialized mft bitmap.)"); return -ENOSPC; +found: + if (new_reserve_end) + *new_reserve_end = mft_reserve_end(buf, data_pos, + data_pos + size, ll, pass_end, + initialized_mft_records); + *byte |= 1 << b; + folio_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", ll); + return ll; } static int ntfs_mft_attr_extend(struct ntfs_inode *ni) @@ -1471,8 +1596,9 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol) * @vol: volume on which to extend the mft data attribute * * Extend the mft data attribute on the ntfs volume @vol by 16 mft records - * worth of clusters or if not enough space for this by one mft record worth - * of clusters. + * worth of clusters or if not enough space for this by two mft records worth + * of clusters. Keeping at least two new records breaks the recursion between + * extending $MFT and allocating a record for a new $MFT attribute extent. * * Note: Only changes allocated_size, i.e. does not touch initialized_size or * data_size. @@ -1526,10 +1652,8 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) } lcn = rl->lcn + rl->length; ntfs_debug("Last lcn of mft data attribute is 0x%llx.", lcn); - /* Minimum allocation is one mft record worth of clusters. */ - min_nr = NTFS_B_TO_CLU(vol, vol->mft_record_size); - if (!min_nr) - min_nr = 1; + /* Keep room for the allocating record and at least one MFT reserve. */ + min_nr = DIV_ROUND_UP_ULL((u64)vol->mft_record_size * 2, vol->cluster_size); /* Want to allocate 16 mft records worth of clusters. */ nr = vol->mft_record_size << 4 >> vol->cluster_size_bits; if (!nr) @@ -1928,6 +2052,7 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n * @ni: [OUT] on success, set to the allocated ntfs inode * @base_ni: [IN] open base inode if allocating an extent mft record or NULL * @ni_mrec: [OUT] on successful return this is the mapped mft record + * @mft_data_vcn: [IN] lowest VCN of a new $MFT/$DATA extent, or -1 * * Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol. * @@ -1955,30 +2080,23 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n * optimize this we start scanning at the place specified by @base_ni or if * @base_ni is NULL we start where we last stopped and we perform wrap around * when we reach the end. Note, we do not try to allocate mft records below - * number 64 because numbers 0 to 15 are the defined system files anyway and 16 - * to 64 are special in that they are used for storing extension mft records - * for the $DATA attribute of $MFT. This is required to avoid the possibility - * of creating a runlist with a circular dependency which once written to disk - * can never be read in again. Windows will only use records 16 to 24 for - * normal files if the volume is completely out of space. We never use them - * which means that when the volume is really out of space we cannot create any - * more files while Windows can still create up to 8 small files. We can start - * doing this at some later time, it does not matter much for now. + * number 24 because numbers 0 to 15 are the defined system files and records + * 16 to 23 are kept for metadata compatibility. Records reserved dynamically + * at the initialized MFT tail are skipped by normal allocation and consumed by + * $MFT metadata extent allocation. * * When scanning the mft bitmap, we only search up to the last allocated mft - * record. If there are no free records left in the range 64 to number of + * record. If there are no free records left in the range 24 to number of * allocated mft records, then we extend the $MFT/$DATA attribute in order to * create free mft records. We extend the allocated size of $MFT/$DATA by 16 * records at a time or one cluster, if cluster size is above 16kiB. If there - * is not sufficient space to do this, we try to extend by a single mft record - * or one cluster, if cluster size is above the mft record size. + * is not sufficient space to do this, we try to extend by two mft records or + * one cluster, if a cluster already contains at least two mft records. * - * No matter how many mft records we allocate, we initialize only the first - * allocated mft record, incrementing mft data size and initialized size - * accordingly, open an struct ntfs_inode for it and return it to the caller, unless - * there are less than 64 mft records, in which case we allocate and initialize - * mft records until we reach record 64 which we consider as the first free mft - * record for use by normal files. + * When extending the initialized MFT tail, we also initialize up to four + * additional records and reserve them in memory for future $MFT metadata + * extents. If there are less than 24 mft records, records are initialized + * until record 24, which is the first record used for normal files. * * If during any stage we overflow the initialized data in the mft bitmap, we * extend the initialized size (and data size) by 8 bytes, allocating another @@ -2014,9 +2132,12 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n */ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct ntfs_inode **ni, struct ntfs_inode *base_ni, - struct mft_record **ni_mrec) + struct mft_record **ni_mrec, const s64 mft_data_vcn) { s64 ll, bit, old_data_initialized, old_data_size; + s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1; + s64 candidate_reserve_end = -1; + s64 *reserve_endp; unsigned long flags; struct folio *folio; struct ntfs_inode *mft_ni, *mftbmp_ni; @@ -2027,7 +2148,9 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, unsigned int ofs; int err; __le16 seq_no, usn; - bool record_formatted = false; + bool record_formatted = false, from_reserve = false, tail_alloc = false; + bool reserve_created = false; + bool forced_reserved_record = false; unsigned int memalloc_flags; if (base_ni && *ni) @@ -2036,6 +2159,21 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, /* @mode and @base_ni are mutually exclusive. */ if (mode && base_ni) return -EINVAL; + if (mft_data_vcn >= 0 && + (!base_ni || base_ni->mft_no != FILE_MFT)) + return -EINVAL; + if (mft_data_vcn >= 0) { + u64 vbo; + + if ((u64)mft_data_vcn > (U64_MAX >> vol->cluster_size_bits)) + return -EOVERFLOW; + vbo = (u64)mft_data_vcn << vol->cluster_size_bits; + /* + * The whole extent record must be reachable without this + * extent, including when an MFT record spans multiple clusters. + */ + max_mft_no = vbo >> vol->mft_record_size_bits; + } if (base_ni) ntfs_debug("Entering (allocating an extent mft record for base mft record 0x%llx).", @@ -2050,10 +2188,39 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mutex_lock(&mft_ni->mrec_lock); mftbmp_ni = NTFS_I(vol->mftbmp_ino); search_free_rec: + from_reserve = false; + reserve_created = false; + candidate_reserve_end = -1; if (!base_ni || base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); - bit = ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(vol, base_ni); + if (base_ni && base_ni->mft_no == FILE_MFT && + vol->mft_record_reserve_pos < vol->mft_record_reserve_end && + (max_mft_no < 0 || vol->mft_record_reserve_pos < max_mft_no)) { + bit = vol->mft_record_reserve_pos; + err = ntfs_bitmap_set_bit(vol->mftbmp_ino, bit); + if (unlikely(err)) { + ntfs_error(vol->sb, + "Failed to allocate reserved MFT record 0x%llx.", + bit); + goto err_out; + } + vol->mft_record_reserve_pos++; + from_reserve = true; + ntfs_debug("Allocated MFT metadata record 0x%llx from tail reserve.", + bit); + goto have_alloc_rec; + } + reserve_endp = vol->mft_record_reserve_pos >= + vol->mft_record_reserve_end ? &candidate_reserve_end : NULL; + bit = mft_bitmap_alloc_free_rec(vol, base_ni, max_mft_no, reserve_endp); if (bit >= 0) { + if (candidate_reserve_end > bit + 1) { + vol->mft_record_reserve_pos = bit + 1; + vol->mft_record_reserve_end = candidate_reserve_end; + reserve_created = true; + ntfs_debug("Reserved free MFT records [0x%llx, 0x%llx) for metadata.", + bit + 1, candidate_reserve_end); + } ntfs_debug("Found and allocated free record (#1), bit 0x%llx.", (long long)bit); goto have_alloc_rec; @@ -2068,6 +2235,24 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, } if (base_ni && base_ni->mft_no == FILE_MFT) { + static const u8 bootstrap_records[] = { + FILE_reserved15, FILE_reserved12, FILE_reserved13, + FILE_reserved14, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(bootstrap_records); i++) { + if (max_mft_no >= 0 && bootstrap_records[i] >= max_mft_no) + continue; + if (!mft_reserved_is_free(vol, mft_ni, + bootstrap_records[i])) + continue; + bit = bootstrap_records[i]; + forced_reserved_record = true; + ntfs_debug("Using reserved MFT record %lld to bootstrap metadata extension.", + bit); + goto have_alloc_rec; + } memalloc_nofs_restore(memalloc_flags); return bit; } @@ -2087,10 +2272,10 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, old_data_initialized = mftbmp_ni->initialized_size; read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); if (old_data_initialized << 3 > ll && - old_data_initialized > RESERVED_MFT_RECORDS / 8) { + old_data_initialized << 3 > FIRST_NORMAL_MFT_RECORD) { bit = ll; - if (bit < RESERVED_MFT_RECORDS) - bit = RESERVED_MFT_RECORDS; + if (bit < FIRST_NORMAL_MFT_RECORD) + bit = FIRST_NORMAL_MFT_RECORD; if (unlikely(bit >= (1ll << 32))) goto max_err_out; ntfs_debug("Found free record (#2), bit 0x%llx.", @@ -2176,6 +2361,11 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, read_lock_irqsave(&mft_ni->size_lock, flags); old_data_initialized = mft_ni->initialized_size; read_unlock_irqrestore(&mft_ni->size_lock, flags); + tail_alloc = (!base_ni || base_ni->mft_no != FILE_MFT) && + bit >= (old_data_initialized >> vol->mft_record_size_bits) && + vol->mft_record_reserve_pos >= vol->mft_record_reserve_end; + if (tail_alloc) + ll = (bit + 2) << vol->mft_record_size_bits; if (ll <= old_data_initialized) { ntfs_debug("Allocated mft record already initialized."); goto mft_rec_already_initialized; @@ -2208,6 +2398,29 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mft_ni->initialized_size); } read_unlock_irqrestore(&mft_ni->size_lock, flags); + if (tail_alloc) { + s64 bitmap_records; + + read_lock_irqsave(&mft_ni->size_lock, flags); + reserve_end = mft_ni->allocated_size >> + vol->mft_record_size_bits; + read_unlock_irqrestore(&mft_ni->size_lock, flags); + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + bitmap_records = mftbmp_ni->initialized_size << 3; + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + if (reserve_end > bitmap_records) + reserve_end = bitmap_records; + if (reserve_end > bit + 1 + MFT_RECORD_RESERVE) + reserve_end = bit + 1 + MFT_RECORD_RESERVE; + reserve_start = bit + 1; + if (reserve_end > reserve_start) { + ll = reserve_end << vol->mft_record_size_bits; + } else { + reserve_start = -1; + reserve_end = -1; + ll = (bit + 1) << vol->mft_record_size_bits; + } + } } else if (ll > mft_ni->allocated_size) { err = -ENOSPC; goto undo_mftbmp_alloc_nolock; @@ -2276,6 +2489,12 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mark_mft_record_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); unmap_mft_record(mft_ni); + if (reserve_start >= 0 && reserve_end > reserve_start) { + vol->mft_record_reserve_pos = reserve_start; + vol->mft_record_reserve_end = reserve_end; + ntfs_debug("Reserved MFT records [0x%llx, 0x%llx) for metadata.", + reserve_start, reserve_end); + } read_lock_irqsave(&mft_ni->size_lock, flags); ntfs_debug("Status of mft data after mft record initialization: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", mft_ni->allocated_size, i_size_read(vol->mft_ino), @@ -2315,8 +2534,8 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, /* If we just formatted the mft record no need to do it again. */ if (!record_formatted) { /* Sanity check that the mft record is really not in use. */ - if (ntfs_is_file_record(m->magic) && - (m->flags & MFT_RECORD_IN_USE)) { + if (!forced_reserved_record && ntfs_is_file_record(m->magic) && + (m->flags & MFT_RECORD_IN_USE)) { ntfs_warning(vol->sb, "Mft record 0x%llx was marked free in mft bitmap but is marked used itself. Unmount and run chkdsk.", bit); @@ -2389,9 +2608,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, ntfs_error(vol->sb, "Failed to map allocated extent mft record 0x%llx.", bit); err = PTR_ERR(m_tmp); - /* Set the mft record itself not in use. */ - m->flags &= cpu_to_le16( - ~le16_to_cpu(MFT_RECORD_IN_USE)); + if (forced_reserved_record) { + m->base_mft_record = 0; + m->flags |= MFT_RECORD_IN_USE; + } else { + /* Set the mft record itself not in use. */ + m->flags &= cpu_to_le16(~le16_to_cpu(MFT_RECORD_IN_USE)); + } /* Make sure the mft record is written out to disk. */ ntfs_mft_mark_dirty(folio); folio_unlock(folio); @@ -2463,7 +2686,8 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, (*ni)->mft_no = bit; if (ni_mrec) *ni_mrec = (*ni)->mrec; - ntfs_dec_free_mft_records(vol, 1); + if (!forced_reserved_record) + ntfs_dec_free_mft_records(vol, 1); return 0; undo_data_init: write_lock_irqsave(&mft_ni->size_lock, flags); @@ -2475,10 +2699,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, if (!base_ni || base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); undo_mftbmp_alloc_nolock: - if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) { + if (!forced_reserved_record && ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) { ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es); NVolSetErrors(vol); } + if ((from_reserve || reserve_created) && + vol->mft_record_reserve_pos == bit + 1) + vol->mft_record_reserve_pos = bit; if (!base_ni || base_ni->mft_no != FILE_MFT) up_write(&vol->mftbmp_lock); err_out: @@ -2514,9 +2741,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) int err; u16 seq_no; __le16 old_seq_no; + __le64 old_base_mft_record; struct mft_record *ni_mrec; unsigned int memalloc_flags; struct ntfs_inode *base_ni; + bool keep_reserved; if (!vol || !ni) return -EINVAL; @@ -2529,9 +2758,23 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) /* Cache the mft reference for later. */ mft_no = ni->mft_no; + if (likely(ni->nr_extents >= 0)) + base_ni = ni; + else + base_ni = ni->ext.base_ntfs_ino; + keep_reserved = mft_no >= FILE_reserved12 && + mft_no <= FILE_reserved15 && + base_ni->mft_no == FILE_MFT; - /* Mark the mft record as not in use. */ - ni_mrec->flags &= ~MFT_RECORD_IN_USE; + old_base_mft_record = ni_mrec->base_mft_record; + if (keep_reserved) { + /* Restore the special, unnamed form used by reserved records. */ + ni_mrec->base_mft_record = 0; + ni_mrec->flags |= MFT_RECORD_IN_USE; + } else { + /* Mark the mft record as not in use. */ + ni_mrec->flags &= ~MFT_RECORD_IN_USE; + } /* Increment the sequence number, skipping zero, if it is not zero. */ old_seq_no = ni_mrec->sequence_number; @@ -2560,16 +2803,20 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) if (err) goto sync_rollback; - if (likely(ni->nr_extents >= 0)) - base_ni = ni; - else - base_ni = ni->ext.base_ntfs_ino; + if (keep_reserved) { + unmap_mft_record(ni); + return 0; + } /* Clear the bit in the $MFT/$BITMAP corresponding to this record. */ memalloc_flags = memalloc_nofs_save(); if (base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (!err && base_ni->mft_no == FILE_MFT && + mft_no + 1 == vol->mft_record_reserve_pos && + mft_no < vol->mft_record_reserve_end) + vol->mft_record_reserve_pos = mft_no; if (base_ni->mft_no != FILE_MFT) up_write(&vol->mftbmp_lock); memalloc_nofs_restore(memalloc_flags); @@ -2595,6 +2842,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) "Eeek! Rollback failed in %s. Leaving inconsistent metadata!\n", __func__); ni_mrec->flags |= MFT_RECORD_IN_USE; ni_mrec->sequence_number = old_seq_no; + ni_mrec->base_mft_record = old_base_mft_record; NInoSetDirty(ni); write_mft_record(ni, ni_mrec, 0); unmap_mft_record(ni); diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index 75a51a98d0f6..ed5c1d595c0d 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -78,7 +78,7 @@ static inline int write_mft_record(struct ntfs_inode *ni, struct mft_record *m, int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct ntfs_inode **ni, struct ntfs_inode *base_ni, - struct mft_record **ni_mrec); + struct mft_record **ni_mrec, const s64 mft_data_vcn); int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni); int ntfs_mft_records_write(const struct ntfs_volume *vol, const u64 mref, const s64 count, struct mft_record *b); diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 7091b2496fac..fdf52fac4329 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -480,7 +480,7 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d mark_inode_dirty(dir); err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL, - &ni_mrec); + &ni_mrec, -1); if (err) { iput(vi); return ERR_PTR(err); diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 65fd3908af26..2b60d14fc7ef 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -55,6 +55,10 @@ * @attrdef_size: Size of the attribute definition table in bytes. * @attrdef: Table of attribute definitions. Obtained from FILE_AttrDef. * @mft_data_pos: Mft record number at which to allocate the next mft record. + * @mft_record_reserve_pos: First record in the in-memory MFT metadata reserve + * (protected by mftbmp_lock). + * @mft_record_reserve_end: First record beyond the MFT metadata reserve + * (protected by mftbmp_lock). * @mft_zone_start: First cluster of the mft zone. * @mft_zone_end: First cluster beyond the mft zone. * @mft_zone_pos: Current position in the mft zone. @@ -119,6 +123,8 @@ struct ntfs_volume { s32 attrdef_size; struct attr_def *attrdef; s64 mft_data_pos; + s64 mft_record_reserve_pos; + s64 mft_record_reserve_end; s64 mft_zone_start; s64 mft_zone_end; s64 mft_zone_pos; From b1d732e62a5b3942546e4edaab8976258e779287 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 6 Sep 2026 11:12:47 +0900 Subject: [PATCH 0929/1317] ntfs: repack $MFT/$ATTRIBUTE LIST Repack the non-resident $MFT/$ATTRIBUTE_LIST into a contiguous run when its mapping pairs no longer fit in the base MFT record. Propagate allocation and writeback errors, and check synchronous replacement writes. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 47 ++++++++--- fs/ntfs/attrlist.c | 202 ++++++++++++++++++++++++++++++++++++++++++--- fs/ntfs/inode.c | 10 ++- 3 files changed, 236 insertions(+), 23 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 81b9e0971b3e..d1696ce6acd5 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -3844,13 +3844,13 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) */ if (ni->type == AT_ATTRIBUTE_LIST) { ntfs_attr_put_search_ctx(ctx); - if (ntfs_inode_free_space(base_ni, mp_size - - cur_max_mp_size)) { - ntfs_debug("Attribute list is too big. Defragment the volume\n"); - return -ENOSPC; - } - if (ntfs_attrlist_update(base_ni)) - return -EIO; + err = ntfs_inode_free_space(base_ni, mp_size - + cur_max_mp_size); + if (err) + return err; + err = ntfs_attrlist_update(base_ni); + if (err) + return err; goto retry; } @@ -4522,13 +4522,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_bytes_to_cluster(vol, ni->allocated_size), first_free_vcn - ntfs_bytes_to_cluster(vol, ni->allocated_size), - lcn_seek_from, DATA_ZONE, false, false, false); + lcn_seek_from, DATA_ZONE, false, + ni->type == AT_ATTRIBUTE_LIST, false); if (IS_ERR(rl)) { ntfs_debug("Cluster allocation failed (%lld)", (long long)first_free_vcn - ntfs_bytes_to_cluster(vol, ni->allocated_size)); return PTR_ERR(rl); } + /* + * A contiguous ATTRIBUTE_LIST allocation keeps its mapping + * pairs small enough to fit in the base MFT record. The + * allocator can return a short run when contiguity was + * requested, so discard it and retry normally if necessary. + */ + if (ni->type == AT_ATTRIBUTE_LIST && + (rl->vcn != ntfs_bytes_to_cluster(vol, + ni->allocated_size) || + rl->length != first_free_vcn - + ntfs_bytes_to_cluster(vol, ni->allocated_size) || + rl[1].length)) { + ntfs_cluster_free_from_rl(vol, rl); + kvfree(rl); + rl = ntfs_cluster_alloc(vol, + ntfs_bytes_to_cluster(vol, + ni->allocated_size), + first_free_vcn - + ntfs_bytes_to_cluster(vol, + ni->allocated_size), + lcn_seek_from, DATA_ZONE, false, + false, false); + if (IS_ERR(rl)) + return PTR_ERR(rl); + } } if (!NInoCompressed(ni)) { @@ -4921,7 +4947,8 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n", (unsigned long long)ni->mft_no, ni->type, newsize); - if (ni->data_size == newsize) { + if (ni->data_size == newsize && + (!prealloc_size || prealloc_size <= ni->allocated_size)) { ntfs_debug("Size is already ok\n"); return 0; } @@ -4936,7 +4963,7 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo } if (NInoNonResident(ni)) { - if (newsize > ni->data_size) + if (newsize > ni->data_size || prealloc_size > ni->allocated_size) err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size, NVolDisableSparse(ni->vol) ? HOLES_NO : HOLES_OK, true); diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index be3086d34338..4e60f7e93010 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -12,6 +12,9 @@ #include "mft.h" #include "attrib.h" #include "attrlist.h" +#include "lcnalloc.h" + +#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024) /* * ntfs_attrlist_need - check whether inode need attribute list @@ -51,11 +54,151 @@ int ntfs_attrlist_need(struct ntfs_inode *ni) return 0; } +/* + * Repack the $MFT/$ATTRIBUTE_LIST data into one run. + * + * The mapping pairs for an $ATTRIBUTE_LIST must remain in the base MFT + * record. Once that record has no room left, extending a fragmented list + * can require one more mapping-pairs byte than the record can hold. There + * is no attribute that can legally be moved out in that state: $STANDARD_ + * INFORMATION, $ATTRIBUTE_LIST, and the first $MFT/$DATA extent all have to + * stay in the base record. Move the list data to one contiguous run. The + * caller supplies the minimum allocation size so a recovery can use the + * smallest useful run while normal updates can still request the maximum + * legal list size as a reserve. + */ +static int ntfs_attrlist_repack(struct inode *attr_vi, + struct ntfs_inode *attr_ni, s64 min_alloc_size) +{ + struct ntfs_volume *vol = attr_ni->vol; + struct runlist_element *old_rl, *new_rl; + u8 *data = NULL; + s64 data_size, alloc_size, nr_clusters, written; + s64 old_alloc_size; + size_t old_rl_count, new_rl_count; + unsigned long flags; + int err, restore_err; + + if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) || + min_alloc_size < 0) + return -EINVAL; + + err = ntfs_attr_map_whole_runlist(attr_ni); + if (err) + return err; + + data_size = attr_ni->data_size; + if (data_size < 0) + return -EIO; + + if (data_size) { + data = kvmalloc(data_size, GFP_NOFS); + if (!data) + return -ENOMEM; + + written = ntfs_inode_attr_pread(attr_vi, 0, data_size, data); + if (written != data_size) { + err = written < 0 ? (int)written : -EIO; + goto out_free_data; + } + } + + old_alloc_size = attr_ni->allocated_size; + alloc_size = max_t(s64, old_alloc_size, min_alloc_size); + nr_clusters = ntfs_bytes_to_cluster(vol, + alloc_size + vol->cluster_size - 1); + if (nr_clusters <= 0) { + err = -EFBIG; + goto out_free_data; + } + + /* A single run keeps the mapping pairs at the minimum size. */ + new_rl = ntfs_cluster_alloc(vol, 0, nr_clusters, -1, DATA_ZONE, + true, true, false); + if (IS_ERR(new_rl)) { + err = PTR_ERR(new_rl); + goto out_free_data; + } + + new_rl_count = 0; + if (new_rl->vcn == 0 && new_rl->length == nr_clusters && + !new_rl[1].length) + new_rl_count = 2; + + if (new_rl_count != 2) { + ntfs_cluster_free_from_rl(vol, new_rl); + kvfree(new_rl); + err = -ENOSPC; + goto out_free_data; + } + + old_rl = attr_ni->runlist.rl; + old_rl_count = attr_ni->runlist.count; + down_write(&attr_ni->runlist.lock); + attr_ni->runlist.rl = new_rl; + attr_ni->runlist.count = new_rl_count; + up_write(&attr_ni->runlist.lock); + + write_lock_irqsave(&attr_ni->size_lock, flags); + attr_ni->allocated_size = ntfs_cluster_to_bytes(vol, nr_clusters); + write_unlock_irqrestore(&attr_ni->size_lock, flags); + + /* Populate the replacement extent before publishing its mapping pairs. */ + if (data_size) { + written = ntfs_inode_attr_pwrite(attr_vi, 0, data_size, data, true); + if (written != data_size) { + err = written < 0 ? (int)written : -EIO; + goto restore_old_runlist; + } + } + + err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + if (err) + goto restore_old_runlist; + + /* The new mapping is now authoritative; release the old data runs. */ + if (ntfs_cluster_free_from_rl(vol, old_rl)) { + ntfs_error(vol->sb, + "Failed to free old ATTRIBUTE_LIST extent: inode %#llx", + (long long)attr_ni->mft_no); + NVolSetErrors(vol); + } + kvfree(old_rl); + kvfree(data); + return 0; + +restore_old_runlist: + down_write(&attr_ni->runlist.lock); + attr_ni->runlist.rl = old_rl; + attr_ni->runlist.count = old_rl_count; + up_write(&attr_ni->runlist.lock); + + write_lock_irqsave(&attr_ni->size_lock, flags); + attr_ni->allocated_size = old_alloc_size; + write_unlock_irqrestore(&attr_ni->size_lock, flags); + + restore_err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + if (restore_err) { + ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)", + restore_err); + NVolSetErrors(vol); + } + + ntfs_cluster_free_from_rl(vol, new_rl); + kvfree(new_rl); + err = err ? err : restore_err; + +out_free_data: + kvfree(data); + return err; +} + int ntfs_attrlist_update(struct ntfs_inode *base_ni) { struct inode *attr_vi; struct ntfs_inode *attr_ni; - int err; + s64 written; + int err, retry_err; /* * generic_shutdown_super() clears SB_ACTIVE before evicting cached @@ -74,21 +217,55 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) attr_ni = NTFS_I(attr_vi); err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO); - if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) { - err = ntfs_attr_truncate(attr_ni, 0); - if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) { + if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT && + NInoNonResident(attr_ni)) { + retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, + base_ni->attr_list_size); + if (retry_err) { + ntfs_error(base_ni->vol->sb, "Failed to repack attribute list"); iput(attr_vi); + return retry_err; + } + + retry_err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, + HOLES_NO); + if (retry_err) { ntfs_error(base_ni->vol->sb, - "Failed to truncate attribute list of inode %#llx", - (long long)base_ni->mft_no); - return -EIO; + "Failed to resize attribute list after repack"); + iput(attr_vi); + return retry_err; } } else if (err) { iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to truncate attribute list of inode %#llx", (long long)base_ni->mft_no); - return -EIO; + return err; + } + + /* + * Reserve the maximum legal list size while the MFT metadata area is + * still easy to allocate contiguously. This prevents a later list entry + * from needing another mapping-pairs byte in the full base MFT record. + * Failure to obtain the optional reserve must not reject the current + * metadata update; the repack retry above remains available if needed. + */ + if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) && + attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) { + retry_err = ntfs_attr_expand(attr_ni, base_ni->attr_list_size, + NTFS_MAX_ATTR_LIST_SIZE); + if (retry_err == -ENOSPC) { + retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, + NTFS_MAX_ATTR_LIST_SIZE); + if (retry_err == -ENOSPC) + retry_err = 0; + } + if (retry_err) { + ntfs_error(base_ni->vol->sb, + "Failed to reserve attribute list space"); + iput(attr_vi); + return retry_err; + } } i_size_write(attr_vi, base_ni->attr_list_size); @@ -96,14 +273,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni)) NInoSetAttrListNonResident(base_ni); - if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size, - base_ni->attr_list, false) != - base_ni->attr_list_size) { + written = ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size, + base_ni->attr_list, false); + if (written != base_ni->attr_list_size) { + err = written < 0 ? (int)written : -EIO; iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to write attribute list of inode %#llx", (long long)base_ni->mft_no); - return -EIO; + return err; } NInoSetAttrListDirty(base_ni); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 5aedc045f65a..332825db477a 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -3745,6 +3745,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, u64 rl_length = 0; s64 vcn; struct runlist_element *rl; + int bio_err; lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len)); vcn = ntfs_pidx_to_cluster(vol, folio->index); @@ -3787,8 +3788,15 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, goto err_unlock_folio; } - submit_bio_wait(bio); + bio_err = submit_bio_wait(bio); bio_put(bio); + if (bio_err) { + ntfs_error(vi->i_sb, + "Synchronous attribute write failed (%d)", + bio_err); + ret = bio_err; + goto err_unlock_folio; + } vcn += rl_length; offset += length; } while (lcn_count != 0); From 631946431ddc66a472c5cc629cd654e62dfa1f88 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 4 Sep 2026 14:46:38 +0900 Subject: [PATCH 0930/1317] ntfs: account for MFT records added during allocation When no free MFT record is available in the initialized $MFT/$BITMAP, ntfs_mft_record_alloc() extends $MFT/$DATA and formats the requested record together with a dynamically sized tail reserve. Those records become visible through the $MFT file size before charging the requested record to the free-record counter. Account for all newly visible records before releasing the MFT allocation lock, then subtract the one record being allocated. Keep MFT counter updates independent of the asynchronous free-cluster scan and update the counter when a record is successfully cleared in the MFT bitmap. Store the clamped result of the MFT bitmap scan and keep statfs from exposing an invalid cached count if an accounting error occurs. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 11 ++++++++--- fs/ntfs/super.c | 12 +++++++++--- fs/ntfs/volume.h | 6 ------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 61d4ee3a57fd..f0656d0bbeeb 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -1539,7 +1539,6 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol) ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0); if (likely(!ret)) { ntfs_debug("Done. (Wrote eight initialized bytes to mft bitmap."); - ntfs_inc_free_mft_records(vol, 8 * 8); return 0; } ntfs_error(vol->sb, "Failed to write to mft bitmap."); @@ -2135,6 +2134,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct mft_record **ni_mrec, const s64 mft_data_vcn) { s64 ll, bit, old_data_initialized, old_data_size; + s64 nr_new_mft_records = 0; s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1; s64 candidate_reserve_end = -1; s64 *reserve_endp; @@ -2501,8 +2501,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mft_ni->initialized_size); WARN_ON(i_size_read(vol->mft_ino) > mft_ni->allocated_size); WARN_ON(mft_ni->initialized_size > i_size_read(vol->mft_ino)); + nr_new_mft_records = (i_size_read(vol->mft_ino) - old_data_size) >> + vol->mft_record_size_bits; read_unlock_irqrestore(&mft_ni->size_lock, flags); mft_rec_already_initialized: + /* Account for newly visible MFT records before dropping the lock. */ + if (nr_new_mft_records > 0) + ntfs_inc_free_mft_records(vol, nr_new_mft_records); /* * We can finally drop the mft bitmap lock as the mft data attribute * has been fully updated. The only disparity left is that the @@ -2813,6 +2818,8 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) if (base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (!err) + ntfs_inc_free_mft_records(vol, 1); if (!err && base_ni->mft_no == FILE_MFT && mft_no + 1 == vol->mft_record_reserve_pos && mft_no < vol->mft_record_reserve_end) @@ -2822,9 +2829,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) memalloc_nofs_restore(memalloc_flags); if (err) goto bitmap_rollback; - unmap_mft_record(ni); - ntfs_inc_free_mft_records(vol, 1); return 0; /* Rollback what we did... */ diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 60d43339c590..0867d6a82ebe 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -2064,8 +2064,7 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol, /* If errors occurred we may well have gone below zero, fix this. */ if (nr_free < 0) nr_free = 0; - else - atomic64_set(&vol->free_mft_records, nr_free); + atomic64_set(&vol->free_mft_records, nr_free); ntfs_debug("Exiting."); return nr_free; @@ -2131,7 +2130,14 @@ static int ntfs_statfs(struct dentry *dentry, struct kstatfs *sfs) read_unlock_irqrestore(&mft_ni->size_lock, flags); /* Free inodes in fs (based on current total count). */ - sfs->f_ffree = atomic64_read(&vol->free_mft_records); + size = atomic64_read(&vol->free_mft_records); + if (unlikely(size < 0 || size > (s64)sfs->f_files)) + ntfs_warning(vol->sb, "Invalid free MFT record count %lld.", size); + if (size < 0) + size = 0; + else if (size > (s64)sfs->f_files) + size = sfs->f_files; + sfs->f_ffree = size; /* * File system id. This is extremely *nix flavour dependent and even diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 2b60d14fc7ef..bc85a9592245 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -258,17 +258,11 @@ static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr) static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr) { - if (!NVolFreeClusterKnown(vol)) - return; - atomic64_add(nr, &vol->free_mft_records); } static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr) { - if (!NVolFreeClusterKnown(vol)) - return; - atomic64_sub(nr, &vol->free_mft_records); } From 91709ba5d6d709b2b663287b7e871e2c6b480502 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 6 Sep 2026 21:37:28 +0900 Subject: [PATCH 0931/1317] ntfs: protect runlist updates with the runlist lock ntfs_non_resident_attr_shrink() calls runlist helpers that require the runlist write lock, but did not hold it while freeing clusters and truncating the runlist. Serialize those operations and the resident conversion with the runlist lock. ntfs_attr_map_cluster() can merge a newly allocated run before updating mapping pairs. If the update fails, free the clusters and restore both the in-memory runlist and on-disk mapping pairs from a saved runlist. Mark the volume in error if either rollback step fails. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 205 +++++++++++++++++++++++++++++++++++---------- fs/ntfs/attrib.h | 9 ++ fs/ntfs/attrlist.c | 46 ++++++---- fs/ntfs/attrlist.h | 2 + fs/ntfs/compress.c | 2 +- fs/ntfs/file.c | 3 +- fs/ntfs/inode.c | 2 +- fs/ntfs/mft.c | 13 +-- 8 files changed, 216 insertions(+), 66 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index d1696ce6acd5..c949ff765075 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -3574,7 +3574,8 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) * update allocated and compressed size. */ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, - struct mft_record *m, struct ntfs_attr_search_ctx *ctx) + struct mft_record *m, struct ntfs_attr_search_ctx *ctx, + struct ntfs_inode *locked_ni, bool defer_attrlist) { int sparse, err = 0; struct ntfs_inode *base_ni; @@ -3610,6 +3611,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, le16_to_cpu(a->data.non_resident.mapping_pairs_offset) == 8) && !(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) { + if (defer_attrlist) + return -ENOSPC; if (!NInoAttrList(base_ni)) { err = ntfs_inode_add_attrlist(base_ni); if (err) @@ -3623,7 +3626,7 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, goto out; } - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked(base_ni, locked_ni); if (err) goto out; err = -EAGAIN; @@ -3703,6 +3706,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, * ntfs_attr_update_mapping_pairs - update mapping pairs for ntfs attribute * @ni: non-resident ntfs inode for which we need update * @from_vcn: update runlist starting this VCN + * @locked_ni: inode whose runlist write lock is already held + * @defer_attrlist: return -ENOSPC instead of updating an attribute list * * Build mapping pairs from @na->rl and write them to the disk. Also, this * function updates sparse bit, allocated and compressed size (allocates/frees @@ -3712,7 +3717,10 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, * call to this function. Vice-versa @na->compressed_size will be calculated and * set to correct value during this function. */ -int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) +static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni, + bool defer_attrlist) { struct ntfs_attr_search_ctx *ctx; struct ntfs_inode *base_ni; @@ -3804,7 +3812,8 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) continue; } - err = ntfs_attr_update_meta(a, ni, m, ctx); + err = ntfs_attr_update_meta(a, ni, m, ctx, locked_ni, + defer_attrlist); if (err < 0) { if (err == -EAGAIN) { ntfs_attr_put_search_ctx(ctx); @@ -3844,11 +3853,17 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) */ if (ni->type == AT_ATTRIBUTE_LIST) { ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + if (locked_ni == ni || defer_attrlist) { + err = -ENOSPC; + goto put_err_out; + } err = ntfs_inode_free_space(base_ni, mp_size - cur_max_mp_size); if (err) return err; - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked( + base_ni, locked_ni); if (err) return err; goto retry; @@ -3856,6 +3871,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) /* Add attribute list if it isn't present, and retry. */ if (!NInoAttrList(base_ni)) { + if (defer_attrlist) { + err = -ENOSPC; + goto put_err_out; + } ntfs_attr_put_search_ctx(ctx); if (ntfs_inode_add_attrlist(base_ni)) { ntfs_error(sb, "Can not add attrlist"); @@ -3883,13 +3902,21 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) } } + if (defer_attrlist && + (ctx->ntfs_ino->nr_extents == -1 || + NInoAttrList(ctx->ntfs_ino)) && + ctx->attr->type != AT_ATTRIBUTE_LIST) { + err = -ENOSPC; + goto put_err_out; + } + /* Update lowest vcn. */ a->data.non_resident.lowest_vcn = cpu_to_le64(stop_vcn); mark_mft_record_dirty(ctx->ntfs_ino); if ((ctx->ntfs_ino->nr_extents == -1 || NInoAttrList(ctx->ntfs_ino)) && ctx->attr->type != AT_ATTRIBUTE_LIST) { ctx->al_entry->lowest_vcn = cpu_to_le64(stop_vcn); - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked(base_ni, locked_ni); if (err) goto put_err_out; } @@ -4064,6 +4091,19 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) return err; } +int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni) +{ + return __ntfs_attr_update_mapping_pairs(ni, from_vcn, locked_ni, + false); +} + +int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) +{ + return ntfs_attr_update_mapping_pairs_locked(ni, from_vcn, NULL); +} + /* * ntfs_attr_make_resident - convert a non-resident to a resident attribute * @ni: open ntfs attribute to make resident @@ -4197,7 +4237,9 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc * * Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes. */ -static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize) +static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, + const s64 newsize, + struct ntfs_inode *locked_ni) { struct ntfs_volume *vol; struct ntfs_attr_search_ctx *ctx; @@ -4205,6 +4247,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz s64 nr_freed_clusters; int err; struct ntfs_inode *base_ni; + bool runlist_locked = locked_ni == ni; ntfs_debug("Inode 0x%llx attr 0x%x new size %lld\n", (unsigned long long)ni->mft_no, ni->type, (long long)newsize); @@ -4250,18 +4293,24 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz * clusters if there is a change. */ if (ntfs_bytes_to_cluster(vol, ni->allocated_size) != first_free_vcn) { - struct ntfs_attr_search_ctx *ctx; + /* + * ntfs_cluster_free() and ntfs_rl_truncate_nolock() + * both require this lock. + */ + if (!runlist_locked) + down_write(&ni->runlist.lock); err = ntfs_attr_map_whole_runlist(ni); if (err) { ntfs_debug("Eeek! ntfs_attr_map_whole_runlist failed.\n"); - return err; + goto unlock_runlist; } ctx = ntfs_attr_get_search_ctx(ni, NULL); if (!ctx) { ntfs_error(vol->sb, "%s: Failed to get search context", __func__); - return -ENOMEM; + err = -ENOMEM; + goto unlock_runlist; } /* Deallocate all clusters starting with the first free one. */ @@ -4269,7 +4318,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz if (nr_freed_clusters < 0) { ntfs_debug("Eeek! Freeing of clusters failed. Aborting...\n"); ntfs_attr_put_search_ctx(ctx); - return (int)nr_freed_clusters; + err = (int)nr_freed_clusters; + goto unlock_runlist; } ntfs_attr_put_search_ctx(ctx); @@ -4282,7 +4332,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz kvfree(ni->runlist.rl); ni->runlist.rl = NULL; ntfs_error(vol->sb, "Eeek! Run list truncation failed.\n"); - return -EIO; + err = -EIO; + goto unlock_runlist; } /* Prepare to mapping pairs update. */ @@ -4298,11 +4349,13 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9; /* Write mapping pairs for new runlist. */ - err = ntfs_attr_update_mapping_pairs(ni, 0 /*first_free_vcn*/); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) { ntfs_debug("Eeek! Mapping pairs update failed. Leaving inconstant metadata. Run chkdsk.\n"); - return err; + goto unlock_runlist; } + if (!runlist_locked) + up_write(&ni->runlist.lock); } /* Get the first attribute record. */ @@ -4344,7 +4397,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz /* If the attribute now has zero size, make it resident. */ if (!newsize && !NInoEncrypted(ni) && !NInoCompressed(ni)) { + if (!runlist_locked) + down_write(&ni->runlist.lock); err = ntfs_attr_make_resident(ni, ctx); + if (!runlist_locked) + up_write(&ni->runlist.lock); if (err) { /* If couldn't make resident, just continue. */ if (err != -EPERM) @@ -4361,6 +4418,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz put_err_out: ntfs_attr_put_search_ctx(ctx); return err; + +unlock_runlist: + if (!runlist_locked) + up_write(&ni->runlist.lock); + return err; } /* @@ -4369,13 +4431,14 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz * @prealloc_size: preallocation size (in bytes) to which to expand the attribute * @newsize: new size (in bytes) to which to expand the attribute * @holes: how to create a hole if expanding - * @need_lock: whether mrec lock is needed or not + * @locked_ni: inode whose runlist lock is already held * * Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes, * by allocating new clusters. */ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsize, - const s64 prealloc_size, unsigned int holes, bool need_lock) + const s64 prealloc_size, unsigned int holes, + struct ntfs_inode *locked_ni) { s64 lcn_seek_from; s64 first_free_vcn; @@ -4573,7 +4636,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz /* Prepare to mapping pairs update. */ ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked( + ni, 0, locked_ni); if (err) { ntfs_debug("Mapping pairs update failed"); goto rollback; @@ -4617,11 +4681,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_debug("Leaking clusters"); /* Now, truncate the runlist itself. */ - if (need_lock) + if (ni != locked_ni) down_write(&ni->runlist.lock); err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist, ntfs_bytes_to_cluster(vol, org_alloc_size)); - if (need_lock) + if (ni != locked_ni) up_write(&ni->runlist.lock); if (err2) { /* @@ -4635,11 +4699,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz /* Prepare to mapping pairs update. */ ni->allocated_size = org_alloc_size; /* Restore mapping pairs. */ - if (need_lock) + if (ni != locked_ni) down_read(&ni->runlist.lock); - if (ntfs_attr_update_mapping_pairs(ni, 0)) + if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true)) ntfs_error(sb, "Failed to restore old mapping pairs"); - if (need_lock) + if (ni != locked_ni) up_read(&ni->runlist.lock); if (NInoSparse(ni) || NInoCompressed(ni)) { @@ -4744,7 +4808,8 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi mark_mft_record_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); /* Resize non-resident attribute */ - return ntfs_non_resident_attr_expand(attr_ni, newsize, prealloc_size, holes, true); + return ntfs_non_resident_attr_expand( + attr_ni, newsize, prealloc_size, holes, NULL); } else if (err != -ENOSPC && err != -EPERM) { ntfs_error(sb, "Failed to make attribute non-resident"); goto put_err_out; @@ -4919,13 +4984,14 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, if (NInoNonResident(ni)) { if (newsize > i_size) { down_write(&ni->runlist.lock); - err = ntfs_non_resident_attr_expand(ni, newsize, 0, - NVolDisableSparse(ni->vol) ? - HOLES_NO : HOLES_OK, - false); + err = ntfs_non_resident_attr_expand( + ni, newsize, 0, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK, ni); up_write(&ni->runlist.lock); } else - err = ntfs_non_resident_attr_shrink(ni, newsize); + err = ntfs_non_resident_attr_shrink( + ni, newsize, NULL); } else err = ntfs_resident_attr_resize(ni, newsize, 0, NVolDisableSparse(ni->vol) ? @@ -4934,7 +5000,9 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, return err; } -int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size) +int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size, + struct ntfs_inode *locked_ni) { int err = 0; @@ -4964,9 +5032,10 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo if (NInoNonResident(ni)) { if (newsize > ni->data_size || prealloc_size > ni->allocated_size) - err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size, - NVolDisableSparse(ni->vol) ? - HOLES_NO : HOLES_OK, true); + err = ntfs_non_resident_attr_expand( + ni, newsize, prealloc_size, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK, locked_ni); } else err = ntfs_resident_attr_resize(ni, newsize, prealloc_size, NVolDisableSparse(ni->vol) ? @@ -4977,6 +5046,12 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo return err; } +int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size) +{ + return ntfs_attr_expand_locked(ni, newsize, prealloc_size, NULL); +} + /* * ntfs_attr_truncate_i - resize an ntfs attribute * @ni: open ntfs inode to resize @@ -4989,7 +5064,9 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo * newly allocated space is marked as not initialised and no real allocation * on disk is performed. */ -int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes) +int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes, + struct ntfs_inode *locked_ni) { int err; @@ -5023,15 +5100,23 @@ int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int if (NInoNonResident(ni)) { if (newsize > ni->data_size) - err = ntfs_non_resident_attr_expand(ni, newsize, 0, holes, true); + err = ntfs_non_resident_attr_expand( + ni, newsize, 0, holes, locked_ni); else - err = ntfs_non_resident_attr_shrink(ni, newsize); + err = ntfs_non_resident_attr_shrink( + ni, newsize, locked_ni); } else err = ntfs_resident_attr_resize(ni, newsize, 0, holes); ntfs_debug("Return status %d\n", err); return err; } +int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes) +{ + return ntfs_attr_truncate_i_locked(ni, newsize, holes, NULL); +} + /* * Resize an attribute, creating a hole if relevant */ @@ -5049,10 +5134,11 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, struct ntfs_volume *vol = ni->vol; struct ntfs_attr_search_ctx *ctx; struct runlist_element *rl, *rlc; + struct runlist_element *old_rl = NULL; s64 vcn = vcn_start, lcn, clu_count; s64 lcn_seek_from = -1; int err = 0; - size_t new_rl_count; + size_t new_rl_count, old_rl_count; err = ntfs_attr_map_whole_runlist(ni); if (err) @@ -5145,6 +5231,19 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, WARN_ON(rlc->vcn != vcn); lcn = rlc->lcn; clu_count = rlc->length; + old_rl_count = ni->runlist.count; + old_rl = kmemdup(ni->runlist.rl, + old_rl_count * sizeof(*old_rl), GFP_NOFS); + if (!old_rl) { + err = -ENOMEM; + if (ntfs_cluster_free_from_rl(vol, rlc)) { + ntfs_error(vol->sb, + "Failed to free cluster allocation after runlist backup failure."); + NVolSetErrors(vol); + } + kvfree(rlc); + goto out; + } rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count); if (IS_ERR(rl)) { @@ -5168,15 +5267,32 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, if (update_mp) { ntfs_attr_reinit_search_ctx(ctx); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) { int err2; err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx); - if (err2 < 0) + if (err2 < 0 || err2 != clu_count) { ntfs_error(vol->sb, - "Failed to free cluster allocation. Leaving inconstant metadata.\n"); - goto out; + "Failed to free cluster allocation. Leaving inconsistent metadata.\n"); + NVolSetErrors(vol); + goto out; + } + + /* + * Restore the runlist before repairing the on-disk + * mapping pairs. + */ + kvfree(ni->runlist.rl); + ni->runlist.rl = old_rl; + ni->runlist.count = old_rl_count; + old_rl = NULL; + if (ntfs_attr_update_mapping_pairs_locked( + ni, 0, ni)) { + ntfs_error(vol->sb, + "Failed to restore mapping pairs after allocation rollback.\n"); + NVolSetErrors(vol); + } } } else { VFS_I(ni)->i_blocks += clu_count << (vol->cluster_size_bits - 9); @@ -5188,6 +5304,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, *lcn_count = clu_count; *balloc = true; out: + kvfree(old_rl); ntfs_attr_put_search_ctx(ctx); return err; } @@ -5431,7 +5548,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6 ni->data_size += ntfs_cluster_to_bytes(vol, len); if (ntfs_cluster_to_bytes(vol, start_vcn) < ni->initialized_size) ni->initialized_size += ntfs_cluster_to_bytes(vol, len); - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (ret) return ret; @@ -5516,7 +5633,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, } if (ni->allocated_size > 0) { - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (ret) { up_write(&ni->runlist.lock); goto out_rl; @@ -5594,7 +5711,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 ni->runlist.rl = rl; ni->runlist.count = new_rl_count; - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (ret) { kvfree(punch_rl); @@ -5770,7 +5887,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo if (NInoRunlistDirty(ni)) { mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); down_write(&ni->runlist.lock); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) ntfs_error(ni->vol->sb, "Updating mapping pairs failed"); else diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h index e2224fbfaabe..6b4fa9f57640 100644 --- a/fs/ntfs/attrib.h +++ b/fs/ntfs/attrib.h @@ -112,7 +112,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, const s64 i_size); int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size); +int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size, + struct ntfs_inode *locked_ni); int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes); +int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes, + struct ntfs_inode *locked_ni); int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize); int ntfs_attr_rm(struct ntfs_inode *ni); int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name, @@ -133,6 +139,9 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, __le16 *name, u8 name_len, u8 *val, u32 size, __le16 flags); int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn); +int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni); struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn); /* diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index 4e60f7e93010..bb191953dcb1 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -68,7 +68,8 @@ int ntfs_attrlist_need(struct ntfs_inode *ni) * legal list size as a reserve. */ static int ntfs_attrlist_repack(struct inode *attr_vi, - struct ntfs_inode *attr_ni, s64 min_alloc_size) + struct ntfs_inode *attr_ni, s64 min_alloc_size, + struct ntfs_inode *locked_ni) { struct ntfs_volume *vol = attr_ni->vol; struct runlist_element *old_rl, *new_rl; @@ -78,10 +79,12 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, size_t old_rl_count, new_rl_count; unsigned long flags; int err, restore_err; - if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) || min_alloc_size < 0) return -EINVAL; + /* The buffered I/O below can reacquire the attribute runlist lock. */ + if (attr_ni == locked_ni) + return -ENOSPC; err = ntfs_attr_map_whole_runlist(attr_ni); if (err) @@ -131,7 +134,6 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, err = -ENOSPC; goto out_free_data; } - old_rl = attr_ni->runlist.rl; old_rl_count = attr_ni->runlist.count; down_write(&attr_ni->runlist.lock); @@ -152,7 +154,7 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, } } - err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(attr_ni, 0, locked_ni); if (err) goto restore_old_runlist; @@ -177,7 +179,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, attr_ni->allocated_size = old_alloc_size; write_unlock_irqrestore(&attr_ni->size_lock, flags); - restore_err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + restore_err = ntfs_attr_update_mapping_pairs_locked( + attr_ni, 0, locked_ni); if (restore_err) { ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)", restore_err); @@ -193,7 +196,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, return err; } -int ntfs_attrlist_update(struct ntfs_inode *base_ni) +int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni, + struct ntfs_inode *locked_ni) { struct inode *attr_vi; struct ntfs_inode *attr_ni; @@ -215,20 +219,27 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) return err; } attr_ni = NTFS_I(attr_vi); + /* Truncation and page-cache writes can reacquire this runlist lock. */ + if (attr_ni == locked_ni) { + iput(attr_vi); + return -ENOSPC; + } - err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO); + err = ntfs_attr_truncate_i_locked( + attr_ni, base_ni->attr_list_size, HOLES_NO, locked_ni); if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni)) { retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, - base_ni->attr_list_size); + base_ni->attr_list_size, locked_ni); if (retry_err) { ntfs_error(base_ni->vol->sb, "Failed to repack attribute list"); iput(attr_vi); return retry_err; } - retry_err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, - HOLES_NO); + retry_err = ntfs_attr_truncate_i_locked( + attr_ni, base_ni->attr_list_size, + HOLES_NO, locked_ni); if (retry_err) { ntfs_error(base_ni->vol->sb, "Failed to resize attribute list after repack"); @@ -252,11 +263,13 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) */ if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) && attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) { - retry_err = ntfs_attr_expand(attr_ni, base_ni->attr_list_size, - NTFS_MAX_ATTR_LIST_SIZE); + retry_err = ntfs_attr_expand_locked( + attr_ni, base_ni->attr_list_size, + NTFS_MAX_ATTR_LIST_SIZE, locked_ni); if (retry_err == -ENOSPC) { - retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, - NTFS_MAX_ATTR_LIST_SIZE); + retry_err = ntfs_attrlist_repack( + attr_vi, attr_ni, + NTFS_MAX_ATTR_LIST_SIZE, locked_ni); if (retry_err == -ENOSPC) retry_err = 0; } @@ -289,6 +302,11 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) return 0; } +int ntfs_attrlist_update(struct ntfs_inode *base_ni) +{ + return ntfs_attrlist_update_locked(base_ni, NULL); +} + /* * ntfs_attrlist_entry_add - add an attribute list attribute entry * @ni: opened ntfs inode, which contains that attribute diff --git a/fs/ntfs/attrlist.h b/fs/ntfs/attrlist.h index 1892a3934d3a..10cc2cc8e208 100644 --- a/fs/ntfs/attrlist.h +++ b/fs/ntfs/attrlist.h @@ -16,5 +16,7 @@ int ntfs_attrlist_need(struct ntfs_inode *ni); int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr); int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx); int ntfs_attrlist_update(struct ntfs_inode *base_ni); +int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni, + struct ntfs_inode *locked_ni); #endif /* defined _NTFS_ATTRLIST_H */ diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 197d8607fc63..6927d115d6af 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -1450,7 +1450,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, ni->runlist.rl = rl; rlc = NULL; - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (err) err = -EIO; diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 8164326b7812..007d1614b9ac 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -111,7 +111,8 @@ static int ntfs_trim_prealloc(struct inode *vi) ntfs_error(vol->sb, "Preallocated block rollback failed"); } else { ni->allocated_size = ntfs_cluster_to_bytes(vol, vcn_tr); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked( + ni, 0, ni); if (err) ntfs_error(vol->sb, "Failed to rollback mapping pairs for prealloc"); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 332825db477a..5d9d48135ecd 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2772,7 +2772,7 @@ int __ntfs_write_inode(struct inode *vi, int sync) if (NInoNonResident(ni) && NInoRunlistDirty(ni)) { down_write(&ni->runlist.lock); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (!err) NInoClearRunlistDirty(ni); up_write(&ni->runlist.lock); diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index f0656d0bbeeb..5b9723abb10f 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -1131,7 +1131,8 @@ static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol, return ll; } -static int ntfs_mft_attr_extend(struct ntfs_inode *ni) +static int ntfs_mft_attr_extend(struct ntfs_inode *ni, + struct ntfs_inode *locked_ni) { int ret = 0; struct ntfs_inode *base_ni; @@ -1152,7 +1153,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni) } } - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, locked_ni); if (ret) pr_err("MP update failed\n"); @@ -1340,7 +1341,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); if (unlikely(ret)) { - ret = ntfs_mft_attr_extend(mftbmp_ni); + ret = ntfs_mft_attr_extend(mftbmp_ni, mftbmp_ni); if (!ret) goto extended_ok; if (ret != -EAGAIN) @@ -1451,7 +1452,9 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) NVolSetErrors(vol); } mark_mft_record_dirty(ctx->ntfs_ino); - } else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) { + } else if (status.mp_extended && + ntfs_attr_update_mapping_pairs_locked(mftbmp_ni, 0, + mftbmp_ni)) { ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es); NVolSetErrors(vol); } @@ -1776,7 +1779,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); if (unlikely(ret)) { - ret = ntfs_mft_attr_extend(mft_ni); + ret = ntfs_mft_attr_extend(mft_ni, NULL); if (!ret) goto extended_ok; if (ret != -EAGAIN) From 1923eeffa63edeff427d76fc302bc5eb835771ce Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:01 +0900 Subject: [PATCH 0932/1317] ntfs: propagate folio errors Return the error from __filemap_get_folio() instead of replacing it with -ENOMEM. Fixes: af0db57d4293 ("ntfs: update inode operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 5d9d48135ecd..0a0b7c5547f7 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -3713,7 +3713,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, FGP_CREAT | FGP_LOCK, mapping_gfp_mask(mapping)); if (IS_ERR(folio)) { - ret = -ENOMEM; + ret = PTR_ERR(folio); break; } } else { From 8c5dc7587fdd45f957af81a9adc1e16863f300fc Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:25 +0900 Subject: [PATCH 0933/1317] ntfs: ignore interrupted inode reads as corruption Do not mark the volume in error or report an inode as corrupt when reading it was interrupted by a signal. -EINTR and -ERESTARTSYS indicate a transient read failure rather than on-disk NTFS corruption. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 21 +++++++++++++-------- fs/ntfs/mft.c | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 0a0b7c5547f7..210adf589319 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -1241,7 +1241,8 @@ static int ntfs_read_locked_inode(struct inode *vi) if (m) unmap_mft_record(ni); err_out: - if (err != -EOPNOTSUPP && err != -ENOMEM && vol_err == true) { + if (err != -EOPNOTSUPP && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS && vol_err == true) { ntfs_error(vol->sb, "Failed with error code %i. Marking corrupt inode 0x%llx as bad. Run chkdsk.", err, ni->mft_no); @@ -1467,12 +1468,13 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) ntfs_attr_put_search_ctx(ctx); unmap_mft_record(base_ni); err_out: - if (err != -ENOENT) + if (err != -ENOENT && err != -EINTR && err != -ERESTARTSYS) ntfs_error(vol->sb, "Failed with error code %i while reading attribute inode (mft_no 0x%llx, type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%llx as bad. Run chkdsk.", err, ni->mft_no, ni->type, ni->name_len, base_ni->mft_no); - if (err != -ENOENT && err != -ENOMEM) + if (err != -ENOENT && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS) NVolSetErrors(vol); return err; } @@ -1676,8 +1678,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) /* Get the index bitmap attribute inode. */ bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len); if (IS_ERR(bvi)) { - ntfs_error(vi->i_sb, "Failed to get bitmap attribute."); err = PTR_ERR(bvi); + if (err != -EINTR && err != -ERESTARTSYS) + ntfs_error(vi->i_sb, "Failed to get bitmap attribute."); goto unm_err_out; } bni = NTFS_I(bvi); @@ -1721,10 +1724,12 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) if (m) unmap_mft_record(base_ni); err_out: - ntfs_error(vi->i_sb, - "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.", - err, ni->mft_no, ni->name_len); - if (err != -EOPNOTSUPP && err != -ENOMEM) + if (err != -EINTR && err != -ERESTARTSYS) + ntfs_error(vi->i_sb, + "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.", + err, ni->mft_no, ni->name_len); + if (err != -EOPNOTSUPP && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS) NVolSetErrors(vol); return err; } diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 5b9723abb10f..6d5e56378afd 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -213,7 +213,8 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni) return m; atomic_dec(&ni->count); - ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); + if (PTR_ERR(m) != -EINTR && PTR_ERR(m) != -ERESTARTSYS) + ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); return m; } From fc440366c47000b768d013e347f60e81d60328ca Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:44 +0900 Subject: [PATCH 0934/1317] ntfs: discard inodes that fail initialization Discard a newly allocated normal, attribute, or index inode when initialization fails. Keeping an incompletely initialized inode in the inode cache can expose stale sequence data to later directory lookups. Fixes: d7aa04984f12 ("ntfs: return errors from inode initialization") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 210adf589319..b1f9a72ea963 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -170,17 +170,18 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no) /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_inode(vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad inodes around. This also * simplifies things in that we never need to check for bad inodes * elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } @@ -231,17 +232,18 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_attr_inode(base_vi, vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad attribute inodes around. This also * simplifies things in that we never need to check for bad attribute * inodes elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } @@ -286,17 +288,18 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_index_inode(base_vi, vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad index inodes around. This also * simplifies things in that we never need to check for bad index * inodes elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } From 0c32a42fd96a0e7c06c8a648a6dd8f1ec0bf643b Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:38:17 +0900 Subject: [PATCH 0935/1317] ntfs: unhash failed inode reads Remove a newly allocated inode from the inode hash before discarding it. NTFS may set i_nlink before a later initialization step fails, in which case iput alone can retain the incomplete inode in the cache. Fixes: bf6be898fbc5 ("ntfs: discard inodes that fail initialization") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index b1f9a72ea963..a777de8a80c7 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -170,9 +170,10 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no) /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_inode(vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* @@ -232,9 +233,10 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_attr_inode(base_vi, vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* @@ -288,9 +290,10 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_index_inode(base_vi, vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* From 229e8188307b9724cc676a49e9600c4acd24b571 Mon Sep 17 00:00:00 2001 From: Zhu Tianhao Date: Tue, 8 Sep 2026 05:41:00 +0900 Subject: [PATCH 0936/1317] ntfs: fix $MFTMirr write offset when it spans multiple folios When commit 115380f9a2f9 ("ntfs: update mft operations") refactored the MFT code to use folios, it assumed $MFTMirr is allocated contiguously, which is indeed what mkfs arranges. However, the folio rewrite kept a leftover from the old buffer-head/runlist based implementation: vol->cluster_size_mask is still applied to the mirror write offset. In fact it is no longer needed -- and in some configurations it is actively wrong. The bug triggers whenever the four mirror records do not fit in a single folio, i.e. when mft_record_size exceeds PAGE_SIZE / 4 (for example mft_record_size > 1KiB on 4KiB pages). For example, on the built-in 4Kn SSD (Apple SSD AP0256J) of a MacBookPro14,1 with 4096-byte MFT records, mirror record 3 is still written to the location of record 0. $MFTMirr therefore gets out of sync with $MFT and the following warning is printed on remount: ntfs: (device nvme0n1p3): check_mft_mirror(): $MFT and $MFTMirr record 0 do not match. Run chkdsk. Fix it by always adding the folio offset (folio->index << PAGE_SHIFT) to the base LCN address instead of applying the cluster_size_mask truncation. This is the standard file-offset calculation and is correct for every combination of cluster size, page size and MFT record size, provided $MFTMirr data is contiguous from mftmirr_lcn (which mkfs always arranges). Tested on the volume above: before the change mirror records 1-3 are all written to record 0's sector; after the change the write-back probe reports: ntfs: NTFSDBG pre mft_no=0x3 folio_idx=0x3 ofs=0x0 mftmirr_lcn=0x2540c5 clu_bits=12 rec_bits=12 PAGE_SHIFT=12 sect=0x12a0640 ntfs: NTFSDBG pre mft_no=0x0 folio_idx=0x0 ofs=0x0 mftmirr_lcn=0x2540c5 clu_bits=12 rec_bits=12 PAGE_SHIFT=12 sect=0x12a0628 which matches the expected sectors computed as: (NTFS_CLU_TO_B(mftmirr_lcn) + (folio->index << PAGE_SHIFT)) >> SECTOR_SHIFT record 0: (0x2540c5 << 12) + 0x0000 = 0x2540c5000 >> 9 = 0x12a0628 record 3: (0x2540c5 << 12) + 0x3000 = 0x2540c8000 >> 9 = 0x12a0640 A 13 MB file write succeeds on this volume and the file is byte-for-byte identical after a umount/mount cycle. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Assisted-by: UOS-AI Assisted-by: CodeBuddy:Hy4 preview Signed-off-by: Zhu Tianhao Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 6d5e56378afd..a09496622c7a 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -463,7 +463,7 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, { u8 *kmirr; struct folio *folio; - unsigned int folio_ofs, lcn_folio_off = 0; + unsigned int folio_ofs; int err = 0; struct bio *bio; @@ -493,15 +493,11 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, memcpy(kmirr, m, vol->mft_record_size); kunmap_local(kmirr); - if (vol->cluster_size_bits > PAGE_SHIFT) { - lcn_folio_off = folio->index << PAGE_SHIFT; - lcn_folio_off &= vol->cluster_size_mask; - } - bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + - lcn_folio_off + folio_ofs); + ((u64)folio->index << PAGE_SHIFT) + + folio_ofs); if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs)) err = submit_bio_wait(bio); From 76a986c980bb502c7688d605ac7a67fd257a9a1b Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 12 Sep 2026 13:05:30 -0700 Subject: [PATCH 0937/1317] ALSA: usb-audio: Clamp implicit feedback packet count to URB capacity data_ep_set_params() allocates each data URB for exactly u->packets isochronous frames, so urb->iso_frame_desc[] has u->packets slots and ctx->packets is the driver's only record of that limit. For an implicit feedback sink, snd_usb_queue_pending_output_urbs() overwrites it with the sync source's packet count, which is calculated independently from the capture endpoint's parameters. When that count is larger, prepare_playback_urb() and prepare_silent_urb() can write iso_frame_desc[] past the allocation; their existing bounds limit payload bytes, not the descriptor index. The reproducer uses a high-speed UAC2 device declaring bInterval 1 for implicit feedback capture (8 packets) and bInterval 4 for playback (1 packet). On the first capture completion after the stream starts, it accesses seven descriptors spanning 112 bytes beyond the one-packet URB: BUG: KASAN: slab-out-of-bounds in prepare_playback_urb (sound/usb/pcm.c:1560) Write of size 4 at addr ffff88801e696ad0 by task vhci_rx/178 prepare_playback_urb (sound/usb/pcm.c:1560) prepare_outbound_urb (sound/usb/endpoint.c:340) snd_usb_queue_pending_output_urbs (sound/usb/endpoint.c:501) snd_complete_urb (sound/usb/endpoint.c:1834) __usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1657) usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1741) vhci_rx_loop (drivers/usb/usbip/vhci_rx.c:107) kthread (kernel/kthread.c:436) The buggy address belongs to the object at ffff88801e696a00 which belongs to the cache kmalloc-256 of size 256 The buggy address is located 0 bytes to the right of allocated 208-byte region [ffff88801e696a00, ffff88801e696ad0) Record the allocated packet count per endpoint and clamp both the adopted count and the packet-size copy to it. Fold the Format Type II delimiter into urb_packs before the allocation loop so the recorded limit matches every URB. Fixes: cf044e441902 ("ALSA: usb-audio: Update the number of packets properly at receiving") Reported-by: co+8eacd4fa193b1b28@bugs.sh Closes: https://lore.kernel.org/all/22xPn8drvIUtYgVeQnBiNqXuevOTpBAjepLz%40bugs.sh/ Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260912200530.1955491-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/card.h | 1 + sound/usb/endpoint.c | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/sound/usb/card.h b/sound/usb/card.h index e34d92d576a2..8299ac241c60 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -116,6 +116,7 @@ struct snd_usb_endpoint { unsigned int phase; /* phase accumulator */ unsigned int maxpacksize; /* max packet size in bytes */ unsigned int maxframesize; /* max packet size in frames */ + unsigned int max_urb_packs; /* packets allocated per data URB */ unsigned int max_urb_frames; /* max URB size in frames */ unsigned int curpacksize; /* current packet size in bytes (for capture) */ unsigned int curframesize; /* current packet size in frames (for capture) */ diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 0835943d7b0e..b72ce1e9bfb1 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -492,9 +492,10 @@ int snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep, /* copy over the length information */ if (implicit_fb) { - ctx->packets = packet->packets; + ctx->packets = min_t(int, packet->packets, + ep->max_urb_packs); memcpy(ctx->packet_size, packet->packet_size, - packet->packets * sizeof(packet->packet_size[0])); + ctx->packets * sizeof(packet->packet_size[0])); } /* call the data handler to fill in playback data */ @@ -1242,15 +1243,16 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); } + if (fmt->fmt_type == UAC_FORMAT_TYPE_II) + urb_packs++; /* for transfer delimiter */ + ep->max_urb_packs = urb_packs; + /* allocate and initialize data urbs */ for (i = 0; i < ep->nurbs; i++) { struct snd_urb_ctx *u = &ep->urb[i]; u->index = i; u->ep = ep; u->packets = urb_packs; - - if (fmt->fmt_type == UAC_FORMAT_TYPE_II) - u->packets++; /* for transfer delimiter */ u->buffer_size = maxsize * u->packets; u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); if (!u->urb) From fd95e68df6fe66344161a1329cbe5e5805e7b704 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 12 Sep 2026 18:21:42 +0200 Subject: [PATCH 0938/1317] ALSA: core: Fix potential UAF after asynchronous card release Usually a sound driver releases the resources assigned to the card via snd_card_free(), and it synchronizes with the whole release procedure. However, when the card is released asynchronously via snd_card_free_when_closed() like USB-audio driver, the situation is slightly different; although the snd_card_disconnect() call at the disconnection guarantees that any newer accesses will be gated, the in-flight tasks might be still accessing to the underlying card->dev device even after the disconnection, which would cause a use-after-free in the end, as reported by fuzzers. For addressing the bug above, this patch takes the refcount of card->dev at initialization of the card object, and releases at its destructor. This assures the availability of the card->dev in its whole lifecycle. Reported-by: Farhad Alemi Closes: https://lore.kernel.org/CA+0ovChexj4TrZL_2iG_P0WBEbZc5+73GfB3DkciQi=R8pZOnA@mail.gmail.com Closes: https://lore.kernel.org/CA+0ovCgQUQNN=Z1tJTouiCsDaXR5M-3-SQEGk-cpPXQkM5Xh+w@mail.gmail.com Cc: Link: https://patch.msgid.link/20260912162150.455144-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/init.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/core/init.c b/sound/core/init.c index 2f7f83a7611b..d05bea3c87f5 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -310,7 +310,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, kfree(card); /* manually free here, as no destructor called */ return err; } - card->dev = parent; + card->dev = get_device(parent); card->number = idx; WARN_ON(IS_MODULE(CONFIG_SND) && !module); card->module = module; @@ -603,6 +603,7 @@ static int snd_card_do_free(struct snd_card *card) dev_warn(card->dev, "unable to free card info\n"); /* Not fatal error */ } + put_device(card->dev); if (card->release_completion) complete(card->release_completion); if (!managed) From 6c05d00af307560e6a9f1631d6270d3df5aa2272 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Thu, 10 Sep 2026 23:11:21 -0400 Subject: [PATCH 0939/1317] ALSA: virtio: reset device before deleting virtqueues virtsnd_remove() and virtsnd_freeze() delete the virtqueues before resetting the device. del_vqs() frees the vring backing, but does not provide a generic device quiesce operation. In particular, modern virtio-pci keeps enabled queues active until the device is reset. Reset the device before deleting the virtqueues so it can no longer access the vring memory when that memory is released. This also covers probe failures after DRIVER_OK, which unwind through virtsnd_remove(). Fixes: de3a9980d8c3 ("ALSA: virtio: add virtio sound driver") Fixes: 575483e90a32 ("ALSA: virtio: introduce device suspend/resume support") Cc: stable@vger.kernel.org Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260911031121.1542502-1-oss.patchbox@gmail.com Signed-off-by: Takashi Iwai --- sound/virtio/virtio_card.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 647190f4d5af..6f35276416fe 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -354,8 +354,8 @@ static void virtsnd_remove(struct virtio_device *vdev) if (snd->card) snd_card_free(snd->card); - vdev->config->del_vqs(vdev); virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) { struct virtio_pcm_substream *vss = &snd->substreams[i]; @@ -383,8 +383,8 @@ static int virtsnd_freeze(struct virtio_device *vdev) virtsnd_disable_event_vq(snd); virtsnd_ctl_msg_cancel_all(snd); - vdev->config->del_vqs(vdev); virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); for (i = 0; i < snd->nsubstreams; ++i) cancel_work_sync(&snd->substreams[i].elapsed_period); From 48e97c59a49c9e90270f5e3d221db253b76de5da Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 16 Jul 2026 21:32:46 +0300 Subject: [PATCH 0940/1317] phy: renesas: rcar-gen3-usb2: Avoid long delay in atomic context The OTG PHY initialization sequence needs to wait for 20 ms at a specific step, as described in commit 72c0339c115b ("phy: renesas: rcar-gen3-usb2: follow the hardware manual procedure"). Commit 55a387ebb921 ("phy: renesas: rcar-gen3-usb2: Lock around hardware registers and driver data") tried to address various problems in the rcar-gen3-usb2 driver and converted the mutex protecting HW register accesses to a spin lock, leaving, however, a long delay in the critical section protected by the spin lock. This may become a problem, especially on RT kernels. To address this, release the spin lock before sleeping for 20 ms as required by the HW manual and reacquire it afterwards. To avoid other threads entering the critical section and configuring the HW while the software is waiting for the OTG initialization to complete, introduce the otg_initializing variable alongside the otg_init_done wait queue. Any other thread trying to configure the HW while the OTG PHY initialization is in progress waits for the wait queue instead of immediately returning errors to PHY users. The IRQs were also disabled while waiting for the OTG PHY initialization to complete, as the interrupt handler may also apply HW settings. The OTG can only be initialized once. It is initialized by the first PHY that calls struct phy_ops::rcar_gen3_phy_usb2_init(). To avoid failures when multiple PHYs call struct phy_ops::rcar_gen3_phy_usb2_init() simultaneously, and the PHY responsible for initializing the OTG either fails or deinit quiqly and another PHY takes over the PHY init role), the code waiting for the channel->otg_init_done wait queue retries up to NUM_OF_PHYS times. Fixes: 55a387ebb921 ("phy: renesas: rcar-gen3-usb2: Lock around hardware registers and driver data") Cc: stable@vger.kernel.org Reported-by: Pavel Machek Closes: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Reported-by: Nobuhiro Iwamatsu Closes: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Signed-off-by: Claudiu Beznea Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Link: https://patch.msgid.link/20260716183246.3183877-1-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Vinod Koul --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 310 +++++++++++++++++++---- 1 file changed, 265 insertions(+), 45 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 9ae9975d3255..b5ba751805aa 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -27,6 +27,7 @@ #include #include #include +#include #include /******* USB2.0 Host registers (original offset is +0x200) *******/ @@ -106,6 +107,13 @@ /* RZ/G2L specific */ #define USB2_LINECTRL1_USB2_IDMON BIT(0) +/* + * The OTG initialization is expected to finish in 20ms. Choose a large enough + * timeout to avoid waiters exit prematurely the waiting section under heavy + * CPU load. + */ +#define USB2_OTG_INIT_TIMEOUT msecs_to_jiffies(120) + #define NUM_OF_PHYS 4 enum rcar_gen3_phy_index { PHY_INDEX_BOTH_HC, @@ -138,12 +146,20 @@ struct rcar_gen3_chan { struct rcar_gen3_phy rphys[NUM_OF_PHYS]; struct regulator *vbus; struct work_struct work; + wait_queue_head_t otg_init_done; spinlock_t lock; /* protects access to hardware and driver data structure. */ enum usb_dr_mode dr_mode; bool extcon_host; bool is_otg_channel; bool uses_otg_pins; bool otg_internal_reg; + /* + * The OTG can be initialized only once and needs to release the spinlock + * and wait for 20 ms due to hardware constraints. If a thread executes + * PHY configuration code while the OTG PHY is waiting for the 20 ms, the + * thread will have to wait for the OTG PHY initialization to complete. + */ + bool otg_initializing; }; struct rcar_gen3_phy_drv_data { @@ -392,26 +408,58 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, struct rcar_gen3_chan *ch = dev_get_drvdata(dev); bool is_b_device; enum phy_mode cur_mode, new_mode; + int retries = NUM_OF_PHYS; + unsigned long flags; + int ret = -EIO; - guard(spinlock_irqsave)(&ch->lock); + spin_lock_irqsave(&ch->lock, flags); - if (!ch->is_otg_channel || !rcar_gen3_is_any_otg_rphy_initialized(ch)) - return -EIO; + if (!ch->is_otg_channel) + goto unlock; - if (sysfs_streq(buf, "host")) + while (retries-- && ch->otg_initializing) { + spin_unlock_irqrestore(&ch->lock, flags); + + ret = wait_event_timeout(ch->otg_init_done, !ch->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + goto exit; + + spin_lock_irqsave(&ch->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (ch->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } + + if (!rcar_gen3_is_any_otg_rphy_initialized(ch)) { + ret = -EIO; + goto unlock; + } + + if (sysfs_streq(buf, "host")) { new_mode = PHY_MODE_USB_HOST; - else if (sysfs_streq(buf, "peripheral")) + } else if (sysfs_streq(buf, "peripheral")) { new_mode = PHY_MODE_USB_DEVICE; - else - return -EINVAL; + } else { + ret = -EINVAL; + goto unlock; + } /* is_b_device: true is B-Device. false is A-Device. */ is_b_device = rcar_gen3_check_id(ch); cur_mode = rcar_gen3_get_phy_mode(ch); /* If current and new mode is the same, this returns the error */ - if (cur_mode == new_mode) - return -EINVAL; + if (cur_mode == new_mode) { + ret = -EINVAL; + goto unlock; + } if (new_mode == PHY_MODE_USB_HOST) { /* And is_host must be false */ if (!is_b_device) /* A-Peripheral */ @@ -425,7 +473,10 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, rcar_gen3_init_for_peri(ch); } - return count; +unlock: + spin_unlock_irqrestore(&ch->lock, flags); +exit: + return ret ?: count; } static ssize_t role_show(struct device *dev, struct device_attribute *attr, @@ -441,14 +492,11 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RW(role); -static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) +static void rcar_gen3_init_otg_phase0(struct rcar_gen3_chan *ch) { void __iomem *usb2_base = ch->base; u32 val; - if (!ch->is_otg_channel || rcar_gen3_is_any_otg_rphy_initialized(ch)) - return; - /* Should not use functions of read-modify-write a register */ val = readl(usb2_base + USB2_LINECTRL1); val = (val & ~USB2_LINECTRL1_DP_RPD) | USB2_LINECTRL1_DPRPD_EN | @@ -471,7 +519,11 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); } } - mdelay(20); +} + +static void rcar_gen3_init_otg_phase1(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; writel(0xffffffff, usb2_base + USB2_OBINTSTA); writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTEN); @@ -502,6 +554,7 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) void __iomem *usb2_base = ch->base; struct device *dev = ch->dev; irqreturn_t ret = IRQ_NONE; + unsigned long flags; u32 status; pm_runtime_get_noresume(dev); @@ -509,33 +562,102 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) if (pm_runtime_suspended(dev)) goto rpm_put; - scoped_guard(spinlock, &ch->lock) { - status = readl(usb2_base + USB2_OBINTSTA); - if (status & ch->phy_data->obint_enable_bits) { - dev_vdbg(dev, "%s: %08x\n", __func__, status); - if (ch->phy_data->vblvl_ctrl) - writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); - else - writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); - rcar_gen3_device_recognition(ch); - rcar_gen3_configure_vblvl_ctrl(ch); - ret = IRQ_HANDLED; - } + spin_lock_irqsave(&ch->lock, flags); + + status = readl(usb2_base + USB2_OBINTSTA); + if (status & ch->phy_data->obint_enable_bits) { + dev_vdbg(dev, "%s: %08x\n", __func__, status); + if (ch->phy_data->vblvl_ctrl) + writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); + else + writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); + + ret = IRQ_HANDLED; + + /* This should not happen! */ + if (ch->otg_initializing) + goto unlock; + + rcar_gen3_device_recognition(ch); + rcar_gen3_configure_vblvl_ctrl(ch); } +unlock: + spin_unlock_irqrestore(&ch->lock, flags); rpm_put: pm_runtime_put_noidle(dev); return ret; } +static void rcar_gen3_phy_usb2_irqs_mask_all(struct rcar_gen3_chan *channel, + u32 *masked_irqs_bits) +{ + u32 val, bitmask = USB2_INT_ENABLE_UCOM_INTEN; + void __iomem *usb2_base = channel->base; + + for (unsigned int i = 0; i < NUM_OF_PHYS; i++) + bitmask |= channel->rphys[i].int_enable_bits; + + val = readl(usb2_base + USB2_INT_ENABLE); + *masked_irqs_bits = val & bitmask; + val &= ~bitmask; + writel(val, usb2_base + USB2_INT_ENABLE); + + /* + * Don't report channel->phy_data->obint_enable_bits IRQs. These are + * unmasked anyway in rcar_gen3_init_otg_phase1(). + */ + val = readl(usb2_base + USB2_OBINTEN); + val &= ~channel->phy_data->obint_enable_bits; + writel(val, usb2_base + USB2_OBINTEN); +} + +static void rcar_gen3_phy_usb2_irqs_unmask(struct rcar_gen3_chan *channel, + u32 irqs_bits) +{ + u32 val, bitmask = USB2_INT_ENABLE_UCOM_INTEN; + void __iomem *usb2_base = channel->base; + + for (unsigned int i = 0; i < NUM_OF_PHYS; i++) + bitmask |= channel->rphys[i].int_enable_bits; + + val = readl(usb2_base + USB2_INT_ENABLE); + val &= ~bitmask; + val |= irqs_bits; + writel(val, usb2_base + USB2_INT_ENABLE); +} + static int rcar_gen3_phy_usb2_init(struct phy *p) { struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; + int ret; - guard(spinlock_irqsave)(&channel->lock); + spin_lock_irqsave(&channel->lock, flags); + + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } /* Initialize USB2 part */ val = readl(usb2_base + USB2_INT_ENABLE); @@ -548,8 +670,23 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) } /* Initialize otg part (only if we initialize a PHY with IRQs). */ - if (rphy->int_enable_bits) - rcar_gen3_init_otg(channel); + if (rphy->int_enable_bits && channel->is_otg_channel && + !rcar_gen3_is_any_otg_rphy_initialized(channel)) { + u32 masked_irq_bits = 0; + + rcar_gen3_init_otg_phase0(channel); + rcar_gen3_phy_usb2_irqs_mask_all(channel, &masked_irq_bits); + channel->otg_initializing = true; + spin_unlock_irqrestore(&channel->lock, flags); + + fsleep(20000); + + spin_lock_irqsave(&channel->lock, flags); + rcar_gen3_phy_usb2_irqs_unmask(channel, masked_irq_bits); + rcar_gen3_init_otg_phase1(channel); + channel->otg_initializing = false; + wake_up_all(&channel->otg_init_done); + } if (channel->phy_data->vblvl_ctrl) { /* SIDDQ mode release */ @@ -568,7 +705,10 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) rphy->initialized = true; - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + + return ret; } static int rcar_gen3_phy_usb2_exit(struct phy *p) @@ -576,9 +716,32 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; + int ret; - guard(spinlock_irqsave)(&channel->lock); + spin_lock_irqsave(&channel->lock, flags); + + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } rphy->initialized = false; @@ -588,7 +751,9 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) val &= ~USB2_INT_ENABLE_UCOM_INTEN; writel(val, usb2_base + USB2_INT_ENABLE); - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + return ret; } static int rcar_gen3_phy_usb2_power_on(struct phy *p) @@ -596,8 +761,10 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; - int ret = 0; + int ret; if (channel->vbus && !channel->otg_internal_reg) { ret = regulator_enable(channel->vbus); @@ -605,7 +772,27 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) return ret; } - guard(spinlock_irqsave)(&channel->lock); + spin_lock_irqsave(&channel->lock, flags); + + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + goto disable_regulator; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } if (!rcar_gen3_are_all_rphys_power_off(channel)) goto out; @@ -620,27 +807,59 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) /* The powered flag should be set for any other phys anyway */ rphy->powered = true; - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + +disable_regulator: + if (ret && channel->vbus && !channel->otg_internal_reg) + regulator_disable(channel->vbus); + + return ret; } static int rcar_gen3_phy_usb2_power_off(struct phy *p) { struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; - int ret = 0; + int retries = NUM_OF_PHYS; + unsigned long flags; + int ret; - scoped_guard(spinlock_irqsave, &channel->lock) { - rphy->powered = false; + spin_lock_irqsave(&channel->lock, flags); - if (rcar_gen3_are_all_rphys_power_off(channel)) { - u32 val = readl(channel->base + USB2_USBCTR); + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); - val |= USB2_USBCTR_PLL_RST; - writel(val, channel->base + USB2_USBCTR); - } + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); } - if (channel->vbus && !channel->otg_internal_reg) + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } + + rphy->powered = false; + + if (rcar_gen3_are_all_rphys_power_off(channel)) { + u32 val = readl(channel->base + USB2_USBCTR); + + val |= USB2_USBCTR_PLL_RST; + writel(val, channel->base + USB2_USBCTR); + } + +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + + if (!ret && channel->vbus && !channel->otg_internal_reg) ret = regulator_disable(channel->vbus); return ret; @@ -1022,6 +1241,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) return ret; spin_lock_init(&channel->lock); + init_waitqueue_head(&channel->otg_init_done); for (i = 0; i < NUM_OF_PHYS; i++) { channel->rphys[i].phy = devm_phy_create(dev, NULL, channel->phy_data->phy_usb2_ops); From de7f29a1fe1dc2864d8a47f8c39d508442cae167 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 11 Sep 2026 09:40:14 +0200 Subject: [PATCH 0941/1317] phy: mediatek: phy-mtk-hdmi-mt8195: Fix PLL calc divisor overflow When trying to calculate a PLL rate for target display resolutions above 2560x1440, 24bpp, 30Hz, the pixel clock value will be more than 32-bits long but the division to finally calculate the digital clock divider is being done with div_u64(), which expects a 32bit unsigned divisor. Fix the overflow by using div64_u64() instead. Fixes: 9d9ff3d2a4a5 ("phy: mediatek: hdmi: mt8195: fix wrong pll calculus") Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260911074015.9994-2-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c index 1426a2db984d..e6ee8e080022 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c @@ -290,7 +290,7 @@ static int mtk_hdmi_pll_calc(struct mtk_hdmi_phy *hdmi_phy, struct clk_hw *hw, posdiv2 = 1; /* Digital clk divider, max /32 */ - digital_div = div_u64(ns_hdmipll_ck, posdiv1 * posdiv2 * pixel_clk); + digital_div = div64_u64(ns_hdmipll_ck, posdiv1 * posdiv2 * pixel_clk); if (!(digital_div <= 32 && digital_div >= 1)) return -EINVAL; From 486a70ef848264dcf9a57f0bb0452848db9537de Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 11 Sep 2026 09:40:15 +0200 Subject: [PATCH 0942/1317] phy: mediatek: phy-mtk-hdmi-mt8195: Fix TMDS clk bit ratio setting The comment in the mtk_phy_tmds_clk_ratio() function clearly and correctly explains that the TMDS ratio has to be 1/10 for data rates under 3.4Gbps, and 1/40 over that. Unfortunately though, the TXC_DIV register setting was wrong, as in value 3 means to divide by 8 and, in order to achieve the in spec 1/40 (tmds) data rate, this has to divide by 4 instead! Add definitions for the TXC_DIV register values clearly explaining the meanings (DIV2, DIV4, DIV8), and program the correct, DIV 4, value to the register in mtk_phy_tmds_clk_ratio(). This fixes out of spec clocking and, with this change, SoCs using the MT8195 class HDMI PHYs can now successfully be configured to output 3840x2160@60Hz over HDMI. Fixes: 45810d486bb4 ("phy: mediatek: add support for phy-mtk-hdmi-mt8195") Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260911074015.9994-3-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c | 2 +- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c index e6ee8e080022..a4bc1268946d 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c @@ -36,7 +36,7 @@ mtk_phy_tmds_clk_ratio(struct mtk_hdmi_phy *hdmi_phy, bool enable) * clock bit ratio 1:40, under 3.4Gbps, clock bit ratio 1:10 */ if (enable) - mtk_phy_update_field(regs + HDMI20_CLK_CFG, REG_TXC_DIV, 3); + mtk_phy_update_field(regs + HDMI20_CLK_CFG, REG_TXC_DIV, VAL_TXC_DIV4); else mtk_phy_clear_bits(regs + HDMI20_CLK_CFG, REG_TXC_DIV); } diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h index e26caaf4d104..58800d7659ca 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h @@ -17,6 +17,9 @@ #define HDMI20_CLK_CFG 0x70 #define REG_TXC_DIV GENMASK(31, 30) +#define VAL_TXC_DIV2 1 +#define VAL_TXC_DIV4 2 +#define VAL_TXC_DIV8 3 #define HDMI_1_CFG_0 0x00 #define RG_HDMITX21_DRV_IBIAS_CLK GENMASK(10, 5) From c7a1c6e8004ab12a9c9bfdcb603f60f9bf4a3cee Mon Sep 17 00:00:00 2001 From: fangqiurong Date: Sat, 12 Sep 2026 21:15:18 +0800 Subject: [PATCH 0943/1317] sched_ext: Close the pre-enable ops error claim window scx_alloc_and_add_sched() publishes ops->priv before scx_root_enable_workfn() switches the state to SCX_ENABLING. An error claimed via scx_bpf_error_bstr() from an associated BPF program in that window is consumed by scx_disable_workfn(), which takes the pre-enable shortcut in scx_root_disable(). The shortcut returns without any teardown and restores SCX_DISABLED with an unconditional scx_set_enable_state() xchg racing the enable workfn's own transition. The enable then completes with the claim consumed: the scheduler stays up but can never be disabled again, and bpf_scx_unreg() frees it while still in use, resulting in a use-after-free. Both WARN_ON_ONCE()s fire back to back: WARNING: kernel/sched/ext/ext.c:7522 at scx_root_enable_workfn+0xeec/0x1be0, CPU#3: scx_enable_help/276 WARNING: kernel/sched/ext/ext.c:6398 at scx_root_disable+0xb50/0xdb8, CPU#0: sched_ext_helpe/664 scx_root_enable_workfn() switches to SCX_ENABLING before the scheduler allocation, so ops->priv is never visible while SCX_DISABLED. The allocation failure path restores SCX_DISABLED. Fixes: 105dcd005be2 ("sched_ext: Introduce scx_prog_sched()") Cc: stable@vger.kernel.org Signed-off-by: fangqiurong Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index adf5993fa597..83999203a63a 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -7516,22 +7516,24 @@ static void scx_root_enable_workfn(struct kthread_work *work) #ifdef CONFIG_EXT_SUB_SCHED cgroup_get(cgrp); #endif + /* + * Transition to ENABLING to arm the disable path. Allocation failure + * still unwinds locally. Full disabling on failure applies only after + * scx_alloc_and_add_sched() succeeds. + */ + WARN_ON_ONCE(scx_set_enable_state(SCX_ENABLING) != SCX_DISABLED); + WARN_ON_ONCE(scx_root); + sch = scx_alloc_and_add_sched(cmd, cgrp, NULL); if (IS_ERR(sch)) { ret = PTR_ERR(sch); + WARN_ON_ONCE(scx_set_enable_state(SCX_DISABLED) != SCX_ENABLING); goto err_free_tid_hash; } if (sch->is_cid_type) static_branch_enable(&__scx_is_cid_type); - /* - * Transition to ENABLING and clear exit info to arm the disable path. - * Failure triggers full disabling from here on. - */ - WARN_ON_ONCE(scx_set_enable_state(SCX_ENABLING) != SCX_DISABLED); - WARN_ON_ONCE(scx_root); - atomic_long_set(&scx_nr_rejected, 0); for_each_possible_cpu(cpu) { From 1e713f9bb2ac583521f06b0eb4e22440b1e3d078 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sun, 13 Sep 2026 20:44:46 +0700 Subject: [PATCH 0944/1317] ALSA: pcm: set timer->private_data before registering the PCM timer snd_pcm_timer_init() calls snd_device_register() to link the new struct snd_timer into the global timer list while it still carries hw.c_resolution = snd_pcm_timer_resolution (and hw.start/hw.stop), and only afterwards sets timer->private_data = substream. Once the timer is on the list under register_mutex, a concurrent reader can already reach it through the same mutex and invoke these callbacks. /proc/asound/timers does this via c_resolution(), and snd_timer_open()+snd_timer_start() reach start()/stop() the same way. All three dereference timer->private_data, which for this brief window is NULL, giving a NULL-pointer dereference: substream = timer->private_data; return substream->runtime ? ... // substream is NULL Move the private_data/private_free assignment before snd_device_register() so the timer is never visible on the list without its private_data set. On the snd_device_register() failure path, private_free() (snd_pcm_timer_free()) can now run, but it only does substream->timer = NULL, which is already NULL at that point since substream->timer is set to the new timer just once, after a successful registration -- so the failure path stays safe. Reported-by: syzbot+19da64013c46df87f971@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=19da64013c46df87f971 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260913134446.114724-1-ngocthang2710.1999@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_timer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/core/pcm_timer.c b/sound/core/pcm_timer.c index ab0e5bd70f8f..18bedd66435d 100644 --- a/sound/core/pcm_timer.c +++ b/sound/core/pcm_timer.c @@ -111,12 +111,15 @@ void snd_pcm_timer_init(struct snd_pcm_substream *substream) snd_pcm_direction_name(substream->stream), tid.card, tid.device, tid.subdevice); timer->hw = snd_pcm_timer; + /* Set before registering: a concurrent reader can invoke our hw + * callbacks as soon as the timer is on the global list. + */ + timer->private_data = substream; + timer->private_free = snd_pcm_timer_free; if (snd_device_register(timer->card, timer) < 0) { snd_device_free(timer->card, timer); return; } - timer->private_data = substream; - timer->private_free = snd_pcm_timer_free; substream->timer = timer; } From 442ffa742daa65a0e8fe003abe9fbe472366e4de Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 11 Sep 2026 20:39:35 +0100 Subject: [PATCH 0945/1317] tracing/remotes: Account for ring buffer page header in size calculation trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount the required pages because every ring buffer page contains a header (BUF_PAGE_HDR_SIZE). Account for that header to ensure allocated remote ring buffers aren't smaller than requested by the user. The newly introduced helper __calc_nr_pages_ring_buffer_desc() can return a value that overflows the descriptor nr_pages field (32 bits). Link: https://patch.msgid.link/20260911193937.602202-2-vdonnefort@google.com Fixes: 2e67fabd8b77 ("ring-buffer: Introduce ring-buffer remotes") Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- include/linux/ring_buffer.h | 15 +++++++++++++-- kernel/trace/trace_remote.c | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h index afc7daa6ee7d..11bffb6a142d 100644 --- a/include/linux/ring_buffer.h +++ b/include/linux/ring_buffer.h @@ -3,8 +3,9 @@ #define _LINUX_RING_BUFFER_H #include -#include #include +#include +#include #include @@ -279,9 +280,19 @@ static inline struct ring_buffer_desc *__first_ring_buffer_desc(struct trace_buf return (struct ring_buffer_desc *)(&desc->__data[0]); } +/* + * Returns the number of pages for a ring_buffer_desc. The caller must ensure it + * does not overflow ring_buffer_desc::nr_page_va. + */ +static inline unsigned long __calc_nr_pages_ring_buffer_desc(size_t size) +{ + /* Takes into account the reader page */ + return max(DIV_ROUND_UP(size, PAGE_SIZE - BUF_PAGE_HDR_SIZE), 2UL) + 1; +} + static inline size_t trace_buffer_desc_size(size_t buffer_size, unsigned int nr_cpus) { - unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1; + unsigned long nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); struct ring_buffer_desc *rbdesc; return size_add(offsetof(struct trace_buffer_desc, __data), diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index 75fa1ffc4c96..c17902e42ef1 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -980,7 +980,7 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, const struct cpumask *cpumask) { size_t min_desc_size = trace_buffer_desc_size(buffer_size, cpumask_weight(cpumask)); - unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1; + unsigned int nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); struct ring_buffer_desc *rb_desc; int cpu, ret = -ENOMEM; From d059d8bf2c9b5d563d15e7552d73e17d7535013a Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 11 Sep 2026 20:39:36 +0100 Subject: [PATCH 0946/1317] tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing The number of pages per remote ring buffer is capped by ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to overflow that field would silently allocate a descriptor smaller than what was asked for. Return SIZE_MAX from trace_buffer_desc_size() on nr_page_va overflow. Link: https://patch.msgid.link/20260911193937.602202-3-vdonnefort@google.com Fixes: 2e67fabd8b77 ("ring-buffer: Introduce ring-buffer remotes") Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- include/linux/ring_buffer.h | 4 ++++ kernel/trace/trace_remote.c | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h index 11bffb6a142d..eac3e9080c3c 100644 --- a/include/linux/ring_buffer.h +++ b/include/linux/ring_buffer.h @@ -295,6 +295,10 @@ static inline size_t trace_buffer_desc_size(size_t buffer_size, unsigned int nr_ unsigned long nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); struct ring_buffer_desc *rbdesc; + /* Capped by ring_buffer_desc::nr_page_va */ + if (nr_pages > UINT_MAX) + return SIZE_MAX; + return size_add(offsetof(struct trace_buffer_desc, __data), size_mul(nr_cpus, struct_size(rbdesc, page_va, nr_pages))); } diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index c17902e42ef1..2d5bc423efca 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -980,9 +980,12 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, const struct cpumask *cpumask) { size_t min_desc_size = trace_buffer_desc_size(buffer_size, cpumask_weight(cpumask)); - unsigned int nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); struct ring_buffer_desc *rb_desc; int cpu, ret = -ENOMEM; + unsigned int nr_pages; + + if (min_desc_size == SIZE_MAX) + return -E2BIG; if (desc_size < min_desc_size) return -EINVAL; @@ -991,6 +994,7 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, desc->struct_len = min_desc_size; rb_desc = __first_ring_buffer_desc(desc); + nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); for_each_cpu(cpu, cpumask) { unsigned int id; From d860c67c051685abb0460b593b193f0f45f4fa92 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 12 Sep 2026 11:39:38 +0100 Subject: [PATCH 0947/1317] ring-buffer: Check resize_disabled before publishing the new subbuf order ring_buffer_subbuf_order_set() stores the new order and only then walks the CPUs, returning -EBUSY if any of them has resizing disabled. A user mapped buffer has resizing disabled, and __rb_map_vma() reads buffer->subbuf_order without buffer->mutex, so an mmap of an already mapped CPU racing the failing order change sizes the mapping with the new order and inserts pages past the sub-buffer into the VMA. Check the CPUs before storing the new order. Cc: stable@vger.kernel.org Fixes: 117c39200d9d ("ring-buffer: Introducing ring-buffer mapping functions") Link: https://patch.msgid.link/20260912103938.1127021-1-devnexen@gmail.com Signed-off-by: David Carlier Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 9bc8ce8c5676..04bb94c29f58 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -7473,6 +7473,14 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) old_capacity = rb_subbuf_capacity(buffer); + /* The mmap fast path reads subbuf_order without buffer->mutex. */ + for_each_buffer_cpu(buffer, cpu) { + if (!cpumask_test_cpu(cpu, buffer->cpumask)) + continue; + if (atomic_read(&buffer->buffers[cpu]->resize_disabled)) + return -EBUSY; + } + atomic_inc(&buffer->record_disabled); /* Make sure all commits have finished */ From 1a296bfd3e775e515233f746218824fc7dd5ff16 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Sun, 16 Aug 2026 23:33:40 +0545 Subject: [PATCH 0948/1317] wifi: mt76: mt7921: skip unknown CLC firmware records Treat an out-of-range CLC index as newer firmware rather than a malformed image. linux-firmware 20260810 ships MT7922 records with idx 3, and rejecting them made mt7921e fail to probe. Keep the record-length checks, and report those as errors so a truncated table is visible instead of a silent retry loop. Fixes: 9417c5818a01 ("wifi: mt76: mt7921: validate CLC firmware records") Reported-by: Mikhail Gavrilov Signed-off-by: Laxman Acharya Padhya Reviewed-by: Junjie Cao Tested-by: Mikhail Gavrilov Signed-off-by: Linus Torvalds --- drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index a118a301564c..40546005c743 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -477,18 +477,23 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) for (offset = 0; offset < len; offset += clc_len) { if (len - offset < sizeof(*clc)) { + dev_err(mdev->dev, "Invalid CLC record\n"); ret = -EINVAL; goto out; } clc = (const struct mt7921_clc *)(clc_base + offset); clc_len = le32_to_cpu(clc->len); - if (clc_len < sizeof(*clc) || clc_len > len - offset || - clc->idx >= ARRAY_SIZE(phy->clc)) { + if (clc_len < sizeof(*clc) || clc_len > len - offset) { + dev_err(mdev->dev, "Invalid CLC record\n"); ret = -EINVAL; goto out; } + /* Newer firmware may add records this driver does not use yet */ + if (clc->idx >= ARRAY_SIZE(phy->clc)) + continue; + /* do not init buf again if chip reset triggered */ if (phy->clc[clc->idx]) continue; From 7825de3f75d184612d77655669a04ea0da252c17 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Tue, 25 Aug 2026 11:17:12 -0700 Subject: [PATCH 0949/1317] wifi: mt76: mt792x: fix NULL dereference in ACPI SAR init during probe Some laptops carry a MediaTek power table in their firmware, and the driver reads it to set a transmit limit for each frequency range. It only fills in the ranges themselves when it registers the device. The startup step that does this existed already, but it never programmed anything. Two recent commits made it run a regulatory update instead, which sets the limits on the way through, long before registration. As a result, on a machine that has the table the driver reads through an empty pointer and the interface never appears: BUG: kernel NULL pointer dereference, address: 0000000000000004 RIP: 0010:mt792x_init_acpi_sar_power Call Trace: mt7921_set_tx_sar_pwr mt7921_mcu_regd_update mt7921_regd_update mt7921_run_firmware mt7921e_mcu_init mt7921_init_work Skip it when the ranges are missing. They are applied again once the device is up, which is where they came from before. Reported-by: Klara Modin Closes: https://lore.kernel.org/linux-wireless/aoyxqHYvSuaBeubf@soda.int.kasm.eu/ Fixes: 9b80bd9cab40 ("wifi: mt76: mt7921: add regulatory wiphy self manager support") Fixes: e9f3f1cc133f ("wifi: mt76: mt7925: add regulatory wiphy self manager support") Signed-off-by: Devin Wittmayer Tested-by: David Gow Tested-by: Klara Modin Signed-off-by: Linus Torvalds --- drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index 946dd7956e4a..b468051fbe68 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -323,7 +323,8 @@ int mt792x_init_acpi_sar_power(struct mt792x_phy *phy, bool set_default) const struct cfg80211_sar_capa *capa = phy->mt76->hw->wiphy->sar_capa; int i; - if (!phy->acpisar || !((struct mt792x_acpi_sar *)phy->acpisar)->dyn) + if (!capa || !phy->acpisar || + !((struct mt792x_acpi_sar *)phy->acpisar)->dyn) return 0; /* When ACPI SAR enabled in HW, we should apply rules for .frp From 856c562c94964a74f63c6d5f38a1509a59a2357d Mon Sep 17 00:00:00 2001 From: Sergey Zagursky Date: Wed, 2 Sep 2026 22:15:24 +0100 Subject: [PATCH 0950/1317] media: ipu-bridge: do not use the CVS device lookup for IVSC Since commit c6b1b34b5090 ("media: pci: intel: Add CVS support for IPU bridge driver") the internal camera no longer works on laptops where the sensor sits behind an IVSC, for example a Dell XPS 16 9640 (IPU6, INTC10CF, ov02c10): intel-ipu6 0000:00:05.0: Found supported sensor OVTI02C1:00 intel-ipu6 0000:00:05.0: Connected 1 cameras ivsc_csi intel_vsc-92335fcf-3203-4472-af93-7b4453ac29da: mei-csi probed without device fwnode! No sensor subdevice is registered, the media graph has no sensor entity and userspace finds no camera at all. ipu_bridge_get_ivsc_csi_dev() first looks for the platform device named "intel_vsc" and returns its mei-csi child. That device is created by mei_vsc, which on this machine only appears once the LJCA USB bridge and its SPI controller have probed, about a second after the IPU6 probe that runs the bridge: 07:59:29.297 platform INTC10CF:00 created (ACPI scan) 07:59:41 intel-ipu6 probe -> ipu_bridge_init() 07:59:42.391 platform intel_vsc created (mei_vsc) The commit above added two fallbacks for CVS which match on the ACPI companion alone. They are reached for every entry of ivsc_acpi_ids[], IVSC IDs included. The IVSC ACPI device has two physical nodes: INTC10CF:00/physical_node -> platform/INTC10CF:00 (no driver bound) INTC10CF:00/physical_node1 -> platform/intel_vsc (mei_vsc) so bus_find_device_by_acpi_dev(&platform_bus_type, adev) returns the bare platform device. ipu_bridge_instantiate_ivsc() then attaches the IVSC software node to that device instead of to the mei-csi client, the bridge reports success, and the probe is never retried. mei_csi later probes without a fwnode, the CSI-2 link is never described, and the sensor ACPI device, which has an honoured _DEP on the IVSC device, is never enumerated. Before those fallbacks existed the lookup returned NULL here, the bridge failed with -ENODEV and the probe was retried once the IVSC device had shown up. Skip those fallbacks for IVSC devices, keying on the IVSC IDs rather than the CVS ones: new CVS IDs keep being added, whereas the IVSC list is complete. CVS binds a driver to the ACPI device itself, so matching on the companion stays unambiguous there. Fixes: c6b1b34b5090 ("media: pci: intel: Add CVS support for IPU bridge driver") Link: https://lore.kernel.org/linux-media/20260901194526.6369-1-gvozdoder@gmail.com/ Cc: stable@vger.kernel.org Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Sergey Zagursky Signed-off-by: Linus Torvalds --- drivers/media/pci/intel/ipu-bridge.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 1bb3a3e98d6b..bd64c0400c0d 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -232,6 +232,19 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { { "INTC10FA" }, /* NVL */ }; +/* + * The subset of ivsc_acpi_ids[] which are IVSC, rather than CVS, devices. The + * CVS IDs are deliberately not listed here: new ones keep being added, whereas + * this list is complete. + */ +static const struct acpi_device_id ivsc_only_acpi_ids[] = { + { "INTC1059" }, + { "INTC1095" }, + { "INTC100A" }, + { "INTC10CF" }, + { } +}; + static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) { unsigned int i; @@ -283,6 +296,17 @@ static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev) return csi_dev; } + /* + * The lookups below match on the ACPI companion alone. That is fine for + * CVS, which binds a driver to that very device, but not for IVSC: there + * the ACPI device also has a driverless platform device, which would be + * returned instead of the mei-csi client. Return NULL for IVSC so that + * the caller fails and the probe is retried once the IVSC device shows + * up. + */ + if (!acpi_match_device_ids(adev, ivsc_only_acpi_ids)) + return NULL; + /* Try to locate CVS device on the I2C bus */ csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev); if (csi_dev) From c65eae6f61d1778ff7a82e4aae4080e26f486af1 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Fri, 11 Sep 2026 22:38:04 -0300 Subject: [PATCH 0951/1317] smb: client: cancel reconnect work in clean_demultiplex_info() clean_demultiplex_info() cancels server->echo delayed work but not server->reconnect, which can cause a use-after-free when the demultiplex thread exits while a reconnect work is still queued: cifs_demultiplex_thread() cifs_readv_from_socket() cifs_reconnect() __cifs_reconnect() cifs_queue_server_reconn() mod_delayed_work(cifsiod_wq, &server->reconnect, 0) clean_demultiplex_info() cancel_delayed_work_sync(&server->echo) // echo canceled // reconnect NOT canceled kfree_sensitive(server) // server freed ...later, on cifsiod_wq: smb2_reconnect_server() server->srv_count // UAF read of freed server Fix this by canceling server->reconnect delayed work in clean_demultiplex_info() before the server is freed, the same way cifs_put_tcp_session() already does. Reported-by: syzbot+5003556314abc915a71f@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/6aa4a12d.f81106d8.2ab401.0023.GAE@google.com Fixes: 53e0e11efe92 ("CIFS: Fix a possible memory corruption during reconnect") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Tom Talpey Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index b6e98eb31673..bef710d9a3c3 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1067,6 +1067,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server) spin_unlock(&server->srv_lock); cancel_delayed_work_sync(&server->echo); + cancel_delayed_work_sync(&server->reconnect); spin_lock(&server->srv_lock); server->tcpStatus = CifsExiting; From 5f270f091256da1338c3631083e15d7f83cc05e1 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 9 Sep 2026 13:52:14 -0300 Subject: [PATCH 0952/1317] smb: client: fix rlist race and missing initialization TCP_Server_Info.rlist is allocated via kzalloc which zeros both ->next and ->prev to NULL instead of pointing to itself, making list_empty() always return false and list_add() dereference a NULL ->prev pointer. Also, cifs_signal_cifsd_for_reconnect() can be called concurrently from multiple cifsd threads, allowing the same server's rlist node to be added twice into the local list, corrupting it. Closes: https://sashiko.dev/#/patchset/20260911204446.1719356-1-pc%40manguebit.org Fixes: df0e03a4fb94 ("smb: client: fix potential deadlock when reconnecting channels") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Tom Talpey Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index bef710d9a3c3..4bd9f9fb93bb 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -174,6 +174,8 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server, nserver = ses->chans[i].server; if (!nserver) continue; + if (!list_empty(&nserver->rlist)) + continue; nserver->srv_count++; list_add(&nserver->rlist, &reco); } @@ -182,11 +184,15 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server, } } + spin_lock(&cifs_tcp_ses_lock); list_for_each_entry_safe(server, nserver, &reco, rlist) { list_del_init(&server->rlist); set_need_reco(server); + spin_unlock(&cifs_tcp_ses_lock); cifs_put_tcp_session(server, 0); + spin_lock(&cifs_tcp_ses_lock); } + spin_unlock(&cifs_tcp_ses_lock); } /* @@ -1824,6 +1830,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, spin_lock_init(&tcp_ses->mid_counter_lock); INIT_LIST_HEAD(&tcp_ses->tcp_ses_list); INIT_LIST_HEAD(&tcp_ses->smb_ses_list); + INIT_LIST_HEAD(&tcp_ses->rlist); INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request); INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server); mutex_init(&tcp_ses->reconnect_mutex); From e75c96157d45e498970158c8f7373d90102e33b9 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 12 Sep 2026 14:20:08 -0300 Subject: [PATCH 0953/1317] smb: client: fix smbd_connection leak on cifs_get_tcp_session() error When an RDMA connection is successfully established via smbd_get_connection() but cifs_get_tcp_session() later fails (e.g. kthread_create() returns an error), the error path frees tcp_ses without first destroying the smbd_connection. Fix this by calling smbd_destroy() in the out_err cleanup path before kfree(tcp_ses). smbd_destroy() safely handles the case where smbd_conn is NULL, so it can be called unconditionally. Closes: https://sashiko.dev/#/patchset/20260912165503.521597-1-pc%40manguebit.org Fixes: 2f8946464b11 ("CIFS: SMBD: Upper layer connects to SMBDirect session") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Tom Talpey Cc: Stefan Metzmacher Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 4bd9f9fb93bb..28e1ddeb6182 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1934,6 +1934,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, kfree(tcp_ses->leaf_fullpath); if (tcp_ses->ssocket) sock_release(tcp_ses->ssocket); + smbd_destroy(tcp_ses); kfree(tcp_ses); } return ERR_PTR(rc); From ea9dadeac79cef509a4b8b4a3e3b39a741e63313 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 30 Jul 2026 16:05:13 +0300 Subject: [PATCH 0954/1317] drm/msm: mark the fbdev framebuffer as system memory msm_fbdev_driver_fbdev_probe() points screen_buffer at a kernel virtual mapping of the GEM object and uses the deferred sysmem fb ops, but never sets FBINFO_VIRTFB. The framebuffer core then assumes the memory is not in the virtual address space and warns on the first console draw: fb0: sys_fillrect: framebuffer is not in virtual address space. The drm_fbdev_dma, drm_fbdev_shmem and drm_fbdev_ttm helpers all set the flag for system memory. Do the same here. Signed-off-by: Dmitry Baryshkov Assisted-by: Claude:claude-opus-5 Patchwork: https://patchwork.freedesktop.org/patch/743293/ Link: https://lore.kernel.org/r/20260730-drm-msm-fbinfo-virt-v1-1-a27099a6dc58@oss.qualcomm.com Acked-by: Rob Clark # on IRC Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_fbdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index 89ca9da3e1f2..dd6d6c507d77 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -155,6 +155,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, helper->fb = buffer->fb; fbi->fbops = &msm_fb_ops; + fbi->flags |= FBINFO_VIRTFB; /* system memory */ drm_fb_helper_fill_info(fbi, helper, sizes); From e249a6e2a130c08bb4d8b0a55cbe29754307e5c9 Mon Sep 17 00:00:00 2001 From: Jesse Casco Date: Sat, 8 Aug 2026 13:13:25 -0400 Subject: [PATCH 0955/1317] drm/msm/dp: skip PUSH_IDLE when the link was never enabled msm_dp_display_atomic_enable() returns early when link training fails, leaving ->power_on false and the main link down. msm_dp_display_atomic_disable() nevertheless writes DP_STATE_CTRL_PUSH_IDLE and waits for an idle-pattern completion that cannot arrive, so every failed enable is followed by "PUSH_IDLE pattern timedout". Every other step of the teardown is already gated on that flag: msm_dp_display_disable(), called from .atomic_post_disable(), returns early on !power_on. The PUSH_IDLE write is the only one that is not, so the controller's runtime-PM reference is then dropped without the link having been taken down. On glymur (Snapdragon X2 Elite) the consequence is not a warning. The SoC does not survive it: TrustZone force-stops the SOCCP and ADSP remote processors and the machine resets silently about 50 ms later, with no oops and no panic. On an ASUS Zenbook A16 (UX3607OA), whose eDP panel does not currently train, this reproduces without any compositor or GPU involvement: # eDP enable has already failed with "Failed link training (rc=-104)" echo 1 > /sys/class/graphics/fb0/blank [535.645455] === marker === [535.694833] qcom_q6v5_pas d00000.remoteproc: fatal error received: \ sys_m_smsm.c:512:TZ force stop [535.694875] remoteproc remoteproc0: crash detected in soccp: type fatal error [535.728857] qcom_q6v5_pas 6800000.remoteproc: fatal error received: \ sys_m_smsm.c:783:err fatal notification received from TZ Gate the PUSH_IDLE write on ->power_on so the disable path is consistent with the rest of the teardown. With this applied the same sequence is harmless and the machine stays up; without it, it resets every time. The unconditional write dates back to the original DP driver (c943b4948b58 ("drm/msm/dp: add displayPort driver support")), but the surrounding code has been restructured several times since, so no Fixes: tag is offered. Note that the eDP link-training failure that exposes this on the A16 is a separate problem in the glymur eDP PHY and is reported separately; this change is about not damaging the machine when training fails, for whatever reason. Tested on ASUS Zenbook A16 (UX3607OA), Snapdragon X2 Elite Extreme, on linux-next next-20260803 and next-20260807. The machine has since been running next-20260807 with this patch as its daily driver. Assisted-by: Anthropic:Claude-Opus-5 Signed-off-by: Jesse Casco Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/745167/ Link: https://lore.kernel.org/r/20260808171325.133041-1-jesse.casco@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index bc646d172abe..5d2ddf1808fe 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1458,6 +1458,20 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + /* + * If .atomic_enable() bailed out - link training failure is the common + * case - the mainlink was never brought up and ->power_on stayed false. + * Driving the PUSH_IDLE pattern into a controller that was never + * enabled times out, and .atomic_post_disable() then drops the + * controller's runtime-PM reference without tearing the PHY back down, + * because msm_dp_display_disable() returns early on !power_on. On + * glymur (Snapdragon X2 Elite) that combination is answered by a + * TrustZone-level SOCCP/ADSP force-stop and a silent SoC reset. + * There is nothing to push idle, so leave it alone. + */ + if (!dp->power_on) + return; + msm_dp_ctrl_push_idle(msm_dp_display->ctrl); } From 6fbbf1e152f34ad3913e4a6476680aba672c5068 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 21:16:24 +0800 Subject: [PATCH 0956/1317] drm/msm/adreno: fix autosuspend cleanup during teardown adreno_gpu_init() calls pm_runtime_use_autosuspend(), but adreno_gpu_cleanup() does not call the matching pm_runtime_dont_use_autosuspend() during teardown. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped and usage_count remains unbalanced. The documentation for pm_runtime_use_autosuspend() also notes that it is important to undo it with pm_runtime_dont_use_autosuspend() at driver exit time, unless runtime PM was initially enabled with devm_pm_runtime_enable(). Add the missing pm_runtime_dont_use_autosuspend() call to adreno_gpu_cleanup(). This issue was found by manual code inspection. Fixes: eeb754746b14 ("drm/msm/gpu: use pm-runtime") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/745110/ Link: https://lore.kernel.org/r/20260808131624.2854412-1-lgs201920130244@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index ca5e4e560cde..5832dc25d6bf 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -1261,6 +1261,8 @@ void adreno_gpu_cleanup(struct adreno_gpu *adreno_gpu) for (i = 0; i < ARRAY_SIZE(adreno_gpu->info->fw); i++) release_firmware(adreno_gpu->fw[i]); + pm_runtime_dont_use_autosuspend(&gpu->pdev->dev); + if (priv && pm_runtime_enabled(&priv->gpu_pdev->dev)) pm_runtime_disable(&priv->gpu_pdev->dev); From 58995b11dfb7dda095d23f22fa4dc79b923b5adf Mon Sep 17 00:00:00 2001 From: William Bright Date: Wed, 12 Aug 2026 11:05:52 +0100 Subject: [PATCH 0957/1317] drm/msm/dp: fix link bandwidth check when wide bus is enabled msm_dp_display_mode_valid() halves the pixel clock when either YUV420 or wide bus is in use, then uses that halved value both for the controller pixel clock limit and for the DP link bandwidth check. Only YUV420 halves the data crossing the link. Wide bus widens the internal DPU to DP interface to two pixels per clock, halving the controller clock. Every pixel is still transmitted, so the link bandwidth requirement remains. As a result, modes needing up to twice the available link bandwidth pass validation. On the IMDT QCS8550 SBC (rev5 with CYPD6125), where DP runs over USB-C alt mode where only two lanes are available, 3840x2160@60 was accepted despite needing 9.6 Gbps against the 8.64 Gbps the link can carry. Use a separate link pixel clock that is only halved for YUV420 for the bandwidth calculation, leaving the wide bus halving to apply solely to the controller pixel clock limit. With this, 4k@60 is correctly rejected and 4k@30 selected instead. Fixes: df9cf852ca30 ("drm/msm/dp: account for widebus and yuv420 during mode validation") Assisted-by: Claude:claude-opus-5 Signed-off-by: William Bright Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/746145/ Link: https://lore.kernel.org/r/20260812-msm-dp-link-bw-v1-1-b0e3ce1190be@imd-tec.com [DB: dropped useless comment] Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 5d2ddf1808fe..4dcbd9b99d06 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -756,6 +756,7 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, struct msm_dp_link_info *link_info; u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; int mode_pclk_khz = mode->clock; + int link_pclk_khz; bool is_yuv_420; if (!dp || !mode_pclk_khz || !dp->connector) { @@ -775,6 +776,8 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, if (is_yuv_420 && !msm_dp_display->panel->vsc_sdp_supported) return MODE_NO_420; + link_pclk_khz = is_yuv_420 ? mode_pclk_khz / 2 : mode_pclk_khz; + if (is_yuv_420 || msm_dp_display->wide_bus_supported) mode_pclk_khz /= 2; @@ -786,9 +789,9 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, mode_bpp = default_bpp; mode_bpp = msm_dp_panel_get_mode_bpp(msm_dp_display->panel, - mode_bpp, mode_pclk_khz); + mode_bpp, link_pclk_khz); - mode_rate_khz = mode_pclk_khz * mode_bpp; + mode_rate_khz = link_pclk_khz * mode_bpp; supported_rate_khz = link_info->num_lanes * link_info->rate * 8; if (mode_rate_khz > supported_rate_khz) From fd73f4a6659897191fa0d40695fe370925dd3780 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 13 Sep 2026 14:38:02 -0700 Subject: [PATCH 0958/1317] Linux 7.3-rc3 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 66654fa71655..0f1b80100b47 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc2 +EXTRAVERSION = -rc3 NAME = Baby Opossum Posse # *DOCUMENTATION* From a5b5cc909931572aec446e129c035b76b3f0c1fa Mon Sep 17 00:00:00 2001 From: Saim Shujah Date: Fri, 28 Aug 2026 11:54:40 +0500 Subject: [PATCH 0959/1317] drm/msm/dpu: clear pending peripheral flush state dpu_hw_ctl_clear_pending_flush() resets the cached per-block state after a flush transaction, but misses pending_periph_flush_mask. The peripheral flush updater accumulates interface bits in this mask. A later transaction which sets the top-level peripheral flush bit can write stale interface bits to CTL_PERIPH_FLUSH together with the current state. Peripheral flush support was added after the helper started clearing every individual pending flush mask. Clear the peripheral mask together with the other cached child masks. Fixes: 64f7b81f0358 ("drm/msm/dpu: add support of new peripheral flush mechanism") Cc: stable@vger.kernel.org Signed-off-by: Saim Shujah Patchwork: https://patchwork.freedesktop.org/patch/748968/ Link: https://lore.kernel.org/r/20260828065440.140410-1-saimzst@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c index 36a497f1d6c1..e8729fe1cfef 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c @@ -118,6 +118,7 @@ static inline void dpu_hw_ctl_clear_pending_flush(struct dpu_hw_ctl *ctx) ctx->pending_intf_flush_mask = 0; ctx->pending_wb_flush_mask = 0; ctx->pending_cwb_flush_mask = 0; + ctx->pending_periph_flush_mask = 0; ctx->pending_merge_3d_flush_mask = 0; ctx->pending_dsc_flush_mask = 0; ctx->pending_cdm_flush_mask = 0; From 26eb3d92c7a4d7adb1ae1740ca6e8e100b11d1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Larocque?= Date: Thu, 10 Sep 2026 12:44:25 -0400 Subject: [PATCH 0960/1317] Input: synaptics - disable InterTouch on ThinkPad T440p (board id 2722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo ThinkPad T440p (PNP ID LEN0036, board id 2722) has a Synaptics touchpad whose SMBus companion is not ready at boot and takes roughly 200 seconds to appear. During this window the touchpad and TrackPoint are completely unresponsive on approximately 50% of boots, making the machine unusable until the companion finally registers. The device is in the topbuttonpad_pnp_ids[] SMBus allowlist, so the kernel attempts to use SMBus/RMI4 mode by default. When the companion is not ready, psmouse_smbus_init() leaves breadcrumbs and returns -EAGAIN, the PS/2 fallback path is taken, but the device does not function properly until the companion appears and RMI4 takes over. Disable SMBus InterTouch for board id 2722 so the touchpad and TrackPoint work immediately via PS/2 from boot. Users can still force SMBus with psmouse.synaptics_intertouch=1 if needed. Tested-by: Raphaël Larocque Signed-off-by: Raphaël Larocque Link: https://patch.msgid.link/20260910164425.12832-1-rlarocque@disroot.org Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/synaptics.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index 2170bbe4c589..a8ce89d41faa 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -1838,6 +1838,14 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, return -ENXIO; } + + /* Disable intertouch on known-broken board revisions */ + if (info->board_id == 2722) { + psmouse_info(psmouse, + "Disabling intertouch for board id %u\n", + info->board_id); + return -ENXIO; + } } psmouse_info(psmouse, "Trying to set up SMBus access\n"); From 25e424eb4ae1a662d9c3573218d06ac32f797fc5 Mon Sep 17 00:00:00 2001 From: Chris Sommers Date: Mon, 7 Sep 2026 11:27:23 -0700 Subject: [PATCH 0961/1317] Input: i8042 - add quirk for Acer Aspire Go 15 AG15-42P On the Acer Aspire Go 15 (AG15-42P), the internal keyboard drops out ~5 seconds after boot on both Linux and Linux-LTS kernels. Keystrokes on the built-in keyboard stop registering while the trackpad and external keyboards remain functional. Testing confirms that booting with the i8042.reset kernel parameter resolves the issue and keeps the internal keyboard responsive. Add SERIO_QUIRK_RESET_ALWAYS to i8042_dmi_quirk_table for the Acer Aspire AG15-42P to automatically apply this quirk on boot. Signed-off-by: Chris Sommers Link: https://patch.msgid.link/20260907182723.2709981-1-chris.sommers@icloud.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/serio/i8042-acpipnpio.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 9ecb0eed48c4..54eb3687000a 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -259,6 +259,13 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { }, .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS) }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire AG15-42P"), + }, + .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS) + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), From 88c4aff39452d7f0ab59f13730182a43bc7257c6 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 13:10:46 +0100 Subject: [PATCH 0962/1317] ASoC: wm_adsp: Firmware search progress log should not look like an error In wm_adsp_request_firmware_file() only log the "Failed to request FILENAME" message when there is a real error (not when the file is missing). Add a new debug message to log the sequence of filenames tried during the file search. People have enabled debug messages, seen the "Failed to request" messages that are only logging the normal file search sequence, and reported them as errors. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910121047.1592541-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 90c24c4b318e..b8f3035af3ba 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -775,9 +775,12 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, s++; } + adsp_dbg(dsp, "Try '%s'\n", fw->filename); ret = wm_adsp_firmware_request(&fw->firmware, fw->filename, cs_dsp->dev); if (ret < 0) { - adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); + if (ret != -ENOENT) + adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); + kfree(fw->filename); fw->filename = NULL; if (ret != -ENOENT) From d56fe35c1bce1f547eb600695f60c139a508ff6e Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Wed, 9 Sep 2026 16:54:49 +0800 Subject: [PATCH 0963/1317] ASoC: rt712-sdca: reconfigure PLL2 to fix calibration time-out Add pll2 reconfiguration sequence in order to fix calibration time-out issue and to support 24.576MHz MCLK on specific platforms. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260909085449.862350-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-sdw.c | 8 +++ sound/soc/codecs/rt712-sdca.c | 86 +++++++++++++++++++++++++++++-- sound/soc/codecs/rt712-sdca.h | 18 +++++++ 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index c50e74e20a88..edba0367d9ce 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -18,12 +18,16 @@ static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) { switch (reg) { + case 0x004d: case 0x201a ... 0x201f: case 0x2029 ... 0x202a: case 0x202d ... 0x2034: case 0x2230 ... 0x2232: case 0x2f01 ... 0x2f0a: case 0x2f35 ... 0x2f36: + case 0x2f3a: + case 0x2f3d: + case 0x2f41: case 0x2f50: case 0x2f54: case 0x2f58 ... 0x2f5d: @@ -48,6 +52,7 @@ static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) { switch (reg) { + case 0x004d: case 0x201b: case 0x201c: case 0x201d: @@ -56,6 +61,9 @@ static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) case 0x2230: case 0x2f01: case 0x2f35: + case 0x2f3a: + case 0x2f3d: + case 0x2f41: case 0x320c: case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT_GE49, RT712_SDCA_CTL_DETECTED_MODE, 0): case SDW_SDCA_CTL(FUNC_NUM_HID, RT712_SDCA_ENT_HID01, RT712_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index eda87eb9ab66..38052cb19790 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -73,14 +73,57 @@ static int rt712_sdca_index_update_bits(struct rt712_sdca_priv *rt712, return rt712_sdca_index_write(rt712, nid, reg, tmp); } +static void rt712_sdca_clk_patch(struct rt712_sdca_priv *rt712) +{ + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); + usleep_range(1000, 1100); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); + usleep_range(1000, 1100); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0000); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0017); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0000); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0001); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); + usleep_range(1000, 1100); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0081); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0080); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0000); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0010); + usleep_range(1000, 1100); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); +} + +static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) +{ + rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0x0800, + 0x0000); + rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0xf000, + 0x0000); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); + rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x0c, 0xc000, + 0xc000); + rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x00, 0xc000, + 0xc000); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); + usleep_range(1000, 1100); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); + usleep_range(1000, 1100); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); +} + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) { unsigned int val, loop_rc = 0, loop_dc = 0; struct device *dev; struct regmap *regmap = rt712->regmap; + unsigned int clk_base; int chk_cnt = 100; int ret = 0; + regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); + mutex_lock(&rt712->calibrate_mutex); dev = regmap_get_device(regmap); @@ -109,8 +152,35 @@ static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) if (ret < 0) goto _cali_fail_; } - if (loop_dc == chk_cnt) - dev_err(dev, "%s, calibration time-out!\n", __func__); + + if (loop_dc == chk_cnt) { + if (clk_base == RT712_CLK_FREQ_24_576MHZ) { + rt712_sdca_clk_patch(rt712); + rt712_sdca_clk_patch2(rt712); + } + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_FSM_CTL, 0x4100); + rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, + RT712_DAC_DC_CALI_CTL1, 0x7883); + rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, + RT712_DAC_DC_CALI_CTL1, 0xf893); + rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, + RT712_DAC_DC_CALI_CTL1, &val); + + for (loop_dc = 0; loop_dc < chk_cnt && + (val & RT712_DAC_DC_CALI_TRIGGER); loop_dc++) { + usleep_range(10000, 11000); + ret = rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, + RT712_DAC_DC_CALI_CTL1, &val); + + if (ret < 0) + goto _cali_fail_; + } + + if (loop_dc == chk_cnt) + dev_err(dev, "%s, calibration time-out!\n", __func__); + else + dev_dbg(dev, "%s, calibration success!\n", __func__); + } if (loop_dc == chk_cnt || loop_rc == chk_cnt) ret = -ETIMEDOUT; @@ -1759,9 +1829,13 @@ static void rt712_sdca_va_io_init(struct rt712_sdca_priv *rt712) static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) { - int ret = 0; unsigned int jack_func_status, mic_func_status, amp_func_status; struct device *dev = &rt712->slave->dev; + unsigned int clk_base; + int ret = 0; + + regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); + dev_dbg(dev, "%s clk_base=%x", __func__, clk_base); regmap_read(rt712->regmap, SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT0, RT712_SDCA_CTL_FUNC_STATUS, 0), &jack_func_status); @@ -1773,6 +1847,12 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) __func__, jack_func_status, mic_func_status, amp_func_status); rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); + + if (clk_base == RT712_CLK_FREQ_24_576MHZ) { + rt712_sdca_clk_patch(rt712); + rt712_sdca_clk_patch2(rt712); + } + /* DMIC */ if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index 46740281a5c1..6229fe341bb5 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -162,6 +162,16 @@ struct rt712_dmic_kctrl_priv { #define RT712_EAPD_HIGH 0x2 #define RT712_EAPD_LOW 0x0 +/* SDW clock root frequency */ +#define RT712_SDW_ROOT_CLK 0x004d +#define RT712_SDW_SCALE_CLK0 0x0062 +#define RT712_SDW_SCALE_CLK1 0x0072 + +/* PLL2 config */ +#define RT712_PLL2_CONF1 0x2f3a +#define RT712_PLL2_CONF2 0x2f3d +#define RT712_PLL2_CONF3 0x2f41 + /* RC Calibration register */ #define RT712_RC_CAL 0x3201 @@ -254,6 +264,14 @@ enum rt712_sdca_version { RT712_VB, }; +enum { + RT712_CLK_FREQ_19_2_MHZ = 1, + RT712_CLK_FREQ_24MHZ = 2, + RT712_CLK_FREQ_24_576MHZ = 3, + RT712_CLK_FREQ_22_5792MHZ = 4, +}; + + int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave); int rt712_sdca_init(struct device *dev, struct regmap *regmap, struct regmap *mbq_regmap, struct sdw_slave *slave); From 4d855d747521505b54457c96bc73577bf74b2374 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:44:56 +0100 Subject: [PATCH 0964/1317] ASoC: Rename snd_soc_dai_link_ch_map.ch_mask to cpu_ch_mask Rename the ch_mask member of snd_soc_dai_link_ch_map to cpu_ch_mask, as that is what it is used for. The CPU and codec channel masks are not necessarily the same, and are quite likely different. SoundWire and I2S/TDM both support assigning different sample slots to each codec, so for example channel 0 on each codec could map to different channels at the CPU. So it's quite normal that the channel mask at the CPU end is different for each codec, but the codec channel masks are the same for each codec. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc.h | 2 +- sound/soc/sdw_utils/soc_sdw_utils.c | 2 +- sound/soc/soc-pcm.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index f46b2bc2a022..94c9b75e27e3 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -699,7 +699,7 @@ struct snd_soc_dai_link_component { struct snd_soc_dai_link_ch_map { unsigned int cpu; unsigned int codec; - unsigned int ch_mask; + unsigned int cpu_ch_mask; }; struct snd_soc_dai_link { diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index a66dcc02fb59..f0cebcbf6288 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1596,7 +1596,7 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, * ASoC will set the corresponding channel numbers for each cpu dai. */ for_each_link_ch_maps(rtd->dai_link, i, ch_maps) - ch_maps->ch_mask = ch_mask << (i * step); + ch_maps->cpu_ch_mask = ch_mask << (i * step); return 0; } diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 0e49290a8c90..cb64ced21149 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1264,7 +1264,7 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, */ for_each_rtd_ch_maps(rtd, j, ch_maps) if (ch_maps->cpu == i) - ch_mask |= ch_maps->ch_mask; + ch_mask |= ch_maps->cpu_ch_mask; /* fixup cpu channel number */ if (ch_mask) From 88b14c0d0bab5c0f3e7c641f274e3c70210c0e36 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:44:57 +0100 Subject: [PATCH 0965/1317] ASoC: Add codec_ch_mask to snd_soc_dai_link_ch_map Add a codec_ch_mask member to snd_soc_dai_link_ch_map. The CPU and codec channel masks are not necessarily the same, and are quite likely different. SoundWire and I2S/TDM both support assigning different sample slots to each codec, so for example channel 0 on each codec could map to different channels at the CPU. It is also possible for one TX channel to map to multiple RX channels. So it isn't _always_ safe to assume that the total number of set bits in the CPU ch_mask is the same as the total number of enabled channels on the codec. For example consider this mapping on a capture stream: CPU0 CODEC0 cpu_ch_mask = 0x03 CPU1 CODEC0 cpu_ch_mask = 0x03 This could be either four TX channels on the codec split across two receiving CPUs, or two TX channels on the codec duplicated to two CPUs. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-3-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 94c9b75e27e3..5afc34b147b5 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -700,6 +700,7 @@ struct snd_soc_dai_link_ch_map { unsigned int cpu; unsigned int codec; unsigned int cpu_ch_mask; + unsigned int codec_ch_mask; }; struct snd_soc_dai_link { From 6b382bdfe26a2232091bf743e454e6794295783e Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:44:58 +0100 Subject: [PATCH 0966/1317] ASoC: soc-pcm: Apply snd_soc_dai_link_ch_map.codec_ch_mask to codec params In __soc_pcm_hw_params() if there is a snd_soc_dai_link_ch_map with non-zero codec_ch_mask, use that channel mask to restrict which channels are enabled on the codec. But only if there isn't a TDM mask. It is possible that a snd_soc_dai_link_ch_map could include the same codec multiple times on different CPUs so the for_each_rtd_ch_maps() loop accumulates the channel masks for all entries of that codec. If a TDM mask was also set, it takes priority and is used instead of any possible snd_soc_dai_link_ch_map entries. (They cannot be ANDed together because the bit positions are indicating different things: TDM is a bit for each TDM slot, codec_ch_mask is a bit for each codec channel.) This fixes a problem of incorrect TX channels enabled on the codec when multiple codecs are aggregated on a single capture link. For example: - Two CPUs with six 4-channel codecs. - The machine driver chooses to assign one channel from each codec to one channel on the CPU - But the codec hw_params() would be passed a channel count of 6, which (a) is more channels than the codec has and (b) allows enabling channels that should not be driving the audio bus. Fixes: ac950278b087 ("ASoC: add N cpus to M codecs dai link support") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-4-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/soc-pcm.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index cb64ced21149..3137c091bdb8 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1206,7 +1206,9 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, goto out; for_each_rtd_codec_dais(rtd, i, codec_dai) { - unsigned int tdm_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream); + unsigned int ch_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream); + struct snd_soc_dai_link_ch_map *ch_maps; + int j; /* * Skip CODECs which don't support the current stream type, @@ -1228,9 +1230,15 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, /* copy params for each codec */ tmp_params = *params; - /* fixup params based on TDM slot masks */ - if (tdm_mask) - soc_pcm_codec_params_fixup(&tmp_params, tdm_mask); + /* fixup params based on TDM or ch_map masks */ + if (!ch_mask) { + for_each_rtd_ch_maps(rtd, j, ch_maps) + if (ch_maps->codec == i) + ch_mask |= ch_maps->codec_ch_mask; + } + + if (ch_mask) + soc_pcm_codec_params_fixup(&tmp_params, ch_mask); ret = snd_soc_dai_hw_params(codec_dai, substream, &tmp_params); From 290845e151cd4e307cc1f25319583aaceb4eeb30 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:44:59 +0100 Subject: [PATCH 0967/1317] ASoC: sdw_utils: Set snd_soc_dai_link_ch_map.codec_ch_mask for capture In asoc_sdw_hw_params() set the codec_ch_mask member of struct snd_soc_dai_link_ch_map for capture streams. ASoC will then pass the correct number of channels to each codec hw_params(). This prevents trying to enable more channels on the codec DP than have been allocated bitslots in the SoundWire frame, which would cause bus clash errors. In theory codec_ch_mask could also be set for playback streams, but for those the CPU is the only sender so there is no risk of bus clash. For playback streams codec_ch_mask is set to 0 to preserve the existing behavior and avoid introducing bugs. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-5-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index f0cebcbf6288..293574a6ccca 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1565,7 +1565,7 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai_link_ch_map *ch_maps; int ch = params_channels(params); - unsigned int ch_mask; + unsigned int cpu_ch_mask, codec_ch_mask; int num_codecs; int step; int i; @@ -1575,8 +1575,9 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, /* Identical data will be sent to all codecs in playback */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - ch_mask = GENMASK(ch - 1, 0); + cpu_ch_mask = GENMASK(ch - 1, 0); step = 0; + codec_ch_mask = 0; } else { num_codecs = rtd->dai_link->num_codecs; @@ -1586,17 +1587,24 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - ch_mask = GENMASK(ch / num_codecs - 1, 0); - step = hweight_long(ch_mask); + cpu_ch_mask = GENMASK(ch / num_codecs - 1, 0); + step = hweight_long(cpu_ch_mask); + codec_ch_mask = cpu_ch_mask; } /* * The captured data will be combined from each cpu DAI if the dai * link has more than one codec DAIs. Set codec channel mask and * ASoC will set the corresponding channel numbers for each cpu dai. + * + * sdw_stream_add_slave() assigns different payload offsets to each + * codec in a capture stream, so that the same channels on each + * codec map to different channels on the CPU. */ - for_each_link_ch_maps(rtd->dai_link, i, ch_maps) - ch_maps->cpu_ch_mask = ch_mask << (i * step); + for_each_link_ch_maps(rtd->dai_link, i, ch_maps) { + ch_maps->cpu_ch_mask = cpu_ch_mask << (i * step); + ch_maps->codec_ch_mask = codec_ch_mask; + } return 0; } From b5b00a57868b1eabdf90a29a51f0eb732c609f3a Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:45:00 +0100 Subject: [PATCH 0968/1317] ASoC: sdw_utils: cs_amp: Delete bogus and incorrect capture channel fixup Delete the asoc_sdw_cs_spk_feedback_rtd_init(). This is not needed now that the ASoC bug it was working around has been fixed. And it was broken anyway because it didn't match the way the core SoundWire code mapped codec channels to frame bitslots. This code was added to avoid a problem where multiple codec DP outputs were mapped to the same SoundWire frame bit slot. This would allow a user to break the SoundWire bus just by enabling mixer outputs using ALSA controls. As no production system has used the capture stream, this workaround was of little consequence and the problem of conflicting DP mappings was not investigated. The ASoC bug that enabled too many channels on each codec has now been fixed. So this workaround can be completely deleted. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc_sdw_utils.h | 2 -- sound/soc/sdw_utils/soc_sdw_cs_amp.c | 46 ---------------------------- sound/soc/sdw_utils/soc_sdw_utils.c | 4 --- 3 files changed, 52 deletions(-) diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 9b28e9aef4f1..9fbb69b9052d 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -250,8 +250,6 @@ int asoc_sdw_cs_amp_init(struct snd_soc_card *card, struct snd_soc_dai_link *dai_links, struct asoc_sdw_codec_info *info, bool playback); -int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, - struct snd_soc_dai *dai); int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix); /* MAXIM codec support */ diff --git a/sound/soc/sdw_utils/soc_sdw_cs_amp.c b/sound/soc/sdw_utils/soc_sdw_cs_amp.c index 325ab7230481..6e21ef8f87e2 100644 --- a/sound/soc/sdw_utils/soc_sdw_cs_amp.c +++ b/sound/soc/sdw_utils/soc_sdw_cs_amp.c @@ -14,7 +14,6 @@ #include #include -#define CS_AMP_CHANNELS_PER_AMP 4 #define CS35L56_SPK_VOLUME_0DB 400 /* 0dB Max */ int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix) @@ -64,51 +63,6 @@ int asoc_sdw_cs_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai } EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_rtd_init, "SND_SOC_SDW_UTILS"); -int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) -{ - const struct snd_soc_dai_link *dai_link = rtd->dai_link; - const struct snd_soc_dai_link_ch_map *ch_map; - const struct snd_soc_dai_link_component *codec_dlc; - struct snd_soc_dai *codec_dai; - u8 ch_slot[8] = {}; - unsigned int amps_per_bus, ch_per_amp, mask; - int i, ret; - - WARN_ON(dai_link->num_cpus > ARRAY_SIZE(ch_slot)); - - /* - * CS35L56 has 4 TX channels. When the capture is aggregated the - * same bus slots will be allocated to all the amps on a bus. Only - * one amp on that bus can be transmitting in each slot so divide - * the available 4 slots between all the amps on a bus. - */ - amps_per_bus = dai_link->num_codecs / dai_link->num_cpus; - if ((amps_per_bus == 0) || (amps_per_bus > CS_AMP_CHANNELS_PER_AMP)) { - dev_err(rtd->card->dev, "Illegal num_codecs:%u / num_cpus:%u\n", - dai_link->num_codecs, dai_link->num_cpus); - return -EINVAL; - } - - ch_per_amp = CS_AMP_CHANNELS_PER_AMP / amps_per_bus; - - for_each_rtd_ch_maps(rtd, i, ch_map) { - codec_dlc = snd_soc_link_to_codec(rtd->dai_link, i); - codec_dai = snd_soc_find_dai(codec_dlc); - mask = GENMASK(ch_per_amp - 1, 0) << ch_slot[ch_map->cpu]; - - ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, mask, 4, 32); - if (ret < 0) { - dev_err(rtd->card->dev, "Failed to set TDM slot:%d\n", ret); - return ret; - } - - ch_slot[ch_map->cpu] += ch_per_amp; - } - - return 0; -} -EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_feedback_rtd_init, "SND_SOC_SDW_UTILS"); - int asoc_sdw_cs_amp_init(struct snd_soc_card *card, struct snd_soc_dai_link *dai_links, struct asoc_sdw_codec_info *info, diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 293574a6ccca..d2eeef4931c6 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -811,7 +811,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_name = "cs35l56-sdw1c", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, - .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, }, }, .dai_num = 2, @@ -840,7 +839,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_name = "cs35l56-sdw1c", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, - .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, }, }, .dai_num = 2, @@ -869,7 +867,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_name = "cs35l56-sdw1c", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, - .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, }, }, .dai_num = 2, @@ -898,7 +895,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_name = "cs35l56-sdw1c", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, - .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, }, }, .dai_num = 2, From 576725ded009f09a28da19852f7edf62dbc5f94c Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Fri, 11 Sep 2026 16:19:32 +0800 Subject: [PATCH 0969/1317] ASoC: Intel: sof_es8336: Add a quirk for Huawei Matebook B3-420 Add DMI entry for Huawei Matebook B3-420 (BDZ-WXX9) with HEADPHONE_GPIO and HEADSET_MIC1 quirks. Similar to Huawei Matebook D (BOD-WXX9). On the same machine,audio routing between speakers and headphones works correctly when running Windows with the Huawei audio driver. However, after reinstalling Linux, both the speakers and headphones output sound simultaneously,indicating that the amplifier enable GPIOs are not being toggled correctly to separate the two outputs. Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260911081932.2605407-1-aichao@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_es8336.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c index 9b016136c639..f1e62c2e79fe 100644 --- a/sound/soc/intel/boards/sof_es8336.c +++ b/sound/soc/intel/boards/sof_es8336.c @@ -360,6 +360,15 @@ static const struct dmi_system_id sof_es8336_quirk_table[] = { .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | SOC_ES8336_HEADSET_MIC1) }, + { + .callback = sof_es8336_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), + DMI_MATCH(DMI_PRODUCT_NAME, "NDZ-WXX9"), + }, + .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | + SOC_ES8336_HEADSET_MIC1) + }, {} }; From 930a7312c946bf4731721cadd82bb9a2ada496ca Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Wed, 9 Sep 2026 16:08:25 +0200 Subject: [PATCH 0970/1317] spi: spi-qpic-snand: avoid writing QPIC_EBI2_ECC_BUF_CFG register The description of commit bfb34eced559 ("mtd: rawnand: qcom: avoid writing to obsolete register") says this: "QPIC_EBI2_ECC_BUF_CFG register got obsolete from QPIC V2.0 onwards. Avoid writing this register if QPIC version is V2.0 or newer." Although the referenced commit is related to the 'qcom-nandc' driver, however the hardware supported by the current driver is also based on QPIC v2.0 so we should avoid writing that register here as well. Remove the register writing code to avoid undefined behaviour. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Signed-off-by: Gabor Juhos Reviewed-by: Md Sadre Alam Link: https://patch.msgid.link/20260909-qpic-snand-avoid-ebi2-reg-write-v1-1-9b1b1466cc75@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 61b1f2eb19ce..05efe6313b7f 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -765,8 +765,6 @@ static int qcom_spi_read_cw_raw(struct qcom_nand_controller *snandc, u8 *data_bu qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, NAND_ERASED_CW_DETECT_CFG, 1, 0); qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, @@ -1104,8 +1102,6 @@ static void qcom_spi_config_page_write(struct qcom_nand_controller *snandc) { qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, - 1, NAND_BAM_NEXT_SGL); } static void qcom_spi_config_cw_write(struct qcom_nand_controller *snandc) From ed22ad5fdbdbf9b4cb4ad3003f60314b5a5eb89d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Sep 2026 11:39:33 +0200 Subject: [PATCH 0971/1317] Input: soc_button_array - fix MS Surface Pro 11 probe failure On the MS Surface Pro 11 soc_button_array probing races with the GPIO driver probing. If soc_button_array wins the race then gpiod_get() returns EPROBE_DEFER, which should normally take care of retrying later, but the soc_button_array code deliberately ignores EPROBE_DEFER causing it to fail its probe() which causes the volume and power buttons to now work. The ignoring of EPROBE_DEFER is there to deal with a problem specific to older Bay Trail (BYT) and Cherry Trail (CHT) tablets which often use this driver. Modify the error handling to only ignore EPROBE_DEFER on BYT and CHT platforms and propagate EPROBE_DEFER normally on other platforms. Fixes: bcf059578980 ("Input: soc_button_array - partial revert of support for newer surface devices") Cc: stable@vger.kernel.org Reported-by: Sergey Lebedev Closes: https://lore.kernel.org/lkml/20260830141355.55898-1-lsa.uz@pm.me/ Signed-off-by: Hans de Goede Tested-by: Sergey Lebedev Link: https://patch.msgid.link/20260909093934.29411-1-johannes.goede@oss.qualcomm.com Signed-off-by: Dmitry Torokhov --- drivers/input/misc/soc_button_array.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index b8cad415c62c..264c41f80d2b 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -16,6 +16,7 @@ #include #include #include +#include #include static bool use_low_level_irq; @@ -160,7 +161,7 @@ soc_button_device_create(struct platform_device *pdev, struct gpio_keys_platform_data *gpio_keys_pdata; const struct dmi_system_id *dmi_id; int invalid_acpi_index = -1; - int error, gpio, irq; + int error, gpio, irq = 0; int n_buttons = 0; for (info = button_info; info->name; info++) @@ -191,8 +192,9 @@ soc_button_device_create(struct platform_device *pdev, error = soc_button_lookup_gpio(&pdev->dev, info->acpi_index, &gpio, &irq); if (error || irq < 0) { /* - * Skip GPIO if not present. Note we deliberately - * ignore -EPROBE_DEFER errors here. On some devices + * Propagate -EPROBE_DEFER, skip button on other errors. + * + * -EPROBE_DEFER is ignored on Bay & Cherry Trail. Here * Intel is using so called virtual GPIOs which are not * GPIOs at all but some way for AML code to check some * random status bits without need a custom opregion. @@ -201,6 +203,12 @@ soc_button_device_create(struct platform_device *pdev, * we do not have a driver for these so they will never * show up, therefore we ignore -EPROBE_DEFER. */ + if ((error == -EPROBE_DEFER || irq == -EPROBE_DEFER) && + !(soc_intel_is_byt() || soc_intel_is_cht())) { + error = -EPROBE_DEFER; + goto err_free_mem; + } + continue; } From fb5022278b6ea7f1838e3ef78028d5d5e3375f65 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Sep 2026 11:39:34 +0200 Subject: [PATCH 0972/1317] Input: soc_button_array - check btns_desc->package.count Check that btns_desc->package.count is not 0 before accessing btns_desc->package.elements[0]. Fixes: 4c3362f44980 ("Input: soc_button_array - add support for ACPI 6.0 Generic Button Device") Cc: stable@vger.kernel.org Reported-by: Shashiko Closes: https://lore.kernel.org/linux-input/20260909091440.3384C1F00A3A@smtp.kernel.org/ Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260909093934.29411-2-johannes.goede@oss.qualcomm.com Signed-off-by: Dmitry Torokhov --- drivers/input/misc/soc_button_array.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index 264c41f80d2b..f08e29af531f 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -377,7 +377,7 @@ static struct soc_button_info *soc_button_get_button_info(struct device *dev) } } - if (!btns_desc) { + if (!btns_desc || !btns_desc->package.count) { dev_err(dev, "ACPI Button Descriptors not found\n"); button_info = ERR_PTR(-ENODEV); goto out; From ea48250a0dc9708c198e8665391973bfc6c86fd2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 13 Sep 2026 17:19:48 -0700 Subject: [PATCH 0973/1317] Input: document that no new LED codes should be added Add a comment to input-event-codes.h clarifying that no new LED definitions should be added to the input subsystem. The existing LED_* definitions are legacy and grandfathered for backwards compatibility with userspace via evdev. Any new LED indicators should instead use the dedicated LED subsystem. Signed-off-by: Dmitry Torokhov --- include/uapi/linux/input-event-codes.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 3528168f7c6d..4217950e5d16 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -970,6 +970,12 @@ /* * LEDs + * + * Do not add any new LED definitions to the input subsystem. The existing + * definitions are legacy and grandfathered for backwards compatibility with + * userspace (via evdev). Any new LEDs should be implemented using the + * LED subsystem (struct led_classdev). The input core provides a bridge to + * the LED subsystem in drivers/input/input-leds.c. */ #define LED_NUML 0x00 From 7bc369cb3d3f3656eb77285628ee264264d28ad4 Mon Sep 17 00:00:00 2001 From: Jeremy Nyberg Date: Sun, 13 Sep 2026 17:43:02 -0700 Subject: [PATCH 0974/1317] Input: xpad - fix PDP Marvel Xbox 360 controller The PDP Marvel Xbox 360 controller with USB ID 0e6f:0147 is incorrectly classified as an Xbox One controller. With the current XTYPE_XBOXONE classification, the controller is detected but produces no input, while its four player LEDs continue blinking indefinitely. Classify USB ID 0e6f:0147 as an Xbox 360 controller instead. Tested on a PDP Marvel Xbox 360 controller with USB ID 0e6f:0147. All inputs register correctly and the player LED indicates the current player. Fixes: c225370e01b8 ("Input: xpad - sync supported devices with 360Controller") Cc: stable@vger.kernel.org Signed-off-by: Jeremy Nyberg Link: https://patch.msgid.link/20260910071627.236014-1-SlickStretch3.0@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 2da0b7f1722a..b7a9a940d250 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -215,7 +215,7 @@ static const struct xpad_device { { 0x0e6f, 0x0139, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x013a, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x0146, "Rock Candy Wired Controller for Xbox One", 0, XTYPE_XBOXONE }, - { 0x0e6f, 0x0147, "PDP Marvel Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0147, "PDP Marvel Xbox 360 Controller", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x015c, "PDP Xbox One Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, { 0x0e6f, 0x015d, "PDP Mirror's Edge Official Wired Controller for Xbox One", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x0161, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, From cba76c0f47af1a389d718c5bb69e75cbd67bba98 Mon Sep 17 00:00:00 2001 From: Roberts Kursitis Date: Sun, 6 Sep 2026 17:30:40 +0300 Subject: [PATCH 0975/1317] Input: xpad - add support for Azeron devices Azeron controllers (Cyro, Cyborg, Classic/Compact, Cyro Lefty, Cyborg II and Keyzen) present a standard Xbox 360 controller interface, so they work with the existing xpad driver once their USB IDs are added. The 0x16d0 vendor ID is a shared block, but this is safe because xpad only binds interfaces that match the Xbox 360 signature. Tested with an Azeron Keyzen. Signed-off-by: Roberts Kursitis Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260906143040.162418-1-roberts.kursitis@azeron.eu Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index b7a9a940d250..8e4e06b53c2a 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -292,6 +292,12 @@ static const struct xpad_device { { 0x1689, 0xfd00, "Razer Onza Tournament Edition", 0, XTYPE_XBOX360 }, { 0x1689, 0xfd01, "Razer Onza Classic Edition", 0, XTYPE_XBOX360 }, { 0x1689, 0xfe00, "Razer Sabertooth", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1103, "Azeron Cyro", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x113c, "Azeron Cyborg", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1192, "Azeron Classic/Compact", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1212, "Azeron Cyro Lefty", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x12f7, "Azeron Cyborg II", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x13ea, "Azeron Keyzen", 0, XTYPE_XBOX360 }, { 0x17ef, 0x6182, "Lenovo Legion Controller for Windows", 0, XTYPE_XBOX360 }, { 0x1949, 0x041a, "Amazon Game Controller", 0, XTYPE_XBOX360 }, { 0x1a86, 0xe310, "Legion Go S", 0, XTYPE_XBOX360 }, @@ -534,6 +540,7 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOX360_VENDOR(0x15e4), /* Numark Xbox 360 controllers */ XPAD_XBOX360_VENDOR(0x162e), /* Joytech Xbox 360 controllers */ XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */ + XPAD_XBOX360_VENDOR(0x16d0), /* Azeron controllers */ XPAD_XBOX360_VENDOR(0x17ef), /* Lenovo */ XPAD_XBOX360_VENDOR(0x1949), /* Amazon controllers */ XPAD_XBOX360_VENDOR(0x1a86), /* Nanjing Qinheng Microelectronics (WCH) */ From a52ae68a937efc353251aec27fc995ff66cbe1ca Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:40:04 +0800 Subject: [PATCH 0976/1317] Input: eeti_ts - publish the OF module alias The EETI driver matches eeti,exc3000-i2c Device Tree clients, but only publishes the legacy eeti_ts I2C ID. The I2C core emits an OF modalias for a Device Tree client. Publish the existing OF match table within its CONFIG_OF guard. Fixes: e32d7f1b246c ("Input: eeti - add device tree matching table") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905134004.66336-1-hppiscas@163.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/eeti_ts.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c index b12602bc368e..b870a939508b 100644 --- a/drivers/input/touchscreen/eeti_ts.c +++ b/drivers/input/touchscreen/eeti_ts.c @@ -276,6 +276,7 @@ static const struct of_device_id of_eeti_ts_match[] = { { .compatible = "eeti,exc3000-i2c", }, { } }; +MODULE_DEVICE_TABLE(of, of_eeti_ts_match); #endif static struct i2c_driver eeti_ts_driver = { From 45b0037899704caf9078be2be4de69361ca7d933 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 12:20:38 +0200 Subject: [PATCH 0977/1317] Input: trackpoint - fix the inertia attribute name in the ABI document The attribute is created as "inertia" (TRACKPOINT_INT_ATTR(inertia, ...) in drivers/input/mouse/trackpoint.c); the ABI file spells the path "intertia". The description below it already says inertia. Fix the spelling. Fixes: aebb47d4e7a9 ("Input: trackpoint: document sysfs interface") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260905102038.42882-1-kmehltretter@gmail.com Signed-off-by: Dmitry Torokhov --- Documentation/ABI/testing/sysfs-devices-platform-trackpoint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-devices-platform-trackpoint b/Documentation/ABI/testing/sysfs-devices-platform-trackpoint index df11901a6b3d..7954434b0434 100644 --- a/Documentation/ABI/testing/sysfs-devices-platform-trackpoint +++ b/Documentation/ABI/testing/sysfs-devices-platform-trackpoint @@ -5,7 +5,7 @@ Contact: linux-input@vger.kernel.org Description: (RW) Trackpoint sensitivity. -What: /sys/devices/platform/i8042/.../intertia +What: /sys/devices/platform/i8042/.../inertia Date: Aug, 2005 KernelVersion: 2.6.14 Contact: linux-input@vger.kernel.org From 7e61560628d17ea6b1d8ee370f6d42694cff8758 Mon Sep 17 00:00:00 2001 From: Ulises Mendez Martinez Date: Fri, 4 Sep 2026 15:07:09 +0000 Subject: [PATCH 0978/1317] objtool: Validate disassembler headers in libopcodes probe commit 3f2de814c059 ("objtool: Fix libopcodes linking with static libraries") tested for libopcodes availability by linking a test snippet with a forward declaration of disassemble_init_for_target(). However, testing symbol linkage with an extern declaration only verifies the presence of the library (.so/.a) and bypasses checking for development headers (binutils-dev). On systems where libopcodes is present without development headers installed, the probe succeeds, enabling BUILD_DISAS. Subsequent compilation of objtool then fails: fatal error: 'bfd.h' file not found 113 | #include Additionally, the probe invokes $(HOSTCC) without $(HOSTCFLAGS), ignoring any sysroot or include flags specified for the host compiler. Fix this by including and directly in the test snippet, passing $(HOSTCFLAGS) so host compiler options are respected, and defining PACKAGE="objtool" to satisfy the configuration check in . Fixes: 3f2de814c059 ("objtool: Fix libopcodes linking with static libraries") Fixes: 436326bc525d ("objtool: fix build failure due to missing libopcodes check") Reported-by: Alice Ryhl Assisted-by: Antigravity:Gemini-Next Signed-off-by: Ulises Mendez Martinez Link: https://patch.msgid.link/20260904150710.2997558-1-umendez@google.com Signed-off-by: Josh Poimboeuf --- tools/objtool/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index a4484fd22a96..4cc2e756af84 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -89,9 +89,11 @@ LIBOPCODES_LIBS := $(shell \ "-lopcodes -lbfd" \ "-lopcodes -lbfd -liberty" \ "-lopcodes -lbfd -liberty -lz"; do \ - echo 'extern void disassemble_init_for_target(void *);' \ - 'int main(void) { disassemble_init_for_target(0); return 0; }' | \ - $(HOSTCC) -xc - -o /dev/null $$libs 2>/dev/null && \ + printf '%s\n' \ + '$(pound)include ' \ + '$(pound)include ' \ + 'int main(void) { disassemble_init_for_target(0); return 0; }' | \ + $(HOSTCC) $(HOSTCFLAGS) -DPACKAGE='"objtool"' -xc - -o /dev/null $$libs 2>/dev/null && \ echo "$$libs" && break; \ done) From 55fc280e951ab2b39f3dcb640edc1b1eebc6d173 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Mon, 31 Aug 2026 14:43:51 -0500 Subject: [PATCH 0979/1317] Input: tsc2007 - read "ti,poll-period" as u32 The "ti,poll-period" property is documented as a normal uint32 cell. The driver used a u64 helper, which makes the helper type disagree with the schema even though the stored value is still small. Read "ti,poll-period" with the u32 helper matching the documented DT cell size. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260831194352.1185860-1-robh@kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/tsc2007_core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c index e4d7da0f4434..e2f49b37e18c 100644 --- a/drivers/input/touchscreen/tsc2007_core.c +++ b/drivers/input/touchscreen/tsc2007_core.c @@ -221,7 +221,6 @@ static int tsc2007_get_pendown_state_gpio(struct device *dev) static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) { u32 val32; - u64 val64; if (!device_property_read_u32(dev, "ti,max-rt", &val32)) ts->max_rt = val32; @@ -237,8 +236,8 @@ static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) if (!device_property_read_u32(dev, "ti,fuzzz", &val32)) ts->fuzzz = val32; - if (!device_property_read_u64(dev, "ti,poll-period", &val64)) - ts->poll_period = msecs_to_jiffies(val64); + if (!device_property_read_u32(dev, "ti,poll-period", &val32)) + ts->poll_period = msecs_to_jiffies(val32); else ts->poll_period = msecs_to_jiffies(1); From 971fa7ea8621e123feb9c8d7dc61be1c656bd945 Mon Sep 17 00:00:00 2001 From: Erich Sartison Date: Thu, 3 Sep 2026 12:31:37 +0200 Subject: [PATCH 0980/1317] Input: xpad - add support for Victrix Pro BFG Controller The controller doesn't currently work via USB-cable. Signed-off-by: Erich Sartison Link: https://patch.msgid.link/20260903103137.630170-1-byt.es@mailbox.org Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 8e4e06b53c2a..6406ceab988a 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -227,6 +227,7 @@ static const struct xpad_device { { 0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x0246, "Rock Candy Gamepad for Xbox One 2015", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x024c, "PDP Victrix Pro BFG Wired Controller for Xbox", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a0, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a1, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a2, "PDP Wired Controller for Xbox One - Crimson Red", 0, XTYPE_XBOXONE }, From 309731e95917125bbd13626a7a5600490a5bf44f Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 23:40:04 +0800 Subject: [PATCH 0981/1317] Input: hp_sdc - shut down kicker timer on module exit hp_sdc_kicker() rearms hp_sdc.kicker with mod_timer() after scheduling the tasklet. The module exit path uses timer_delete_sync(). That waits for a callback already running but can still leave the timer rearmed. A callback can therefore leave the timer pending while hp_sdc_exit() tears down the driver, allowing timer activity to access dismantled driver state. Use timer_shutdown_sync() for final teardown. It waits for a running callback and prevents rearming after module exit begins. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Acked-by: Helge Deller Link: https://patch.msgid.link/20260902154004.3595416-1-runyu.xiao@seu.edu.cn Signed-off-by: Dmitry Torokhov --- drivers/input/serio/hp_sdc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/serio/hp_sdc.c b/drivers/input/serio/hp_sdc.c index 1461ef319f92..ecf5347b63ab 100644 --- a/drivers/input/serio/hp_sdc.c +++ b/drivers/input/serio/hp_sdc.c @@ -981,7 +981,7 @@ static void hp_sdc_exit(void) free_irq(hp_sdc.irq, &hp_sdc); write_unlock_irq(&hp_sdc.lock); - timer_delete_sync(&hp_sdc.kicker); + timer_shutdown_sync(&hp_sdc.kicker); tasklet_kill(&hp_sdc.task); From 1c21452d02eec2f008e2c5535820f85adbd7587a Mon Sep 17 00:00:00 2001 From: Pu Lehui Date: Sat, 5 Sep 2026 02:11:39 +0000 Subject: [PATCH 0982/1317] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Syzkaller repeatedly triggered UAF splats related to nodes in waiting_for_gp_ttrace within the bpf memalloc: BUG: KASAN: slab-use-after-free in llist_del_first+0x85/0x110 lib/llist.c:61 Read of size 8 at addr ffff8881572cd080 by task syz.4.470/5112 ... llist_del_first+0x85/0x110 lib/llist.c:61 alloc_bulk+0x193/0x460 kernel/bpf/memalloc.c:229 bpf_mem_refill+0x386/0x560 kernel/bpf/memalloc.c:436 Freed by task 14: ... __free_rcu kernel/bpf/memalloc.c:281 [inline] __free_rcu_tasks_trace+0x48/0xd0 kernel/bpf/memalloc.c:291 rcu_tasks_invoke_cbs+0x1ec/0x3e0 kernel/rcu/tasks.h:571 rcu_tasks_one_gp+0x13d/0x220 kernel/rcu/tasks.h:621 rcu_tasks_kthread+0xf3/0x120 kernel/rcu/tasks.h:651 The reason is that the UAF occurs after the RCU Tasks Trace GP expires: when the __free_rcu() callback runs, there is no synchronization protecting llist_del_all() against concurrent alloc_bulk() operating on waiting_for_gp_ttrace, leading to the race condition below: CPU0 CPU1 __free_rcu (RCU Tasks Trace callback) alloc_bulk llist_del_first(&c->waiting_for_gp_ttrace) entry = smp_load_acquire(&head->first); do { if (entry == NULL) return NULL; free_all(llist_del_all(&c->waiting_for_gp_ttrace)) llist_for_each_safe(pos, t, llnode) free_one(pos); next = READ_ONCE(entry->next); <-- trigger UAF } while (!try_cmpxchg(&head->first, &entry, next)); In addition, there is also a theoretical race condition on the free_by_rcu_ttrace list. This race requires two preconditions: an in-flight Tasks Trace GP keeping c->call_rcu_ttrace_in_progress == 1, and concurrent cross-CPU frees repopulating c->free_by_rcu_ttrace with new nodes. Under these conditions, the following scenario triggers UAF: // CPU0 // irq work is still busy (on PREEMPT_RT) alloc_bulk() llist_del_first(&c->free_by_rcu_ttrace) entry = smp_load_acquire(&head->first); do { if (entry == NULL) return NULL; // CPU1 bpf_mem_alloc_destroy() WRITE_ONCE(c->draining, true) // wait for CPU0 irq_work_sync() // CPU2 do_call_rcu_ttrace(tgt(CPU0)) if (c->draining) { llist_del_all(&c->free_by_rcu_ttrace) free_all() } // CPU0 continue next = READ_ONCE(entry->next); <-- trigger UAF while (!try_cmpxchg(&head->first, &entry, next)); Fix this by introducing a raw spinlock to synchronize the concurrent consumption on waiting_for_gp_ttrace and free_by_rcu_ttrace. Fixes: 04fabf00b4d3 ("bpf: Allow reuse from waiting_for_gp_ttrace list.") Suggested-by: Alexei Starovoitov Suggested-by: Hou Tao Signed-off-by: Pu Lehui Acked-by: Hou Tao Link: https://lore.kernel.org/r/20260905021139.4116529-1-pulehui@huaweicloud.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/memalloc.c | 50 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c index e9662db7198f..8a8f088e83e6 100644 --- a/kernel/bpf/memalloc.c +++ b/kernel/bpf/memalloc.c @@ -119,6 +119,7 @@ struct bpf_mem_cache { struct llist_head waiting_for_gp_ttrace; struct rcu_head rcu_ttrace; atomic_t call_rcu_ttrace_in_progress; + raw_spinlock_t lock; }; struct bpf_mem_caches { @@ -214,25 +215,24 @@ static void alloc_bulk(struct bpf_mem_cache *c, int cnt, int node, bool atomic) gfp = __GFP_NOWARN | __GFP_ACCOUNT; gfp |= atomic ? GFP_NOWAIT : GFP_KERNEL; - for (i = 0; i < cnt; i++) { - /* - * For every 'c' llist_del_first(&c->free_by_rcu_ttrace); is - * done only by one CPU == current CPU. Other CPUs might - * llist_add() and llist_del_all() in parallel. - */ - obj = llist_del_first(&c->free_by_rcu_ttrace); - if (!obj) - break; - add_obj_to_free_list(c, obj); - } - if (i >= cnt) - return; + /* + * c->lock serializes concurrent llist_del_first() against + * llist_del_all() in __free_rcu() and do_call_rcu_ttrace(). + */ + scoped_guard(raw_spinlock_irqsave, &c->lock) { + for (i = 0; i < cnt; i++) { + obj = llist_del_first(&c->free_by_rcu_ttrace); + if (!obj) + break; + add_obj_to_free_list(c, obj); + } - for (; i < cnt; i++) { - obj = llist_del_first(&c->waiting_for_gp_ttrace); - if (!obj) - break; - add_obj_to_free_list(c, obj); + for (; i < cnt; i++) { + obj = llist_del_first(&c->waiting_for_gp_ttrace); + if (!obj) + break; + add_obj_to_free_list(c, obj); + } } if (i >= cnt) return; @@ -279,8 +279,12 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per static void __free_rcu(struct rcu_head *head) { struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace); + struct llist_node *llnode; - free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size); + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->waiting_for_gp_ttrace); + + free_all(c, llnode, !!c->percpu_size); atomic_set(&c->call_rcu_ttrace_in_progress, 0); } @@ -300,7 +304,8 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c) if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) { if (unlikely(READ_ONCE(c->draining))) { - llnode = llist_del_all(&c->free_by_rcu_ttrace); + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->free_by_rcu_ttrace); free_all(c, llnode, !!c->percpu_size); } return; @@ -535,6 +540,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } @@ -557,7 +563,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } @@ -609,7 +615,7 @@ int bpf_mem_alloc_percpu_unit_init(struct bpf_mem_alloc *ma, int size) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } From 451b1c19dc7cbbad194a6e717b6a732c443d7dd5 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Fri, 11 Sep 2026 02:39:25 +0800 Subject: [PATCH 0983/1317] hwmon: (k10temp) Fix model id range of Zen5 Turin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model 20h-2Fh are mobile processors with single CCD. For example, model 24h is Strix Point, i.e., Ryzen AI 7 (PRO) (H/HX) 360/365/370. Including mobile processors in the model id range of Zen5 Turin processors leads to bogus reporting: k10temp-pci-00c3 Adapter: PCI adapter Tctl: +54.1°C Tccd4: +148.6°C Tccd6: +148.4°C Tccd7: +149.1°C Tccd8: +149.2°C Tccd9: +149.2°C Tccd12: +149.1°C Tccd14: +22.0°C Tccd15: +22.0°C Tccd16: +22.0°C Fix it by removing the said range. Fixes: 8440d5aca227 ("hwmon: (k10temp) Add per-CCD temperature monitoring for Zen5 Turin") Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260911-k10temp-fix-zen5-epyc-v1-1-643f5a248ae1@rong.moe Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 75a45010d687..3e7e63edc6a3 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -523,7 +523,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) } } else if (boot_cpu_data.x86 == 0x1a) { switch (boot_cpu_data.x86_model) { - case 0x00 ... 0x2f: /* Zen5 Turin */ + case 0x00 ... 0x1f: /* Zen5 Turin */ data->ccd_offset = 0x1F0; k10temp_get_ccd_support(data, 16); break; From 26d5ff79768548efb1e604bb6e8697c101e06269 Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Fri, 11 Sep 2026 15:18:09 +0800 Subject: [PATCH 0984/1317] hwmon: (pwm-fan) Stop RPM timer before freeing tach data sample_timer() rearms the RPM timer and accesses the devm-managed ctx->tachs and ctx->pulses_per_revolution arrays. The cleanup action which stops the timer is registered before those arrays are allocated. Since devres releases entries in reverse order, driver detach can free the arrays before pwm_fan_cleanup() shuts down the timer. A timer expiry in that window accesses the freed tach data. With a KASAN kernel, a test-only kprobe delayed entry to pwm_fan_cleanup() while normal sysfs unbind ran. Each of three runs reported three four-byte reads and two four-byte writes in sample_timer() after its backing devm allocations had been freed. The helper did not invoke the timer callback, cleanup actions or free functions. With the fix, three matching unbind runs completed without KASAN, BUG, WARNING, Oops or panic. Instrumentation confirmed that timer retirement completed before the first timer backing allocation was released. Split timer retirement from the power cleanup and register its devres action after the timer backing data and IRQ actions are installed. This preserves the early power rollback action while ensuring the timer is retired before its backing data is released. Use timer_shutdown_sync() because the callback can rearm itself. Fixes: 01695410d452 ("hwmon: (pwm-fan) Store tach data separately") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Yibo Tan Link: https://patch.msgid.link/20260911071809.130151-1-lhfff@tju.edu.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 3b87f65bae05..c633d7f6464c 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -483,7 +483,6 @@ static void pwm_fan_cleanup(void *__ctx) { struct pwm_fan_ctx *ctx = __ctx; - timer_delete_sync(&ctx->rpm_timer); if (ctx->pwm_shutdown) { ctx->enable_mode = pwm_enable_reg_enable; __set_pwm(ctx, ctx->pwm_shutdown); @@ -494,6 +493,13 @@ static void pwm_fan_cleanup(void *__ctx) } } +static void pwm_fan_timer_cleanup(void *__ctx) +{ + struct pwm_fan_ctx *ctx = __ctx; + + timer_shutdown_sync(&ctx->rpm_timer); +} + static int pwm_fan_probe(struct platform_device *pdev) { struct thermal_cooling_device *cdev; @@ -644,6 +650,10 @@ static int pwm_fan_probe(struct platform_device *pdev) } if (ctx->tach_count > 0) { + ret = devm_add_action_or_reset(dev, pwm_fan_timer_cleanup, ctx); + if (ret) + return ret; + ctx->sample_start = ktime_get(); mod_timer(&ctx->rpm_timer, jiffies + HZ); @@ -700,6 +710,7 @@ static void pwm_fan_shutdown(struct platform_device *pdev) { struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev); + pwm_fan_timer_cleanup(ctx); pwm_fan_cleanup(ctx); } From 7bae83ffb133bc373d098fa6828cef2ef4da49fe Mon Sep 17 00:00:00 2001 From: "Thomas Richard (congatec GmbH)" Date: Fri, 11 Sep 2026 19:31:58 +0200 Subject: [PATCH 0985/1317] hwmon: (cgbc-hwmon) Fix current sensors ID lookup Current sensors on the Congatec Board Controller don't use consecutive IDs, unlike other sensor types (voltage, temperature, fan). The driver assumed consecutive IDs and performed a simple lookup, which caused an unknown sensor warning. Define current sensor IDs explicitly. Changes the warning on conga-SA7 (type and channel are correct now). Before: Board Controller returned an unknown sensor (type=2, channel=17), ignore it After: Board Controller returned an unknown sensor (bc_type=1, bc_id=11), ignore it Cc: stable@kernel.org Fixes: 08ebc9def79f ("hwmon: Add Congatec Board Controller monitoring driver") Signed-off-by: Thomas Richard (congatec GmbH) Link: https://patch.msgid.link/20260911-cgbc-hwmon-fix-and-new-sensors-v2-1-0c6bf078d173@bootlin.com Signed-off-by: Guenter Roeck --- drivers/hwmon/cgbc-hwmon.c | 95 ++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/drivers/hwmon/cgbc-hwmon.c b/drivers/hwmon/cgbc-hwmon.c index 3aff4e092132..2effa0b56286 100644 --- a/drivers/hwmon/cgbc-hwmon.c +++ b/drivers/hwmon/cgbc-hwmon.c @@ -52,30 +52,35 @@ static const char * const cgbc_hwmon_labels_temp[] = { "BOTTOMDIM Temperature", }; -static const struct { - enum hwmon_sensor_types type; - const char *label; -} cgbc_hwmon_labels_in[] = { - { hwmon_in, "CPU Voltage" }, - { hwmon_in, "DC Runtime Voltage" }, - { hwmon_in, "DC Standby Voltage" }, - { hwmon_in, "CMOS Battery Voltage" }, - { hwmon_in, "Battery Voltage" }, - { hwmon_in, "AC Voltage" }, - { hwmon_in, "Other Voltage" }, - { hwmon_in, "5V Voltage" }, - { hwmon_in, "5V Standby Voltage" }, - { hwmon_in, "3V3 Voltage" }, - { hwmon_in, "3V3 Standby Voltage" }, - { hwmon_in, "VCore A Voltage" }, - { hwmon_in, "VCore B Voltage" }, - { hwmon_in, "12V Voltage" }, - { hwmon_curr, "DC Current" }, - { hwmon_curr, "5V Current" }, - { hwmon_curr, "12V Current" }, +static const char * const cgbc_hwmon_labels_in[] = { + "CPU Voltage", + "DC Runtime Voltage", + "DC Standby Voltage", + "CMOS Battery Voltage", + "Battery Voltage", + "AC Voltage", + "Other Voltage", + "5V Voltage", + "5V Standby Voltage", + "3V3 Voltage", + "3V3 Standby Voltage", + "VCore A Voltage", + "VCore B Voltage", + "12V Voltage", }; -#define CGBC_HWMON_NB_IN_SENSORS 14 +/* + * Current sensors are a bit special, they don't have consecutive IDs like + * other types of sensors. So they need to be defined explicitly. + */ +static const struct { + const char *label; + int id; +} cgbc_hwmon_labels_curr[] = { + { "DC Current", 0x12 }, + { "5V Current", 0x18 }, + { "12V Current", 0x1E }, +}; static const char * const cgbc_hwmon_labels_fan[] = { "CPU Fan", @@ -114,7 +119,8 @@ static int cgbc_hwmon_probe_sensors(struct device *dev, struct cgbc_hwmon_data * for (i = 0; i < nb_sensors; i++) { enum cgbc_sensor_types type; - unsigned int channel; + unsigned int channel, id; + int j; /* * No need to request data for the first sensor. @@ -128,32 +134,49 @@ static int cgbc_hwmon_probe_sensors(struct device *dev, struct cgbc_hwmon_data * } type = FIELD_GET(CGBC_HWMON_TYPE_MASK, data[1]); - channel = FIELD_GET(CGBC_HWMON_ID_MASK, data[1]) - 1; + id = FIELD_GET(CGBC_HWMON_ID_MASK, data[1]); + channel = id - 1; if (type == CGBC_HWMON_TYPE_TEMP && channel < ARRAY_SIZE(cgbc_hwmon_labels_temp)) { sensor->type = hwmon_temp; sensor->label = cgbc_hwmon_labels_temp[channel]; - } else if (type == CGBC_HWMON_TYPE_IN && - channel < ARRAY_SIZE(cgbc_hwmon_labels_in)) { + } else if (type == CGBC_HWMON_TYPE_IN) { /* * The Board Controller doesn't differentiate current and voltage sensors. - * Get the sensor type from cgbc_hwmon_labels_in[channel].type instead. + * First check if it is a current sensor. */ - sensor->type = cgbc_hwmon_labels_in[channel].type; - sensor->label = cgbc_hwmon_labels_in[channel].label; + for (j = 0; j < ARRAY_SIZE(cgbc_hwmon_labels_curr); j++) { + if (id == cgbc_hwmon_labels_curr[j].id) { + sensor->type = hwmon_curr; + sensor->label = cgbc_hwmon_labels_curr[j].label; + channel = j; + } + } + + /* If it's not a current sensor, it may be a voltage sensor. */ + if (!sensor->label && channel < ARRAY_SIZE(cgbc_hwmon_labels_in)) { + sensor->type = hwmon_in; + sensor->label = cgbc_hwmon_labels_in[channel]; + } } else if (type == CGBC_HWMON_TYPE_FAN && channel < ARRAY_SIZE(cgbc_hwmon_labels_fan)) { sensor->type = hwmon_fan; sensor->label = cgbc_hwmon_labels_fan[channel]; - } else { - dev_warn(dev, "Board Controller returned an unknown sensor (type=%d, channel=%d), ignore it", - type, channel); + } + + if (!sensor->label) { + dev_warn(dev, "Board Controller returned an unknown sensor (bc_type=%d, bc_id=%d), ignore it", + type, id); continue; } sensor->active = FIELD_GET(CGBC_HWMON_ACTIVE_BIT, data[1]); sensor->channel = channel; sensor->index = i; + + dev_dbg(dev, "Found sensor: bc_type=%d, bc_id=%d, hwmon_type=%d, hwmon_channel=%d, hwmon_label='%s', active=%d\n", + type, id, sensor->type, sensor->channel, sensor->label, sensor->active); + sensor++; hwmon->nb_sensors++; } @@ -167,14 +190,6 @@ static struct cgbc_hwmon_sensor *cgbc_hwmon_find_sensor(struct cgbc_hwmon_data * struct cgbc_hwmon_sensor *sensor = NULL; int i; - /* - * The Board Controller doesn't differentiate current and voltage sensors. - * The channel value (from the Board Controller point of view) shall be computed for current - * sensors. - */ - if (type == hwmon_curr) - channel += CGBC_HWMON_NB_IN_SENSORS; - for (i = 0; i < hwmon->nb_sensors; i++) { if (hwmon->sensors[i].type == type && hwmon->sensors[i].channel == channel) { sensor = &hwmon->sensors[i]; From 3550d1dbbcb9f51b77e077e8958423ee2c401c6c Mon Sep 17 00:00:00 2001 From: "Thomas Richard (congatec GmbH)" Date: Fri, 11 Sep 2026 19:31:59 +0200 Subject: [PATCH 0986/1317] hwmon: (cgbc-hwmon) Add missing sensors Add the following sensors: - Alternate Board Temperature (temp11_input) - Top DIMM 1-7 Temperature (temp12_input to temp18_input) - Bottom DIMM 1 Temperature (temp19_input) - 12V Standby Voltage (in14_input) This fixes the following warning on conga-SA7: Board Controller returned an unknown sensor (bc_type=1, bc_id=11), ignore it Also update existing labels to match Congatec documentation. Cc: stable@kernel.org Fixes: 08ebc9def79f ("hwmon: Add Congatec Board Controller monitoring driver") Signed-off-by: Thomas Richard (congatec GmbH) Link: https://patch.msgid.link/20260911-cgbc-hwmon-fix-and-new-sensors-v2-2-0c6bf078d173@bootlin.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/cgbc-hwmon.rst | 42 +++++++++++++++--------- drivers/hwmon/cgbc-hwmon.c | 52 +++++++++++++++++++----------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/Documentation/hwmon/cgbc-hwmon.rst b/Documentation/hwmon/cgbc-hwmon.rst index 3a5e6e6e8639..c6d09232392a 100644 --- a/Documentation/hwmon/cgbc-hwmon.rst +++ b/Documentation/hwmon/cgbc-hwmon.rst @@ -28,34 +28,44 @@ system. Name Description ============= ====================== temp1_input CPU temperature -temp2_input Box temperature +temp2_input Case temperature temp3_input Ambient temperature -temp4_input Board temperature -temp5_input Carrier temperature -temp6_input Chipset temperature -temp7_input Video temperature +temp4_input CPU Board temperature +temp5_input Carrier Board temperature +temp6_input System Chipset temperature +temp7_input Video Controller/Board temperature temp8_input Other temperature -temp9_input TOPDIM temperature -temp10_input BOTTOMDIM temperature -in0_input CPU voltage +temp9_input Top DIMM 0 temperature +temp10_input Bottom DIMM 0 temperature +temp11_input Alternate Board temperature +temp12_input Top DIMM 1 temperature +temp13_input Top DIMM 2 temperature +temp14_input Top DIMM 3 temperature +temp15_input Top DIMM 4 temperature +temp16_input Top DIMM 5 temperature +temp17_input Top DIMM 6 temperature +temp18_input Top DIMM 7 temperature +temp19_input Bottom DIMM 1 temperature +in0_input CPU Core voltage in1_input DC Runtime voltage in2_input DC Standby voltage in3_input CMOS Battery voltage -in4_input Battery voltage +in4_input Battery Supply voltage in5_input AC voltage in6_input Other voltage -in7_input 5V voltage +in7_input 5V Runtime voltage in8_input 5V Standby voltage -in9_input 3V3 voltage +in9_input 3V3 Runtime voltage in10_input 3V3 Standby voltage in11_input VCore A voltage in12_input VCore B voltage -in13_input 12V voltage -curr1_input DC current -curr2_input 5V current -curr3_input 12V current +in13_input 12V Runtime voltage +in14_input 12V Standby voltage +curr1_input DC Runtime current +curr2_input 5V Runtime current +curr3_input 12V Runtime current fan1_input CPU fan -fan2_input Box fan +fan2_input Case fan fan3_input Ambient fan fan4_input Chiptset fan fan5_input Video fan diff --git a/drivers/hwmon/cgbc-hwmon.c b/drivers/hwmon/cgbc-hwmon.c index 2effa0b56286..230062a46907 100644 --- a/drivers/hwmon/cgbc-hwmon.c +++ b/drivers/hwmon/cgbc-hwmon.c @@ -41,32 +41,42 @@ enum cgbc_sensor_types { static const char * const cgbc_hwmon_labels_temp[] = { "CPU Temperature", - "Box Temperature", + "Case Temperature", "Ambient Temperature", - "Board Temperature", - "Carrier Temperature", - "Chipset Temperature", - "Video Temperature", + "CPU Board Temperature", + "Carrier Board Temperature", + "System Chipset Temperature", + "Video Controller/Board Temperature", "Other Temperature", - "TOPDIM Temperature", - "BOTTOMDIM Temperature", + "Top DIMM 0 Temperature", + "Bottom DIMM 0 Temperature", + "Alternate Board Temperature", + "Top DIMM 1 Temperature", + "Top DIMM 2 Temperature", + "Top DIMM 3 Temperature", + "Top DIMM 4 Temperature", + "Top DIMM 5 Temperature", + "Top DIMM 6 Temperature", + "Top DIMM 7 Temperature", + "Bottom DIMM 1 Temperature", }; static const char * const cgbc_hwmon_labels_in[] = { - "CPU Voltage", + "CPU Core Voltage", "DC Runtime Voltage", "DC Standby Voltage", "CMOS Battery Voltage", - "Battery Voltage", + "Battery Supply Voltage", "AC Voltage", "Other Voltage", - "5V Voltage", + "5V Runtime Voltage", "5V Standby Voltage", - "3V3 Voltage", + "3V3 Runtime Voltage", "3V3 Standby Voltage", "VCore A Voltage", "VCore B Voltage", - "12V Voltage", + "12V Runtime Voltage", + "12V Standby Voltage", }; /* @@ -77,14 +87,14 @@ static const struct { const char *label; int id; } cgbc_hwmon_labels_curr[] = { - { "DC Current", 0x12 }, - { "5V Current", 0x18 }, - { "12V Current", 0x1E }, + { "DC Runtime Current", 0x12 }, + { "5V Runtime Current", 0x18 }, + { "12V Runtime Current", 0x1E }, }; static const char * const cgbc_hwmon_labels_fan[] = { "CPU Fan", - "Box Fan", + "Case Fan", "Ambient Fan", "Chipset Fan", "Video Fan", @@ -255,7 +265,12 @@ static const struct hwmon_channel_info * const cgbc_hwmon_info[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, - HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, @@ -263,7 +278,8 @@ static const struct hwmon_channel_info * const cgbc_hwmon_info[] = { HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL), From 06bd6794b5fd2163880ac3bfe973d4cc61f359f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 11 Sep 2026 14:53:37 +0100 Subject: [PATCH 0987/1317] hwmon: (pmbus/core) increase number of phases and add new mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increase the number of phases to 16 as a new upcoming device supports such a number. While at it, add a new mask for controlling the source of the output voltage. Note (groeck): This patch was meant to prepare for support of MAX20826 and compatible devices, which support more than 10 phases per page. However, Sashiko reports that the mp2975 driver already supports up to 14 phases, and the mp2856 driver supports up to 12 phases. This already has the potential for out-of-bounds writes when probing the affected chips, making this patch a bug fix. Fixes: 2c6fcbb21149 ("hwmon: (pmbus) Add support for MPS Multi-phase mp2975 controller") Fixes: f9e5f289b686 ("hwmon: (pmbus) Add support for MPS Multi-phase mp2856/mp2857 controller") Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20260911-hwmon-max20826-support-v2-1-5e30cbd97d84@analog.com Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 2cd3216b3cd9..920c1102ab6d 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -242,6 +242,7 @@ enum pmbus_regs { /* * OPERATION */ +#define PB_OPERATION_CONTROL_V_SRC GENMASK(5, 4) #define PB_OPERATION_CONTROL_ON BIT(7) /* @@ -386,7 +387,7 @@ enum pmbus_sensor_classes { }; #define PMBUS_PAGES 32 /* Per PMBus specification */ -#define PMBUS_PHASES 10 /* Maximum number of phases per page */ +#define PMBUS_PHASES 16 /* Maximum number of phases per page */ /* Functionality bit mask */ #define PMBUS_HAVE_VIN BIT(0) From 490a83d6386eec1d29f470c8d7331677fb46c3b7 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Tue, 8 Sep 2026 17:08:32 +0800 Subject: [PATCH 0988/1317] bpf, sockmap: Fix self-redirect copied_seq double-counting When a BPF stream_verdict program redirects an skb back to the same socket (self-redirect with BPF_F_INGRESS), sk_psock_verdict_apply() calls tcp_eat_skb() which advances tcp_sk->copied_seq. However, the skb is then delivered to the socket's psock ingress queue and later read by tcp_bpf_recvmsg_parser(), which also advances copied_seq via the copied_from_self accounting path. This double-counting causes copied_seq to advance by 2x the actual data length, triggering: TCP recvmsg seq # bug 2: copied BF2E806, seq BF2E7FD, \ rcvnxt BF2E806, fl 0 WARNING: net/ipv4/tcp.c:2745 at tcp_recvmsg_locked+0x72b/0x2640 Call Trace: tcp_recvmsg+0x10a/0x500 sock_recvmsg+0x168/0x1d0 __sys_recvfrom+0x19a/0x2a0 __x64_sys_recvfrom+0xe4/0x1f0 do_syscall_64+0xf7/0x530 entry_SYSCALL_64_after_hwframe+0x77/0x7f cleanup rbuf bug: copied BF2E806 seq BF2E806 rcvnxt BF2E806 WARNING: net/ipv4/tcp.c:1609 at tcp_cleanup_rbuf+0xf2/0x1c0 Call Trace: tcp_recvmsg_locked+0x8d1/0x2640 tcp_recvmsg+0x10a/0x500 sock_recvmsg+0x168/0x1d0 __sys_recvfrom+0x19a/0x2a0 __x64_sys_recvfrom+0xe4/0x1f0 do_syscall_64+0xf7/0x530 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fix this by converting self-redirect verdict to __SK_PASS at the beginning of sk_psock_verdict_apply(). This bypasses the __SK_REDIRECT case entirely (which calls sk_psock_eat_skb), letting the __SK_PASS path queue the skb to the psock ingress queue. The data is then read via tcp_bpf_recvmsg_parser(), which advances copied_seq exactly once through copied_from_self. Cross-socket redirects continue through __SK_REDIRECT with sk_psock_eat_skb() unchanged. Fixes: e5c6de5fa025 ("bpf, sockmap: Incorrectly handling copied_seq") Suggested-by: Jakub Sitnicki Suggested-by: Jiayuan Chen Signed-off-by: Geliang Tang Reviewed-by: Emil Tsalapatis Reviewed-by: Jiayuan Chen Link: https://lore.kernel.org/r/1a8e797a1b26e2f695aaac22ac644c2862f63466.1788858299.git.tanggeliang@kylinos.cn Signed-off-by: Alexei Starovoitov --- net/core/skmsg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/core/skmsg.c b/net/core/skmsg.c index 2521b643fa05..df385a5a961e 100644 --- a/net/core/skmsg.c +++ b/net/core/skmsg.c @@ -1000,6 +1000,10 @@ static int sk_psock_verdict_apply(struct sk_psock *psock, struct sk_buff *skb, int err = 0; u32 len, off; + if (verdict == __SK_REDIRECT && skb_bpf_ingress(skb) && + skb_bpf_redirect_fetch(skb) == psock->sk) + verdict = __SK_PASS; + switch (verdict) { case __SK_PASS: err = -EIO; From 953824e508b27d12837e32ef37ef6248e1f6fc7a Mon Sep 17 00:00:00 2001 From: Masoud Aghasi Date: Thu, 3 Sep 2026 09:27:34 +0100 Subject: [PATCH 0989/1317] bpf: Fix u32 overflow issue in map batch operations Several map batch operation implementations such as generic_map_lookup_batch() use calculations in the form of "values + cp * map->value_size" to compute the desired userspace memory address for reading or writing. This can overflow the u32 type (the result of "cp * map->value_size") when the map size exceeds 4GB. generic_map_lookup_batch() may corrupt values for some keys in userspace memory, and in some cases it mismatches values for some keys while still reporting success. Other batch operations may fail to delete or update some keys, or the syscall may return unexpected errors. Add size_t casts to prevent the affected offset and size calculations from overflowing. Fixes: cb4d03ab499d ("bpf: Add generic support for lookup batch op") Fixes: aa2e93b8e58e ("bpf: Add generic support for update and delete batch ops") Fixes: 057996380a42 ("bpf: Add batch ops to all htab bpf map") Signed-off-by: Masoud Aghasi Link: https://lore.kernel.org/r/20260903082734.623904-1-maghasi@disroot.org Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 8 ++++---- kernel/bpf/syscall.c | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index a72dc5b9f184..4f495dcbf670 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1997,10 +1997,10 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, rcu_read_unlock(); bpf_enable_instrumentation(); - if (bucket_cnt && (copy_to_user(ukeys + total * key_size, keys, - key_size * bucket_cnt) || - copy_to_user(uvalues + total * value_size, values, - value_size * bucket_cnt))) { + if (bucket_cnt && (copy_to_user(ukeys + (size_t)total * key_size, keys, + (size_t)key_size * bucket_cnt) || + copy_to_user(uvalues + (size_t)total * value_size, values, + (size_t)value_size * bucket_cnt))) { ret = -EFAULT; goto after_loop; } diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index c7bc9ba9b331..853b47f81384 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2036,7 +2036,7 @@ int generic_map_delete_batch(struct bpf_map *map, for (cp = 0; cp < max_count; cp++) { err = -EFAULT; - if (copy_from_user(key, keys + cp * map->key_size, + if (copy_from_user(key, keys + (size_t)cp * map->key_size, map->key_size)) break; @@ -2098,9 +2098,9 @@ int generic_map_update_batch(struct bpf_map *map, struct file *map_file, for (cp = 0; cp < max_count; cp++) { err = -EFAULT; - if (copy_from_user(key, keys + cp * map->key_size, + if (copy_from_user(key, keys + (size_t)cp * map->key_size, map->key_size) || - copy_from_user(value, values + cp * value_size, value_size)) + copy_from_user(value, values + (size_t)cp * value_size, value_size)) break; err = bpf_map_update_value(map, map_file, key, value, @@ -2179,12 +2179,12 @@ int generic_map_lookup_batch(struct bpf_map *map, if (err) goto free_buf; - if (copy_to_user(keys + cp * map->key_size, key, + if (copy_to_user(keys + (size_t)cp * map->key_size, key, map->key_size)) { err = -EFAULT; goto free_buf; } - if (copy_to_user(values + cp * value_size, value, value_size)) { + if (copy_to_user(values + (size_t)cp * value_size, value, value_size)) { err = -EFAULT; goto free_buf; } From ef1fb82f12186dd26153b14d9fbcf4ec98db81b3 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 22:06:23 +0900 Subject: [PATCH 0990/1317] bpf, arm64: set up the frame pointer for the exception callback A program acting as exception boundary saves all callee-saved registers, so build_prologue() takes the exception_cb path and never calls push_callee_regs(). That is the only place find_used_callee_regs() runs, and with it the only place ctx->fp_used is set, so the callback prologue does not emit the mov x25, sp that points BPF_REG_FP at the frame the callback runs on. x25 keeps whatever it held when bpf_throw() was called. If the throw came from a subprogram that uses its own BPF stack, that is the subprogram's frame pointer, and since the subprogram never returns it never restores x25 either. Stack accesses through BPF_REG_FP are rewritten to be stack pointer relative, so those still land in the callback's own frame. Materializing the register does not: a callback that passes the address of a local variable to a helper hands over an address in the dead subprogram's frame. That address is below the callback's stack pointer by then, and the helper's own call chain covers it, so the helper can write over its own return address. 0x1234 below is the value the helper was asked to store: pc : 0x1234 lr : 0x1234 Call trace: 0x1234 (P) bpf_test_run+0x188/0x3e0 bpf_prog_test_run_skb+0x47c/0x998 __sys_bpf+0xbdc/0xdd8 Kernel panic - not syncing: Oops: Fatal exception in interrupt Set ctx->fp_used on the exception callback path so that the existing code further down sets x25 from the stack pointer. The epilogue restores it from the main program's save area along with the other callee-saved registers, as it already does. x86 sets the frame pointer for the callback from the argument it is passed, and powerpc computes it from the stack pointer. Fixes: 5d4fa9ec5643 ("bpf, arm64: Avoid blindly saving/restoring all callee-saved registers") Acked-by: Xu Kuohai Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907130624.611942-2-donggeunyoo.kernel@gmail.com Signed-off-by: Alexei Starovoitov --- arch/arm64/net/bpf_jit_comp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index c18e005a41db..c5f55d6161fe 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -600,6 +600,8 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf) * 12 registers are on the stack */ emit(A64_SUB_I(1, A64_SP, A64_FP, 96), ctx); + /* The callback may use its own BPF stack, set up fp for it. */ + ctx->fp_used = true; } /* Stack must be multiples of 16B */ From 26a43a5f8c31b9132dc40a449d0f6c7ecfaa1f40 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 22:06:24 +0900 Subject: [PATCH 0991/1317] selftests/bpf: cover the exception callback using its own BPF stack The existing exception tests do not reach a callback that materializes BPF_REG_FP into a register. They either throw from the main program, where BPF_REG_FP already holds the value the callback needs, or use a callback whose only stack accesses are frame pointer relative, which the arm64 JIT rewrites to be stack pointer relative. Add a test that throws from a subprogram using its own BPF stack, with a callback that hands the address of a local variable to bpf_probe_read_kernel(). The helper and the callback have to name the same slot for the value read back to be the one the helper stored. Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907130624.611942-3-donggeunyoo.kernel@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/exceptions.c | 1 + .../testing/selftests/bpf/progs/exceptions.c | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/exceptions.c b/tools/testing/selftests/bpf/prog_tests/exceptions.c index 3588d6f97fd4..639866ce09a9 100644 --- a/tools/testing/selftests/bpf/prog_tests/exceptions.c +++ b/tools/testing/selftests/bpf/prog_tests/exceptions.c @@ -55,6 +55,7 @@ static void test_exceptions_success(void) RUN_SUCCESS(exception_ext, 0); RUN_SUCCESS(exception_ext_mod_cb_runtime, 35); RUN_SUCCESS(exception_throw_subprog, 1); + RUN_SUCCESS(exception_throw_subprog_stack_cb, 0x1234); RUN_SUCCESS(exception_assert_nz_gfunc, 1); RUN_SUCCESS(exception_assert_zero_gfunc, 1); RUN_SUCCESS(exception_assert_neg_gfunc, 1); diff --git a/tools/testing/selftests/bpf/progs/exceptions.c b/tools/testing/selftests/bpf/progs/exceptions.c index c8d716fbd419..91c81971e58c 100644 --- a/tools/testing/selftests/bpf/progs/exceptions.c +++ b/tools/testing/selftests/bpf/progs/exceptions.c @@ -212,6 +212,36 @@ int exception_throw_subprog(struct __sk_buff *ctx) return 0; } +u64 exception_cb_stack_src = 0x1234; + +/* + * The address handed to the helper has to be this callback's own stack + * slot, not one from a frame that is already gone. + */ +__noinline int exception_cb_stack(u64 cookie) +{ + volatile u64 val = 0xdead; + + bpf_probe_read_kernel((void *)&val, sizeof(val), &exception_cb_stack_src); + return val; +} + +/* Throws from a subprogram that has a stack of its own. */ +__noinline static int throwing_subprog_stack(struct __sk_buff *ctx) +{ + volatile u64 pad[4] = {}; + + bpf_throw(pad[0]); + return 0; +} + +SEC("tc") +__exception_cb(exception_cb_stack) +int exception_throw_subprog_stack_cb(struct __sk_buff *ctx) +{ + return throwing_subprog_stack(ctx); +} + __noinline int assert_nz_gfunc(u64 c) { volatile u64 cookie = c; From 2de887f891662814b1160cbfb8c1bf2a5a46d418 Mon Sep 17 00:00:00 2001 From: Xavier Goffin Date: Sun, 13 Sep 2026 23:11:55 +0200 Subject: [PATCH 0992/1317] ALSA: hda/realtek: Add mute LED quirk for HP OMEN 15-ax On the HP OMEN 15 ax-202nf, the keyboard mute LED is exposed through NID 0x1b rather than 0x18. This reuses the existing quirk (ALC269_FIXUP_HP_MUTE_LED_MIC3) to allow the keyboard LED to reflect the built-in speaker mute state. Tested on HP OMEN 15 ax-202nf with Realtek ALC295: - hda::mute/brightness properly follows mute state - mute/unmute via keyboard shortcut or via GUI volume control - state is kept on suspend & resume, and reboot - plugging a 3.5mm jack headset reflects the headset mute status - unplugging reverts the LED to the speaker mute status - USB/Bluetooth headsets are not covered Signed-off-by: Xavier Goffin Link: https://patch.msgid.link/20260913211155.20305-1-xaviergoffin42@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index dc73fa95b00a..fa83b739fb50 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7358,6 +7358,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC), SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360), SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), + SND_PCI_QUIRK(0x103c, 0x8259, "HP OMEN 15-ax202nf", ALC269_FIXUP_HP_MUTE_LED_MIC3), SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE), From beb34fe8312eda37b9cf1568550d722530444822 Mon Sep 17 00:00:00 2001 From: Kitty Makin Date: Mon, 14 Sep 2026 00:24:24 +0000 Subject: [PATCH 0993/1317] ALSA: usb-audio: Add capture quirk for Behringer FCA1616 The Behringer FCA1616 (1397:0004) returns silent capture samples unless its playback endpoint is active. Use the existing fixed implicit-feedback mechanism to keep playback endpoint 0x01 on interface 1 active during capture. Tested with 16-channel S32_LE capture at 44.1 and 48 kHz. Signed-off-by: Kitty Makin Link: https://patch.msgid.link/20260914002334.12691-1-autumnull@posteo.net Signed-off-by: Takashi Iwai --- sound/usb/implicit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c index 77f06da93151..bd4d569a8190 100644 --- a/sound/usb/implicit.c +++ b/sound/usb/implicit.c @@ -76,6 +76,7 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { /* Implicit feedback quirk table for capture: only FIXED type */ static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { + IMPLICIT_FB_FIXED_DEV(0x1397, 0x0004, 0x01, 1), /* Behringer FCA1616 */ {} /* terminator */ }; From 5f90f85eae4e9d2e9628b2019870994ba830b533 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:07 +0200 Subject: [PATCH 0994/1317] power: sequencing: don't call .post_enable() if pwrseq_unit_enable() failed If the call to pwrseq_unit_enable() failed in pwrseq_enable(), bail out instead of calling target->post_enable() which assumes the target was successfully enabled. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-1-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 721e888b658d..76c39600f690 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -912,6 +912,8 @@ int pwrseq_enable(struct pwrseq_desc *desc) if (!ret) desc->powered_on = true; } + if (ret) + return ret; if (target->post_enable) { ret = target->post_enable(pwrseq); From 115b303e8e093d964089ec6f3c40d984d77b33d0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:08 +0200 Subject: [PATCH 0995/1317] power: sequencing: fix NULL-pointer dereference in pwrseq_unit_new() If memory allocation fails in pwrseq_unit_setup_deps(), pwrseq_unit_put() is called to release the partially initialized unit. However, we've never initialized unit->list and pwrseq_unit_release() will unconditionally call list_del() on it. Initialize unit->list right after allocating the unit struct. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260903-pwrseq-kunit-v1-0-1f893d2cabc2%40oss.qualcomm.com?part=1 Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-2-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 76c39600f690..7751ce8cd8f5 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -101,6 +101,7 @@ static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data) } kref_init(&unit->ref); + INIT_LIST_HEAD(&unit->list); INIT_LIST_HEAD(&unit->deps); unit->enable = data->enable; unit->disable = data->disable; From 242da4318d97380741516b595af3920207b2f0f1 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:09 +0200 Subject: [PATCH 0996/1317] power: sequencing: fix NULL-pointer dereference in pwrseq_device_register() If dev_set_name() fails in pwrseq_device_register(), we jump to the err_put_pwrseq label before initializing pwrseq->targets. pwrseq_release() will try to iterate over targets unconditionally and subsequently dereference an invalid pointer. Move the call to dev_set_name() after the list head is initialized. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260903-pwrseq-kunit-v1-0-1f893d2cabc2%40oss.qualcomm.com?part=2 Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-3-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 7751ce8cd8f5..392d72537485 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -505,10 +505,6 @@ pwrseq_device_register(const struct pwrseq_config *config) */ device_initialize(&pwrseq->dev); - ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id); - if (ret) - goto err_put_pwrseq; - pwrseq->owner = config->owner ?: THIS_MODULE; pwrseq->match = config->match; @@ -517,6 +513,10 @@ pwrseq_device_register(const struct pwrseq_config *config) INIT_LIST_HEAD(&pwrseq->targets); INIT_LIST_HEAD(&pwrseq->units); + ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id); + if (ret) + goto err_put_pwrseq; + ret = pwrseq_setup_targets(config->targets, pwrseq); if (ret) goto err_put_pwrseq; From 8dc2615d5702059b2b71fca6f93c0d7d10ae54cb Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Fri, 11 Sep 2026 16:30:58 +0900 Subject: [PATCH 0997/1317] arm64: dts: renesas: r8a779f0: Set UFS lane count Since commit e72323f3b09f ("scsi: ufs: core: Configure only active lanes during link"), the following error is observed on R-Car S4: ufshcd-renesas e6860000.ufs: Tx lane mismatch [config,reported] [2,1] ufshcd-renesas e6860000.ufs: link startup failed -67 ufshcd-renesas e6860000.ufs: error -ENOLINK: Initialization failed with error -67 ufshcd-renesas e6860000.ufs: probe with driver ufshcd-renesas failed with error -67 R-Car S4 has one UFS lane per direction, as described in section 152.1 of its hardware manual. Without lanes-per-direction, the UFS platform driver defaults to two lanes. Previously, the core used PA_CONNECTEDRXDATALANES and PA_CONNECTEDTXDATALANES to configure the link without checking them against lanes-per-direction, so the missing property did not prevent initialization. Explicitly set lanes-per-direction to 1, now that the validation is in place. Fixes: 5235d551779d ("arm64: dts: renesas: r8a779f0: Add UFS node") Cc: stable@vger.kernel.org # 7.2+ Signed-off-by: Koichiro Den Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260911073058.253000-1-den@valinux.co.jp Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r8a779f0.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/renesas/r8a779f0.dtsi b/arch/arm64/boot/dts/renesas/r8a779f0.dtsi index cbb161c863ac..a5118c2f2742 100644 --- a/arch/arm64/boot/dts/renesas/r8a779f0.dtsi +++ b/arch/arm64/boot/dts/renesas/r8a779f0.dtsi @@ -901,6 +901,7 @@ ufs: ufs@e6860000 { clocks = <&cpg CPG_MOD 1514>, <&ufs30_clk>; clock-names = "fck", "ref_clk"; freq-table-hz = <200000000 200000000>, <38400000 38400000>; + lanes-per-direction = <1>; power-domains = <&sysc R8A779F0_PD_ALWAYS_ON>; resets = <&cpg 1514>; status = "disabled"; From 692f32609a30f75ca3401e25b504bfd06bd5662a Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 17 Aug 2026 12:22:11 -0400 Subject: [PATCH 0998/1317] pinctrl: meson: Fix typo in s4 group name One of the i2c pin groups has some junk at the end. The name should be i2c2_scl_h1, and indeed that's the name used by i2c2_pins3 in meson-s4.dtsi. Fixes: 775214d389c25 ("pinctrl: meson: add pinctrl driver support for Meson-S4 Soc") Signed-off-by: Sean Anderson Reviewed-by: Neil Armstrong Signed-off-by: Linus Walleij --- drivers/pinctrl/meson/pinctrl-meson-s4.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/meson/pinctrl-meson-s4.c b/drivers/pinctrl/meson/pinctrl-meson-s4.c index 872948699e9f..365dafe457a9 100644 --- a/drivers/pinctrl/meson/pinctrl-meson-s4.c +++ b/drivers/pinctrl/meson/pinctrl-meson-s4.c @@ -854,7 +854,7 @@ static const char * const i2c1_groups[] = { static const char * const i2c2_groups[] = { "i2c2_sda_d", "i2c2_scl_d", "i2c2_sda_h8", "i2c2_scl_h9", - "i2c2_sda_h0", "i2c2_scl_h1l," + "i2c2_sda_h0", "i2c2_scl_h1", }; static const char * const i2c3_groups[] = { From 50fd0ada8d37587223001600933270b59cb30e19 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Mon, 14 Sep 2026 13:15:37 +0800 Subject: [PATCH 0999/1317] gpio: virtuser: skip free_irq when no IRQ is installed Disabling interrupt monitoring uses atomic_xchg() to clear the stored IRQ. When monitoring is already disabled, atomic_xchg() returns 0. It must not be passed to free_irq(). The bug is reproducible on an x86_64 QEMU guest with CONFIG_GPIO_VIRTUSER=y and CONFIG_GPIO_SIM=y. Configure a live gpio-virtuser device through configfs. Its input lookup must refer to a live gpio-sim bank, such as key gpio-sim-test with offset 0. The consumer's dev_name attribute is shown as below; then run: echo 0 > /sys/kernel/debug/gpio-virtuser//gpiod:input:0/interrupts On an unpatched kernel, this reaches gpio_virtuser_interrupts_set() with ld->irq still at its initial value 0, and free_irq() reports: Trying to free already-free IRQ 0 The same reproducer completes without the warning on the patched kernel. Fixes: 91581c4b3f29 ("gpio: virtuser: new virtual testing driver for the GPIO API") Assisted-by: LLM Signed-off-by: Runyu Xiao Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260914051537.15320-1-runyu.xiao@seu.edu.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-virtuser.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index 7d0d366be37a..d5876c19cfef 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -692,7 +692,8 @@ static int gpio_virtuser_interrupts_set(void *data, u64 val) atomic_set(&ld->irq, irq); } else { irq = atomic_xchg(&ld->irq, 0); - free_irq(irq, ld); + if (irq) + free_irq(irq, ld); } return 0; From 51ae99659469edba2e83931efa26b77d36ca02c2 Mon Sep 17 00:00:00 2001 From: Sarah Emery Date: Fri, 28 Aug 2026 17:58:17 +0200 Subject: [PATCH 1000/1317] pinctrl: generic: serialise pinctrl_generic_dt_node_to_map() pinctrl_generic_add_group() documents that the caller must take care of locking, and pinmux_generic_add_function() needs it too, but pinctrl_generic_dt_node_to_map() calls them without holding pctldev->mutex, and the core caller in create_pinctrl() does not take it either. The driver core calls pinctrl_bind_pins() before probing a device, so two devices that reference the same pin controller can run pinctrl_generic_dt_node_to_map() on one pctldev at the same time. Both `add` functions take the new selector from pctldev->num_groups or pctldev->num_functions, and radix_tree_insert() at that index. Two racing callers can read the same selector before either has inserted, so the second insert collides and fails: k1-pinctrl d401e000.pinctrl: error -EEXIST: error adding function pcie2-0-cfg k1-pinctrl d401e000.pinctrl: does not have pin group pcie0-0-cfg.pcie0-0-pins leaving one consumer without its pin configuration. This was hit on a SpacemiT K3 board, where PCIe devices probe in parallel against the single shared pin controller. Take pctldev->mutex across the whole function, so that the groups and the function referring are in a single critical section. Fixes: 43722575e5cd ("pinctrl: add generic functions + pins mapper") Signed-off-by: Sarah Emery Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-generic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pinctrl/pinctrl-generic.c b/drivers/pinctrl/pinctrl-generic.c index fd6bdb74028a..4277c8748513 100644 --- a/drivers/pinctrl/pinctrl-generic.c +++ b/drivers/pinctrl/pinctrl-generic.c @@ -3,8 +3,10 @@ #define pr_fmt(fmt) "generic pinconfig core: " fmt #include +#include #include #include +#include #include #include @@ -196,6 +198,8 @@ static int pinctrl_generic_dt_node_to_map(struct pinctrl_dev *pctldev, int ngroups = 0; int ret; + guard(mutex)(&pctldev->mutex); + *maps = NULL; *num_maps = 0; From e6c5d6c589764a41218cbd81bd7d6c9b906e2cf0 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Mon, 31 Aug 2026 11:50:58 +0100 Subject: [PATCH 1001/1317] pinctrl: mpfs-mssio: fix width of unused bank voltage setting The bank voltages are only 4 bits wide, so when a pin was unused the driver was not correctly interpreting it as being at zero volts, because the driver's value for unused had two extra set bits. CC: stable@vger.kernel.org Fixes: 488d704ed7b7 ("pinctrl: add polarfire soc mssio pinctrl driver") Signed-off-by: Conor Dooley Signed-off-by: Linus Walleij --- drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c index ea1026a0d22c..dafca82f3e54 100644 --- a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c +++ b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c @@ -86,7 +86,7 @@ static struct mpfs_pinctrl_bank_voltage mpfs_pinctrl_bank_voltages[8] = { { .uv = 1800000, .val = 4 }, { .uv = 2500000, .val = 6 }, { .uv = 3300000, .val = 8 }, - { .uv = 0, .val = 0x3f }, // pin unused + { .uv = 0, .val = 0xf }, // pin unused }; static int mpfs_pinctrl_get_drive_strength_ma(u32 drive_strength) From 8039b75af5808572ff3656eaed07d348aed3989a Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Mon, 31 Aug 2026 11:50:59 +0100 Subject: [PATCH 1002/1317] pinctrl: mpfs-mssio: use correct regmap function to set bank voltage regmap_assign_bits() is not the correct function to use for an RMW operation, as it maps to regmap_set_bits() or regmap_clear_bits() and the former will never zero a bit. Use regmap_update_bits() instead, which will actually set the bank voltages to what have been requested. CC: stable@vger.kernel.org Fixes: 488d704ed7b7 ("pinctrl: add polarfire soc mssio pinctrl driver") Signed-off-by: Conor Dooley Signed-off-by: Linus Walleij --- drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c index dafca82f3e54..92d38e5336de 100644 --- a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c +++ b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c @@ -156,10 +156,10 @@ static void mpfs_pinctrl_set_bank_voltage(struct mpfs_pinctrl *pctrl, unsigned i u32 val = FIELD_PREP(MPFS_PINCTRL_BANK_VOLTAGE_MASK, bank_voltage); if (pin < MPFS_PINCTRL_BANK2_START) - regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, + regmap_update_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, MPFS_PINCTRL_BANK_VOLTAGE_MASK, val); else - regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, + regmap_update_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, MPFS_PINCTRL_BANK_VOLTAGE_MASK, val); } From 1589afe2d099d3e817873bc474676968d7080410 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Mon, 14 Sep 2026 00:43:24 -0700 Subject: [PATCH 1003/1317] ALSA: 6fire: fix OOB write from device-reported iso length usb6fire_pcm_in_urb_handler() sizes each outgoing isochronous packet as (actual_length - 4) / (in_n_analog << 2) * (out_n_analog << 2) + 4, where actual_length is the unsigned length the device reported for the matching IN packet. A packet completed with status 0 and actual_length < 4 wraps the subtraction to 0x7fffffec; a zero-length isochronous packet is legal on the bus, and the preceding loop rejects only non-zero status. The sum reaches memset() on out_urb->buffer, a 4832-byte object from kcalloc(PCM_MAX_PACKET_SIZE, PCM_N_PACKETS_PER_URB). Even without the wrap the result is out of bounds: at 88.2/96 kHz the 4-in/6-out scaling turns a full 420-byte IN packet into 628, so eight packets span 5024 bytes of that buffer. usb_submit_urb() rejects an over-long descriptor only after the memset() and the usb6fire_pcm_playback() copy of user PCM data have run. Guard the subtraction as the sibling usb6fire_pcm_capture() already does, and limit the frame count to what fits in rt->out_packet_size, the OUT endpoint's wMaxPacketSize. This bounds total_length by the buffer size while keeping each packet length aligned to a whole output frame. BUG: KASAN: out-of-bounds in usb6fire_pcm_in_urb_handler (sound/usb/6fire/pcm.c:338) Write of size 18446744073709551456 at addr ffff88802a3d0000 by task vhci_rx/5018 Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) kasan_report (mm/kasan/report.c:595) kasan_check_range (mm/kasan/generic.c:186 mm/kasan/generic.c:200) __asan_memset (mm/kasan/shadow.c:84) usb6fire_pcm_in_urb_handler (sound/usb/6fire/pcm.c:338) __usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1657) usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1741) vhci_rx_loop (drivers/usb/usbip/vhci_rx.c:107 drivers/usb/usbip/vhci_rx.c:242) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Allocated by task 10: __kmalloc_cache_noprof (mm/slub.c:5563) usb6fire_pcm_init (sound/usb/6fire/pcm.c:560 sound/usb/6fire/pcm.c:595) usb6fire_chip_probe (sound/usb/6fire/chip.c:133) usb_probe_interface (drivers/usb/core/driver.c:399) The buggy address belongs to the object at ffff88802a3d0000 which belongs to the cache kmalloc-8k of size 8192 The buggy address is located 0 bytes inside of 4832-byte region [ffff88802a3d0000, ffff88802a3d12e0) Kernel panic - not syncing: Fatal exception in interrupt Fixes: c6d43ba816d1 ("ALSA: usb/6fire - Driver for TerraTec DMX 6Fire USB") Reported-by: co+855929c2df672879@bugs.sh Closes: https://lore.kernel.org/all/gisnub8aWGLbyZLcDCSc7zWsHonMWGcyRgt5%40bugs.sh/ Assisted-by: LLM Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260914074324.3590843-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/6fire/pcm.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index 21789db6657d..0285d79ace0f 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -335,11 +335,19 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* setup out urb structure */ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + unsigned int frames = 0; + isoc_out = &out_urb->instance->iso_frame_desc[i]; isoc_in = &in_urb->instance->iso_frame_desc[i]; + if (isoc_in->actual_length > 4) + frames = (isoc_in->actual_length - 4) + / (rt->in_n_analog << 2); + frames = min_t(unsigned int, frames, + (rt->out_packet_size - 4) + / (rt->out_n_analog << 2)); + isoc_out->offset = total_length; - isoc_out->length = (isoc_in->actual_length - 4) / (rt->in_n_analog << 2) - * (rt->out_n_analog << 2) + 4; + isoc_out->length = frames * (rt->out_n_analog << 2) + 4; isoc_out->status = 0; total_length += isoc_out->length; } From 4396d70bb7fec531bcf934fed016b2f3300c670b Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 18:35:38 -0400 Subject: [PATCH 1004/1317] mmc: sdhci-of-aspeed: Remove children before releasing SDC resources Probe failure and removal leave SDHCI child devices registered after the parent clock and managed resources are released. Unregister the OF children in reverse order before disabling the parent clock on both paths. Use of_platform_device_destroy() because manual child creation does not set the flag required by of_platform_depopulate(). This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: bb7b8ec62dfb ("mmc: sdhci-of-aspeed: Add support for the ASPEED SD controller") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Assisted-by: OpenAI:GPT-5.6 Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-aspeed.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c index f5d973783cbe..d317626feb4a 100644 --- a/drivers/mmc/host/sdhci-of-aspeed.c +++ b/drivers/mmc/host/sdhci-of-aspeed.c @@ -560,12 +560,14 @@ static int aspeed_sdc_probe(struct platform_device *pdev) cpdev = of_platform_device_create(child, NULL, &pdev->dev); if (!cpdev) { ret = -ENODEV; - goto err_clk; + goto err_children; } } return 0; +err_children: + device_for_each_child_reverse(&pdev->dev, NULL, of_platform_device_destroy); err_clk: clk_disable_unprepare(sdc->clk); return ret; @@ -575,6 +577,7 @@ static void aspeed_sdc_remove(struct platform_device *pdev) { struct aspeed_sdc *sdc = dev_get_drvdata(&pdev->dev); + device_for_each_child_reverse(&pdev->dev, NULL, of_platform_device_destroy); clk_disable_unprepare(sdc->clk); } From d7fd1f98607f2cd358e583f9548bdf0173090a86 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 3 Sep 2026 14:06:02 +0900 Subject: [PATCH 1005/1317] ksmbd: follow SMB2 session expiration semantics ksmbd_session_register() destroys valid sessions after ten seconds of inactivity whenever a client starts another SessionSetup exchange. This confuses Session.IdleTime with Session.ExpirationTime. Windows can create additional authenticated sessions on an existing connection, so deleting the older session invalidates its tree connects and makes mapped drives fail with STATUS_NETWORK_NAME_DELETED. The session expiration rules require the server to change a valid session to expired only after its credential expiration time passes. A valid or expired session otherwise keeps its connection from being scavenged. Connections that have not negotiated a dialect, have no sessions, or have only InProgress sessions are disconnected after an implementation-specific timeout. Use the Windows-compatible 45 second value and run the expiration check periodically for both TCP and SMB Direct. Keep zero as an infinite credential expiration time, and count each Valid-to-Expired transition. Set expired sessions to InProgress when they reauthenticate. If authentication fails, remove the session from the global and per-connection tables immediately, including SMB3 multichannel connections. Retain protection against abandoned SessionId-zero exchanges by allowing only one InProgress authentication per connection. Additional exchanges fail with STATUS_INSUFFICIENT_RESOURCES, and stale InProgress sessions are reaped after the same 45 second setup timeout. This bounds the original unauthenticated memory-exhaustion path without evicting established sessions. Fixes: ea174a918939 ("ksmbd: destroy expired sessions") Reported-by: Mobin Aydinfar Signed-off-by: Namjae Jeon --- fs/smb/server/connection.c | 72 +++++++++++++++++ fs/smb/server/connection.h | 3 + fs/smb/server/mgmt/user_session.c | 124 ++++++++++++++++++++++++++++-- fs/smb/server/mgmt/user_session.h | 8 +- fs/smb/server/proc.c | 2 + fs/smb/server/server.c | 2 +- fs/smb/server/smb2pdu.c | 37 ++++++--- fs/smb/server/smb2pdu.h | 2 - fs/smb/server/stats.h | 1 + 9 files changed, 227 insertions(+), 24 deletions(-) diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index 4cb92d6599ee..d211861ff86f 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -22,6 +22,8 @@ static DEFINE_MUTEX(init_lock); static struct ksmbd_conn_ops default_conn_ops; +static struct delayed_work session_expiration_work; +static bool stopping_session_expiration_work; DEFINE_HASHTABLE(conn_list, CONN_HASH_BITS); DECLARE_RWSEM(conn_list_lock); @@ -158,18 +160,28 @@ static void delete_proc_clients(void) {} static struct workqueue_struct *ksmbd_conn_wq; +static void ksmbd_session_expiration_worker(struct work_struct *work); + int ksmbd_conn_wq_init(void) { ksmbd_conn_wq = alloc_workqueue("ksmbd-conn-release", WQ_UNBOUND | WQ_MEM_RECLAIM, 0); if (!ksmbd_conn_wq) return -ENOMEM; + + WRITE_ONCE(stopping_session_expiration_work, false); + INIT_DELAYED_WORK(&session_expiration_work, + ksmbd_session_expiration_worker); + queue_delayed_work(ksmbd_conn_wq, &session_expiration_work, + KSMBD_SESSION_EXPIRATION_INTERVAL); return 0; } void ksmbd_conn_wq_destroy(void) { if (ksmbd_conn_wq) { + WRITE_ONCE(stopping_session_expiration_work, true); + cancel_delayed_work_sync(&session_expiration_work); destroy_workqueue(ksmbd_conn_wq); ksmbd_conn_wq = NULL; } @@ -279,6 +291,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void) return NULL; conn->need_neg = true; + conn->creation_time = jiffies; ksmbd_conn_set_new(conn); conn->local_nls = load_nls("utf8"); if (!conn->local_nls) @@ -588,6 +601,20 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn) if (kthread_should_stop()) return false; + /* + * Stale connections that have not completed NEGOTIATE and SESSION_SETUP + * must be disconnected. Do not race a request that is currently + * completing authentication. + */ + if (!atomic_read(&conn->req_running) && + time_after(jiffies, conn->creation_time + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) && + (READ_ONCE(conn->need_neg) || + !ksmbd_conn_has_valid_or_expired_session(conn))) { + ksmbd_debug(CONN, "Connection setup timed out\n"); + return false; + } + if (atomic_read(&conn->stats.open_files_count) > 0) return true; @@ -605,6 +632,51 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn) return true; } +static void ksmbd_session_expiration_worker(struct work_struct *work) +{ + struct ksmbd_conn *conn, *target; + int bkt; + + if (!ksmbd_server_running()) + goto reschedule; + + ksmbd_expire_sessions(); + + /* + * An old connection without a Valid or Expired session must be + * disconnected. Process one connection at a time without holding + * conn_list_lock across transport shutdown. + */ +again: + target = NULL; + down_read(&conn_list_lock); + hash_for_each(conn_list, bkt, conn, hlist) { + if (ksmbd_conn_exiting(conn) || ksmbd_conn_releasing(conn) || + atomic_read(&conn->req_running) || + time_before_eq(jiffies, conn->creation_time + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) || + (!READ_ONCE(conn->need_neg) && + ksmbd_conn_has_valid_or_expired_session(conn))) + continue; + + target = ksmbd_conn_get(conn); + break; + } + up_read(&conn_list_lock); + + if (target) { + ksmbd_debug(CONN, "Connection setup timed out\n"); + ksmbd_conn_abort(target); + ksmbd_conn_put(target); + goto again; + } + +reschedule: + if (!READ_ONCE(stopping_session_expiration_work)) + queue_delayed_work(ksmbd_conn_wq, &session_expiration_work, + KSMBD_SESSION_EXPIRATION_INTERVAL); +} + /* "+2" for BCC field (ByteCount, 2 bytes) */ #define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2) #define SMB2_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb2_pdu)) diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h index 63484c8efbbd..371f17b4f02a 100644 --- a/fs/smb/server/connection.h +++ b/fs/smb/server/connection.h @@ -77,6 +77,7 @@ struct ksmbd_conn { struct rw_semaphore session_lock; /* smb session 1 per user */ struct xarray sessions; + unsigned long creation_time; unsigned long last_active; /* How many request are running currently */ atomic_t req_running; @@ -192,6 +193,8 @@ struct ksmbd_transport { #define KSMBD_TCP_RECV_TIMEOUT (7 * HZ) #define KSMBD_TCP_SEND_TIMEOUT (5 * HZ) +#define KSMBD_SESSION_EXPIRATION_INTERVAL (5 * HZ) +#define KSMBD_UNAUTHENTICATED_CONN_TIMEOUT (45 * HZ) #define KSMBD_TCP_PEER_SOCKADDR(c) ((struct sockaddr *)&((c)->peer_addr)) #define CONN_HASH_BITS 12 diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 2eb8f730e99e..44dc3f800cd4 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -22,6 +22,7 @@ static DEFINE_IDA(session_ida); #define SESSION_HASH_BITS 12 +#define KSMBD_MAX_PENDING_SESSIONS 1 static DEFINE_HASHTABLE(sessions_table, SESSION_HASH_BITS); static DECLARE_RWSEM(sessions_table_lock); @@ -432,26 +433,31 @@ struct ksmbd_session *__session_lookup(unsigned long long id) return NULL; } -static void ksmbd_expire_session(struct ksmbd_conn *conn) +static bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn) { unsigned long id; struct ksmbd_session *sess; + unsigned int pending = 0; down_write(&sessions_table_lock); down_write(&conn->session_lock); xa_for_each(&conn->sessions, id, sess) { + if (READ_ONCE(sess->state) != SMB2_SESSION_IN_PROGRESS) + continue; + if (atomic_read(&sess->refcnt) <= 1 && - (sess->state != SMB2_SESSION_VALID || - time_after(jiffies, - sess->last_active + SMB2_SESSION_TIMEOUT))) { + time_after(jiffies, sess->last_active + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT)) { xa_erase(&conn->sessions, sess->id); ksmbd_session_remove_from_table(sess); ksmbd_session_destroy(sess); continue; } + pending++; } up_write(&conn->session_lock); up_write(&sessions_table_lock); + return pending >= KSMBD_MAX_PENDING_SESSIONS; } int ksmbd_session_register(struct ksmbd_conn *conn, @@ -461,9 +467,12 @@ int ksmbd_session_register(struct ksmbd_conn *conn, sess->dialect = conn->dialect; memcpy(sess->ClientGUID, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE); - ksmbd_expire_session(conn); - ret = xa_err(xa_store(&conn->sessions, sess->id, sess, - KSMBD_DEFAULT_GFP)); + /* Bound abandoned SessionId-zero authentication exchanges. */ + if (ksmbd_too_many_session_setups(conn)) + ret = -ENOSPC; + else + ret = xa_err(xa_store(&conn->sessions, sess->id, sess, + KSMBD_DEFAULT_GFP)); if (ret) { down_write(&sessions_table_lock); ksmbd_session_remove_from_table(sess); @@ -474,6 +483,105 @@ int ksmbd_session_register(struct ksmbd_conn *conn, return ret; } +void ksmbd_session_unregister(struct ksmbd_conn *conn, + struct ksmbd_session *sess) +{ + struct ksmbd_conn *session_conns[KSMBD_MAX_CHANNELS]; + struct channel *chann; + unsigned long index; + unsigned int nr_conns = 0, i; + bool removed = false; + + down_write(&sessions_table_lock); + if (!hlist_unhashed(&sess->hlist)) { + /* Keep each channel connection stable under sessions_table_lock. */ + down_read(&sess->chann_lock); + xa_for_each(&sess->ksmbd_chann_list, index, chann) { + if (nr_conns == ARRAY_SIZE(session_conns)) + break; + session_conns[nr_conns++] = chann->conn; + } + up_read(&sess->chann_lock); + + ksmbd_session_remove_from_table(sess); + removed = true; + } + + down_write(&conn->session_lock); + if (xa_load(&conn->sessions, sess->id) == sess) + xa_erase(&conn->sessions, sess->id); + up_write(&conn->session_lock); + for (i = 0; i < nr_conns; i++) { + if (session_conns[i] == conn) + continue; + down_write(&session_conns[i]->session_lock); + if (xa_load(&session_conns[i]->sessions, sess->id) == sess) + xa_erase(&session_conns[i]->sessions, sess->id); + up_write(&session_conns[i]->session_lock); + } + up_write(&sessions_table_lock); + + if (removed) + ksmbd_user_session_put(sess); +} + +bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn) +{ + struct ksmbd_session *sess; + unsigned long id; + int state, bkt; + bool found = false; + + down_read(&conn->session_lock); + xa_for_each(&conn->sessions, id, sess) { + state = READ_ONCE(sess->state); + if (state == SMB2_SESSION_VALID || + state == SMB2_SESSION_EXPIRED) { + found = true; + break; + } + } + up_read(&conn->session_lock); + if (found) + return true; + + /* A session bound through SMB3 multichannel is not in conn->sessions. */ + down_read(&sessions_table_lock); + hash_for_each(sessions_table, bkt, sess, hlist) { + state = READ_ONCE(sess->state); + if (state != SMB2_SESSION_VALID && + state != SMB2_SESSION_EXPIRED) + continue; + + down_read(&sess->chann_lock); + found = xa_load(&sess->ksmbd_chann_list, (long)conn); + up_read(&sess->chann_lock); + if (found) + break; + } + up_read(&sessions_table_lock); + return found; +} + +void ksmbd_expire_sessions(void) +{ + struct ksmbd_session *sess; + u64 now = ktime_get_real_seconds(); + int bkt; + + down_read(&sessions_table_lock); + hash_for_each(sessions_table, bkt, sess, hlist) { + if (READ_ONCE(sess->state) != SMB2_SESSION_VALID || + !sess->kerberos_expiry || now < sess->kerberos_expiry) + continue; + + if (cmpxchg(&sess->state, SMB2_SESSION_VALID, + SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID) + ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS); + } + up_read(&sessions_table_lock); +} + static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess) { struct channel *chann; @@ -488,7 +596,7 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess) return 0; } -void ksmbd_sessions_deregister(struct ksmbd_conn *conn) +void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn) { struct ksmbd_session *sess; unsigned long id; diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h index 3e52d4cc1324..217258551d6d 100644 --- a/fs/smb/server/mgmt/user_session.h +++ b/fs/smb/server/mgmt/user_session.h @@ -72,6 +72,8 @@ struct ksmbd_session { struct rw_semaphore rpc_lock; }; +#define KSMBD_MAX_CHANNELS 32 + static inline int test_session_flag(struct ksmbd_session *sess, int bit) { return sess->flags & bit; @@ -98,7 +100,11 @@ bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn, unsigned long long id); int ksmbd_session_register(struct ksmbd_conn *conn, struct ksmbd_session *sess); -void ksmbd_sessions_deregister(struct ksmbd_conn *conn); +void ksmbd_session_unregister(struct ksmbd_conn *conn, + struct ksmbd_session *sess); +void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn); +bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn); +void ksmbd_expire_sessions(void); struct ksmbd_session *__session_lookup(unsigned long long id); struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn, unsigned long long id); diff --git a/fs/smb/server/proc.c b/fs/smb/server/proc.c index 826353ed0553..19f0f2cfbf54 100644 --- a/fs/smb/server/proc.c +++ b/fs/smb/server/proc.c @@ -178,6 +178,8 @@ static int proc_show_ksmbd_stats(struct seq_file *m, void *v) proc_show_runtime_totals(m); seq_printf(m, "sessions:\t%lld\n", ksmbd_counter_sum(KSMBD_COUNTER_SESSIONS)); + seq_printf(m, "session_timeouts:\t%lld\n", + ksmbd_counter_sum(KSMBD_COUNTER_SESSION_TIMEOUTS)); seq_printf(m, "tree_connects:\t%lld\n", ksmbd_counter_sum(KSMBD_COUNTER_TREE_CONNS)); seq_printf(m, "requests:\t%lld\n", diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c index 0069d4e6a60a..0827c8c51006 100644 --- a/fs/smb/server/server.c +++ b/fs/smb/server/server.c @@ -414,7 +414,7 @@ static int ksmbd_server_process_request(struct ksmbd_conn *conn) static int ksmbd_server_terminate_conn(struct ksmbd_conn *conn) { - ksmbd_sessions_deregister(conn); + ksmbd_conn_sessions_cleanup(conn); destroy_lease_table(conn); return 0; } diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index b7ce67094626..8acc5174530b 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -85,8 +85,6 @@ struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn return chann; } -#define KSMBD_MAX_CHANNELS 32 - static int register_session_channel(struct ksmbd_session *sess, struct ksmbd_conn *conn, const char *sess_key) @@ -933,8 +931,14 @@ static bool smb2_session_expired_cmd_allowed(struct ksmbd_work *work, static bool smb2_session_kerberos_expired(struct ksmbd_session *sess) { - return sess->kerberos_expiry && - ktime_get_real_seconds() >= sess->kerberos_expiry; + if (!sess->kerberos_expiry || + ktime_get_real_seconds() < sess->kerberos_expiry) + return false; + + if (cmpxchg(&sess->state, SMB2_SESSION_VALID, + SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID) + ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS); + return true; } /** @@ -969,9 +973,8 @@ int smb2_check_user_session(struct ksmbd_work *work) if (!work->next_smb2_rcv_hdr_off && sess_id) work->sess = ksmbd_session_lookup_all_states(conn, sess_id); if (work->sess) { - if (smb2_session_kerberos_expired(work->sess)) { - work->sess->state = SMB2_SESSION_EXPIRED; - } else if (work->sess->state != SMB2_SESSION_VALID) { + if (!smb2_session_kerberos_expired(work->sess) && + work->sess->state != SMB2_SESSION_VALID) { ksmbd_user_session_put(work->sess); work->sess = NULL; } @@ -996,8 +999,7 @@ int smb2_check_user_session(struct ksmbd_work *work) sess_id, work->sess->id); return -EINVAL; } - if (smb2_session_kerberos_expired(work->sess)) - work->sess->state = SMB2_SESSION_EXPIRED; + smb2_session_kerberos_expired(work->sess); if (work->sess->state != SMB2_SESSION_VALID) { pr_err("compound request on a non-valid session (state %d)\n", work->sess->state); @@ -1014,7 +1016,6 @@ int smb2_check_user_session(struct ksmbd_work *work) work->sess = ksmbd_session_lookup_all_states(conn, sess_id); if (work->sess) { if (smb2_session_kerberos_expired(work->sess)) { - work->sess->state = SMB2_SESSION_EXPIRED; return smb2_session_expired_cmd_allowed(work, cmd) ? 1 : -EKEYEXPIRED; } @@ -2436,7 +2437,7 @@ int smb2_sess_setup(struct ksmbd_work *work) struct ksmbd_conn *conn = work->conn; struct smb2_sess_setup_req *req; struct smb2_sess_setup_rsp *rsp; - struct ksmbd_session *sess; + struct ksmbd_session *sess = NULL; struct negotiate_message *negblob; unsigned int negblob_len, negblob_off; int rc = 0; @@ -2592,6 +2593,9 @@ int smb2_sess_setup(struct ksmbd_work *work) goto out_err; } + if (work->session_setup_reauth) + WRITE_ONCE(sess->state, SMB2_SESSION_IN_PROGRESS); + conn->binding = false; } work->sess = sess; @@ -2703,6 +2707,14 @@ int smb2_sess_setup(struct ksmbd_work *work) } if (rc < 0) { + bool setup_in_progress = sess && + READ_ONCE(sess->state) == SMB2_SESSION_IN_PROGRESS && + !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING); + + /* Authentication errors must not leave the new session published. */ + if (setup_in_progress) + ksmbd_session_unregister(conn, sess); + if (sess && conn->dialect == SMB311_PROT_ID && (req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { struct preauth_session *preauth_sess; @@ -2736,7 +2748,8 @@ int smb2_sess_setup(struct ksmbd_work *work) * For binding requests, session belongs to another * connection. Do not expire it. */ - if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { + if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) && + !setup_in_progress) { sess->last_active = jiffies; sess->kerberos_expiry = 0; sess->state = SMB2_SESSION_EXPIRED; diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index 3f08d1ca5a38..a6200d8630e2 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -61,8 +61,6 @@ struct preauth_integrity_info { #define SMB2_SESSION_IN_PROGRESS BIT(0) #define SMB2_SESSION_VALID BIT(1) -#define SMB2_SESSION_TIMEOUT (10 * HZ) - /* Apple Defined Contexts */ #define SMB2_CREATE_AAPL "AAPL" diff --git a/fs/smb/server/stats.h b/fs/smb/server/stats.h index bc864efa0d46..8b32b8b4e8be 100644 --- a/fs/smb/server/stats.h +++ b/fs/smb/server/stats.h @@ -15,6 +15,7 @@ enum { KSMBD_COUNTER_SESSIONS = 0, + KSMBD_COUNTER_SESSION_TIMEOUTS, KSMBD_COUNTER_TREE_CONNS, KSMBD_COUNTER_REQUESTS, KSMBD_COUNTER_STATUS_SUCCESS, From 65bcc5f89704efe5b9d69d4ea2c1002d90c31382 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Mon, 14 Sep 2026 11:50:07 +0200 Subject: [PATCH 1006/1317] HID: amd_sfh: Validate PCI BAR size before mapping The amd_sfh driver maps PCI BAR 2 using pcim_iomap_regions() and subsequently accesses MMIO registers at offsets up to 0x10958 (e.g., AMD_P2C_MSG3 at 0x1068C). However, the driver never validates that the BAR size is large enough to cover these accesses. If the driver is bound to a device with a smaller BAR 2, this leads to an out-of-bounds memory access and a page fault during the probe function. For example, a page fault can occur when reading from privdata->mmio + AMD_P2C_MSG3 in mp2_select_ops(): BUG: unable to handle page fault for address: ffffc9000390368c PGD 100000067 P4D 100000067 PUD 1012c1067 PMD 105b64067 PTE 0 Oops: Oops: 0000 [#1] SMP KASAN NOPTI RIP: 0010:readl arch/x86/include/asm/io.h:59 [inline] RIP: 0010:mp2_select_ops drivers/hid/amd-sfh-hid/amd_sfh_pcie.c:282 [inline] RIP: 0010:amd_mp2_pci_probe+0x337/0x5f0 drivers/hid/amd-sfh-hid/amd_sfh_pcie.c:487 Call Trace: local_pci_probe drivers/pci/pci-driver.c:332 [inline] pci_call_probe drivers/pci/pci-driver.c:394 [inline] __pci_device_probe drivers/pci/pci-driver.c:455 [inline] pci_device_probe+0x431/0xc90 drivers/pci/pci-driver.c:489 Fix this by verifying that the length of BAR 2 is at least 128KB before attempting to map it. Since the maximum accessed offset is 0x10958, and PCI BAR sizes are powers of 2, any legitimate hardware will have a BAR size of at least 128KB. Fixes: 4f567b9f8141 ("SFH: PCIe driver to add support of AMD sensor fusion hub") Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+4eadd4dfe9e66522bae8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4eadd4dfe9e66522bae8 Link: https://syzkaller.appspot.com/ai_job?id=3bc1c45c-548f-4ab5-8243-d2c8ec321d6c Signed-off-by: Slawomir Stepien Acked-by: Basavaraj Natikar Link: https://syzkaller.appspot.com/bug?extid=4eadd4dfe9e66522bae8 Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/amd_sfh_common.h | 4 ++++ drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index f8c6b7fc34fb..3d29119a3765 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -13,11 +13,15 @@ #include #include #include +#include #include "amd_sfh_hid.h" #define PCI_DEVICE_ID_AMD_MP2 0x15E4 #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A +/* The BAR 2 size must cover the highest register offset (0x10958) */ +#define AMD_SFH_MIN_BAR_SIZE SZ_128K + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index eda26a094d3f..061a63519d44 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -497,6 +497,16 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i if (rc) return rc; + if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { + dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); + return -ENODEV; + } + + if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { + dev_err(&pdev->dev, "BAR 2 is too small\n"); + return -EINVAL; + } + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); if (rc) return rc; From ba7a79b9bc87776c8c1808407a7508a8be3a789e Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Mon, 14 Sep 2026 10:13:50 +0200 Subject: [PATCH 1007/1317] wifi: cfg80211: verify if AP_VLAN belongs to the correct AP The get_vlan() only checks if NL80211_ATTR_STA_VLAN target is an AP/AP_VLAN/P2P_GO interface on the same wiphy. It has no notion of which specific AP a given AP_VLAN belongs to. Fix that by comparing the ethernet addresses of the two net devices. Given VLAN A' must have the same address as AP A. Otherwise, return error code. Signed-off-by: Slawomir Stepien Reported-by: Johannes Berg Link: https://lore.kernel.org/all/22e7ddfc50d7a6a16c437b876dab5fe223799610.camel@sipsolutions.net/ Link: https://patch.msgid.link/20260914081350.83484-1-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 44f2bad08670..9fd1367483c6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8966,10 +8966,12 @@ int cfg80211_check_station_change(struct wiphy *wiphy, EXPORT_SYMBOL(cfg80211_check_station_change); /* - * Get vlan interface making sure it is running and on the right wiphy. + * Get vlan interface making sure it is running, on the right wiphy + * and actually belongs to the given AP/P2P_GO interface. */ static struct net_device *get_vlan(struct genl_info *info, - struct cfg80211_registered_device *rdev) + struct cfg80211_registered_device *rdev, + struct net_device *dev) { struct nlattr *vlanattr = info->attrs[NL80211_ATTR_STA_VLAN]; struct net_device *v; @@ -8999,6 +9001,12 @@ static struct net_device *get_vlan(struct genl_info *info, goto error; } + /* Check if the VLAN interface belongs to the AP interface */ + if (!dev || !ether_addr_equal(v->dev_addr, dev->dev_addr)) { + ret = -EINVAL; + goto error; + } + return v; error: dev_put(v); @@ -9296,7 +9304,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) if (err) return err; - params.vlan = get_vlan(info, rdev); + params.vlan = get_vlan(info, rdev, dev); if (IS_ERR(params.vlan)) return PTR_ERR(params.vlan); @@ -9597,7 +9605,7 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) } /* must be last in here for error handling */ - params.vlan = get_vlan(info, rdev); + params.vlan = get_vlan(info, rdev, dev); if (IS_ERR(params.vlan)) return PTR_ERR(params.vlan); break; From 4ae3128c230372b43d0c417e0fcf816e8290e9fa Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:16 +0200 Subject: [PATCH 1008/1317] wifi: cfg80211: do not support direct add of station to AP_VLAN interfaces Prevent userspace from adding stations directly to AP_VLAN type interfaces. Userspace should first add the station to the base interface (AP type) and then can use CMD_SET_STATION to move it to AP_VLAN. The other way is by using NL80211_ATTR_STA_VLAN. Without this path, we cannot check if the AP has been started before adding the station - wdev for AP_VLAN does not store information about the base AP interface. Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-1-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9fd1367483c6..677a78f72b0e 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9564,7 +9564,6 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) switch (wdev->iftype) { case NL80211_IFTYPE_AP: - case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_P2P_GO: /* ignore WME attributes if iface/sta is not capable */ if (!(rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) || From e3d1acb0276742f288094cd7a497745364876bb8 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:17 +0200 Subject: [PATCH 1009/1317] wifi: cfg80211: move link_id validation earlier in nl80211_new_station() I do not see a reason why this check is so low in the function. Move it up right next to param fetch. This new position is more beneficial for AP/Link state check that will be added in upcoming commit. Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-2-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 677a78f72b0e..7b0ad66cf587 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9384,6 +9384,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) params.link_sta_params.link_id = nl80211_link_id_or_invalid(info->attrs); + if (wdev->valid_links) { + if (params.link_sta_params.link_id < 0) + return -EINVAL; + if (!(wdev->valid_links & BIT(params.link_sta_params.link_id))) + return -ENOLINK; + } else { + if (params.link_sta_params.link_id >= 0) + return -EINVAL; + } + if (info->attrs[NL80211_ATTR_MLD_ADDR]) { mac_addr = nla_data(info->attrs[NL80211_ATTR_MLD_ADDR]); params.link_sta_params.mld_mac = mac_addr; @@ -9656,27 +9666,10 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) /* be aware of params.vlan when changing code here */ - if (wdev->valid_links) { - if (params.link_sta_params.link_id < 0) { - err = -EINVAL; - goto out; - } - if (!(wdev->valid_links & BIT(params.link_sta_params.link_id))) { - err = -ENOLINK; - goto out; - } - } else { - if (params.link_sta_params.link_id >= 0) { - err = -EINVAL; - goto out; - } - } - params.epp_peer = nla_get_flag(info->attrs[NL80211_ATTR_EPP_PEER]); err = rdev_add_station(rdev, wdev, mac_addr, ¶ms); -out: dev_put(params.vlan); return err; } From a842cfc1d6d85b34ad73959460def4d4641e82e8 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:18 +0200 Subject: [PATCH 1010/1317] wifi: cfg80211: check if AP has been started or joined a mesh before adding new station Adding a new station to AP makes only sense when the AP has been started (nl80211_start_ap()) or joined a mesh (__cfg80211_join_mesh()). Check if AP is up and beaconing on the link or joined the mesh, when adding new station. Return error if this isn't the case. Note that libertas devices need special handling since they do not implement join_mesh() and the decision must be made on channel definition. Reported-by: syzbot+9bdc0c5998ab45b05030@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9bdc0c5998ab45b05030 Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-3-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 7b0ad66cf587..f18d526149c6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9336,7 +9336,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; - int err; + int err, link_id; struct wireless_dev *wdev = info->user_ptr[1]; struct net_device *dev = wdev->netdev; struct station_parameters params; @@ -9575,6 +9575,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) switch (wdev->iftype) { case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: + /* Add a new station only after the AP and link has been started */ + link_id = wdev->valid_links ? params.link_sta_params.link_id : 0; + if (!wdev->links[link_id].ap.beacon_interval) + return -ENETDOWN; + /* ignore WME attributes if iface/sta is not capable */ if (!(rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) || !(params.sta_flags_set & BIT(NL80211_STA_FLAG_WME))) @@ -9619,6 +9624,19 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) return PTR_ERR(params.vlan); break; case NL80211_IFTYPE_MESH_POINT: + /* + * Add a new station only after the mesh has been started. + * libertas doesn't implement join_mesh(); it configures the + * mesh via sysfs and joins it when the channel is set, so + * use that as the started indication instead. + */ + if (rdev->ops->libertas_set_mesh_channel) { + if (!wdev->u.mesh.chandef.chan) + return -ENETDOWN; + } else if (!wdev->u.mesh.beacon_interval) { + return -ENETDOWN; + } + /* ignore uAPSD data */ params.sta_modify_mask &= ~STATION_PARAM_APPLY_UAPSD; From e5c8d7acd31b27057ea42cd405d0b3ece097bc89 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 9 Sep 2026 12:37:18 +0000 Subject: [PATCH 1011/1317] wifi: virt_wifi: don't transfer operstate before register virt_wifi_newlink() calls netif_stacked_transfer_operstate() before register_netdevice(). If the lower device is dormant, that queues the new netdev on lweventlist while it is still uninitialized. If registration fails after that, for example because of an invalid name such as "bad/name", free_netdev() immediately frees the object. A later linkwatch_fire_event() then use-after-frees the list entry. Move the transfer to after netdev_upper_dev_link(), as macvlan and ipvlan already do. Fixes: c7cdba31ed8b ("mac80211-next: rtnetlink wifi simulation device") Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Link: https://patch.msgid.link/f5a832fb0ab228ce6e2b5a91fba4ca8b79198a2f.1788948455.git.zihanx@nebusec.ai Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/virt_wifi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/virtual/virt_wifi.c b/drivers/net/wireless/virtual/virt_wifi.c index b69a4650fba8..48afc2432f93 100644 --- a/drivers/net/wireless/virtual/virt_wifi.c +++ b/drivers/net/wireless/virtual/virt_wifi.c @@ -558,7 +558,6 @@ static int virt_wifi_newlink(struct net_device *dev, } eth_hw_addr_inherit(dev, priv->lowerdev); - netif_stacked_transfer_operstate(priv->lowerdev, dev); dev->ieee80211_ptr = kzalloc_obj(*dev->ieee80211_ptr); @@ -584,6 +583,8 @@ static int virt_wifi_newlink(struct net_device *dev, goto unregister_netdev; } + netif_stacked_transfer_operstate(priv->lowerdev, dev); + dev->priv_destructor = virt_wifi_net_device_destructor; priv->being_deleted = false; priv->is_connected = false; From 06f42accaf3c6aecab1dcc57f68dde6c06c8b380 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 9 Sep 2026 15:11:24 +0900 Subject: [PATCH 1012/1317] wifi: libipw: reject TKIP frames without a full MIC libipw_michael_mic_verify() assumes that an skb contains an eight-byte Michael MIC. A short TKIP frame makes the unsigned payload length wrap, causing michael_mic() to read past the skb. Check that the MIC is present before verifying it, and use the existing MICHAEL_MIC_LEN constant for all MIC lengths in the verifier. Fixes: b453872c35cf ("[NET] ieee80211 subsystem") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260909061124.3802517-1-4ncienth@gmail.com Signed-off-by: Johannes Berg --- .../net/wireless/intel/ipw2x00/libipw_crypto_tkip.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c b/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c index 24bb28ab7a49..2b0cf0ec496a 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c @@ -474,14 +474,16 @@ static int libipw_michael_mic_verify(struct sk_buff *skb, int keyidx, int hdr_len, void *priv) { struct libipw_tkip_data *tkey = priv; - u8 mic[8]; + u8 mic[MICHAEL_MIC_LEN]; - if (!tkey->key_set) + if (!tkey->key_set || skb->len < hdr_len + MICHAEL_MIC_LEN) return -1; michael_mic(&tkey->key[24], (struct ieee80211_hdr *)skb->data, - skb->data + hdr_len, skb->len - 8 - hdr_len, mic); - if (memcmp(mic, skb->data + skb->len - 8, 8) != 0) { + skb->data + hdr_len, + skb->len - MICHAEL_MIC_LEN - hdr_len, mic); + if (memcmp(mic, skb->data + skb->len - MICHAEL_MIC_LEN, + MICHAEL_MIC_LEN) != 0) { struct ieee80211_hdr *hdr; hdr = (struct ieee80211_hdr *)skb->data; printk(KERN_DEBUG "%s: Michael MIC verification failed for " @@ -499,7 +501,7 @@ static int libipw_michael_mic_verify(struct sk_buff *skb, int keyidx, tkey->rx_iv32 = tkey->rx_iv32_new; tkey->rx_iv16 = tkey->rx_iv16_new; - skb_trim(skb, skb->len - 8); + skb_trim(skb, skb->len - MICHAEL_MIC_LEN); return 0; } From 247a82da6f563dcfd9074a68f99a0c0997d0679c Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:48 +0100 Subject: [PATCH 1013/1317] drm/xe/mmio_gem: forbid VMA split The fault handler assumes it always operates on a VMA spanning the entire GEM object. This does not hold when the VMA has been split, e.g. by a partial munmap or mprotect. In that case the handler may map wrong physical pages or cause SIGBUS. Handle this by forbidding VMA split, as partial unmaps are not deemed useful for MMIO GEMs. Suggested-by: Matthew Auld Signed-off-by: Ilia Levi Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-11-matthew.auld@intel.com (cherry picked from commit f3391a0b12d7bf826a0b21600d2f294f3dce4c14) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3741ae60f532..d54477b93b6e 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -39,10 +39,20 @@ struct xe_mmio_gem { phys_addr_t phys_addr; }; +static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) +{ + /* + * Forbid splitting. Together with VM_DONTEXPAND, this keeps the VMA + * matching the GEM object exactly. + */ + return -EINVAL; +} + static const struct vm_operations_struct vm_ops = { .open = drm_gem_vm_open, .close = drm_gem_vm_close, .fault = xe_mmio_gem_vm_fault, + .may_split = xe_mmio_gem_vm_may_split, }; static const struct drm_gem_object_funcs xe_mmio_gem_funcs = { From 819f189265a5955da743e78e20a713a038982e89 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:49 +0100 Subject: [PATCH 1014/1317] drm/xe/mmio_gem: use write-back mapping for dummy page Currently vmf_insert_pfn() maps the dummy page as UC, inheriting the VMA's page protection which was set for the real MMIO region. This conflicts with the direct map's WB mapping of the same page, creating a cache type alias which is architecturally undefined on some platforms. Use vmf_insert_pfn_prot() with a WB pgprot instead. Also simplify to fault in the requested page instead of the whole VMA. Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260525125801.975038-6-ilia.levi%40intel.com Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-12-matthew.auld@intel.com (cherry picked from commit 1e8e28e35df0e77ae1b22fc091c1f422f62fa5e9) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index d54477b93b6e..96f3bf46fd75 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -172,14 +172,13 @@ static void xe_mmio_gem_release_dummy_page(struct drm_device *dev, void *res) __free_page((struct page *)res); } -static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_area_struct *vma) +static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) { + struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; struct drm_device *dev = base->dev; - vm_fault_t ret = VM_FAULT_NOPAGE; struct page *page; unsigned long pfn; - unsigned long i; page = alloc_page(GFP_KERNEL | __GFP_ZERO); if (!page) @@ -190,16 +189,8 @@ static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_area_struct *vma) pfn = page_to_pfn(page); - /* Map the entire VMA to the same dummy page */ - for (i = 0; i < base->size; i += PAGE_SIZE) { - unsigned long addr = vma->vm_start + i; - - ret = vmf_insert_pfn(vma, addr, pfn); - if (ret & VM_FAULT_ERROR) - break; - } - - return ret; + return vmf_insert_pfn_prot(vma, vmf->address, pfn, + vm_get_page_prot(vma->vm_flags)); } static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) @@ -219,7 +210,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) * It is assumed the userspace will receive the notification via some * other channel (e.g. drm uevent). */ - return xe_mmio_gem_vm_fault_dummy_page(vma); + return xe_mmio_gem_vm_fault_dummy_page(vmf); } for (i = 0; i < base->size; i += PAGE_SIZE) { From 2b04d6556964ae9f89819b86a0a7801e39c3aae5 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 4 Sep 2026 13:03:38 -0700 Subject: [PATCH 1015/1317] wifi: mac80211: refuse to make a monitor active when it has no queue A monitor interface only gets a TXQ if it's created active, and one can't be added later. Setting the flag on a down interface is still allowed, so the driver is handed a monitor with no queue. ath9k dereferences it: BUG: kernel NULL pointer dereference, address: 0000000000000066 RIP: 0010:ath_tx_node_init+0x49/0x170 [ath9k] ath9k_add_interface+0x10c/0x140 [ath9k] drv_add_interface+0x54/0x250 [mac80211] ieee80211_do_open+0x32f/0x800 [mac80211] Reached with CAP_NET_ADMIN by "iw dev X set monitor active" followed by "ip link set X up". RTNL is held, so netlink operations block behind it. Refuse the flag when there is no queue to give. Fixes: 79af1f866193 ("mac80211: avoid allocating TXQs that won't be used") Cc: stable@vger.kernel.org Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260904200338.10829-1-lucid_duck@justthetip.ca Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index d3558f0c7550..a1753335eb9d 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -115,6 +115,10 @@ static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata, return -EBUSY; } + /* TXQs are reserved in ieee80211_if_add() and cannot be added later */ + if ((params->flags & MONITOR_FLAG_ACTIVE) && !sdata->vif.txq) + return -EOPNOTSUPP; + /* validate whether MU-MIMO can be configured */ if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) && !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR) && From 0c50663403b7aa271b1974ba32ee6c8296711142 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:50 +0100 Subject: [PATCH 1016/1317] drm/xe/mmio_gem: simplify fault handler loop Make the iteration over the addresses in the VMA more explicit. No functional change, as the VMA matches the GEM object exactly. Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-13-matthew.auld@intel.com (cherry picked from commit 6666ca9192f3bdf839aad33b9e1c9ebb7a222a29) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 96f3bf46fd75..3b6d04efe81a 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -200,7 +200,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) struct xe_mmio_gem *obj = to_xe_mmio_gem(base); struct drm_device *dev = base->dev; vm_fault_t ret = VM_FAULT_NOPAGE; - unsigned long i; + unsigned long addr, pfn; int idx; if (!drm_dev_enter(dev, &idx)) { @@ -213,13 +213,13 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) return xe_mmio_gem_vm_fault_dummy_page(vmf); } - for (i = 0; i < base->size; i += PAGE_SIZE) { - unsigned long addr = vma->vm_start + i; - unsigned long phys_addr = obj->phys_addr + i; - - ret = vmf_insert_pfn(vma, addr, PHYS_PFN(phys_addr)); + pfn = PHYS_PFN(obj->phys_addr); + for (addr = vma->vm_start; addr < vma->vm_end; addr += PAGE_SIZE) { + ret = vmf_insert_pfn(vma, addr, pfn); if (ret & VM_FAULT_ERROR) break; + + pfn++; } drm_dev_exit(idx); From 37fcbd7b2f8996d783932dab11bc668e169b0de6 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 8 Sep 2026 17:50:51 +0100 Subject: [PATCH 1017/1317] drm/xe/mmio_gem: Revoke drm_vma_node on xe_mmio_gem destroy xe_mmio_gem_create() calls drm_vma_node_allow() but nothing ever calls drm_vma_node_revoke(). The drm_vma_offset_file rb-tree entry allocated by drm_vma_node_allow() is not freed by drm_gem_object_release(), so it is leaked on every create/destroy cycle. Add a struct drm_file * parameter to xe_mmio_gem_destroy() and call drm_vma_node_revoke() from there, mirroring the drm_vma_node_allow() call in xe_mmio_gem_create(). Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Suggested-by: Ilia Levi Assisted-by: Claude:claude-opus-4.6 Signed-off-by: Shuicheng Lin Reviewed-by: Ilia Levi Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-14-matthew.auld@intel.com (cherry picked from commit 32f0cb250598456d812fb7ca57a040282858323d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 4 +++- drivers/gpu/drm/xe/xe_mmio_gem.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3b6d04efe81a..3c42d8358c7d 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -138,14 +138,16 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) /** * xe_mmio_gem_destroy - Destroy the GEM object that exposes an MMIO region * @gem: the GEM object to destroy + * @file: DRM file descriptor previously passed to xe_mmio_gem_create() * * This function releases resources associated with the GEM object created by * xe_mmio_gem_create(). * * See: "Exposing MMIO regions to userspace" */ -void xe_mmio_gem_destroy(struct xe_mmio_gem *gem) +void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file) { + drm_vma_node_revoke(&gem->base.vma_node, file); xe_mmio_gem_free(&gem->base); } diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.h b/drivers/gpu/drm/xe/xe_mmio_gem.h index 4b76d5586ebb..80d7795f07c8 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.h +++ b/drivers/gpu/drm/xe/xe_mmio_gem.h @@ -15,6 +15,6 @@ struct xe_mmio_gem; struct xe_mmio_gem *xe_mmio_gem_create(struct xe_device *xe, struct drm_file *file, phys_addr_t phys_addr, size_t size); u64 xe_mmio_gem_mmap_offset(struct xe_mmio_gem *gem); -void xe_mmio_gem_destroy(struct xe_mmio_gem *gem); +void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file); #endif /* _XE_MMIO_GEM_H_ */ From de40d31275cd57408ff1fdd97ea117a3f8c50cc5 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:52 +0100 Subject: [PATCH 1018/1317] drm/xe/mmio_gem: cache the dummy page per object Currently, when the fault handler provides a dummy page, it allocates a new one on every invocation and ties its lifetime to the drm_device via drmm_add_action_or_reset(). Concurrent faults after hot-unplug therefore accumulate pages that persist until device teardown. Cache a single dummy page in the xe_mmio_gem object and use dma_resv lock to protect its allocation. Free it with the object. v2: use dma_resv lock to protect the allocation (Matt Auld) Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-15-matthew.auld@intel.com (cherry picked from commit 8bf6213f9831e46313af4722a1ee6db1b7596378) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3c42d8358c7d..970b1e2f4981 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -5,9 +5,9 @@ #include "xe_mmio_gem.h" +#include #include #include -#include #include "xe_device_types.h" @@ -37,6 +37,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *); struct xe_mmio_gem { struct drm_gem_object base; phys_addr_t phys_addr; + struct page *dummy_page; /* protected by the GEM's dma_resv */ }; static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) @@ -131,6 +132,8 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) { struct xe_mmio_gem *obj = to_xe_mmio_gem(base); + if (obj->dummy_page) + __free_page(obj->dummy_page); drm_gem_object_release(base); kfree(obj); } @@ -169,27 +172,29 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct * return 0; } -static void xe_mmio_gem_release_dummy_page(struct drm_device *dev, void *res) +static int alloc_dummy_page_if_needed(struct drm_gem_object *base) { - __free_page((struct page *)res); + struct xe_mmio_gem *obj = to_xe_mmio_gem(base); + + dma_resv_lock(base->resv, NULL); + if (!obj->dummy_page) + obj->dummy_page = alloc_page(GFP_KERNEL | __GFP_ZERO); + dma_resv_unlock(base->resv); + + return obj->dummy_page ? 0 : -ENOMEM; } static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; - struct drm_device *dev = base->dev; - struct page *page; + struct xe_mmio_gem *obj = to_xe_mmio_gem(base); unsigned long pfn; - page = alloc_page(GFP_KERNEL | __GFP_ZERO); - if (!page) + if (alloc_dummy_page_if_needed(base)) return VM_FAULT_OOM; - if (drmm_add_action_or_reset(dev, xe_mmio_gem_release_dummy_page, page)) - return VM_FAULT_OOM; - - pfn = page_to_pfn(page); + pfn = page_to_pfn(obj->dummy_page); return vmf_insert_pfn_prot(vma, vmf->address, pfn, vm_get_page_prot(vma->vm_flags)); From d0c09528781938362655d9c336364e777119bd6b Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:53 +0100 Subject: [PATCH 1019/1317] drm/xe/mmio_gem: fix destroy flow xe_mmio_gem_destroy() currently frees the GEM object directly, bypassing reference counting. Since existing VMAs hold a reference and the fault handler accesses the object through vma->vm_private_data, this is use-after-free. Additionally, nothing prevents the fault handler from installing PTEs to the real MMIO after destroy. Fix this with proper synchronization and refcounting. Also, do not set vm_pgoff to zero. Many DRM drivers do this because helpers like dma_mmap_pages() interpret vm_pgoff as an intra-buffer page offset; leaving the DRM fake offset there would break these helpers. Those drivers can get away with zeroing it because they map eagerly - all PTEs are established before mmap returns, so vm_pgoff is never consulted again. Our driver does not use such helpers and the newly introduced call to drm_vma_node_unmap() relies on vm_pgoff being untouched. v2: (Matt Auld) - use dma_resv lock to serialize fault handler with destroy - SIGBUS on access after destroy Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-16-matthew.auld@intel.com (cherry picked from commit fb2ee38bab8025ad6a7a9cbb4635c5a178e4a7bc) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 42 ++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 970b1e2f4981..5ffe03d36190 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -38,6 +38,7 @@ struct xe_mmio_gem { struct drm_gem_object base; phys_addr_t phys_addr; struct page *dummy_page; /* protected by the GEM's dma_resv */ + bool destroyed; /* protected by the GEM's dma_resv */ }; static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) @@ -150,8 +151,22 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) */ void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file) { - drm_vma_node_revoke(&gem->base.vma_node, file); - xe_mmio_gem_free(&gem->base); + struct drm_gem_object *base = &gem->base; + struct drm_device *dev = base->dev; + + drm_vma_node_revoke(&base->vma_node, file); + + dma_resv_lock(base->resv, NULL); + gem->destroyed = true; + dma_resv_unlock(base->resv); + /* + * Setting 'destroyed' under lock takes care of the subsequent faults. + * Zap the existing PTEs to cut off access to the real MMIO through + * currently mapped pages. + */ + drm_vma_node_unmap(&base->vma_node, dev->anon_inode->i_mapping); + + drm_gem_object_put(base); } static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct *vma) @@ -162,8 +177,6 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct * if ((vma->vm_flags & VM_SHARED) == 0) return -EINVAL; - /* Set vm_pgoff (used as a fake buffer offset by DRM) to 0 */ - vma->vm_pgoff = 0; vma->vm_page_prot = pgprot_noncached(vma_get_page_prot(vma)); vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP | VM_DONTCOPY | VM_NORESERVE); @@ -176,10 +189,9 @@ static int alloc_dummy_page_if_needed(struct drm_gem_object *base) { struct xe_mmio_gem *obj = to_xe_mmio_gem(base); - dma_resv_lock(base->resv, NULL); + dma_resv_assert_held(base->resv); if (!obj->dummy_page) obj->dummy_page = alloc_page(GFP_KERNEL | __GFP_ZERO); - dma_resv_unlock(base->resv); return obj->dummy_page ? 0 : -ENOMEM; } @@ -200,7 +212,7 @@ static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) vm_get_page_prot(vma->vm_flags)); } -static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) +static vm_fault_t xe_mmio_gem_vm_fault_locked(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; @@ -210,6 +222,10 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) unsigned long addr, pfn; int idx; + dma_resv_assert_held(base->resv); + if (obj->destroyed) + return VM_FAULT_SIGBUS; + if (!drm_dev_enter(dev, &idx)) { /* * Provide a dummy page to avoid SIGBUS for events such as hot-unplug. @@ -232,3 +248,15 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) drm_dev_exit(idx); return ret; } + +static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct drm_gem_object *base = vma->vm_private_data; + vm_fault_t ret; + + dma_resv_lock(base->resv, NULL); + ret = xe_mmio_gem_vm_fault_locked(vmf); + dma_resv_unlock(base->resv); + return ret; +} From 3c90e42a01426262f0cd166bc01b45c05562640d Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Wed, 9 Sep 2026 16:21:01 +0000 Subject: [PATCH 1020/1317] drm/xe/shrinker: Return the freed page count through a parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __xe_shrinker_walk() and xe_shrinker_walk() return either the number of pages freed or a negative error, so the two cannot be reported at once. On error the pages already freed are dropped, and since xe_shrinker_scan() only accumulates non-negative returns while *scanned is updated by pointer, the shrinker tells mm that it scanned without freeing. Accumulate the count into a caller-provided counter and return only the status, so an error no longer discards what the walk had freed. Fixes: 00c8efc3180f ("drm/xe: Add a shrinker for xe bos") Assisted-by: Claude:claude-opus-5 Reviewed-by: Thomas Hellström Cc: Matthew Brost Link: https://patch.msgid.link/20260909162102.1097006-2-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit d7aac1a0235a6ce41e30cec385e2db8c33dad12d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_shrinker.c | 62 ++++++++++++++------------------ 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c index 83374cd57660..89445cd20238 100644 --- a/drivers/gpu/drm/xe/xe_shrinker.c +++ b/drivers/gpu/drm/xe/xe_shrinker.c @@ -54,13 +54,14 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea write_unlock(&shrinker->lock); } -static s64 __xe_shrinker_walk(struct xe_device *xe, +static int __xe_shrinker_walk(struct xe_device *xe, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, - unsigned long to_scan, unsigned long *scanned) + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) { unsigned int mem_type; - s64 freed = 0, lret; + s64 lret; for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type); @@ -82,7 +83,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, if (lret < 0) return lret; - freed += lret; + *freed += lret; if (*scanned >= to_scan) break; } @@ -90,7 +91,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, xe_assert(xe, !IS_ERR(ttm_bo)); } - return freed; + return 0; } /* @@ -99,40 +100,35 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, * add writeback. This avoids stalls and explicit writebacks with light or * moderate memory pressure. */ -static s64 xe_shrinker_walk(struct xe_device *xe, +static int xe_shrinker_walk(struct xe_device *xe, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, - unsigned long to_scan, unsigned long *scanned) + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) { bool no_wait_gpu = true; struct xe_bo_shrink_flags save_flags = flags; - s64 lret, freed; + int ret; swap(no_wait_gpu, ctx->no_wait_gpu); save_flags.writeback = false; - lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); + ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); swap(no_wait_gpu, ctx->no_wait_gpu); - if (lret < 0 || *scanned >= to_scan) - return lret; + if (ret || *scanned >= to_scan) + return ret; - freed = lret; if (!ctx->no_wait_gpu) { - lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); - if (lret < 0) - return lret; - freed += lret; - if (*scanned >= to_scan) - return freed; + ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, + freed); + if (ret || *scanned >= to_scan) + return ret; } - if (flags.writeback) { - lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned); - if (lret < 0) - return lret; - freed += lret; - } + if (flags.writeback) + ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, + freed); - return freed; + return ret; } static unsigned long @@ -214,7 +210,6 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con bool runtime_pm; bool purgeable; bool can_backup = !!(sc->gfp_mask & __GFP_FS); - s64 lret; nr_to_scan = sc->nr_to_scan; @@ -225,12 +220,9 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con /* Might need runtime PM. Try to wake early if it looks like it. */ runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); - if (purgeable && nr_scanned < nr_to_scan) { - lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, - nr_to_scan, &nr_scanned); - if (lret >= 0) - freed += lret; - } + if (purgeable && nr_scanned < nr_to_scan) + xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; if (nr_scanned >= nr_to_scan || !can_backup) @@ -242,10 +234,8 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con shrink_flags.purge = false; - lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, - nr_to_scan, &nr_scanned); - if (lret >= 0) - freed += lret; + xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; out: From 985862be16c7e4da808c51f393d631fb60c0be5c Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Wed, 9 Sep 2026 16:21:02 +0000 Subject: [PATCH 1021/1317] drm/xe/shrinker: Take a runtime PM ref before shrinking non-system memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __xe_shrinker_walk() walks the SYSTEM and TT LRUs without a runtime PM reference. Shrinking a bo outside system memory invalidates its GPU mappings, which needs the device resumed, so while it is runtime suspended the page table zap trips an assert and the TLB invalidation returns -ENODEV: WARNING: drivers/gpu/drm/xe/xe_bo.c:770 at xe_bo_move_notify+0x1fc/0x450 [xe] xe_bo_shrink+0x20f/0x2b0 [xe] __xe_shrinker_walk+0x174/0x410 [xe] xe_shrinker_scan+0x10c/0x1e0 [xe] do_shrink_slab+0x176/0x7e0 drop_caches_sysctl_handler+0x9c/0xf0 Take a reference before walking a memory type other than XE_PL_SYSTEM and stop there if it cannot be acquired. Reuse the shrinker's existing acquire path, which resumes the device directly where reclaim allows that and otherwise queues the PM worker for a later scan. Stop the walk once the scan target is met, so a satisfied scan does not wake the device. System memory is still reclaimed while the device is suspended. Gate this on xe_device_is_l2_flush_optimized(), the same condition under which xe_bo_trigger_rebind() issues the invalidation for a non-fault-mode vm, so reclaim is unaffected elsewhere. The System CCS copy already has its own reference in xe_bo_shrink(). Only a non-fault-mode vm can reach this, since a fault-mode vm requires LR mode and that holds a runtime PM reference for the vm's lifetime. Reproduced with igt@xe_madvise@dontneed-before-exec while the GPU is runtime suspended. v2: simplify needs_rpm check. (Matt) retarget Fixes tag since the issue occurs with the non-fault-mode path added by 4e7ebff69aed. v3: handle this in xe_shrinker.c instead of xe_bo.c (Thomas) v4: stop the walk once the scan target is met. (Sashiko) v5: rebase on the freed page accounting fix. (Sashiko) v6: reuse the shrinker acquire path so runtime pm can be resumed directly instead of always queueing a worker. (Thomas) v7: replace xe_pm_runtime_put() with xe_shrinker_runtime_pm_put(). (Thomas) Fixes: 4e7ebff69aed ("drm/xe/xe3p_lpg: flush shrinker bo cachelines manually") Assisted-by: Claude:claude-opus-5 Cc: Tejas Upadhyay Cc: Matthew Brost Reviewed-by: Thomas Hellström Link: https://patch.msgid.link/20260909162102.1097006-3-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit 628f92b28bf4c371c10207daf6fc4caee0c0db2e) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_shrinker.c | 78 +++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c index 89445cd20238..deb4378c1ec1 100644 --- a/drivers/gpu/drm/xe/xe_shrinker.c +++ b/drivers/gpu/drm/xe/xe_shrinker.c @@ -54,13 +54,39 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea write_unlock(&shrinker->lock); } -static int __xe_shrinker_walk(struct xe_device *xe, +static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) +{ + struct xe_device *xe = shrinker->xe; + + if (xe_pm_runtime_get_if_active(xe)) + return true; + + if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { + xe_pm_runtime_get(xe); + return true; + } + + queue_work(xe->unordered_wq, &shrinker->pm_worker); + + return false; +} + +static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +{ + if (runtime_pm) + xe_pm_runtime_put(shrinker->xe); +} + +static int __xe_shrinker_walk(struct xe_shrinker *shrinker, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, unsigned long to_scan, unsigned long *scanned, unsigned long *freed) { + struct xe_device *xe = shrinker->xe; unsigned int mem_type; + bool rpm = false; + int ret = 0; s64 lret; for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { @@ -75,23 +101,35 @@ static int __xe_shrinker_walk(struct xe_device *xe, if (!man || !man->use_tt) continue; + if (mem_type != XE_PL_SYSTEM && !rpm && + xe_device_is_l2_flush_optimized(xe)) { + if (!__xe_shrinker_runtime_pm_get(shrinker)) + break; + rpm = true; + } + ttm_bo_lru_for_each_reserved_guarded(&curs, man, &arg, ttm_bo) { if (!ttm_bo_shrink_suitable(ttm_bo, ctx)) continue; lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned); - if (lret < 0) - return lret; + if (lret < 0) { + ret = lret; + goto out; + } *freed += lret; if (*scanned >= to_scan) - break; + goto out; } /* Trylocks should never error, just fail. */ xe_assert(xe, !IS_ERR(ttm_bo)); } - return 0; +out: + xe_shrinker_runtime_pm_put(shrinker, rpm); + + return ret; } /* @@ -100,7 +138,7 @@ static int __xe_shrinker_walk(struct xe_device *xe, * add writeback. This avoids stalls and explicit writebacks with light or * moderate memory pressure. */ -static int xe_shrinker_walk(struct xe_device *xe, +static int xe_shrinker_walk(struct xe_shrinker *shrinker, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, unsigned long to_scan, unsigned long *scanned, @@ -112,20 +150,21 @@ static int xe_shrinker_walk(struct xe_device *xe, swap(no_wait_gpu, ctx->no_wait_gpu); save_flags.writeback = false; - ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); + ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, + freed); swap(no_wait_gpu, ctx->no_wait_gpu); if (ret || *scanned >= to_scan) return ret; if (!ctx->no_wait_gpu) { - ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, + ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, freed); if (ret || *scanned >= to_scan) return ret; } if (flags.writeback) - ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, + ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned, freed); return ret; @@ -176,22 +215,7 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, return false; } - if (!xe_pm_runtime_get_if_active(xe)) { - if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { - xe_pm_runtime_get(xe); - return true; - } - queue_work(xe->unordered_wq, &shrinker->pm_worker); - return false; - } - - return true; -} - -static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) -{ - if (runtime_pm) - xe_pm_runtime_put(shrinker->xe); + return __xe_shrinker_runtime_pm_get(shrinker); } static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc) @@ -221,7 +245,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); if (purgeable && nr_scanned < nr_to_scan) - xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; @@ -234,7 +258,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con shrink_flags.purge = false; - xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; From 073a30d75f309812ed61af134f24ffef4107b13a Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 16:31:10 +0200 Subject: [PATCH 1022/1317] drm/vc4: Use managed KMS polling to fix UAF on unbind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_kms_load() calls drm_kms_helper_poll_init() but the driver provides no matching drm_kms_helper_poll_fini(). The output poll work stays scheduled after unbind and runs on the freed drm_device: # modprobe vc4; rmmod vc4; sleep 10 BUG: KASAN: slab-use-after-free in delayed_work_timer_fn BUG: KASAN: slab-use-after-free in drm_client_dev_hotplug [drm] Workqueue: events output_poll_execute [drm_kms_helper] Allocated by task 171: __devm_drm_dev_alloc Freed by task 262 (rmmod): drm_dev_put / component_del Use drmm_kms_helper_poll_init() so polling is finalized with the device, as other drivers do. Fixes: c8b75bca92cb ("drm/vc4: Add KMS support for Raspberry Pi.") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260822143110.68594-1-kmehltretter@gmail.com Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_kms.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index b17e73bce384..82a7f2ac4c7e 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -1188,7 +1188,7 @@ int vc4_kms_load(struct drm_device *dev) drm_mode_config_reset(dev); - drm_kms_helper_poll_init(dev); + drmm_kms_helper_poll_init(dev); return 0; } From bdf5f731957de48acada392f28e82bc019713adb Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 14 Sep 2026 17:41:36 +0700 Subject: [PATCH 1023/1317] hwmon: (gpio-fan) return IRQ_HANDLED from the shared alarm IRQ handler fan_alarm_irq_handler() always schedules alarm_work but returns IRQ_NONE, so the kernel treats every alarm interrupt as unhandled. On a shared line that risks the whole line being disabled as spurious. v1 just fixed that, but it was still IRQF_SHARED, and always returning IRQ_HANDLED there defeats spurious-interrupt detection for the line -- if the interrupt ever fires without a real event, nothing catches it, and a fault could spin the CPU in the handler. Sashiko flagged this in v1, and Guenter confirmed: this interrupt must not be shared. So v2 drops IRQF_SHARED too. Fixes: d6fe1360f42e ("hwmon: add generic GPIO fan driver") Reported-by: Sashiko AI review Link: https://lore.kernel.org/r/20260901160931.DD3811F00A3D@smtp.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://patch.msgid.link/20260914104136.1797979-1-congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index df8bd9707605..3f78375eeb44 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -68,7 +68,7 @@ static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) schedule_work(&fan_data->alarm_work); - return IRQ_NONE; + return IRQ_HANDLED; } static ssize_t fan1_alarm_show(struct device *dev, @@ -103,7 +103,7 @@ static int fan_alarm_init(struct gpio_fan_data *fan_data) irq_set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler, - IRQF_SHARED, "GPIO fan alarm", fan_data); + 0, "GPIO fan alarm", fan_data); } /* From 0ff9c7775e51ac6d47b1bb5c46f06b1434fe58a8 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 14:28:09 +0800 Subject: [PATCH 1024/1317] hwmon: (w83791d) remove fan/pwm 4-5 sysfs group on remove When the fan/pwm 4-5 pins are not used as GPIO, w83791d_probe() creates the w83791d_group_fanpwm45 sysfs group on the I2C client device. The probe error path removes this group when a later initialization step fails, but the normal remove path only removes w83791d_group. As a result, the optional fan/pwm 4-5 sysfs files can remain after the driver is unbound. The callbacks associated with these files access the driver data, which is devm allocated and released after driver unbind. Leaving the sysfs files behind can therefore result in accesses to stale driver data. Remove w83791d_group_fanpwm45 during normal teardown as well. This issue was found by manual code inspection. Fixes: 6e1ecd9b8f13 ("hwmon: (w83791d) fan 4/5 pins can also be used for gpio") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260914062809.1650538-1-lgs201920130244@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/w83791d.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/w83791d.c b/drivers/hwmon/w83791d.c index 4a777430af5c..4b07a25ae59e 100644 --- a/drivers/hwmon/w83791d.c +++ b/drivers/hwmon/w83791d.c @@ -1415,6 +1415,7 @@ static void w83791d_remove(struct i2c_client *client) struct w83791d_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &w83791d_group_fanpwm45); sysfs_remove_group(&client->dev.kobj, &w83791d_group); } From c702a5f18b780e477eccbbab558e590e9673e4cb Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 15:36:38 +0800 Subject: [PATCH 1025/1317] hwmon: (w83793) release probe data through kref w83793_probe() initializes data->kref to manage the lifetime of the driver data. The normal remove path drops the driver-owned reference with kref_put(), while watchdog users take and release additional references through the same kref. However, the probe error path still frees data directly with kfree(). This bypasses the kref-managed lifetime and discards the initial reference without a matching kref_put(), leaving the reference accounting unbalanced. Drop the probe-owned reference with kref_put() instead and let w83793_release_resources() perform the final free, matching the normal remove path. This issue was found by manual code inspection. Fixes: 5852f9609d21 ("hwmon: (w83793) Add watchdog functionality") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260914073638.1662500-1-lgs201920130244@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/w83793.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/w83793.c b/drivers/hwmon/w83793.c index a548586369e1..c6ef04c69856 100644 --- a/drivers/hwmon/w83793.c +++ b/drivers/hwmon/w83793.c @@ -1928,7 +1928,9 @@ static int w83793_probe(struct i2c_client *client) for (i = 0; i < ARRAY_SIZE(w83793_temp); i++) device_remove_file(dev, &w83793_temp[i].dev_attr); free_mem: - kfree(data); + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, w83793_release_resources); + mutex_unlock(&watchdog_data_mutex); exit: return err; } From 1c4f6202876a74e752a8cfd3696955f761671c7a Mon Sep 17 00:00:00 2001 From: Jaeho Cho Date: Mon, 14 Sep 2026 10:24:42 -0400 Subject: [PATCH 1026/1317] ALSA: hda/realtek: Enable mute LEDs on HP OmniBook 7 17-dc0xxx The HP OmniBook 7 Laptop 17-dc0xxx (SSID 103c:8d9c) has mute and mic-mute LEDs on its F6 and F9 keys, but neither lights up. Its quirk entry only sets up the two CS35L41 amplifiers, so no LED control is registered for either key. Writing to the ALC245 by hand with hda-verb, the mute LED responds to COEF 0x0b bits 2-3 and the mic-mute LED to GPIO 0x04, lit when the pin is low. That appears to match what ALC245_FIXUP_HP_X360_MUTE_LEDS already does, so add a fixup that chains the two-amp I2C setup to it and use it for this model. Tested on 7.2.4 with the patched module: hda::mute and hda::micmute are registered, and both LEDs follow the speaker and microphone mute state, including from the F6 and F9 keys. Signed-off-by: Jaeho Cho Link: https://patch.msgid.link/20260914142445.3476212-1-jaeho2025@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fa83b739fb50..5127a111c59c 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4303,6 +4303,7 @@ enum { ALC287_FIXUP_LEGION_16ACHG6, ALC287_FIXUP_CS35L41_I2C_2, ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED, + ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, ALC287_FIXUP_CS35L41_I2C_4, ALC245_FIXUP_CS35L41_SPI_1, ALC245_FIXUP_CS35L41_SPI_2, @@ -6614,6 +6615,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_HP_MUTE_LED, }, + [ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + .chained = true, + .chain_id = ALC245_FIXUP_HP_X360_MUTE_LEDS, + }, [ALC287_FIXUP_CS35L41_I2C_4] = { .type = HDA_FIXUP_FUNC, .v.func = cs35l41_fixup_i2c_four, @@ -7662,7 +7669,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8dcd, "HP Victus 15-fa2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8d9b, "HP 17 Turbine OmniBook 7 UMA", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), From 5ab3dc647751996784cff20a51f3730f4e88afe4 Mon Sep 17 00:00:00 2001 From: Asai Neko Date: Mon, 14 Sep 2026 18:58:29 +0800 Subject: [PATCH 1027/1317] ALSA: usb-audio: skip the broken mute control on AVerMedia GC553Pro Skip the nonfunctional master mute control on the AVerMedia Live Gamer ULTRA S GC553Pro (07ca:1553). USB tracing shows that GET_CUR returns zero bytes instead of the required one-byte value, both through usbfs and during ALSA initialization. SET_CUR succeeds, but switching capture off does not mute HDMI audio. Before the change, the driver exposed a misleading PCM Capture Switch and logged: 3:2: failed to get current value for ch 0 (-22) With the patch applied, the switch and warning are absent. A ten-second sound recording through PipeWire confirmed that stereo 48 kHz, 16-bit capture still works. Tested on NixOS with the patched 7.3.0-rc3 kernel. The USB audio driver object builds with Clang and W=1; sparse and strict checkpatch pass. Signed-off-by: Asai Neko Link: https://patch.msgid.link/20260914-avermedia-gc553pro-alsa-v1-1-4c694e8b0cd5@sne.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 69093c666282..41454cb403d0 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -531,6 +531,15 @@ static const struct usbmix_name_map audient_id24_map[] = { {} }; +/* + * The GC553Pro returns no data for GET_CUR on its advertised mute control. + * SET_CUR succeeds but does not mute capture, so skip the control entirely. + */ +static const struct usbmix_name_map avermedia_gc553pro_map[] = { + { 3, NULL, UAC_FU_MUTE }, + {} +}; + /* * Control map entries */ @@ -577,6 +586,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x0763, 0x2031), .selector_map = c400_selectors, }, + { + .id = USB_ID(0x07ca, 0x1553), + .map = avermedia_gc553pro_map, + }, { .id = USB_ID(0x08bb, 0x2702), .map = linex_map, From 9d1e523b92d8cb11a4a7e5a2655d6824c2d0f6e3 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:52:59 +0200 Subject: [PATCH 1028/1317] selftests/hid: add define for commonly used buf size If we want to add another report descriptor without report IDs with a report size bigger than 10, we have multiple magic values to replace. Put a #define once and for all, so we don't have dangling ones. Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 38 ++++++++++++------------ tools/testing/selftests/hid/hid_common.h | 3 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 7ab86296ff23..0d03ad5245fc 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -5,7 +5,7 @@ #include struct hid_hw_request_syscall_args { - __u8 data[10]; + __u8 data[MAX_BUF_SIZE]; unsigned int hid; int retval; size_t size; @@ -175,7 +175,7 @@ TEST_F(hid_bpf, raw_event) const struct test_program progs[] = { { .name = "hid_first_event" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -226,7 +226,7 @@ TEST_F(hid_bpf, subprog_raw_event) const struct test_program progs[] = { { .name = "hid_subprog_first_event" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -284,7 +284,7 @@ TEST_F(hid_bpf, test_attach_detach) { .name = "hid_second_event" }, }; struct bpf_link *link; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err, link_fd; LOAD_PROGRAMS(progs); @@ -369,7 +369,7 @@ TEST_F(hid_bpf, test_hid_change_report) const struct test_program progs[] = { { .name = "hid_change_report_id" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -396,13 +396,13 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) { struct hid_hw_request_syscall_args args = { .retval = -1, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, .ctx_size_in = sizeof(args), ); - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err, prog_fd; LOAD_BPF; @@ -442,7 +442,7 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) { struct hid_hw_request_syscall_args args = { .retval = -1, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, @@ -491,7 +491,7 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call) .retval = -1, .type = HID_FEATURE_REPORT, .request_type = HID_REQ_GET_REPORT, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, @@ -524,7 +524,7 @@ TEST_F(hid_bpf, test_hid_filter_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_filter_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -577,7 +577,7 @@ TEST_F(hid_bpf, test_hid_change_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_hidraw_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -603,7 +603,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -626,7 +626,7 @@ TEST_F(hid_bpf, test_hid_filter_output_report_call) const struct test_program progs[] = { { .name = "hid_test_filter_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -679,7 +679,7 @@ TEST_F(hid_bpf, test_hid_change_output_report_call) const struct test_program progs[] = { { .name = "hid_test_hidraw_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -703,7 +703,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_output_report_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -729,7 +729,7 @@ TEST_F(hid_bpf, test_multiply_events_wq) const struct test_program progs[] = { { .name = "hid_test_multiply_events_wq" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -767,7 +767,7 @@ TEST_F(hid_bpf, test_multiply_events) const struct test_program progs[] = { { .name = "hid_test_multiply_events" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -801,7 +801,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_input_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -855,7 +855,7 @@ TEST_F(hid_bpf, test_hid_attach_flags) .insert_head = 0, }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h index e3b267446fa0..4567336f131d 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -13,6 +13,7 @@ #include #define SHOW_UHID_DEBUG 0 +#define MAX_BUF_SIZE 10 #define min(a, b) \ ({ __typeof__(a) _a = (a); \ @@ -110,7 +111,7 @@ static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER; static pthread_mutex_t uhid_output_mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t uhid_output_cond = PTHREAD_COND_INITIALIZER; -static unsigned char output_report[10]; +static unsigned char output_report[MAX_BUF_SIZE]; /* no need to protect uhid_stopped, only one thread accesses it */ static bool uhid_stopped; From c4afa4862b878d56e0cc1021298794ac1b45bc49 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:53:00 +0200 Subject: [PATCH 1029/1317] HID: bpf: fix __hid_bpf_hw_check_params report length Turns out that USB, I2C and other transport drivers (except uhid which just passes the data) still need to have the report ID in the first byte. Because they expect the first byte to be the report ID or 0, when the report ID is 0, they strip that first byte before forwarding to the device. This means that the transport layer forwards a buffer of size N-1 to the device, which gets rejected. Fixes: 5599f8019661 ("HID: bpf: export hid_hw_output_report as a BPF kfunc") Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_dispatch.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 536f6d01fd14..b1de1dd0f21d 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -359,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) static int __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, - enum hid_report_type rtype) + enum hid_report_type rtype, bool hw_request) { struct hid_report_enum *report_enum; struct hid_report *report; @@ -388,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, report_len = hid_report_len(report); + /* unnumbered reports need to have a report ID reserved in the first byte */ + if (hw_request && report_enum->numbered == 0) + report_len += 1; + if (*buf__sz > report_len) *buf__sz = report_len; @@ -420,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); if (ret) return ret; @@ -480,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); if (ret) return ret; @@ -506,7 +510,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); if (ret) return ret; From b6f69097c8271cca30314fd6e4c802f90737bfcc Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:53:01 +0200 Subject: [PATCH 1030/1317] selftests/hid: add unnumbered variant to the hid_bpf tests A bug appeared in hid_bpf_dispatch.c where it wasn't properly handling unnumbered reports. Add a device variant without report IDs so we can also test them. Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 53 ++++++++++++++++++++---- tools/testing/selftests/hid/hid_common.h | 24 ++++++++++- tools/testing/selftests/hid/progs/hid.c | 2 +- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 0d03ad5245fc..32d81ba15a25 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -54,11 +54,27 @@ FIXTURE_TEARDOWN(hid_bpf) { hid_bpf_teardown(_metadata, self, variant); \ } while (0) +FIXTURE_VARIANT(hid_bpf) { + __u8 *rdesc; + size_t rdesc_size; +}; + +FIXTURE_VARIANT_ADD(hid_bpf, numbered) { + .rdesc = rdesc, + .rdesc_size = sizeof(rdesc), +}; + +FIXTURE_VARIANT_ADD(hid_bpf, unnumbered) { + .rdesc = fido2_rdesc, + .rdesc_size = sizeof(fido2_rdesc), +}; + FIXTURE_SETUP(hid_bpf) { int err; - err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, rdesc, sizeof(rdesc)); + err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, + variant->rdesc, variant->rdesc_size); ASSERT_OK(err); } @@ -409,8 +425,11 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) args.hid = self->hid.hid_id; args.data[0] = 1; /* report ID */ - args.data[1] = 2; /* report ID */ - args.data[2] = 42; /* report ID */ + args.data[1] = 2; + args.data[2] = 42; + + if (variant->rdesc == fido2_rdesc) + args.data[0] = 0; prog_fd = bpf_program__fd(self->skel->progs.hid_user_input_report); @@ -428,8 +447,13 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) /* read the data from hidraw */ memset(buf, 0, sizeof(buf)); err = read(self->hidraw_fd, buf, sizeof(buf)); - ASSERT_EQ(err, 6) TH_LOG("read_hidraw"); - ASSERT_EQ(buf[0], 1); + if (variant->rdesc == rdesc) { + ASSERT_EQ(err, 6) TH_LOG("read_hidraw"); + } else { + ASSERT_EQ(err, 64) + TH_LOG("read_hidraw"); + } + ASSERT_EQ(buf[0], args.data[0]); ASSERT_EQ(buf[1], 2); ASSERT_EQ(buf[2], 42); } @@ -455,8 +479,11 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) args.hid = self->hid.hid_id; args.data[0] = 1; /* report ID */ - args.data[1] = 2; /* report ID */ - args.data[2] = 42; /* report ID */ + args.data[1] = 2; + args.data[2] = 42; + + if (variant->rdesc == fido2_rdesc) + args.data[0] = 0; prog_fd = bpf_program__fd(self->skel->progs.hid_user_output_report); @@ -472,9 +499,14 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts"); ASSERT_OK(cond_err) TH_LOG("error while calling waiting for the condition"); - ASSERT_EQ(args.retval, 3); + if (variant->rdesc == rdesc) { + ASSERT_EQ(args.retval, 3); + } else if (variant->rdesc == fido2_rdesc) { + ASSERT_EQ(args.retval, 65) + TH_LOG("report size error, should have 64 + 1 extra byte for the report ID 0"); + } - ASSERT_EQ(output_report[0], 1); + ASSERT_EQ(output_report[0], args.data[0]); ASSERT_EQ(output_report[1], 2); ASSERT_EQ(output_report[2], 42); @@ -886,6 +918,9 @@ TEST_F(hid_bpf, test_rdesc_fixup) }; int err, desc_size; + if (variant->rdesc != rdesc) + SKIP(return, "not compatible report descriptor"); + LOAD_PROGRAMS(progs); /* check that hid_rdesc_fixup() was executed */ diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h index 4567336f131d..b7890ba2878f 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -13,7 +13,7 @@ #include #define SHOW_UHID_DEBUG 0 -#define MAX_BUF_SIZE 10 +#define MAX_BUF_SIZE 128 #define min(a, b) \ ({ __typeof__(a) _a = (a); \ @@ -98,6 +98,28 @@ static unsigned char rdesc[] = { static __u8 feature_data[] = { 1, 2 }; +static __maybe_unused unsigned char fido2_rdesc[] = { + 0x06, 0xd0, 0xf1, /* Usage Page (FIDO Alliance) */ + 0x09, 0x01, /* Usage (U2F Authenticator Device) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x20, /* Usage (Input Report Data) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (64) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x09, 0x21, /* Usage (Output Report Data) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (64) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x22, /* Usage (Vendor Usage 0x22) */ + 0xb1, 0x02, /* Feature (Data,Var,Abs) */ + 0xc0, /* End Collection */ +}; + #define ASSERT_OK(data) ASSERT_FALSE(data) #define ASSERT_OK_PTR(ptr) ASSERT_NE(NULL, ptr) diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c index 361dc7eaad22..48aa8088cc53 100644 --- a/tools/testing/selftests/hid/progs/hid.c +++ b/tools/testing/selftests/hid/progs/hid.c @@ -98,7 +98,7 @@ struct hid_bpf_ops change_report_id = { struct hid_hw_request_syscall_args { /* data needs to come at offset 0 so we can use it in calls */ - __u8 data[10]; + __u8 data[128]; unsigned int hid; int retval; size_t size; From c9e6e5f38bf75276605f1952b22285f5f3abcaff Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:54 +0800 Subject: [PATCH 1031/1317] ALSA: hda: trace PCM open only after assigning a stream Stream assignment can fail when hardware streams are exhausted. Move the tracepoint after the NULL check because its payload accesses the assigned stream tag. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: 184865085b88 ("ALSA: hda - rename hda_intel_trace.h to hda_controller_trace.h") Assisted-by: LLM Signed-off-by: Slavin Liu Link: https://patch.msgid.link/20260913125154.109944-1-bolin.liu@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/hda/common/controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index afec5c5546ec..18dae022b324 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -586,11 +586,11 @@ static int azx_pcm_open(struct snd_pcm_substream *substream) snd_hda_codec_pcm_get(apcm->info); mutex_lock(&chip->open_mutex); azx_dev = azx_assign_device(chip, substream); - trace_azx_pcm_open(chip, azx_dev); if (azx_dev == NULL) { err = -EBUSY; goto unlock; } + trace_azx_pcm_open(chip, azx_dev); runtime->private_data = azx_dev; runtime->hw = azx_pcm_hw; From ce9d5197d651cdd0fbb586c3d77c28438abe1b10 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 25 Aug 2026 10:13:14 +0200 Subject: [PATCH 1032/1317] wifi: ath12k: ahb: Revert undocumented ABI and dead code Commit 96f46607bbce ("wifi: ath12k: add AHB platform descriptor support") added undocumented OF ABI, by relying on a very specific node name. This is not allowed and was never acked by Devicetree maintainers. Additionally that part of code is not even used, because all devices have exactly the same user pd, so this was added "for future". Adding dead code just "for future" is heavily discouraged in kernel coding. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260825081313.71351-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/ahb.c | 51 +-------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c index 98a6606ffd76..6e9e9034cba1 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c @@ -15,21 +15,6 @@ #include "dp.h" #include "core.h" -/* - * Node name to UserPD ID mapping - * - * The io_start field is used for additional validation when the reg - * property is present in the device tree. If io_start is 0, only - * node_name matching is performed. - * - * For platforms where not all WiFi nodes have a 'reg' property, set - * io_start to 0 for those entries. The driver will match purely by - * node name in such cases. - */ -static const struct ath12k_ahb_userpd_map ath12k_wifi7_ahb_userpd_map[] = { - { .io_start = 0x0c000000, .node_name = "wifi", .upd_id = ATH12K_AHB_USERPD_ID_0 }, -}; - static const struct ath12k_ahb_desc ath12k_wifi7_ahb_desc[] = { [ATH12K_HW_IPQ5332_HW10] = { .hw_rev = ATH12K_HW_IPQ5332_HW10, @@ -55,40 +40,6 @@ static const struct of_device_id ath12k_wifi7_ahb_of_match[] = { MODULE_DEVICE_TABLE(of, ath12k_wifi7_ahb_of_match); -/* - * ath12k_wifi7_ahb_get_userpd_id - Resolve UserPD ID from DT properties - * @ab: ath12k base structure - * - * Returns: UserPD ID (1-based) on success, 0 on failure - * - * Resolution logic: - * 1. If reg property exist in DT, get userpd_id from io_start - * 2. If reg property is absent, get userpd_id from DT node name - * 3. Return 0 if no match found (probe will fail) - */ -static u32 ath12k_wifi7_ahb_get_userpd_id(struct ath12k_base *ab) -{ - const struct ath12k_ahb_userpd_map *map; - struct resource *res; - size_t i; - - res = platform_get_resource(ab->pdev, IORESOURCE_MEM, 0); - - for (i = 0; i < ARRAY_SIZE(ath12k_wifi7_ahb_userpd_map); i++) { - map = &ath12k_wifi7_ahb_userpd_map[i]; - - if (res) { - if (map->io_start && map->io_start == res->start) - return map->upd_id; - } else if (map->node_name && - of_node_name_eq(ab->dev->of_node, map->node_name)) { - return map->upd_id; - } - } - - return 0; -} - static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) { const struct ath12k_ahb_desc *desc; @@ -106,7 +57,7 @@ static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) ab->hw_rev = desc->hw_rev; ab->hif.ops = desc->ops; ab_ahb->scm_auth_enabled = desc->auth_enabled; - ab_ahb->userpd_id = ath12k_wifi7_ahb_get_userpd_id(ab); + ab_ahb->userpd_id = ATH12K_AHB_USERPD_ID_0; if (!ab_ahb->userpd_id) return -EOPNOTSUPP; From d9be5e75530772fc31637070d51e5717d6aeaa2a Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 10 Sep 2026 02:09:07 +0000 Subject: [PATCH 1033/1317] wifi: wcn36xx: Fix potential use-after-free in TX ack timer teardown wcn36xx_dxe_deinit() tears down the TX ack timer with timer_delete(), which only dequeues the timer and does not wait for a callback that is already executing; the preceding free_irq() calls synchronize the interrupt handlers only. The callback, wcn36xx_dxe_tx_timer(), can therefore be running past the teardown and use the wcn freed along with the ieee80211_hw in wcn36xx_remove(): it takes wcn->dxe_lock, reads wcn->tx_ack_skb and passes wcn->hw to ieee80211_tx_status_irqsafe(). Fix this by using timer_shutdown_sync(), which waits for a running callback and also prevents the timer from being rearmed again. The timer is set up again by wcn36xx_dxe_init() on the next start, so the start/stop cycle is unaffected. This issue was found by an in-house static analysis tool. Fixes: fdf21cc37149 ("wcn36xx: Add TX ack support") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Loic Poulain Link: https://patch.msgid.link/20260910020907.3353-1-fanwu01@zju.edu.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/wcn36xx/dxe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.c b/drivers/net/wireless/ath/wcn36xx/dxe.c index 44020ec265fb..801f1218ef89 100644 --- a/drivers/net/wireless/ath/wcn36xx/dxe.c +++ b/drivers/net/wireless/ath/wcn36xx/dxe.c @@ -1055,7 +1055,7 @@ void wcn36xx_dxe_deinit(struct wcn36xx *wcn) free_irq(wcn->tx_irq, wcn); free_irq(wcn->rx_irq, wcn); - timer_delete(&wcn->tx_ack_timer); + timer_shutdown_sync(&wcn->tx_ack_timer); if (wcn->tx_ack_skb) { ieee80211_tx_status_irqsafe(wcn->hw, wcn->tx_ack_skb); From 820b8cff81c796ba20573e04722ab62500713f97 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Fri, 31 Jul 2026 16:58:30 +0200 Subject: [PATCH 1034/1317] wifi: ath11k: cleanup arsta in ath11k_mac_peer_cleanup_all() When mac80211 removes a sta, it calls .sta_state() which in turn calls ath11k_mac_station_remove(). In that function we clean up both peers & arsta related resources. But when the firmware crashes, ath11k calls ieee80211_restart_hw(), which assumes that all driver related resources are cleaned up beforehand. This cleanup is supposedly done by ath11k_mac_peer_cleanup_all() but does not in fact free arsta->rx_stats / tx_stats. Extract the arsta cleanup from ath11k_mac_station_remove() into a new ath11k_mac_station_cleanup() and call it from both there and ath11k_mac_peer_cleanup_all(). This should handle kmemleaks reports like: unreferenced object 0xffffff801ae66400 (size 1024): comm "hostapd", pid 1306, jiffies 4295011565 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace (crc d61c08ec): kmemleak_alloc+0x3c/0x50 __kmalloc_cache_noprof+0x2b0/0x3e0 ath11k_mac_op_sta_state+0x1dc/0xb10 drv_sta_state+0xac/0x6f8 sta_info_insert_rcu+0x314/0x5e0 sta_info_insert+0x14/0x38 ieee80211_add_station+0x10c/0x1a0 nl80211_new_station+0x3e8/0x680 genl_family_rcv_msg_doit+0xc0/0x120 genl_rcv_msg+0x1b4/0x258 netlink_rcv_skb+0x4c/0x108 genl_rcv+0x38/0x60 netlink_unicast+0x190/0x278 netlink_sendmsg+0x15c/0x370 ____sys_sendmsg+0x120/0x290 ___sys_sendmsg+0x70/0xa0 Tested-on: QCN9074 hw1.0 PCI WLAN.HK.2.9.0.1-01977-QCAHKSWPL_SILICONZ-1 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Nicolas Escande Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260731145830.769811-1-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/mac.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index 2d55cdc4d165..ae91b57c8422 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -873,6 +873,22 @@ static int ath11k_mac_set_kickout(struct ath11k_vif *arvif) return 0; } +static void ath11k_mac_station_cleanup(struct ieee80211_sta *sta) +{ + struct ath11k_sta *arsta; + + if (!sta) + return; + + arsta = ath11k_sta_to_arsta(sta); + + kfree(arsta->tx_stats); + arsta->tx_stats = NULL; + + kfree(arsta->rx_stats); + arsta->rx_stats = NULL; +} + void ath11k_mac_peer_cleanup_all(struct ath11k *ar) { struct ath11k_peer *peer, *tmp; @@ -885,6 +901,7 @@ void ath11k_mac_peer_cleanup_all(struct ath11k *ar) list_for_each_entry_safe(peer, tmp, &ab->peers, list) { ath11k_peer_rx_tid_cleanup(ar, peer); ath11k_peer_rhash_delete(ab, peer); + ath11k_mac_station_cleanup(peer->sta); list_del(&peer->list); kfree(peer); } @@ -9892,7 +9909,6 @@ static int ath11k_mac_station_remove(struct ath11k *ar, { struct ath11k_base *ab = ar->ab; struct ath11k_vif *arvif = ath11k_vif_to_arvif(vif); - struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta); int ret; if (ab->hw_params.vdev_start_delay && @@ -9916,12 +9932,7 @@ static int ath11k_mac_station_remove(struct ath11k *ar, sta->addr, arvif->vdev_id); ath11k_mac_dec_num_stations(arvif, sta); - - kfree(arsta->tx_stats); - arsta->tx_stats = NULL; - - kfree(arsta->rx_stats); - arsta->rx_stats = NULL; + ath11k_mac_station_cleanup(sta); return ret; } From 3a35e787ac1acf94d33eebb26fd7248811cc66c9 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 24 Aug 2026 18:19:10 +0200 Subject: [PATCH 1035/1317] btrfs: zoned: handle RAID profiles in btrfs_can_activate_zone() btrfs_can_activate_zone() only accounts for the single and DUP profiles. For a RAID0, RAID1, RAID1C3, RAID1C4 or RAID10 block group the profile switch matches no case, so 'ret' stays false and the function reports that no zone can be activated, even when the devices have plenty of active zones left. As a side effect BTRFS_FS_NEED_ZONE_FINISH gets set and, since btrfs_can_activate_zone() bails out early once that bit is set, data allocations will fail permanently: writers loop on -EAGAIN and hang in btrfs_new_extent_direct() waiting for the bit to clear. Each of these profiles needs one active zone per device, just like single, so handle them the same way. Reviewed-by: Boris Burkov Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index 9cc2c9c1a606..08a15465a087 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2688,6 +2688,11 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags) switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) { case 0: /* single */ + case BTRFS_BLOCK_GROUP_RAID0: + case BTRFS_BLOCK_GROUP_RAID1: + case BTRFS_BLOCK_GROUP_RAID1C3: + case BTRFS_BLOCK_GROUP_RAID1C4: + case BTRFS_BLOCK_GROUP_RAID10: ret = (atomic_read(&zinfo->active_zones_left) >= (1 + reserved)); break; case BTRFS_BLOCK_GROUP_DUP: From 0594e3423f4ba3137c734371169491f9a98e9af4 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 31 Aug 2026 13:38:01 +0800 Subject: [PATCH 1036/1317] btrfs: take commit root semaphore when iterating in mark_block_group_to_copy() mark_block_group_to_copy() iterates over the commit root with skip_locking=true. A concurrent transaction commit can swap and free the commit root during iteration, causing use-after-free when accessing extent buffers. Fix it by using path->need_commit_sem to protect the commit root search. Fixes: 78ce9fc269af ("btrfs: zoned: mark block groups to copy for device-replace") CC: stable@vger.kernel.org Assisted-by: Codex:gpt-5.5 Reviewed-by: Johannes Thumshirn Signed-off-by: Hongling Zeng Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/dev-replace.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index bf0b78790171..1894c7ebe9d9 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -493,6 +493,7 @@ static int mark_block_group_to_copy(struct btrfs_fs_info *fs_info, path->reada = READA_FORWARD; path->search_commit_root = true; path->skip_locking = true; + path->need_commit_sem = true; key.objectid = src_dev->devid; key.type = BTRFS_DEV_EXTENT_KEY; From a1167d9420474ab9ed9efca99d86aeb6217c0265 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 10 Sep 2026 17:37:48 +0100 Subject: [PATCH 1037/1317] btrfs: tree-checker: print dev extent offset in error message If a dev extent's offset is not sector size aligned, the error message is printing the dev extent's objectid instead of the offset. This is a copy paste error, as before this check we check the objectid field. Fixes: 008e2512dc56 ("btrfs: tree-checker: add dev extent item checks") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index ab5abbb475e2..8b1b706e0996 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -2129,7 +2129,7 @@ static int check_dev_extent_item(const struct extent_buffer *leaf, sectorsize))) { generic_err(leaf, slot, "invalid dev extent chunk offset, has %llu not aligned to %u", - btrfs_dev_extent_chunk_objectid(leaf, de), + btrfs_dev_extent_chunk_offset(leaf, de), sectorsize); return -EUCLEAN; } From f59d86d25e41c5ac511584d9d6808ee448fd42b0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 10 Sep 2026 17:48:06 +0100 Subject: [PATCH 1038/1317] btrfs: tree-checker: fix error message regarding free space extent items The error message mentions a free space info item, but we are processing a free space extent item, so fix the message. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 8b1b706e0996..4b1e47173c63 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -2306,7 +2306,7 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key if (unlikely(btrfs_item_size(leaf, slot) != 0)) { generic_err(leaf, slot, - "invalid item size for free space info, has %u expect 0", + "invalid item size for free space extent, has %u expect 0", btrfs_item_size(leaf, slot)); return -EUCLEAN; } From 057dac23d329d5c5ed62352f2659a39fd46c6d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Koutn=C3=BD?= Date: Mon, 14 Sep 2026 14:19:10 +0200 Subject: [PATCH 1039/1317] cgroup: Avoid iteration of dying tasks with zero refcount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit 260fbcb92bbea ("cgroup: Move dying_tasks cleanup from cgroup_task_release() to cgroup_task_free()") extended the lifetime of tasks on the dying_tasks list. The iterators have provision to go through dying_tasks because of dying threadgroup leaders or explicit CSS_TASK_ITER_WITH_DEAD, however, it was expected that such tasks can obtain a new reference (that is possible before cgroup_task_release()/put_task_struct_rcu_user()). The tasks after cgroup_task_release() and before cgroup_task_free() are subject to race when they may or may not have ->usage count > 0. The race window is between css_task_iter_next() invocations when css_set_lock is released and we may arrive at a new ->task_pos. The iterator should not attempt to resurrect tasks whose ->usage count dropped to zero. (When that happens, __put_task_struct_rcu_cb() is already imminent and the returned task_struct would could be used after free.) As for the fix, we cannot simply check the signal->live count of a task on the dying list because that won't distinguish regular zombies waiting to be reaped from RCU remnant tasks that are going to be free'd. Therefore add an extra check to rule out ->usage==0 tasks from any iteration. The repeat: loop in css_task_iter_advance() doesn't consider ->usage count, so add a new loop to css_task_iter_next() to skip de-used tasks on the dying_list. Rough illustration of the possible race R (reader of cgroup.procs) T (thread) L (group leader) --------------------------------- -------------------------------- -------------------------------- L exits, signal->live > 0 cgroup_task_dead(L) css_set_skip_task_iters() // skips only cset->tasks list_add_tail(&L->cg_list, &cset->dying_tasks) css_task_iter_next() take css_set_lock css_task_iter_advance() leader && signal->live != 0 => it->task_pos = &L->cg_list release css_set_lock T exits --signal->live == 0 cgroup_task_dead(T) // css_set_lock release_task(T) cgroup_task_release(T) release_task(L) // zap_leader cgroup_task_release(L) put_task_struct_rcu_user(L) ...RCU... put_task_struct(L) L->usage = 0 /* L still on dying_tasks */ ...RCU... __put_task_struct(L) css_task_iter_next() // another iteration take css_set_lock it->task_pos = &L->cg_list get_task_struct(L) => addition on 0 drop css_set_lock cgroup_task_free(L) css_set_skip_task_iters() // dying skip comes too late free_task(L) cgroup_procs_show() task_pid_vnr(L) Fixes: 260fbcb92bbea ("cgroup: Move dying_tasks cleanup from cgroup_task_release() to cgroup_task_free()") Cc: stable@vger.kernel.org # v6.19+ Link: https://lists.debian.org/debian-kernel/2026/08/msg00220.html Reported-by: Noah Elias Feldt Reported-by: Salvatore Bonaccorso Tested-by: Salvatore Bonaccorso Signed-off-by: Michal Koutný Signed-off-by: Tejun Heo --- kernel/cgroup/cgroup.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 353c8f83439a..a3d363502b7b 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -5207,10 +5207,13 @@ struct task_struct *css_task_iter_next(struct css_task_iter *it) if (it->flags & CSS_TASK_ITER_SKIPPED) css_task_iter_advance(it); - if (it->task_pos) { + while (it->task_pos && !it->cur_task) { it->cur_task = list_entry(it->task_pos, struct task_struct, cg_list); - get_task_struct(it->cur_task); + /* a task on dying_tasks with zero refcount is only valid for + * RCU readers, not even interesting for + * CSS_TASK_ITER_WITH_DEAD, find another one */ + it->cur_task = tryget_task_struct(it->cur_task); css_task_iter_advance(it); } From 2028280686f4fa78e2f1f6dede4b6c1fd782b9e3 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 3 Sep 2026 15:19:10 +0300 Subject: [PATCH 1040/1317] drm/msm/dsi: round the byte clock rate after reparenting to the PHY PLL DSI 6G v2.9 hosts (SM8650, SM8750, Kaanapali, etc.) reparent the byte and pixel RCGs to the DSI PHY PLL at runtime from dsi_link_clk_set_rate_6g_v2_9(), after the PHY has been enabled. However dsi_calc_clk_rate_6g() runs earlier, in order to compute the bit clock request for the PHY. At that point the byte RCG still has its reset parent (XO), so clk_round_rate() returns a bogus rate, which then ends up in the PHY bit clock request and the PLL gets programmed to a wrong frequency, breaking the panel. Move the rounding to dsi_link_clk_set_rate_6g(), which is called after the RCGs have been reparented to the PLL. Storing the rounded rate at this point still makes later link_clk_set_rate() calls no-ops in the CCF. Derive the byte interface clock rate from the rounded byte clock rate, otherwise it would keep requesting the idealized rate and retrigger the PLL on every transfer. Reported-by: Abel Vesa Reported-by: Krzysztof Kozlowski Fixes: 6cd33b6f4155 ("drm/msm/dsi: round 6G byte clock rate to the PLL-achievable value") Assisted-by: LLM Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Tested-by: Konrad Dybcio # SM6115P J606F Tested-by: Abel Vesa Reviewed-by: Abel Vesa Patchwork: https://patchwork.freedesktop.org/patch/750496/ Link: https://lore.kernel.org/r/20260903-fix-eliza-dsi-v1-1-3474a6c9f2e0@oss.qualcomm.com --- drivers/gpu/drm/msm/dsi/dsi_host.c | 36 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 7e4e3718b536..b292dfd266d1 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -129,7 +129,7 @@ struct msm_dsi_host { struct clk *dsi_pll_pixel_clk; unsigned long byte_clk_rate; - unsigned long byte_intf_clk_rate; + bool byte_intf_clk_div_2; unsigned long pixel_clk_rate; unsigned long esc_clk_rate; @@ -382,8 +382,20 @@ int msm_dsi_runtime_resume(struct device *dev) int dsi_link_clk_set_rate_6g(struct msm_dsi_host *msm_host) { + unsigned long byte_intf_clk_rate; + long rounded_byte_clk_rate; int ret; + rounded_byte_clk_rate = clk_round_rate(msm_host->byte_clk, + msm_host->byte_clk_rate); + if (rounded_byte_clk_rate < 0) { + pr_err("%s: failed to round byte clock rate, %ld\n", + __func__, rounded_byte_clk_rate); + return rounded_byte_clk_rate; + } + + msm_host->byte_clk_rate = rounded_byte_clk_rate; + DBG("Set clk rates: pclk=%lu, byteclk=%lu", msm_host->pixel_clk_rate, msm_host->byte_clk_rate); @@ -401,7 +413,11 @@ int dsi_link_clk_set_rate_6g(struct msm_dsi_host *msm_host) } if (msm_host->byte_intf_clk) { - ret = clk_set_rate(msm_host->byte_intf_clk, msm_host->byte_intf_clk_rate); + byte_intf_clk_rate = msm_host->byte_clk_rate; + if (msm_host->byte_intf_clk_div_2) + byte_intf_clk_rate /= 2; + + ret = clk_set_rate(msm_host->byte_intf_clk, byte_intf_clk_rate); if (ret) { pr_err("%s: Failed to set rate byte intf clk, %d\n", __func__, ret); @@ -669,24 +685,12 @@ static void dsi_calc_pclk(struct msm_dsi_host *msm_host, bool is_bonded_dsi) int dsi_calc_clk_rate_6g(struct msm_dsi_host *msm_host, bool is_bonded_dsi) { - long rounded_byte_clk_rate; - if (!msm_host->mode) { pr_err("%s: mode not set\n", __func__); return -EINVAL; } dsi_calc_pclk(msm_host, is_bonded_dsi); - - rounded_byte_clk_rate = clk_round_rate(msm_host->byte_clk, - msm_host->byte_clk_rate); - if (rounded_byte_clk_rate < 0) { - pr_err("%s: failed to round byte clock rate, %ld\n", - __func__, rounded_byte_clk_rate); - return rounded_byte_clk_rate; - } - - msm_host->byte_clk_rate = rounded_byte_clk_rate; msm_host->esc_clk_rate = clk_get_rate(msm_host->esc_clk); return 0; } @@ -2495,9 +2499,7 @@ int msm_dsi_host_power_on(struct mipi_dsi_host *host, goto unlock_ret; } - msm_host->byte_intf_clk_rate = msm_host->byte_clk_rate; - if (phy_shared_timings->byte_intf_clk_div_2) - msm_host->byte_intf_clk_rate /= 2; + msm_host->byte_intf_clk_div_2 = phy_shared_timings->byte_intf_clk_div_2; msm_dsi_sfpb_config(msm_host, true); From f4fae975db08a9aeec0b15e145c7d4d0fe02a0ec Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 13 Sep 2026 16:58:14 +0800 Subject: [PATCH 1041/1317] drm/msm/hdmi_phy: fix runtime PM cleanup on probe failure msm_hdmi_phy_probe() enables runtime PM before enabling the PHY resources and initializing the PLL, but failures from either operation return without calling the matching pm_runtime_disable(). The remove path disables runtime PM, but it is not called when probe fails. As a result, runtime PM remains enabled after an unsuccessful probe. Route failures after pm_runtime_enable() through a common error path and disable runtime PM before returning. This issue was found by manual code inspection. Fixes: 15b4a4523859 ("drm/msm/hdmi: Create a separate HDMI PHY driver") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Krzysztof Kozlowski Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/753043/ Link: https://lore.kernel.org/r/20260913085814.1509352-1-lgs201920130244@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/hdmi/hdmi_phy.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c index eb1088755cb3..77dce35cd45e 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_phy.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c @@ -168,13 +168,13 @@ static int msm_hdmi_phy_probe(struct platform_device *pdev) ret = msm_hdmi_phy_resource_enable(phy); if (ret) - return ret; + goto err_pm_disable; ret = msm_hdmi_phy_pll_init(pdev, phy->cfg->type); if (ret) { DRM_DEV_ERROR(dev, "couldn't init PLL\n"); msm_hdmi_phy_resource_disable(phy); - return ret; + goto err_pm_disable; } msm_hdmi_phy_resource_disable(phy); @@ -182,6 +182,10 @@ static int msm_hdmi_phy_probe(struct platform_device *pdev) platform_set_drvdata(pdev, phy); return 0; + +err_pm_disable: + pm_runtime_disable(dev); + return ret; } static void msm_hdmi_phy_remove(struct platform_device *pdev) From a15fac810c76397ec9f62a6fc26c4d7ab6e238a7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 13 Sep 2026 14:33:33 +0200 Subject: [PATCH 1042/1317] dt-bindings: display/msm: Use consistent indentation in the example Correct indentation in the examples to consistent 2- or 4-spaces indentation to fix dt-check-style warnings ("example 0 [indent-consistent] indent mismatch ..."). Preferred is 4-spaces, but re-indenting entire example just for that is too much churn. Signed-off-by: Krzysztof Kozlowski Patchwork: https://patchwork.freedesktop.org/patch/753054/ Link: https://lore.kernel.org/r/20260913123331.100293-4-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- .../display/msm/qcom,qcs8300-mdss.yaml | 6 +- .../display/msm/qcom,sar2130p-mdss.yaml | 4 +- .../bindings/display/msm/qcom,sm6150-dpu.yaml | 14 ++--- .../display/msm/qcom,sm8350-mdss.yaml | 2 +- .../display/msm/qcom,x1e80100-mdss.yaml | 60 +++++++++---------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml index c41a86203e78..287cce4dbddc 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml @@ -150,7 +150,7 @@ examples: reg = <0>; dpu_intf0_out: endpoint { - remote-endpoint = <&mdss_dp0_in>; + remote-endpoint = <&mdss_dp0_in>; }; }; @@ -352,9 +352,9 @@ examples: }; port@1 { - reg = <1>; + reg = <1>; - mdss_dp_out: endpoint { }; + mdss_dp_out: endpoint { }; }; }; diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml index 44c1bb9e4109..fb3d24bbac7d 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml @@ -248,9 +248,9 @@ examples: remote-endpoint = <&usb_dp_qmpphy_dp_in>; }; }; - }; + }; - dp_opp_table: opp-table { + dp_opp_table: opp-table { compatible = "operating-points-v2"; opp-162000000 { diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml index b4f437172218..8cbf99f9fcba 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml @@ -81,7 +81,7 @@ examples: port@1 { reg = <1>; dpu_intf1_out: endpoint { - remote-endpoint = <&mdss_dsi0_in>; + remote-endpoint = <&mdss_dsi0_in>; }; }; }; @@ -90,18 +90,18 @@ examples: compatible = "operating-points-v2"; opp-19200000 { - opp-hz = /bits/ 64 <19200000>; - required-opps = <&rpmhpd_opp_low_svs>; + opp-hz = /bits/ 64 <19200000>; + required-opps = <&rpmhpd_opp_low_svs>; }; opp-25600000 { - opp-hz = /bits/ 64 <25600000>; - required-opps = <&rpmhpd_opp_svs>; + opp-hz = /bits/ 64 <25600000>; + required-opps = <&rpmhpd_opp_svs>; }; opp-307200000 { - opp-hz = /bits/ 64 <307200000>; - required-opps = <&rpmhpd_opp_nom>; + opp-hz = /bits/ 64 <307200000>; + required-opps = <&rpmhpd_opp_nom>; }; }; }; diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml index 68176de854b3..f5acf528d0e4 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml @@ -223,7 +223,7 @@ examples: phys = <&mdss_dsi0_phy>; ports { - #address-cells = <1>; + #address-cells = <1>; #size-cells = <0>; port@0 { diff --git a/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml index 8d698a2e055a..8c65a80b46e4 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml @@ -208,47 +208,47 @@ examples: #sound-dai-cells = <0>; ports { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - port@0 { - reg = <0>; + port@0 { + reg = <0>; - mdss_dp0_in: endpoint { - remote-endpoint = <&mdss_intf0_out>; - }; - }; + mdss_dp0_in: endpoint { + remote-endpoint = <&mdss_intf0_out>; + }; + }; - port@1 { - reg = <1>; + port@1 { + reg = <1>; - mdss_dp0_out: endpoint { - }; - }; + mdss_dp0_out: endpoint { + }; + }; }; mdss_dp0_opp_table: opp-table { - compatible = "operating-points-v2"; + compatible = "operating-points-v2"; - opp-160000000 { - opp-hz = /bits/ 64 <160000000>; - required-opps = <&rpmhpd_opp_low_svs>; - }; + opp-160000000 { + opp-hz = /bits/ 64 <160000000>; + required-opps = <&rpmhpd_opp_low_svs>; + }; - opp-270000000 { - opp-hz = /bits/ 64 <270000000>; - required-opps = <&rpmhpd_opp_svs>; - }; + opp-270000000 { + opp-hz = /bits/ 64 <270000000>; + required-opps = <&rpmhpd_opp_svs>; + }; - opp-540000000 { - opp-hz = /bits/ 64 <540000000>; - required-opps = <&rpmhpd_opp_svs_l1>; - }; + opp-540000000 { + opp-hz = /bits/ 64 <540000000>; + required-opps = <&rpmhpd_opp_svs_l1>; + }; - opp-810000000 { - opp-hz = /bits/ 64 <810000000>; - required-opps = <&rpmhpd_opp_nom>; - }; + opp-810000000 { + opp-hz = /bits/ 64 <810000000>; + required-opps = <&rpmhpd_opp_nom>; + }; }; }; }; From e1aeaf79dea51e6065da56924bc07e22d59012ac Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 13 Sep 2026 21:09:15 -0300 Subject: [PATCH 1043/1317] smb: client: fix unaligned access in WSL reparse point parser When wsl_to_fattr() parses WSL extended attributes, it computes a payload pointer from ea->ea_data + ea_name_length + 1. Since the smb2_file_full_ea_info struct is __packed and all WSL xattr names are 6 bytes long, the value pointer always lands at an odd byte offset, never satisfying __le32 or __le64 alignment requirements. The code then casts this pointer to __le32 * or __le64 * and dereferences it directly, which may cause alignment faults on some architectures. Replace all such casts with get_unaligned_le32() and get_unaligned_le64() in reparse_mkdev(), wsl_make_kuid(), wsl_make_kgid() and wsl_to_fattr(). Closes: https://sashiko.dev/#/patchset/20260906200517.725015-1-pc%40manguebit.org Fixes: 78e26bec4d6d ("smb: client: parse uid, gid, mode and dev from WSL reparse points") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Tom Talpey Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 4 ++-- fs/smb/client/reparse.h | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 9e31fce7e0a5..6ac69f4d391a 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1201,9 +1201,9 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, fattr->cf_gid = wsl_make_kgid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { /* File type in reparse point tag and in xattr mode must match. */ - if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v))) + if (S_DT(fattr->cf_mode) != S_DT(get_unaligned_le32(v))) return false; - fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v); + fattr->cf_mode = (umode_t)get_unaligned_le32(v); } else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) { fattr->cf_rdev = reparse_mkdev(v); have_xattr_dev = true; diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h index 49efd85b1e94..05b2cecb4495 100644 --- a/fs/smb/client/reparse.h +++ b/fs/smb/client/reparse.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "fs_context.h" #include "cifsglob.h" #include "../common/smbfsctl.h" @@ -23,7 +24,7 @@ static inline dev_t reparse_mkdev(void *ptr) { - u64 v = le64_to_cpu(*(__le64 *)ptr); + u64 v = get_unaligned_le64(ptr); return MKDEV(v & 0xffffffff, v >> 32); } @@ -31,7 +32,7 @@ static inline dev_t reparse_mkdev(void *ptr) static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb, void *ptr) { - u32 uid = le32_to_cpu(*(__le32 *)ptr); + u32 uid = get_unaligned_le32(ptr); if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_UID) return cifs_sb->ctx->linux_uid; @@ -41,7 +42,7 @@ static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb, static inline kgid_t wsl_make_kgid(struct cifs_sb_info *cifs_sb, void *ptr) { - u32 gid = le32_to_cpu(*(__le32 *)ptr); + u32 gid = get_unaligned_le32(ptr); if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_GID) return cifs_sb->ctx->linux_gid; From e1253a82bb4c0fed6706a5839fc8b6e01be1abe2 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 13 Sep 2026 21:15:41 -0300 Subject: [PATCH 1044/1317] smb: client: fix fattr leaking on wsl_to_fattr() failure wsl_to_fattr() mutates fattr fields as it parses each WSL EA. If validation later fails, the function returns false with partially mutated fattr fields that callers do not reset. Fix this by parsing into local variables and only committing them to fattr on success. Closes: https://sashiko.dev/#/patchset/20260906200517.725015-1-pc%40manguebit.org Fixes: 78e26bec4d6d ("smb: client: parse uid, gid, mode and dev from WSL reparse points") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Tom Talpey Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 6ac69f4d391a..3a27773186ae 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1149,29 +1149,30 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, u32 tag, struct cifs_fattr *fattr) { unsigned int sbflags = cifs_sb_flags(cifs_sb); + kuid_t uid = cifs_sb->ctx->linux_uid; + kgid_t gid = cifs_sb->ctx->linux_gid; struct smb2_file_full_ea_info *ea; bool have_xattr_dev = false; + dev_t rdev = 0; + umode_t mode; u32 next = 0; - fattr->cf_uid = cifs_sb->ctx->linux_uid; - fattr->cf_gid = cifs_sb->ctx->linux_gid; - - fattr->cf_mode &= ~S_IFMT; + mode = fattr->cf_mode & ~S_IFMT; switch (tag) { case IO_REPARSE_TAG_LX_SYMLINK: - fattr->cf_mode |= S_IFLNK; + mode |= S_IFLNK; break; case IO_REPARSE_TAG_LX_FIFO: - fattr->cf_mode |= S_IFIFO; + mode |= S_IFIFO; break; case IO_REPARSE_TAG_AF_UNIX: - fattr->cf_mode |= S_IFSOCK; + mode |= S_IFSOCK; break; case IO_REPARSE_TAG_LX_CHR: - fattr->cf_mode |= S_IFCHR; + mode |= S_IFCHR; break; case IO_REPARSE_TAG_LX_BLK: - fattr->cf_mode |= S_IFBLK; + mode |= S_IFBLK; break; } @@ -1195,26 +1196,29 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) { if (!(sbflags & CIFS_MOUNT_OVERR_UID)) - fattr->cf_uid = wsl_make_kuid(cifs_sb, v); + uid = wsl_make_kuid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) { if (!(sbflags & CIFS_MOUNT_OVERR_GID)) - fattr->cf_gid = wsl_make_kgid(cifs_sb, v); + gid = wsl_make_kgid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { /* File type in reparse point tag and in xattr mode must match. */ - if (S_DT(fattr->cf_mode) != S_DT(get_unaligned_le32(v))) + if (S_DT(mode) != S_DT(get_unaligned_le32(v))) return false; - fattr->cf_mode = (umode_t)get_unaligned_le32(v); + mode = get_unaligned_le32(v); } else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) { - fattr->cf_rdev = reparse_mkdev(v); + rdev = reparse_mkdev(v); have_xattr_dev = true; } } while (next); out: - /* Major and minor numbers for char and block devices are mandatory. */ if (!have_xattr_dev && (tag == IO_REPARSE_TAG_LX_CHR || tag == IO_REPARSE_TAG_LX_BLK)) return false; + fattr->cf_uid = uid; + fattr->cf_gid = gid; + fattr->cf_mode = mode; + fattr->cf_rdev = rdev; return true; } From f97d8c7bab7843631206a114986c9059da03efeb Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Fri, 11 Sep 2026 15:34:32 +0800 Subject: [PATCH 1045/1317] rds: ib: use rds_conn_drop() on protocol version mismatch rds_ib_cm_connect_complete() runs from the RDMA-CM event handler with conn->c_cm_lock held. When the peer negotiates a protocol version older than RDS_PROTOCOL_COMPAT_VERSION, the handler calls rds_conn_destroy(), which is only safe in the rmmod path: it synchronously tears the connection down and flush_work()es the shutdown work cp_down_w. That shutdown work (rds_conn_shutdown()) needs cp_cm_lock, which is the very lock the event handler still holds, so the flush never completes: the two workers wait on each other and the RDS connection workqueues stall for good. All other RDMA-CM failure paths (REJECTED, CONNECT_ERROR, DISCONNECTED) use rds_conn_drop(), which marks the connection RDS_CONN_ERROR and schedules the shutdown work asynchronously. Use it here as well. Fixes: f147dd9ecabf ("RDS/IB: Disallow connections less than RDS 3.1") Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Reviewed-by: Allison Henderson Signed-off-by: Aohan Mei Link: https://patch.msgid.link/20260911073436.3542080-1-ljp1205831794@gmail.com Signed-off-by: Jakub Kicinski --- net/rds/ib_cm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c index 4feb0edc360c..6e3110a04ae6 100644 --- a/net/rds/ib_cm.c +++ b/net/rds/ib_cm.c @@ -115,7 +115,7 @@ void rds_ib_cm_connect_complete(struct rds_connection *conn, struct rdma_cm_even &conn->c_laddr, &conn->c_faddr, RDS_PROTOCOL_MAJOR(conn->c_version), RDS_PROTOCOL_MINOR(conn->c_version)); - rds_conn_destroy(conn); + rds_conn_drop(conn); return; } } From 6fb0a9d9071f1ff0cc5cfc0782302d9c90d642cb Mon Sep 17 00:00:00 2001 From: Chunfeng Song Date: Thu, 10 Sep 2026 05:51:10 +0000 Subject: [PATCH 1046/1317] rust: net: phy: fix off-by-one bit positions in device status accessors The hand-written bitfield offsets in is_link_up(), is_autoneg_enabled() and is_autoneg_completed() were correct when the abstraction was merged: at that time autoneg, link, and autoneg_complete were at bits 13, 14, and 15 of struct phy_device's first bitfield unit. Commit 2796ff1e3dca ("net: phy: add flag is_genphy_driven to struct phy_device") later inserted is_genphy_driven just before autoneg, shifting the three fields up by one, so the accessors now read: is_link_up() reads bit 14 = autoneg is_autoneg_enabled() reads bit 13 = is_genphy_driven is_autoneg_completed() reads bit 15 = link The official ax88796b Rust driver uses all three accessors in its read_status() implementation, so it inherits the bug. phy_attach_direct() sets is_genphy_driven only when it falls back to the generic driver, and ax88796b has a real driver, so is_genphy_driven stays 0. The broken is_autoneg_enabled() therefore reads bit 13 as 0, compares it against AUTONEG_ENABLE (1), and always returns false, so read_status() never reaches the resolve_aneg_linkmode() call. The ordinary bindgen accessors take &self. Calling them through (*phydev).link() would create a shared reference to the complete bindings::phy_device, which is not appropriate for an object wrapped in Opaque. Use the bindgen-generated raw accessors (link_raw(), autoneg_raw(), and autoneg_complete_raw()) instead. They retain the bit positions and endianness handling generated from the C layout without creating a Rust reference to the complete phy_device. Drop the hand-written numbers together with the TODO comment that marked them as a stopgap. The raw accessors are only emitted by bindgen 0.71 and later, and were added at the Rust-for-Linux project's request, so this fix can only be backported to stable branches whose minimum bindgen version is at least that, hence the scope on the Cc: stable line below. Found by a static equivalence audit (C2RustDrv, a C-to-Rust driver migration tool) that compares hand-written bitfield offsets against the bindgen layout of struct phy_device. Verified by building the bindings and checking the generated accessors; no runtime testing was possible without PHY hardware. Fixes: 2796ff1e3dca ("net: phy: add flag is_genphy_driven to struct phy_device") Cc: stable@vger.kernel.org # Only 7.1.y and later (requires bindgen's raw pointer accessors). Link: https://github.com/rust-lang/rust-bindgen/issues/2674 Signed-off-by: Chunfeng Song Reviewed-by: FUJITA Tomonori Link: https://patch.msgid.link/20260910055110.167110-1-springbreeze@stu.pku.edu.cn Signed-off-by: Jakub Kicinski --- rust/kernel/net/phy.rs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs index 956cda573ddb..c4e7b1d6c6f4 100644 --- a/rust/kernel/net/phy.rs +++ b/rust/kernel/net/phy.rs @@ -123,39 +123,37 @@ pub fn state(&self) -> DeviceState { /// Gets the current link state. /// /// It returns true if the link is up. + #[inline] pub fn is_link_up(&self) -> bool { - const LINK_IS_UP: u64 = 1; - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(14, 1) == LINK_IS_UP + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let link = unsafe { bindings::phy_device::link_raw(phydev) }; + link == 1 } /// Gets the current auto-negotiation configuration. /// /// It returns true if auto-negotiation is enabled. + #[inline] pub fn is_autoneg_enabled(&self) -> bool { - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(13, 1) == u64::from(bindings::AUTONEG_ENABLE) + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let autoneg = unsafe { bindings::phy_device::autoneg_raw(phydev) }; + autoneg == bindings::AUTONEG_ENABLE } /// Gets the current auto-negotiation state. /// /// It returns true if auto-negotiation is completed. + #[inline] pub fn is_autoneg_completed(&self) -> bool { - const AUTONEG_COMPLETED: u64 = 1; - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(15, 1) == AUTONEG_COMPLETED + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let completed = unsafe { bindings::phy_device::autoneg_complete_raw(phydev) }; + completed == 1 } /// Sets the speed of the PHY. From bde5212360bd44506edec073ebbd6d0c72f75820 Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Sat, 12 Sep 2026 17:43:06 +0400 Subject: [PATCH 1047/1317] net: phy: mediatek: do not report link and per-speed LED rules together mtk_phy_led_hw_ctrl_get() reports TRIGGER_NETDEV_LINK whenever any of the speed bits in on_set is on, and in addition reports every individual TRIGGER_NETDEV_LINK_* bit that is set. The netdev trigger refuses that combination: netdev_led_attr_store() rejects TRIGGER_NETDEV_LINK together with any per-speed rule, and it validates the whole resulting mode rather than just the bit being written. Once the hardware has any link bit programmed, every write to the trigger attributes of that LED therefore fails with -EINVAL and the LED can no longer be configured. The rules are also fed back into the hardware: the trigger stores what is read back, and a later write of device_name programs it again, expanding TRIGGER_NETDEV_LINK to every speed in on_set. An LED configured for a single speed is thereby silently widened to "on at any link speed". Both are easy to see on the EcoNet EN7528, whose four PHYs share one LED block. The first LED programs the block correctly, the second reads those rules back and rewrites them widened, and the remaining two then read the widened value, so an LED configured for "link_10 link_100" ends up lit on a 1000 Mbps link. on_set holds every speed the LED can indicate and is exactly what mtk_phy_led_hw_ctrl_set() programs for TRIGGER_NETDEV_LINK, so report the speed independent rule only when all of them are on, and the individual speeds otherwise. The mapping is then the inverse of the one used when programming the LED and round trips without changing the register. Fixes: c66937b0f8db ("net: phy: mediatek-ge-soc: support PHY LEDs") Cc: stable@vger.kernel.org Signed-off-by: Ahmed Naseef Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260912134306.3544329-1-naseefkm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/mediatek/mtk-phy-lib.c | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c index dfd0f4e439a2..608072fbfde9 100644 --- a/drivers/net/phy/mediatek/mtk-phy-lib.c +++ b/drivers/net/phy/mediatek/mtk-phy-lib.c @@ -156,20 +156,27 @@ int mtk_phy_led_hw_ctrl_get(struct phy_device *phydev, u8 index, if (!rules) return 0; - if (on & on_set) + /* TRIGGER_NETDEV_LINK must not be reported together with any of the + * per-speed rules, the netdev trigger rejects that combination. + * on_set holds every speed this LED can indicate and is what + * mtk_phy_led_hw_ctrl_set() programs for TRIGGER_NETDEV_LINK, so + * report the speed independent rule only when they are all on. + */ + if ((on & on_set) == on_set) { *rules |= BIT(TRIGGER_NETDEV_LINK); + } else { + if (on & MTK_PHY_LED_ON_LINK10) + *rules |= BIT(TRIGGER_NETDEV_LINK_10); - if (on & MTK_PHY_LED_ON_LINK10) - *rules |= BIT(TRIGGER_NETDEV_LINK_10); + if (on & MTK_PHY_LED_ON_LINK100) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); - if (on & MTK_PHY_LED_ON_LINK100) - *rules |= BIT(TRIGGER_NETDEV_LINK_100); + if (on & MTK_PHY_LED_ON_LINK1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); - if (on & MTK_PHY_LED_ON_LINK1000) - *rules |= BIT(TRIGGER_NETDEV_LINK_1000); - - if (on & MTK_PHY_LED_ON_LINK2500) - *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + if (on & MTK_PHY_LED_ON_LINK2500) + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + } if (on & MTK_PHY_LED_ON_FDX) *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX); From 7616242a2b37883f7322aaa1d2bd6cd0fed28315 Mon Sep 17 00:00:00 2001 From: Andrea Mayer Date: Sun, 13 Sep 2026 21:44:21 +0200 Subject: [PATCH 1048/1317] seg6: set IPSKB_L3SLAVE from IP6SKB_L3SLAVE on IPIP decapsulation When an SRv6 packet arrives on an interface enslaved to a VRF, vrf_ip6_rcv() sets IP6SKB_L3SLAVE in IP6CB, but decap_and_validate() has never set IPSKB_L3SLAVE in IPCB. The bit stayed clear in the common case, and with CONFIG_IPV6_MIP6 the leftover frag_max_size of a reassembled outer packet could even set it, with no VRF involved. Commit 44930446dde4 ("ipv6: seg6: clear IPv4 control block on IPIP decapsulation") then made the unreliable bit reliably clear. The effect of the missing flag is visible with End.DX4 when a delivery to a local address of the node reaches the socket lookup. For example, a UDP socket bound to the enslaved ingress interface does not receive any of the decapsulated packets, while an unbound socket outside the VRF does. This contradicts Documentation/networking/vrf.rst: by default the scope of an unbound UDP or TCP socket is limited to the default VRF. Set IPSKB_L3SLAVE for IPv4 in decap_and_validate(), which already does the same for IPv6. The socket lookup then matches the decapsulated packet like any other packet received on that enslaved interface. Such a packet matches an unbound UDP or TCP socket only when udp_l3mdev_accept or tcp_l3mdev_accept is set. Fixes: 891ef8dd2a8d ("ipv6: sr: implement additional seg6local actions") Signed-off-by: Andrea Mayer Reviewed-by: David Ahern Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260913194421.31-1-andrea.mayer@uniroma2.it Signed-off-by: Jakub Kicinski --- net/ipv6/seg6_local.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 7b5212220185..d1070aec7b72 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -257,10 +257,13 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) return false; if (proto == IPPROTO_IPIP) { + bool l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags); int iif = IP6CB(skb)->iif; memset(IPCB(skb), 0, sizeof(*IPCB(skb))); IPCB(skb)->iif = iif; + if (l3slave) + IPCB(skb)->flags |= IPSKB_L3SLAVE; } else if (proto == IPPROTO_IPV6) { bool l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags); int iif = IP6CB(skb)->iif; From 9e92ad4630f5dd1838ce6bbe6b1bd2c73d34de36 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Thu, 10 Sep 2026 14:13:15 +0100 Subject: [PATCH 1049/1317] net: dsa: mxl862xx: disable the stats poll on teardown mxl862xx_setup() arms the stats poll before mxl862xx_setup_mdio(), and nothing stops it until dsa_register_switch() has returned an error to mxl862xx_probe(). DSA frees the dsa_port list before it returns, so a poll that fires once .setup or a later step of dsa_tree_setup() has failed walks freed ports. On shutdown the user ports stay registered, and the WORK_STOPPED flag test in mxl862xx_get_stats64() is not atomic with the cancel in mxl862xx_shutdown(), so a re-arm that read the flag before it was set queues the poll after cancel_delayed_work_sync() has returned. Arm the poll once .setup has succeeded and stop it from a .teardown op, which DSA calls on unregister and after a failed registration, in both cases before it frees the ports. Use disable_delayed_work_sync() there and in shutdown(): it drains a running poll as the cancel did and turns every later attempt to queue the work into a no-op, so the re-arm cannot bring the poll back. remove() and the probe error path only set WORK_STOPPED, which crc_err_work tests before it walks the ports. Fixes: a21d33a5265f ("net: dsa: mxl862xx: implement .get_stats64") Signed-off-by: Daniel Golle Link: https://patch.msgid.link/1eb6f7fc1789b67e4b11e3f4d5ff080d0b6f7cbb.1789045590.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mxl862xx/mxl862xx.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c index cfa7e3e269a2..e05ad52cd297 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -685,10 +685,22 @@ static int mxl862xx_setup(struct dsa_switch *ds) if (ret) return ret; + ret = mxl862xx_setup_mdio(ds); + if (ret) + return ret; + schedule_delayed_work(&priv->stats_work, MXL862XX_STATS_POLL_INTERVAL); - return mxl862xx_setup_mdio(ds); + return 0; +} + +static void mxl862xx_teardown(struct dsa_switch *ds) +{ + struct mxl862xx_priv *priv = ds->priv; + + set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); + disable_delayed_work_sync(&priv->stats_work); } static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable) @@ -2047,9 +2059,7 @@ static void mxl862xx_get_stats64(struct dsa_switch *ds, int port, spin_unlock_bh(&priv->ports[port].stats_lock); - /* Trigger a fresh poll so the next read sees up-to-date counters. - * No-op if the work is already pending, running, or teardown started. - */ + /* Trigger a fresh poll so the next read sees up-to-date counters. */ if (!test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags)) schedule_delayed_work(&priv->stats_work, 0); } @@ -2057,6 +2067,7 @@ static void mxl862xx_get_stats64(struct dsa_switch *ds, int port, static const struct dsa_switch_ops mxl862xx_switch_ops = { .get_tag_protocol = mxl862xx_get_tag_protocol, .setup = mxl862xx_setup, + .teardown = mxl862xx_teardown, .port_setup = mxl862xx_port_setup, .port_teardown = mxl862xx_port_teardown, .phylink_get_caps = mxl862xx_phylink_get_caps, @@ -2131,7 +2142,6 @@ static int mxl862xx_probe(struct mdio_device *mdiodev) err = dsa_register_switch(ds); if (err) { set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); mxl862xx_host_shutdown(priv); for (i = 0; i < MXL862XX_MAX_PORTS; i++) cancel_work_sync(&priv->ports[i].host_flood_work); @@ -2152,7 +2162,6 @@ static void mxl862xx_remove(struct mdio_device *mdiodev) priv = ds->priv; set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); dsa_unregister_switch(ds); @@ -2181,7 +2190,7 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev) dsa_switch_shutdown(ds); set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); + disable_delayed_work_sync(&priv->stats_work); mxl862xx_host_shutdown(priv); From 23ca4ddc4fce2c233a49e9fd34d4b5b02bd7324e Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sun, 13 Sep 2026 21:00:52 +0200 Subject: [PATCH 1050/1317] net: bcmgenet: restore the hardware filters on open bcmgenet_hfb_init() runs INIT_LIST_HEAD() on priv->rxnfc_list, which drops every rule off the list, and bcmgenet_open() calls it on each ifup. Every rule the user configured is silently lost: # ethtool -N eth0 flow-type ether dst $MAC action 0 Added rule with ID 0 # ethtool -n eth0 | grep -c Filter: 1 # ip link set eth0 down && ip link set eth0 up # ethtool -n eth0 | grep -c Filter: 0 Initialise the lists once at probe and restore the rules on open, as bcmgenet_resume() already does. Fixes: 3e370952287c ("net: bcmgenet: add support for ethtool rxnfc flows") Signed-off-by: Nicolai Buchwitz Reviewed-by: Justin Chen Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260913190052.939955-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/genet/bcmgenet.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index a2305e6428d1..b916080f4ff1 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -749,8 +749,17 @@ static void bcmgenet_hfb_init(struct bcmgenet_priv *priv) INIT_LIST_HEAD(&priv->rxnfc_rules[i].list); priv->rxnfc_rules[i].state = BCMGENET_RXNFC_STATE_UNUSED; } +} + +static void bcmgenet_hfb_restore(struct bcmgenet_priv *priv) +{ + struct bcmgenet_rxnfc_rule *rule; bcmgenet_hfb_clear(priv); + + list_for_each_entry(rule, &priv->rxnfc_list, list) + if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) + bcmgenet_hfb_create_rxnfc_filter(priv, rule); } static int bcmgenet_begin(struct net_device *dev) @@ -3376,8 +3385,8 @@ static int bcmgenet_open(struct net_device *dev) bcmgenet_set_hw_addr(priv, dev->dev_addr); - /* HFB init */ - bcmgenet_hfb_init(priv); + /* Restore the filters, the MAC was reset above */ + bcmgenet_hfb_restore(priv); /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, true); @@ -4075,6 +4084,7 @@ static int bcmgenet_probe(struct platform_device *pdev) /* Mii wait queue */ init_waitqueue_head(&priv->wq); + bcmgenet_hfb_init(priv); INIT_WORK(&priv->bcmgenet_irq_work, bcmgenet_irq_task); priv->clk_wol = devm_clk_get_optional(&priv->pdev->dev, "enet-wol"); @@ -4272,10 +4282,7 @@ static int bcmgenet_resume(struct device *d) bcmgenet_set_hw_addr(priv, dev->dev_addr); /* Restore hardware filters */ - bcmgenet_hfb_clear(priv); - list_for_each_entry(rule, &priv->rxnfc_list, list) - if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) - bcmgenet_hfb_create_rxnfc_filter(priv, rule); + bcmgenet_hfb_restore(priv); /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, false); From 82431877d837a6c2593efd8e66ba28b35a220ca4 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 5 Sep 2026 23:36:30 +0000 Subject: [PATCH 1051/1317] sysctl: Check range in proc_dointvec_ms_jiffies_minmax Add the range check to do_proc_int_conv_ms_jiffies_minmax that commit d174174c6776 ("sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions") incorrectly removed. Fixes: d174174c6776 ("sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions") Signed-off-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 213ae1d6a014..70926b73905a 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -181,7 +181,7 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, int *k_ptr, int dir, const struct ctl_table *tbl) { - return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false, + return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, true, sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms); } From 318012c56576e09806747f64f89f8f3cde1f999f Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 5 Sep 2026 23:36:31 +0000 Subject: [PATCH 1052/1317] sysctl: Check range in do_proc_ulong_conv_ms_jiffies Add the range check back to do_proc_ulong_conv_ms_jiffies that commit b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") incorrectly removed. Append "_minmax" to the end of do_proc_ulong_conv_ms_jiffies so it is clear that there should be a range check. Fixes: b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") Signed-off-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 70926b73905a..41f88ebac74a 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -195,10 +195,10 @@ static int sysctl_k2u_ulong_conv_ms(ulong *u_ptr, const ulong *k_ptr) return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, sysctl_jiffies_to_msecs); } -static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *tbl) +static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) { - return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, false, + return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, true, sysctl_u2k_ulong_conv_ms, sysctl_k2u_ulong_conv_ms); } @@ -229,8 +229,8 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, return -ENOSYS; } -static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *tbl) +static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) { return -ENOSYS; } @@ -333,7 +333,7 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return proc_doulongvec_conv(table, dir, buffer, lenp, ppos, - do_proc_ulong_conv_ms_jiffies); + do_proc_ulong_conv_ms_jiffies_minmax); } EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); From afdf35cfae0d039a4a6c907fa5d8391f1ef0a0aa Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 10 Sep 2026 12:22:16 +0200 Subject: [PATCH 1053/1317] sysctl: Fix type truncation in sysctl_msec_to_jiffies Return MAX_JIFFY_OFFSET for all the values truncated when val (u64) is passed to msecs_to_jiffies (u32). This aligns with how very large millisecond values get translated into MAX_JIFFY_OFFSET. Fixes: b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") Suggested-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 41f88ebac74a..80c354811538 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -136,6 +136,8 @@ static int sysctl_k2u_int_conv_userhz(bool *negp, ulong *u_ptr, const int *k_ptr static ulong sysctl_msecs_to_jiffies(const ulong val) { + if (val > jiffies_to_msecs(MAX_JIFFY_OFFSET)) + return MAX_JIFFY_OFFSET; return msecs_to_jiffies(val); } From 3ed11c671ff7ec58c8fd96410233c677df23f407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Fri, 4 Sep 2026 10:26:13 +0200 Subject: [PATCH 1054/1317] dma-buf/dma-fence: fix checking signaling bit for timeline and driver name v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The patch "dma-buf: dma-fence: Fix potential NULL pointer dereference" changed the check to test for the ops pointer instead of the signaled bit to avoid a potential NULL dereference when the ops pointer has been cleared. The problem is now that the ops pointer is cleared only when neither the release nor the wait callback is implemented and this isn't true for a lot of dma_fence implementations yet. So those implementations lost the RCU protection after signaling of the returned string resulting in potential use after free. Add the signaling check additional to the ops pointer check so that we have both the protection against NULL dereference as well as the RCU protection after signaling for the returned string. v2: improve comments to note RCU protection and explain why we check both signaling state and ops pointer v3: some comment improvements suggested by Philip Signed-off-by: Christian König Fixes: 035219a760ed ("dma-buf: dma-fence: Fix potential NULL pointer dereference") CC: stable@vger.kernel.org # 7.2+ Reported-by: Jonghyuk Kim(MalHyuk) Tested-by: Jonghyuk Kim(MalHyuk) Reviewed-by: Philipp Stanner Link: https://lore.kernel.org/r/20260914182740.1587-1-christian.koenig@amd.com --- drivers/dma-buf/dma-fence.c | 14 ++++++++++++-- include/linux/dma-fence.h | 6 ++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c index 05090fb0fd5a..bd58688b81a7 100644 --- a/drivers/dma-buf/dma-fence.c +++ b/drivers/dma-buf/dma-fence.c @@ -1170,7 +1170,12 @@ const char __rcu *dma_fence_driver_name(struct dma_fence *fence) /* RCU protection is required for safe access to returned string */ ops = rcu_dereference(fence->ops); - if (ops) + + /* + * Make load ordering irrelevant by checking both signaled state and ops + * pointer and ops pointer is only set to NULL on newer implementations. + */ + if (!dma_fence_test_signaled_flag(fence) && ops) return (const char __rcu *)ops->get_driver_name(fence); else return (const char __rcu *)"detached-driver"; @@ -1203,7 +1208,12 @@ const char __rcu *dma_fence_timeline_name(struct dma_fence *fence) /* RCU protection is required for safe access to returned string */ ops = rcu_dereference(fence->ops); - if (ops) + + /* + * Make load ordering irrelevant by checking both signaled state and ops + * pointer and ops pointer is only set to NULL on newer implementations. + */ + if (!dma_fence_test_signaled_flag(fence) && ops) return (const char __rcu *)ops->get_timeline_name(fence); else return (const char __rcu *)"signaled-timeline"; diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h index 158cd609f103..ffa99b930843 100644 --- a/include/linux/dma-fence.h +++ b/include/linux/dma-fence.h @@ -141,6 +141,9 @@ struct dma_fence_ops { * compute the name at runtime, without having it to store permanently * for each fence, or build a cache of some sort. * + * The returned string is RCU protected and can be freed after the fence + * signaled and a RCU grace period passed. + * * This callback is mandatory. */ const char * (*get_driver_name)(struct dma_fence *fence); @@ -153,6 +156,9 @@ struct dma_fence_ops { * having it to store permanently for each fence, or build a cache of * some sort. * + * The returned string is RCU protected and can be freed after the fence + * signaled and a RCU grace period passed. + * * This callback is mandatory. */ const char * (*get_timeline_name)(struct dma_fence *fence); From 5b644229bd677d52c4efa5973c1fab842c57f896 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 22:59:59 -0700 Subject: [PATCH 1055/1317] xfs: guard against igrab failure in xrep_findparent_from_dcache LOLLM suggests that we need to handle igrab returning NULL here. I don't think it's possible for the inode to enter I_FREEING or I_WILL_FREE while we have an active reference to the corresponding dentry, but we can code defensively anyway. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/findparent.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/xfs/scrub/findparent.c b/fs/xfs/scrub/findparent.c index eab3ac2704be..d921fe5a9b0c 100644 --- a/fs/xfs/scrub/findparent.c +++ b/fs/xfs/scrub/findparent.c @@ -473,6 +473,9 @@ xrep_findparent_from_dcache( pip = igrab(d_inode(parent)); dput(parent); + if (!pip) + goto out_dput; + if (S_ISDIR(pip->i_mode)) { ret = pip->i_ino; trace_xrep_findparent_from_dcache(sc->ip, ret); From afbccf99f7f82117cba9ad4b0b006692030f49e8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:16 -0700 Subject: [PATCH 1056/1317] xfs: don't assert when XFS_SCRUB_TYPE_HEALTHY scans return corruption XFS_SCRUB_TYPE_HEALTHY is a synthentic scrub type so that xfs_scrub can tell the kernel "Hey, I finished a scan and saw no problems" and have the kernel forget that it saw indirect evidence of corruption. Unfortunately, as LOLLM points out, it's possible for the health system to record a new corruption just before xfs_scrub gets to XFS_SCRUB_TYPE_HEALTHY. In this case, the existing logic doesn't return early and instead wanders into unknown regions of type_to_health_flag and trips the assert because HEALTHY doesn't have a group assignment. Fix the logic so that we always return early for a HEALTHY scrub type, even if we decide not to call xchk_mark_all_healthy. Cc: stable@vger.kernel.org # v6.9 Fixes: a1f3e0cca41036 ("xfs: update health status if we get a clean bill of health") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/health.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/xfs/scrub/health.c b/fs/xfs/scrub/health.c index 2171bcf0f6c1..487ecc5f9f3c 100644 --- a/fs/xfs/scrub/health.c +++ b/fs/xfs/scrub/health.c @@ -202,9 +202,9 @@ xchk_update_health( * there's no sick flag defined for it, so we branch here ahead of the * mask check. */ - if (sc->sm->sm_type == XFS_SCRUB_TYPE_HEALTHY && - !(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) { - xchk_mark_all_healthy(sc->mp); + if (sc->sm->sm_type == XFS_SCRUB_TYPE_HEALTHY) { + if (!(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) + xchk_mark_all_healthy(sc->mp); return; } From bb991b7f79dd34cc5f24db0f736bf75630c970e7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:31 -0700 Subject: [PATCH 1057/1317] xfs: fix attr fork block count checks in xrep_inode_blockcounts LOLLM points out that a file has an attr fork, it will call xchk_inode_count_blocks to set @ablocks to the number of fsblocks mapped by the attr fork; but then it'll compare @blocks (aka the count of fsblocks mapped by the data fork). We already checked that and we never do anything with @acount, so I think this is clearly a bug. Fix the comparison. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 8bc508336aa5..b87c22146233 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1702,7 +1702,7 @@ xrep_inode_blockcounts( &acount); if (error) return error; - if (count >= sc->mp->m_sb.sb_dblocks) + if (acount >= sc->mp->m_sb.sb_dblocks) return -EFSCORRUPTED; error = xrep_ino_ensure_extent_count(sc, XFS_ATTR_FORK, nextents); From 1c32cdc986467eaffeedb6c5334852809555b82d Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:47 -0700 Subject: [PATCH 1058/1317] xfs: release orphanage dir inode if chown fails LOLLM points out that we leak the igrab'd reference to the orphanage directory inode if chowning it fails. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: 1e58a8ccf2597c ("xfs: move orphan files to the orphanage") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/orphanage.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/orphanage.c b/fs/xfs/scrub/orphanage.c index 3aca66869b80..21e31eeaa042 100644 --- a/fs/xfs/scrub/orphanage.c +++ b/fs/xfs/scrub/orphanage.c @@ -192,12 +192,16 @@ xrep_orphanage_create( /* Make sure the orphanage is owned by root. */ error = xrep_chown_orphanage(sc, XFS_I(orphanage_inode)); if (error) - goto out_dput_orphanage; + goto out_rele_orphanage; /* Stash the reference for later and bail out. */ sc->orphanage = XFS_I(orphanage_inode); sc->orphanage_ilock_flags = 0; + orphanage_inode = NULL; +out_rele_orphanage: + if (orphanage_inode) + xchk_irele(sc, XFS_I(orphanage_inode)); out_dput_orphanage: end_creating(orphanage_dentry); out_dput_root: From 8b4ad2814274d43ed8bdfcf857efb8c79815d9c6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:01:03 -0700 Subject: [PATCH 1059/1317] xfs: release AGFL after walking it during rmapbt repair LOLLM suggests that we not hold the AGFL locked to the scrub transaction for any longer than we have to when we're rebuilding the rmapbt. Since we only took it to generate an rmap record for the AGFL blocks, I think we can safely release it. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rmap_repair.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/rmap_repair.c b/fs/xfs/scrub/rmap_repair.c index 590f9f41856e..725035bf4903 100644 --- a/fs/xfs/scrub/rmap_repair.c +++ b/fs/xfs/scrub/rmap_repair.c @@ -1109,6 +1109,7 @@ xrep_rmap_try_reserve( return error; error = xfs_agfl_walk(sc->mp, agf, agfl_bp, xrep_rmap_walk_agfl, &ra); + xfs_trans_brelse(sc->tp, agfl_bp); if (error) return error; From 984aab2d905a8557fafb27cd9e8713d6d12b3437 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:01:18 -0700 Subject: [PATCH 1060/1317] xfs: use correct jiffies comparison function in xchk_maybe_relax LOLLM points out that we're supposed to use time_after_eq, not a raw >= operation here, or else jiffies wraps can go unnoticed. Fix this. Cc: stable@vger.kernel.org # v6.10 Fixes: 271557de7cbfde ("xfs: reduce the rate of cond_resched calls inside scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/scrub.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h index 737a5d6db15f..b093945f3631 100644 --- a/fs/xfs/scrub/scrub.h +++ b/fs/xfs/scrub/scrub.h @@ -40,7 +40,7 @@ static inline int xchk_maybe_relax(struct xchk_relax *widget) return 0; widget->resched_nr = 0; - if (unlikely(widget->next_resched <= jiffies)) { + if (unlikely(time_after_eq(jiffies, widget->next_resched))) { cond_resched(); widget->next_resched = XCHK_RELAX_NEXT; } From 3083ba8dde765a9ab2337f3db68d00724a6b1202 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:53:39 -0700 Subject: [PATCH 1061/1317] xfs: check padding field in xfs_ioc_commit_range LOLLM points out that we don't check the ioctl padding field here, so let's do that. I don't think there are many users yet since exchrange requires a new feature flag, so it's a good time to try to plug this hole. Cc: stable@vger.kernel.org # v6.12 Fixes: 398597c3ef7fb1 ("xfs: introduce new file range commit ioctls") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchrange.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index c69ecd6a19de..07090487c581 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -902,7 +902,7 @@ xfs_ioc_commit_range( if (copy_from_user(&args, argp, sizeof(args))) return -EFAULT; - if (args.flags & ~XFS_EXCHANGE_RANGE_ALL_FLAGS) + if (args.pad || (args.flags & ~XFS_EXCHANGE_RANGE_ALL_FLAGS)) return -EINVAL; if (kern_f->magic != XCR_FRESH_MAGIC) return -EBUSY; From 8fc18580ec17f90beac4c933fbe4c74dcd3b7f36 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:53:55 -0700 Subject: [PATCH 1062/1317] xfs: don't call xfs_exchange_range_finish for a dry run LOLLM noticed that we strip file privileges and whatnot even for a dry run. We also shouldn't flush dirty data to disk or trim COW staging events for a dry run. Neither of those behaviors are allowed by the manpage, so fix that by exiting early on DRY_RUN in various functions. Cc: stable@vger.kernel.org # v6.10 Fixes: 42672471f938cd ("xfs: bind together the front and back ends of the file range exchange code") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchrange.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index 07090487c581..fafb4e3f065c 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -633,6 +633,9 @@ xfs_exchrange_prep( if (error) return error; + if (fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN) + return 0; + trace_xfs_exchrange_flush(fxr, ip1, ip2); /* Flush the relevant ranges of both files. */ @@ -709,9 +712,11 @@ xfs_exchrange_contents( * other file write would do. This may involve turning on support for * logged xattrs if either file has security capabilities. */ - error = xfs_exchange_range_finish(fxr); - if (error) - goto out_unlock; + if (!(fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN)) { + error = xfs_exchange_range_finish(fxr); + if (error) + goto out_unlock; + } out_unlock: xfs_iunlock2_io_mmap(ip1, ip2); From 471e0b6e2ddac9e16b8dc2153d6e5575fefb8a2f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:11 -0700 Subject: [PATCH 1063/1317] xfs: use the correct reservations for rtrmap/refcount recovery LOLLM noticed that we might reserve the wrong number of blocks for recovering rtrmap and rtrefcount updates after a crash. Fix that. Cc: stable@vger.kernel.org # v6.14 Fixes: 5e0679d1c62f25 ("xfs: support recovering rmap intent items targetting realtime extents") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_refcount_item.c | 8 ++++++-- fs/xfs/xfs_rmap_item.c | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_refcount_item.c b/fs/xfs/xfs_refcount_item.c index 8bccf89a7766..682c6e1b45e3 100644 --- a/fs/xfs/xfs_refcount_item.c +++ b/fs/xfs/xfs_refcount_item.c @@ -508,6 +508,7 @@ xfs_refcount_recover_work( struct xfs_cui_log_item *cuip = CUI_ITEM(lip); struct xfs_trans *tp; struct xfs_mount *mp = lip->li_log->l_mp; + unsigned int dblocks; bool isrt = xfs_cui_item_isrt(lip); int i; int error = 0; @@ -543,8 +544,11 @@ xfs_refcount_recover_work( * full btree split on either end of the refcount range. */ resv = xlog_recover_resv(&M_RES(mp)->tr_itruncate); - error = xfs_trans_alloc(mp, &resv, mp->m_refc_maxlevels * 2, 0, - XFS_TRANS_RESERVE, &tp); + if (isrt) + dblocks = mp->m_rtrefc_maxlevels * 2; + else + dblocks = mp->m_refc_maxlevels * 2; + error = xfs_trans_alloc(mp, &resv, dblocks, 0, XFS_TRANS_RESERVE, &tp); if (error) return error; diff --git a/fs/xfs/xfs_rmap_item.c b/fs/xfs/xfs_rmap_item.c index 2a3a73a8566d..000cff1ce324 100644 --- a/fs/xfs/xfs_rmap_item.c +++ b/fs/xfs/xfs_rmap_item.c @@ -573,6 +573,7 @@ xfs_rmap_recover_work( struct xfs_rui_log_item *ruip = RUI_ITEM(lip); struct xfs_trans *tp; struct xfs_mount *mp = lip->li_log->l_mp; + unsigned int dblocks; bool isrt = xfs_rui_item_isrt(lip); int i; int error = 0; @@ -596,8 +597,11 @@ xfs_rmap_recover_work( } resv = xlog_recover_resv(&M_RES(mp)->tr_itruncate); - error = xfs_trans_alloc(mp, &resv, mp->m_rmap_maxlevels, 0, - XFS_TRANS_RESERVE, &tp); + if (isrt) + dblocks = mp->m_rtrmap_maxlevels; + else + dblocks = mp->m_rmap_maxlevels; + error = xfs_trans_alloc(mp, &resv, dblocks, 0, XFS_TRANS_RESERVE, &tp); if (error) return error; From 46c1b6674a7b3a8385e7fa27f4efc6f45eddf967 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:26 -0700 Subject: [PATCH 1064/1317] xfs: fix integer overflows in xbitmap set functions LOLLM complains that the xbitmap set functions can suffer an integer underflow or overflow and thereby return the wrong left and right pointers. Fix that logic bomb, even though (AFAICT) we never actually try to set the *entire* bitmap. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bitmap.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/fs/xfs/scrub/bitmap.c b/fs/xfs/scrub/bitmap.c index c7fa908d92b2..08f216d26ec6 100644 --- a/fs/xfs/scrub/bitmap.c +++ b/fs/xfs/scrub/bitmap.c @@ -122,8 +122,8 @@ xbitmap64_set( uint64_t start, uint64_t len) { - struct xbitmap64_node *left; - struct xbitmap64_node *right; + struct xbitmap64_node *left = NULL; + struct xbitmap64_node *right = NULL; uint64_t last = start + len - 1; int error; @@ -131,6 +131,7 @@ xbitmap64_set( left = xbitmap64_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) return 0; + left = NULL; /* Clear out everything in the range we want to set. */ error = xbitmap64_clear(bitmap, start, len); @@ -138,11 +139,15 @@ xbitmap64_set( return error; /* Do we have a left-adjacent extent? */ - left = xbitmap64_tree_iter_first(&bitmap->xb_root, start - 1, start - 1); + if (start > 0) + left = xbitmap64_tree_iter_first(&bitmap->xb_root, start - 1, + start - 1); ASSERT(!left || left->bn_last + 1 == start); /* Do we have a right-adjacent extent? */ - right = xbitmap64_tree_iter_first(&bitmap->xb_root, last + 1, last + 1); + if (last < U64_MAX) + right = xbitmap64_tree_iter_first(&bitmap->xb_root, last + 1, + last + 1); ASSERT(!right || right->bn_start == last + 1); if (left && right) { @@ -397,8 +402,8 @@ xbitmap32_set( uint32_t start, uint32_t len) { - struct xbitmap32_node *left; - struct xbitmap32_node *right; + struct xbitmap32_node *left = NULL; + struct xbitmap32_node *right = NULL; uint32_t last = start + len - 1; int error; @@ -406,6 +411,7 @@ xbitmap32_set( left = xbitmap32_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) return 0; + left = NULL; /* Clear out everything in the range we want to set. */ error = xbitmap32_clear(bitmap, start, len); @@ -413,11 +419,15 @@ xbitmap32_set( return error; /* Do we have a left-adjacent extent? */ - left = xbitmap32_tree_iter_first(&bitmap->xb_root, start - 1, start - 1); + if (start > 0) + left = xbitmap32_tree_iter_first(&bitmap->xb_root, start - 1, + start - 1); ASSERT(!left || left->bn_last + 1 == start); /* Do we have a right-adjacent extent? */ - right = xbitmap32_tree_iter_first(&bitmap->xb_root, last + 1, last + 1); + if (last < U32_MAX) + right = xbitmap32_tree_iter_first(&bitmap->xb_root, last + 1, + last + 1); ASSERT(!right || right->bn_start == last + 1); if (left && right) { From c54110d814c3e8ed6bbbb5e02874994ed9f668ac Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:42 -0700 Subject: [PATCH 1065/1317] xfs: only flag zero padding for dir3 data blocks, not dir3 block blocks LOLLM complains that xchk_directory_data_bestfree can be passed a directory block that is either in "block" or "data" format, but the check here unconditionally treats the dir3_block and dir3_data blocks as if they have the same header format (they don't). Consequently, we can incorrectly set the preen state on dir3_block blocks, which of course we can't preen away because dir3_block blocks do not have a padding field. Fix this. Cc: stable@vger.kernel.org # v7.1-rc4 Fixes: 939919ccddfcc3 ("xfs: check directory data block header padding in scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c index 2a037aae904d..19d974c7e2b7 100644 --- a/fs/xfs/scrub/dir.c +++ b/fs/xfs/scrub/dir.c @@ -492,7 +492,7 @@ xchk_directory_data_bestfree( goto out; xchk_buffer_recheck(sc, bp); - if (xfs_has_crc(sc->mp)) { + if (!is_block && xfs_has_crc(sc->mp)) { struct xfs_dir3_data_hdr *hdr3 = bp->b_addr; if (hdr3->pad) From e9193f2f1ce32d02b9230094ffdbfab715ab6137 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:58 -0700 Subject: [PATCH 1066/1317] xfs: check di_forkoff correctly in scrub The di_forkoff check in xchk_dinode is incorrect, according to LOLLM. XFS_DFORK_BOFF returns a byte count relative to the start of the literal area, not the start of the inode. Therefore, this check won't flag di_forkoff values that are larger than the literal area but not the inode size itself. Fix this check; sadly the old APTR code was correct. Cc: stable@vger.kernel.org # v6.8 Fixes: 6b5d917780219d ("xfs: dont cast to char * for XFS_DFORK_*PTR macros") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index 65b13e311916..46e9bf4a4317 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -607,7 +607,7 @@ xchk_dinode( } /* di_forkoff */ - if (XFS_DFORK_BOFF(dip) >= mp->m_sb.sb_inodesize) + if (dip->di_forkoff >= (XFS_LITINO(mp) >> 3)) xchk_ino_set_corrupt(sc, ino); if (naextents != 0 && dip->di_forkoff == 0) xchk_ino_set_corrupt(sc, ino); From 14e379600d3e57ab0872b049c28cfe5ef519d439 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 14 Sep 2026 13:35:38 +0200 Subject: [PATCH 1067/1317] xfs: don't try to get a reference to a NULL oz in xfs_get_cached_zone oz can be NULL when we resample it after taking i_flags_lock, so account for that. Fixes: 2d829cc76777 ("xfs: fix racy open zone caching") Signed-off-by: Christoph Hellwig Reviewed-by: Hans Holmberg Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index 28c1e48909fa..d7ec055a9a06 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -820,7 +820,7 @@ xfs_get_cached_zone( spin_unlock(&ip->i_flags_lock); } - if (!atomic_inc_not_zero(&oz->oz_ref)) + if (oz && !atomic_inc_not_zero(&oz->oz_ref)) oz = NULL; out_unlock: rcu_read_unlock(); From ee415ce8cba154d07d02a6d2fbb27ff518264c3a Mon Sep 17 00:00:00 2001 From: Luca Coelho Date: Tue, 8 Sep 2026 13:06:51 +0300 Subject: [PATCH 1068/1317] drm/i915/display: check configuration index before shifting The calc_allowed_config_filter() function passes the return value of iter_pos_to_idx() directly to BIT(), but the helper can return -1 for an invalid iterator. The iterator already rejects negative indices before doing a configuration, so this should not matter in normal flows. In any case, for robustness, check the index explicitly and warn if it is negative, avoiding an undefined shift. Fixes: 39e30bdf2f92 ("drm/i915/dp_link_caps: Add link configuration iterator") Reviewed-by: Imre Deak Link: https://patch.msgid.link/20260908100659.113555-1-luciano.coelho@intel.com Signed-off-by: Luca Coelho (cherry picked from commit fe05cb9b9fb0ecc10409c4c6133257214b6cd8c8) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 7b6cc6055da8..98657aa4d3d5 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -426,12 +426,15 @@ calc_allowed_config_filter(struct intel_dp_link_caps *link_caps, const struct intel_dp_link_config *forced_params) { struct intel_dp_link_caps_filter allowed_configs = INTEL_DP_LINK_CAPS_FILTER_NONE; + struct intel_display *display = to_intel_display(link_caps->dp); struct intel_dp_link_caps_order order = bw_desc_config_order(); struct intel_dp_link_caps_iter iter; struct intel_dp_link_config config; iter_start(&iter, link_caps, order, enabled_configs); for_each_dp_link_config(&iter, &config) { + int config_idx; + if (forced_params->rate && forced_params->rate != config.rate) continue; @@ -446,7 +449,11 @@ calc_allowed_config_filter(struct intel_dp_link_caps *link_caps, if (config.lane_count > max_limits->lane_count) continue; - allowed_configs.config_mask |= BIT(iter_pos_to_idx(link_caps, order, iter.pos)); + config_idx = iter_pos_to_idx(link_caps, order, iter.pos); + if (drm_WARN_ON(display->drm, config_idx < 0)) + continue; + + allowed_configs.config_mask |= BIT(config_idx); } intel_dp_link_caps_iter_end(&iter); From ce2b91bebc7bc0495fe3ad5ee47e4977fbb77fc5 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 16:21:11 +0800 Subject: [PATCH 1069/1317] xfs: remove duplicate INO1_WRITTEN check Commit a23eca88448e ("xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN") duplicated commit b2d5a81dae38 ("xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN"), so xmi_can_exchange_reflink_flags() ended up with two identical XFS_EXCHMAPS_INO1_WRITTEN checks. The second one is dead code, since the first one already returned false. Remove it. Signed-off-by: Jiangshan Yi Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 49eda8d0994d..3efed37cb98a 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -959,16 +959,6 @@ xmi_can_exchange_reflink_flags( { struct xfs_mount *mp = req->ip1->i_mount; - /* - * The INO1_WRITTEN optimization can skip exchanging hole and - * unwritten mappings, which means we cannot guarantee that all - * shared extents actually moved to the other file. Clearing the - * reflink flag of an inode that still holds shared extents breaks - * the CoW write path, so refuse to exchange the flags in that case. - */ - if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) - return false; - /* * The INO1_WRITTEN optimization can skip exchanging hole and * unwritten mappings, which means we cannot guarantee that all From c16b885ad6b589b233534ee99ade82110d2bc381 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Fri, 4 Sep 2026 18:06:41 +0530 Subject: [PATCH 1070/1317] xfs: remove unused xfs_reflink_remap_range declaration The implementation was inlined into xfs_file_remap_range() by commit 3fc9f5e40931 ("xfs: remove xfs_reflink_remap_range"), leaving this declaration orphaned. Signed-off-by: Anuj Gupta Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_reflink.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/xfs/xfs_reflink.h b/fs/xfs/xfs_reflink.h index 9d1ed9bb0bee..683c1841e640 100644 --- a/fs/xfs/xfs_reflink.h +++ b/fs/xfs/xfs_reflink.h @@ -48,9 +48,6 @@ extern int xfs_reflink_end_cow(struct xfs_inode *ip, xfs_off_t offset, int xfs_reflink_end_atomic_cow(struct xfs_inode *ip, xfs_off_t offset, xfs_off_t count); extern int xfs_reflink_recover_cow(struct xfs_mount *mp); -extern loff_t xfs_reflink_remap_range(struct file *file_in, loff_t pos_in, - struct file *file_out, loff_t pos_out, loff_t len, - unsigned int remap_flags); extern int xfs_reflink_inode_has_shared_extents(struct xfs_trans *tp, struct xfs_inode *ip, bool *has_shared); extern int xfs_reflink_clear_inode_flag(struct xfs_inode *ip, From 2998147b59c9df0a51477c7a6b3d1f0ba3127dd4 Mon Sep 17 00:00:00 2001 From: Dong Chenchen Date: Thu, 10 Sep 2026 22:00:42 +0800 Subject: [PATCH 1071/1317] ipv4: icmp: reject RTN_UNREACHABLE input routes in icmp_route_lookup When the forward output route cannot be used in icmp_route_lookup(), it enters the "reverse path" and calls ip_route_input() on fl4_dec.daddr, the original packet's source address. ip_route_input() only returns an error for truly invalid packets. For unreachable addresses it will succeed and return an input route whose dst.output is set to ip_rt_bug(). The existing check only rejects RTN_LOCAL routes, so the RTN_UNREACHABLE route types can still be returned and later used for output, syzkaller triggering a WARN_ON_ONCE() in ip_rt_bug() as bellow: ------------[ cut here ]------------ WARNING: net/ipv4/route.c:1273 at ip_rt_bug+0x14/0x20 RIP: 0010:ip_rt_bug+0x14/0x20 Call Trace: ip_push_pending_frames+0xfa/0x100 __icmp_send+0x905/0xf10 ip_options_compile+0xc0/0xd0 ip_rcv_finish_core+0x321/0xae0 ip_rcv+0x1de/0x260 __netif_receive_skb_one_core+0x11a/0x130 netif_receive_skb+0x7b/0x260 tun_get_user+0x11bf/0x1c10 ------------[ cut here ]------------ Reject input route that is RTN_UNREACHABLE to fix it. The net warning is only printed for RTN_LOCAL, as RTN_UNREACHABLE is not the result of a race condition. Fixes: 8b7817f3a959 ("[IPSEC]: Add ICMP host relookup support") Suggested-by: Ido Schimmel Reviewed-by: Jiayuan Chen Reviewed-by: Ido Schimmel Signed-off-by: Dong Chenchen Link: https://patch.msgid.link/20260910140042.1880242-1-dongchenchen2@huawei.com Signed-off-by: Paolo Abeni --- net/ipv4/icmp.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c index 0caedfc7ca92..90c0e22c29be 100644 --- a/net/ipv4/icmp.c +++ b/net/ipv4/icmp.c @@ -581,16 +581,19 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4, skb_dstref_restore(skb_in, orefdst); /* - * At this point, fl4_dec.daddr should NOT be local (we - * checked fl4_dec.saddr above). However, a race condition - * may occur if the address is added to the interface - * concurrently. In that case, ip_route_input() returns a - * LOCAL route with dst.output=ip_rt_bug, which must not - * be used for output. + * fl4_dec.daddr is not expected to be local here, but it can be + * added to an interface concurrently, in which case + * ip_route_input() returns a LOCAL route. It can also fail to + * build a forwarding route towards fl4_dec.daddr, for example, + * when forwarding is disabled, and return an UNREACHABLE route. + * Both cases will result in a route with dst.output=ip_rt_bug, + * which must not be used for output. */ - if (!err && rt2 && rt2->rt_type == RTN_LOCAL) { + if (!err && rt2 && rt2->rt_type == RTN_LOCAL) net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n", &fl4_dec.daddr, &fl4_dec.saddr); + if (!err && rt2 && + (rt2->rt_type == RTN_LOCAL || rt2->rt_type == RTN_UNREACHABLE)) { dst_release(&rt2->dst); err = -EINVAL; } From 7c8810c2e69c3d9ca6df870b40ae9218e50b4fb1 Mon Sep 17 00:00:00 2001 From: Hohyun Sim Date: Thu, 10 Sep 2026 15:37:43 +0900 Subject: [PATCH 1072/1317] net: fddi: skfp: fix NULL deref when setting the MAC address while down skfp_ctl_set_mac_address() calls ResetAdapter() unconditionally, without checking netif_running(). ResetAdapter() first calls card_stop(), which sets smc->hw.hw_state to STOPPED, and then mac_drv_clear_tx_queue(), which walks the two transmit queues: for (i = QUEUE_S; i <= QUEUE_A0; i++) { queue = smc->hw.fp.tx[i] ; ... t = queue->tx_curr_get ; smc->hw.fp.tx[] is only populated by init_tx(), which is reached from skfp_open() through init_smt() -> init_fddi_driver() -> init_fplus() -> init_mac() -> init_tx(). The private area is allocated and zeroed by alloc_fddidev(), so on an interface that has never been brought up both queue pointers are still NULL. The hw_state test at the top of mac_drv_clear_tx_queue() does not catch this, because card_stop() has just set STOPPED; the function proceeds into the loop and dereferences NULL. ResetAdapter() does call init_smt() itself, but only after the queues have been cleared. Setting the MAC address on a down interface therefore oopses: ip link set dev fddi0 address 02:00:00:00:00:01 BUG: KASAN: null-ptr-deref in mac_drv_clear_tx_queue+0x68/0x2c0 [skfp] Read of size 8 at addr 0000000000000010 by task ip/302 Call Trace: mac_drv_clear_tx_queue+0x68/0x2c0 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] ResetAdapter+0x29/0x100 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] skfp_ctl_set_mac_address+0x57/0x80 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] netif_set_mac_address+0x1e4/0x2c0 do_setlink+0x684/0x2680 Address 0x10 is the offset of tx_curr_get, the third pointer in struct s_smt_tx_queue, on 64-bit. mac_drv_clear_rx_queue(), which ResetAdapter() calls immediately afterwards, dereferences smc->hw.fp.rx[QUEUE_R1] in the same way behind the same ineffective hw_state test; the transmit queue merely crashes first. Both are covered by the guard below. Skip the adapter reset when the interface is down. dev_addr_set() is left unconditional, so the new address is still recorded in dev->dev_addr. Nothing is lost by not resetting the adapter here: skfp_open() deliberately re-reads the factory address on every open, read_address(smc, NULL); eth_hw_addr_set(dev, smc->hw.fddi_canon_addr.a); and the comment above it states this is done to discard exactly such an address override across a close/open cycle. An address set while the interface is down could not have survived the following open even before this change, so the guard removes no working behaviour. Guarding the hardware side of ndo_set_mac_address() with netif_running() is established practice; skge_set_mac_address() has done so since commit 2eb3e621c4e0 ("skge: set mac address bonding fix"). Guarding the reset as a whole, rather than NULL-checking the queues, is also what the rest of the driver expects. After a previous open/close the queue pointers are stale but non-NULL, so there is no crash, yet ResetAdapter() goes on to call smt_online() and STI_FBI() ("Enable Board Interrupts") while skfp_close() has already called free_irq() - the adapter would be brought back online with no handler installed. The only other ResetAdapter() caller is skfp_interrupt(), which by construction runs only while the device is open. Found by automated driver testing against an emulated SysKonnect FDDI adapter under a KASAN-enabled 7.0.0 kernel. Triggering it requires CAP_NET_ADMIN. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: LLM KASAN Signed-off-by: Hohyun Sim Link: https://patch.msgid.link/20260910063743.110747-1-tlaghgus0425@korea.ac.kr Signed-off-by: Paolo Abeni --- drivers/net/fddi/skfp/skfddi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/fddi/skfp/skfddi.c b/drivers/net/fddi/skfp/skfddi.c index a273362c9e70..feea7baa4816 100644 --- a/drivers/net/fddi/skfp/skfddi.c +++ b/drivers/net/fddi/skfp/skfddi.c @@ -928,7 +928,8 @@ static int skfp_ctl_set_mac_address(struct net_device *dev, void *addr) dev_addr_set(dev, p_sockaddr->sa_data); spin_lock_irqsave(&bp->DriverLock, Flags); - ResetAdapter(smc); + if (netif_running(dev)) + ResetAdapter(smc); spin_unlock_irqrestore(&bp->DriverLock, Flags); return 0; /* always return zero */ From 2f3c2a6f963e57cf4f0f34cbf24ab11abb64d118 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 11 Sep 2026 13:00:07 +0530 Subject: [PATCH 1073/1317] xfs: fix typos and repeated words in comments Correct ten misspellings and eight accidentally doubled words in comments. No code changes. The doubled word in xfs_zone_alloc.c was not a duplicate: "so that is is reused" is "it is" misspelt, so that one reads "so that it is reused" rather than dropping a word. Signed-off-by: Hemanth Selam Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_ag.h | 2 +- fs/xfs/libxfs/xfs_alloc.c | 4 ++-- fs/xfs/libxfs/xfs_attr_leaf.c | 2 +- fs/xfs/libxfs/xfs_errortag.h | 2 +- fs/xfs/libxfs/xfs_exchmaps.c | 2 +- fs/xfs/libxfs/xfs_format.h | 2 +- fs/xfs/libxfs/xfs_inode_buf.c | 2 +- fs/xfs/scrub/agheader_repair.c | 2 +- fs/xfs/scrub/alloc_repair.c | 2 +- fs/xfs/scrub/dirtree.c | 2 +- fs/xfs/scrub/reap.c | 2 +- fs/xfs/xfs_bmap_item.c | 2 +- fs/xfs/xfs_inode.c | 2 +- fs/xfs/xfs_log_cil.c | 2 +- fs/xfs/xfs_platform.h | 2 +- fs/xfs/xfs_zone_alloc.c | 2 +- fs/xfs/xfs_zone_gc.c | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ag.h b/fs/xfs/libxfs/xfs_ag.h index fd22fe598931..ee636b66a72f 100644 --- a/fs/xfs/libxfs/xfs_ag.h +++ b/fs/xfs/libxfs/xfs_ag.h @@ -207,7 +207,7 @@ xfs_perag_next( } /* - * Per-ag geometry infomation and validation + * Per-ag geometry information and validation */ xfs_agblock_t xfs_ag_block_count(struct xfs_mount *mp, xfs_agnumber_t agno); void xfs_agino_range(struct xfs_mount *mp, xfs_agnumber_t agno, diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index d99602bcc16f..f762dcce8d13 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -3487,7 +3487,7 @@ xfs_alloc_read_agf( } /* - * Pre-proces allocation arguments to set initial state that we don't require + * Pre-process allocation arguments to set initial state that we don't require * callers to set up correctly, as well as bounds check the allocation args * that are set up. */ @@ -3608,7 +3608,7 @@ xfs_alloc_vextent_finish( * ABBA AGF deadlocks because a future allocation attempt in this * transaction may attempt to lock a lower number AGF. * - * We can't release the AGF until the transaction is commited, so at + * We can't release the AGF until the transaction is committed, so at * this point we must update the "first allocation" tracker to point at * this AG if the tracker is empty or points to a lower AG. This allows * the next allocation attempt to be modified appropriately to avoid diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index b6288395f853..2c80f4fd0b78 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -1715,7 +1715,7 @@ xfs_attr3_leaf_add_work( /* * This freemap entry starts at the old end of the * leaf entry array, so we need to adjust its base - * upward to accomodate the larger array. + * upward to accommodate the larger array. */ diff = sizeof(struct xfs_attr_leaf_entry); } else if (ichdr->freemap[i].size > 0 && diff --git a/fs/xfs/libxfs/xfs_errortag.h b/fs/xfs/libxfs/xfs_errortag.h index 6de207fed2d8..f0c83f1f0b3b 100644 --- a/fs/xfs/libxfs/xfs_errortag.h +++ b/fs/xfs/libxfs/xfs_errortag.h @@ -83,7 +83,7 @@ #define XFS_RANDOM_DEFAULT 100 /* - * Table of errror injection knobs. The parameters to the XFS_ERRTAG macro are: + * Table of error injection knobs. The parameters to the XFS_ERRTAG macro are: * 1. The XFS_ERRTAG_ flag but without the prefix; * 2. The name of the sysfs knob; and * 3. The default value for the knob. diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 3efed37cb98a..6a66b6075e0a 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -395,7 +395,7 @@ xfs_exchmaps_one_step( /* * Re-add both mappings. We exchange the file offsets between the two * maps and add the opposite map, which has the effect of filling the - * logical offsets we just unmapped, but with with the physical mapping + * logical offsets we just unmapped, but with the physical mapping * information exchanged. */ swap(irec1->br_startoff, irec2->br_startoff); diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index dd0ed046fbe9..1a7a7e60a170 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -1051,7 +1051,7 @@ enum xfs_dinode_fmt { * block is 1KB in size. * * With XFS_MAX_EXTCNT_DATA_FORK_SMALL representing maximum extent count and - * with 1KB sized blocks, a file can reach upto, + * with 1KB sized blocks, a file can reach up to, * 1KB * (2^31) = 2TB * * This is much larger than the theoretical maximum size of a directory diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index e4c3f7b24e95..0340e2189921 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -626,7 +626,7 @@ xfs_dinode_verify( * have di_nlink track the link count, even if the actual filesystem * only supported V1 inodes (i.e. di_onlink). When writing out the * ondisk inode, it would set both the ondisk di_nlink and di_onlink to - * the the incore di_nlink value, which is why we cannot check for + * the incore di_nlink value, which is why we cannot check for * di_nlink==0 on a V1 inode. V2/3 inodes would get written out with * di_onlink==0, so we can check that. */ diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index a66b611588c4..ff1b4b361cf2 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1369,7 +1369,7 @@ xrep_iunlink_mark_ondisk( /* * Walk an iunlink bucket's inode list. For each inode that should be on this - * chain, clear its entry in in iunlink_bmp because it's ok and we don't need + * chain, clear its entry in iunlink_bmp because it's ok and we don't need * to touch it further. */ STATIC int diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 95e318e4f3a6..2398e3819597 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -338,7 +338,7 @@ xrep_cntbt_extent_cmp( } /* - * Sort the free extents by length so so that we can put the records into the + * Sort the free extents by length so that we can put the records into the * cntbt in the correct order. Don't let userspace kill us if we're resorting * after allocating btree blocks. */ diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index 9b0ab2316612..887383d2e941 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -1021,7 +1021,7 @@ xchk_dirtree( return error; } -/* Does the directory targetted by this scrub have no parents? */ +/* Does the directory targeted by this scrub have no parents? */ bool xchk_dirtree_parentless(const struct xchk_dirtree *dl) { diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index f698b9be3dd1..0dfe61bafc6a 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -172,7 +172,7 @@ static inline bool xreap_is_dirty(const struct xreap_state *rs) } /* - * Decide if we need to roll the transaction to clear out the the log + * Decide if we need to roll the transaction to clear out the log * reservation that we allocated to buffer invalidations. */ static inline bool xreap_want_binval_roll(const struct xreap_state *rs) diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c index 89f6e79a955f..aa5b41629747 100644 --- a/fs/xfs/xfs_bmap_item.c +++ b/fs/xfs/xfs_bmap_item.c @@ -339,7 +339,7 @@ xfs_bmap_update_get_group( /* * Bump the intent count on behalf of the deferred rmap and refcount - * intent items that that we can queue when we finish this bmap work. + * intent items that we can queue when we finish this bmap work. * This new intent item will bump the intent count before the bmap * intent drops the intent count, ensuring that the intent count * remains nonzero across the transaction roll. diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 030a7c8f2c12..621513d7215e 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -2669,7 +2669,7 @@ xfs_irele( } /* - * Ensure all commited transactions touching the inode are written to the log. + * Ensure all committed transactions touching the inode are written to the log. */ int xfs_log_force_inode( diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index f9e07a32f60f..9446ac44ba88 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1370,7 +1370,7 @@ xlog_cil_cleanup_whiteouts( * allocation context. However, we do not want to block on memory reclaim * recursing back into the filesystem because this push may have been triggered * by memory reclaim itself. Hence we really need to run under full GFP_NOFS - * contraints here. + * constraints here. */ static void xlog_cil_push_work( diff --git a/fs/xfs/xfs_platform.h b/fs/xfs/xfs_platform.h index 5d542e95fe44..745d715b4c64 100644 --- a/fs/xfs/xfs_platform.h +++ b/fs/xfs/xfs_platform.h @@ -153,7 +153,7 @@ static inline void delay(long ticks) /* * XFS wrapper structure for sysfs support. It depends on external data * structures and is embedded in various internal data structures to implement - * the XFS sysfs object heirarchy. Define it here for broad access throughout + * the XFS sysfs object hierarchy. Define it here for broad access throughout * the codebase. */ struct xfs_kobj { diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index d7ec055a9a06..b75cf3bfe33c 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -828,7 +828,7 @@ xfs_get_cached_zone( } /* - * Stash our zone in the inode so that is is reused for future allocations. + * Stash our zone in the inode so that it is reused for future allocations. * * The open_zone structure will be pinned until either the inode is freed or * until the cached open zone is replaced with a different one because the diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index 5fdcf98a2133..54b70ed2922f 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -46,7 +46,7 @@ * before remapping. * * Once a zone does not contain any valid data, be that through GC or user - * block removal, it is queued for for a zone reset. The reset operation + * block removal, it is queued for a zone reset. The reset operation * carefully ensures that the RT device cache is flushed and all transactions * referencing the rmap have been committed to disk. */ From 82e47533221d4746947b74d2e79a478c36c6433a Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 4 Sep 2026 15:43:11 +0200 Subject: [PATCH 1074/1317] ata: libahci: clear PxCLBU and PxFBU for AHCI_HFLAG_32BIT_ONLY A user reported that commit 105c42566a55 ("ata: ahci: force 32-bit DMA for JMicron JMB582/JMB585") made the JMicron JMB585 unusable on his board. The failure is seen as soon as the ahci driver is probed, and booting with iommu=off does not solve the problem. Looking at the AHCI specification, PxCLBU and PxFBU are both read only '0' for HBAs that do not support 64-bit addressing. For HBAs that do support 64-bit addressing, the registers are read write, with a reset value that is Implementation Specific. When using the AHCI_HFLAG_32BIT_ONLY flag, the HBA does support 64-bit addressing, and a 32-bit DMA mask is set by simply clearing HOST_CAP_64. Thus, in this case, we need to explicitly clear the registers to 0. Fixes: 105c42566a55 ("ata: ahci: force 32-bit DMA for JMicron JMB582/JMB585") Fixes: c7a42156d99b ("ahci: disable 64bit dma on sb600") Cc: stable@vger.kernel.org Reported-by: Roland Waltersson Closes: https://lore.kernel.org/linux-ide/IA0PR17MB668730A4ECCD65F7A1DC3EDC9EB62@IA0PR17MB6687.namprd17.prod.outlook.com/ Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20260904134310.1465051-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libahci.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index 6d72eb017b49..08ab56e95566 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -744,15 +744,28 @@ void ahci_start_fis_rx(struct ata_port *ap) struct ahci_port_priv *pp = ap->private_data; u32 tmp; - /* set FIS registers */ + /* + * On HBAs that only support 32-bit addressing PxCLBU is read only '0'. + * When applying the AHCI_HFLAG_32BIT_ONLY quirk, PxCLBU is RW, and the + * reset value is Implementation Specific, so we need to clear it to 0. + */ if (hpriv->cap & HOST_CAP_64) writel((pp->cmd_slot_dma >> 16) >> 16, port_mmio + PORT_LST_ADDR_HI); + else if (hpriv->flags & AHCI_HFLAG_32BIT_ONLY) + writel(0, port_mmio + PORT_LST_ADDR_HI); writel(pp->cmd_slot_dma & 0xffffffff, port_mmio + PORT_LST_ADDR); + /* + * On HBAs that only support 32-bit addressing PxFBU is read only '0'. + * When applying the AHCI_HFLAG_32BIT_ONLY quirk, PxFBU is RW, and the + * reset value is Implementation Specific, so we need to clear it to 0. + */ if (hpriv->cap & HOST_CAP_64) writel((pp->rx_fis_dma >> 16) >> 16, port_mmio + PORT_FIS_ADDR_HI); + else if (hpriv->flags & AHCI_HFLAG_32BIT_ONLY) + writel(0, port_mmio + PORT_FIS_ADDR_HI); writel(pp->rx_fis_dma & 0xffffffff, port_mmio + PORT_FIS_ADDR); /* enable FIS reception */ From 621d90169cef6c8da5b6134db5c0c4e23cdd09ce Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 11 Aug 2026 10:27:02 +0200 Subject: [PATCH 1075/1317] wifi: brcmfmac: fix lost 802.1x TX completion wakeup brcmf_txfinalize() decrements pend_8021x_cnt before a lockless waitqueue_active() check. atomic_dec() does not order the decrement against the check. The waiter can therefore observe a nonzero count while the waker observes an empty queue, losing the final wakeup and delaying key installation until the 950 ms timeout. Add smp_mb__after_atomic() to order the decrement before the queue check. wait_event_timeout() provides the matching barrier. LKMM confirms that this forbids the lost-wakeup outcome. Fixes: 21fff75d2fb6 ("brcmfmac: use wait_event_timeout for 8021x pending count") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260811082702.44521-1-kmehltretter@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index dad6f4563d14..d2ae67985606 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -555,6 +555,8 @@ void brcmf_txfinalize(struct brcmf_if *ifp, struct sk_buff *txp, bool success) if (type == ETH_P_PAE) { atomic_dec(&ifp->pend_8021x_cnt); + /* Order the decrement before waitqueue_active() */ + smp_mb__after_atomic(); if (waitqueue_active(&ifp->pend_8021x_wait)) wake_up(&ifp->pend_8021x_wait); } From 1eeca1d5e0920fbdad6449768fd2d4364e714180 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Sat, 15 Aug 2026 20:10:43 +0800 Subject: [PATCH 1076/1317] wifi: brcmsmac: fix UAF in brcms_free_timer() brcms_free_timer() calls brcms_del_timer() which uses the non-synchronous cancel_delayed_work() to cancel the timer's underlying delayed work. If the work callback (_brcms_timer) is already running, cancel_delayed_work() returns false without waiting, and brcms_free_timer() proceeds to kfree(t) while the callback still accesses t through container_of(). Add an explicit cancel_delayed_work_sync() after brcms_del_timer() to guarantee that any in-flight callback has completed before the timer structure is freed. Fixes: 5b435de0d786 ("net: wireless: add brcm80211 drivers") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260815121043.938414-1-yijiangshan@kylinos.cn Signed-off-by: Johannes Berg --- .../net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c index 6255d673d2d3..c1a2318d7ea6 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c @@ -1571,6 +1571,10 @@ void brcms_free_timer(struct brcms_timer *t) /* delete the timer in case it is active */ brcms_del_timer(t); + /* Ensure the callback has finished before freeing the timer + * structure, since brcms_del_timer() uses non-synchronous cancel. + */ + cancel_delayed_work_sync(&t->dly_wrk); if (wl->timers == t) { wl->timers = wl->timers->next; From 18a6fe05fb6e18de29fa90d388bb34044114b3d8 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Fri, 11 Sep 2026 13:50:21 +0300 Subject: [PATCH 1077/1317] net: bridge: mst: move switchdev call outside rcu This is a follow-up of one of sashiko's pre-existing bug reports. br_mst_set_state() calls switchdev_port_attr_set() for nonzero MSTIs while holding rcu_read_lock() which invokes the blocking switchdev notifier chain and may sleep. Nonzero MSTI changes come from netlink with rtnl held. Move the switchdev call before entering the rcu section and assert that rtnl is held. The call cannot be deferred because netlink needs its error and extack. Also DSA reads the old bridge MST state during the callback and checks it. A deferred callback will be late and will see the updated state. Fixes: 3a7c1661ae13 ("net: bridge: mst: fix vlan use-after-free") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260911105021.1385934-1-razor@blackwall.org Signed-off-by: Paolo Abeni --- net/bridge/br_mst.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/net/bridge/br_mst.c b/net/bridge/br_mst.c index 43a300ae6bfa..1654efd3045b 100644 --- a/net/bridge/br_mst.c +++ b/net/bridge/br_mst.c @@ -107,21 +107,24 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state, struct net_bridge_vlan *v; int err = 0; - rcu_read_lock(); - vg = nbp_vlan_group_rcu(p); - if (!vg) - goto out; - /* MSTI 0 (CST) state changes are notified via the regular - * SWITCHDEV_ATTR_ID_PORT_STP_STATE. + * SWITCHDEV_ATTR_ID_PORT_STP_STATE. All other MSTIs are handled via + * netlink with RTNL held */ if (msti) { + ASSERT_RTNL(); + err = switchdev_port_attr_set(p->dev, &attr, extack); if (err && err != -EOPNOTSUPP) goto out; + err = 0; } - err = 0; + rcu_read_lock(); + vg = nbp_vlan_group_rcu(p); + if (!vg) + goto out_rcu_unlock; + list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { if (v->brvlan->msti != msti) continue; @@ -129,8 +132,9 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state, br_mst_vlan_set_state(vg, v, state); } -out: +out_rcu_unlock: rcu_read_unlock(); +out: return err; } From 2cef2588c995722a901368def30befeef9ae55c6 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:09:19 -0400 Subject: [PATCH 1078/1317] net/sched: hhf: cap hh_flows_limit at change time hhf_change() stores TCA_HHF_HH_FLOWS_LIMIT with no upper bound. A huge hh_flows_limit lets each new heavy-hitter flow pass the hh_flows_current_cnt check in alloc_new_hh() and forces a fixed-size kzalloc(GFP_ATOMIC) per flow under spoofed traffic, for unbounded memory growth. Bound the attribute with NLA_POLICY_MAX() at 2*HH_FLOWS_CNT (the hhf_init() default) and report the rejected value via extack. The deprecated nested parse is kept: legacy tc does not set NLA_F_NESTED on TCA_OPTIONS. Configs relying on hh_limit above the default were relying on unbounded, unsafe behaviour and are not supported going forward. hhf_init() also ran hhf_change() before setting the default hh_flows_limit, so a user-supplied hh_limit at add time was clobbered back to 2048. Set the default before hhf_change() so the configured value sticks. This is a follow-up to commit eb56a495f59b ("net/sched: hhf: clamp quantum in change and init paths"), which bounded the quantum of the same qdisc; the hh_flows_limit bound is the remaining unbounded knob of that series' scope. Conditions to recreate the bug: CAP_NET_ADMIN in a user namespace; tc qdisc change dev X root hhf hh_limit 4294967295 succeeds and the value is echoed by tc qdisc show, unbounding heavy-hitter flow allocations; also tc qdisc add dev X root hhf hh_limit 500 stores 2048 instead of 500. Fixes: 10239edf86f1 ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc") Cc: stable@vger.kernel.org Reported-by: Sashiko (gemini) Closes: https://sashiko.dev/#/patchset/20260822195509.112717-1-jhs@mojatatu.com Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-B855.v1.20260911153152@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_hhf.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index fc72f825fbd9..5dec1ed969ad 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -527,7 +527,7 @@ static void hhf_destroy(struct Qdisc *sch) static const struct nla_policy hhf_policy[TCA_HHF_MAX + 1] = { [TCA_HHF_BACKLOG_LIMIT] = { .type = NLA_U32 }, [TCA_HHF_QUANTUM] = { .type = NLA_U32 }, - [TCA_HHF_HH_FLOWS_LIMIT] = { .type = NLA_U32 }, + [TCA_HHF_HH_FLOWS_LIMIT] = NLA_POLICY_MAX(NLA_U32, 2 * HH_FLOWS_CNT), [TCA_HHF_RESET_TIMEOUT] = { .type = NLA_U32 }, [TCA_HHF_ADMIT_BYTES] = { .type = NLA_U32 }, [TCA_HHF_EVICT_TIMEOUT] = { .type = NLA_U32 }, @@ -546,7 +546,7 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt, u32 new_hhf_non_hh_weight = q->hhf_non_hh_weight; err = nla_parse_nested_deprecated(tb, TCA_HHF_MAX, opt, hhf_policy, - NULL); + extack); if (err < 0) return err; @@ -624,6 +624,9 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, q->hhf_evict_timeout = HZ; /* 1 sec */ q->hhf_non_hh_weight = 2; + /* Cap max active HHs at twice len of hh_flows table. */ + q->hh_flows_limit = 2 * HH_FLOWS_CNT; + if (opt) { int err = hhf_change(sch, opt, extack); @@ -639,8 +642,6 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, for (i = 0; i < HH_FLOWS_CNT; i++) INIT_LIST_HEAD(&q->hh_flows[i]); - /* Cap max active HHs at twice len of hh_flows table. */ - q->hh_flows_limit = 2 * HH_FLOWS_CNT; q->hh_flows_overlimit = 0; q->hh_flows_total_cnt = 0; q->hh_flows_current_cnt = 0; From 0654f4dba1fbc697f2653aba30cd68587fcbf10e Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:09:20 -0400 Subject: [PATCH 1079/1317] selftests/tc-testing: add hhf hh_limit cap tests Cover the new TCA_HHF_HH_FLOWS_LIMIT bound: values above 2*HH_FLOWS_CNT (4294967295, 65536, 2049) are rejected with the configured limit left untouched on both the change and the add path, the boundary value 2048 is accepted (installed at 100 first so the boundary change is load-bearing), and an add-time hh_limit 500 is preserved instead of being clobbered by the default. Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-B855.v1.20260911153152@mojatatu.com.2 Signed-off-by: Paolo Abeni --- .../tc-tests/qdiscs/hhf_flows_limit.json | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json new file mode 100644 index 000000000000..44538b9266b6 --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json @@ -0,0 +1,128 @@ +[ + { + "id": "e3cc", + "name": "HHF hh_limit rejects value above 2*HH_FLOWS_CNT cap (4294967295)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 4294967295", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "f681", + "name": "HHF hh_limit rejects 65536 (above 2*HH_FLOWS_CNT cap)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 65536", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "223d", + "name": "HHF hh_limit accepts boundary value 2048 (2*HH_FLOWS_CNT)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 100" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 2048", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "147f", + "name": "HHF hh_limit rejects first value above cap (2049)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 2049", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "4d4f", + "name": "HHF add-time hh_limit 500 is preserved (init does not clobber user value)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 500", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 500", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "ca99", + "name": "HHF add-time hh_limit 4294967295 is rejected (no qdisc installed)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 4294967295", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root", + "matchCount": "0", + "teardown": [] + } +] From ba13f1f32d96d7ce9069ae4f32404ecde8da89c4 Mon Sep 17 00:00:00 2001 From: Meijing Zhao Date: Tue, 15 Sep 2026 20:46:41 +0800 Subject: [PATCH 1080/1317] mm: memblock: add missing HugeTLB flag name Commit 7d163a75f821 ("memblock: make HugeTLB bootmem allocation work with KHO") added MEMBLOCK_RSRV_HUGETLB but did not add the corresponding entry to flagname[]. As a result, memblock debugfs cannot report the flag by name. Add the missing RSV_HUGETLB entry. Fixes: 7d163a75f821 ("memblock: make HugeTLB bootmem allocation work with KHO") Signed-off-by: Meijing Zhao Signed-off-by: Wandun Chen Link: https://lore.kernel.org/lkml/20260821020910.3428585-2-zhaomeijing100@gmail.com/ Link: https://patch.msgid.link/20260915124641.2498280-1-chenwandun1@gmail.com Signed-off-by: Mike Rapoport (Microsoft) --- mm/memblock.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/memblock.c b/mm/memblock.c index 021db49eb7fc..e6fdf95a0534 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -2886,6 +2886,7 @@ static const char * const flagname[] = { [ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT", [ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN", [ilog2(MEMBLOCK_KHO_SCRATCH)] = "KHO_SCRATCH", + [ilog2(MEMBLOCK_RSRV_HUGETLB)] = "RSV_HUGETLB", }; static int memblock_debug_show(struct seq_file *m, void *private) From 8e759cd1f6444a946bd1fd2b2b29eea582eea1d5 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 14 Sep 2026 01:14:01 +0000 Subject: [PATCH 1081/1317] tcp: Don't call skb_clone_and_charge_r() for close()d listener in tcp_v6_do_rcv(). tcp_v6_do_rcv() no longer calls skb_clone_and_charge_r() for TCP_LISTEN since commit 073d89808c06 ("net: fix data-races around sk->sk_forward_alloc"). However, there is still a small race window between tcp_v6_rcv() and tcp_v6_do_rcv(), where concurrent close() changes TCP_LISTEN to TCP_CLOSE, causing skb_clone_and_charge_r() to be called locklessly and resulting in the splat below. [0] Let's avoid calling skb_clone_and_charge_r() for TCP_CLOSE as well. This is fine for non-listeners because tcp_rcv_state_process() drops skb for TCP_CLOSE and opt_skb was freed immediately anyway. [0]: sk->sk_forward_alloc WARNING: net/ipv4/af_inet.c:162 at inet_sock_destruct+0x64d/0x810 net/ipv4/af_inet.c:162, CPU#1: ksoftirqd/1/28 Modules linked in: CPU: 1 UID: 0 PID: 28 Comm: ksoftirqd/1 Not tainted 7.2.0 #17 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 RIP: 0010:inet_sock_destruct+0x64d/0x810 net/ipv4/af_inet.c:162 Code: 3d 49 ff e9 06 fd ff ff e8 d0 5b 83 f8 90 0f 0b 90 e9 35 fe ff ff e8 c2 5b 83 f8 90 0f 0b 90 e9 c5 fe ff ff e8 b4 5b 83 f8 90 <0f> 0b 90 e9 04 ff ff ff e8 a6 5b 83 f8 90 0f 0b 90 e9 65 fe ff ff RSP: 0018:ffffc90000677bb8 EFLAGS: 00010246 RAX: 0000000000000000 RBX: ffff8880117bde80 RCX: ffffffff8957eb41 RDX: ffff88801dad5d00 RSI: ffffffff8957ec3c RDI: 0000000000000005 RBP: 00000000fffff000 R08: ffffffff8957eb41 R09: 00000000fffff000 R10: 0000000000000005 R11: 0000000000000000 R12: dffffc0000000000 R13: ffff8880117bdf10 R14: ffffffff81c08eb7 R15: 0000000000000003 FS: 0000000000000000(0000) GS:ffff8880d7ae5000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f93a1021138 CR3: 00000000207a9000 CR4: 0000000000350ef0 Call Trace: __sk_destruct+0x82/0xae0 net/core/sock.c:2356 rcu_do_batch kernel/rcu/tree.c:2645 [inline] rcu_core+0x59c/0x1100 kernel/rcu/tree.c:2897 handle_softirqs+0x1e4/0x9b0 kernel/softirq.c:622 run_ksoftirqd kernel/softirq.c:1076 [inline] run_ksoftirqd+0x38/0x60 kernel/softirq.c:1068 smpboot_thread_fn+0x458/0xc80 kernel/smpboot.c:160 kthread+0x396/0x4a0 kernel/kthread.c:436 ret_from_fork+0x8e0/0xe40 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fixes: e994b2f0fb92 ("tcp: do not lock listener to process SYN packets") Reported-by: Taras Madan Signed-off-by: Kuniyuki Iwashima Reviewed-by: Xuanqiang Luo Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914011420.115556-1-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/ipv6/tcp_ipv6.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index df9c29eb5c1f..7fa4ed2fd4f1 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1604,7 +1604,8 @@ int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) by tcp. Feel free to propose better solution. --ANK (980728) */ - if (np->rxopt.all && sk->sk_state != TCP_LISTEN) + if (np->rxopt.all && + !((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE))) opt_skb = skb_clone_and_charge_r(skb, sk); if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */ From 83a945a529d6e002dd7339c532288a931f463dba Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 12 Sep 2026 14:48:48 +0000 Subject: [PATCH 1082/1317] tcp: do not let tcp_rmem be set below 4096 We can hit a division by zero crash in tcp_rcvbuf_grow() and tcp_rcv_space_adjust(): divide error: 0000 [#1] PREEMPT SMP RIP: 0010:tcp_rcvbuf_grow+0x187/0x450 net/ipv4/tcp_input.c:939 ... grow = div_u64(((u64)rcvwin << 1) * (newval - oldval), oldval); The division uses oldval = tp->rcvq_space.space as divisor. When tp->rcvq_space.space is zero, this leads to a divide-by-zero exception. tp->rcvq_space.space is initialized in tcp_init_buffer_space(): tp->rcvq_space.space = min3(tp->rcv_ssthresh, tp->rcv_wnd, (u32)TCP_INIT_CWND * tp->advmss); If tcp_rmem[1] is configured to very small values (such as 1), sk->sk_rcvbuf is initialized to 1. Then tcp_full_space(sk), which computes (sk->sk_rcvbuf * scaling_ratio) >> 8, truncates to 0. This sets tp->window_clamp = 0, tp->rcv_ssthresh = 0, and tp->rcvq_space.space = 0. Later, when data arrives and DRS is invoked, tcp_rcvbuf_grow() divides by oldval == 0. Back in 2015, commit b1cb59cf2efe ("net: sysctl_net_core: check SNDBUF and RCVBUF for min length") ensured that net.core.rmem_default and net.core.rmem_max cannot be set below SOCK_MIN_RCVBUF. Similarly, SO_RCVBUF setsockopt enforces max_t(int, val * 2, SOCK_MIN_RCVBUF). However, net.ipv4.tcp_rmem still had .extra1 = SYSCTL_ONE, allowing arbitrarily small values. Because SOCK_MIN_RCVBUF depends on sizeof(struct sk_buff) and cacheline alignment, its value varies across architectures and configuration options. Using a fixed constant of 4096 ensures a predictable, architecture- independent lower bound that is safely above SOCK_MIN_RCVBUF everywhere and matches the documented 4K default. Fix this by setting tcp_rmem.extra1 to 4096 and updating the documentation. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912144848.3448026-1-edumazet@google.com Signed-off-by: Paolo Abeni --- Documentation/networking/ip-sysctl.rst | 2 ++ net/ipv4/sysctl_net_ipv4.c | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index b05829e44d8f..f7af0286341c 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -874,6 +874,8 @@ tcp_rmem - vector of 3 INTEGERs: min, default, max case this value is ignored. Default: between 131072 and 32MB, depending on RAM size. + Each of the three values cannot be set below 4096. + tcp_sack - BOOLEAN Enable select acknowledgments (SACKS). diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index 2f0363bca2a8..e3760daa3470 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -51,6 +51,8 @@ static int tcp_ecn_mode_max = 5; static u32 icmp_errors_extension_mask_all = GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0); +static int tcp_min_rcvbuf = 4096; + /* obsolete */ static int sysctl_tcp_low_latency __read_mostly; @@ -1462,7 +1464,7 @@ static const struct ctl_table ipv4_net_table[] = { .maxlen = sizeof(init_net.ipv4.sysctl_tcp_rmem), .mode = 0644, .proc_handler = proc_dointvec_minmax, - .extra1 = SYSCTL_ONE, + .extra1 = &tcp_min_rcvbuf, }, { .procname = "tcp_comp_sack_delay_ns", From f4fafaf02174c32bce2f9bb4196fadf13f1fd96e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 9 Sep 2026 09:58:22 +0900 Subject: [PATCH 1083/1317] ksmbd: fix partial normalized name responses Windows may request FILE_NORMALIZED_NAME_INFORMATION with an output buffer that only fits the fixed portion of the variable-length response. Treat the fixed portion as FILE_NORMALIZED_NAME_INFORMATION_SIZE so ksmbd returns STATUS_BUFFER_OVERFLOW instead of STATUS_INFO_LENGTH_MISMATCH. This avoids rejecting valid partial normalized-name responses. Fixes: 6b8b79226bc3 ("ksmbd: fix partial file information responses") Reported-by: Mobin Aydinfar Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 3 +++ fs/smb/server/smb2pdu.h | 1 + 2 files changed, 4 insertions(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 8acc5174530b..0436b7c898b1 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7312,6 +7312,9 @@ static int smb2_get_info_file(struct ksmbd_work *work, case FILE_ALTERNATE_NAME_INFORMATION: fixed_len = FILE_ALTERNATE_NAME_INFORMATION_SIZE; break; + case FILE_NORMALIZED_NAME_INFORMATION: + fixed_len = FILE_NORMALIZED_NAME_INFORMATION_SIZE; + break; case FILE_STREAM_INFORMATION: fixed_len = FILE_STREAM_INFORMATION_SIZE; break; diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index a6200d8630e2..ca8e27f7b712 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -212,6 +212,7 @@ struct file_sparse { #define FILE_ALLOCATION_INFORMATION_SIZE 19 #define FILE_END_OF_FILE_INFORMATION_SIZE 20 #define FILE_ALTERNATE_NAME_INFORMATION_SIZE 8 +#define FILE_NORMALIZED_NAME_INFORMATION_SIZE 8 #define FILE_STREAM_INFORMATION_SIZE 32 #define FILE_PIPE_INFORMATION_SIZE 23 #define FILE_PIPE_LOCAL_INFORMATION_SIZE 24 From 9fa26285ae70ac2d3d1b47459a6b4463ab053e1c Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 9 Sep 2026 09:58:48 +0900 Subject: [PATCH 1084/1317] ksmbd: keep compound responses on query info errors Do not reset the RFC1002 length of the complete response when a query info buffer is too small. The current command will add its error response through ksmbd_iov_pin_rsp(), while resetting the base length can truncate earlier responses in a compound request. This lets ksmbd return the earlier responses and the query-info error response together. Remove the now-unused rsp_org parameter from the pipe query-info helpers. Fixes: e2b76ab8b5c9 ("ksmbd: add support for read compound") Reported-by: Mobin Aydinfar Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 0436b7c898b1..6b8809f67b92 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -6275,21 +6275,18 @@ int smb2_query_dir(struct ksmbd_work *work) * @reqOutputBufferLength: max buffer length expected in command response * @fixed_len: minimum fixed response length * @rsp: query info response buffer contains output buffer length - * @rsp_org: base response buffer pointer in case of chained response * * Return: 0 on success, otherwise error */ static int buffer_check_err(int reqOutputBufferLength, unsigned int fixed_len, - struct smb2_query_info_rsp *rsp, - void *rsp_org) + struct smb2_query_info_rsp *rsp) { unsigned int output_len = le32_to_cpu(rsp->OutputBufferLength); if (reqOutputBufferLength < fixed_len) { pr_err("Invalid Buffer Size Requested\n"); rsp->hdr.Status = STATUS_INFO_LENGTH_MISMATCH; - *(__be32 *)rsp_org = cpu_to_be32(sizeof(struct smb2_hdr)); return -EINVAL; } @@ -6300,8 +6297,7 @@ static int buffer_check_err(int reqOutputBufferLength, return 0; } -static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp, - void *rsp_org) +static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp) { struct smb2_file_standard_info *sinfo; @@ -6316,8 +6312,7 @@ static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp, cpu_to_le32(sizeof(struct smb2_file_standard_info)); } -static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num, - void *rsp_org) +static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num) { struct smb2_file_internal_info *file_info; @@ -6331,8 +6326,7 @@ static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num, static int smb2_get_info_file_pipe(struct ksmbd_session *sess, struct smb2_query_info_req *req, - struct smb2_query_info_rsp *rsp, - void *rsp_org) + struct smb2_query_info_rsp *rsp) { u64 id; int rc; @@ -6357,16 +6351,16 @@ static int smb2_get_info_file_pipe(struct ksmbd_session *sess, switch (req->FileInfoClass) { case FILE_STANDARD_INFORMATION: - get_standard_info_pipe(rsp, rsp_org); + get_standard_info_pipe(rsp); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, rsp_org); + rsp); break; case FILE_INTERNAL_INFORMATION: - get_internal_info_pipe(rsp, id, rsp_org); + get_internal_info_pipe(rsp, id); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, rsp_org); + rsp); break; default: ksmbd_debug(SMB, "smb2_info_file_pipe for %u not supported\n", @@ -7202,8 +7196,7 @@ static int smb2_get_info_file(struct ksmbd_work *work, if (test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_PIPE)) { /* smb2 info file called for pipe */ - rc = smb2_get_info_file_pipe(work->sess, req, rsp, - work->response_buf); + rc = smb2_get_info_file_pipe(work->sess, req, rsp); goto iov_pin_out; } @@ -7321,7 +7314,7 @@ static int smb2_get_info_file(struct ksmbd_work *work, } rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), fixed_len, - rsp, work->response_buf); + rsp); } ksmbd_fd_put(work, fp); @@ -7592,7 +7585,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, } rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), fixed_len, - rsp, work->response_buf); + rsp); path_put(&path); if (!rc) @@ -7706,7 +7699,7 @@ static int smb2_get_info_sec(struct ksmbd_work *work, rsp->OutputBufferLength = cpu_to_le32(secdesclen); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, work->response_buf); + rsp); if (rc) goto err_out; From 075bc7b1d3dde5ed43fbaabbc1a69f09b7fc3a47 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Thu, 10 Sep 2026 10:16:52 +0800 Subject: [PATCH 1085/1317] dmaengine: mmp_pdma: fix wrong sg length in mmp_pdma_prep_slave_sg() In mmp_pdma_prep_slave_sg(), for_each_sg() iterates the scatterlist putting each entry into 'sg', but the entry length is read from 'sgl' (the list head) instead of 'sg' (the current entry): for_each_sg(sgl, sg, sg_len, i) { addr = sg_dma_address(sg); avail = sg_dma_len(sgl); /* should be 'sg' */ Consequently 'avail' is always the length of the first entry. For multi-sg lists this causes out-of-bounds reads when a later entry is shorter than the first, and silent data loss when it is longer. Single-sg or uniformly-sized lists happen to mask the issue. Fixes: c8acd6aa6bed3 ("dmaengine: mmp-pdma support") Signed-off-by: Baineng Shou Reviewed-by: Frank Li Link: https://patch.msgid.link/20260910021652.1296640-1-shoubaineng@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/mmp_pdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index 78e3e07e681d..ed520737882b 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -712,7 +712,7 @@ mmp_pdma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl, for_each_sg(sgl, sg, sg_len, i) { addr = sg_dma_address(sg); - avail = sg_dma_len(sgl); + avail = sg_dma_len(sg); do { len = min_t(size_t, avail, PDMA_MAX_DESC_BYTES); From 095858324f063dba830041f067872f0a08765d2f Mon Sep 17 00:00:00 2001 From: Hao-Qun Huang Date: Sun, 13 Sep 2026 03:20:49 +0800 Subject: [PATCH 1086/1317] spi: virtio: Use the per-transfer bits per word virtio_spi_transfer_one() puts spi->bits_per_word into the request header, so a transfer that sets its own word size reaches the backend with the device default instead. The SPI core has already copied that default into xfer->bits_per_word when the transfer leaves it at zero, the same way it does for xfer->speed_hz, which this function already uses. Per-transfer word sizes are ordinary SPI usage. mipi_dbi, for one, sends a 9-bit command and reads the reply as 8-bit data in the same message. With a 16-bit device default, a one-byte transfer asking for 8 bits goes out as a partial 16-bit word, which the backend may reject. Fixes: f98cabe3f6cf ("SPI: Add virtio SPI driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hao-Qun Huang Link: https://patch.msgid.link/20260913032049.11209.alvinhuang0603@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 2256dfec5407..3e181bd8bc94 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -168,7 +168,7 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, /* Fill struct spi_transfer_head */ th->chip_select_id = spi_get_chipselect(spi, 0); - th->bits_per_word = spi->bits_per_word; + th->bits_per_word = xfer->bits_per_word; th->cs_change = xfer->cs_change; th->tx_nbits = xfer->tx_nbits; th->rx_nbits = xfer->rx_nbits; From 9a0b159ff18c8f6fcf982bb81e15a9ceb14db43a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 14 Sep 2026 22:12:34 -1000 Subject: [PATCH 1087/1317] sched_ext: scx_qmap: Restore unused idle claims from ops.dispatch() scx_qmap tracks idle cids itself. pick_direct_dispatch_cid() claims a cid by clearing its bit and the task is inserted into that cid's local DSQ, which kicks the CPU. When the task does not arrive, for example because the insert fell back to the global DSQ after an affinity change, the CPU wakes, finds nothing and picks idle again. That is not an idle transition, so ops.update_idle() is not called and the cid stays marked busy until an unrelated task runs on it. Restore the claim from ops.dispatch(). The kick guarantees a dispatch on the kicked CPU, and when it finds nothing to run with a NULL @prev, the CPU is going back to idle. Document the pattern in ops.update_idle(), which reports only actual transitions. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi Cc: Andrea Righi --- kernel/sched/ext/internal.h | 6 ++++++ tools/sched_ext/scx_qmap.bpf.c | 14 ++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 076a351bb3f2..0adaf649d5e0 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -572,6 +572,12 @@ struct sched_ext_ops { * * Specify the %SCX_OPS_KEEP_BUILTIN_IDLE flag to keep the built-in idle * tracking. + * + * Only actual transitions are reported. A CPU that is claimed with an + * idle pick and kicked but dispatches no task returns to idle without a + * transition. A scheduler tracking idle CPUs itself must restore the + * idle state from ops.dispatch() when it returns without the next task + * to run. */ void (*update_idle)(s32 cpu, bool idle); diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index bda56c37acb5..67b7c01cae55 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -818,10 +818,10 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) batch--; cpuc->dsp_cnt--; if (!batch || !scx_bpf_dispatch_nr_slots()) { - if (scan_shared_dsq(false)) + if (scan_shared_dsq(false) || + scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid))) return; - scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid)); - return; + goto prev; } if (!cpuc->dsp_cnt) break; @@ -832,10 +832,14 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) if (scan_shared_dsq(false)) return; - +prev: /* * No other tasks. @prev will keep running. Update its core_sched_seq as * if the task were enqueued and dispatched immediately. + * + * No @prev to keep running means the CPU goes idle. If its claim was + * never used, that is not a transition and ops.update_idle() stays + * silent. Restore the claim here. */ if (prev) { taskc = lookup_task_ctx(prev); @@ -844,6 +848,8 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) taskc->core_sched_seq = qa.core_sched_tail_seqs[weight_to_idx(prev->scx.weight)]++; + } else { + cmask_set(cid, &qa.idle_cids.mask); } } From a9e3760b0838299649c0d57cca44daaf40ba3c33 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 14 Sep 2026 22:12:34 -1000 Subject: [PATCH 1088/1317] sched_ext: Maintain an online cid mask in the scheduler arena Schedulers on the default cid mapping treat [0, nr_online_cids) as the online set and restart on hotplug. Schedulers that install their own mapping with scx_bpf_cid_override() have no way to learn which cids are online: the count no longer identifies members and the CPU-form cpumask is unusable from cid programs. This is an obvious hole in the cid API. Add scx_bpf_online_cmask(), a kernel-maintained cmask in the scheduler's arena, allocated alongside the per-CPU scratch masks and populated after the cid mapping is finalized and before ops.init(), for child schedulers too. The pointer stays valid through ops.exit() with no reference to take. It is the arena offset as a void pointer, the same form struct_ops arena arguments arrive in. The verifier types the void return as a scalar for the program's arena cast. The mask follows the SCX hotplug notifications: seeded from cpu_active_mask and updated before ops.cid_online/offline() runs, so it lags cpu_online_mask only inside a hotplug transition. Updates walk the scheduler list under the lock that also serializes unlinking. Reads are live, not atomic snapshots. Root initialization excludes hotplug. v2: Reworded the getter kerneldoc (Andrea Righi). Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 75 ++++++++++++++++++++++-- kernel/sched/ext/internal.h | 3 +- kernel/sched/ext/sub.c | 10 ++-- tools/sched_ext/include/scx/common.bpf.h | 1 + 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 83999203a63a..70b711c4de6e 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3670,8 +3670,20 @@ static void handle_hotplug(struct rq *rq, bool online) s16 *tbl = rcu_dereference_check(scx_cpu_to_cid_tbl, lockdep_is_cpus_held()); - if (tbl) + if (tbl) { + struct scx_sched *pos; + cpu_or_cid = tbl[cpu]; + + guard(raw_spinlock_irqsave)(&scx_sched_lock); + list_for_each_entry(pos, &scx_sched_all, all) { + struct scx_cmask *mask = pos->online_cmask; + + if (mask) + __assign_bit(cpu_or_cid, (unsigned long *)mask->bits, + online); + } + } } if (online && SCX_HAS_OP(sch, cpu_online)) @@ -5280,12 +5292,17 @@ static void free_exit_info(struct scx_exit_info *ei); static const char *scx_exit_reason(enum scx_exit_kind kind); static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind); -s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch) +s32 scx_alloc_kern_arena_objs(struct scx_sched *sch) { size_t size = struct_size_t(struct scx_cmask, bits, SCX_CMASK_NR_WORDS(num_possible_cpus())); + struct scx_cmask *online; + struct scx_cmask_ref ref; int cpu; + /* hotplug stays excluded until the online mask is published */ + lockdep_assert_cpus_held(); + if (!sch->is_cid_type || !sch->arena_pool) return 0; @@ -5301,15 +5318,28 @@ s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch) return -ENOMEM; scx_cmask_init(*slot, 0, num_possible_cpus()); } + + /* pack the online mask alongside the scratch masks */ + online = scx_arena_alloc(sch, size); + if (!online) + return -ENOMEM; + + scoped_guard(rcu) { + scx_cmask_ref_init_kern(sch, online, 0, num_possible_cpus(), &ref); + scx_cmask_ref_from_cpumask(&ref, cpu_active_mask); + } + sch->online_cmask = online; + return 0; } -static void scx_set_cmask_scratch_free(struct scx_sched *sch) +static void scx_free_kern_arena_objs(struct scx_sched *sch) { size_t size = struct_size_t(struct scx_cmask, bits, SCX_CMASK_NR_WORDS(num_possible_cpus())); int cpu; + scx_arena_free(sch, sch->online_cmask, size); if (!sch->set_cmask_scratch) return; @@ -5396,7 +5426,7 @@ static void scx_sched_free_rcu_work(struct work_struct *work) rhashtable_free_and_destroy(&sch->dsq_hash, NULL, NULL); free_exit_info(sch->exit_info); - scx_set_cmask_scratch_free(sch); + scx_free_kern_arena_objs(sch); scx_arena_pool_destroy(sch); if (sch->arena_map) bpf_map_put(sch->arena_map); @@ -7601,7 +7631,7 @@ static void scx_root_enable_workfn(struct kthread_work *work) goto err_disable; } - ret = scx_set_cmask_scratch_alloc(sch); + ret = scx_alloc_kern_arena_objs(sch); if (ret) { cpus_read_unlock(); goto err_disable; @@ -10338,13 +10368,45 @@ __bpf_kfunc u32 scx_bpf_nr_cids(void) * hotplug, which lets schedulers treat [0, nr_online_cids) as the online * range. Schedulers that prefer to handle hotplug without a restart should * install a custom mapping via scx_bpf_cid_override() and track onlining - * through the ops.cid_online / ops.cid_offline callbacks. + * through the ops.cid_online / ops.cid_offline callbacks, starting from the + * mask scx_bpf_online_cmask() returns. */ __bpf_kfunc u32 scx_bpf_nr_online_cids(void) { return num_online_cpus(); } +/** + * scx_bpf_online_cmask - Return the online cid mask in the scheduler arena + * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs + * + * Return a kernel-maintained cmask covering [0, scx_bpf_nr_cids()), or NULL if + * the calling program is not associated with a live cid-form scheduler or the + * mask is not allocated yet, as in ops.init_cids(). Treat the mask as read-only + * even though arena memory stays writable by the BPF scheduler. The mask + * follows the SCX hotplug notifications: a cid's bit is updated before + * ops.cid_online/offline() runs for it. The pointer is valid from ops.init() + * through ops.exit(). Root ops.init() runs with hotplug excluded. Other + * contexts can observe concurrent updates. + */ +__bpf_kfunc const void *scx_bpf_online_cmask(const struct bpf_prog_aux *aux) +{ + struct scx_sched *sch; + struct scx_cmask *online; + + guard(rcu)(); + + sch = scx_prog_sched(aux); + if (unlikely(!sch)) + return NULL; + online = sch->online_cmask; + if (unlikely(!online)) + return NULL; + + /* BPF rebases by the low 32 bits, like __arena callback args */ + return (void *)((unsigned long)online - sch->arena_kern_base); +} + /** * scx_bpf_this_cid - Return the cid of the CPU this program is running on * @@ -10708,6 +10770,7 @@ BTF_ID_FLAGS(func, scx_bpf_nr_node_ids) BTF_ID_FLAGS(func, scx_bpf_nr_cpu_ids) BTF_ID_FLAGS(func, scx_bpf_nr_cids) BTF_ID_FLAGS(func, scx_bpf_nr_online_cids) +BTF_ID_FLAGS(func, scx_bpf_online_cmask, KF_IMPLICIT_ARGS | KF_ARENA_RET) BTF_ID_FLAGS(func, scx_bpf_this_cid) BTF_ID_FLAGS(func, scx_bpf_get_possible_cpumask, KF_ACQUIRE) BTF_ID_FLAGS(func, scx_bpf_get_online_cpumask, KF_ACQUIRE) diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 0adaf649d5e0..3464e0f113c1 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -1561,6 +1561,7 @@ struct scx_sched { * and passes it to the callback's __arena argument. */ struct scx_cmask * __percpu *set_cmask_scratch; + struct scx_cmask *online_cmask; DECLARE_BITMAP(has_op, SCX_OPI_END); @@ -2087,7 +2088,7 @@ void scx_disable_and_exit_task(struct scx_sched *sch, struct task_struct *p); void scx_cgroup_lock(void); void scx_cgroup_unlock(void); #endif -s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch); +s32 scx_alloc_kern_arena_objs(struct scx_sched *sch); void scx_disable_bypass_dsp(struct scx_sched *sch); void scx_bypass(struct scx_sched *sch, bool bypass); s32 scx_link_sched(struct scx_sched *sch); diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 385302d19914..f7aeb1488566 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -1805,6 +1805,12 @@ void scx_sub_enable_workfn(struct kthread_work *work) goto err_disable; } + scoped_guard(cpus_read_lock) { + ret = scx_alloc_kern_arena_objs(sch); + if (ret) + goto err_disable; + } + if (sch->ops.init) { ret = SCX_CALL_OP_RET(sch, init, NULL); if (ret) { @@ -1815,10 +1821,6 @@ void scx_sub_enable_workfn(struct kthread_work *work) sch->exit_info->flags |= SCX_EFLAG_INITIALIZED; } - ret = scx_set_cmask_scratch_alloc(sch); - if (ret) - goto err_disable; - struct scx_sub_attach_args sub_attach_args = { .ops = &sch->ops, .cgroup_path = sch->cgrp_path, diff --git a/tools/sched_ext/include/scx/common.bpf.h b/tools/sched_ext/include/scx/common.bpf.h index 76f5e025e107..2ddb01a059fd 100644 --- a/tools/sched_ext/include/scx/common.bpf.h +++ b/tools/sched_ext/include/scx/common.bpf.h @@ -113,6 +113,7 @@ s32 scx_bpf_this_cid(void) __ksym __weak; struct task_struct *scx_bpf_cid_curr(s32 cid) __ksym __weak; u32 scx_bpf_nr_cids(void) __ksym __weak; u32 scx_bpf_nr_online_cids(void) __ksym __weak; +const void __arena *scx_bpf_online_cmask(void) __ksym __weak; u32 scx_bpf_cidperf_cap(s32 cid) __ksym __weak; u32 scx_bpf_cidperf_cur(s32 cid) __ksym __weak; s32 scx_bpf_cidperf_set(s32 cid, u32 perf) __ksym __weak; From 2b50adefed9808a56d84d1de803cad882cc787fa Mon Sep 17 00:00:00 2001 From: Nicolas Thibert Date: Tue, 8 Sep 2026 10:01:08 +0200 Subject: [PATCH 1089/1317] Bluetooth: btusb: fix NXP IW610 composite device handling The NXP IW610 module exposes itself as a composite USB device (0471:0215) with three interfaces: two real Bluetooth HCI interfaces (class 0xe0) and one vendor-specific WiFi interface (class 0xff) used by mwifiex-nxp. The composite device's whole USB descriptor reports class 0xe0/01/01 (Bluetooth), so btusb_table's generic USB_DEVICE_INFO(0xe0, 0x01, 0x01) entry matches every interface, not just the two real HCI ones -- btusb ends up binding the WiFi interface too, and mwifiex-nxp never gets it. Fix: 1. In btusb_table (the table the USB core actually matches against), explicitly ignore the WiFi interface via BTUSB_IGNORE, ahead of the generic entry. 2. In quirks_table, scope the existing BTUSB_MARVELL entry to the BT interface class instead of matching the whole device by VID/PID (harmless either way since quirks_table isn't consulted for initial binding, but keep it correct). Not upstream anywhere: checked NXP's own i.MX kernel fork (nxp-imx/linux-imx), no IW610 references in btusb.c on any branch -- their reference designs wire this chip differently (WiFi over SDIO per their release notes), so they never hit this. Signed-off-by: Nicolas Thibert Cc: stable@vger.kernel.org Assisted-by: LLM (Claude Sonnet 5, Anthropic) Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 002b9f975710..dc7191bf4234 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -71,6 +71,15 @@ static struct usb_driver btusb_driver; #define BTUSB_BROKEN_EXT_SCAN BIT(29) static const struct usb_device_id btusb_table[] = { + /* + * NXP IW610 (0471:0215): the composite device reports Bluetooth + * class at the whole-device level, so the generic entry below + * would also match this WiFi vendor interface. Ignore it here + * first so mwifiex-nxp can bind it instead. + */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0471, 0x0215, 0xff, 0xff, 0xff), + .driver_info = BTUSB_IGNORE }, + /* Generic Bluetooth USB device */ { USB_DEVICE_INFO(0xe0, 0x01, 0x01) }, @@ -477,6 +486,14 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, + /* + * NXP IW610 BT interfaces (Marvell-lineage silicon, same quirk as + * the 0x1286 entries above). Scoped to the BT interface class, + * not just VID/PID -- see the btusb_table entry above. + */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0471, 0x0215, 0xe0, 0x01, 0x01), + .driver_info = BTUSB_MARVELL }, + /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, { USB_DEVICE(0x8087, 0x0026), .driver_info = BTUSB_INTEL_COMBINED }, From e8241766794cf551d787fa3a77c0d54bbea6f6aa Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Mon, 7 Sep 2026 00:37:43 +0100 Subject: [PATCH 1090/1317] Bluetooth: eir: validate service data length before reading UUID eir_get_service_data() reads a 16-bit UUID from the service data using get_unaligned_le16() without first checking that the data is long enough to hold a UUID16 (2 bytes). If a malformed EIR entry has a service data field with only 1 byte of payload (field_len=2), eir_get_data() returns dlen=1. The subsequent get_unaligned_le16() then reads 1 byte past the field boundary. Additionally, if the corrupted UUID happens to match, the length calculation "dlen - 2" underflows to SIZE_MAX since dlen is size_t. Current callers either pass NULL for the length parameter or bounds-check the returned length, but future callers may not. Add a check that dlen >= sizeof(u16) and skip fields that are too short to contain a valid UUID16. Fixes: 8f9ae5b3ae80 ("Bluetooth: eir: Add helpers for managing service data") Cc: stable@vger.kernel.org Signed-off-by: Aamir Ahmed Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/eir.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/eir.c b/net/bluetooth/eir.c index a55696820b22..ee0136bfae40 100644 --- a/net/bluetooth/eir.c +++ b/net/bluetooth/eir.c @@ -373,7 +373,15 @@ void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len) size_t dlen; while ((eir = eir_get_data(eir, eir_len, EIR_SERVICE_DATA, &dlen))) { - u16 value = get_unaligned_le16(eir); + u16 value; + + if (dlen < sizeof(value)) { + eir += dlen; + eir_len = eir_end - eir; + continue; + } + + value = get_unaligned_le16(eir); if (uuid == value) { if (len) From 6610c6fe4b8936c232048e6049bf77c70a6f759c Mon Sep 17 00:00:00 2001 From: ThangNN99 Date: Sun, 6 Sep 2026 22:21:27 +0700 Subject: [PATCH 1091/1317] Bluetooth: hci_core: Fix queuing tx_work after workqueue is drained hci_send_acl(), hci_send_sco() and hci_send_iso() queue hdev->tx_work unconditionally. They can run from the L2CAP/SCO/ISO socket send path while hci_dev_close_sync() is draining hdev->workqueue (HCIDEVDOWN racing with a socket write). Since that queue_work() is not chained work from the tx_work worker itself, __queue_work() sees the queue marked __WQ_DRAINING, warns "cannot queue %ps on wq %s", and drops the work: WARNING: CPU: 1 PID: 5985 at kernel/workqueue.c:2352 __queue_work Call Trace: queue_work_on l2cap_chan_send l2cap_sock_sendmsg ... hci_dev_close_sync() already sets HCI_CMD_DRAIN_WORKQUEUE before draining, but only hci_cmd_work() and handle_cmd_cnt_and_timer() check it before queuing. Route the tx_work producers through the same guard via a shared hci_sched_tx() helper. Fixes: 525daaea459f ("Bluetooth: hci_sync: Set HCI_CMD_DRAIN_WORKQUEUE during device close") Reported-by: syzbot+b6919040d9958e2fc1ae@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b6919040d9958e2fc1ae Signed-off-by: ThangNN99 Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index d7355c73f93e..c322e4736621 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3236,6 +3236,17 @@ static void hci_queue_acl(struct hci_chan *chan, struct sk_buff_head *queue, bt_dev_dbg(hdev, "chan %p queued %d", chan, skb_queue_len(queue)); } +/* Queue hdev->tx_work, unless hdev->workqueue is being drained by + * hci_dev_close_sync(), which would otherwise WARN and drop the work. + */ +static void hci_sched_tx(struct hci_dev *hdev) +{ + rcu_read_lock(); + if (!hci_dev_test_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE)) + queue_work(hdev->workqueue, &hdev->tx_work); + rcu_read_unlock(); +} + void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags) { struct hci_dev *hdev = chan->conn->hdev; @@ -3244,7 +3255,7 @@ void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags) hci_queue_acl(chan, &chan->data_q, skb, flags); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* Send SCO data */ @@ -3269,7 +3280,7 @@ void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb) bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(&conn->data_q)); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* Send ISO data */ @@ -3340,7 +3351,7 @@ void hci_send_iso(struct hci_conn *conn, struct sk_buff *skb) hci_queue_iso(conn, &conn->data_q, skb); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* ---- HCI TX task (outgoing data) ---- */ From 4b837ebd0ea21ae5cc26f02dc042edc6fe7b46b9 Mon Sep 17 00:00:00 2001 From: Chandrashekar Devegowda Date: Tue, 8 Sep 2026 15:26:58 +0530 Subject: [PATCH 1092/1317] Bluetooth: btintel_pcie: validate TX skb length in send_sync btintel_pcie_prepare_tx() copies skb->len bytes into a fixed BTINTEL_PCIE_BUFFER_SIZE (4096) DMA slot via an unchecked memcpy. Oversized packets are currently rejected only in btintel_pcie_send_frame(); any future caller of btintel_pcie_send_sync() would silently overflow the DMA buffer. Add the bounds check in btintel_pcie_send_sync() itself, right before skb_push() and the DMA copy. Assisted-by: Copilot:claude-sonnet-5 code-review code-generation Fixes: 6e65a09f9275 ("Bluetooth: btintel_pcie: Add *setup* function to download firmware") Signed-off-by: Chandrashekar Devegowda Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 6d9649776ae7..405b23e21473 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -404,6 +404,12 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data, if (tfd_index > txq->count) return -ERANGE; + if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { + bt_dev_err(hdev, "TX skb too large (%u > %u)", skb->len, + BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); + return -EMSGSIZE; + } + /* Firmware raises alive interrupt on HCI_OP_RESET or * BTINTEL_HCI_OP_RESET */ From d236517c264e41dc09833c708ef23bccb7a91219 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 6 Sep 2026 23:43:32 +0800 Subject: [PATCH 1093/1317] Bluetooth: coredump: Quiesce dump work on unregister hci_devcd_handle_pkt_init() arms dump_timeout and coredump producers queue dump_rx without holding an hdev reference. Unregister leaves both works live, so disconnecting during an active dump lets them access hdev after hci_release_dev() frees it. Shut down coredump processing during unregister. Close the producer gate under dump_q.lock before disabling both works, then free the active buffer and queued packets under hci_dev_lock. Serializing the gate with enqueue prevents controller-specific workers from adding packets after the final purge. Fixes: 9695ef876fd1 ("Bluetooth: Add support for hci devcoredump") Reported-by: syzbot+b170dbf55520ebf5969a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b170dbf55520ebf5969a Reported-by: Aby Sam Ross Link: https://lore.kernel.org/r/20260322210849.68743-1-abysamross@gmail.com Suggested-by: Aby Sam Ross Reported-by: Tristan Madani Link: https://lore.kernel.org/r/20260814231248.3096377-1-tristmd@gmail.com Reported-by: Xiang Mei Assisted-by: OpenAI Codex:gpt-5 Signed-off-by: Weiming Shi Reported-by: Xiang Mei Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/coredump.h | 2 + net/bluetooth/coredump.c | 65 +++++++++++++++++++++----------- net/bluetooth/hci_core.c | 1 + 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h index 1f071ab55416..acc1849f66c0 100644 --- a/include/net/bluetooth/coredump.h +++ b/include/net/bluetooth/coredump.h @@ -70,6 +70,7 @@ struct hci_devcoredump { const char *hci_devcd_state_name(enum devcoredump_state state); void hci_devcd_reset(struct hci_dev *hdev); +void hci_devcd_shutdown(struct hci_dev *hdev); void hci_devcd_rx(struct work_struct *work); void hci_devcd_timeout(struct work_struct *work); @@ -89,6 +90,7 @@ static inline const char *hci_devcd_state_name(enum devcoredump_state state) } static inline void hci_devcd_reset(struct hci_dev *hdev) {} +static inline void hci_devcd_shutdown(struct hci_dev *hdev) {} static inline void hci_devcd_rx(struct work_struct *work) {} static inline void hci_devcd_timeout(struct work_struct *work) {} diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c index 5bee863bd6d2..71fc8dab4004 100644 --- a/net/bluetooth/coredump.c +++ b/net/bluetooth/coredump.c @@ -104,6 +104,22 @@ static void hci_devcd_free(struct hci_dev *hdev) hci_devcd_reset(hdev); } +void hci_devcd_shutdown(struct hci_dev *hdev) +{ + unsigned long flags; + + spin_lock_irqsave(&hdev->dump.dump_q.lock, flags); + hdev->dump.supported = false; + spin_unlock_irqrestore(&hdev->dump.dump_q.lock, flags); + + disable_work_sync(&hdev->dump.dump_rx); + disable_delayed_work_sync(&hdev->dump.dump_timeout); + + hci_dev_lock(hdev); + hci_devcd_free(hdev); + hci_dev_unlock(hdev); +} + /* Call with hci_dev_lock only. */ static int hci_devcd_alloc(struct hci_dev *hdev, u32 size) { @@ -442,7 +458,29 @@ EXPORT_SYMBOL(hci_devcd_register); static inline bool hci_devcd_enabled(struct hci_dev *hdev) { - return hdev->dump.supported; + return READ_ONCE(hdev->dump.supported); +} + +static int hci_devcd_queue(struct hci_dev *hdev, struct sk_buff *skb) +{ + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&hdev->dump.dump_q.lock, flags); + if (!hdev->dump.supported) + err = -EOPNOTSUPP; + else + __skb_queue_tail(&hdev->dump.dump_q, skb); + spin_unlock_irqrestore(&hdev->dump.dump_q.lock, flags); + + if (err) { + kfree_skb(skb); + return err; + } + + queue_work(hdev->workqueue, &hdev->dump.dump_rx); + + return 0; } int hci_devcd_init(struct hci_dev *hdev, u32 dump_size) @@ -459,10 +497,7 @@ int hci_devcd_init(struct hci_dev *hdev, u32 dump_size) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT; put_unaligned_le32(dump_size, skb_put(skb, 4)); - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_init); @@ -478,10 +513,7 @@ int hci_devcd_append(struct hci_dev *hdev, struct sk_buff *skb) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_SKB; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_append); @@ -503,10 +535,7 @@ int hci_devcd_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_PATTERN; skb_put_data(skb, &p, sizeof(p)); - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_append_pattern); @@ -523,10 +552,7 @@ int hci_devcd_complete(struct hci_dev *hdev) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_COMPLETE; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_complete); @@ -543,10 +569,7 @@ int hci_devcd_abort(struct hci_dev *hdev) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_ABORT; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_abort); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index c322e4736621..d183efaf9063 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2673,6 +2673,7 @@ void hci_unregister_dev(struct hci_dev *hdev) disable_work_sync(&hdev->error_reset); disable_delayed_work_sync(&hdev->cmd_timer); disable_delayed_work_sync(&hdev->ncmd_timer); + hci_devcd_shutdown(hdev); hci_cmd_sync_clear(hdev); From 4914c499896121ae8b9d5b90f0abc5c8287ff396 Mon Sep 17 00:00:00 2001 From: Radek Podgorny Date: Wed, 9 Sep 2026 00:29:37 +0200 Subject: [PATCH 1094/1317] Bluetooth: put the peer's on-air address on air when we cannot resolve An identity address only reaches a peer that is advertising an RPA if the controller resolves it on our behalf. Where it cannot, the host has to put the peer's on-air address on air itself. hci_connect_le() still swaps the caller's identity address for the peer's cached RPA before creating the connection, but __hci_conn_add() resolves the RPA back to the identity address when it stores it, so the identity is what goes out. Storing the identity is right when the controller translates it on the way to the radio; without LL Privacy, or with this peer absent from the resolving list, nothing does. A peer advertising an RPA cannot answer its identity address, so the attempt burns a full create-connection timeout. That is not merely a slow connect: a controller without extended scanning cannot scan while it is initiating, so every dead attempt also takes the scanner off the air for the whole timeout. Measured on a CYW43438, which reports neither LL Privacy nor extended advertising (LE features 3f 00 00 08 00 00 00 00), against a peer advertising a resolvable private address the host holds the IRK for, with the connection requested on the peer's identity address: before: LE Create Connection to the identity address, public type 1.61s -> 22.07s, then LE Create Connection Cancel LE Connection Complete: Unknown Connection Identifier (0x02) after: LE Create Connection to the peer's RPA, random type LE Connection Complete: Success Advertising reports reaching the host per second, same window, same five unrelated devices on the adapter: before 1s:2 [nothing from 2s through 21s] 22s:5 23s:3 after 0s:11 1s:5 2s:2 3s:5 4s:3 5s:4 ... 21s:2 22s:1 23s:2 One dead connect costs twenty seconds of scanning for every device on the adapter, not just the one being dialled. Keep the RPA in conn->dst unless the controller will translate the identity address: address resolution enabled and the peer's identity actually programmed into the resolving list. Testing ll_privacy_capable() alone would not be enough: it reports the feature bit, not whether resolution is switched on and not whether this peer is in the list. Resolution is cleared with the other volatile flags on power-off and switched off again while suspend pauses scanning, and a peer's IRK is only programmed along the accept list path, so a direct-connect target, a peer without HCI_CONN_FLAG_ADDRESS_RESOLUTION, and one that did not fit in a full list are all absent from it. With the peer programmed, the identity address stays in conn->dst and the controller translates it: measured on an Intel controller, the host dials the identity and LE Enhanced Connection Complete reports Resolved Public with the peer's RPA in the separate peer resolvable private address field. With the peer absent from the list the same setup dials the RPA itself. Everything downstream already copes with an RPA in conn->dst: it is what every outgoing LE connection stored before 14b06c3a88f7, the connection complete event names the address that was dialled, and le_conn_complete_evt() resolves it back to the identity once the link is up. ISO links keep the unconditional conversion: they are created from an existing ACL or a periodic sync and never dial this address themselves. Keeping the RPA is only right while the peer is still using it, which is why the preceding patch drops the cached RPA as soon as the peer is seen advertising its identity address. Without that, a peer that turns privacy off would be dialled on the address it abandoned rather than the one it is answering on. Fixes: 14b06c3a88f7 ("Bluetooth: HCI: Always use the identity address when initializing a connection") Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5 Signed-off-by: Radek Podgorny Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 8de98af2fb58..c9466cb2c7c0 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1023,6 +1023,19 @@ static struct hci_conn *__hci_conn_add(struct hci_dev *hdev, int type, if (!hdev->le_mtu && hdev->acl_mtu < HCI_MIN_LE_MTU) return ERR_PTR(-ECONNREFUSED); irk = hci_get_irk(hdev, dst, dst_type); + /* An identity address only reaches a peer advertising an RPA + * if the controller translates it. Unless address resolution + * is enabled and this peer is programmed into the resolving + * list, keep the RPA the peer is on air with; + * le_conn_complete_evt() resolves it back once the link is + * up. + */ + if (irk && + (!hci_dev_test_flag(hdev, HCI_LL_RPA_RESOLUTION) || + !hci_bdaddr_list_lookup_with_irk(&hdev->le_resolv_list, + &irk->bdaddr, + irk->addr_type))) + irk = NULL; break; case SCO_LINK: case ESCO_LINK: From d0795cfd6f655f4de84868a4f4bb41a03f037b3d Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 24 Aug 2026 21:42:36 +0545 Subject: [PATCH 1095/1317] Bluetooth: hci_codec: validate vendor codec count length The Read Local Supported Codecs parsers consume the variable-sized standard codec array before parsing the vendor codec count. Although the initial reply-size check includes a vendor count byte in the fixed layout, it does not guarantee that the byte remains after the standard codec array. If a controller reply ends immediately after that array, calculating the vendor codec array size reads vnd_codecs->num beyond the skb data. Use skb_pull_data() to validate and consume each codec header before using its count in both command variants. Fixes: 8961987f3f5f ("Bluetooth: Enumerate local supported codec and cache details") Fixes: 9ae664028a9e ("Bluetooth: Add support for Read Local Supported Codecs V2") Cc: stable@vger.kernel.org Suggested-by: Luiz Augusto von Dentz Signed-off-by: Laxman Acharya Padhya Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_codec.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/net/bluetooth/hci_codec.c b/net/bluetooth/hci_codec.c index 5bc5003c387c..7a7e813dcdda 100644 --- a/net/bluetooth/hci_codec.c +++ b/net/bluetooth/hci_codec.c @@ -145,11 +145,12 @@ void hci_read_supported_codecs(struct hci_dev *hdev) skb_pull(skb, sizeof(rp->status)); - std_codecs = (void *)skb->data; + std_codecs = skb_pull_data(skb, sizeof(*std_codecs)); + if (!std_codecs) + goto error; /* validate codecs length before accessing */ - if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)) + if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num)) goto error; /* enumerate codec capabilities of standard codecs */ @@ -161,15 +162,14 @@ void hci_read_supported_codecs(struct hci_dev *hdev) LOCAL_CODEC_ACL_MASK | LOCAL_CODEC_SCO_MASK, &caps); } - skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)); + skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num)); - vnd_codecs = (void *)skb->data; + vnd_codecs = skb_pull_data(skb, sizeof(*vnd_codecs)); + if (!vnd_codecs) + goto error; /* validate vendor codecs length before accessing */ - if (skb->len < - flex_array_size(vnd_codecs, codec, vnd_codecs->num) - + sizeof(vnd_codecs->num)) + if (skb->len < flex_array_size(vnd_codecs, codec, vnd_codecs->num)) goto error; /* enumerate vendor codec capabilities */ @@ -214,11 +214,12 @@ void hci_read_supported_codecs_v2(struct hci_dev *hdev) skb_pull(skb, sizeof(rp->status)); - std_codecs = (void *)skb->data; + std_codecs = skb_pull_data(skb, sizeof(*std_codecs)); + if (!std_codecs) + goto error; /* check for payload data length before accessing */ - if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)) + if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num)) goto error; memset(&caps, 0, sizeof(caps)); @@ -229,15 +230,14 @@ void hci_read_supported_codecs_v2(struct hci_dev *hdev) &caps); } - skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)); + skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num)); - vnd_codecs = (void *)skb->data; + vnd_codecs = skb_pull_data(skb, sizeof(*vnd_codecs)); + if (!vnd_codecs) + goto error; /* check for payload data length before accessing */ - if (skb->len < - flex_array_size(vnd_codecs, codec, vnd_codecs->num) - + sizeof(vnd_codecs->num)) + if (skb->len < flex_array_size(vnd_codecs, codec, vnd_codecs->num)) goto error; for (i = 0; i < vnd_codecs->num; i++) { From 4e93c65f87825e1e012bce56615320aeb123815d Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Wed, 19 Aug 2026 14:54:25 +0200 Subject: [PATCH 1096/1317] Bluetooth: hci_qca: Do not write to the serial port after it is closed hci_uart_close() closes the serdev port if HCI_QUIRK_NON_PERSISTENT_SETUP is set (for example, for the WCN399x family). A failed hci_dev_open_sync() following a successful qca_setup() calls hdev->close() but not hdev->shutdown(), so the port is closed while power->vregs_on is left true. qca_serdev_remove() then passes its power->vregs_on test and calls qca_power_off(), which writes to the closed port unconditionally. Seen on a WCN3988 by unbinding the driver after a controller failure. The trace below is from a 7.0.0 based kernel, where qca_power_off() was still named qca_power_shutdown(): Unable to handle kernel NULL pointer dereference at virtual address 0000000000000038 Call trace: tty_set_termios+0x50/0x238 (P) ttyport_set_baudrate+0x84/0xc0 serdev_device_set_baudrate+0x24/0x40 qca_power_shutdown+0x158/0x1fc [hci_uart] qca_serdev_remove+0x54/0x68 [hci_uart] serdev_drv_remove+0x1c/0x2c device_remove+0x4c/0x80 device_release_driver_internal+0x1cc/0x224 device_driver_detach+0x18/0x24 unbind_store+0xb4/0xc0 Check HCI_UART_PROTO_READY, which hci_uart_close() clears in the same place it closes the port, before writing to it. The regulator disable is left unconditional so the controller is still powered down. The dangling serport->tty that turns this into a use-after-free is addressed in a separate patch. Fixes: fa9ad876b8e0 ("Bluetooth: hci_qca: Add support for Qualcomm Bluetooth chip wcn3990") Signed-off-by: Ibrahim Abdelkader Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_qca.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index faa964735adb..7089e9b639b2 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2228,8 +2228,8 @@ static void qca_power_off(struct hci_uart *hu) bool sw_ctrl_state; struct qca_power *power; - /* From this point we go into power off state. But serial port is - * still open, stop queueing the IBS data and flush all the buffered + /* From this point we go into power off state. But serial port may + * still be open, stop queueing the IBS data and flush all the buffered * data in skb's. */ spin_lock_irqsave(&qca->hci_ibs_lock, flags); @@ -2251,8 +2251,14 @@ static void qca_power_off(struct hci_uart *hu) case QCA_WCN3990: case QCA_WCN3991: case QCA_WCN3998: - host_set_baudrate(hu, 2400); - qca_send_power_pulse(hu, false); + /* Both of these write to the serial port which may have + * already been closed by hci_uart_close(), which closes + * the port if HCI_QUIRK_NON_PERSISTENT_SETUP is set. + */ + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + host_set_baudrate(hu, 2400); + qca_send_power_pulse(hu, false); + } break; default: break; From 9a10987a2f160a44a638c9a35994ca6e3089696e Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sat, 22 Aug 2026 01:43:50 +0800 Subject: [PATCH 1097/1317] Bluetooth: hci_sync: Serialize local codec list cleanup hci_dev_close_sync() clears hdev->local_codecs after releasing hdev->lock. Codec list additions and both traversals in sco_sock_getsockopt() use that lock, but the close path does not. A close and BT_CODEC query can therefore interleave as follows: hci_dev_close_sync() sco_sock_getsockopt() hci_dev_lock() fetch codec entry hci_codec_list_clear() kfree(entry) read entry->id The reader then accesses an entry which the close path has freed. KASAN reported: BUG: KASAN: slab-use-after-free in sco_sock_getsockopt+0xfa0/0xfe0 Read of size 1 at addr ffff8881001c3450 Call Trace: sco_sock_getsockopt+0xfa0/0xfe0 do_sock_getsockopt+0x537/0x7b0 __sys_getsockopt+0xf2/0x170 Allocated by task 92: hci_codec_list_add.isra.0+0x2c/0x440 hci_read_codec_capabilities+0x224/0x590 hci_read_supported_codecs+0x2c2/0x640 Freed by task 92: kfree+0x131/0x3c0 hci_codec_list_clear+0xd8/0x160 hci_dev_close_sync+0x92a/0xfa0 Take hdev->lock around the clear operation at its existing point in the close path. This makes the clear wait for active readers and prevents a new traversal until the list is empty without changing teardown ordering. Fixes: b938790e7054 ("Bluetooth: hci_codec: Fix leaking content of local_codecs") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 2a651a4d60e6..74e2b04c84b2 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5673,7 +5673,9 @@ int hci_dev_close_sync(struct hci_dev *hdev) memset(hdev->eir, 0, sizeof(hdev->eir)); memset(hdev->dev_class, 0, sizeof(hdev->dev_class)); bacpy(&hdev->random_addr, BDADDR_ANY); + hci_dev_lock(hdev); hci_codec_list_clear(&hdev->local_codecs); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; From ca18ee413a7cb6f09885778039225e58bae0d607 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 10 Sep 2026 14:06:27 -0400 Subject: [PATCH 1098/1317] Bluetooth: ISO: Fix parent socket leak in iso_conn_ready() iso_get_sock() returns the parent socket with a reference held, which is dropped by sock_put() once the child socket has been set up. The error path taken when iso_sock_alloc() fails only calls release_sock() and returns, leaking the reference and thus the parent socket itself. Drop the reference on that path as well. Fixes: fa224d0c094a ("Bluetooth: ISO: Reassociate a socket with an active BIS") Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 75bfd5938b2e..4de332b8901f 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -2289,6 +2289,7 @@ static void iso_conn_ready(struct iso_conn *conn) BTPROTO_ISO, GFP_ATOMIC, 0); if (!sk) { release_sock(parent); + sock_put(parent); return; } From 296e7f3c5071cc02dc22e1566e759179fa1792ae Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 10 Sep 2026 14:07:24 -0400 Subject: [PATCH 1099/1317] Bluetooth: ISO: set BT_LISTEN before requesting a BIG sync A BIS connection is matched to its parent socket by looking for a socket in BT_LISTEN state with the same BIG handle: iso_conn_ready() if (test_bit(HCI_CONN_BIG_SYNC, &hcon->flags)) parent = iso_get_sock(hdev, &hcon->src, &hcon->dst, BT_LISTEN, iso_match_big_hcon, hcon); The socket was only moved to BT_LISTEN after iso_conn_big_sync() returned, while the LE BIG Create Sync command has already been queued by then. If the BIG sync is established before the state is updated, which is easy to hit with an emulated controller as the command may complete in a few hundred microseconds, no parent is found and the BIS connections are never notified to the listening socket. The user space is then left waiting for connections that never arrive, e.g. bluetoothd never completes a MediaTransport1.Acquire of a Broadcast Sink transport. Move the socket to BT_LISTEN before requesting the BIG sync, so the state is visible by the time the command is queued, and restore the previous state if the request could not be started. Since the socket is briefly visible as a listening socket, child sockets may have been queued in the meantime, so drain the accept queue before restoring the state: the cleanup paths of BT_CONNECT2/BT_CONNECTED don't do it and the children would be left with a dangling parent pointer. Fixes: fbdc4bc47268 ("Bluetooth: ISO: Use defer setup to separate PA sync and BIG sync") Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 52 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 4de332b8901f..eb99653f33f9 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -819,19 +819,24 @@ static void iso_sock_destruct(struct sock *sk) skb_queue_purge(&sk->sk_error_queue); } -static void iso_sock_cleanup_listen(struct sock *parent) +/* Close not yet accepted channels */ +static void iso_sock_flush_accept_q(struct sock *parent) { struct sock *sk; - BT_DBG("parent %p", parent); - - /* Close not yet accepted channels */ while ((sk = bt_accept_dequeue(parent, NULL))) { iso_sock_close(sk); iso_sock_kill(sk); /* Drop the reference handed back by bt_accept_dequeue(). */ sock_put(sk); } +} + +static void iso_sock_cleanup_listen(struct sock *parent) +{ + BT_DBG("parent %p", parent); + + iso_sock_flush_accept_q(parent); /* If listening socket has a hcon, properly disconnect it */ if (iso_pi(parent)->conn && iso_pi(parent)->conn->hcon) { @@ -1737,6 +1742,13 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, switch (sk->sk_state) { case BT_CONNECT2: if (test_bit(BT_SK_PA_SYNC, &pi->flags)) { + /* Move to BT_LISTEN before requesting the BIG + * sync: the BIS connections are matched to a + * parent socket in BT_LISTEN state, and they + * may be notified before the request returns. + */ + sk->sk_state = BT_LISTEN; + release_sock(sk); err = iso_conn_big_sync(sk); lock_sock(sk); @@ -1745,12 +1757,20 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, * connection may have been torn down * meanwhile and iso_chan_del() may have * already moved the socket to BT_CLOSED. - * Only move on to BT_LISTEN if the BIG sync - * was actually started and nothing else has - * changed the state. + * Only move back if the BIG sync could not be + * started and nothing else has changed the + * state. */ - if (!err && sk->sk_state == BT_CONNECT2) - sk->sk_state = BT_LISTEN; + if (err && sk->sk_state == BT_LISTEN) { + /* Discard any child socket that may + * have been queued while the socket + * was in BT_LISTEN, as the cleanup of + * BT_CONNECT2 doesn't drain the + * accept queue. + */ + iso_sock_flush_accept_q(sk); + sk->sk_state = BT_CONNECT2; + } } else { iso_conn_defer_accept(pi->conn->hcon); sk->sk_state = BT_CONFIG; @@ -1760,12 +1780,22 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, break; case BT_CONNECTED: if (test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) { + /* As above, the BIS connections may be + * notified before the request returns. + */ + sk->sk_state = BT_LISTEN; + release_sock(sk); err = iso_conn_big_sync(sk); lock_sock(sk); - if (!err && sk->sk_state == BT_CONNECTED) - sk->sk_state = BT_LISTEN; + if (err && sk->sk_state == BT_LISTEN) { + /* As above, don't leave any child + * socket behind in the accept queue. + */ + iso_sock_flush_accept_q(sk); + sk->sk_state = BT_CONNECTED; + } early_ret = true; } From 78b6abd6c7a7591aacdae657f813214dae4fcd3b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 14 Sep 2026 14:56:53 +0800 Subject: [PATCH 1100/1317] Bluetooth: btmtk: fix wrong status for short WMT FUNC_CTRL events A too-short BTMTK_WMT_FUNC_CTRL event (WMT header only, no trailing 2-byte status word) is always treated as BTMTK_WMT_ON_UNDONE. This short form is how firmware acks a plain enable/disable request, and the actual result is carried in the header's own flag byte (0 = success), not a separate status word. Decode it from there instead of assuming failure. Verified setup on MT7920, MT7921, MT7922 and MT7925: no regression. Fixes: e3ac0d9f1a20 ("Bluetooth: btmtk: accept too short WMT FUNC_CTRL events") Assisted-by: Claude:claude-opus-5 Signed-off-by: Chris Lu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 26d525acd659..7ea8bcd8a7ec 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -721,7 +721,12 @@ static int btmtk_usb_hci_wmt_sync(struct hci_dev *hdev, case BTMTK_WMT_FUNC_CTRL: if (!skb_pull_data(data->evt_skb, sizeof(wmt_evt_funcc->status))) { - status = BTMTK_WMT_ON_UNDONE; + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; break; } From 8879e3e0a84a86954c855caceead4867e74a9a27 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 14 Sep 2026 14:56:54 +0800 Subject: [PATCH 1101/1317] Bluetooth: btmtksdio, btmtkuart: validate WMT event length before struct access btmtksdio.c and btmtkuart.c cast a received WMT event straight to struct btmtk_hci_wmt_evt and read its op/flag fields without checking the event is long enough to contain them, unlike btmtk.c. The FUNC_CTRL case then further casts to struct btmtk_hci_wmt_evt_funcc and reads its 2-byte status field, again without a length check. Firmware that sends a short or malformed WMT event makes both drivers read past the end of the received SKB. Mirror btmtk.c: validate the base WMT header with skb_pull_data() before touching any of its fields, and when a FUNC_CTRL event turns out to be the short, header-only form (a plain enable/disable ack with no status word), decode the result from the header's own flag byte instead (0 = success, otherwise failure). Verified setup on MT7920, MT7921, MT7922 and MT7925: no regression. Fixes: 9aebfd4a2200 ("Bluetooth: mediatek: add support for MediaTek MT7663S and MT7668S SDIO devices") Fixes: e0b67035a90b ("Bluetooth: mediatek: update the common setup between MT7622 and other devices") Assisted-by: Claude:claude-opus-5 Signed-off-by: Chris Lu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 20 +++++++++++++++++++- drivers/bluetooth/btmtkuart.c | 20 +++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 94aa60d9cc20..7fab678925d1 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -217,7 +217,14 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, } /* Parse and handle the return WMT event */ - wmt_evt = (struct btmtk_hci_wmt_evt *)bdev->evt_skb->data; + wmt_evt = skb_pull_data(bdev->evt_skb, sizeof(*wmt_evt)); + if (!wmt_evt) { + bt_dev_err(hdev, "WMT event too short (%u bytes)", + bdev->evt_skb->len); + err = -EINVAL; + goto err_free_skb; + } + if (wmt_evt->whdr.op != hdr->op) { bt_dev_err(hdev, "Wrong op received %d expected %d", wmt_evt->whdr.op, hdr->op); @@ -233,6 +240,17 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, status = BTMTK_WMT_PATCH_DONE; break; case BTMTK_WMT_FUNC_CTRL: + if (!skb_pull_data(bdev->evt_skb, + sizeof(wmt_evt_funcc->status))) { + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; + break; + } + wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; if (be16_to_cpu(wmt_evt_funcc->status) == 0x404) status = BTMTK_WMT_ON_DONE; diff --git a/drivers/bluetooth/btmtkuart.c b/drivers/bluetooth/btmtkuart.c index 27aa48ff3ac2..4af6fbbbd302 100644 --- a/drivers/bluetooth/btmtkuart.c +++ b/drivers/bluetooth/btmtkuart.c @@ -151,7 +151,14 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, } /* Parse and handle the return WMT event */ - wmt_evt = (struct btmtk_hci_wmt_evt *)bdev->evt_skb->data; + wmt_evt = skb_pull_data(bdev->evt_skb, sizeof(*wmt_evt)); + if (!wmt_evt) { + bt_dev_err(hdev, "WMT event too short (%u bytes)", + bdev->evt_skb->len); + err = -EINVAL; + goto err_free_wc; + } + if (wmt_evt->whdr.op != hdr->op) { bt_dev_err(hdev, "Wrong op received %d expected %d", wmt_evt->whdr.op, hdr->op); @@ -167,6 +174,17 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, status = BTMTK_WMT_PATCH_DONE; break; case BTMTK_WMT_FUNC_CTRL: + if (!skb_pull_data(bdev->evt_skb, + sizeof(wmt_evt_funcc->status))) { + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; + break; + } + wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; if (be16_to_cpu(wmt_evt_funcc->status) == 0x404) status = BTMTK_WMT_ON_DONE; From 7b60ee5f46f2ee329de661f7c68b6818d8136220 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 14 Sep 2026 09:47:29 +0000 Subject: [PATCH 1102/1317] Bluetooth: btmtksdio: Fix PM runtime reference leak in shutdown In btmtksdio_shutdown(), pm_runtime_get_sync() is called at the beginning of the function. However, if sending the WMT function control command fails later, the driver returns early. It bypasses the corresponding pm_runtime_put_noidle() and pm_runtime_disable() calls, leaking the PM usage counter and leaving PM runtime enabled indefinitely. Fall through to execute the PM runtime cleanup block even if WMT errors. Fixes: 7f3c563c575e ("Bluetooth: btmtksdio: Add runtime PM support to SDIO based Bluetooth") Signed-off-by: Tzung-Bi Shih Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 7fab678925d1..a15ae6598c66 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -1262,10 +1262,8 @@ static int btmtksdio_shutdown(struct hci_dev *hdev) wmt_params.status = NULL; err = mtk_hci_wmt_sync(hdev, &wmt_params); - if (err < 0) { + if (err < 0) bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); - return err; - } ignore_wmt_cmd: pm_runtime_put_noidle(bdev->dev); From 2ea5a87a5a7ae58cb2662b8a7d06f209383e1765 Mon Sep 17 00:00:00 2001 From: Sai Teja Aluvala Date: Fri, 11 Sep 2026 17:22:22 +0530 Subject: [PATCH 1103/1317] Bluetooth: btintel_pcie: fix off-by-one bounds check in RX submit btintel_pcie_submit_rx() used frbd_index > rxq->count to guard the FRBD array access, allowing frbd_index == rxq->count to pass through and index one element past the end of the array. Change the check to >= rxq->count so every out-of-range index is rejected. This issue was reported by Claude Mythos. Fixes: c2b636b3f788 (Bluetooth: btintel_pcie: Add support for PCIe transport) Signed-off-by: Sai Teja Aluvala Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 405b23e21473..6e6e2b19815c 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -508,7 +508,7 @@ static int btintel_pcie_submit_rx(struct btintel_pcie_data *data) frbd_index = data->ia.tr_hia[BTINTEL_PCIE_RXQ_NUM]; - if (frbd_index > rxq->count) + if (frbd_index >= rxq->count) return -ERANGE; /* Prepare for RX submit. It updates the FRBD with the address of DMA From 555cd2bd860e7c4bdc3f4e4405b05515b0d9bc87 Mon Sep 17 00:00:00 2001 From: Radek Podgorny Date: Sun, 13 Sep 2026 22:28:02 +0200 Subject: [PATCH 1104/1317] Bluetooth: keep dst_type with dst when reusing an LE connection hci_connect_le() swaps the caller's identity address for the peer's cached RPA when one is known, and stamps the matching ADDR_LE_DEV_RANDOM on the local dst_type. On the conn-reuse path only the address is copied into the connection: if (conn) { bacpy(&conn->dst, dst); so conn->dst ends up holding an RPA while conn->dst_type still names the identity it was resolved from, and hci_le_create_conn_sync() puts that pair on air unchanged. An RPA declared as a public address is not something any peer can answer. Measured on a CYW43438 against a peer advertising an RPA the host holds the IRK for, connecting to the identity address over a raw L2CAP socket. The first attempt creates the connection, the second takes the reuse path: LE Create Connection 3C:78:95:78:37:C3 type public LE Create Connection 5B:75:A2:26:D6:18 type public LE Connection Complete: Unknown Connection Identifier (0x02) The second address is the peer's RPA. btmon annotates it with an OUI lookup rather than "(Resolvable)" precisely because the command declares it public; the same bit pattern annotates as resolvable once the type is right. The mistyped pair is also why nothing downstream repairs it. hci_bdaddr_is_rpa() tests the type before the address, so an RPA carrying a public type is not recognised as one, and hci_find_irk_by_addr() then searches for an identity address that does not match it either. Copy the type along with the address. The assignment used to be unconditional just below this block and covered both paths; it moved into hci_conn_add_unset(), which the reuse path does not go through. Cc: stable@vger.kernel.org Fixes: 14b06c3a88f7 ("Bluetooth: HCI: Always use the identity address when initializing a connection") Assisted-by: Claude:claude-opus-5 Signed-off-by: Radek Podgorny Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index c9466cb2c7c0..fa72cf8aaa7a 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1518,7 +1518,15 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst, } if (conn) { + /* dst may just have been swapped for the peer's RPA above, and + * dst_type describes dst -- it has to travel with it. Leaving + * the identity type behind makes the pair describe a peer that + * does not exist, and nothing downstream repairs it: + * hci_bdaddr_is_rpa() tests the type before the address, so + * the RPA is never treated as one. + */ bacpy(&conn->dst, dst); + conn->dst_type = dst_type; } else { conn = hci_conn_add_unset(hdev, LE_LINK, dst, dst_type, role); if (IS_ERR(conn)) From 801fb950cae7048eb7d83b18857d1ca37b8cd5a4 Mon Sep 17 00:00:00 2001 From: Juan Perdomo Date: Sat, 12 Sep 2026 23:09:45 -0400 Subject: [PATCH 1105/1317] Bluetooth: RFCOMM: avoid socket lock inversion in listener cleanup rfcomm_sock_cleanup_listen() closes unaccepted child sockets through rfcomm_sock_close(), which takes the child socket lock before rfcomm_dlc_close() acquires rfcomm_mutex. The RFCOMM worker takes these locks in reverse order while handling connections and DLC state changes, so lockdep reports a possible deadlock. Close dequeued children without taking their socket lock. The accept queue owns a reference to each child, and bt_accept_dequeue() locks the child while unlinking it and clearing its parent pointer. Dropping the child lock makes it important to prevent a concurrent rfcomm_connect_ind() from enqueueing a new child after cleanup observes an empty queue. Set a listening socket to BT_CLOSED while its lock is still held, before dropping the lock and draining the queue. The state check in rfcomm_connect_ind() then rejects new children once cleanup starts. Reported-by: syzbot+0cece8fa7d83523f47a3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0cece8fa7d83523f47a3 Fixes: b7ce436a5d79 ("Bluetooth: switch to lock_sock in RFCOMM") Signed-off-by: Juan Perdomo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index 958081adb9b5..e2486bc11cbc 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -242,9 +242,7 @@ static void __rfcomm_sock_close(struct sock *sk) */ static void rfcomm_sock_close(struct sock *sk) { - lock_sock(sk); __rfcomm_sock_close(sk); - release_sock(sk); } static void rfcomm_sock_init(struct sock *sk, struct sock *parent) @@ -905,6 +903,7 @@ static int rfcomm_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsig static int rfcomm_sock_shutdown(struct socket *sock, int how) { struct sock *sk = sock->sk; + bool cleanup_listen = false; int err = 0; BT_DBG("sock %p, sk %p", sock, sk); @@ -915,9 +914,17 @@ static int rfcomm_sock_shutdown(struct socket *sock, int how) lock_sock(sk); if (!sk->sk_shutdown) { sk->sk_shutdown = SHUTDOWN_MASK; + if (sk->sk_state == BT_LISTEN) { + /* Block new children before cleaning up without sk lock. */ + sk->sk_state = BT_CLOSED; + cleanup_listen = true; + } release_sock(sk); - __rfcomm_sock_close(sk); + if (cleanup_listen) + rfcomm_sock_cleanup_listen(sk); + else + __rfcomm_sock_close(sk); lock_sock(sk); if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime && From 8861db305103107199b1426f25fde1fb6d465583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 3 Sep 2026 13:43:38 +0200 Subject: [PATCH 1106/1317] selinux: always fill AVC decision in avc_has_perm_noaudit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avc_has_perm_noaudit() is documented to return a copy of the access decision in @avd, but its early return for an empty requested permission set leaves the buffer untouched. All callers pass an uninitialized stack variable and afterwards feed it to avc_audit(), and the inode hook even stores it in the per-task decision cache. Fill in a deny-all, audit-all decision, similar to avd_init(), so every caller receives a defined value at no cost on the hot path. Cc: stable@vger.kernel.org Fixes: e6f2f381e4015386 ("selinux: replace BUG_ONs with WARN_ONs in avc.c") Signed-off-by: Christian Göttsche Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/avc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index a9401d6c2e5f..560a82c6d682 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -1149,8 +1149,11 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid, u32 denied; struct avc_node *node; - if (WARN_ON(!requested)) + if (WARN_ON(!requested)) { + /* Provide a deny-all, audit-all decision to the caller. */ + *avd = (struct av_decision){ .auditdeny = 0xffffffff }; return -EACCES; + } rcu_read_lock(); node = avc_lookup(ssid, tsid, tclass); From 4a4263dfeabad72f95e8ab6e15146861fa4144dd Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 03:07:51 +0000 Subject: [PATCH 1107/1317] af_unix: Unify scc_index when finalising SCC in __unix_walk_scc(). Commit bfdb01283ee8 ("af_unix: Assign a unique index to SCC.") changed Tarjan's algorithm to update lowlink with lowlink, which is called lowpoint (unix_vertex.scc_index). unix_vertex_dead() assumes all vertices in an SCC share the same lowpoint, but this is not always true if an SCC has two or more back edges, depending on the order of DFS. For example, the graph below has two back edges from B to A and from C to B. A --> B --> C ^ | ^ | `----' `----' If DFS walks through A -> B -> C -> B (-> C -> B) -> A (-> B -> A), each index and scc_index will be updated as follows. A --> B --> C C = (3, 3) (index, scc_index) B = (2, 2) A = (1, 1) A ... B ... C C = (3, 2)<-. ^ | B = (2, 2) -' `----' A = (1, 1) A ... B ... C C = (3, 2) ^ | . . B = (2, 1)<-. `----' .... A = (1, 1) -' Then, unix_vertex_dead() thinks that B is passed to another SCC with scc_index 2, and the SCC is not garbage-collected. This does not happen if DFS walks in a different order below or starts from B. 1 3 A --> B --> C ^ | ^ | `----' `----' 2 4 Let's unify scc_index across the SCC when finalising it. Note that updating v->index was previously done in unix_scc_dead(), when called from __unix_walk_scc(), just to save one loop. Since __unix_walk_scc() now iterates over the SCC anyway, the update is moved back to __unix_walk_scc() and 'fast' argument is dropped. Fixes: 4090fa373f0e ("af_unix: Replace garbage collection algorithm.") Reported-by: James Burton Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912030852.1467872-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/unix/garbage.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/net/unix/garbage.c b/net/unix/garbage.c index 9fcaaf55cba5..da774f56ca64 100644 --- a/net/unix/garbage.c +++ b/net/unix/garbage.c @@ -374,7 +374,7 @@ static bool unix_vertex_dead(struct unix_vertex *vertex) static LIST_HEAD(unix_visited_vertices); static unsigned long unix_vertex_grouped_index = UNIX_VERTEX_INDEX_MARK2; -static bool unix_scc_dead(struct list_head *scc, bool fast) +static bool unix_scc_dead(struct list_head *scc) { struct unix_vertex *vertex; bool scc_dead = true; @@ -386,10 +386,6 @@ static bool unix_scc_dead(struct list_head *scc, bool fast) /* Don't restart DFS from this vertex. */ list_move_tail(&vertex->entry, &unix_visited_vertices); - /* Mark vertex as off-stack for __unix_walk_scc(). */ - if (!fast) - vertex->index = unix_vertex_grouped_index; - if (scc_dead) scc_dead = unix_vertex_dead(vertex); } @@ -521,6 +517,7 @@ static unsigned long __unix_walk_scc(struct unix_vertex *vertex, } if (vertex->index == vertex->scc_index) { + struct unix_vertex *v; struct list_head scc; /* SCC finalised. @@ -530,7 +527,13 @@ static unsigned long __unix_walk_scc(struct unix_vertex *vertex, */ __list_cut_position(&scc, &vertex_stack, &vertex->scc_entry); - if (unix_scc_dead(&scc, false)) { + list_for_each_entry_reverse(v, &scc, scc_entry) { + /* Mark vertex as off-stack and assign a unique ID. */ + v->index = unix_vertex_grouped_index; + v->scc_index = vertex->scc_index; + } + + if (unix_scc_dead(&scc)) { unix_collect_skb(&scc, hitlist); } else { if (unix_vertex_max_scc_index < vertex->scc_index) @@ -588,7 +591,7 @@ static void unix_walk_scc_fast(struct sk_buff_head *hitlist) vertex = list_first_entry(&unix_unvisited_vertices, typeof(*vertex), entry); list_add(&scc, &vertex->scc_entry); - if (unix_scc_dead(&scc, true)) { + if (unix_scc_dead(&scc)) { cyclic_sccs--; unix_collect_skb(&scc, hitlist); } From b645ccd410547d0e0e4a9543f828119e24dc7635 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 03:07:52 +0000 Subject: [PATCH 1108/1317] selftest: af_unix: Add test case with mixed lowpoint in scm_rights.c. The new test case creates two SCCs so that each of them has multiple scc_index. Without patch, GC cannot free the sockets and the test fails. # RUN scm_rights.dgram.mixed_lowpoints ... # scm_rights.c:176:mixed_lowpoints:Expected 0 (0) == ret (12) # mixed_lowpoints: Test terminated by assertion # FAIL scm_rights.dgram.mixed_lowpoints not ok 5 scm_rights.dgram.mixed_lowpoints ... # FAILED: 45 / 50 tests passed. # Totals: pass:45 fail:5 xfail:0 xpass:0 skip:0 error:0 With the patch, all tests pass. # PASSED: 50 / 50 tests passed. # Totals: pass:50 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912030852.1467872-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/af_unix/scm_rights.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/testing/selftests/net/af_unix/scm_rights.c b/tools/testing/selftests/net/af_unix/scm_rights.c index d82a79c21c17..c165f250220a 100644 --- a/tools/testing/selftests/net/af_unix/scm_rights.c +++ b/tools/testing/selftests/net/af_unix/scm_rights.c @@ -378,4 +378,21 @@ TEST_F(scm_rights, backtrack_from_scc) close_sockets(10); } +TEST_F(scm_rights, mixed_lowpoint) +{ + create_sockets(6); + + send_fd(0, 1); + send_fd(1, 2); + send_fd(2, 1); + send_fd(1, 0); + + send_fd(3, 4); + send_fd(4, 5); + send_fd(5, 4); + send_fd(4, 3); + + close_sockets(6); +} + TEST_HARNESS_MAIN From 15989abd74f16f44bf953d056b95f1d2fda9b0cd Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 11:20:15 +0200 Subject: [PATCH 1109/1317] net: stmmac: fix TSO header length truncation stmmac_tso_xmit() stores the protocol header length returned by stmmac_tso_header_size() in a u8. stmmac_tso_valid_packet() admits headers up to 1023 bytes, so a header longer than 255 bytes wraps modulo 256 (486 becomes 230, 256 becomes 0). A TCP over IPv6 socket carrying a few hundred bytes of sticky destination/hop-by-hop options makes skb_tcp_all_headers() exceed 255 while staying below the 1023-byte limit, so such an skb reaches stmmac_tso_xmit(). Widen proto_hdr_len to unsigned int, which is sufficient since the value is bounded by the hardware limit, and adjust the debug print specifier accordingly. Fixes: 9edfa7dab811 ("net: stmmac: enable TSO for IPv6") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260911-stmmac-fix-header-length-v1-1-8fc103334327@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 62c3441911e7..1fb5f804ea23 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4513,16 +4513,16 @@ static int stmmac_tso_get_num_desc(struct stmmac_tx_queue *tx_q, */ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) { + unsigned int first_entry, entry, tx_packets, proto_hdr_len; struct dma_desc *desc, *first, *mss_desc = NULL; struct stmmac_priv *priv = netdev_priv(dev); - unsigned int first_entry, entry, tx_packets; struct stmmac_txq_stats *txq_stats; int i, first_tx, nfrags, ndesc; struct stmmac_tx_queue *tx_q; bool set_ic, is_last_segment; u32 pay_len, mss, queue; - u8 proto_hdr_len, hdr; dma_addr_t des; + u8 hdr; nfrags = skb_shinfo(skb)->nr_frags; queue = skb_get_queue_mapping(skb); @@ -4570,7 +4570,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) } if (netif_msg_tx_queued(priv)) { - pr_info("%s: hdrlen %d, hdr_len %d, pay_len %d, mss %d\n", + pr_info("%s: hdrlen %d, hdr_len %u, pay_len %d, mss %d\n", __func__, hdr, proto_hdr_len, pay_len, mss); pr_info("\tskb->len %d, skb->data_len %d\n", skb->len, skb->data_len); From 6e05e46fa821a5c1b281355f1f622ac76cb6080a Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 10 Sep 2026 17:34:12 +0800 Subject: [PATCH 1110/1317] net/sched: act_api: release tail references on DELACTION failure A batched RTM_DELACTION request takes a temporary reference on each action before attempting any deletion. tcf_action_delete() clears each processed slot and drops its temporary reference before attempting the deletion. If deletion fails, tca_action_gd() calls tcf_action_put_many() to release the remaining references, but its tcf_act_for_each_action() iterator stops at the first NULL slot. When a batch stops at an action bound to a filter, this leaks a reference on each subsequent action. A later delete of an unbound action can then return success without removing it from the IDR. Walk the full array in tcf_action_put_many() and skip NULL slots to release the references held on the unprocessed actions. Fixes: a0e947c9ccff ("net/sched: act_api: avoid non-contiguous action array") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260910093413.34509-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 19501dc99464..3f653721c45f 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1218,11 +1218,16 @@ static int tcf_action_put(struct tc_action *p) static void tcf_action_put_many(struct tc_action *actions[]) { - struct tc_action *a; int i; - tcf_act_for_each_action(i, a, actions) { - const struct tc_action_ops *ops = a->ops; + /* Deletion may have cleared entries before failing. */ + for (i = 0; i < TCA_ACT_MAX_PRIO; i++) { + struct tc_action *a = actions[i]; + const struct tc_action_ops *ops; + + if (!a) + continue; + ops = a->ops; if (tcf_action_put(a)) module_put(ops->owner); } From 14c5eb685cdefbd32e73d2723071ecbd8effbce9 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 10 Sep 2026 17:34:13 +0800 Subject: [PATCH 1111/1317] selftests: tc-testing: test action batch deletion failure cleanup Add tests for cleanup after a batched RTM_DELACTION request fails at a gact action bound to a filter. Check that subsequent actions retain their original reference counts and that earlier successful deletions are preserved. Cover failures at the first and middle entries. Verify that a remaining unbound action can be removed with one subsequent delete. Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260910093413.34509-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- .../tc-tests/actions/batch-delete.json | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json b/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json new file mode 100644 index 000000000000..ef7ca4a6775b --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json @@ -0,0 +1,115 @@ +[ + { + "id": "d710", + "name": "Release tail references after first action deletion fails", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC actions add action pass index 3", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 1" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2 action gact index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 3\\b.*index 1 ref 2 bind 1\\b.*index 2 ref 1 bind 0\\b.*index 3 ref 1 bind 0\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + }, + { + "id": "d711", + "name": "Release tail references after middle action deletion fails", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC actions add action pass index 3", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 2" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2 action gact index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 2\\b.*index 2 ref 2 bind 1\\b.*index 3 ref 1 bind 0\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + }, + { + "id": "d713", + "name": "Delete a tail action once after a failed batch", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 1" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2", + "expExitCode": "255", + "verifyCmd": "sh -c '$TC actions del action gact index 2 && $TC actions ls action gact'", + "matchPattern": "total acts 1\\b.*index 1 ref 2 bind 1\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + } +] From ecc7253683a3c55caa868ce0ee530fcb0044bd3c Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 12 Sep 2026 23:30:48 +0000 Subject: [PATCH 1112/1317] pppoatm: ensure a writable skb header and linear data In pppoatm_send(), LLC encapsulation checks whether there is sufficient headroom for the 4-byte LLC header, but does not ensure that the skb header is writable. Normal transmit packets passing through ppp_start_xmit() have their header unshared via skb_cow_head(). However, packets can also reach pppoatm_send() via PPP channel bridging (PPPIOCBRIDGECHAN) without going through ppp_start_xmit(). Use skb_cow_head() to ensure both sufficient headroom and a writable header before pushing the LLC header. While at it: - Call pskb_may_pull(skb, 1) before inspecting skb->data[0] to prevent out-of-bounds reads on zero-length or non-linear frames (e.g. from bridging). - Defer SC_COMP_PROT protocol compression until after pppoatm_may_send() succeeds. This eliminates the temporary skb allocation on admission failure and completely removes the fragile "undo" heuristic at the nospace label, avoiding any risk of reading uninitialized headroom or performing an unbalanced skb_push(). Fixes: 4cf476ced45d ("ppp: add PPPIOCBRIDGECHAN and PPPIOCUNBRIDGECHAN ioctls") Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912233048.3977192-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/atm/pppoatm.c | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/net/atm/pppoatm.c b/net/atm/pppoatm.c index 6da52d12df68..5214786e61d1 100644 --- a/net/atm/pppoatm.c +++ b/net/atm/pppoatm.c @@ -292,10 +292,13 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) struct atm_vcc *vcc; int ret; + if (!pskb_may_pull(skb, 1)) { + kfree_skb(skb); + return DROP_PACKET; + } + ATM_SKB(skb)->vcc = pvcc->atmvcc; pr_debug("(skb=0x%p, vcc=0x%p)\n", skb, pvcc->atmvcc); - if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT)) - (void) skb_pull(skb, 1); vcc = ATM_SKB(skb)->vcc; bh_lock_sock(sk_atm(vcc)); @@ -317,23 +320,13 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) switch (pvcc->encaps) { /* LLC encapsulation needed */ case e_llc: - if (skb_headroom(skb) < LLC_LEN) { - struct sk_buff *n; - n = skb_realloc_headroom(skb, LLC_LEN); - if (n != NULL && - !pppoatm_may_send(pvcc, n->truesize)) { - kfree_skb(n); - goto nospace; - } - consume_skb(skb); - skb = n; - if (skb == NULL) { - bh_unlock_sock(sk_atm(vcc)); - return DROP_PACKET; - } - } else if (!pppoatm_may_send(pvcc, skb->truesize)) + if (skb_cow_head(skb, LLC_LEN)) { + bh_unlock_sock(sk_atm(vcc)); + kfree_skb(skb); + return DROP_PACKET; + } + if (!pppoatm_may_send(pvcc, skb->truesize)) goto nospace; - memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN); break; case e_vc: if (!pppoatm_may_send(pvcc, skb->truesize)) @@ -346,6 +339,12 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) return 1; } + if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT)) + skb_pull(skb, 1); + + if (pvcc->encaps == e_llc) + memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN); + atm_account_tx(vcc, skb); pr_debug("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, ATM_SKB(skb)->vcc, ATM_SKB(skb)->vcc->dev); @@ -355,13 +354,6 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) return ret; nospace: bh_unlock_sock(sk_atm(vcc)); - /* - * We don't have space to send this SKB now, but we might have - * already applied SC_COMP_PROT compression, so may need to undo - */ - if ((pvcc->flags & SC_COMP_PROT) && skb_headroom(skb) > 0 && - skb->data[-1] == '\0') - (void) skb_push(skb, 1); return 0; } From 455ebeadf714f51e1dbbd6a022c74c9215b1cd76 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Sun, 13 Sep 2026 17:08:50 +0800 Subject: [PATCH 1113/1317] net: ip_tunnel: initialize `options_len` before referencing options The following command triggers a kernel panic: ip link add d0 type dummy; ip link set d0 up ip route add 10.30.0.0/16 \ encap ip id 300 geneve_opts 4660:66:11223344 dev d0 memcpy: detected buffer overflow: 4 byte write of buffer size 0 kernel BUG at lib/string_helpers.c:1044! ... ip_tun_parse_opts.part.0.cold+0x10/0x10 ip_tun_build_state+0x116/0x2a0 On kernels built with GCC 15+ and `CONFIG_FORTIFY_SOURCE`, the fortified `memcpy()` got 0 sized destination with request of 4 bytes length: static int ip_tun_parse_opts_geneve(...) { ... attr = tb[LWTUNNEL_IP_OPT_GENEVE_DATA]; data_len = nla_len(attr); /* == 4 */ struct geneve_opt *opt = ip_tunnel_info_opts(info) + opts_len; memcpy(opt->opt_data, nla_data(attr), data_len); /* ^^^^^^^^^^^^^ 0 since options_len is assigned afterwards */ Fixed by initializing the counter before the options are referenced. Matching what `tunnel_key_opts_set()` already does. Fixes: bb5e62f2d547 ("net: Add options as a flexible array to struct ip_tunnel_info") Cc: stable@vger.kernel.org Signed-off-by: Gris Ge Reviewed-by: Hangbin Liu Reviewed-by: Gustavo A. R. Silva Link: https://patch.msgid.link/20260913090851.468216-1-cnfourt@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_tunnel_core.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c index 5168d546ea2f..bab42b9e277f 100644 --- a/net/ipv4/ip_tunnel_core.c +++ b/net/ipv4/ip_tunnel_core.c @@ -680,8 +680,14 @@ static int ip_tun_get_optlen(struct nlattr *attr, } static int ip_tun_set_opts(struct nlattr *attr, struct ip_tunnel_info *info, - struct netlink_ext_ack *extack) + int opts_len, struct netlink_ext_ack *extack) { + /* `options_len` is the __counted_by() annotation of the `options` + * flexible array, it must be initialized before parsing writes + * into it. + */ + info->options_len = opts_len; + return ip_tun_parse_opts(attr, info, extack); } @@ -712,7 +718,8 @@ static int ip_tun_build_state(struct net *net, struct nlattr *attr, tun_info = lwt_tun_info(new_state); - err = ip_tun_set_opts(tb[LWTUNNEL_IP_OPTS], tun_info, extack); + err = ip_tun_set_opts(tb[LWTUNNEL_IP_OPTS], tun_info, opt_len, + extack); if (err < 0) { lwtstate_free(new_state); return err; @@ -753,7 +760,6 @@ static int ip_tun_build_state(struct net *net, struct nlattr *attr, } tun_info->mode = IP_TUNNEL_INFO_TX; - tun_info->options_len = opt_len; *ts = new_state; @@ -1006,7 +1012,8 @@ static int ip6_tun_build_state(struct net *net, struct nlattr *attr, tun_info = lwt_tun_info(new_state); - err = ip_tun_set_opts(tb[LWTUNNEL_IP6_OPTS], tun_info, extack); + err = ip_tun_set_opts(tb[LWTUNNEL_IP6_OPTS], tun_info, opt_len, + extack); if (err < 0) { lwtstate_free(new_state); return err; @@ -1040,7 +1047,6 @@ static int ip6_tun_build_state(struct net *net, struct nlattr *attr, } tun_info->mode = IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_IPV6; - tun_info->options_len = opt_len; *ts = new_state; From 6a038ef2b57922b6d9ca98ddac0df0681849b704 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:09 +0000 Subject: [PATCH 1114/1317] drop_monitor: synchronize tracepoint unregistration on error path If register_trace_napi_poll() fails in net_dm_trace_on_set(), unregister_trace_kfree_skb() is called to roll back the kfree_skb tracepoint registration. However, tracepoint_synchronize_unregister() is omitted before calling cancel_work_sync() and module_put(). An in-flight probe executing concurrently on another CPU could call schedule_work() after cancel_work_sync() has already returned, leaving a pending work item scheduled after the module reference is dropped. If the module is then unloaded, executing the work item triggers a kernel panic. Add tracepoint_synchronize_unregister() after unregister_trace_kfree_skb() in the error path, matching net_dm_trace_off_set() and net_dm_hw_probe_unregister(). Fixes: 7c747838a558 ("drop_monitor: Split tracing enable / disable to different functions") Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260910204612.3762015-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index abaf108ac4db..018d19e3a71d 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -1173,6 +1173,7 @@ static int net_dm_trace_on_set(struct netlink_ext_ack *extack) err_unregister_trace: unregister_trace_kfree_skb(ops->kfree_skb_probe, NULL); + tracepoint_synchronize_unregister(); err_module_put: for_each_possible_cpu(cpu) { struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); From c391a40f71886b28c082b47270f0e856fa3e1150 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:10 +0000 Subject: [PATCH 1115/1317] drop_monitor: use timer_shutdown_sync() to prevent timer rearming during teardown In drop_monitor teardown paths (net_dm_trace_off_set(), net_dm_hw_monitor_stop(), and error unwind paths in net_dm_trace_on_set() and net_dm_hw_monitor_start()), per-CPU timers are stopped using timer_delete_sync() followed by cancel_work_sync(). However, there is a circular dependency between send_timer and dm_alert_work: 1) sched_send_work() (timer callback) schedules dm_alert_work. 2) send_dm_alert() / net_dm_hw_summary_work() calls reset_per_cpu_data() or net_dm_hw_reset_per_cpu_data(). 3) If memory allocation fails under memory pressure in the reset function, it re-arms the timer via mod_timer(&data->send_timer, ...). If dm_alert_work is running concurrently while timer_delete_sync() executes on another CPU, an allocation failure in the worker will re-arm the timer after timer_delete_sync() has already returned. Once cancel_work_sync() completes and module_put() is called, the timer remains active in the timer wheel. If the module is then unloaded, the timer will fire and execute sched_send_work() in freed memory, triggering a kernel panic / use-after-free. Switch from timer_delete_sync() to timer_shutdown_sync(). This guarantees that any in-flight timer handler has finished and prevents subsequent re-arming attempts from running workers from succeeding. When monitoring is restarted later, timer_setup() is invoked, which cleanly re-initializes the timer. Fixes: 9398e9c0b1d4 ("drop_monitor: Perform cleanup upon probe registration failure") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260910204612.3762015-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 018d19e3a71d..873155ca7243 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -1083,7 +1083,7 @@ static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack) struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&hw_data->send_timer); + timer_shutdown_sync(&hw_data->send_timer); cancel_work_sync(&hw_data->dm_alert_work); while ((skb = __skb_dequeue(&hw_data->drop_queue))) { struct devlink_trap_metadata *hw_metadata; @@ -1117,7 +1117,7 @@ static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack) struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&hw_data->send_timer); + timer_shutdown_sync(&hw_data->send_timer); cancel_work_sync(&hw_data->dm_alert_work); while ((skb = __skb_dequeue(&hw_data->drop_queue))) { struct devlink_trap_metadata *hw_metadata; @@ -1179,7 +1179,7 @@ static int net_dm_trace_on_set(struct netlink_ext_ack *extack) struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&data->send_timer); + timer_shutdown_sync(&data->send_timer); cancel_work_sync(&data->dm_alert_work); while ((skb = __skb_dequeue(&data->drop_queue))) consume_skb(skb); @@ -1207,7 +1207,7 @@ static void net_dm_trace_off_set(void) struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&data->send_timer); + timer_shutdown_sync(&data->send_timer); cancel_work_sync(&data->dm_alert_work); while ((skb = __skb_dequeue(&data->drop_queue))) consume_skb(skb); From c19b7d35086b7d240f1ca3088b0079d2bd39ffb9 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:11 +0000 Subject: [PATCH 1116/1317] drop_monitor: use raw_cpu_ptr() in tracepoint probes syzbot reported a preemption warning in sk_skb_reason_drop(): BUG: using smp_processor_id() in preemptible [00000000] code: syz.0.17/5917 caller is net_dm_packet_trace_kfree_skb_hit+0x119/0x350 net/core/drop_monitor.c:519 In net_dm_packet_trace_kfree_skb_hit(), data = this_cpu_ptr(&dm_cpu_data) is evaluated before spin_lock_irqsave(&data->drop_queue.lock, flags). When kfree_skb() is called from preemptible context (e.g. process context during close() on /dev/net/tun), preemption is enabled, triggering the CONFIG_DEBUG_PREEMPT warning in smp_processor_id(). The same pattern exists in net_dm_hw_trap_summary_probe() and net_dm_hw_trap_packet_probe() for dm_hw_cpu_data. This is a false positive because each per-cpu structure is protected by its own spinlock. If the task migrates to another CPU right after reading the per-cpu pointer, the lock still safely synchronizes access to that queue. Use raw_cpu_ptr() instead of this_cpu_ptr() to silence CONFIG_DEBUG_PREEMPT without disturbing interrupt state or breaking PREEMPT_RT locking semantics. Fixes: ca30707dee2b ("drop_monitor: Add packet alert mode") Fixes: 5855357cd40e ("drop_monitor: Prepare probe functions for devlink tracepoint") Reported-by: syzbot+dc57fd6722deb17e92af@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6aa316b2.f81106d8.2ab401.0014.GAE@google.com/ Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260910204612.3762015-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 873155ca7243..795c15dd1771 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -448,7 +448,7 @@ net_dm_hw_trap_summary_probe(void *ignore, const struct devlink *devlink, if (metadata->trap_type == DEVLINK_TRAP_TYPE_CONTROL) return; - hw_data = this_cpu_ptr(&dm_hw_cpu_data); + hw_data = raw_cpu_ptr(&dm_hw_cpu_data); raw_spin_lock_irqsave(&hw_data->lock, flags); hw_entries = hw_data->hw_entries; @@ -516,7 +516,7 @@ static void net_dm_packet_trace_kfree_skb_hit(void *ignore, */ nskb->tstamp = tstamp; - data = this_cpu_ptr(&dm_cpu_data); + data = raw_cpu_ptr(&dm_cpu_data); spin_lock_irqsave(&data->drop_queue.lock, flags); if (skb_queue_len(&data->drop_queue) < net_dm_queue_len) @@ -983,7 +983,7 @@ net_dm_hw_trap_packet_probe(void *ignore, const struct devlink *devlink, NET_DM_SKB_CB(nskb)->hw_metadata = n_hw_metadata; nskb->tstamp = tstamp; - hw_data = this_cpu_ptr(&dm_hw_cpu_data); + hw_data = raw_cpu_ptr(&dm_hw_cpu_data); spin_lock_irqsave(&hw_data->drop_queue.lock, flags); if (skb_queue_len(&hw_data->drop_queue) < net_dm_queue_len) From 439f392084f8f7f59ab9d47a9579185accefe1d8 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:12 +0000 Subject: [PATCH 1117/1317] drop_monitor: fix out-of-bounds write in reset_per_cpu_data() In reset_per_cpu_data(), al is computed as: al = sizeof(struct net_dm_alert_msg); al += dm_hit_limit * sizeof(struct net_dm_drop_point); al += sizeof(struct nlattr); skb = genlmsg_new(al, GFP_KERNEL); ... nla = nla_reserve(skb, NLA_UNSPEC, sizeof(struct net_dm_alert_msg)); ... msg = nla_data(nla); memset(msg, 0, al); Because al includes sizeof(struct nlattr) (the 4-byte attribute header), genlmsg_new() allocates al bytes of tailroom starting at nla. However, msg points to nla_data(nla), which is located sizeof(struct nlattr) bytes past nla. Calling memset(msg, 0, al) therefore writes al bytes starting from msg, exceeding the allocated buffer by sizeof(struct nlattr) (4 bytes) and corrupting skb_shared_info. Fix this by letting al represent only the payload length, allocating the skb with genlmsg_new(nla_total_size(al), GFP_KERNEL), and zeroing al bytes from msg. Fixes: 683703a26e46 ("drop_monitor: Update netlink protocol to include netlink attribute header in alert message") Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260910204612.3762015-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 795c15dd1771..edc660778408 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -141,9 +141,8 @@ static struct sk_buff *reset_per_cpu_data(struct per_cpu_dm_data *data) al = sizeof(struct net_dm_alert_msg); al += dm_hit_limit * sizeof(struct net_dm_drop_point); - al += sizeof(struct nlattr); - skb = genlmsg_new(al, GFP_KERNEL); + skb = genlmsg_new(nla_total_size(al), GFP_KERNEL); if (!skb) goto err; From 3f118c8217c109fd13ca61caa301d72c483897ef Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 12 Sep 2026 21:22:43 +0800 Subject: [PATCH 1118/1317] openvswitch: avoid reallocating confirmed conntrack labels ovs_ct_get_conn_labels() adds the labels extension when a conntrack entry does not have one. Confirmed conntracks can be read locklessly, so adding an extension may reallocate and free the extension block while another CPU accesses it. Only add the extension for unconfirmed conntracks. A confirmed conntrack without labels now fails the caller's label operation instead of reallocating its extension storage. Fixes: c2ac66735870 ("openvswitch: Allow matching on conntrack label") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/372fbb062b40ae6723684f55484be86ff0064f8e.1789218015.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 27115967e5d9..0f433688e17b 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -366,7 +366,7 @@ static struct nf_conn_labels *ovs_ct_get_conn_labels(struct nf_conn *ct) struct nf_conn_labels *cl; cl = nf_ct_labels_find(ct); - if (!cl) { + if (!cl && !nf_ct_is_confirmed(ct)) { nf_ct_labels_ext_add(ct); cl = nf_ct_labels_find(ct); } From f0ef4b1eaed000a304726a43091588e8426ba08a Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 14 Sep 2026 09:41:07 +0200 Subject: [PATCH 1119/1317] net: stmmac: do not overwrite phc_index when no PTP clock is registered stmmac_get_ts_info() reports phc_index as 0 when hardware timestamping is supported but no PTP clock has been registered yet (e.g. while the interface is down). Zero is a valid PHC index and would make userspace resolve the wrong clock; the absence of a clock should be reported as -1. The ethtool core already initializes phc_index to -1 before invoking the get_ts_info callback (ethtool_init_tsinfo()), so just drop the erroneous assignment. Fixes: 9364fa7fcf12 ("net: stmmac: Remove setting of RX software timestamp") Reviewed-by: Maxime Chevallier Reviewed-by: Rahul Rameshbabu Signed-off-by: Lorenzo Bianconi Reviewed-by: Gal Pressman Link: https://patch.msgid.link/20260914-stmmac-fix-phc_index-v2-1-bf3d90373fe4@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 154cc0c7623d..1be5310ca766 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -1016,8 +1016,6 @@ static int stmmac_get_ts_info(struct net_device *dev, if (priv->ptp_clock) info->phc_index = ptp_clock_index(priv->ptp_clock); - else - info->phc_index = 0; info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON); From 2842ce397dd09882530b42f7fdb0c855767eb24e Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Mon, 14 Sep 2026 13:52:58 +0300 Subject: [PATCH 1120/1317] net: bridge: vlan: fix bugs caused by switchdev deletion errors Allowing switchdev to prevent vlan deletion and error out in __vlan_del could cause multiple different issues - inconsistent state, memory leaks when flushing, NULL pointer dereference on bridge error when flushing. It doesn't make sense to allow it to stop __vlan_del, so log the error and continue with software vlan deletion. This is also consistent with 8021q behaviour. Suggested-by: Ido Schimmel Fixes: bf361ad38165 ("net: bridge: check __vlan_vid_del for error") Fixes: 5454f5c28eca ("net: bridge: vlan: check for errors from __vlan_del in __vlan_flush") Fixes: 2594e9064a57 ("bridge: vlan: add per-vlan struct and move to rhashtables") Fixes: 9c86ce2c1ae3 ("net: bridge: Notify about bridge VLANs") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260914105258.3436918-1-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_vlan.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c index 1e0e436629ec..92b3cb621a26 100644 --- a/net/bridge/br_vlan.c +++ b/net/bridge/br_vlan.c @@ -387,12 +387,12 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags, goto out; } -static int __vlan_del(struct net_bridge_vlan *v) +static void __vlan_del(struct net_bridge_vlan *v) { struct net_bridge_vlan *masterv = v; struct net_bridge_vlan_group *vg; struct net_bridge_port *p = NULL; - int err = 0; + int err; if (br_vlan_is_master(v)) { vg = br_vlan_group(v->br); @@ -406,12 +406,16 @@ static int __vlan_del(struct net_bridge_vlan *v) if (p) { err = __vlan_vid_del(p->dev, p->br, v); if (err) - goto out; + br_warn(p->br, + "port %u(%s) failed to delete vlan %u from switchdev: %pe\n", + (unsigned int)p->port_no, p->dev->name, + v->vid, ERR_PTR(err)); } else { err = br_switchdev_port_vlan_del(v->br->dev, v->vid); if (err && err != -EOPNOTSUPP) - goto out; - err = 0; + br_warn(v->br, + "failed to delete bridge vlan %u from switchdev: %pe\n", + v->vid, ERR_PTR(err)); } if (br_vlan_should_use(v)) { @@ -431,8 +435,6 @@ static int __vlan_del(struct net_bridge_vlan *v) } br_vlan_put_master(masterv); -out: - return err; } static void __vlan_group_free(struct net_bridge_vlan_group *vg) @@ -449,7 +451,6 @@ static void __vlan_flush(const struct net_bridge *br, { struct net_bridge_vlan *vlan, *tmp; u16 v_start = 0, v_end = 0; - int err; __vlan_delete_pvid(vg, vg->pvid); list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) { @@ -463,13 +464,7 @@ static void __vlan_flush(const struct net_bridge *br, } v_end = vlan->vid; - err = __vlan_del(vlan); - if (err) { - br_err(br, - "port %u(%s) failed to delete vlan %d: %pe\n", - (unsigned int) p->port_no, p->dev->name, - vlan->vid, ERR_PTR(err)); - } + __vlan_del(vlan); } /* notify about the last/whole vlan range */ @@ -837,8 +832,9 @@ int br_vlan_delete(struct net_bridge *br, u16 vid) br_fdb_delete_by_port(br, NULL, vid, 0); vlan_tunnel_info_del(vg, v); + __vlan_del(v); - return __vlan_del(v); + return 0; } void br_vlan_flush(struct net_bridge *br) @@ -1368,8 +1364,9 @@ int nbp_vlan_delete(struct net_bridge_port *port, u16 vid) return -ENOENT; br_fdb_find_delete_local(port->br, port, port->dev->dev_addr, vid); br_fdb_delete_by_port(port->br, port, vid, 0); + __vlan_del(v); - return __vlan_del(v); + return 0; } void nbp_vlan_flush(struct net_bridge_port *port) From ceac0de741bfb47ca255eee075257b3bb31f0651 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 11 Sep 2026 16:08:04 +0000 Subject: [PATCH 1121/1317] netlink: do not free nlk->groups while lockless readers can use it netlink_realloc_groups() uses krealloc() under netlink_table_grab(). Whenever NLGRPSZ(groups) lands in a different kmalloc bucket, the old bitmap is freed immediately. Two readers of nlk->groups / nlk->ngroups do not hold the netlink table lock: 1) sk_diag_dump_groups(). Hashed (bound) sockets are dumped from the rhashtable walk in __netlink_diag_dump(), which only holds RCU. Only the mc_list part of the dump takes nl_table_lock. 2) netlink_native_seq_show() (/proc/net/netlink), whose walk has been lockless since commit 21e4902aea80 ("netlink: Lockless lookup with RCU grace period in socket release"). Both can read a freed buffer, and sk_diag_dump_groups() can also read past the end of the old (smaller) buffer if it happens to load the old @groups pointer together with the new @ngroups value, copying the result into a NETLINK_DIAG_GROUPS attribute. This is the same class of bug that commit f773608026ee ("netlink: access nlk groups safely in netlink bind and getname") fixed for bind() and getname(); these two readers were missed. Simply grabbing the table lock in sk_diag_dump_groups() is not an option, because it is also called with nl_table_lock already held from the mc_list section of the dump. Make the lockless readers safe instead: - Allocate a new bitmap and free the old one after an RCU grace period, instead of relying on the implicit kfree() done by krealloc(). - Publish @groups before @ngroups, both with release semantics, and have the lockless readers load @ngroups first. A reader can then never pair the new (bigger) size with the old (smaller) buffer, and a reader picking up the new pointer while still seeing the old size is guaranteed to see the initialized bitmap. netlink_realloc_groups() is called from process context (bind() and setsockopt()), so kfree_rcu_mightsleep() can be used, once the table has been released. Fixes: 21e4902aea80 ("netlink: Lockless lookup with RCU grace period in socket release") Fixes: ad202074320c ("netlink: Use rhashtable walk interface in diag dump") Reported-by: James Burton Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260911160804.917099-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/netlink/af_netlink.c | 42 ++++++++++++++++++++++++++++++++-------- net/netlink/diag.c | 18 ++++++++++++++--- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index e6b1d9758c9c..9fdf964224ab 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -922,9 +922,9 @@ netlink_update_subscriptions(struct sock *sk, unsigned int subscriptions) static int netlink_realloc_groups(struct sock *sk) { + unsigned long *new_groups, *old_groups = NULL; struct netlink_sock *nlk = nlk_sk(sk); unsigned int groups; - unsigned long *new_groups; int err = 0; netlink_table_grab(); @@ -938,18 +938,37 @@ static int netlink_realloc_groups(struct sock *sk) if (nlk->ngroups >= groups) goto out_unlock; - new_groups = krealloc(nlk->groups, NLGRPSZ(groups), GFP_ATOMIC); - if (new_groups == NULL) { + /* Can not use krealloc(), because the old buffer might be freed + * immediately, while lockless readers (netlink diag dump and + * /proc/net/netlink) can still be looking at it. + */ + new_groups = kzalloc(NLGRPSZ(groups), GFP_ATOMIC); + if (!new_groups) { err = -ENOMEM; goto out_unlock; } - memset((char *)new_groups + NLGRPSZ(nlk->ngroups), 0, - NLGRPSZ(groups) - NLGRPSZ(nlk->ngroups)); + old_groups = nlk->groups; + if (old_groups) + memcpy(new_groups, old_groups, NLGRPSZ(nlk->ngroups)); + + /* Publish the new bitmap and its content: pairs with the address + * dependency in lockless readers, which can pick up the new pointer + * while still seeing the old (smaller) nlk->ngroups. + */ + smp_store_release(&nlk->groups, new_groups); + + /* Then publish the new size: pairs with smp_load_acquire() from + * lockless readers, so that they can not read NLGRPSZ(new ngroups) + * bytes from the old buffer. + */ + smp_store_release(&nlk->ngroups, groups); - nlk->groups = new_groups; - nlk->ngroups = groups; out_unlock: netlink_table_ungrab(); + + if (old_groups) + kfree_rcu_mightsleep(old_groups); + return err; } @@ -2705,12 +2724,19 @@ static int netlink_native_seq_show(struct seq_file *seq, void *v) } else { struct sock *s = v; struct netlink_sock *nlk = nlk_sk(s); + const unsigned long *groups; + + /* Lockless read : netlink_realloc_groups() can change + * nlk->groups under us. The old buffer is freed after an + * RCU grace period, and this walk is RCU protected. + */ + groups = READ_ONCE(nlk->groups); seq_printf(seq, "%pK %-3d %-10u %08x %-8d %-8d %-5d %-8d %-8u %-8llu\n", s, s->sk_protocol, nlk->portid, - nlk->groups ? (u32)nlk->groups[0] : 0, + groups ? (u32)groups[0] : 0, sk_rmem_alloc_get(s), sk_wmem_alloc_get(s), READ_ONCE(nlk->cb_running), diff --git a/net/netlink/diag.c b/net/netlink/diag.c index 0b3e021bd0ed..7979bd9b2606 100644 --- a/net/netlink/diag.c +++ b/net/netlink/diag.c @@ -12,12 +12,24 @@ static int sk_diag_dump_groups(struct sock *sk, struct sk_buff *nlskb) { struct netlink_sock *nlk = nlk_sk(sk); + unsigned long *groups; + unsigned int ngroups; - if (nlk->groups == NULL) + /* Hashed sockets are dumped from the rhashtable walk, which only + * holds rcu_read_lock(), while netlink_realloc_groups() can replace + * nlk->groups and nlk->ngroups at any time. + * + * Read nlk->ngroups first : this pairs with smp_store_release() + * from netlink_realloc_groups(), so that we can not use the new + * (bigger) size with the old (smaller) buffer. The old buffer is + * freed after an RCU grace period. + */ + ngroups = smp_load_acquire(&nlk->ngroups); + groups = READ_ONCE(nlk->groups); + if (!groups) return 0; - return nla_put(nlskb, NETLINK_DIAG_GROUPS, NLGRPSZ(nlk->ngroups), - nlk->groups); + return nla_put(nlskb, NETLINK_DIAG_GROUPS, NLGRPSZ(ngroups), groups); } static int sk_diag_put_flags(struct sock *sk, struct sk_buff *skb) From 8c0c602202b9a4909b00bc3354e3c0355bc69e65 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 23:32:55 +0200 Subject: [PATCH 1122/1317] selinux: preserve user SID across nested backing files SELinux saves the user file SID in a backing-file security blob so it remains available after mmap() replaces vma->vm_file with a backing file. For nested backing files (overlayfs over overlayfs, or FUSE passthrough backed by overlayfs), user_file may itself be a backing file. Its fsec->sid is the SID of the mounter that opened it, rather than the user that opened the top-level file. mprotect() then checks fd { use } against the mounter SID. This can incorrectly deny access without a domain transition, or check the wrong target SID after one. Copy the saved user SID when user_file is a backing file. Keep using the regular file SID for the first backing layer. With two nested overlayfs mounts and SELinux enforcing, mprotect(PROT_READ) returns EACCES with an fd { use } denial against the mounter SID. With this change, mprotect() succeeds. Tested on arm64 QEMU with a small BusyBox initramfs and a purpose-built SELinux policy. The original test was also repeated with Fedora Cloud Base 44 userspace and gave the same result. Cc: stable@vger.kernel.org Fixes: 82544d36b172 ("selinux: fix overlayfs mmap() and mprotect() access checks") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Amir Goldstein Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 9 ++++++++- security/selinux/include/objsec.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e5e17f100aae..b0bb5f8c90bf 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3843,13 +3843,20 @@ static int selinux_file_alloc_security(struct file *file) return 0; } +static inline u32 selinux_file_user_sid(const struct file *file) +{ + if (unlikely(file->f_mode & FMODE_BACKING)) + return selinux_backing_file(file)->uf_sid; + return selinux_file(file)->sid; +} + static int selinux_backing_file_alloc(struct file *backing_file, const struct file *user_file) { struct backing_file_security_struct *bfsec; bfsec = selinux_backing_file(backing_file); - bfsec->uf_sid = selinux_file(user_file)->sid; + bfsec->uf_sid = selinux_file_user_sid(user_file); return 0; } diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 3c0a16ec978b..853f7266ed18 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -87,7 +87,7 @@ struct file_security_struct { }; struct backing_file_security_struct { - u32 uf_sid; /* associated user file fsec->sid */ + u32 uf_sid; /* top-level user file fsec->sid */ }; struct superblock_security_struct { From 78fc54b934bfb2c18aad8154c7302067146946f9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 23:32:56 +0200 Subject: [PATCH 1123/1317] selinux: recheck intermediate backing files on mprotect() mprotect() can be used to bypass the SELinux checks that mmap() performs against the intermediate layers of a stacked filesystem. mmap() checks every backing layer as the request descends through the stack. mprotect() only has the lowest backing file in vma->vm_file, so it rechecks the top-level user and the lowest mounter, but skips the mounters of every layer in between. With two nested overlayfs mounts and a policy denying mounter_t -> middle_file_t:file { execute }, a direct mmap(PROT_EXEC) is denied: avc: denied { execute } for pid=71 comm="nested_exec" path="/payload" dev="overlay" ino=9 scontext=user_u:base_r:mounter_t tcontext=user_u:object_r:middle_file_t tclass=file permissive=0 while mmap(PROT_NONE) followed by mprotect(PROT_EXEC) succeeds. Preserve each intermediate path, mounter SID and file-description SID in the backing-file security blob, copying the saved entries when another backing layer is opened. Allocate the array only for nested backing files, and release it and the path references in the backing_file_free hook. During mprotect(), recheck fd { use } and the requested inode permissions for every saved mounter, and include the intermediate layers in the execmod checks. Policy for nested stacking may then need to grant intermediate mounters what a direct mmap() already requires, and execmod on intermediate labels for binaries using text relocations. Tested on arm64 QEMU with a small BusyBox initramfs and a purpose-built SELinux policy, on a mainline tree containing commit f2381b546e7e ("fs: fix user path of nested backing files"). Cc: stable@vger.kernel.org Fixes: 82544d36b172 ("selinux: fix overlayfs mmap() and mprotect() access checks") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Stephen Smalley [PM: subject tweak] Signed-off-by: Paul Moore --- security/selinux/hooks.c | 149 ++++++++++++++++++++++++++---- security/selinux/include/objsec.h | 8 ++ 2 files changed, 137 insertions(+), 20 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b0bb5f8c90bf..3f4a6ddee322 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1674,24 +1674,30 @@ static int cred_has_capability(const struct cred *cred, return rc; } -/* Check whether a task has a particular permission to an inode. - The 'adp' parameter is optional and allows other audit - data to be passed (e.g. the dentry). */ +/* + * Check whether a SID has a particular permission to an inode. The 'adp' + * parameter is optional and allows other audit data to be passed (e.g. the + * dentry). + */ +static int inode_sid_has_perm(u32 sid, struct inode *inode, u32 perms, + struct common_audit_data *adp) +{ + struct inode_security_struct *isec; + + if (unlikely(IS_PRIVATE(inode))) + return 0; + + isec = selinux_inode(inode); + + return avc_has_perm(sid, isec->sid, isec->sclass, perms, adp); +} + static int inode_has_perm(const struct cred *cred, struct inode *inode, u32 perms, struct common_audit_data *adp) { - struct inode_security_struct *isec; - u32 sid; - - if (unlikely(IS_PRIVATE(inode))) - return 0; - - sid = cred_sid(cred); - isec = selinux_inode(inode); - - return avc_has_perm(sid, isec->sid, isec->sclass, perms, adp); + return inode_sid_has_perm(cred_sid(cred), inode, perms, adp); } /* Same as inode_has_perm, but pass explicit audit data containing @@ -3854,13 +3860,63 @@ static int selinux_backing_file_alloc(struct file *backing_file, const struct file *user_file) { struct backing_file_security_struct *bfsec; + const struct backing_file_security_struct *ubfsec; + struct backing_file_security_layer *layer; + u32 i; bfsec = selinux_backing_file(backing_file); bfsec->uf_sid = selinux_file_user_sid(user_file); + if (!(user_file->f_mode & FMODE_BACKING)) + return 0; + + ubfsec = selinux_backing_file(user_file); + /* a wrapped count would make kmalloc_array() return ZERO_SIZE_PTR */ + if (unlikely(ubfsec->layer_count == U32_MAX)) + return -EOVERFLOW; + + /* + * The final VMA only retains the lowest backing file, so record the + * whole chain here rather than in the mmap hook, where concurrent + * mappings would have to be serialized. Size it dynamically: erofs + * inode sharing adds a backing file without bumping s_stack_depth. + */ + bfsec->layers = kmalloc_array(ubfsec->layer_count + 1, + sizeof(*bfsec->layers), GFP_KERNEL); + if (!bfsec->layers) + return -ENOMEM; + + for (i = 0; i < ubfsec->layer_count; i++) { + layer = &bfsec->layers[i]; + *layer = ubfsec->layers[i]; + path_get(&layer->path); + } + + /* f_path, not file_user_path(): this layer, not the top-level file */ + layer = &bfsec->layers[i]; + layer->path = user_file->f_path; + layer->mounter_sid = cred_sid(user_file->f_cred); + layer->fd_sid = selinux_file(user_file)->sid; + path_get(&layer->path); + bfsec->layer_count = ubfsec->layer_count + 1; return 0; } +static void selinux_backing_file_free(struct file *backing_file) +{ + struct backing_file_security_struct *bfsec; + + /* security_backing_file_free() may be called twice after an error */ + if (!backing_file_security(backing_file)) + return; + + bfsec = selinux_backing_file(backing_file); + while (bfsec->layer_count) + path_put(&bfsec->layers[--bfsec->layer_count].path); + kfree(bfsec->layers); + bfsec->layers = NULL; +} + /* * Check whether a task has the ioctl permission and cmd * operation to an inode. @@ -3978,6 +4034,53 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd, static int default_noexec __ro_after_init; +static u32 file_map_prot_to_av(unsigned long prot, bool shared) +{ + u32 av = FILE__READ; + + if (shared && (prot & PROT_WRITE)) + av |= FILE__WRITE; + if (prot & PROT_EXEC) + av |= FILE__EXECUTE; + + return av; +} + +static int backing_mounters_has_perm(const struct file *file, u32 av) +{ + const struct backing_file_security_struct *bfsec; + const struct backing_file_security_layer *layer; + struct common_audit_data ad; + struct inode *inode; + u32 i; + int rc; + + if (WARN_ON_ONCE(!(file->f_mode & FMODE_BACKING))) + return -EIO; + + bfsec = selinux_backing_file(file); + for (i = 0; i < bfsec->layer_count; i++) { + layer = &bfsec->layers[i]; + inode = d_inode(layer->path.dentry); + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = layer->path; + + if (layer->mounter_sid != layer->fd_sid) { + rc = avc_has_perm(layer->mounter_sid, layer->fd_sid, + SECCLASS_FD, FD__USE, &ad); + if (rc) + return rc; + } + + rc = inode_sid_has_perm(layer->mounter_sid, inode, av, &ad); + if (rc) + return rc; + } + + return 0; +} + static int __file_map_prot_check(const struct file *file, unsigned long prot, bool shared, bool mounter_check, bool bf_user_file) @@ -4011,14 +4114,10 @@ static int __file_map_prot_check(const struct file *file, unsigned long prot, if (file) { const struct cred *cred = mounter_check ? file->f_cred : current_cred(); - /* "read" always possible, "write" only if shared */ - u32 av = FILE__READ; - if (shared && prot_write) - av |= FILE__WRITE; - if (prot_exec) - av |= FILE__EXECUTE; - return __file_has_perm(cred, file, av, bf_user_file); + return __file_has_perm(cred, file, + file_map_prot_to_av(prot, shared), + bf_user_file); } return 0; @@ -4113,6 +4212,7 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, int rc; const struct cred *cred = current_cred(); u32 sid = cred_sid(cred); + u32 av; const struct file *file = vma->vm_file; bool backing_file; bool shared = vma->vm_flags & VM_SHARED; @@ -4156,6 +4256,10 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, if (rc) return rc; if (backing_file) { + rc = backing_mounters_has_perm(file, + FILE__EXECMOD); + if (rc) + return rc; rc = file_has_perm(file->f_cred, file, FILE__EXECMOD); if (rc) @@ -4168,6 +4272,10 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, if (rc) return rc; if (backing_file) { + av = file_map_prot_to_av(prot, shared); + rc = backing_mounters_has_perm(file, av); + if (rc) + return rc; rc = file_map_prot_check(file, prot, shared, true); if (rc) return rc; @@ -7626,6 +7734,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(file_permission, selinux_file_permission), LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), LSM_HOOK_INIT(backing_file_alloc, selinux_backing_file_alloc), + LSM_HOOK_INIT(backing_file_free, selinux_backing_file_free), LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl), LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat), LSM_HOOK_INIT(mmap_file, selinux_mmap_file), diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 853f7266ed18..2f21568251ff 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -86,8 +86,16 @@ struct file_security_struct { u32 pseqno; /* Policy seqno at the time of file open */ }; +struct backing_file_security_layer { + struct path path; /* this layer's real path */ + u32 mounter_sid; /* SID of the mounter that opened it */ + u32 fd_sid; /* SID of its open file description */ +}; + struct backing_file_security_struct { u32 uf_sid; /* top-level user file fsec->sid */ + u32 layer_count; /* number of intermediate backing files */ + struct backing_file_security_layer *layers; }; struct superblock_security_struct { From e0c3e9d76adbe522dd420a766ce42d03ce887c29 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 17:15:44 +0800 Subject: [PATCH 1124/1317] i2c: imx: disable autosuspend on remove i2c_imx_probe() enables runtime PM autosuspend with pm_runtime_use_autosuspend(). The probe error path correctly undoes this setting with pm_runtime_dont_use_autosuspend(), but the normal remove path only disables runtime PM. The runtime PM API requires pm_runtime_use_autosuspend() to be undone with pm_runtime_dont_use_autosuspend() at driver exit unless runtime PM was enabled with devm_pm_runtime_enable(). Leaving the autosuspend flag set therefore leaves the runtime PM state incompletely cleaned up after the driver is unbound. Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: 588eb93ea49f ("i2c: imx: add runtime pm support to improve the performance") Signed-off-by: Guangshuo Li Cc: # v4.5+ Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260914091544.1667137-1-lgs201920130244@gmail.com --- drivers/i2c/busses/i2c-imx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 19ec056b00af..4dc3df6a41a4 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1920,6 +1920,7 @@ static void i2c_imx_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); } static int i2c_imx_runtime_suspend(struct device *dev) From 0d1cb83337f13af082afb68b28d3fdfe29cde7fb Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 15 Sep 2026 06:59:33 +0000 Subject: [PATCH 1125/1317] ata: libahci_platform: Fix device reference leak in ahci_platform_get_resources() of_find_device_by_node() takes a reference on the port platform device, which is only used to look up its port regulator and is never released, neither on success nor on the error paths. Drop the reference with put_device() once the regulator has been obtained, which covers both the success and error paths. Fixes: c7d7ddee7e24 ("ata: libahci: Allow using multiple regulators") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://lore.kernel.org/r/20260915065933.1733061-1-vulab@iscas.ac.cn Reviewed-by: Damien Le Moal Signed-off-by: Niklas Cassel --- drivers/ata/libahci_platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ata/libahci_platform.c b/drivers/ata/libahci_platform.c index 6e072d681341..f6524135ea11 100644 --- a/drivers/ata/libahci_platform.c +++ b/drivers/ata/libahci_platform.c @@ -620,10 +620,10 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev, of_platform_device_create(child, NULL, NULL); port_dev = of_find_device_by_node(child); - if (port_dev) { rc = ahci_platform_get_regulator(hpriv, port, &port_dev->dev); + put_device(&port_dev->dev); if (rc == -EPROBE_DEFER) goto err_out; } From ad34235808b63a70ca4989b7a2852923193d06ef Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Mon, 7 Sep 2026 15:11:02 +0800 Subject: [PATCH 1126/1317] i2c: atr: fix dangling adapter pointer on add failure i2c_atr_add_adapter() stores atr->adapter[chan_id] before i2c_add_adapter() so that the I2C bus notifier can match child clients during registration. On failure the channel is freed but the slot was left pointing at freed memory, which can lead to use-after-free in i2c_atr_del_adapter() / cleanup and also block reuse with -EEXIST. Clear the slot on the i2c_add_adapter() error path before freeing chan. Fixes: a076a860acae ("media: i2c: add I2C Address Translator (ATR) support") Signed-off-by: Linkai Gong Cc: # v6.6+ Reviewed-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260907071102.1080840-1-gonglinkai@kylinos.cn --- drivers/i2c/i2c-atr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c index e6d2af659d81..ca29633dcd62 100644 --- a/drivers/i2c/i2c-atr.c +++ b/drivers/i2c/i2c-atr.c @@ -855,6 +855,7 @@ int i2c_atr_add_adapter(struct i2c_atr *atr, struct i2c_atr_adap_desc *desc) ret = i2c_add_adapter(&chan->adap); if (ret) { + atr->adapter[chan_id] = NULL; dev_err(dev, "failed to add atr-adapter %u (error=%d)\n", chan_id, ret); goto err_free_alias_pool; From 2ab510e63197360945f915dd5631a77c63ac6b27 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Tue, 15 Sep 2026 16:05:57 +0100 Subject: [PATCH 1127/1317] drm/sched: Fix virtual runtime race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent pushing a new job to an entity seeing it being the first in the queue, and hence entering the drm_sched_rq_add_entity() path, if the pop side in drm_sched_entity_pop_job() has just de-queued the job but not yet updated the saved virtual time. Restoring the unsaved virtual time, which is at this point not a delta but still an absolute value, pushes the said entity to the rear of the run queue for a potentially very long time. We close this race by pulling the locked sections out to encompass both the queue push/pop and corresponding rbtree management. This is aligned with the future direction to replace the current lockless job queue with one of the fully locked standard list primitives. Signed-off-by: Tvrtko Ursulin Fixes: 2fa4d8e2c109 ("drm/sched: Add fair scheduling policy") Suggested-by: Luke.Wildhardt@proton.me # via Claude Opus Tested-by: Luke.Wildhardt@proton.me Cc: Christian König Cc: Danilo Krummrich Cc: Philipp Stanner Cc: Pierre-Eric Pelloux-Prayer Cc: Matthew Brost Cc: Vitaly Prosyak Cc: stable@vger.kernel.org # v7.2+ [phasta: commit title] Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260915150557.62847-1-tvrtko.ursulin@igalia.com --- drivers/gpu/drm/scheduler/sched_entity.c | 8 +++++++- drivers/gpu/drm/scheduler/sched_rq.c | 20 +++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_entity.c b/drivers/gpu/drm/scheduler/sched_entity.c index a4a7efdbf229..673ca9cbf362 100644 --- a/drivers/gpu/drm/scheduler/sched_entity.c +++ b/drivers/gpu/drm/scheduler/sched_entity.c @@ -559,9 +559,10 @@ struct drm_sched_job *drm_sched_entity_pop_job(struct drm_sched_entity *entity) */ smp_wmb(); + spin_lock(&entity->lock); spsc_queue_pop(&entity->job_queue); - drm_sched_rq_pop_entity(entity); + spin_unlock(&entity->lock); /* Jobs and entities might have different lifecycles. Since we're * removing the job from the entities queue, set the jobs entity pointer @@ -647,6 +648,9 @@ void drm_sched_entity_push_job(struct drm_sched_job *sched_job) * Make sure to set the submit_ts first, to avoid a race. */ sched_job->submit_ts = submit_ts = ktime_get(); + + spin_lock(&entity->lock); + first = spsc_queue_push(&entity->job_queue, &sched_job->queue_node); /* first job wakes up scheduler */ @@ -657,5 +661,7 @@ void drm_sched_entity_push_job(struct drm_sched_job *sched_job) if (sched) drm_sched_wakeup(sched); } + + spin_unlock(&entity->lock); } EXPORT_SYMBOL(drm_sched_entity_push_job); diff --git a/drivers/gpu/drm/scheduler/sched_rq.c b/drivers/gpu/drm/scheduler/sched_rq.c index 0464d324d98d..23f46ec610e7 100644 --- a/drivers/gpu/drm/scheduler/sched_rq.c +++ b/drivers/gpu/drm/scheduler/sched_rq.c @@ -257,19 +257,17 @@ static ktime_t drm_sched_entity_get_job_ts(struct drm_sched_entity *entity) struct drm_gpu_scheduler * drm_sched_rq_add_entity(struct drm_sched_entity *entity, ktime_t ts) { + struct drm_sched_rq *rq = entity->rq; struct drm_gpu_scheduler *sched; - struct drm_sched_rq *rq; /* Add the entity to the run queue */ - spin_lock(&entity->lock); - if (entity->stopped) { - spin_unlock(&entity->lock); + lockdep_assert_held(&entity->lock); + if (entity->stopped) { DRM_ERROR("Trying to push to a killed entity\n"); return NULL; } - rq = entity->rq; spin_lock(&rq->lock); sched = rq->sched; @@ -289,7 +287,6 @@ drm_sched_rq_add_entity(struct drm_sched_entity *entity, ktime_t ts) drm_sched_rq_update_fifo_locked(entity, rq, ts); spin_unlock(&rq->lock); - spin_unlock(&entity->lock); return sched; } @@ -343,16 +340,17 @@ drm_sched_rq_next_rr_ts(struct drm_sched_rq *rq, */ void drm_sched_rq_pop_entity(struct drm_sched_entity *entity) { + struct drm_sched_rq *rq = entity->rq; struct drm_sched_job *next_job; - struct drm_sched_rq *rq; + + lockdep_assert_held(&entity->lock); + + spin_lock(&rq->lock); /* * Update the entity's location in the min heap according to * the timestamp of the next job, if any. */ - spin_lock(&entity->lock); - rq = entity->rq; - spin_lock(&rq->lock); next_job = drm_sched_entity_queue_peek(entity); if (next_job) { ktime_t ts; @@ -375,8 +373,8 @@ void drm_sched_rq_pop_entity(struct drm_sched_entity *entity) drm_sched_entity_save_vruntime(entity, min_vruntime); } } + spin_unlock(&rq->lock); - spin_unlock(&entity->lock); } /** From dbd9d1cbf9700528c8595ab1fa7ef832e79821fe Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Tue, 15 Sep 2026 23:31:10 +0700 Subject: [PATCH 1128/1317] ALSA: usb-audio: fix list_add double-add in push_back_to_ready_list stop_urbs() clears ep->ready_playback_urbs with a bare INIT_LIST_HEAD() instead of unlinking each queued snd_urb_ctx. If a URB survives past wait_clear_urbs()'s forced STOPPING->STOPPED timeout, its ctx is left looking "linked" (stale next/prev) even though the list head has forgotten it. When the endpoint later restarts and re-queues that same ctx onto the (now real) ready list, and the old URB's completion handler then calls push_back_to_ready_list() for it a second time, the ctx is still the list's own tail and list_add's double-add check trips: kernel BUG at lib/list_debug.c:35 (list_add double add) Guard push_back_to_ready_list() with a list_empty() check so a still-linked ctx isn't re-added, and make stop_urbs() actually unlink each ctx via list_del_init() instead of only resetting the head, so a dropped ctx doesn't keep looking linked to that guard. Reported-by: syzbot+9fe3b8d9f5c64ff410a7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9fe3b8d9f5c64ff410a7 Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260915163110.58124-1-ngocthang2710.1999@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/endpoint.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index b72ce1e9bfb1..879d0451536e 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -449,7 +449,9 @@ static void push_back_to_ready_list(struct snd_usb_endpoint *ep, struct snd_urb_ctx *ctx) { guard(spinlock_irqsave)(&ep->lock); - list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + /* ctx may still be linked: a stale completion racing a stop/restart. */ + if (list_empty(&ctx->ready_list)) + list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); } /* @@ -1037,6 +1039,7 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) */ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) { + struct snd_urb_ctx *ctx, *n; unsigned int i; if (!force && atomic_read(&ep->running)) @@ -1046,7 +1049,9 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) return 0; scoped_guard(spinlock_irqsave, &ep->lock) { - INIT_LIST_HEAD(&ep->ready_playback_urbs); + /* Unlink each ctx; INIT_LIST_HEAD() alone would leave them looking linked. */ + list_for_each_entry_safe(ctx, n, &ep->ready_playback_urbs, ready_list) + list_del_init(&ctx->ready_list); ep->next_packet_head = 0; ep->next_packet_queued = 0; } From 51938dfa8a51a4f85328413fca9b6e21f9d2d088 Mon Sep 17 00:00:00 2001 From: Amit Machhiwal Date: Tue, 15 Sep 2026 22:04:15 +0530 Subject: [PATCH 1129/1317] KVM: PPC: Book3S HV: fix use-after-free in kvmhv_emulate_tlbie_all_lpid() kvmhv_emulate_tlbie_all_lpid() iterates the nested-guest IDR and drops mmu_lock before calling kvmhv_emulate_tlbie_lpid(), but does not hold a reference on the kvm_nested_guest pointer obtained from the IDR. A concurrent vCPU issuing a single-LPID tlbie (is=2, ric=2) can race through kvmhv_flush_nested() -> kvmhv_remove_nested() -> idr_remove / --refcnt -> kvmhv_release_nested() -> kfree(gp) in that window, leaving the iterating vCPU with a dangling pointer. The subsequent mutex_lock(&gp->tlb_lock) and accesses to gp->shadow_pgtable, gp->shadow_lpid and gp->l1_host all touch freed memory. The free path is fully L1-controlled. Fix this by incrementing gp->refcnt inside the loop before dropping mmu_lock, mirroring what kvmhv_get_nested() does, and releasing the reference with kvmhv_put_nested() after the per-guest work completes. This is the same get/put discipline already used at every other call site that drops mmu_lock while holding a nested-guest pointer. Fixes: e3b6b4661527 ("KVM: PPC: Book3S HV: Implement H_TLB_INVALIDATE hcall") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Amit Machhiwal Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kvm/book3s_hv_nested.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/kvm/book3s_hv_nested.c b/arch/powerpc/kvm/book3s_hv_nested.c index 22e616662255..a6ff42d7666c 100644 --- a/arch/powerpc/kvm/book3s_hv_nested.c +++ b/arch/powerpc/kvm/book3s_hv_nested.c @@ -1204,8 +1204,10 @@ static void kvmhv_emulate_tlbie_all_lpid(struct kvm_vcpu *vcpu, int ric) spin_lock(&kvm->mmu_lock); idr_for_each_entry(&kvm->arch.kvm_nested_guest_idr, gp, lpid) { + ++gp->refcnt; spin_unlock(&kvm->mmu_lock); kvmhv_emulate_tlbie_lpid(vcpu, gp, ric); + kvmhv_put_nested(gp); spin_lock(&kvm->mmu_lock); } spin_unlock(&kvm->mmu_lock); From 0a416ee20bcccddf91ca5b63696a23b9d11d73aa Mon Sep 17 00:00:00 2001 From: Amit Machhiwal Date: Tue, 15 Sep 2026 22:04:16 +0530 Subject: [PATCH 1130/1317] KVM: PPC: Book3S HV: fix secure device page leak on uv_page_in() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In kvmppc_svm_page_in(), if uv_page_in() fails after kvmppc_uvmem_get_page() has succeeded, the secure device page is never released. kvmppc_uvmem_get_page() sets a bit in kvmppc_uvmem_bitmap, allocates a kvmppc_uvmem_page_pvt struct, marks the GFN as KVMPPC_GFN_UVMEM_PFN, and calls zone_device_page_init() which sets refcount=1 and locks the page. The subsequent goto out_finalize skips the *mig.dst assignment, so migrate_vma_finalize() is a no-op for the page, and none of those resources are ever reclaimed. Each occurrence permanently consumes one entry from the firmware-bounded secure memory pool (kvmppc_uvmem_bitmap), leaks pvt, and leaves the GFN marked as secure — making it unusable for the lifetime of the VM. The twin __kvmppc_svm_page_out() already handles the analogous uv_page_out() failure correctly with unlock_page(dpage); __free_page(dpage). Apply the same pattern here: unlock_page() followed by put_page(), which chains through free_zone_device_folio() into kvmppc_uvmem_folio_free() to clear the bitmap bit, free pvt, and reset the GFN state. Reachable whenever uv_page_in() returns an error (e.g. UV pool exhaustion) on any POWER9/10 + Ultravisor/PEF system. Fixes: ca9f4942670c ("KVM: PPC: Book3S HV: Support for running secure guests") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Amit Machhiwal Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kvm/book3s_hv_uvmem.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/kvm/book3s_hv_uvmem.c b/arch/powerpc/kvm/book3s_hv_uvmem.c index 5fbb95d90e99..463aef870c4e 100644 --- a/arch/powerpc/kvm/book3s_hv_uvmem.c +++ b/arch/powerpc/kvm/book3s_hv_uvmem.c @@ -779,8 +779,11 @@ static int kvmppc_svm_page_in(struct vm_area_struct *vma, if (spage) { ret = uv_page_in(kvm->arch.lpid, pfn << page_shift, gpa, 0, page_shift); - if (ret) + if (ret) { + unlock_page(dpage); + put_page(dpage); goto out_finalize; + } } } From 0b271f7d7f5ed45bc498a03ce0aa9cfd8402fc71 Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Tue, 15 Sep 2026 22:04:17 +0530 Subject: [PATCH 1131/1317] powerpc/iommu: Fix the overflow validation in iommu_tce_check_ioba The commit b1af23d836f8 ("KVM: PPC: iommu: Unify TCE checking") unified IOBA parameter checking across KVM and VFIO into iommu_tce_check_ioba(). While doing so, the passed in argument npages is ignored and constant value '1' is used leaving out a possible overflow as the callers can legitimately be using npages > 1 for H_STUFF_TCE or H_PUT_TCE_INDIRECT cases. Fix this by accounting for 'npages', checking for arithmetic overflow, and verifying that the entire requested range (ioba - offset + npages) does not exceed the table capacity 'size'. Fixes: b1af23d836f8 ("KVM: PPC: iommu: Unify TCE checking") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Shivaprasad G Bhat Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kernel/iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/iommu.c b/arch/powerpc/kernel/iommu.c index ee1b5cb557c9..1ae8384637b5 100644 --- a/arch/powerpc/kernel/iommu.c +++ b/arch/powerpc/kernel/iommu.c @@ -1076,7 +1076,7 @@ int iommu_tce_check_ioba(unsigned long page_shift, if (ioba < offset) return -EINVAL; - if ((ioba + 1) > (offset + size)) + if ((ioba + npages < ioba) || (ioba - offset + npages > size)) return -EINVAL; return 0; From 5a83606d9f31a38273ee803f3aae2e39247032c5 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:49:04 +0800 Subject: [PATCH 1132/1317] drm/verisilicon: set blend mode for the cursor plane Blend mode properties are now required to expose pixel formats w/ alpha. Experiments show that the fixed blending mode for the cursor seems to be COVERAGE: - With a cursor plane filled with R=G=0, B=0xff, A=0x40, the cursor is visible on a pure-white background, which means the background is multiplied. - With a cursor plane filled with R=G=B=0xff, A=0x40, the cursor isn't pure white and non-white patterns can be see through, which means the cursor is multiplied. Add a fixed COVERAGE blend mode property for the cursor plane. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910094904.3502741-1-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_cursor_plane.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c index fa4f601dd0c8..da456dced88a 100644 --- a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -267,6 +268,7 @@ struct drm_plane *vs_cursor_plane_init(struct drm_device *drm_dev, return plane; drm_plane_helper_add(plane, &vs_cursor_plane_helper_funcs); + drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); return plane; } From e5d43d7e924d7e1d0c815a5c7407f2a40a6dd2f2 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:49:59 +0800 Subject: [PATCH 1133/1317] drm/verisilicon: add primary modifier for format tables Currently the format tables are only used for the primary plane. Add primary modifiers to names related to the tables. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910095000.3505878-1-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_hwdb.c | 12 ++++++------ drivers/gpu/drm/verisilicon/vs_hwdb.h | 4 ++-- drivers/gpu/drm/verisilicon/vs_primary_plane.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c index 2a0f7c59afa3..56aa45044306 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.c +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -10,7 +10,7 @@ #include "vs_dc_top_regs.h" #include "vs_hwdb.h" -static const u32 vs_formats_array_no_yuv444[] = { +static const u32 vs_primary_formats_array_no_yuv444[] = { DRM_FORMAT_XRGB4444, DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, @@ -44,7 +44,7 @@ static const u32 vs_formats_array_no_yuv444[] = { /* TODO: non-RGB formats */ }; -static const u32 vs_formats_array_with_yuv444[] = { +static const u32 vs_primary_formats_array_with_yuv444[] = { DRM_FORMAT_XRGB4444, DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, @@ -79,13 +79,13 @@ static const u32 vs_formats_array_with_yuv444[] = { }; static const struct vs_formats vs_formats_no_yuv444 = { - .array = vs_formats_array_no_yuv444, - .num = ARRAY_SIZE(vs_formats_array_no_yuv444) + .primary_array = vs_primary_formats_array_no_yuv444, + .primary_num = ARRAY_SIZE(vs_primary_formats_array_no_yuv444) }; static const struct vs_formats vs_formats_with_yuv444 = { - .array = vs_formats_array_with_yuv444, - .num = ARRAY_SIZE(vs_formats_array_with_yuv444) + .primary_array = vs_primary_formats_array_with_yuv444, + .primary_num = ARRAY_SIZE(vs_primary_formats_array_with_yuv444) }; static struct vs_chip_identity vs_chip_identities[] = { diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h index 2065ecb73043..616076d931a5 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.h +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h @@ -10,8 +10,8 @@ #include struct vs_formats { - const u32 *array; - unsigned int num; + const u32 *primary_array; + unsigned int primary_num; }; struct vs_chip_identity { diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c index 1f2be41ae496..8e9449169301 100644 --- a/drivers/gpu/drm/verisilicon/vs_primary_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c @@ -168,8 +168,8 @@ struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, &vs_primary_plane_funcs, - dc->identity.formats->array, - dc->identity.formats->num, + dc->identity.formats->primary_array, + dc->identity.formats->primary_num, NULL, DRM_PLANE_TYPE_PRIMARY, NULL); From 6c62dfd2820f0c32b0083668edbe0baca6e10b92 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:50:00 +0800 Subject: [PATCH 1134/1317] drm/verisilicon: remove ARGB formats from primary plane As the blending of the primary plane is currently explicitly disabled (and it's not possible on DC8000), remove the ARGB formats from the primary plane format tables. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910095000.3505878-2-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_hwdb.c | 32 --------------------------- 1 file changed, 32 deletions(-) diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c index 56aa45044306..ebf6f843bc88 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.c +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -15,32 +15,16 @@ static const u32 vs_primary_formats_array_no_yuv444[] = { DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444, - DRM_FORMAT_ARGB4444, - DRM_FORMAT_ABGR4444, - DRM_FORMAT_RGBA4444, - DRM_FORMAT_BGRA4444, DRM_FORMAT_XRGB1555, DRM_FORMAT_XBGR1555, DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551, - DRM_FORMAT_ARGB1555, - DRM_FORMAT_ABGR1555, - DRM_FORMAT_RGBA5551, - DRM_FORMAT_BGRA5551, DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_BGRA8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_RGBA1010102, - DRM_FORMAT_BGRA1010102, /* TODO: non-RGB formats */ }; @@ -49,32 +33,16 @@ static const u32 vs_primary_formats_array_with_yuv444[] = { DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444, - DRM_FORMAT_ARGB4444, - DRM_FORMAT_ABGR4444, - DRM_FORMAT_RGBA4444, - DRM_FORMAT_BGRA4444, DRM_FORMAT_XRGB1555, DRM_FORMAT_XBGR1555, DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551, - DRM_FORMAT_ARGB1555, - DRM_FORMAT_ABGR1555, - DRM_FORMAT_RGBA5551, - DRM_FORMAT_BGRA5551, DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_BGRA8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_RGBA1010102, - DRM_FORMAT_BGRA1010102, /* TODO: non-RGB formats */ }; From 53cf0f26eece095a3752642151150404bc3351cc Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 1 Sep 2026 13:37:36 -0700 Subject: [PATCH 1135/1317] crypto: caam - map job ring registers without claiming region devm_platform_ioremap_resource() ends up calling request_mem_region(), which fails with -EBUSY on i.MX SoCs: There the job rings are modelled as sub-regions of their parent fsl,sec-v4.0 register window, and caam_probe() already reserves the whole window exclusively via devm_of_iomap() before the children are populated. Every job ring therefore collides with its own parent and fails to probe, taking the hardware RNG offline (seen on i.MX6, i.MX7 and i.MX8 boards such as colibri-imx7 and verdin-imx8mp). Map the job ring registers with devm_ioremap() instead, which does not claim the (already owned) region. Fixes: 9a955c0a7d11 ("crypto: caam - simplify probe resource and IRQ handling") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Reported-by: Emanuele Ghidoli Tested-by: Emanuele Ghidoli Tested-by: Richard Leitner # i.MX8MP Tested-by: Sahil Malhotra # i.MX8MM-EVK Signed-off-by: Herbert Xu --- drivers/crypto/caam/jr.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/crypto/caam/jr.c b/drivers/crypto/caam/jr.c index 239469f6882e..cf725a6ac0fb 100644 --- a/drivers/crypto/caam/jr.c +++ b/drivers/crypto/caam/jr.c @@ -583,12 +583,29 @@ static int caam_jr_probe(struct platform_device *pdev) struct caam_drv_private_jr *jrpriv; static int total_jobrs; void __iomem *ctrl; + struct resource *r; int error; int irq; - ctrl = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(ctrl)) - return PTR_ERR(ctrl); + /* + * The job rings live inside the register window of their parent + * fsl,sec-v4.0 node, which caam_probe() already reserves (and maps) + * via devm_of_iomap(). A requested region that overlaps that + * reservation, e.g. from devm_platform_ioremap_resource(), would + * therefore fail with -EBUSY, so map the registers without claiming + * the region here. + */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "platform_get_resource() failed\n"); + return -EINVAL; + } + + ctrl = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!ctrl) { + dev_err(&pdev->dev, "devm_ioremap() failed\n"); + return -ENOMEM; + } irq = platform_get_irq(pdev, 0); if (irq < 0) From a26204be587c57bd5c54fa513be26c4fd7bf252d Mon Sep 17 00:00:00 2001 From: Nemesa Garg Date: Wed, 9 Sep 2026 16:33:31 +0530 Subject: [PATCH 1136/1317] Revert "drm/i915/display: Clear SEL_FETCH_PLANE_CTL on plane disable" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7f1172a2ac0d7e50850785e2e65789c8aac8411a. This commit replaced the crtc_state->enable_psr2_sel_fetch guard in icl_plane_disable_sel_fetch_arm() and i9xx_cursor_disable_sel_fetch_arm() with HAS_PSR2_SEL_FETCH(). This is a display version check and says nothing about the pipe, so every plane and cursor disable on a display 12+ platform started writing SEL_FETCH_PLANE_CTL() / SEL_FETCH_CUR_CTL(), including on pipes that do not implement them. It shows up as an unclaimed register access on pipes driving HDMI where selective fetch was never enabled. The stale selective fetch enable bit that commit addressed is handled in the next patch. Fixes: 7f1172a2ac0d ("drm/i915/display: Clear SEL_FETCH_PLANE_CTL on plane disable") Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16876 Signed-off-by: Nemesa Garg Reviewed-by: Jouni Högander Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260909110332.3528029-2-nemesa.garg@intel.com (cherry picked from commit d393529394167e0f5f706657eebe84d8529ce4fc) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cursor.c | 15 +++++---------- .../gpu/drm/i915/display/skl_universal_plane.c | 15 +++++---------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c index 86bb96ac449b..0673f16f6fd0 100644 --- a/drivers/gpu/drm/i915/display/intel_cursor.c +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -530,18 +530,13 @@ static int i9xx_check_cursor(struct intel_crtc_state *crtc_state, } static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, - struct intel_plane *plane) + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - /* - * Clear this whenever the hardware has selective fetch, not just when - * the current state uses it. The cursor may have been enabled with - * selective fetch earlier and had its enable bit orphaned when the - * feature was switched off. - */ - if (!HAS_PSR2_SEL_FETCH(display)) + if (!crtc_state->enable_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); @@ -591,7 +586,7 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, if (crtc_state->enable_psr2_su_region_et) wa_16021440873(dsb, plane, crtc_state, plane_state); else - i9xx_cursor_disable_sel_fetch_arm(dsb, plane); + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); } } @@ -700,7 +695,7 @@ static void i9xx_cursor_update_arm(struct intel_dsb *dsb, if (plane_state) i9xx_cursor_update_sel_fetch_arm(dsb, plane, crtc_state, plane_state); else - i9xx_cursor_disable_sel_fetch_arm(dsb, plane); + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); if (plane->cursor.base != base || plane->cursor.size != fbc_ctl || diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index 5cda1ab90e40..07a683293352 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -879,18 +879,13 @@ skl_plane_disable_arm(struct intel_dsb *dsb, } static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, - struct intel_plane *plane) + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - /* - * Clear this whenever the hardware has selective fetch, not just when - * the current state uses it. The plane may have been enabled with - * selective fetch earlier and had its enable bit orphaned when the - * feature was switched off. - */ - if (!HAS_PSR2_SEL_FETCH(display)) + if (!crtc_state->enable_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); @@ -926,7 +921,7 @@ icl_plane_disable_arm(struct intel_dsb *dsb, skl_write_plane_wm(dsb, plane, crtc_state); - icl_plane_disable_sel_fetch_arm(dsb, plane); + icl_plane_disable_sel_fetch_arm(dsb, plane, crtc_state); if (plane_has_normalizer(plane)) intel_de_write_dsb(display, dsb, @@ -1646,7 +1641,7 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), SEL_FETCH_PLANE_CTL_ENABLE); else - icl_plane_disable_sel_fetch_arm(dsb, plane); + icl_plane_disable_sel_fetch_arm(dsb, plane, crtc_state); } static void From b61b6f95d6722ddbbbd09e689fa41b55fd36f9a5 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 11 Sep 2026 11:04:47 +0200 Subject: [PATCH 1137/1317] futex: Also allocate private hash on vfork() As Jann demonstrated, it is entirely feasible to access the mm through vfork(). Therefore we need to allocate a private hash on vfork() as well as any other CLONE_VM user. Specifically, it must be avoided to have (private) futex waiters before allocating the private hash. Fixes: ee9dce44362b ("futex: Drop CLONE_THREAD requirement for private default hash alloc") Reported-by: Jann Horn Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260911090447.GT788244@noisy.programming.kicks-ass.net --- kernel/fork.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index a5934a317634..5ef413368912 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1996,9 +1996,9 @@ static bool need_futex_hash_allocate_default(u64 clone_flags) { /* * Allocate a default futex hash for any sibling that will - * share the parent's mm, except vfork. + * share the parent's mm. */ - return (clone_flags & (CLONE_VM | CLONE_VFORK)) == CLONE_VM; + return clone_flags & CLONE_VM; } /* From f0e9f963a3d209d7dc7ddd61116118ab5da2797d Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Fri, 11 Sep 2026 17:45:47 +0530 Subject: [PATCH 1138/1317] drm/xe/i2c: Disable IRQ on unbind Currently, struct xe_i2c is freed before SGUnit IRQ is disabled in unbind path, leaving a potential UAF in case I2C IRQ is hit during this small window. Explicitly disable I2C IRQ in xe_i2c_remove() and fix this. Fixes: 0bb78ce09926 ("drm/xe/i2c: Wire up reset/postinstall for I2C IRQ") Signed-off-by: Raag Jadav Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260911121547.2407261-1-raag.jadav@intel.com Signed-off-by: Matt Roper (cherry picked from commit 8ba5c8b8ab3fd362267c11df2cd5a90ee46f6e24) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_i2c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index 399099ff0dbc..f4f381988289 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -318,8 +318,10 @@ void xe_i2c_pm_resume(struct xe_device *xe, bool d3cold) static void xe_i2c_remove(void *data) { struct xe_i2c *i2c = data; + struct xe_device *xe = tile_to_xe(i2c->mmio->tile); unsigned int i; + xe_i2c_irq_reset(xe); xe_amc_exit(i2c); for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) { @@ -329,6 +331,7 @@ static void xe_i2c_remove(void *data) bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); xe_i2c_unregister_adapter(i2c); + xe->i2c = NULL; } /** From a13f7f5d14af9baf34eb12c25962f8d3542b281d Mon Sep 17 00:00:00 2001 From: Ilya Titov Date: Thu, 3 Sep 2026 12:17:18 +0300 Subject: [PATCH 1139/1317] pinctrl: sunxi: keep a shadow copy of the data register output latches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Allwinner SoCs, reading a bank's data register returns the pin level, not the output latch, for pins that are muxed as inputs. Writing a GPIO therefore corrupts the output latches of all input-muxed pins in the same bank: the read-modify-write in sunxi_pinctrl_gpio_set() reads back their pin levels and writes those into their latches. This breaks emulated open-drain lines (e.g. a bit-banged I2C bus from i2c-gpio). Such a line is released high by muxing it as input and letting the pull-up raise it, so any concurrent GPIO write in the same bank stores 1 into its latch. Driving the line low afterwards is a non-atomic data-then-mux sequence in sunxi_pinctrl_gpio_direction_output(); if the poisoning write lands between the two steps, the pin actively drives high (push-pull) instead of low. Observed in practice as sporadic glitches on a T507 board bit-banging I2C on port E while other PE GPIOs are toggled. On a scope the failure is unmistakable: on a clock pulse where SCL should fall to GND, the line instead steps *above* its idle high level for the whole low phase — the pad drives a strong push-pull 3.3 V high, higher than the level the pull-up sustains on the loaded bus — before the next transition recovers it. The same can hit SDA, corrupting data instead of clocks. Steps to reproduce on any sunxi board with a bit-banged (i2c-gpio) bus: # background: toggle any other GPIO of the same bank, e.g. line 21 gpioset -c --toggle 100us 21=0 & # foreground: keep the bit-banged bus busy while :; do i2cdetect -y 0x50 0x57; done # watch SCL/SDA with a scope or logic analyzer: sporadic clock-low # phases driven high (above the pull-up level) instead of low The bank spinlock cannot help: the racing write is a perfectly valid whole-register RMW that faithfully writes back what the hardware returned. There are no set/clear registers on this IP to write a single bit atomically. Fix it the same way gpio-mmio handles hardware whose data register read does not return the output latch: keep a shadow copy of each bank's latches, base the read-modify-write on the shadow, and only write the register. The shadow is seeded from the hardware at probe time so pins left in output mode by the bootloader keep their state. Pins that reach output mode through the gpiolib paths write their value (and thereby their shadow bit) before the mux switch in sunxi_pinctrl_gpio_direction_output(); pins muxed to gpio_out directly through a pinmux node bypass that path, so sunxi_pmx_set() refreshes their shadow bit from the latch (readable once the pin is in output mode) to keep them driving their pre-existing level. Seeding the shadow reads the PIO registers at probe time, which requires the bus clock to be enabled. The clock was only requested at the very end of probe, after devm_pinctrl_register() had already claimed the pin hogs described in the device tree - which mux pins, and thus access registers, with the clock still gated. Move the request ahead of both. Boards whose bootloader leaves the PIO clock running are unaffected, which is why the pre-existing hog problem has gone unnoticed since commit 950707c0eb5c ("pinctrl: sunxi: add clock support"). Fixes: df7b34f4c3d2 ("pinctrl: sunxi: Fix gpio_set behaviour") Cc: stable@vger.kernel.org Signed-off-by: Ilya Titov Signed-off-by: Linus Walleij --- drivers/pinctrl/sunxi/pinctrl-sunxi.c | 76 +++++++++++++++++++++------ drivers/pinctrl/sunxi/pinctrl-sunxi.h | 7 +++ 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index 25489beeb312..2881a83be99c 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -837,6 +837,21 @@ static void sunxi_pmx_set(struct pinctrl_dev *pctldev, writel((readl(pctl->membase + reg) & ~mask) | config << shift, pctl->membase + reg); + /* + * A pin muxed to gpio_out directly through a pinmux node bypasses + * sunxi_pinctrl_gpio_set() and drives whatever its output latch + * holds. Now that the pin is in output mode the data register + * reads back the latch, so refresh the shadow to keep such pins + * driving their pre-existing level. + */ + if (config == SUN4I_FUNC_OUTPUT) { + u32 *shadow = &pctl->dat_shadow[pin / PINS_PER_BANK]; + + sunxi_data_reg(pctl, pin, ®, &shift, &mask); + *shadow = (*shadow & ~mask) | + (readl(pctl->membase + reg) & mask); + } + raw_spin_unlock_irqrestore(&pctl->lock, flags); } @@ -1017,21 +1032,29 @@ static int sunxi_pinctrl_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { struct sunxi_pinctrl *pctl = gpiochip_get_data(chip); - u32 reg, shift, mask, val; + u32 *shadow = &pctl->dat_shadow[offset / PINS_PER_BANK]; + u32 reg, shift, mask; unsigned long flags; sunxi_data_reg(pctl, offset, ®, &shift, &mask); raw_spin_lock_irqsave(&pctl->lock, flags); - val = readl(pctl->membase + reg); - + /* + * Reading the data register returns the pin level, not the output + * latch, for pins muxed as inputs. A read-modify-write based on + * the register would therefore corrupt the latches of input-muxed + * pins in the same bank (e.g. an emulated open-drain I2C line + * released high), making them drive the wrong level once switched + * to output. Base the read-modify-write on a shadow copy of the + * latches instead. + */ if (value) - val |= mask; + *shadow |= mask; else - val &= ~mask; + *shadow &= ~mask; - writel(val, pctl->membase + reg); + writel(*shadow, pctl->membase + reg); raw_spin_unlock_irqrestore(&pctl->lock, flags); @@ -1572,7 +1595,7 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, struct pinctrl_pin_desc *pins; struct sunxi_pinctrl *pctl; struct pinmux_ops *pmxops; - int i, ret, last_pin, pin_idx; + int i, ret, last_pin, pin_idx, nbanks; struct clk *clk; pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL); @@ -1610,6 +1633,37 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, if (!pctl->irq_array) return -ENOMEM; + /* + * The bus clock has to be enabled before the pinctrl device + * registers, as the pin hogs claimed from there access registers. + */ + ret = of_clk_get_parent_count(node); + clk = devm_clk_get_enabled(&pdev->dev, ret == 1 ? NULL : "apb"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + /* + * Seed the output latch shadow from the hardware so pins the + * bootloader left in output mode keep their state; see + * sunxi_pinctrl_gpio_set() for why a shadow is needed. This must + * happen before the pinctrl device registers, as pin hogs can mux + * pins to gpio_out and thereby update the shadow. + */ + last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number; + nbanks = DIV_ROUND_UP(last_pin + 1 - pctl->desc->pin_base, + PINS_PER_BANK); + pctl->dat_shadow = devm_kcalloc(&pdev->dev, nbanks, + sizeof(*pctl->dat_shadow), GFP_KERNEL); + if (!pctl->dat_shadow) + return -ENOMEM; + + for (i = 0; i < nbanks; i++) { + u32 reg, shift, mask; + + sunxi_data_reg(pctl, i * PINS_PER_BANK, ®, &shift, &mask); + pctl->dat_shadow[i] = readl(pctl->membase + reg); + } + ret = sunxi_pinctrl_build_state(pdev); if (ret) { dev_err(&pdev->dev, "dt probe failed: %d\n", ret); @@ -1665,7 +1719,6 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, if (!pctl->chip) return -ENOMEM; - last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number; pctl->chip->owner = THIS_MODULE; pctl->chip->request = gpiochip_generic_request; pctl->chip->free = gpiochip_generic_free; @@ -1699,13 +1752,6 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, goto gpiochip_error; } - ret = of_clk_get_parent_count(node); - clk = devm_clk_get_enabled(&pdev->dev, ret == 1 ? NULL : "apb"); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - goto gpiochip_error; - } - pctl->irq = devm_kcalloc(&pdev->dev, pctl->desc->irq_banks, sizeof(*pctl->irq), diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h index 0daf7600e2fb..498e88a19f71 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.h +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h @@ -85,6 +85,7 @@ #define IO_BIAS_MASK GENMASK(3, 0) #define SUN4I_FUNC_INPUT 0 +#define SUN4I_FUNC_OUTPUT 1 #define SUN4I_FUNC_IRQ 6 #define SUN4I_FUNC_DISABLED_OLD 7 #define SUN4I_FUNC_DISABLED_NEW 15 @@ -175,6 +176,12 @@ struct sunxi_pinctrl { int *irq; unsigned *irq_array; raw_spinlock_t lock; + /* + * Output latch shadow, one word per bank. Seeded lockless at + * probe before the pinctrl device registers, protected by @lock + * afterwards. + */ + u32 *dat_shadow; struct pinctrl_dev *pctl_dev; unsigned long flags; u32 bank_mem_size; From cc337324cc6be1ce0ae7e5a068dcb7e99ae1b59c Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 14 Sep 2026 18:11:30 +0100 Subject: [PATCH 1140/1317] btrfs: fix creation of compressed inline extents that don't save space If the compressed data of an inline extent is larger than or equals to the size of the uncompressed data, we are still allowing the creation of the compressed inline extent, which does not result in any benefits, quite the contrary as we waste metadata space and have to decompress when reading. This is a recent regression introduced in commit 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper"). It happens because we are passing the block size to btrfs_compress_bio(), so we don't get -E2BIG from the compression code anymore, but we can not pass i_size either, because if i_size is smaller than sector size, we end up never creating lzo compressed inline extent for such small i_size values. So refuse the compressed result at run_delalloc_inline() if its size is not smaller than the uncompressed size (i_size). Reported-by: Hanabishi Link: https://lore.kernel.org/linux-btrfs/c97652a5-ac6b-4de6-aa23-3cdebc01d00b@gmail.com/ Fixes: 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper") CC: stable@vger.kernel.org # 7.1+ Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/inode.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 3668cbc7598e..4f976b52996d 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2339,12 +2339,27 @@ static int run_delalloc_inline(struct btrfs_inode *inode, struct folio *locked_f } else if (inode->prop_compress) { compress_type = inode->prop_compress; } + /* + * We need to pass blocksize and not i_size, otherwise we can't + * create compressed inline extents for data smaller than sector + * size with lzo. + */ cb = btrfs_compress_bio(inode, 0, blocksize, compress_type, compress_level, 0); if (IS_ERR(cb)) { cb = NULL; /* Just fall back to non-compressed case. */ } else { compressed_size = cb->bbio.bio.bi_iter.bi_size; + /* + * If we did not save space, it's pointless and wasteful + * to have an inline compressed extent, so fallback to + * an uncompressed inline extent. + */ + if (compressed_size >= i_size) { + cleanup_compressed_bio(cb); + cb = NULL; + compressed_size = 0; + } } } if (!can_cow_file_range_inline(inode, 0, i_size, compressed_size)) { From 76bf149cd0298544631e756670b89c399c7acbca Mon Sep 17 00:00:00 2001 From: Daniel Linjama Date: Wed, 16 Sep 2026 09:15:56 +0300 Subject: [PATCH 1141/1317] btrfs: handle lack of space when cleaning up verity items When enable_verity() hits the qgroup limit, rollback_verity() needs its own metadata reservation. When the qgroup limit or lack of space refuses the rollback, the whole filesystem is forced read-only even though the qgroup limit was for one subvolume only. Also orphan cleanup at the next mount fails the same way, so the leftover items are never removed: with -EDQUOT the subvolume stays unreachable, and with -ENOSPC on a full filesystem the next read-write mount fails. Start transactions with btrfs_start_transaction_fallback_global_rsv() in btrfs_orphan_cleanup(), drop_verity_items() and rollback_verity(). Those calls only delete items and free the space in the end, so they may use the global reserve and skip the qgroup limit, which avoids -ENOSPC and -EDQUOT. Fixes: 146054090b08 ("btrfs: initial fsverity support") Reviewed-by: Qu Wenruo Signed-off-by: Daniel Linjama Signed-off-by: David Sterba --- fs/btrfs/inode.c | 3 ++- fs/btrfs/verity.c | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 4f976b52996d..e97446fc9e10 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3892,7 +3892,8 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) if (ret) goto out; } - trans = btrfs_start_transaction(root, 1); + /* Only deletes the orphan. */ + trans = btrfs_start_transaction_fallback_global_rsv(root, 1); if (IS_ERR(trans)) { ret = PTR_ERR(trans); goto out; diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c index 4e0ab5842274..600337a84fbe 100644 --- a/fs/btrfs/verity.c +++ b/fs/btrfs/verity.c @@ -93,6 +93,20 @@ static loff_t merkle_file_pos(const struct inode *inode) return rounded; } +/* + * Start a transaction for removing verity items or the verity orphan. + * + * Like unlink, this only deletes items and frees space in the end, so the + * reservation may come from the global reserve when the filesystem is full + * (-ENOSPC) and is not subject to the qgroup limit (-EDQUOT). Otherwise a + * failed enable could never be cleaned up in either situation. + */ +static struct btrfs_trans_handle *start_verity_cleanup_trans(struct btrfs_root *root, + unsigned int num_items) +{ + return btrfs_start_transaction_fallback_global_rsv(root, num_items); +} + /* * Drop all the items for this inode with this key_type. * @@ -120,7 +134,7 @@ static int drop_verity_items(struct btrfs_inode *inode, u8 key_type) while (1) { /* 1 for the item being dropped */ - trans = btrfs_start_transaction(root, 1); + trans = start_verity_cleanup_trans(root, 1); if (IS_ERR(trans)) return PTR_ERR(trans); @@ -466,7 +480,7 @@ static int rollback_verity(struct btrfs_inode *inode) * 1 for updating the inode flag * 1 for deleting the orphan */ - trans = btrfs_start_transaction(root, 2); + trans = start_verity_cleanup_trans(root, 2); if (IS_ERR(trans)) { ret = PTR_ERR(trans); trans = NULL; From 8d836581f9b1f57ceaa2b47654754ef1260b410b Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 16 Sep 2026 15:00:32 +0200 Subject: [PATCH 1142/1317] ata: libata-scsi: fix ata_dsm_trim_pages() kernel-doc make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal Reviewed-by: Thomas Huth Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index b3666519b648..7e22bbc38238 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -2297,10 +2297,9 @@ static unsigned int ata_scsiop_inq_89(struct ata_device *dev, * logical block, so it can hold at most sector_size / 512 pages. * * Return: the maximum number of 512-byte pages a single translated WRITE SAME - * command may send to @dev (never less than one), that is the smaller of: - * - MAX PAGES PER DSM COMMAND (IDENTIFY DEVICE word 105), when the device - * reports a non-zero limit; and - * - the logical sector size expressed in 512-byte pages (see above). + * command may send to @dev, that is the smaller of MAX PAGES PER DSM COMMAND + * (IDENTIFY DEVICE word 105, when the device reports a non-zero limit) and + * the logical sector size expressed in 512-byte pages; never less than one. */ static unsigned int ata_dsm_trim_pages(struct ata_device *dev) { From e6cb0b4d4ecb8e71fd2200d907ab2e9663356f69 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 16 Sep 2026 05:29:26 +0500 Subject: [PATCH 1143/1317] hwmon: (hp-wmi-sensors) Fix use-after-free in fungible_show() nsensor->current_state is dynamically replaced as the sensor's state changes. update_numeric_sensor_from_wobj() does this by freeing the old string and installing a new one: if (strcmp(trimmed, nsensor->current_state)) { new_string = hp_wmi_strdup(dev, trimmed); if (new_string) { devm_kfree(dev, nsensor->current_state); nsensor->current_state = new_string; } } This function is only ever called from hp_wmi_update_info() while state->lock is held, so the free-and-replace itself is properly serialized against concurrent updates. fungible_show(), however, reads the same pointer after the lock has already been dropped: err = hp_wmi_update_info(state, info); if (err) return err; switch (prop) { ... case HP_WMI_PROPERTY_CURRENT_STATE: seq_printf(seqf, "%s\n", nsensor->current_state); break; hp_wmi_update_info() takes state->lock internally and releases it before returning, so by the time fungible_show() dereferences nsensor->current_state in seq_printf(), no lock is held. Two processes reading a sensor's current_state debugfs entry at overlapping times (or one reading it while another read of the same sensor triggers a refresh) can race: one thread's seq_printf() can be part-way through printing the string at the moment another thread's call into update_numeric_sensor_from_wobj() frees it with devm_kfree() and installs a new pointer, causing a use-after-free read. Take state->lock around the read in fungible_show() as well, so it can never run concurrently with the free-and-replace in update_numeric_sensor_from_wobj(). Fixes: 23902f98f8d4 ("hwmon: add HP WMI Sensors driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Acked-by: James Seo Link: https://patch.msgid.link/20260916002926.161595-1-meatuni001@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/hp-wmi-sensors.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index 03c684ba83bd..55aee16df57d 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -1247,7 +1247,9 @@ static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop) break; case HP_WMI_PROPERTY_CURRENT_STATE: + mutex_lock(&state->lock); seq_printf(seqf, "%s\n", nsensor->current_state); + mutex_unlock(&state->lock); break; case HP_WMI_PROPERTY_UNIT_MODIFIER: From 1d12fb94ac0975566545871dda100df34df5f845 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Tue, 15 Sep 2026 16:48:35 +0000 Subject: [PATCH 1144/1317] hwmon: (pmbus/tps53679) Fix TPS53676 phase page decoding tps53676_identify() reads the USER_DATA_03 phase configuration to count the phases assigned to each channel and derive the number of PMBus pages. In each 16-bit phase descriptor the channel (PAGE) is encoded in bit 4 and the firing order in bits 3:0, but the code tested bit 3 (0x08), which is part of the firing-order field. TPS53676 supports up to seven phases, so firing-order bit 3 is never set. As a result the existing test classifies every enabled phase as channel A. On a dual-channel configuration the phases assigned to channel B are therefore miscounted as channel A and page 1 is not exposed. Test the PAGE field (bit 4) instead. Fixes: cb3d37b59012 ("hwmon: (pmbus/tps53679) Add support for TI TPS53676") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://patch.msgid.link/20260915164823.160977-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tps53679.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index 31e54608b3c9..9f27832703ff 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -187,7 +187,7 @@ static int tps53676_identify(struct i2c_client *client, return -EIO; for (i = 0; i < 2 * TPS53676_MAX_PHASES; i += 2) { if (buf[i + 1] & 0x80) { - if (buf[i] & 0x08) + if (buf[i] & BIT(4)) phases_b++; else phases_a++; From d2710c8d938ae6a825a6463158e6e6f31eac792a Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:12 +0200 Subject: [PATCH 1145/1317] signal: Prevent exec() race Hyunwoo debugged the following KASAN UAF splat: BUG: KASAN: slab-use-after-free in __send_signal_locked+0xb27/0xba0 Write of size 8 at addr ffff888007ed80c8 by task poc/79 ... Call Trace: __send_signal_locked+0xb27/0xba0 do_send_sig_info+0xa7/0x160 do_send_specific+0x76/0xa0 __x64_sys_tgkill+0x193/0x270 ... Allocated by task 80: do_timer_create+0x1a4/0x1030 __x64_sys_timer_create+0x145/0x190 ... Freed by task 12: kmem_cache_free_bulk+0x1f8/0x4a0 kvfree_rcu_bulk+0x14f/0x1c0 kfree_rcu_work+0x128/0x1a0 ... Last potentially related work creation: kvfree_call_rcu+0x39/0x390 __flush_itimer_signals+0x211/0x320 flush_itimer_signals+0x47/0x90 begin_new_exec+0xa6b/0x28c0 It turned out that this happens with a non-leader exec() as Hyunwoo explained: de_thread() calls exchange_tids() before release_task(leader), so the struct pid held by a SIGEV_THREAD_ID timer created against the leader's tid now points to the thread which called execve(). pid_task() returns that thread and lock_task_sighand() on it succeeds. If the timer signal is blocked, its sigqueue stays queued on the leader's task::pending. The next expiry of that timer can then run while release_task() flushes the queue. posixtimer_send_sigqueue() checks whether the sigqueue is already queued with a plain list_empty(), which only reads list_head::next. list_del_init() is not atomic and INIT_LIST_HEAD() stores list_head::next before list_head::prev, so the check can pass in between. list_add_tail() queues the entry on the task::pending of the live thread, and the list_head::prev store from the flush then overwrites the list_head::prev link that list_add_tail() has just set. __flush_itimer_signals() does not undo that either. With list_head::prev pointing at the entry itself, its list_del_init() only stores the same values again, so the entry is not removed from the list. It is still there after the last reference is dropped and the timer is freed by RCU, and the list_add_tail() of a later tgkill() follows that list_head::prev into the freed timer. This problem surfaced with the recent commit which moved the sigqueue flush out of the sighand lock held region. Hyonwoo proposed to fix this by using list_del_init_careful(), but that just papers over the problem. After some disucssions and various attempts to solve it, Eric pointed out that there is no reason to flush task::pending late in release_task() and it should be done in exit_signals() already. As nothing can collect and deliver signals which are queued in a dying task's pending queue, there is no reason to delay it further. But it has to be ensured that no signals can be queued into it after that point. exit_signals() sets PF_EXITING in task::flags, which can be used as an indicator for this. Cure it by: - Preventing signal queueing for task private signals (PIDTYPE_PID) when the task has PF_EXITING set in __send_signal_locked() and in posixtimer_send_sigqueue(). - Protecting the unlocked setting of PF_EXITING in exit_signals() for the task group empty and the group exit case with sighand lock - Flushing task::pending signals right there. Optimize that by moving the whole pending list to an on-stack list head under sighand lock and free the signals without the lock held. There has been quite some discussion about the lockless flush and the non-leader exec case on weakly ordered systems. The problem is that a third party which tries to send a posix timer signal relies on the PID lookup to find the target task and that lookup might result in the new leader when the signal was originaly directed to the old leader. In case that the signal was queued on the old leader then the lockless flush raised a concern over the following situation: old_leader new_leader third party A: flush_list() // list_del_init() stores to sigqueue LOCK (tasklist) old_leader->exit_state = EXIT_ZOMBIE; B: UNLOCK (tasklist) C: LOCK (tasklist) if (old_leader->exit_state) transfer_tids() D: store PID posix_timer_send_sigqueue() // Observes #D so t = new_leader E: t = get_target() F: LOCK (sighand) G: if (list_empty(sigqueue)) list_add(sigqueue) The concern was that the third party might observe #D but not observe #A and therefore would proceed to #G while the list_del() stores (#A) in flush_list() are not visible yet, which could result in list corruption. That would be possible if looking at it solely from a RELEASE+ACQUIRE ordering point of view, but B-C is a UNLOCK+LOCK hand-over, which is not the same as RELEASE+ACQUIRE: RELEASE+ACQUIRE: RCpc, only the CPUs involved agree on the ordering UNLOCK+LOCK: RCtso, the hand-over is store-ordering As B-C is UNLOCK+LOCK, which is RCtso and that does impose store order, A stores must happen before the D store. Combine with E-F, which has a data dependency from the LOAD to the LOCK and thereby constraints later LOADs, those sigqueue loads in G that come after F must in fact observe the A stores. Fixes: fb3bbcfe344e ("exit: change the release_task() paths to call flush_sigqueue() lockless") Reported-by: Hyunwoo Kim Debugged-by: Hyunwoo Kim Suggested-by: "Eric W. Biederman" Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260911090541.572536604@kernel.org Closes: https://patch.msgid.link/aok1rdkBgZsynHZB@v4bel --- kernel/exit.c | 11 ++--- kernel/signal.c | 117 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 92 insertions(+), 36 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index 4e028f157597..424c44a42a4d 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -302,12 +302,13 @@ void release_task(struct task_struct *p) free_pids(post.pids); release_thread(p); /* - * This task was already removed from the process/thread/pid lists - * and lock_task_sighand(p) can't succeed. Nobody else can touch - * ->pending or, if group dead, signal->shared_pending. We can call - * flush_sigqueue() lockless. + * This task was already removed from the process/thread/pid lists and + * lock_task_sighand(p) can't succeed. If it's the group leader then + * flush tsk->signal->shared_pending. tsk->pending has been flushed + * already in exit_signals(). Nothing else can touch + * signal->shared_pending anymore, so flush_sigqueue() can be invoked + * lockless. */ - flush_sigqueue(&p->pending); if (thread_group_leader(p)) flush_sigqueue(&p->signal->shared_pending); diff --git a/kernel/signal.c b/kernel/signal.c index ec30550951ec..d31ebcb6ed4d 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -457,18 +457,42 @@ static void __sigqueue_free(struct sigqueue *q) kmem_cache_free(sigqueue_cachep, q); } -void flush_sigqueue(struct sigpending *queue) +/* + * flush_sigqueue_list() can only be invoked without holding sighand::siglock in + * the following cases: + * + * 1) When flushing task::pending _after_ setting task::flags PF_EXITING + * + * All functions which try to send a signal to @task will observe PF_EXITING + * and drop the signal. + * + * 2) When flushing task::signal::shared_pending _after_ the last task in a + * thread group was unhashed and task::sighand is NULL. + * + * Nothing can queue a signal anymore because sighand is NULL. + */ +static void flush_sigqueue_list(struct list_head *head) { - struct sigqueue *q; + struct sigqueue *q, *tmp; - sigemptyset(&queue->signal); - while (!list_empty(&queue->list)) { - q = list_entry(queue->list.next, struct sigqueue , list); + list_for_each_entry_safe(q, tmp, head, list) { list_del_init(&q->list); __sigqueue_free(q); } } +void flush_sigqueue(struct sigpending *queue) +{ + sigemptyset(&queue->signal); + flush_sigqueue_list(&queue->list); +} + +static void sigqueue_dequeue_pending(struct sigpending *queue, struct list_head *head) +{ + sigemptyset(&queue->signal); + list_splice_init(&queue->list, head); +} + /* * Flush all pending signals for this kthread. */ @@ -1019,6 +1043,21 @@ static inline bool legacy_queue(struct sigpending *signals, int sig) return (sig < SIGRTMIN) && sigismember(&signals->signal, sig); } +/* + * When PF_EXITING is set the task is on the way out and has t::pending + * flushed already. Prevent queueing of PIDTYPE_PID signals as they would + * be leaked. + */ +static inline bool task_can_queue_signal(struct task_struct *t, enum pid_type type) +{ + lockdep_assert_held(&t->sighand->siglock); + + if (!(t->flags & PF_EXITING)) + return true; + + return type != PIDTYPE_PID; +} + static int __send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type, bool force) { @@ -1030,6 +1069,10 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info, lockdep_assert_held(&t->sighand->siglock); result = TRACE_SIGNAL_IGNORED; + + if (!task_can_queue_signal(t, type)) + goto ret; + if (!prepare_signal(sig, t, force)) goto ret; @@ -1980,11 +2023,25 @@ static inline struct task_struct *posixtimer_get_target(struct k_itimer *tmr) struct task_struct *t = pid_task(tmr->it_pid, tmr->it_pid_type); if (t && tmr->it_pid_type != PIDTYPE_PID && - same_thread_group(t, current) && !current->exit_state) + same_thread_group(t, current) && !(current->flags & PF_EXITING)) t = current; return t; } +/* + * Find the target task for the POSIX timer signal and prevent that a + * PIDTYPE_PID signal is queued on a task which has PF_EXITING set. + */ +static inline struct task_struct *posixtimer_get_unignore_target(struct k_itimer *tmr) +{ + struct task_struct *t = posixtimer_get_target(tmr); + + if (t && task_can_queue_signal(t, tmr->it_pid_type)) + return t; + + return NULL; +} + void posixtimer_send_sigqueue(struct k_itimer *tmr) { struct sigqueue *q = &tmr->sigq; @@ -2002,6 +2059,9 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr) if (!likely(lock_task_sighand(t, &flags))) return; + if (!task_can_queue_signal(t, tmr->it_pid_type)) + goto unlock; + /* * Update @tmr::sigqueue_seq for posix timer signals with sighand * locked to prevent a race against dequeue_signal(). @@ -2093,6 +2153,7 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr) result = TRACE_SIGNAL_DELIVERED; out: trace_signal_generate(sig, &q->info, t, tmr->it_pid_type != PIDTYPE_PID, result); +unlock: unlock_task_sighand(t, &flags); } @@ -2148,7 +2209,7 @@ static void posixtimer_sig_unignore(struct task_struct *tsk, int sig) * has exited by now, drop the reference count. */ guard(rcu)(); - target = posixtimer_get_target(tmr); + target = posixtimer_get_unignore_target(tmr); if (target) posixtimer_queue_sigqueue(&tmr->sigq, target, tmr->it_pid_type); else @@ -3132,42 +3193,36 @@ static void retarget_shared_pending(struct task_struct *tsk, sigset_t *which) void exit_signals(struct task_struct *tsk) { + LIST_HEAD(sigq_list); int group_stop = 0; - sigset_t unblocked; /* * @tsk is about to have PF_EXITING set - lock out users which - * expect stable threadgroup. + * expect a stable threadgroup. */ cgroup_threadgroup_change_begin(tsk); - if (thread_group_empty(tsk) || (tsk->signal->flags & SIGNAL_GROUP_EXIT)) { + scoped_guard(spinlock_irq, &tsk->sighand->siglock) { tsk->flags |= PF_EXITING; - cgroup_threadgroup_change_end(tsk); - return; - } - spin_lock_irq(&tsk->sighand->siglock); - /* - * From now this task is not visible for group-wide signals, - * see wants_signal(), do_signal_stop(). - */ - tsk->flags |= PF_EXITING; + sigqueue_dequeue_pending(&tsk->pending, &sigq_list); + + if (task_sigpending(tsk) && !thread_group_empty(tsk) && + !(tsk->signal->flags & SIGNAL_GROUP_EXIT)) { + sigset_t unblocked = tsk->blocked; + + signotset(&unblocked); + retarget_shared_pending(tsk, &unblocked); + + if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && + task_participate_group_stop(tsk)) + group_stop = CLD_STOPPED; + } + } cgroup_threadgroup_change_end(tsk); - if (!task_sigpending(tsk)) - goto out; - - unblocked = tsk->blocked; - signotset(&unblocked); - retarget_shared_pending(tsk, &unblocked); - - if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && - task_participate_group_stop(tsk)) - group_stop = CLD_STOPPED; -out: - spin_unlock_irq(&tsk->sighand->siglock); + flush_sigqueue_list(&sigq_list); /* * If group stop has completed, deliver the notification. This From acb03d3881818581052924a9bbbe92b8741ed448 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Fri, 11 Sep 2026 11:09:17 +0200 Subject: [PATCH 1146/1317] exec: Cleanup POSIX timers right after de_thread() A per-thread CPU timer holds a reference to the PID of the thread it is attached to and, while it is armed, its node is queued in that thread's posix_cputimers. The task is looked up by that PID. When a non-leader thread exec()s, de_thread() changes which task owns that PID. pid_task(timer->it.cpu.pid, PIDTYPE_PID) then returns NULL, but the node is still queued on tsk, which is alive. timer_lock_sighand() takes a failed lookup to mean that the node is already dequeued, so it has nothing to undo. begin_new_exec() calls posix_cpu_timers_exit(me) right after exec_task_namespaces() and that removes the leftover node, so the state normally stays invisible. But bprm->point_of_no_return is set before de_thread(), so if unshare_files(), set_mm_exe_file(), exec_mmap() or exec_task_namespaces() fails, the task dies before it gets there. exit_itimers() then frees the k_itimer while its node is still queued, and reaping tsk later erases that freed node from the rbtree. In short: the non-leader thread B the parent timer_create(CLOCK_THREAD_CPUTIME_ID) timer_settime() arm_timer() // the node is queued on B execve() de_thread(B) exchange_tids(B, leader) // B's PID now belongs to the leader release_task(leader) __exit_signal(leader) posix_cpu_timers_exit(leader) // cleans leader's queue, not B's __unhash_process(leader) // that PID has no task anymore exec_mmap() mmap_read_lock_killable(old_mm) kill(B, SIGKILL) // -EINTR get_signal() do_exit() exit_itimers() posix_timer_delete() posix_cpu_timer_del() posix_timer_unhash_and_free() // freed while still queued wait4() release_task(B) posix_cpu_timers_exit(B) cleanup_timerqueue() timerqueue_del() // use-after-free Move the POSIX timer cleanup right after de_thread() before any of the later failure conditions brings the task into do_exit(). [ tglx: Move the cleanup right after de_thread() ] Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") Signed-off-by: Hyunwoo Kim Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org Link: https://patch.msgid.link/ao7Q8miiuLAPVnWv@v4bel Link: https://patch.msgid.link/20260911090541.627712075@kernel.org --- fs/exec.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index d3081c8f7c10..819643408e6d 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1115,6 +1115,17 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm) return bprm->file; } +static void posixtimer_exec(struct task_struct *me) +{ +#ifdef CONFIG_POSIX_TIMERS + spin_lock_irq(&me->sighand->siglock); + posix_cpu_timers_exit(me); + spin_unlock_irq(&me->sighand->siglock); + exit_itimers(me); + flush_itimer_signals(); +#endif +} + /* * Calling this is the point of no return. None of the failures will be * seen by userspace since either the process is already taking a fatal @@ -1152,6 +1163,16 @@ int begin_new_exec(struct linux_binprm * bprm) retval = de_thread(me); if (retval) goto out; + + /* + * This must be done here to ensure that POSIX CPU timers which were + * armed on the current task are dequeued from me::posix_cputimers. + * Otherwise in case of a TID switch the deletion of the related POSIX + * timer would not remove an enqueued timer because the TID lookup + * of the old TID fails. + */ + posixtimer_exec(me); + /* see the comment in check_unsafe_exec() */ current->fs->in_exec = 0; /* @@ -1206,14 +1227,6 @@ int begin_new_exec(struct linux_binprm * bprm) if (retval) goto out_unlock; -#ifdef CONFIG_POSIX_TIMERS - spin_lock_irq(&me->sighand->siglock); - posix_cpu_timers_exit(me); - spin_unlock_irq(&me->sighand->siglock); - exit_itimers(me); - flush_itimer_signals(); -#endif - /* * Make the signal table private. */ From ef56085dfd1df3a53ecce8a4ff440cf6d67f430d Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Mon, 14 Sep 2026 12:07:44 +0200 Subject: [PATCH 1147/1317] pinctrl: sunxi: A523: fix voltage withstand encoding The Allwinner A523 uses the same GPIO voltage "withstand" programming (setting the input level voltage thresholds) as the previous SoCs, but for some odd reason inverts the encoding of 1.8V vs. 3.3V. Add a new bias voltage type to note this difference, and select it for the A523. At the same time also use the newer "CTL" version, which in addition allows to turn off the withstand programming for I/O voltages other than exact 1.8V or 3.3V (for instance for 2.5V sometimes used for Ethernet PHYs). The A523 has that enable register, but didn't use it so far. This fixes eMMC and reportedly Ethernet operation on some A523 boards. Fixes: 648be4cd9517 ("pinctrl: sunxi: Add support for the Allwinner A523") Signed-off-by: Andre Przywara Tested-by: Per Larsson Tested-by: Juan Manuel Lopez Carrillo Reviewed-by: Chen-Yu Tsai Tested-by: Chen-Yu Tsai # Fixes eMMC on Orange Pi 4A Signed-off-by: Linus Walleij --- drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c | 2 +- drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c | 2 +- drivers/pinctrl/sunxi/pinctrl-sunxi.c | 6 ++++++ drivers/pinctrl/sunxi/pinctrl-sunxi.h | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c index 462aa1c4a5fa..e27e4945def2 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c +++ b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c @@ -26,7 +26,7 @@ static const u8 a523_r_irq_bank_muxes[SUNXI_PINCTRL_MAX_BANKS] = static struct sunxi_pinctrl_desc a523_r_pinctrl_data = { .irq_banks = ARRAY_SIZE(a523_r_irq_bank_map), .irq_bank_map = a523_r_irq_bank_map, - .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_SEL, + .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, .pin_base = PL_BASE, }; diff --git a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c index b6f78f1f30ac..88d8acd5bc24 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c +++ b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c @@ -26,7 +26,7 @@ static const u8 a523_irq_bank_muxes[SUNXI_PINCTRL_MAX_BANKS] = static struct sunxi_pinctrl_desc a523_pinctrl_data = { .irq_banks = ARRAY_SIZE(a523_irq_bank_map), .irq_bank_map = a523_irq_bank_map, - .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_SEL, + .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, }; static int a523_pinctrl_probe(struct platform_device *pdev) diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index 2881a83be99c..31cd142ce0f7 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -728,6 +728,7 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, { unsigned short bank; unsigned long flags; + bool inverted = false; u32 val, reg; int uV; @@ -766,6 +767,9 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, reg &= ~IO_BIAS_MASK; writel(reg | val, pctl->membase + sunxi_grp_config_reg(pin)); return 0; + case BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV: + inverted = true; + fallthrough; case BIAS_VOLTAGE_PIO_POW_MODE_CTL: val = uV > 1800000 && uV <= 2500000 ? BIT(bank) : 0; @@ -780,6 +784,8 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, fallthrough; case BIAS_VOLTAGE_PIO_POW_MODE_SEL: val = uV <= 1800000 ? 1 : 0; + if (inverted) + val = !val; raw_spin_lock_irqsave(&pctl->lock, flags); reg = readl(pctl->membase + pctl->pow_mod_sel_offset); diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h index 498e88a19f71..8bd00c6ff628 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.h +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h @@ -117,8 +117,10 @@ enum sunxi_desc_bias_voltage { * Bias voltage is set through PIO_POW_MOD_SEL_REG * and PIO_POW_MOD_CTL_REG register, as seen on * A100 and D1 SoC, for example. + * Some SoCs invert the encoding for 1.8V vs. 3.3V. */ BIAS_VOLTAGE_PIO_POW_MODE_CTL, + BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, }; struct sunxi_desc_function { From 1d9bb9c870632adea249cfdec49ac37e6f069164 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 00:03:12 -0400 Subject: [PATCH 1148/1317] pinctrl: single: free the IRQ on domain creation failure pcs_irq_init_chained_handler() requests a shared IRQ on affected SoCs, but its domain creation failure path only removes a chained handler. That does not release the action installed by request_irq(). The probe can continue without interrupt support while leaving the shared IRQ action registered. Use pcs_irq_free() to undo the appropriate type of handler registration. At this point pcs->domain is NULL, so the helper only releases the parent IRQ handler. Then mark the IRQ invalid, as the other initialization error paths already do, to prevent another release from a later probe unwind or remove. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 3e6cee1786a1 ("pinctrl: single: Add support for wake-up interrupts") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-single.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c index 4d5f85b7e6bb..e0eb7240a985 100644 --- a/drivers/pinctrl/pinctrl-single.c +++ b/drivers/pinctrl/pinctrl-single.c @@ -1628,7 +1628,8 @@ static int pcs_irq_init_chained_handler(struct pcs_device *pcs, &pcs_irqdomain_ops, pcs_soc); if (!pcs->domain) { - irq_set_chained_handler(pcs_soc->irq, NULL); + pcs_irq_free(pcs); + pcs_soc->irq = -1; return -EINVAL; } From e922bad8b2d5028c51a096d083fea41cd0987154 Mon Sep 17 00:00:00 2001 From: Itai Handler Date: Thu, 10 Sep 2026 20:48:32 +0300 Subject: [PATCH 1149/1317] spi: spi-zynqmp-gqspi: stop the controller on shutdown The driver has no ->shutdown, and platform_drv_shutdown() has no fallback of its own. Unlike pci_device_shutdown(), which clears bus mastering when kexec_in_progress, nothing on the platform bus disarms a device that can still write to memory. The normal kexec path never calls ->suspend either, so the quiesce in zynqmp_qspi_suspend() is not reached. A controller that is still executing a DMA read may therefore keep writing to memory across a kexec. QSPIDMA_DST_ADDR still points at memory owned by the kernel that called kexec, DST_SIZE is non-zero and the flash is still clocked, so data can keep landing in RAM while the new kernel is being relocated, and after it has started executing. That destination is a physical address which means nothing to the new kernel, so the writes can corrupt whatever now occupies it: kernel text or data, page tables, or the initrd. Nothing reports an error and the resulting behaviour is undefined. This can be observed by reading GQSPI_EN (offset 0x114) and QSPIDMA_DST_ADDR/SIZE/STS/CTRL (offsets 0x800 to 0x80c) early in the new kernel, before the driver probes: without this patch GQSPI_EN reads 1 and QSPIDMA_DST_ADDR still points into the previous kernel's memory. Add a ->shutdown that stops the controller the way zynqmp_qspi_suspend() already does. spi_controller_suspend() stops the queue, waits for a message that is already executing and makes any later transfer fail with -ESHUTDOWN, so nothing can be cut short by the register write that follows. It may sleep, which is fine here: device_shutdown() runs in process context. Unlike ->suspend this cannot abort on error, because a controller left mastering the bus is worse than a truncated transfer, so a failure to drain is only logged. GQSPI_EN_OFST is then cleared, as zynqmp_qspi_remove() and zynqmp_qspi_suspend() already do. Skip that write only when pm_runtime_get_if_in_use() returns 0, i.e. runtime suspended: the clocks are gated, so the registers are unreachable and the controller cannot be mastering the bus. A negative return is not the same thing - it is what the CONFIG_PM=n stub always returns, and there probe() has enabled pclk and refclk for good, so the controller is running and must be stopped. Fixes: dfe11a11d523 ("spi: Add support for Zynq Ultrascale+ MPSoC GQSPI controller") Cc: stable@vger.kernel.org Signed-off-by: Itai Handler Link: https://patch.msgid.link/20260910174832.873352-1-itai.handler@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-zynqmp-gqspi.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/spi/spi-zynqmp-gqspi.c b/drivers/spi/spi-zynqmp-gqspi.c index 4d55090fa443..15e9d3ef8839 100644 --- a/drivers/spi/spi-zynqmp-gqspi.c +++ b/drivers/spi/spi-zynqmp-gqspi.c @@ -1373,11 +1373,45 @@ static void zynqmp_qspi_remove(struct platform_device *pdev) clk_disable_unprepare(xqspi->pclk); } +static void zynqmp_qspi_shutdown(struct platform_device *pdev) +{ + struct zynqmp_qspi *xqspi = platform_get_drvdata(pdev); + int ret; + + /* + * Stop the queue and reject any later transfer first, so the write + * below cannot cut into a message that is still being executed. + * Unlike ->suspend this cannot abort on error: a controller left + * mastering the bus is worse than a truncated transfer. + */ + ret = spi_controller_suspend(xqspi->ctlr); + if (ret) + dev_warn(&pdev->dev, "could not stop the queue: %d\n", ret); + + /* + * Only a runtime suspended controller can be left alone: its clocks + * are gated, so it cannot be mastering the bus, and its registers + * must not be accessed either. Any other answer means it may be + * running and has to be stopped. In particular, on a kernel built + * without runtime PM this returns -EINVAL, and there the clocks + * enabled in probe() are never gated at all. + */ + ret = pm_runtime_get_if_in_use(&pdev->dev); + if (!ret) + return; + + zynqmp_gqspi_write(xqspi, GQSPI_EN_OFST, 0x0); + + if (ret > 0) + pm_runtime_put_noidle(&pdev->dev); +} + MODULE_DEVICE_TABLE(of, zynqmp_qspi_of_match); static struct platform_driver zynqmp_qspi_driver = { .probe = zynqmp_qspi_probe, .remove = zynqmp_qspi_remove, + .shutdown = zynqmp_qspi_shutdown, .driver = { .name = "zynqmp-qspi", .of_match_table = zynqmp_qspi_of_match, From d034e836eefd7ce75e588f7031cffbeec594f5ac Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 1 Sep 2026 09:04:13 +0800 Subject: [PATCH 1150/1317] smb: client: fix use-after-free of iface in cifs_try_adding_channels() cifs_try_adding_channels() iterates ses->iface_list with list_for_each_entry_safe_from(), which captures the next entry (niface) under iface_lock. The loop body then drops iface_lock for the whole duration of cifs_ses_add_channel(). A concurrent interface refresh (SMB3_request_interfaces() -> parse_server_interfaces()) marks all ifaces inactive and removes and frees any that are not re-advertised via list_del() + kref_put(), where release_iface() is a bare kfree(). Since niface typically has no channel holding a reference, the list reference is its last and it can be freed inside the unlocked window. On continue, the iterator advance step then dereferences niface->iface_head.next, and the loop body reads iface->rdma_capable/is_active, both on freed memory. Fix this by never keeping an unreferenced list pointer across the unlocked window. Each channel attempt now re-scans the list from the head under iface_lock, takes a kref on the selected candidate, and passes only that referenced candidate to cifs_ses_add_channel(). weight_fulfilled still tracks selection progress, so restarting the scan preserves the original weighted distribution and the weight_fulfilled-before-kref_put ordering on the failure path. Add a per-pass attempts cap so a flapping interface refresh cannot keep the inner loop spinning within a single tries increment. Fixes: aa45dadd34e4 ("cifs: change iface_list from array to sorted linked list") Cc: stable@vger.kernel.org Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Joseph Qi Acked-by: Shyam Prasad N Signed-off-by: Paulo Alcantara --- fs/smb/client/sess.c | 104 ++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index 7cf7dd104f7c..e095f41b5882 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -149,9 +149,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses) int old_chan_count, new_chan_count; int left; int rc = 0; - int tries = 0; + int tries = 0, attempts; size_t iface_weight = 0, iface_min_speed = 0; - struct cifs_server_iface *iface = NULL, *niface = NULL; + struct cifs_server_iface *iface = NULL, *candidate = NULL; struct cifs_server_iface *last_iface = NULL; spin_lock(&ses->chan_lock); @@ -197,67 +197,89 @@ int cifs_try_adding_channels(struct cifs_ses *ses) break; } - if (!iface) - iface = list_first_entry(&ses->iface_list, struct cifs_server_iface, - iface_head); last_iface = list_last_entry(&ses->iface_list, struct cifs_server_iface, iface_head); iface_min_speed = last_iface->speed; + spin_unlock(&ses->iface_lock); - list_for_each_entry_safe_from(iface, niface, &ses->iface_list, - iface_head) { - /* do not mix rdma and non-rdma interfaces */ - if (iface->rdma_capable != ses->server->rdma) - continue; + attempts = 0; + while (left > 0) { + spin_lock(&ses->iface_lock); - /* skip ifaces that are unusable */ - if (!iface->is_active || - (is_ses_using_iface(ses, iface) && - !iface->rss_capable)) - continue; + /* + * iface_lock must be dropped while opening a channel, + * and a concurrent interface refresh may remove and + * free entries during that window, so no list entry + * may be kept across it without a reference. Scan + * the list from the beginning each time and only pass + * a referenced candidate to cifs_ses_add_channel(); + * weight_fulfilled tracks the progress so that no + * iface is selected beyond its weight. + */ + candidate = NULL; + list_for_each_entry(iface, &ses->iface_list, iface_head) { + /* do not mix rdma and non-rdma interfaces */ + if (iface->rdma_capable != ses->server->rdma) + continue; - /* check if we already allocated enough channels */ - iface_weight = iface->speed / iface_min_speed; + /* skip ifaces that are unusable */ + if (!iface->is_active || + (is_ses_using_iface(ses, iface) && + !iface->rss_capable)) + continue; - if (iface->weight_fulfilled >= iface_weight) - continue; + /* check if we already allocated enough channels */ + iface_weight = iface->speed / iface_min_speed; - /* take ref before unlock */ - kref_get(&iface->refcount); + if (iface->weight_fulfilled >= iface_weight) + continue; + + /* take ref before unlock */ + kref_get(&iface->refcount); + candidate = iface; + break; + } + + if (!candidate) { + /* no usable iface. reset weight_fulfilled and start over */ + list_for_each_entry(iface, &ses->iface_list, iface_head) + iface->weight_fulfilled = 0; + spin_unlock(&ses->iface_lock); + break; + } + + attempts++; + if (attempts > 3 * ses->chan_max) { + kref_put(&candidate->refcount, release_iface); + spin_unlock(&ses->iface_lock); + break; + } spin_unlock(&ses->iface_lock); - rc = cifs_ses_add_channel(ses, iface); + rc = cifs_ses_add_channel(ses, candidate); spin_lock(&ses->iface_lock); if (rc) { cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n", - &iface->sockaddr, + &candidate->sockaddr, rc); /* failure to add chan should increase weight */ - iface->weight_fulfilled++; - kref_put(&iface->refcount, release_iface); + candidate->weight_fulfilled++; + kref_put(&candidate->refcount, release_iface); + spin_unlock(&ses->iface_lock); continue; } - iface->num_channels++; - iface->weight_fulfilled++; + candidate->num_channels++; + candidate->weight_fulfilled++; cifs_info("successfully opened new channel on iface:%pIS\n", - &iface->sockaddr); + &candidate->sockaddr); + spin_unlock(&ses->iface_lock); + + left--; + new_chan_count++; break; } - - /* reached end of list. reset weight_fulfilled and start over */ - if (list_entry_is_head(iface, &ses->iface_list, iface_head)) { - list_for_each_entry(iface, &ses->iface_list, iface_head) - iface->weight_fulfilled = 0; - spin_unlock(&ses->iface_lock); - iface = NULL; - continue; - } - spin_unlock(&ses->iface_lock); - - left--; - new_chan_count++; } return new_chan_count - old_chan_count; From a5e22cba3549b3b9ca592a6bc62329c9b85ce285 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Wed, 16 Sep 2026 18:18:03 +0800 Subject: [PATCH 1151/1317] ASoC: rt721: Reset codec to fix abnormal sound The audio output may become abnormal after a warm reboot from Windows. Reset the codec once during hardware initialization to restore it to a known state and prevent the issue. Signed-off-by: Oder Chiou Link: https://patch.msgid.link/20260916101803.2301508-1-oder_chiou@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 3 +++ sound/soc/codecs/rt721-sdca.c | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index eae7d662efae..910583162d3e 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -70,6 +70,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x0310100: case 0x2000000 ... 0x2000003: case 0x2000013: + case 0x2000026: case 0x200002c: case 0x200003c: case 0x2000046: @@ -142,6 +143,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x200000d: case 0x2000019: case 0x2000020: + case 0x2000026: case 0x200002c: case 0x2000030: case 0x2000046: @@ -155,6 +157,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810039: case 0x5b10018: case 0x5b10019: + case 0x6100006: return true; default: return false; diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index a9479d0e4941..738644018fb9 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -1497,6 +1497,15 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); } +static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) +{ + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_VD_HIDDEN_CTRL, RT721_HIDDEN_REG_SW_RESET, + RT721_HIDDEN_REG_SW_RESET); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_RESET_CTL, 0x1, 0x1); +} + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); @@ -1530,9 +1539,17 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) } pm_runtime_get_noresume(&slave->dev); + + if (!rt721->first_hw_init) + rt721_sdca_reset(rt721); + rt721_sdca_dmic_preset(rt721); rt721_sdca_amp_preset(rt721); rt721_sdca_jack_preset(rt721); + + if (rt721->hs_jack && (!rt721->first_hw_init)) + rt721_sdca_jack_init(rt721); + if (rt721->first_hw_init) { regcache_cache_bypass(rt721->regmap, false); regcache_mark_dirty(rt721->regmap); From 11fc0048a6930f4fca44fe3bd16a0023e78846a2 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sun, 13 Sep 2026 13:31:32 -0400 Subject: [PATCH 1152/1317] ASoC: ux500: Parenthesize MSP_{RX,TX}_CLKPOL_BIT() arguments arm allmodconfig fails to build with gcc: In file included from sound/soc/ux500/ux500_msp_i2s.c:20: sound/soc/ux500/ux500_msp_i2s.h:151:38: error: suggest parentheses around arithmetic in operand of '^' [-Werror=parentheses] sound/soc/ux500/ux500_msp_i2s.c:204:21: note: in expansion of macro 'MSP_TX_CLKPOL_BIT' cc1: all warnings being treated as errors The macros never parenthesized their argument: #define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) That went unnoticed while every caller passed a plain variable, but configure_protocol() now passes an XOR expression, which binds as "a ^ (b & MASK)" rather than "(a ^ b) & MASK", and gcc rightly complains. No functional change: tx_clk_pol and rx_clk_pol only ever hold MSP_FALLING_EDGE (0) or MSP_RISING_EDGE (1), and bclk_inverted is a bool, so masking before or after the XOR gives the same 0/1 result. Parenthesize the argument anyway - it fixes the build and stops the macros from silently mis-evaluating a future composite argument. Fixes: 9ccbacf5a012 ("ASoC: ux500: Validate MSP DAI configuration") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609051547.G9SJp8UQ-lkp@intel.com/ Assisted-by: LLM Signed-off-by: Sasha Levin Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260913173132.1172003-1-sashal@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_i2s.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 2bf2699bdc49..c66ef455e138 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -147,8 +147,8 @@ enum msp_direction { #define RCKPOL_MASK BIT(0) #define TCKPOL_MASK BIT(0) #define SPICKM_MASK (BIT(1) | BIT(0)) -#define MSP_RX_CLKPOL_BIT(n) ((n & RCKPOL_MASK) << RCKPOL_SHIFT) -#define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) +#define MSP_RX_CLKPOL_BIT(n) (((n) & RCKPOL_MASK) << RCKPOL_SHIFT) +#define MSP_TX_CLKPOL_BIT(n) (((n) & TCKPOL_MASK) << TCKPOL_SHIFT) #define P1ELEN_SHIFT 0 #define P1FLEN_SHIFT 3 From c17ae8c26eac16ad244daef44044d714f68a2ddc Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 15 Sep 2026 18:25:15 +0900 Subject: [PATCH 1153/1317] ASoC: hdmi-codec: Report a change when the channel status moves The put() callback of "IEC958 Playback Default" stores all 24 channel status bytes and then returns 0. The core notifies userspace only on a positive return, so a write that changes what the get() callback hands back is never announced, and a mixer holding the control open keeps showing the old value. Compare the stored bytes and return 1 when they move, the way snd_hda_spdif_default_put() does. The same shape is in img-spdif-out and uniperif_player. No board with this codec was to hand. The change is a comparison of driver state with no hardware behaviour in it, and mixer-test counts the missing notification as event_missing. Fixes: 7a8e1d44211e ("ASoC: hdmi-codec: Add iec958 controls") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260915092515.2638542-1-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/hdmi-codec.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index bc2c22436ba6..7aa50c5bd3df 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -426,10 +426,14 @@ static int hdmi_codec_iec958_default_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); + if (!memcmp(hcp->iec_status, ucontrol->value.iec958.status, + sizeof(hcp->iec_status))) + return 0; + memcpy(hcp->iec_status, ucontrol->value.iec958.status, sizeof(hcp->iec_status)); - return 0; + return 1; } static int hdmi_codec_iec958_mask_get(struct snd_kcontrol *kcontrol, From 03a5699a0a04309c597683967aaaf25d1e555ea2 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 18:47:12 +0800 Subject: [PATCH 1154/1317] ASoC: codecs: rt712-sdca-dmic: fix uninitialized stream_config->type stream_config is not initialized before being passed to sdw_stream_add_slave(). The type field may contain garbage and is later copied to stream->type by sdw_config_stream(). Zero-initialize stream_config so type defaults to SDW_STREAM_PCM. While at it, use snd_sdw_params_to_config() helper instead of open-coding the same logic. Fixes: 63a511284c9e ("ASoC: rt712-sdca: Add RT712 SDCA driver for Mic topology") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260914104712.379574-1-yijiangshan@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-dmic.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c index 8860d81134e7..a9f3aa4e143a 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.c +++ b/sound/soc/codecs/rt712-sdca-dmic.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "rt712-sdca.h" #include "rt712-sdca-dmic.h" @@ -632,10 +633,10 @@ static int rt712_sdca_dmic_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; struct sdw_stream_runtime *sdw_stream; - int retval, num_channels; + int retval; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -647,13 +648,8 @@ static int rt712_sdca_dmic_hw_params(struct snd_pcm_substream *substream, if (!rt712->slave) return -EINVAL; - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = SDW_DATA_DIR_TX; - - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = 2; retval = sdw_stream_add_slave(rt712->slave, &stream_config, From 3482062c786ce4233f8ed3224d824184f53ec154 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 14 Sep 2026 13:26:11 +0100 Subject: [PATCH 1155/1317] ASoC: cs-amp-lib: Prevent NULL pointer if efi variable is zero length In cs_amp_alloc_get_efi_variable() the first call to cs_amp_get_efi_variable() might return EFI_SUCCESS if the variable exists with zero length. Trap this and return -ENOENT to prevent returning an unexpected NULL pointer. The first cs_amp_get_efi_variable() call was assumed to return EFI_BUFFER_TOO_SMALL if the variable existed, but if instead it returned EFI_SUCCESS this would be converted to 0 by cs_amp_convert_efi_status() and then be returned as a NULL pointer. Fixes: 00fd40bc7acec ("ASoC: cs-amp-lib: Support Dell SSIDExV2 UEFI variable") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260914122611.2783563-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs-amp-lib.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index 41a9a5b005c6..9bc19d2e1639 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -317,6 +317,8 @@ static void *cs_amp_alloc_get_efi_variable(efi_char16_t *name, unsigned long size = 0; status = cs_amp_get_efi_variable(name, guid, NULL, &size, NULL); + if (status == EFI_SUCCESS) + return ERR_PTR(-ENOENT); if (status != EFI_BUFFER_TOO_SMALL) return ERR_PTR(cs_amp_convert_efi_status(status)); From 29218a4d11a31a8157389bc2b9e62dd768d7ea42 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:46 +0530 Subject: [PATCH 1156/1317] ASoC: amd: acp: bounds-check SoundWire link ID in machine drivers Add a bounds check in create_sdw_dailink() to validate that the SoundWire link ID derived from link_mask does not exceed the maximum supported by the platform. If the link ID is out of range or link_mask is zero, log an error and return -EINVAL to prevent accessing invalid CPU pin ID tables. Applied to both acp-sdw-sof-mach.c and acp-sdw-legacy-mach.c. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 10 ++++++++++ sound/soc/amd/acp/acp-sdw-sof-mach.c | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 6eac42bac855..2ea226a195c3 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -205,6 +205,16 @@ static int create_sdw_dailink(struct snd_soc_card *card, return -EINVAL; } + if (!soc_end->link_mask) { + dev_err(dev, "invalid zero link_mask\n"); + return -EINVAL; + } + if ((ffs(soc_end->link_mask) - 1) >= amd_ctx->max_sdw_links) { + dev_err(dev, "link_id %d exceeds max_sdw_links %d\n", + ffs(soc_end->link_mask) - 1, amd_ctx->max_sdw_links); + return -EINVAL; + } + switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask - 1), diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index a9cd1f335167..6c74e67b134f 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -121,6 +121,15 @@ static int create_sdw_dailink(struct snd_soc_card *card, return -EINVAL; } + if (!sof_end->link_mask) { + dev_err(dev, "invalid zero link_mask\n"); + return -EINVAL; + } + if ((ffs(sof_end->link_mask) - 1) >= amd_ctx->max_sdw_links) { + dev_err(dev, "link_id %d exceeds max_sdw_links %d\n", + ffs(sof_end->link_mask) - 1, amd_ctx->max_sdw_links); + return -EINVAL; + } switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask - 1), From 0b7d55d3a91200f2b1ed710f525a944b0a7d6369 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:47 +0530 Subject: [PATCH 1157/1317] ASoC: amd: acp: refactor codec config count in SOF SoundWire machine driver num_devs was used both as the endpoint count and as the output for asoc_sdw_parse_sdw_endpoints(), which overwrites it with the codec configuration count. Introduce a separate num_confs variable to hold the codec conf count so the two values remain distinct across codec_conf allocation and card->num_configs assignment. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 6c74e67b134f..b7926967593f 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -286,6 +286,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) int num_devs = 0; int num_ends = 0; int num_aux = 0; + int num_confs; int num_links; int be_id = 0; int ret; @@ -296,6 +297,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) return ret; } + num_confs = num_ends; /* One per DAI link, worst case is a DAI link for every endpoint */ struct asoc_sdw_dailink *sof_dais __free(kfree) = kzalloc_objs(*sof_dais, num_ends); @@ -312,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) if (!sof_aux) return -ENOMEM; - ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); if (ret < 0) return ret; @@ -324,7 +326,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) dev_dbg(dev, "sdw %d, dmic %d", sdw_be_num, dmic_num); - codec_conf = devm_kcalloc(dev, num_devs, sizeof(*codec_conf), GFP_KERNEL); + codec_conf = devm_kcalloc(dev, num_confs, sizeof(*codec_conf), GFP_KERNEL); if (!codec_conf) return -ENOMEM; @@ -335,7 +337,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) return -ENOMEM; card->codec_conf = codec_conf; - card->num_configs = num_devs; + card->num_configs = num_confs; card->dai_link = dai_links; card->num_links = num_links; card->aux_dev = sof_aux; From 27098aaf28b96ab4e6891709062c343566d4882b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:48 +0530 Subject: [PATCH 1158/1317] ASoC: amd: acp: fix ffs() operator precedence for SoundWire link ID ffs(link_mask - 1) computes ffs on (link_mask - 1) instead of subtracting 1 from the result of ffs(link_mask). For a typical power-of-2 link_mask this returns the wrong link ID, causing cpu_pin_id lookup to select the incorrect SoundWire manager. Fix the operator precedence to ffs(link_mask) - 1 in both acp-sdw-sof-mach.c and acp-sdw-legacy-mach.c. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 4 ++-- sound/soc/amd/acp/acp-sdw-sof-mach.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 2ea226a195c3..1a05d4288a46 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -217,7 +217,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: - ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask - 1), + ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; @@ -225,7 +225,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, case ACP70_PCI_REV: case ACP71_PCI_REV: case ACP72_PCI_REV: - ret = get_acp70_cpu_pin_id(ffs(soc_end->link_mask - 1), + ret = get_acp70_cpu_pin_id(ffs(soc_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index b7926967593f..e6d545fd665e 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -132,7 +132,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, } switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: - ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask - 1), + ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; @@ -140,7 +140,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, case ACP70_PCI_REV: case ACP71_PCI_REV: case ACP72_PCI_REV: - ret = get_acp70_cpu_pin_id(ffs(sof_end->link_mask - 1), + ret = get_acp70_cpu_pin_id(ffs(sof_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; From d57616f8be5601d210bbb0f677b9cb88a5186c3c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:49 +0530 Subject: [PATCH 1159/1317] ASoC: amd: acp: fix card name length warning in SOF SoundWire machine driver The ALSA snd_card driver[] field is 16 bytes (including the NUL terminator), leaving 15 usable characters. The SOF framework prepends a "sof-" prefix when registering the card, so card->name = "amd-soundwire" becomes driver name "sof-amd-soundwire" which is 17 characters and overflows the driver[16] buffer, triggering a kernel warning. Fix by shortening the card name to "amd-sdw"; the resulting driver name "sof-amd-sdw" fits within the 15-character limit. Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index e6d545fd665e..ec3e1f5f1052 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -390,7 +390,7 @@ static int mc_probe(struct platform_device *pdev) ctx->private = amd_ctx; card = &ctx->card; card->dev = &pdev->dev; - card->name = "amd-soundwire"; + card->name = "amd-sdw"; card->owner = THIS_MODULE; card->late_probe = asoc_sdw_card_late_probe; From 0030f62683d5061d43b80577b7ab27196f1adb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:35 +0200 Subject: [PATCH 1160/1317] ASoC: adau1977: make the Kconfig symbols user selectable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SND_SOC_ADAU1977_{SPI,I2C} are missing Kconfig text, so they don't show up in menuconfig and can't be selected by a user - only by another symbol such as a machine driver. Add the text to make these symbols selectable and usable with generic machine drivers like the simple audio card. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-1-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f9a47e262a77..d88593c2bac8 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -524,13 +524,13 @@ config SND_SOC_ADAU1977 tristate config SND_SOC_ADAU1977_SPI - tristate + tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - SPI" depends on SPI_MASTER select SND_SOC_ADAU1977 select REGMAP_SPI config SND_SOC_ADAU1977_I2C - tristate + tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - I2C" depends on I2C select SND_SOC_ADAU1977 select REGMAP_I2C From 528a0da3e55b24d1113b3658e94cf432e0020913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:36 +0200 Subject: [PATCH 1161/1317] ASoC: adau1977-spi: drop __maybe_unused and of_match_ptr() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 5ab23c7923a1 ("modpost: Create modalias for builtin modules") MODULE_DEVICE_TABLE() is enough to reference a match table and the data isn't discarded by the linker even when the driver is built-in and CONFIG_OF is disabled. Drop the of_match_ptr() wrapping so that OF matching keeps working regardless of CONFIG_OF. This also means we can drop __maybe_unused since it's always used. The entries in adau1977_spi_of_match were also erroneously indented with spaces - replace the indentation with tabs to conform with coding style. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-2-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1977-spi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/adau1977-spi.c b/sound/soc/codecs/adau1977-spi.c index 878cde9d1014..c98da5ba9e9e 100644 --- a/sound/soc/codecs/adau1977-spi.c +++ b/sound/soc/codecs/adau1977-spi.c @@ -53,18 +53,18 @@ static const struct spi_device_id adau1977_spi_ids[] = { }; MODULE_DEVICE_TABLE(spi, adau1977_spi_ids); -static const struct of_device_id adau1977_spi_of_match[] __maybe_unused = { - { .compatible = "adi,adau1977" }, - { .compatible = "adi,adau1978" }, - { .compatible = "adi,adau1979" }, - { }, +static const struct of_device_id adau1977_spi_of_match[] = { + { .compatible = "adi,adau1977" }, + { .compatible = "adi,adau1978" }, + { .compatible = "adi,adau1979" }, + { }, }; MODULE_DEVICE_TABLE(of, adau1977_spi_of_match); static struct spi_driver adau1977_spi_driver = { .driver = { .name = "adau1977", - .of_match_table = of_match_ptr(adau1977_spi_of_match), + .of_match_table = adau1977_spi_of_match, }, .probe = adau1977_spi_probe, .id_table = adau1977_spi_ids, From 76a8fe25b97881223976363044924d5cf0511749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:37 +0200 Subject: [PATCH 1162/1317] ASoC: adau1977-i2c: add OF match table for I2C MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like for SPI, the I2C driver needs an OF match table for the kernel to be able to automatically load the driver when built as a module. Add one. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-3-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1977-i2c.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c index d1c6c4ddf506..5a11cafdff36 100644 --- a/sound/soc/codecs/adau1977-i2c.c +++ b/sound/soc/codecs/adau1977-i2c.c @@ -34,9 +34,18 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); +static const struct of_device_id adau1977_i2c_of_match[] = { + { .compatible = "adi,adau1977" }, + { .compatible = "adi,adau1978" }, + { .compatible = "adi,adau1979" }, + { }, +}; +MODULE_DEVICE_TABLE(of, adau1977_i2c_of_match); + static struct i2c_driver adau1977_i2c_driver = { .driver = { .name = "adau1977", + .of_match_table = adau1977_i2c_of_match, }, .probe = adau1977_i2c_probe, .id_table = adau1977_i2c_ids, From 089070b51ccbac411462a30a454690274c6e4270 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Wed, 16 Sep 2026 23:54:17 +0000 Subject: [PATCH 1163/1317] hwmon: (pmbus/tps53679) Select page 0 for single-page TPS53676 tps53676_identify() derives the number of PMBus pages but does not ensure that page 0 is selected for single-page configurations. pmbus_set_page() does not update the PAGE register when info->pages is 1, so if boot firmware leaves PAGE set to another value subsequent register accesses may target the wrong page. For single-page devices, select page 0 explicitly. Fixes: cb3d37b59012 ("hwmon: (pmbus/tps53679) Add support for TI TPS53676") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://patch.msgid.link/20260916235406.681131-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tps53679.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index 9f27832703ff..6c25701b36fc 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -200,6 +200,15 @@ static int tps53676_identify(struct i2c_client *client, if (phases_b > 0) { info->pages = 2; info->phases[1] = phases_b; + } else { + /* + * pmbus_set_page() does not update the PAGE register on + * single-page devices, so select page 0 explicitly in case + * the boot firmware left the device on another page. + */ + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; } return 0; } From 92b68492eae701e5b0e9d142ffe229921af7b1fa Mon Sep 17 00:00:00 2001 From: James Seo Date: Wed, 16 Sep 2026 15:19:15 -0700 Subject: [PATCH 1164/1317] hwmon: (hp-wmi-sensors) Improve raw WMI string handling Commit c9ba59258094 ("hwmon: (hp-wmi-sensors) Fix failure to load on EliteDesk 800 G6") left out some logic for recognizing raw WMI strings in check_numeric_sensor_wobj(). This issue was reported by a user along with an incomplete and unsuitable proposed solution [1]. Add the missing logic and properly remedy the issue. Also slightly refactor how raw WMI strings are recognized elsewhere to make the intent that they should be treated as regular ACPI strings clearer. Reported-by: Muhammad Bilal Link: https://lore.kernel.org/linux-hwmon/20260916002907.161210-1-meatuni001@gmail.com/ [1] Fixes: c9ba59258094 ("hwmon: (hp-wmi-sensors) Fix failure to load on EliteDesk 800 G6") Signed-off-by: James Seo Link: https://patch.msgid.link/20260916221912.434119-5-james@equiv.tech Signed-off-by: Guenter Roeck --- drivers/hwmon/hp-wmi-sensors.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index 55aee16df57d..cfa0d6ad0fb3 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -526,14 +526,12 @@ static int check_wobj(const union acpi_object *wobj, for (prop = 0; prop <= last_prop; prop++) { type = elements[prop].type; valid_type = property_map[prop]; - if (type != valid_type) { - if (type == ACPI_TYPE_BUFFER && - valid_type == ACPI_TYPE_STRING && - is_raw_wmi_string(elements[prop].buffer.pointer, - elements[prop].buffer.length)) - continue; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[prop].buffer.pointer, + elements[prop].buffer.length)) + type = ACPI_TYPE_STRING; + if (type != valid_type) return -EINVAL; - } } return 0; @@ -579,6 +577,7 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, int prop = HP_WMI_PROPERTY_NAME; acpi_object_type valid_type; union acpi_object *elements; + union acpi_object *element; u32 elem_count; int last_prop; bool is_new; @@ -602,13 +601,20 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, elem_count > HP_WMI_MAX_PROPERTIES) return -EINVAL; - type = elements[HP_WMI_PROPERTY_SIZE].type; + element = &elements[HP_WMI_PROPERTY_SIZE]; + type = element->type; switch (type) { case ACPI_TYPE_INTEGER: is_new = true; last_prop = HP_WMI_PROPERTY_RATE_UNITS; break; + case ACPI_TYPE_BUFFER: + if (!is_raw_wmi_string(element->buffer.pointer, + element->buffer.length)) + return -EINVAL; + fallthrough; + case ACPI_TYPE_STRING: is_new = false; last_prop = HP_WMI_PROPERTY_CURRENT_READING; @@ -631,6 +637,10 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, for (i = 0; i < elem_count && prop <= last_prop; i++, prop++) { type = elements[i].type; valid_type = hp_wmi_property_map[prop]; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[i].buffer.pointer, + elements[i].buffer.length)) + type = ACPI_TYPE_STRING; if (type != valid_type) return -EINVAL; @@ -651,6 +661,10 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, /* PossibleStates[0] has already been type-checked. */ for (j = 0; i + 1 < elem_count && j + 1 < count; j++) { type = elements[++i].type; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[i].buffer.pointer, + elements[i].buffer.length)) + type = ACPI_TYPE_STRING; if (type != valid_type) return -EINVAL; } From 7f4a5ec6258fd7c92633ec4b0493fc51166d9398 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:08:30 -0400 Subject: [PATCH 1165/1317] net/sched: codel: bound the dropping loop per dequeue call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CoDel control law schedules the next drop one interval/sqrt(count) after the previous drop, using the configured interval (codel_params.interval). For very small intervals the scheduled step rounds down to zero, so the dropping loop in codel_dequeue() never advances and drains the entire backlog under the qdisc lock in one call - an unprivileged user can trigger a soft lockup this way. Fix in the shared codel code used by both codel and fq_codel: 1. Make the control-law step at least 1 tick so the dropping loop always moves forward. 2. Cap the dropping loop at CODEL_MAX_DROPS_PER_DEQUEUE (256) drops per codel_dequeue() call, resyncing drop_next to now when the cap is hit: the catch-up owed to the loop grows with the idle gap and the backlog, which no interval threshold can bound. This is a deliberate behaviour change after long idle gaps. The cap applies to fq_codel (4b549a2ef4be) and the mac80211 TXQ path (fixed interval, cap only). The target sojourn delay (codel_params.target) is not validated: it does not feed the control law, so a sub-tick value is aggressive rather than deadlock-prone. Conditions to recreate the bug: - tc qdisc add dev lo root handle 1: tbf rate 1kbit burst 2kb limit 1000000 - tc qdisc add dev lo parent 1:1 handle 10: codel interval 2us target 1ms noecn limit 1000000 (same for fq_codel) - unpatched kernel: tc accepts it; a UDP flood under the 1kbit tbf soft-lockups (watchdog: BUG: soft lockup) while one codel_dequeue() call drops the backlog under the qdisc lock - patched kernel: same setup, at most 256 drops per dequeue call, no soft lockup Testing: claim reproducer and interval 2us/3us variants run clean; tdc qdisc category passes (see the selftests patch). Fixes: 76e3cc126bb2 ("codel: Controlled Delay AQM") Reported-by: Vega Reviewed-by: Eric Dumazet Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/QDISC-1L5H.v1.20260912080102@mojatatu.com Signed-off-by: Jakub Kicinski --- include/net/codel.h | 5 +++++ include/net/codel_impl.h | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/net/codel.h b/include/net/codel.h index aa80f744826c..183d43c2bd43 100644 --- a/include/net/codel.h +++ b/include/net/codel.h @@ -140,6 +140,11 @@ struct codel_vars { /* needed shift to get a Q0.32 number from rec_inv_sqrt */ #define REC_INV_SQRT_SHIFT (32 - REC_INV_SQRT_BITS) +/* Cap on drops per codel_dequeue() call: the loop's work depends on the + * idle gap and backlog, both outside our control; resync when exceeded. + */ +#define CODEL_MAX_DROPS_PER_DEQUEUE 256 + /** * struct codel_stats - contains codel shared variables and stats * @maxpacket: largest packet we've seen so far diff --git a/include/net/codel_impl.h b/include/net/codel_impl.h index 2c1f0ec309e9..8f26132d45b7 100644 --- a/include/net/codel_impl.h +++ b/include/net/codel_impl.h @@ -93,12 +93,17 @@ static void codel_Newton_step(struct codel_vars *vars) * CoDel control_law is t + interval/sqrt(count) * We maintain in rec_inv_sqrt the reciprocal value of sqrt(count) to avoid * both sqrt() and divide operation. + * + * Clamp the increment to at least 1 tick: a very small interval (or a + * large count) can truncate it to zero, stalling the dropping loop. */ static codel_time_t codel_control_law(codel_time_t t, codel_time_t interval, u32 rec_inv_sqrt) { - return t + reciprocal_scale(interval, rec_inv_sqrt << REC_INV_SQRT_SHIFT); + return t + max_t(u32, 1, + reciprocal_scale(interval, + rec_inv_sqrt << REC_INV_SQRT_SHIFT)); } static bool codel_should_drop(const struct sk_buff *skb, @@ -154,6 +159,7 @@ static struct sk_buff *codel_dequeue(void *ctx, codel_skb_dequeue_t dequeue_func) { struct sk_buff *skb = dequeue_func(vars, ctx); + unsigned int drops = 0; codel_time_t now; bool drop; @@ -180,6 +186,14 @@ static struct sk_buff *codel_dequeue(void *ctx, */ while (vars->dropping && codel_time_after_eq(now, vars->drop_next)) { + if (++drops > CODEL_MAX_DROPS_PER_DEQUEUE) { + /* fell far behind the schedule */ + WRITE_ONCE(vars->drop_next, + codel_control_law(now, + params->interval, + vars->rec_inv_sqrt)); + break; + } /* dont care of possible wrap * since there is no more divide. */ From f6fb2ac5e19ae4b66112a698050db80e51f841c3 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:08:31 -0400 Subject: [PATCH 1166/1317] selftests/tc-testing: add codel/fq_codel interval boundary cases Add tdc cases locking the codel/fq_codel small-interval uAPI after the dropping-loop bound (previous patch): sub-tick and two-tick intervals are ACCEPTED (the loop bound makes them safe), the 1024us boundary is accepted, and a sub-tick target sojourn delay is accepted (it does not participate in the control law): codel: 6e44/a8c3/a695/9793 - interval 1us/3us/1024us and target 1us accepted (rendered 0us/2us/1.02ms/0us by tc) fq_codel: 1b4d/3540/49c5/3e0f - interval 1us/3us/1024us and target 1us accepted The positive cases match the full rendered qdisc line (tc renders interval 1us as 0us, 3us as 2us, 1024us as 1.02ms), mirroring the existing tests in these files. These cases do not test the dropping-loop bound itself: tdc cannot observe per-dequeue drop counts. c797 (fq_codel target 1 interval 1) passes unmodified on the patched kernel, which is the uAPI evidence for the previous patch. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-1L5H.v1.20260912080102@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/qdiscs/codel.json | 72 +++++++++++++++++++ .../tc-testing/tc-tests/qdiscs/fq_codel.json | 72 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json index 6d515d0e5ed6..a894e6f0e267 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json @@ -213,5 +213,77 @@ "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1p target 5ms interval 100ms", "matchCount": "1", "teardown": ["$TC qdisc del dev $DEV1 handle 1: root"] + }, + { + "id": "6e44", + "name": "Create CODEL with 1us interval, accepted (sub-tick, uAPI locked)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 0us", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "a8c3", + "name": "Create CODEL with 3us interval, accepted (two ticks)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 3us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 2us", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "a695", + "name": "Create CODEL with 1024us interval boundary accepted", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 1024us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 1.02ms", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "9793", + "name": "Create CODEL with 1us target, accepted (target not in control law)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel target 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 0us interval 100ms", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] } ] diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json index 4ce62b857fd7..de6a1b8d954a 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json @@ -316,5 +316,77 @@ "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 1p flows 1024 quantum.*target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64", "matchCount": "1", "teardown": ["$TC qdisc del dev $DEV1 handle 1: root"] + }, + { + "id": "1b4d", + "name": "Create FQ_CODEL with 1us interval, accepted (sub-tick, uAPI locked)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 0us memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "3540", + "name": "Create FQ_CODEL with 3us interval, accepted (two ticks)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 3us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 2us memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "49c5", + "name": "Create FQ_CODEL with 1024us interval boundary accepted", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 1024us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 1.02ms memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "3e0f", + "name": "Create FQ_CODEL with 1us target, accepted (target not in control law)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel target 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 0us interval 100ms memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] } ] From 88f113634028ca90a857031837d8061d1a9e1a7b Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:05:11 +0000 Subject: [PATCH 1167/1317] watchdog: sp5100_tco: Fix pci_dev reference leak in sp5100_tco_init() sp5100_tco_init() stores the PCI device matched by for_each_pci_dev() in the global sp5100_tco_pci and keeps its reference for the lifetime of the driver, but neither sp5100_tco_exit() nor the error paths of sp5100_tco_init() call pci_dev_put(), leaking the reference on driver registration failure and on every module load/unload cycle. Drop the reference when the platform driver or device registration fails and when the module is unloaded. Fixes: 15e28bf13008 ("watchdog: Add support for sp5100 chipset TCO") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260916170511.2086199-1-vulab@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/sp5100_tco.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 7e99c3b1f367..72ad59649ccc 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -605,8 +605,10 @@ static int __init sp5100_tco_init(void) pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n"); err = platform_driver_register(&sp5100_tco_driver); - if (err) + if (err) { + pci_dev_put(sp5100_tco_pci); return err; + } sp5100_tco_platform_device = platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0); @@ -619,6 +621,7 @@ static int __init sp5100_tco_init(void) unreg_platform_driver: platform_driver_unregister(&sp5100_tco_driver); + pci_dev_put(sp5100_tco_pci); return err; } @@ -626,6 +629,7 @@ static void __exit sp5100_tco_exit(void) { platform_device_unregister(sp5100_tco_platform_device); platform_driver_unregister(&sp5100_tco_driver); + pci_dev_put(sp5100_tco_pci); } module_init(sp5100_tco_init); From a9ce4053dc945c5372dedba5017ee675b30dc0c5 Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 17:14:09 -0700 Subject: [PATCH 1168/1317] net: lan743x: fix RX checksum use-after-free lan743x_rx_process_buffer() adds each non-first receive buffer to the head skb's frag_list. On the last descriptor, lan743x_rx_trim_skb() linearizes the head and frees the fragment skb metadata. The checksum-success path then writes ip_summed through the local skb pointer, which still points to the final fragment. This causes a use-after-free write when a packet spans more than one receive buffer. Set ip_summed on the surviving head skb instead. Multi-buffer receive can occur after a live MTU increase because existing ring entries keep their old buffer size until they are replenished. A KUnit test invoking lan743x_rx_process_buffer() with a two-buffer packet produced a one-byte KASAN use-after-free write before this change. The same test passed after the change. The driver object also builds with W=1. This was not tested on physical LAN743x hardware. Fixes: cd6910501cfd ("net: lan743x: Add support for Rx IP & TCP checksum offload") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Chenguang Zhao Link: https://patch.msgid.link/20260913-b4-send-lan743x-uaf-v1-1-73d563d08ba9@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/lan743x_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c index 24ae56a3c9ed..82d3ec20ba25 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.c +++ b/drivers/net/ethernet/microchip/lan743x_main.c @@ -2604,7 +2604,7 @@ static int lan743x_rx_process_buffer(struct lan743x_rx *rx) rx->adapter->netdev); if (rx->adapter->netdev->features & NETIF_F_RXCSUM) { if (!is_ice && !is_tce && !is_icsm) - skb->ip_summed = CHECKSUM_UNNECESSARY; + rx->skb_head->ip_summed = CHECKSUM_UNNECESSARY; } netdev_dbg(netdev, "sending %d byte frame to OS", rx->skb_head->len); From 33ff111d7ba3beb86e28938d6382bb5beabd865a Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:28:08 +0000 Subject: [PATCH 1169/1317] net/packet: clear RX owner on VNET header error Commit 61fad6816fc1 ("net/packet: tpacket_rcv: avoid a producer race condition") added rx_owner_map and made tpacket_rcv() claim a V1 or V2 ring slot before converting the virtio-net header. If the conversion fails, the drop path leaves the slot claimed. With a one-frame TPACKET_V2 ring, an unsupported UDP GSO packet leaves the only slot unavailable, so the ring also drops the next valid packet. Clear the ownership bit on this error path. TPACKET_V3 already clears its block state here. Fixes: 61fad6816fc1 ("net/packet: tpacket_rcv: avoid a producer race condition") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260913-b4-send-packet-vnet-v1-1-5545ffb528ae@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 76bde7906d49..50cae32ae269 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2384,7 +2384,9 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, virtio_net_hdr_from_skb(skb, h.raw + macoff - sizeof(struct virtio_net_hdr), vio_le(), true, 0)) { - if (po->tp_version == TPACKET_V3) + if (po->tp_version <= TPACKET_V2) + __clear_bit(slot_id, po->rx_ring.rx_owner_map); + else prb_clear_blk_fill_status(&po->rx_ring); goto drop_n_account; } From 60404266ef3e0a1cd8f7a164060e0c83efb72f4b Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:30:05 +0000 Subject: [PATCH 1170/1317] mptcp: return sk_wait_data() errors from recvmsg() Commit 581302298524 ("mptcp: error out earlier on disconnect") made mptcp_recvmsg() stop when sk_wait_data() returns an error. The error is stored in err, but the function then jumps to a path which returns copied. When no data was copied, recvmsg() therefore returns zero and reports a false EOF. Store the result in copied, which is the value returned by the function. This also keeps the usual partial-read result when data was copied before the error. A recvmsg() blocked in one thread reproduces the issue when another thread disconnects the same MPTCP socket with connect(AF_UNSPEC). Before this change recvmsg() returns zero; afterwards it returns -EPIPE. Fixes: 581302298524 ("mptcp: error out earlier on disconnect") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260913-b4-send-mptcp-recv-error-v1-1-4eaa3684a8b8@gmail.com Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 0098e2830931..8dc25ef1542c 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2459,7 +2459,7 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, mptcp_cleanup_rbuf(msk, copied); err = sk_wait_data(sk, &timeo, last); if (err < 0) { - err = copied ? : err; + copied = copied ? : err; goto out_err; } } From 37213e61120297920ae4c937fcb326a360da5084 Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:31:08 +0000 Subject: [PATCH 1171/1317] net/packet: avoid truncating TPACKET_V3 private size tpacket_req3.tp_sizeof_priv is an unsigned int, and packet_set_ring() validates the full value against the block size. init_prb_bdqc() then stores it in the unsigned short blk_sizeof_priv field. Commit 2b6867c2ce76 ("net/packet: fix overflow in check for priv area size") fixed the validation arithmetic, but an accepted value above USHRT_MAX still narrows when it is stored. For a 131072-byte block, tp_sizeof_priv=65536 is valid. The narrowing makes offset_to_first_pkt 48 instead of 65584, so packet records can be placed in the private area that userspace asked the kernel to preserve. blk_sizeof_priv is internal state, so widen it to hold the validated UAPI value. Fixes: f6fb8f100b80 ("af-packet: TPACKET_V3 flexible buffer implementation.") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260913-b4-send-packet-private-v1-1-925eab2cd388@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/packet/internal.h b/net/packet/internal.h index b76e645cd78d..f5c8cd0eed2c 100644 --- a/net/packet/internal.h +++ b/net/packet/internal.h @@ -21,7 +21,7 @@ struct tpacket_kbdq_core { unsigned int hdrlen; unsigned char reset_pending_on_curr_blk; unsigned short kactive_blk_num; - unsigned short blk_sizeof_priv; + unsigned int blk_sizeof_priv; unsigned short version; From 150dba2c69e93302af24a0c868eebe4871e2e107 Mon Sep 17 00:00:00 2001 From: Farhad Alemi Date: Sat, 12 Sep 2026 07:40:09 +0000 Subject: [PATCH 1172/1317] net: remove WARN_ON_ONCE() from the dev_fill_forward_path() loop check ipip_fill_forward_path() and ip6_tnl_fill_forward_path() look up the route to the tunnel's remote endpoint and set ctx->dev to its device, which is the tunnel itself when that route resolves back to the tunnel. dev_fill_forward_path() then makes no progress and trips WARN_ON_ONCE(last_dev == ctx->dev) as soon as a flowtable tries to offload a flow through the tunnel. That routing loop is a configuration any CAP_NET_ADMIN user can set up, and ip_tunnel_xmit() and ip6_tnl_xmit() already treat it as a tx error, so remove the warning and just fail the walk, as commit 008e7a7c293b ("net: remove WARN_ON_ONCE when accessing forward path array") did for the path stack overflow. Fixes: ab427db17885 ("netfilter: flowtable: Add IPIP rx sw acceleration") Fixes: d98103575dcd ("netfilter: flowtable: Add IP6IP6 rx sw acceleration") Closes: https://lore.kernel.org/all/CA+0ovCgaRvbd0Udj70b2xxG8Cx3CaCpNhnf1V4RWQuDveZYZhA@mail.gmail.com/ Suggested-by: Pablo Neira Ayuso Signed-off-by: Farhad Alemi Reviewed-by: Xuanqiang Luo Link: https://patch.msgid.link/CA+0ovCgKDOk+Bg6Gh5Lwx94u_jJjQ30-vY1JcY2BYfhnWJJbPA@mail.gmail.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index ecfbd72d5d1a..c67900354fa6 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -789,7 +789,7 @@ int dev_fill_forward_path(struct net_device_path_ctx *ctx, goto err_out; stack->num_paths++; - if (WARN_ON_ONCE(last_dev == ctx->dev)) + if (last_dev == ctx->dev) goto err_out; } From 5ae916fabca141b79b32e2e57f3c915c0f1e1b2e Mon Sep 17 00:00:00 2001 From: Yige Jiang Date: Sun, 13 Sep 2026 14:41:02 +0800 Subject: [PATCH 1173/1317] net: netsec: fix device_node reference leak on phy_np netsec_of_probe() takes a reference on the PHY device_node with of_parse_phandle() and stores it in priv->phy_np, but the driver never drops it. One device_node reference is leaked per probe, on the success path as well as on every error path reached after netsec_of_probe(). Neither consumer takes ownership. of_mdio_parse_addr() is a static inline taking a const struct device_node * that only reads the "reg" property. of_phy_connect() borrows as well: of_phy_get_and_connect() in drivers/net/mdio/of_mdio.c brackets its own call with of_node_get() at :364 and of_node_put() at :373, which would be a double put if of_phy_connect() consumed the reference. The node is still in use at netsec_netdev_open() time, where it is passed to of_phy_connect(), so it has device lifetime. Release it at the probe error label, which every failure path after the acquire funnels through, and in netsec_remove(). Both releases precede free_netdev(), since priv is netdev_priv(ndev). The ACPI probe path leaves priv->phy_np NULL and of_node_put(NULL) is a no-op. There is no end-user visible symptom on currently supported platforms: a device_node is only freed once OF_DYNAMIC is enabled and the node has been detached, so on a static device tree the imbalance is inert. It is observable as a refcount that grows across bind/unbind cycles, and would matter under device tree overlays. Found by static analysis of reference acquire/release pairing rather than from a runtime report. No reproducer was produced and the change has not been runtime tested; it is compile-tested only (arm64, CONFIG_SNI_NETSEC=m via COMPILE_TEST). Fixes: 533dd11a12f6 ("net: socionext: Add Synquacer NetSec driver") Signed-off-by: Yige Jiang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260913064102.37452-1-yigejiang86@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/socionext/netsec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/socionext/netsec.c b/drivers/net/ethernet/socionext/netsec.c index d14a6584473c..79a0a324c921 100644 --- a/drivers/net/ethernet/socionext/netsec.c +++ b/drivers/net/ethernet/socionext/netsec.c @@ -2149,6 +2149,7 @@ static int netsec_probe(struct platform_device *pdev) pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); free_ndev: + of_node_put(priv->phy_np); free_netdev(ndev); dev_err(&pdev->dev, "init failed\n"); @@ -2166,6 +2167,7 @@ static void netsec_remove(struct platform_device *pdev) netif_napi_del(&priv->napi); pm_runtime_disable(&pdev->dev); + of_node_put(priv->phy_np); free_netdev(priv->ndev); } From 490599ab23134962a6d18a024e84541d77bdb999 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 14 Sep 2026 19:23:27 -0700 Subject: [PATCH 1174/1317] eth: fbnic: ring the doorbell if a burst ends in a drop fbnic_tx_map() skips the doorbell write, and the completion request, for every packet handed to it with xmit_more set, counting on the packet which ends the burst to publish them all. When that packet is dropped instead - skb_put_padto(), skb_cow_head() or a DMA mapping failure - nothing rings. The descriptors of the preceding packets stay invisible to the HW until the next transmit on that queue, which for a burst-then-idle workload may never come. Remember the meta descriptor of the last packet left without a doorbell and flush it from the error paths. The completion request has to be set on that descriptor rather than simply writing the tail, otherwise the HW would transmit the packets but never report a head, and the ring would fill up and stall for good. This is very similar to Joe's recent series of fixes for bnxt. Not seen in real life, reproduced under QEMU with failure injection. Fixes: 9a57bacd574b ("eth: fbnic: Add basic Tx handling") Reviewed-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915022327.913218-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 42 +++++++++++++++----- drivers/net/ethernet/meta/fbnic/fbnic_txrx.h | 9 ++++- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index 401f8b8ae1ca..e7918d3f6aba 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -311,6 +311,29 @@ fbnic_rx_csum(u64 rcd, struct sk_buff *skb, struct fbnic_ring *rcq, } } +static void fbnic_tx_doorbell(struct fbnic_ring *ring, __le64 *meta) +{ + *meta |= cpu_to_le64(FBNIC_TWD_FLAG_REQ_COMPLETION); + ring->deferred_meta = -1; + + /* Force DMA writes to flush before writing to tail */ + dma_wmb(); + + writel(ring->tail, ring->doorbell); +} + +/* Packets handed to us with xmit_more set are left in the ring without a + * doorbell, and without a completion request, in the expectation that the + * packet ending the burst will ring for all of them. If that packet gets + * dropped instead we have to ring here, otherwise the descriptors sit in + * the ring until the next transmit, which may never come. + */ +static void fbnic_tx_flush_doorbell(struct fbnic_ring *ring) +{ + if (ring->deferred_meta >= 0) + fbnic_tx_doorbell(ring, &ring->desc[ring->deferred_meta]); +} + static bool fbnic_tx_map(struct fbnic_ring *ring, struct sk_buff *skb, __le64 *meta) { @@ -378,14 +401,10 @@ fbnic_tx_map(struct fbnic_ring *ring, struct sk_buff *skb, __le64 *meta) /* Verify there is room for another packet */ fbnic_maybe_stop_tx(skb->dev, ring, FBNIC_MAX_SKB_DESC); - if (fbnic_tx_sent_queue(skb, ring)) { - *meta |= cpu_to_le64(FBNIC_TWD_FLAG_REQ_COMPLETION); - - /* Force DMA writes to flush before writing to tail */ - dma_wmb(); - - writel(tail, ring->doorbell); - } + if (fbnic_tx_sent_queue(skb, ring)) + fbnic_tx_doorbell(ring, meta); + else + ring->deferred_meta = meta - ring->desc; return false; dma_error: @@ -425,8 +444,10 @@ fbnic_xmit_frame_ring(struct sk_buff *skb, struct fbnic_ring *ring) * otherwise try next time */ desc_needed = skb_shinfo(skb)->nr_frags + 10; - if (fbnic_maybe_stop_tx(skb->dev, ring, desc_needed)) + if (fbnic_maybe_stop_tx(skb->dev, ring, desc_needed)) { + fbnic_tx_flush_doorbell(ring); return NETDEV_TX_BUSY; + } *meta = cpu_to_le64(FBNIC_TWD_FLAG_DEST_MAC); @@ -447,6 +468,8 @@ fbnic_xmit_frame_ring(struct sk_buff *skb, struct fbnic_ring *ring) err_free: dev_kfree_skb_any(skb); err_count: + fbnic_tx_flush_doorbell(ring); + u64_stats_update_begin(&ring->stats.syncp); ring->stats.dropped++; u64_stats_update_end(&ring->stats.syncp); @@ -2491,6 +2514,7 @@ static void fbnic_enable_twq0(struct fbnic_ring *twq) fbnic_ring_wr32(twq, FBNIC_QUEUE_TWQ0_CTL, FBNIC_QUEUE_TWQ_CTL_RESET); twq->tail = 0; twq->head = 0; + twq->deferred_meta = -1; /* Store descriptor ring address and size */ fbnic_ring_wr32(twq, FBNIC_QUEUE_TWQ0_BAL, lower_32_bits(twq->dma)); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h index e03c9d2c38dc..f5899446dcc5 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h @@ -128,9 +128,14 @@ struct fbnic_ring { /* Rx BDQs only */ struct page_pool *page_pool; - /* Deferred_head is used to cache the head for TWQ1 if + /* TWQ0 only, index of the meta descriptor of the last packet + * placed in the ring without ringing the doorbell, -1 if the + * doorbell is in sync with the tail. + */ + s32 deferred_meta; + + /* TCQ only, used to cache the head for TWQ1 if * an attempt is made to clean TWQ1 with zero napi_budget. - * We do not use it for any other ring. */ s32 deferred_head; }; From 9ed55f3dbef4f4adfe65eb03b0c35c53229a8490 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 15 Sep 2026 04:30:54 +0000 Subject: [PATCH 1175/1317] net: lock the socket in sock_gettstamp() sk->sk_flags must only be changed while holding the socket lock, because sock_set_flag() and sock_reset_flag() use non atomic operations (__set_bit() and __clear_bit()). sock_gettstamp() is one of the last places where a bit of sk->sk_flags is changed from a syscall without owning the socket lock, through sock_enable_timestamp(sk, SOCK_TIMESTAMP). sk_set_memalloc() and sk_clear_memalloc() also change sk->sk_flags without the socket lock, but their callers (nbd, iscsi_tcp, nvme-tcp, sunrpc, wireguard) need a careful audit, this will be addressed in a separate patch. Jungwoo Lee and Wongi Lee reported an UDP socket use-after-free caused by this bug: a SIOCGSTAMPNS_NEW ioctl racing with bind() can cancel the SOCK_RCU_FREE bit that udp_lib_get_port() just set, because both threads perform a read-modify-write on the same word. CPU 0 (bind) CPU 1 (SIOCGSTAMPNS_NEW) -------------------------------- ---------------------------- read sk_flags = F read sk_flags = F compute F | BIT(SOCK_RCU_FREE) compute F | BIT(SOCK_TIMESTAMP) store F | BIT(SOCK_RCU_FREE) sk_add_node_rcu(sk, ...) store F | BIT(SOCK_TIMESTAMP) After the lost update, SOCK_RCU_FREE is clear while the socket is visible to lockless UDP receive lookups. sk_destruct() then frees the socket immediately instead of waiting for a RCU grace period, while the receive path still holds a reference-less pointer to it: BUG: KASAN: slab-use-after-free in ipv4_pktinfo_prepare+0x30/0x410 Read of size 8 at addr ffff888008806610 by task exploit/207 CPU: 0 UID: 1000 PID: 207 Comm: exploit Not tainted 6.12.95+ #1 ipv4_pktinfo_prepare+0x30/0x410 udp_queue_rcv_one_skb+0x51c/0x1180 udp_unicast_rcv_skb+0x109/0x350 ip_protocol_deliver_rcu+0x14b/0x310 ip_local_deliver_finish+0x29d/0x390 ip_local_deliver+0x24d/0x2a0 Only grab the socket lock when SOCK_TIMESTAMP has to be set, to keep the common case lockless. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Jungwoo Lee Reported-by: Wongi Lee Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915043055.3441600-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/sock.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/core/sock.c b/net/core/sock.c index fa60b7494c58..d5e302e21e85 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -3911,7 +3911,14 @@ int sock_gettstamp(struct socket *sock, void __user *userstamp, struct sock *sk = sock->sk; struct timespec64 ts; - sock_enable_timestamp(sk, SOCK_TIMESTAMP); + /* sk->sk_flags must only be changed under the socket lock, + * because sock_set_flag() uses non atomic operations. + */ + if (!sock_flag(sk, SOCK_TIMESTAMP)) { + lock_sock(sk); + sock_enable_timestamp(sk, SOCK_TIMESTAMP); + release_sock(sk); + } ts = ktime_to_timespec64(sock_read_timestamp(sk)); if (ts.tv_sec == -1) return -ENOENT; From 400cb663ca019bae6eb878f06f1094ddf7c0b0df Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:49 +0800 Subject: [PATCH 1176/1317] watchdog: digicolor: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Since get_timeleft() cannot propagate errors, check the clock rate early in probe() and cache the rate in the driver data as it is unlikely to change at runtime. Fixes: 336694a01dae ("watchdog: digicolor: driver for Conexant Digicolor CX92755 SoC") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Acked-by: Baruch Siach Link: https://patch.msgid.link/20260913064851.8239-2-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/digicolor_wdt.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/watchdog/digicolor_wdt.c b/drivers/watchdog/digicolor_wdt.c index 073d37867f47..de1a3267a972 100644 --- a/drivers/watchdog/digicolor_wdt.c +++ b/drivers/watchdog/digicolor_wdt.c @@ -25,6 +25,7 @@ struct dc_wdt { void __iomem *base; struct clk *clk; spinlock_t lock; + unsigned long rate; }; static unsigned timeout; @@ -61,7 +62,7 @@ static int dc_wdt_start(struct watchdog_device *wdog) { struct dc_wdt *wdt = watchdog_get_drvdata(wdog); - dc_wdt_set(wdt, wdog->timeout * clk_get_rate(wdt->clk)); + dc_wdt_set(wdt, wdog->timeout * wdt->rate); return 0; } @@ -79,7 +80,7 @@ static int dc_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t) { struct dc_wdt *wdt = watchdog_get_drvdata(wdog); - dc_wdt_set(wdt, t * clk_get_rate(wdt->clk)); + dc_wdt_set(wdt, t * wdt->rate); wdog->timeout = t; return 0; @@ -90,7 +91,7 @@ static unsigned int dc_wdt_get_timeleft(struct watchdog_device *wdog) struct dc_wdt *wdt = watchdog_get_drvdata(wdog); uint32_t count = readl_relaxed(wdt->base + TIMER_A_COUNT); - return count / clk_get_rate(wdt->clk); + return count / wdt->rate; } static const struct watchdog_ops dc_wdt_ops = { @@ -130,7 +131,11 @@ static int dc_wdt_probe(struct platform_device *pdev) wdt->clk = devm_clk_get(dev, NULL); if (IS_ERR(wdt->clk)) return PTR_ERR(wdt->clk); - dc_wdt_wdd.max_timeout = U32_MAX / clk_get_rate(wdt->clk); + + wdt->rate = clk_get_rate(wdt->clk); + if (!wdt->rate) + return -EINVAL; + dc_wdt_wdd.max_timeout = U32_MAX / wdt->rate; dc_wdt_wdd.timeout = dc_wdt_wdd.max_timeout; dc_wdt_wdd.parent = dev; From 5af7d2cbd20f893def03c8310a460ade66a5d822 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:50 +0800 Subject: [PATCH 1177/1317] watchdog: rtd119x: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Fixes: 2bdf6acbfead ("watchdog: Add Realtek RTD1295") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913064851.8239-3-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/rtd119x_wdt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/rtd119x_wdt.c b/drivers/watchdog/rtd119x_wdt.c index 984905695dde..0bfadb58917b 100644 --- a/drivers/watchdog/rtd119x_wdt.c +++ b/drivers/watchdog/rtd119x_wdt.c @@ -98,6 +98,7 @@ static int rtd119x_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rtd119x_watchdog_device *data; + unsigned long rate; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -111,10 +112,14 @@ static int rtd119x_wdt_probe(struct platform_device *pdev) if (IS_ERR(data->clk)) return PTR_ERR(data->clk); + rate = clk_get_rate(data->clk); + if (!rate) + return -EINVAL; + data->wdt_dev.info = &rtd119x_wdt_info; data->wdt_dev.ops = &rtd119x_wdt_ops; data->wdt_dev.timeout = 120; - data->wdt_dev.max_timeout = 0xffffffff / clk_get_rate(data->clk); + data->wdt_dev.max_timeout = 0xffffffff / rate; data->wdt_dev.min_timeout = 1; data->wdt_dev.parent = dev; From 6274281c41efa8dd1aa5234c59ad904ff89d7af4 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:51 +0800 Subject: [PATCH 1178/1317] watchdog: rzv2h: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Fixes: f6febd0a30b6 ("watchdog: Add Watchdog Timer driver for RZ/V2H(P)") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913064851.8239-4-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/rzv2h_wdt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/rzv2h_wdt.c b/drivers/watchdog/rzv2h_wdt.c index 3b6abb66a1da..83540dd9a37b 100644 --- a/drivers/watchdog/rzv2h_wdt.c +++ b/drivers/watchdog/rzv2h_wdt.c @@ -278,6 +278,7 @@ static int rzv2h_wdt_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct rzv2h_wdt_priv *priv; struct clk *count_clk; + unsigned long rate; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -314,8 +315,12 @@ static int rzv2h_wdt_probe(struct platform_device *pdev) return dev_err_probe(dev, -EINVAL, "Invalid count source\n"); } + rate = clk_get_rate(count_clk); + if (!rate) + return dev_err_probe(dev, -EINVAL, "Invalid clock rate\n"); + priv->wdev.max_hw_heartbeat_ms = (MILLI * priv->of_data->timeout_cycles * - priv->of_data->cks_div) / clk_get_rate(count_clk); + priv->of_data->cks_div) / rate; dev_dbg(dev, "max hw timeout of %dms\n", priv->wdev.max_hw_heartbeat_ms); ret = devm_pm_runtime_enable(dev); From 1d9763f34a85680db1e8233d654fdb85e5f897cc Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 00:33:34 +0800 Subject: [PATCH 1179/1317] watchdog: msc313e: Propagate error code in resume() If msc313e_wdt_start() fails during system resume, the error is currently ignored. Consequently, the watchdog isn't running without the user's knowledge. Propagate the error code and print a message if msc313e_wdt_start() fails. Signed-off-by: Tzung-Bi Shih Fixes: e9800b7994642 ("watchdog: Add Mstar MSC313e WDT driver") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260912163334.28636-1-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 4a5cce2a16b1..9a400562d1ae 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -191,11 +191,15 @@ static int __maybe_unused msc313e_wdt_suspend(struct device *dev) static int __maybe_unused msc313e_wdt_resume(struct device *dev) { struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); + int ret = 0; - if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) - msc313e_wdt_start(&priv->wdev); + if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) { + ret = msc313e_wdt_start(&priv->wdev); + if (ret) + dev_err(dev, "Failed to restart watchdog (err=%d)\n", ret); + } - return 0; + return ret; } static SIMPLE_DEV_PM_OPS(msc313e_wdt_pm_ops, msc313e_wdt_suspend, msc313e_wdt_resume); From 22737cfced627ffcb4b5c36d63bb3d4476f63213 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:51:26 +0800 Subject: [PATCH 1180/1317] watchdog: msc313e: Fix premature reset during timeout update Updating the 32-bit hardware timeout requires writing to two 16-bit registers sequentially. If the watchdog is actively running, this non-atomic update might trigger a premature system reset. Clear the watchdog counter before updating the registers to prevent the timer from timing out prematurely against an intermediate threshold. Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913065126.8350-1-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 9a400562d1ae..d62586f6e09d 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -46,6 +46,9 @@ static void msc313e_wdt_set_hw_timeout(struct msc313e_wdt_priv *priv, { u32 t = timeout * clk_get_rate(priv->clk); + /* Clear before to prevent premature reset during non-atomic updates. */ + writew(1, priv->base + REG_WDT_CLR); + writew(t & 0xffff, priv->base + REG_WDT_MAX_PRD_L); writew((t >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H); writew(1, priv->base + REG_WDT_CLR); @@ -76,6 +79,9 @@ static int msc313e_wdt_stop(struct watchdog_device *wdev) { struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); + /* Clear before to prevent premature reset during non-atomic updates. */ + writew(1, priv->base + REG_WDT_CLR); + writew(0, priv->base + REG_WDT_MAX_PRD_L); writew(0, priv->base + REG_WDT_MAX_PRD_H); writew(0, priv->base + REG_WDT_CLR); From 5071122bf5a628494db16d98d253f622a5aab074 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 14 Sep 2026 14:23:53 +0800 Subject: [PATCH 1181/1317] watchdog: da9062: fix suspend/resume handling of HW_RUNNING watchdog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit da9062_wdt_suspend() and da9062_wdt_resume() only check watchdog_active(), when the watchdog is left running by the driver sets WDOG_HW_RUNNING in da9062_wdt_probe() but userspace never opens the device, so WDOG_ACTIVE remains cleared, the wdt_disable() will not be executed in da9062_wdt_suspend. In this case, the suspend callback is a no-op and the watchdog keeps counting during system suspend, leading to an unexpected system reset. Check WDOG_HW_RUNNING and wdt->wdd,can fix this issue. Fixes: f6c98b08381c7 ("watchdog: da9062: add power management ops") Cs: stable@vger.kernel.org Signed-off-by: Li Jun Link: https://patch.msgid.link/20260914062353.582205-1-lijun01@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/da9062_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index 426962547df1..4d558652e9e7 100644 --- a/drivers/watchdog/da9062_wdt.c +++ b/drivers/watchdog/da9062_wdt.c @@ -256,7 +256,7 @@ static int __maybe_unused da9062_wdt_suspend(struct device *dev) if (!wdt->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9062_wdt_stop(wdd); return 0; @@ -270,7 +270,7 @@ static int __maybe_unused da9062_wdt_resume(struct device *dev) if (!wdt->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9062_wdt_start(wdd); return 0; From 1dd85662fee6e2ac580b1c4f9a0c0a7ae6e31f0e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 14 Sep 2026 23:26:41 +0200 Subject: [PATCH 1182/1317] net: ethernet: cortina: Ack RX overrun interrupt correctly The RX overrun interrupt is reported in interrupt status register 4, but gmac_irq() acknowledges it using the RX descriptor error bit from status register 0. For GMAC0 this writes the GMAC1 overrun bit, while for GMAC1 the shift leaves no bit in the 32-bit register. Acknowledge the same per-port RX overrun bit that was detected. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260914-b4-gemini-ethernet-fixes-2-v2-1-5ab39a047b90@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cortina/gemini.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index f08de623e6f7..2dd2fa801829 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1798,7 +1798,7 @@ static irqreturn_t gmac_irq(int irq, void *data) if (val & (GMAC0_RX_OVERRUN_INT_BIT << (netdev->dev_id * 8))) { spin_lock(&geth->irq_lock); - writel(GMAC0_RXDERR_INT_BIT << (netdev->dev_id * 8), + writel(GMAC0_RX_OVERRUN_INT_BIT << (netdev->dev_id * 8), geth->base + GLOBAL_INTERRUPT_STATUS_4_REG); u64_stats_update_begin(&port->ir_stats_syncp); ++port->stats.rx_fifo_errors; From 8f0ca55016a7647109ae2bc91bcb346fc8b13785 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:07:04 +0000 Subject: [PATCH 1183/1317] watchdog: starfive-wdt: Fix runtime PM leak in starfive_wdt_pm_start() starfive_wdt_pm_start() takes a runtime PM reference with pm_runtime_get_sync(), which increments the usage counter even when it fails, and returns the error without dropping it again. The watchdog core does not invoke the stop callback when start fails, so the reference taken on the error path is leaked. Use pm_runtime_resume_and_get() instead, which keeps the usage counter balanced when the resume fails. Fixes: db728ea9c7be ("drivers: watchdog: Add StarFive Watchdog driver") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260916170704.2086331-1-vulab@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/starfive-wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c index af55adc4a3c6..5a3254c83d3b 100644 --- a/drivers/watchdog/starfive-wdt.c +++ b/drivers/watchdog/starfive-wdt.c @@ -371,7 +371,7 @@ static void starfive_wdt_stop(struct starfive_wdt *wdt) static int starfive_wdt_pm_start(struct watchdog_device *wdd) { struct starfive_wdt *wdt = watchdog_get_drvdata(wdd); - int ret = pm_runtime_get_sync(wdd->parent); + int ret = pm_runtime_resume_and_get(wdd->parent); if (ret < 0) return ret; From 5d063822ac5184939c1ed377a339a01d8ae814e8 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:32 +0800 Subject: [PATCH 1184/1317] net: wwan: mhi_wwan_mbim: guard against a cyclic NDP chain The NDP traversal in mhi_mbim_rx() only stops when wNextNdpIndex is zero. Nothing requires the offsets to advance, so a modem that points an NDP at itself, or at an earlier NDP, keeps the loop spinning forever on one CPU. Break out when the next NDP offset is not larger than the current one. Fixes: aa730a9905b7 ("net: wwan: Add MHI MBIM network driver") Cc: stable@vger.kernel.org Suggested-by: Loic Poulain Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector feeding the driver's receive callback an NTB whose single NDP points at itself: the unpatched driver spins in mhi_mbim_rx() with one CPU pinned at 100% and the thread never returns. With this check the loop terminates within one iteration. Changes in v2: move the non-increasing check to the wNextNdpIndex retrieval site, as suggested by Loic Poulain, instead of tracking the previous offset in a separate variable. Link: https://patch.msgid.link/20260911021734.1396599-1-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/mhi_wwan_mbim.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wwan/mhi_wwan_mbim.c b/drivers/net/wwan/mhi_wwan_mbim.c index a94998712597..5679948546a5 100644 --- a/drivers/net/wwan/mhi_wwan_mbim.c +++ b/drivers/net/wwan/mhi_wwan_mbim.c @@ -349,9 +349,13 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) unlock: rcu_read_unlock(); next_ndp: - /* Other NDP to process? */ - ndpoffset = (int)le16_to_cpu(ndp16.wNextNdpIndex); - if (!ndpoffset) + /* Other NDP to process? The offsets must advance, or a + * self-referencing NDP keeps the loop spinning forever. + */ + n = (int)le16_to_cpu(ndp16.wNextNdpIndex); + if (n > ndpoffset) + ndpoffset = n; + else break; } From 31550d585589fde1ae95bf7f7a8188b2d2fdf1c7 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:33 +0800 Subject: [PATCH 1185/1317] net: wwan: mhi_wwan_mbim: check skb_copy_bits() return value mhi_mbim_rx() ignores the return value of skb_copy_bits() when it copies each datagram out of the NTB. The datagram offset and length come from the DPE, which is only checked to lie within the NTB itself, so a modem can point a datagram outside the received skb. The copy then fails and the freshly allocated skbn is passed to netif_rx() with its uninitialized contents still in place, leaking kernel heap memory into the network stack. Free the skb and account an error when the copy fails. Fixes: aa730a9905b7 ("net: wwan: Add MHI MBIM network driver") Cc: stable@vger.kernel.org Suggested-by: Loic Poulain Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector pointing a DPE outside the received NTB: the copy fails, and the unpatched driver hands the uninitialized skbn to the network stack (observed as "unknown protocol" on bytes that were never written). With this check the failed datagram is dropped and counted as an rx error. Changes in v2: factor the free-and-count sequence out into mhi_mbim_rx_drop(), shared with the unknown-protocol path, as suggested by Loic Poulain. Link: https://patch.msgid.link/20260911021734.1396599-2-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/mhi_wwan_mbim.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/wwan/mhi_wwan_mbim.c b/drivers/net/wwan/mhi_wwan_mbim.c index 5679948546a5..336f89756f10 100644 --- a/drivers/net/wwan/mhi_wwan_mbim.c +++ b/drivers/net/wwan/mhi_wwan_mbim.c @@ -251,6 +251,14 @@ static int mbim_rx_verify_ndp16(struct sk_buff *skb, struct usb_cdc_ncm_ndp16 *n return ret; } +static void mhi_mbim_rx_drop(struct mhi_mbim_link *link, struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + u64_stats_update_begin(&link->rx_syncp); + u64_stats_inc(&link->rx_errors); + u64_stats_update_end(&link->rx_syncp); +} + static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) { int ndpoffset; @@ -320,7 +328,10 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) continue; skb_put(skbn, dgram_len); - skb_copy_bits(skb, dgram_offset, skbn->data, dgram_len); + if (skb_copy_bits(skb, dgram_offset, skbn->data, dgram_len)) { + mhi_mbim_rx_drop(link, skbn); + continue; + } switch (skbn->data[0] & 0xf0) { case 0x40: @@ -332,10 +343,7 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) default: net_err_ratelimited("%s: unknown protocol\n", link->ndev->name); - dev_kfree_skb_any(skbn); - u64_stats_update_begin(&link->rx_syncp); - u64_stats_inc(&link->rx_errors); - u64_stats_update_end(&link->rx_syncp); + mhi_mbim_rx_drop(link, skbn); continue; } From c7ead9704249d57d4693a04697e3bbd285138fa9 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:34 +0800 Subject: [PATCH 1186/1317] net: wwan: t7xx: validate the netif index in t7xx_ccmni_recv_skb() The netif index carried in the DPMAIF PIT header is five bits wide, but ccmni_inst[] only has room for NIC_DEV_MAX (21) entries. t7xx_ccmni_recv_skb() indexes the array without a bounds check, so indexes 21 to 31 read past it. The out-of-bounds value lands in the callback table that follows the array, which is never NULL, so the existing !ccmni check does not catch it and the driver dereferences whatever sits there as a struct t7xx_ccmni. Drop the skb when the index is out of range. Fixes: 05d19bf500f8 ("net: wwan: t7xx: Add WWAN network interface") Cc: stable@vger.kernel.org Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector setting the netif index to 25: the unpatched driver reads a value past ccmni_inst[], which lands in the callback table, and dereferences it far enough to queue the skb. With this check the packet is dropped. Well-formed traffic on index 0 is unaffected. Changes in v2: none. Link: https://patch.msgid.link/20260911021734.1396599-3-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/t7xx/t7xx_netdev.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wwan/t7xx/t7xx_netdev.c b/drivers/net/wwan/t7xx/t7xx_netdev.c index fc0a7cb181df..8f32c2d26931 100644 --- a/drivers/net/wwan/t7xx/t7xx_netdev.c +++ b/drivers/net/wwan/t7xx/t7xx_netdev.c @@ -420,6 +420,10 @@ static void t7xx_ccmni_recv_skb(struct t7xx_ccmni_ctrl *ccmni_ctlb, struct sk_bu skb_cb = T7XX_SKB_CB(skb); netif_id = skb_cb->netif_idx; + if (netif_id >= NIC_DEV_MAX) { + dev_kfree_skb(skb); + return; + } ccmni = READ_ONCE(ccmni_ctlb->ccmni_inst[netif_id]); if (!ccmni) { dev_kfree_skb(skb); From 90e4b849dfa6fc8e6c050bcfe1b331b69c015d28 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 10:58:29 +0200 Subject: [PATCH 1187/1317] net: stmmac: propagate FPE preemption-class mapping errors stmmac_fpe_map_preemption_class() dispatches through the stmmac_do_void_callback() helper, which forces the callback's return value to 0 whenever the op pointer is populated. As a result the -EINVAL returned by dwmac5_fpe_map_preemption_class() (e.g. when a preemptible TC owns more than one TXQ under SP scheduling) is silently swallowed by every caller. Switch the dispatch macro to stmmac_do_callback() so the callback's real result is propagated, and honour it in the taprio and mqprio qdisc offload. Note that the taprio "if (ret)" check in tc_taprio_configure() used to be dead code and now becomes live: a preemptible TC spanning more than one TXQ under SP scheduling cannot be programmed in hardware, so a taprio or mqprio configuration that previously returned success while leaving the preemption-class register unprogrammed now fails with -EINVAL. For taprio, the failure also runs the disable path, tearing down the schedule that was just installed; this is the intended behaviour. Fixes: 195e4f409a40 ("net: stmmac: support fp parameter of tc-mqprio") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260911-stmmac-tc_setup_dwmac510_mqprio-error-path-v3-1-a76b1e2547c1@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 04dafec021b4..9314bcb85c22 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -494,7 +494,7 @@ struct stmmac_ops { #define stmmac_set_arp_offload(__priv, __args...) \ stmmac_do_void_callback(__priv, mac, set_arp_offload, __args) #define stmmac_fpe_map_preemption_class(__priv, __args...) \ - stmmac_do_void_callback(__priv, mac, fpe_map_preemption_class, __args) + stmmac_do_callback(__priv, mac, fpe_map_preemption_class, __args) /* PTP and HW Timer helpers */ struct stmmac_hwtimestamp { diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index 14cabe76e53e..5398616fcdfe 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -970,7 +970,7 @@ static int tc_taprio_configure(struct stmmac_priv *priv, struct netlink_ext_ack *extack = qopt->mqprio.extack; struct timespec64 time, current_time, qopt_time; ktime_t current_time_ns; - int i, ret = 0; + int err, i, ret = 0; u64 ctr; if (qopt->base_time < 0) @@ -1120,9 +1120,9 @@ static int tc_taprio_configure(struct stmmac_priv *priv, mutex_unlock(&priv->est_lock); } - stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0); + err = stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0); - return ret; + return qopt->cmd == TAPRIO_CMD_DESTROY ? err : ret; } static void tc_taprio_stats(struct stmmac_priv *priv, @@ -1237,14 +1237,15 @@ static int tc_query_caps(struct stmmac_priv *priv, } } -static void stmmac_reset_tc_mqprio(struct net_device *ndev, - struct netlink_ext_ack *extack) +static int stmmac_reset_tc_mqprio(struct net_device *ndev, + struct netlink_ext_ack *extack) { struct stmmac_priv *priv = netdev_priv(ndev); netdev_reset_tc(ndev); netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use); - stmmac_fpe_map_preemption_class(priv, ndev, extack, 0); + + return stmmac_fpe_map_preemption_class(priv, ndev, extack, 0); } static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, @@ -1257,10 +1258,8 @@ static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, u32 num_tc = qopt->num_tc; int err; - if (!num_tc) { - stmmac_reset_tc_mqprio(ndev, extack); - return 0; - } + if (!num_tc) + return stmmac_reset_tc_mqprio(ndev, extack); err = netdev_set_num_tc(ndev, num_tc); if (err) From 02fffd1939f6b45892f61822459953ce95e42948 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 10:58:30 +0200 Subject: [PATCH 1188/1317] net: stmmac: preserve real_num_tx_queues on mqprio setup failure With the FPE preemption-class mapping error now propagated from stmmac_fpe_map_preemption_class(), tc_setup_dwmac510_mqprio() can fail on the mapping step. The error path used to call stmmac_reset_tc_mqprio(), which resets the number of real TX queues to priv->plat->tx_queues_to_use (the platform maximum), overwriting the value that was active before the offload was attempted (for example a lower count left over from a previous mqprio configuration). The issue can be triggered using the following configuration: # First mqprio config lowers the hw queue count below the platform # default (e.g. 8 TX queues). $tc qdisc add dev eth0 root handle 1: mqprio queues 2@0 2@2 # Replace mqprio configuration with a second one that fails FPE # preemption-class mapping. stmmac driver resets the real_num_tx_queues # to the platform maximum, losing the previous configuration. $tc qdisc replace dev eth0 root handle 2: mqprio queues 2@0 2@2 fp E P Save ndev->real_num_tx_queues before lowering it and restore it, together with the TC-to-queue and priority-to-TC mappings, when the FPE preemption-class mapping fails, instead of resetting the queue count to the platform maximum. Note that a failed setup makes the qdisc layer run mqprio_destroy() on the new qdisc. Because priv->hw_offload is only assigned after ndo_setup_tc() succeeds, mqprio_destroy() calls netdev_set_num_tc(dev, 0), so dev->num_tc ends up 0 regardless of the driver-side restore and the previous qdisc is not reactivated. The restore is still needed to keep real_num_tx_queues and to avoid leaving the failed configuration's TC-to-queue and priority-to-TC mappings in place. Fixes: 195e4f409a40 ("net: stmmac: support fp parameter of tc-mqprio") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260911-stmmac-tc_setup_dwmac510_mqprio-error-path-v3-2-a76b1e2547c1@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 80 ++++++++++++++----- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index 5398616fcdfe..42a00446e9b4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -1237,6 +1237,30 @@ static int tc_query_caps(struct stmmac_priv *priv, } } +static int stmmac_set_ndev_tcs(struct net_device *ndev, u8 ntc, + struct netdev_tc_txq *tc_to_txq) +{ + int i, err; + + netdev_reset_tc(ndev); + if (!ntc) + return 0; + + err = netdev_set_num_tc(ndev, ntc); + if (err) + return err; + + for (i = 0; i < ntc; i++) { + u16 count, offset; + + count = tc_to_txq[i].count; + offset = tc_to_txq[i].offset; + netdev_set_tc_queue(ndev, i, count, offset); + } + + return 0; +} + static int stmmac_reset_tc_mqprio(struct net_device *ndev, struct netlink_ext_ack *extack) { @@ -1251,43 +1275,61 @@ static int stmmac_reset_tc_mqprio(struct net_device *ndev, static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, struct tc_mqprio_qopt_offload *mqprio) { + unsigned int ndev_num_tx_queues, num_tx_queues = 0; + struct netdev_tc_txq ndev_tc_to_txq[TC_MAX_QUEUE]; + struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE] = {}; struct netlink_ext_ack *extack = mqprio->extack; struct tc_mqprio_qopt *qopt = &mqprio->qopt; - u32 offset, count, num_stack_tx_queues = 0; struct net_device *ndev = priv->dev; - u32 num_tc = qopt->num_tc; - int err; + u8 ndev_prio_tc_map[TC_BITMASK + 1]; + int i, err, ndev_ntc; - if (!num_tc) + if (!qopt->num_tc) return stmmac_reset_tc_mqprio(ndev, extack); - err = netdev_set_num_tc(ndev, num_tc); - if (err) - return err; + if (qopt->num_tc > ARRAY_SIZE(tc_to_txq)) + return -EINVAL; - for (u32 tc = 0; tc < num_tc; tc++) { - offset = qopt->offset[tc]; - count = qopt->count[tc]; - num_stack_tx_queues += count; + /* save current tc values for reset */ + ndev_ntc = netdev_get_num_tc(ndev); + for (i = 0; i < ARRAY_SIZE(ndev->tc_to_txq); i++) + ndev_tc_to_txq[i].combined = + READ_ONCE(ndev->tc_to_txq[i].combined); + for (i = 0; i < ARRAY_SIZE(ndev_prio_tc_map); i++) + ndev_prio_tc_map[i] = READ_ONCE(ndev->prio_tc_map[i]); - err = netdev_set_tc_queue(ndev, tc, count, offset); - if (err) - goto err_reset_tc; + for (i = 0; i < qopt->num_tc; i++) { + tc_to_txq[i] = (struct netdev_tc_txq) { + .count = qopt->count[i], + .offset = qopt->offset[i], + }; + num_tx_queues += qopt->count[i]; } - err = netif_set_real_num_tx_queues(ndev, num_stack_tx_queues); + err = stmmac_set_ndev_tcs(ndev, qopt->num_tc, tc_to_txq); if (err) - goto err_reset_tc; + goto error_reset_tc; + + ndev_num_tx_queues = ndev->real_num_tx_queues; + err = netif_set_real_num_tx_queues(ndev, num_tx_queues); + if (err) + goto error_reset_tc; err = stmmac_fpe_map_preemption_class(priv, ndev, extack, mqprio->preemptible_tcs); if (err) - goto err_reset_tc; + goto error_reset_num_tx_queues; return 0; -err_reset_tc: - stmmac_reset_tc_mqprio(ndev, extack); +error_reset_num_tx_queues: + if (netif_set_real_num_tx_queues(ndev, ndev_num_tx_queues)) + netdev_warn(ndev, "Failed to restore %u TX queues\n", + ndev_num_tx_queues); +error_reset_tc: + stmmac_set_ndev_tcs(ndev, ndev_ntc, ndev_tc_to_txq); + for (i = 0; i < ARRAY_SIZE(ndev_prio_tc_map); i++) + netdev_set_prio_tc_map(ndev, i, ndev_prio_tc_map[i]); return err; } From a41f24c612c3f5139a3143307eb85bbcf1bd4d07 Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 15 Sep 2026 16:11:37 -0700 Subject: [PATCH 1189/1317] net: psp: avoid conflicts with skb->decrypted and sk_validate_xmit_skb() PSP conflicts with TLS ULP in its usage of both skb->decrypted and sk->sk_validate_xmit_skb(). Make PSP mutually exclusive with TLS ULP, the only other user of either of these. As other users of skb->decrypted come along, they can be added to sk_has_decrypt_user(). It would make sense to also assert that sk->sk_validate_xmit_skb() is also NULL in both of these setup paths for similar future proofing, but the PSP listener/sk_clone() path is still broken and it could be seen as a regression to not allow rx assoc to run on a child of a listener socket with PSP tx assoc state. Include all TCP ULPs in the sk_has_decrypt_user() check, even though TLS is the only one that conflicts with PSP via the decrypted bit. This is intentional because PSP was not designed to be used with ULPs. It is best to close off surface area that may make bugs reachable, until someone wishes to design and test an actual user of PSP with ULPs. Fixes: 6b46ca260e22 ("net: psp: add socket security association code") Signed-off-by: Daniel Zahka Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260915-psp-ktls-fix-v2-1-0eedc3b148ec@gmail.com Signed-off-by: Jakub Kicinski --- include/net/sock.h | 2 ++ net/core/sock.c | 7 +++++++ net/ipv4/tcp_ulp.c | 4 ++++ net/psp/psp_sock.c | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/include/net/sock.h b/include/net/sock.h index 51185222aac2..60ea55dc1885 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -2312,6 +2312,8 @@ static inline void sk_gso_disable(struct sock *sk) sk->sk_route_caps &= ~NETIF_F_GSO_MASK; } +bool sk_has_decrypt_user(const struct sock *sk); + static inline int skb_do_copy_data_nocache(struct sock *sk, struct sk_buff *skb, struct iov_iter *from, char *to, int copy, int offset) diff --git a/net/core/sock.c b/net/core/sock.c index d5e302e21e85..d23333bb4f3f 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -142,6 +142,7 @@ #include +#include #include #include #include @@ -2670,6 +2671,12 @@ void sk_setup_caps(struct sock *sk, struct dst_entry *dst) } EXPORT_SYMBOL_GPL(sk_setup_caps); +bool sk_has_decrypt_user(const struct sock *sk) +{ + return psp_sk_assoc(sk) || + (sk_is_inet(sk) && inet_csk_has_ulp(sk)); /* for tls */ +} + /* * Simple resource managers for sockets. */ diff --git a/net/ipv4/tcp_ulp.c b/net/ipv4/tcp_ulp.c index 2aa442128630..b58045df101e 100644 --- a/net/ipv4/tcp_ulp.c +++ b/net/ipv4/tcp_ulp.c @@ -136,6 +136,10 @@ static int __tcp_set_ulp(struct sock *sk, const struct tcp_ulp_ops *ulp_ops) if (icsk->icsk_ulp_ops) goto out_err; + err = -EINVAL; + if (sk_has_decrypt_user(sk)) + goto out_err; + if (sk->sk_socket) clear_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags); diff --git a/net/psp/psp_sock.c b/net/psp/psp_sock.c index 1a2a6b7516b0..e9b53eedf8db 100644 --- a/net/psp/psp_sock.c +++ b/net/psp/psp_sock.c @@ -143,6 +143,10 @@ int psp_sock_assoc_set_rx(struct sock *sk, struct psp_assoc *pas, NL_SET_ERR_MSG(extack, "Socket already has PSP state"); err = -EBUSY; goto exit_unlock; + } else if (sk_has_decrypt_user(sk)) { + NL_SET_ERR_MSG(extack, "Socket has incompatible state"); + err = -EINVAL; + goto exit_unlock; } refcount_inc(&pas->refcnt); From b4288c59bda883b0e5cd95099dc3b0b7b7fc50f6 Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 15 Sep 2026 16:11:38 -0700 Subject: [PATCH 1190/1317] selftests: drv-net: psp: test PSP and TCP ULP mutual exclusion Test both setting PSP after TLS ULP, and TLS ULP after PSP. Add CONFIG_TLS=y to the drivers/net/config. Signed-off-by: Daniel Zahka Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260915-psp-ktls-fix-v2-2-0eedc3b148ec@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/config | 1 + tools/testing/selftests/drivers/net/psp.py | 46 ++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index b6989c7d3d9d..4838adf27fa1 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -21,5 +21,6 @@ CONFIG_NET_SCH_INGRESS=y CONFIG_NET_SCH_PRIO=m CONFIG_PPP=y CONFIG_PPPOE=y +CONFIG_TLS=y CONFIG_VLAN_8021Q=m CONFIG_XDP_SOCKETS=y diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 315648a770d0..a5b1e14f120f 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -23,6 +23,8 @@ from lib.py import NetNSEnter from lib.py import bkg, rand_port, wait_port_listen from lib.py import ip +TCP_ULP = 31 + def _get_outq(s): one = b'\0' * 4 @@ -333,6 +335,50 @@ def assoc_version_mismatch(cfg): ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) +def _require_tls_ulp(): + with socket.create_server(("localhost", 0)) as srv, \ + socket.create_connection(srv.getsockname()) as s: + try: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + except OSError as exc: + raise KsftSkipEx("kTLS not available") from exc + + +def assoc_psp_ulp_exclusive(cfg): + """ Test that a TCP ULP cannot be attached to a PSP socket """ + _init_psp_dev(cfg) + _require_tls_ulp() + + with _make_clr_conn(cfg) as s: + try: + cfg.pspnl.rx_assoc({"version": 0, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + with ksft_raises(OSError) as cm: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + ksft_eq(cm.exception.errno, errno.EINVAL) + finally: + _close_conn(cfg, s) + + +def assoc_ulp_psp_exclusive(cfg): + """ Test that a PSP assoc cannot be added to a socket with a TCP ULP """ + _init_psp_dev(cfg) + _require_tls_ulp() + + with _make_clr_conn(cfg) as s: + try: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + with ksft_raises(NlError) as cm: + cfg.pspnl.rx_assoc({"version": 0, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL) + ksft_eq(cm.exception.nl_msg.extack['bad-attr'], ".sock-fd") + finally: + _close_conn(cfg, s) + + def assoc_twice(cfg): """ Test reusing Tx assoc for two sockets """ _init_psp_dev(cfg) From 5535d5e61a77ad79118ea7665ce1c14f857f0f12 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Thu, 3 Sep 2026 16:42:12 +0800 Subject: [PATCH 1191/1317] drm/loongson: Create blend mode property for cursor plane After commit 860e748bddcc929 ("drm: ensure blend mode supported if pixel format with alpha exposed") we get warnings at boot: loongson 0000:00:06.1: [drm] [PLANE:41:ls-cursor-plane-0] pixel format with alpha exposed but blend mode not setup. Please fix. loongson 0000:00:06.1: [drm] [PLANE:46:ls-cursor-plane-1] pixel format with alpha exposed but blend mode not setup. Please fix. The reason is the cursor plane supports color formats with alpha but the driver doesn't create blend mode property, which triggers the warning in validate_blend_mode_for_alpha_formats(). The loongson DC HW doesn't support DRM_MODE_BLEND_PREMULTI, so create blend mode property with DRM_MODE_BLEND_COVERAGE for cursor planes since it is the only one implemented in the driver. Signed-off-by: Huacai Chen Reviewed-by: Jianmin Lv Signed-off-by: Icenowy Zheng Link: https://patch.msgid.link/20260903084212.3621540-1-chenhuacai@loongson.cn --- drivers/gpu/drm/loongson/lsdc_plane.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/loongson/lsdc_plane.c b/drivers/gpu/drm/loongson/lsdc_plane.c index bea42215796d..bcc0ffa17bdf 100644 --- a/drivers/gpu/drm/loongson/lsdc_plane.c +++ b/drivers/gpu/drm/loongson/lsdc_plane.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -765,7 +766,7 @@ int ls7a1000_cursor_plane_init(struct drm_device *ddev, drm_plane_helper_add(plane, &ls7a1000_cursor_plane_helper_funcs); - return 0; + return drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); } int ls7a2000_cursor_plane_init(struct drm_device *ddev, @@ -790,5 +791,5 @@ int ls7a2000_cursor_plane_init(struct drm_device *ddev, drm_plane_helper_add(plane, &ls7a2000_cursor_plane_helper_funcs); - return 0; + return drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); } From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: [PATCH 1192/1317] x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- arch/x86/include/asm/text-patching.h | 4 ++-- arch/x86/kernel/alternative.c | 6 ++++-- arch/x86/kernel/kprobes/core.c | 5 ++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/arch/x86/include/asm/text-patching.h b/arch/x86/include/asm/text-patching.h index f2d142a0a862..ea09381070e8 100644 --- a/arch/x86/include/asm/text-patching.h +++ b/arch/x86/include/asm/text-patching.h @@ -164,9 +164,9 @@ unsigned long int3_emulate_pop(struct pt_regs *regs) } static __always_inline -void int3_emulate_call(struct pt_regs *regs, unsigned long func) +void int3_emulate_call(struct pt_regs *regs, unsigned long ip, unsigned long func) { - int3_emulate_push(regs, regs->ip - INT3_INSN_SIZE + CALL_INSN_SIZE); + int3_emulate_push(regs, ip); int3_emulate_jmp(regs, func); } diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index 741d8767ddf8..582c6d8307d1 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -2176,6 +2176,7 @@ int3_exception_notify(struct notifier_block *self, unsigned long val, void *data unsigned long selftest = (unsigned long)&int3_selftest_asm; struct die_args *args = data; struct pt_regs *regs = args->regs; + unsigned long ip; OPTIMIZER_HIDE_VAR(selftest); @@ -2188,7 +2189,8 @@ int3_exception_notify(struct notifier_block *self, unsigned long val, void *data if (regs->ip - INT3_INSN_SIZE != selftest) return NOTIFY_DONE; - int3_emulate_call(regs, (unsigned long)&int3_selftest_callee); + ip = regs->ip - INT3_INSN_SIZE + CALL_INSN_SIZE; + int3_emulate_call(regs, ip, (unsigned long)&int3_selftest_callee); return NOTIFY_STOP; } @@ -2758,7 +2760,7 @@ noinstr int smp_text_poke_int3_handler(struct pt_regs *regs) break; case CALL_INSN_OPCODE: - int3_emulate_call(regs, (long)ip + tpl->disp); + int3_emulate_call(regs, (long)ip, (long)ip + tpl->disp); break; case JMP32_INSN_OPCODE: diff --git a/arch/x86/kernel/kprobes/core.c b/arch/x86/kernel/kprobes/core.c index 4e5f8c1736ec..133ff20caccd 100644 --- a/arch/x86/kernel/kprobes/core.c +++ b/arch/x86/kernel/kprobes/core.c @@ -510,10 +510,9 @@ NOKPROBE_SYMBOL(kprobe_emulate_ret); static void kprobe_emulate_call(struct kprobe *p, struct pt_regs *regs) { - unsigned long func = regs->ip - INT3_INSN_SIZE + p->ainsn.size; + unsigned long ip = regs->ip - INT3_INSN_SIZE + p->ainsn.size; - func += p->ainsn.rel32; - int3_emulate_call(regs, func); + int3_emulate_call(regs, ip, ip + p->ainsn.rel32); } NOKPROBE_SYMBOL(kprobe_emulate_call); From 065f3ce5936e68da75f3dc18201d290073d78f3c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:05 -0700 Subject: [PATCH 1193/1317] xfs: call xfs_dquot_set_prealloc_limits if we installed default rtb limits Now that we have quotas for the realtime volume, we also have precomputed watermark limits for the realtime block counts. These precomputations should be done any time we change the rtb limits, which means that xfs_qm_adjust_dqlimits needs to ensure that if we installed a default rtb limit. Cc: stable@vger.kernel.org # v6.13 Fixes: 5dd70852b03901 ("xfs: create quota preallocation watermarks for realtime quota") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_dquot.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index b4f6c594808c..e696ee36c2e8 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -139,10 +139,14 @@ xfs_qm_adjust_dqlimits( dq->q_ino.softlimit = defq->ino.soft; if (!dq->q_ino.hardlimit) dq->q_ino.hardlimit = defq->ino.hard; - if (!dq->q_rtb.softlimit) + if (!dq->q_rtb.softlimit) { dq->q_rtb.softlimit = defq->rtb.soft; - if (!dq->q_rtb.hardlimit) + prealloc = 1; + } + if (!dq->q_rtb.hardlimit) { dq->q_rtb.hardlimit = defq->rtb.hard; + prealloc = 1; + } if (prealloc) xfs_dquot_set_prealloc_limits(dq); From 41c4c41cf6c44f98db2916e1781f537d9ba6461a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:20 -0700 Subject: [PATCH 1194/1317] xfs: fix rtgroup repair estimations When I added online fsck for realtime reflink, I forgot to update xrep_calc_rtgroup_resblks to factor in the size of the refcount btree when it guesses how much space we need to start a repair. This hasn't been a huge problem in practice because there are few filesystems with (a) realtime, (b) rtgroups, (c) reflink, and (d) no rmap. But let's fix this before someone stumbles upon it, especially since LOLLM flagged this for me. Cc: stable@vger.kernel.org # v6.14 Fixes: 83ccffc489975d ("xfs: online repair of the realtime refcount btree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/repair.c | 21 ++++++++++++++++++--- fs/xfs/scrub/trace.h | 12 ++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index 11697a8b2a1d..c2a437416227 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -399,6 +399,7 @@ xrep_calc_rtgroup_resblks( struct xfs_mount *mp = sc->mp; struct xfs_scrub_metadata *sm = sc->sm; uint64_t usedlen; + xfs_extlen_t refcbt_sz = 0; xfs_extlen_t rmapbt_sz = 0; if (!(sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR)) @@ -411,13 +412,27 @@ xrep_calc_rtgroup_resblks( usedlen = xfs_rtbxlen_to_blen(mp, xfs_rtgroup_extents(mp, sm->sm_agno)); ASSERT(usedlen <= XFS_MAX_RGBLOCKS); + if (xfs_has_reflink(mp)) + refcbt_sz = xfs_rtrefcountbt_calc_size(mp, usedlen); + if (xfs_has_rmapbt(mp)) rmapbt_sz = xfs_rtrmapbt_calc_size(mp, usedlen); - trace_xrep_calc_rtgroup_resblks_btsize(mp, sm->sm_agno, usedlen, - rmapbt_sz); + /* + * Guess how many blocks we need to rebuild the rmapbt. For + * non-reflink filesystems we can't have more records than used blocks. + * However, with reflink it's possible to have more than one rmap + * record per rtgroup block. We don't know how many rmaps there could + * be in the rtgroup, so we start off with what we hope is an generous + * over-estimation. + */ + if (refcbt_sz > 0 && rmapbt_sz > 0) + rmapbt_sz *= 2; - return rmapbt_sz; + trace_xrep_calc_rtgroup_resblks_btsize(mp, sm->sm_agno, usedlen, + rmapbt_sz, refcbt_sz); + + return max(rmapbt_sz, refcbt_sz); } #endif /* CONFIG_XFS_RT */ diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 0f5adc293962..cb85f75ce101 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -2376,25 +2376,29 @@ TRACE_EVENT(xrep_calc_ag_resblks_btsize, #ifdef CONFIG_XFS_RT TRACE_EVENT(xrep_calc_rtgroup_resblks_btsize, TP_PROTO(struct xfs_mount *mp, xfs_rgnumber_t rgno, - xfs_rgblock_t usedlen, xfs_rgblock_t rmapbt_sz), - TP_ARGS(mp, rgno, usedlen, rmapbt_sz), + xfs_rgblock_t usedlen, xfs_rgblock_t rmapbt_sz, + xfs_rgblock_t refcbt_sz), + TP_ARGS(mp, rgno, usedlen, rmapbt_sz, refcbt_sz), TP_STRUCT__entry( __field(dev_t, dev) __field(xfs_rgnumber_t, rgno) __field(xfs_rgblock_t, usedlen) __field(xfs_rgblock_t, rmapbt_sz) + __field(xfs_rgblock_t, refcbt_sz) ), TP_fast_assign( __entry->dev = mp->m_super->s_dev; __entry->rgno = rgno; __entry->usedlen = usedlen; __entry->rmapbt_sz = rmapbt_sz; + __entry->refcbt_sz = refcbt_sz; ), - TP_printk("dev %d:%d rgno 0x%x usedlen %u rmapbt %u", + TP_printk("dev %d:%d rgno 0x%x usedlen %u rmapbt %u refcountbt %u", MAJOR(__entry->dev), MINOR(__entry->dev), __entry->rgno, __entry->usedlen, - __entry->rmapbt_sz) + __entry->rmapbt_sz, + __entry->refcbt_sz) ); #endif /* CONFIG_XFS_RT */ From d7b92cbe566f6fe54368f62e4b515d9f80c43a18 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:36 -0700 Subject: [PATCH 1195/1317] xfs: don't cross reference rmapbt with bitmaps if they're incomplete LOLLM points out that runtime errors (e.g. ENOMEM) when we're trying to compute space usag bitmaps are silently dropped by the rmapbt scrubber. We ought to flag that as an incomplete scrub instead of reporting cross-referencing errors based on faulty data. Cc: stable@vger.kernel.org # v6.4 Fixes: fed050f3452da0 ("xfs: cross-reference rmap records with ag btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rmap.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/rmap.c b/fs/xfs/scrub/rmap.c index 0cd3eecd2ca5..68e2847c962b 100644 --- a/fs/xfs/scrub/rmap.c +++ b/fs/xfs/scrub/rmap.c @@ -493,11 +493,18 @@ xchk_rmapbt_walk_ag_metadata( * If there's an error, set XFAIL and disable the bitmap * cross-referencing checks, but proceed with the scrub anyway. */ - if (error) - xchk_btree_xref_process_error(sc, sc->sa.rmap_cur, - sc->sa.rmap_cur->bc_nlevels - 1, &error); - else - cr->bitmaps_complete = true; + if (error) { + if (!xchk_btree_xref_process_error(sc, sc->sa.rmap_cur, + sc->sa.rmap_cur->bc_nlevels - 1, &error)) { + /* only set incomplete if we didn't set xfail */ + if (error) + xchk_set_incomplete(sc); + } + + return 0; + } + + cr->bitmaps_complete = true; return 0; } @@ -567,7 +574,8 @@ xchk_rmapbt( if (error) goto out; - xchk_rmapbt_check_bitmaps(sc, cr); + if (cr->bitmaps_complete) + xchk_rmapbt_check_bitmaps(sc, cr); out: xagb_bitmap_destroy(&cr->refcbt_owned); From ab1c416d2377cdc16123ef521aac4da1c468c3d4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:52 -0700 Subject: [PATCH 1196/1317] xfs: don't let memory failures leak blocks and kill repairs LOLLM complains that a memory allocation failure in xrep_newbt_add_blocks results in online repair leaking blocks that were previously allocated to write a new btree, but the problem is worse than that -- a limitation of the codebase is that the callers cannot undo the transaction /and/ return the error -- either you undo all changes and commit the transaction, or you error out and the filesystem goes down. However, the new btree space reservation object isn't that big (~48 bytes). Let's just do a NOFAIL allocation and the problem goes away. Cc: stable@vger.kernel.org # v6.8 Fixes: be408417630427 ("xfs: implement block reservation accounting for btrees we're staging") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/newbt.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/xfs/scrub/newbt.c b/fs/xfs/scrub/newbt.c index c82f4631fd9c..584076b2a6ee 100644 --- a/fs/xfs/scrub/newbt.c +++ b/fs/xfs/scrub/newbt.c @@ -193,9 +193,11 @@ xrep_newbt_add_blocks( struct xrep_newbt_resv *resv; int error; - resv = kmalloc_obj(struct xrep_newbt_resv, XCHK_GFP_FLAGS); - if (!resv) - return -ENOMEM; + /* + * We have no way to clean up the allocated space *and* return an + * ENOMEM if we fail to allocate this control structure. + */ + resv = kmalloc_obj(struct xrep_newbt_resv, GFP_KERNEL | __GFP_NOFAIL); INIT_LIST_HEAD(&resv->list); resv->agbno = XFS_FSB_TO_AGBNO(mp, args->fsbno); From d80993655f7be2a461c0a63e2022da5757f47dac Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:07 -0700 Subject: [PATCH 1197/1317] xfs: don't merge different file IO error types LOLLM noticed that we can accidentally merge file range health monitoring events even if they have different errors. We shouldn't do that. Cc: stable@vger.kernel.org # v7.0 Fixes: dfa8bad3a8796c ("xfs: convey file I/O errors to the health monitor") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index c3749675ef19..2bdd747f1ead 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -247,7 +247,9 @@ xfs_healthmon_merge_events( case XFS_HEALTHMON_DIOWRITE: case XFS_HEALTHMON_DATALOST: /* logically adjacent file ranges can merge */ - if (existing->fino != new->fino || existing->fgen != new->fgen) + if (existing->fino != new->fino || + existing->fgen != new->fgen || + existing->error != new->error) return false; if (existing->fpos + existing->flen == new->fpos) { From f8f6382ff13109e19d0fc1d0224ff7d29641e56a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:23 -0700 Subject: [PATCH 1198/1317] xfs: fix blockgc group quota scanning when usrquota isn't enforced LOLLM noticed the copy-paste error here -- if user quotas aren't enforced but we're near the group quota limit, we fail to set FLAG_GID and hence we might not actually free any preallocations, causing unnecessary EDQUOT. Fix that. Cc: stable@vger.kernel.org # v5.12 Fixes: c237dd7c709432 ("xfs: flush eof/cowblocks if we can't reserve quota for inode creation") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_icache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index 82dac88e3c4c..de8be344e987 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -1653,7 +1653,7 @@ xfs_blockgc_free_dquots( do_work = true; } - if (XFS_IS_UQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) { + if (XFS_IS_GQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) { icw.icw_gid = make_kgid(mp->m_super->s_user_ns, gdqp->q_id); icw.icw_flags |= XFS_ICWALK_FLAG_GID; do_work = true; From 65f39d09d73718611cee40399179323b5d4ead00 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:38 -0700 Subject: [PATCH 1199/1317] xfs: fix cursor and pointer handling when recovering iunlink buckets LOLLM pointed out a bug in xlog_recover_iunlink_bucket: 1. We don't null out prev_ip after releasing it, which can lead to UAF problems if the inodegc flush call in the loop fails. at which point I noticed even more bugs: 2. If the inodegc flush inside the loop fails, we also leak @ip. 3. We set prev_agino to agino having already advanced agino, which results in inodes with i_prev_unlinked set to itself. 4. If we exit the bottom of the loop with prev_ip set, then prev_ip aliases ip and we also set its i_prev_unlinked to itself. Bugs 3 and 4 introduce loops into the unlinked list, though these loops don't surface because we immediately flush each unlinked inode after loading it. Fix all of these issues. Cc: stable@vger.kernel.org # v6.0 Fixes: 04755d2e5821b3 ("xfs: refactor xlog_recover_process_iunlinks()") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_recover.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index e7e49529658b..cf0d610265fe 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -2736,12 +2736,13 @@ xlog_recover_iunlink_bucket( { struct xfs_mount *mp = pag_mount(pag); struct xfs_inode *prev_ip = NULL; - struct xfs_inode *ip; xfs_agino_t prev_agino, agino; int error = 0; agino = be32_to_cpu(agi->agi_unlinked[bucket]); while (agino != NULLAGINO) { + struct xfs_inode *ip; + error = xfs_iget(mp, NULL, xfs_agino_to_ino(pag, agino), 0, 0, &ip); if (error) @@ -2750,11 +2751,11 @@ xlog_recover_iunlink_bucket( ASSERT(VFS_I(ip)->i_nlink == 0); ASSERT(VFS_I(ip)->i_mode != 0); xfs_iflags_clear(ip, XFS_IRECOVERY); - agino = ip->i_next_unlinked; if (prev_ip) { ip->i_prev_unlinked = prev_agino; xfs_irele(prev_ip); + prev_ip = NULL; /* * Ensure the inode is removed from the unlinked list @@ -2766,18 +2767,20 @@ xlog_recover_iunlink_bucket( * complete. */ error = xfs_inodegc_flush(mp); - if (error) - break; + if (error) { + xfs_irele(ip); + return error; + } } prev_agino = agino; + agino = ip->i_next_unlinked; prev_ip = ip; } if (prev_ip) { int error2; - ip->i_prev_unlinked = prev_agino; xfs_irele(prev_ip); error2 = xfs_inodegc_flush(mp); From ffb48dccce1960a9ea24463a2f3c21d124d6b672 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:54 -0700 Subject: [PATCH 1200/1317] xfs: drop dquot flush lock when we can't find a buffer to flush LOLLM noticed that xfs_qm_flush_one fails to drop the dquot flush lock if it can't grab the buffer associated with the dquot. Since there's no buffer, nobody else is going to drop the dqflock, so we need to do it ourselves. Cc: stable@vger.kernel.org # v6.13 Fixes: ca378189fdfa89 ("xfs: convert quotacheck to attach dquot buffers") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_qm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 99a82107b8e6..54d00d543b51 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -1432,16 +1432,22 @@ xfs_qm_flush_one( error = xfs_dquot_use_attached_buf(dqp, &bp); if (error) - goto out_unlock; + goto out_dqflock; if (!bp) { error = -EFSCORRUPTED; - goto out_unlock; + goto out_dqflock; } error = xfs_qm_dqflush(dqp, bp); if (!error) xfs_buf_delwri_queue(bp, buffer_list); xfs_buf_relse(bp); + mutex_unlock(&dqp->q_qlock); + xfs_qm_dqrele(dqp); + return error; + +out_dqflock: + xfs_dqfunlock(dqp); out_unlock: mutex_unlock(&dqp->q_qlock); xfs_qm_dqrele(dqp); From 476582d754cdc5110f806001417fea6c77824c13 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:39:10 -0700 Subject: [PATCH 1201/1317] xfs: don't let hidden_space go negative in xfs_metafile_resv_init LOLLM points out that if the amount of fdblocks that we can reserve for a metadata btree file goes below the space already used by that file, then the hidden_space subtraction can underflow, causing xfs_dec_fdblocks to subtract a huge amount of space. We never want the target to be less than the used sapce, so fix the logic that adjusts dblocks_avail downwards. Also fix an error in the adjacent comment. Cc: stable@vger.kernel.org # v6.15 Fixes: 1df8d75030b787 ("xfs: make metabtree reservations global") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_metafile.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/xfs/libxfs/xfs_metafile.c b/fs/xfs/libxfs/xfs_metafile.c index 71f004e9dc64..1f54d39003c2 100644 --- a/fs/xfs/libxfs/xfs_metafile.c +++ b/fs/xfs/libxfs/xfs_metafile.c @@ -297,14 +297,14 @@ xfs_metafile_resv_init( goto out_unlock; /* - * Space taken by the per-AG metadata btrees are accounted on-disk as - * used space. We therefore only hide the space that is reserved but - * not used by the trees. + * Space taken by metadata btrees are accounted on-disk as used space. + * We therefore only hide the space that is reserved but not used by + * the trees. */ if (used > target) target = used; else if (target > dblocks_avail) - target = dblocks_avail; + target = max(dblocks_avail, used); hidden_space = target - used; error = xfs_dec_fdblocks(mp, hidden_space, true); From fe2f9135df43db849e74f03956d322ac20b59af7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:39:25 -0700 Subject: [PATCH 1202/1317] xfs: fix wild memcpy access when formatting ondisk rtrefcount btree roots LOLLM noticed that the inode btree root formatting methods copy too many bytes -- there's only one set of keys in node blocks, not two. This causes memory corruption of whatever's beyond the buffers. Cc: stable@vger.kernel.org # v6.14 Fixes: f0415af60f482a ("xfs: wire up a new metafile type for the realtime refcount") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index e2950dbe2068..dcc89b8e149b 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -617,7 +617,7 @@ xfs_rtrefcountbt_from_disk( fpp = xfs_rtrefcount_droot_ptr_addr(dblock, 1, maxrecs); tpp = xfs_rtrefcount_broot_ptr_addr(mp, rblock, 1, rblocklen); numrecs = be16_to_cpu(dblock->bb_numrecs); - memcpy(tkp, fkp, 2 * sizeof(*fkp) * numrecs); + memcpy(tkp, fkp, sizeof(*fkp) * numrecs); memcpy(tpp, fpp, sizeof(*fpp) * numrecs); } else { frp = xfs_rtrefcount_droot_rec_addr(dblock, 1); @@ -703,7 +703,7 @@ xfs_rtrefcountbt_to_disk( fpp = xfs_rtrefcount_broot_ptr_addr(mp, rblock, 1, rblocklen); tpp = xfs_rtrefcount_droot_ptr_addr(dblock, 1, maxrecs); numrecs = be16_to_cpu(rblock->bb_numrecs); - memcpy(tkp, fkp, 2 * sizeof(*fkp) * numrecs); + memcpy(tkp, fkp, sizeof(*fkp) * numrecs); memcpy(tpp, fpp, sizeof(*fpp) * numrecs); } else { frp = xfs_rtrefcount_rec_addr(rblock, 1); From 9ca4ba24259183ce15665be86b2956cd896c4687 Mon Sep 17 00:00:00 2001 From: James Clark Date: Tue, 15 Sep 2026 11:58:17 +0700 Subject: [PATCH 1203/1317] net: macb: fix ordering around PTP timestamp read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PTP_SYS_OFFSET_EXTENDED returns system timestamps that do not correctly bracket the PHC register read on MACB/GEM. On a Raspberry Pi 5, the returned interval can be as short as 37 ns, while an ordered register read takes approximately 1 us. This biases the midpoint used by phc2sys, causing CLOCK_REALTIME to run approximately 0.5 us ahead when synchronized to the PHC. gem_tsu_get_time() reads the nanoseconds register using the driver's relaxed MMIO accessor. On weakly ordered systems, the subsequent system timestamp can be taken before the register read completes. The internal smp_rmb() in the pre-timestamp path also does not guarantee ordering against the subsequent MMIO read. Add rmb() before and after the bracketed nanoseconds read in both the normal and seconds rollover paths so the system timestamps bracket the PHC read. Adding the post-read barrier increases the minimum interval on the same Raspberry Pi 5 to approximately 1 us. Fixes: e51bb5c2784c ("net: macb: ptp: Switch to gettimex64() interface") Tested-by: Nicolai Buchwitz # Raspberry Pi CM5, min bracket 37 ns -> 981 ns Reviewed-by: Nicolai Buchwitz Reviewed-by: Théo Lebrun Assisted-by: LLM Signed-off-by: James Clark Link: https://patch.msgid.link/20260915045823.76100-1-jjc@jclark.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_ptp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_ptp.c b/drivers/net/ethernet/cadence/macb_ptp.c index 6d9166389988..14ae57fa00cb 100644 --- a/drivers/net/ethernet/cadence/macb_ptp.c +++ b/drivers/net/ethernet/cadence/macb_ptp.c @@ -50,7 +50,12 @@ static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts, spin_lock_irqsave(&bp->tsu_clk_lock, flags); ptp_read_system_prets(sts); + /* explicit barriers are needed because gem_readl() is relaxed */ + if (sts) + rmb(); first = gem_readl(bp, TN); + if (sts) + rmb(); ptp_read_system_postts(sts); secl = gem_readl(bp, TSL); sech = gem_readl(bp, TSH); @@ -62,7 +67,11 @@ static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts, * (assume all done within 1s) */ ptp_read_system_prets(sts); + if (sts) + rmb(); ts->tv_nsec = gem_readl(bp, TN); + if (sts) + rmb(); ptp_read_system_postts(sts); secl = gem_readl(bp, TSL); sech = gem_readl(bp, TSH); From 14cb1e7702e5cb3c58888f6aed498381a73927d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Okunev Date: Mon, 14 Sep 2026 12:15:57 +0300 Subject: [PATCH 1204/1317] net: mvpp2: prevent buffer overflow in page_pool allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per‑processor buffering scheme is supported only if the number of pools (nrxqs * 2) does not exceed MVPP2_BM_MAX_POOLS (8). This is already checked in mvpp2_probe() during the initial activation of percpu_pools. However, mvpp2_change_mtu() may later call mvpp2_bm_switch_buffers(priv, true) without this check, which can lead to an out-of-bounds access in the priv->page_pool array in mvpp2_bm_init(). The array is sized to hold MVPP2_PORT_MAX_RXQ entries, and mvpp2_get_nrxqs() may return exactly that value. The per-CPU scheme then doubles it to nrxqs * 2, exceeding the array bounds. Check that the hardware version is MVPP22 or newer and that the number of pools (nrxqs * 2) does not exceed MVPP2_BM_MAX_POOLS before switching to per-CPU mode. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7d04b0b13b11 ("mvpp2: percpu buffers") Signed-off-by: Dmitriy Okunev Link: https://patch.msgid.link/20260914091557.71769-1-dokunevdmitriy@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c index ccc24a1301f2..848ee655c6ea 100644 --- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c @@ -5086,7 +5086,8 @@ static int mvpp2_change_mtu(struct net_device *dev, int mtu) netdev_warn(dev, "mtu %d too high, switching to shared buffers", mtu); mvpp2_bm_switch_buffers(priv, false); } - } else { + } else if (priv->hw_version >= MVPP22 && + mvpp2_get_nrxqs(priv) * 2 <= MVPP2_BM_MAX_POOLS) { bool jumbo = false; int i; From d798162eb364df2e77a56fdbe5bae54440152d3b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 15 Sep 2026 14:30:47 -0700 Subject: [PATCH 1205/1317] dpll: reject a reference sync pin which is not on the pin's dpll dpll_pin_ref_sync_state_set() resolves the partner's driver private data with dpll_pin_on_dpll_priv() and passes the result to ref_sync_get() and ref_sync_set() without looking at it. The helper returns NULL when the partner holds no ref on that dpll. Of the two drivers implementing the feature only zl3073x dereferences the pointer (sync_pin->id); ice ignores it, so ice cannot fault here. The NULL is a teardown race, not a steady state - zl3073x registers every input pin with every channel, so the partner is normally present on the dpll the base pin resolves to. zl3073x_dev_stop() unregisters pins one at a time, taking and dropping dpll_lock for each, and between the partner's turn and the base pin's the partner is out of that dpll's pin_refs while still registered with the channels not yet torn down, so dpll_pin_available() keeps passing. That path is not only driver removal: devlink reload and devlink dev flash both run zl3073x_dev_stop(). Reproduced by holding that state open with a mock dpll device, which is where the frame name comes from: BUG: kernel NULL pointer dereference, address: 0000000000000000 Oops: Oops: 0000 [#1] SMP NOPTI RIP: 0010:mock_ref_sync_get+0x5/0x30 Call Trace: dpll_pin_ref_sync_set+0x19f/0x4a0 dpll_nl_pin_set_doit+0x17d/0x840 genl_family_rcv_msg_doit+0xd6/0x130 genl_rcv_msg+0x181/0x2b0 netlink_rcv_skb+0x55/0x100 genl_rcv+0x23/0x30 netlink_unicast+0x24d/0x370 netlink_sendmsg+0x1e2/0x420 __sys_sendto+0x1db/0x1f0 __x64_sys_sendto+0x1f/0x30 do_syscall_64+0xe1/0x490 Commit d2e914a4a0d0 ("dpll: fix NULL pointer dereference in dpll_msg_add_pin_ref_sync()") added the same guard to the read side, which the kernel walks into by itself because the delete notification is emitted from inside the unregister; the write side needs a pin-set to land in the window and was left alone. Test the priv rather than look up pin_refs directly, so that the two halves key off the same condition. Fixes: 58256a26bfb3 ("dpll: add reference sync get/set") Signed-off-by: Jakub Kicinski Reviewed-by: Ivan Vecera Link: https://patch.msgid.link/20260915213047.1352286-1-kuba@kernel.org Signed-off-by: Paolo Abeni --- drivers/dpll/dpll_netlink.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 523d76a5fd49..45365214fbef 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -1202,6 +1202,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, const enum dpll_pin_state state, struct netlink_ext_ack *extack) { + void *pin_priv, *ref_sync_pin_priv; const struct dpll_pin_ops *ops; enum dpll_pin_state old_state; struct dpll_pin *ref_sync_pin; @@ -1230,9 +1231,15 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, return -EOPNOTSUPP; } dpll = ref->dpll; - ret = ops->ref_sync_get(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, ref_sync_pin), + pin_priv = dpll_pin_on_dpll_priv(dpll, pin); + ref_sync_pin_priv = dpll_pin_on_dpll_priv(dpll, ref_sync_pin); + /* Pin may have been unregistered from this dpll already */ + if (!ref_sync_pin_priv) { + NL_SET_ERR_MSG(extack, + "reference sync pin not registered with the dpll"); + return -ENODEV; + } + ret = ops->ref_sync_get(pin, pin_priv, ref_sync_pin, ref_sync_pin_priv, &old_state, extack); if (ret) { NL_SET_ERR_MSG(extack, "unable to get old reference sync state"); @@ -1241,9 +1248,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, if (state == old_state) return 0; - ret = ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, ref_sync_pin), + ret = ops->ref_sync_set(pin, pin_priv, ref_sync_pin, ref_sync_pin_priv, state, extack); if (ret) { NL_SET_ERR_MSG_FMT(extack, From f81e6c3fb06327bc49cdd6e559845293ba06a704 Mon Sep 17 00:00:00 2001 From: Inbal Schussheim Date: Mon, 14 Sep 2026 12:04:07 +0300 Subject: [PATCH 1206/1317] tcp: exclude old ACKs from tcp fast path Exclude old ACKs before SND.UNA from the tcp fast path as well as ACKs after SND.NXT. Such ACKs will fall through to the slow path, where tcp_ack() performs the appropriate validation and challenge ACK handling according to RFC5961 and Commit 3d501dd326fb1c7 ("tcp: do not accept ACK of bytes we never sent"). This prevents old ACKs from being accepted or modifying connection state as part of the fast path before appropriate ACK validation is applied. In particular, this prevents payload carried by a segment with an excessively old ACK from advancing RCV.NXT before the ACK is rejected. Fixes: 31770e34e43d ("tcp: Revert "tcp: remove header prediction"") Reported-by: Amit Klein Reported-by: Tamir Shahar Reported-by: Inbal Schussheim Suggested-by: Eric Dumazet Cc: stable@vger.kernel.org Signed-off-by: Inbal Schussheim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914090408.1435080-2-inbal.lipshtat@mail.huji.ac.il Signed-off-by: Paolo Abeni --- net/ipv4/tcp_input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 0f60a1dbf927..92bc60716f33 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -6490,6 +6490,7 @@ static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, * or pure receivers (this means either the sequence number or the ack * value must stay constant) * - Unexpected TCP option. + * - ACK sequence number is outside [SND.UNA, SND.NXT]. * * When these conditions are not satisfied it drops into a standard * receive procedure patterned after RFC793 to handle all cases. @@ -6539,7 +6540,7 @@ void tcp_rcv_established(struct sock *sk, struct sk_buff *skb) if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags && TCP_SKB_CB(skb)->seq == tp->rcv_nxt && - !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) { + between(TCP_SKB_CB(skb)->ack_seq, tp->snd_una, tp->snd_nxt)) { int tcp_header_len = tp->tcp_header_len; s32 delta = 0; int flag = 0; From d841cd7513f3d48018175ecb1fb972cfd3c3c10b Mon Sep 17 00:00:00 2001 From: Inbal Schussheim Date: Mon, 14 Sep 2026 12:04:08 +0300 Subject: [PATCH 1207/1317] selftests: net: packetdrill: test exclusion of old ACK from TCP fast path Add a packetdrill test for an in-sequence data segment carrying an excessively old ACK. Verify that the segment falls through from the TCP fast path to the slow path, where the existing ACK validation rejects it and sends a challenge ACK. The payload is not accepted and RCV.NXT remains unchanged. Based on the reproducer from Commit 3d501dd326fb ("tcp: do not accept ACK of bytes we never sent"). Signed-off-by: Inbal Schussheim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914090408.1435080-3-inbal.lipshtat@mail.huji.ac.il Signed-off-by: Paolo Abeni --- .../tcp_rfc5961_reject-old-ack.pkt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt new file mode 100644 index 000000000000..32dd9de1d366 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +`./defaults.sh +sysctl -q net.ipv4.tcp_invalid_ratelimit=0 +` + +// Test rejection of data segments carrying excessively old ACKs + +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 ++0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 ++0 bind(3, ..., ...) = 0 ++0 listen(3, 1024) = 0 + +// ---------------- Handshake ------------------- // ++0 < S 0:0(0) win 65535 ++0 > S. 0:0(0) ack 1 <...> ++0 < . 1:1(0) ack 1 win 65535 ++0 accept(3, ..., ...) = 4 + +// Populate receive memory so the following segment can use +// header prediction. ++0 < P. 1:501(500) ack 1 win 65535 ++0 > . 1:1(0) ack 501 + +// Send an in-sequence data segment carrying an excessively old ACK. ++0 < P. 501:1501(1000) ack 2794967397 win 65535 + +// Challenge ACK; RCV.NXT must remain 501. ++0 > . 1:1(0) ack 501 From a5117e1eccac6ee3bd4aed7cacf8ebcb6b3eb309 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 15 Sep 2026 13:04:23 +0000 Subject: [PATCH 1208/1317] net: skbuff: do not leave stale header offsets after pskb_carve() pskb_carve_inside_header() and pskb_carve_inside_nonlinear() remove the first bytes of a packet and reallocate skb->head. All the headers that were present before the operation are gone, but both functions call skb_headers_offset_update(skb, 0), which is a no-op : skb->mac_header, skb->network_header, skb->transport_header and skb->csum_start keep their old values and now describe bytes which are no longer there. Both helpers size the new head from the old skb_end_offset(), so the stale offsets still land inside the new allocation. They point past skb_tail_pointer() though, to bytes that were never initialized. pskb_carve_inside_nonlinear() is the worst case, because it leaves a zombie skb with an empty linear part (skb->data == skb_tail_pointer(skb), skb_headlen(skb) == 0), while skb_mac_header_was_set() is still true and skb->mac_header is way ahead of skb->data. The only user of pskb_extract() is rds_tcp_data_recv(), and the carved skb is queued on tinc->ti_skb_list. When the RDS incoming message is released, rds_tcp_inc_free() calls skb_queue_purge(), which frees the skbs with SKB_DROP_REASON_QUEUE_PURGE. This is visible from drop_monitor, which then tries to pull back to the (bogus) mac header : skbuff: __skb_pull(len=234) skb len=6968 data_len=6968 headroom=0 headlen=0 tailroom=0 end-tail=384 mac=(234,14) mac_len=14 net=(248,40) trans=288 shinfo(txflags=0 nr_frags=1 gso(size=1428 type=16 segs=5)) csum(0x100120 start=288 offset=16 ip_summed=3 complete_sw=0 valid=1 level=0) hash(0x7b446c6c sw=0 l4=1) proto=0x86dd pkttype=0 iif=60 kernel BUG at ./include/linux/skbuff.h:2847! Add skb_carve_reset_headers() to mark the mac and transport headers as not set, reset the network header, clear skb->mac_len, and drop a now meaningless CHECKSUM_PARTIAL (csum_start no longer describes anything). Invalidate the inner offsets as well. Unlike mac_header and transport_header they have no "unset" sentinel, so a leftover non-zero value still looks like a real header. Zero skb->inner_mac_header, skb->inner_network_header, skb->inner_transport_header, skb->inner_protocol and skb->encapsulation, so that all the header state is invalidated in one place. v2: fixed an inaccurate changelog. The stale offsets stay inside the new skb->head, which is never smaller than the old one, they simply point past skb_tail_pointer() to bytes that are gone. Thanks to Xuanqiang Luo for insisting on this. Also invalidate the inner header state, as suggested by the netdev AI review : https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260911114922.621937-1-edumazet%40google.com Fixes: 6fa01ccd8830 ("skbuff: Add pskb_extract() helper function") Reported-by: syzbot+586af68eb819833c2d91@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6aa3e9d3.f2639fcc.29487d.0028.GAE@google.com/ Cc: Xuanqiang Luo Cc: Allison Henderson Cc: rds-devel@oss.oracle.com Signed-off-by: Eric Dumazet Reviewed-by: Xuanqiang Luo Link: https://patch.msgid.link/20260915130423.3956471-1-edumazet@google.com Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index cc3b4b70288b..609f2c7f4a47 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6832,6 +6832,34 @@ struct sk_buff *alloc_skb_with_frags(unsigned long header_len, } EXPORT_SYMBOL(alloc_skb_with_frags); +/* pskb_carve_inside_header() and pskb_carve_inside_nonlinear() + * remove the first bytes of a packet and reallocate skb->head. + * + * Whatever headers were present before the operation are gone, + * we must not leave stale offsets, otherwise users of this skb + * (skb_dump(), drop_monitor, taps, ...) would read or pull garbage. + */ +static void skb_carve_reset_headers(struct sk_buff *skb) +{ + skb_unset_mac_header(skb); + skb_unset_transport_header(skb); + skb_reset_network_header(skb); + skb->mac_len = 0; + + /* Inner offsets have no "unset" marker, zero them so that + * skb_inner_network_header_was_set() becomes false and no + * consumer mistakes them for a real (and long gone) header. + */ + skb->inner_mac_header = 0; + skb->inner_network_header = 0; + skb->inner_transport_header = 0; + skb->inner_protocol = 0; + skb->encapsulation = 0; + + if (skb->ip_summed == CHECKSUM_PARTIAL) + skb->ip_summed = CHECKSUM_NONE; +} + /* carve out the first off bytes from skb when off < headlen */ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off, const int headlen, gfp_t gfp_mask) @@ -6887,7 +6915,7 @@ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off, skb->head_frag = 0; skb_set_end_offset(skb, size); skb_set_tail_pointer(skb, skb_headlen(skb)); - skb_headers_offset_update(skb, 0); + skb_carve_reset_headers(skb); skb->cloned = 0; skb->hdr_len = 0; skb->nohdr = 0; @@ -7027,7 +7055,7 @@ static int pskb_carve_inside_nonlinear(struct sk_buff *skb, const u32 off, skb->data = data; skb_set_end_offset(skb, size); skb_reset_tail_pointer(skb); - skb_headers_offset_update(skb, 0); + skb_carve_reset_headers(skb); skb->cloned = 0; skb->hdr_len = 0; skb->nohdr = 0; From 2b0f561f21b27c40c91ea4975268a06092bd7e9c Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:57 +0200 Subject: [PATCH 1209/1317] mptcp: avoid unneeded actions on subflow reset Once in a blue moon, the mptcp receive path can recursively call mptcp_data_ready() via state change under unlucky error conditions, and then try to hold the data lock again. Break the recursion loop explicitly checking for the exceptional condition. Add a new flag instead of using an existing one like 'closing', to exit early in subflow_state_change(), and explicitly flush the RX queue at reset time. This avoids unneeded processing to check for available data -- calling get_mapping_status() and more on a dying subflow -- but also in error reporting and worker scheduling. Note that we must consume the currently peeked skb before invoking mptcp_dss_corruption to avoid consuming it again after the eventual reset has freed it. Fixes: e32d262c89e2 ("mptcp: handle consistently DSS corruption") Cc: stable@vger.kernel.org Reported-by: Xinyang Ge Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-1-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 4 ++-- net/mptcp/protocol.h | 3 ++- net/mptcp/subflow.c | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 8dc25ef1542c..d9fc3be9d2db 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -856,12 +856,12 @@ static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk, mptcp_dss_corruption(msk, ssk); } } else { + sk_eat_skb(ssk, skb); + if (unlikely(!fin)) { DEBUG_NET_WARN_ON_ONCE(1); mptcp_dss_corruption(msk, ssk); } - - sk_eat_skb(ssk, skb); } WRITE_ONCE(tp->copied_seq, seq); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 2b4c27426477..0384d6a023f9 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -585,7 +585,8 @@ struct mptcp_subflow_context { is_mptfo : 1, /* subflow is doing TFO */ close_event_done : 1, /* has done the post-closed part */ mpc_drop : 1, /* the MPC option has been dropped in a rtx */ - __unused : 9; + resetting : 1, /* subflow is resetting */ + __unused : 8; bool data_avail; bool scheduled; bool pm_listener; /* a listener managed by the kernel PM? */ diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index 01db7edce18a..f0a6725d2c37 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -438,6 +438,10 @@ void mptcp_subflow_reset(struct sock *ssk) /* must hold: tcp_done() could drop last reference on parent */ sock_hold(sk); + subflow->resetting = 1; + + /* No need to delay the actual close for to-be discarded data. */ + __skb_queue_purge(&ssk->sk_receive_queue); mptcp_send_active_reset_reason(ssk); tcp_done(ssk); if (!test_and_set_bit(MPTCP_WORK_CLOSE_SUBFLOW, &mptcp_sk(sk)->flags)) @@ -1883,6 +1887,13 @@ static void subflow_state_change(struct sock *sk) __subflow_state_change(sk); + /* Rx queue processing is unneeded, error reporting will take place at + * __mptcp_close_ssk() time and subflow reset can't happen in case of + * fallback: subflow_sched_work_if_closed() would be a no-op. + */ + if (subflow->resetting) + return; + /* as recvmsg() does not acquire the subflow socket for ssk selection * a fin packet carrying a DSS can be unnoticed if we don't trigger * the data available machinery here. From 42064de57fb83231fcc89663a94885f228a1ee53 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:58 +0200 Subject: [PATCH 1210/1317] mptcp: close race between scheduler and state change The mptcp scheduler may race with subflow sockets state change: data transmission on the selected socket may fail and a later release could try to use mss_now reset to 0 for a divide operation. Address the issue by explicitly checking for the critical scenario. Fixes: c886d70286bf ("mptcp: do not queue data on closed subflows") Cc: stable@vger.kernel.org Reported-by: Shardul Bankar Reported-by: Xinyang Ge Closes: https://lore.kernel.org/20260525194828.1137119-1-shardul.b@mpiricsoftware.com Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-2-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index d9fc3be9d2db..577d0134b9ec 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1664,7 +1664,9 @@ struct sock *mptcp_subflow_get_send(struct mptcp_sock *msk) static void mptcp_push_release(struct sock *ssk, struct mptcp_sendmsg_info *info) { - tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, info->size_goal); + if (info->mss_now) + tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, + info->size_goal); release_sock(ssk); } From f3ef03357396d4b147d8e76c75fb612c2f264ffc Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:59 +0200 Subject: [PATCH 1211/1317] mptcp: fix bad accounting in __mptcp_subflow_push_pending() If __subflow_push_pending() errors out we should avoid updating the copied byte counters, to avoid mismatch push call later on. Fixes: 0fa1b3783a17 ("mptcp: use get_send wrapper") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-3-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 577d0134b9ec..e89a69ab927c 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1854,7 +1854,8 @@ static void __mptcp_subflow_push_pending(struct sock *sk, struct sock *ssk, bool ret = __subflow_push_pending(sk, ssk, &info); if (ret <= 0) keep_pushing = false; - copied += ret; + else + copied += ret; } mptcp_for_each_subflow(msk, subflow) { From 9413959fa9fe2d94d4814f8cc2b60409f4cd46b5 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 25 Aug 2026 23:09:32 +0800 Subject: [PATCH 1212/1317] drm/amd/pm: report energy accumulator for smu 14.0.3 add energy accumulator on pmfw 0x00685000 and above version. Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit 4aa733ab15b303e2a40e2985ac21a0e01f24cc4a) --- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c index 56a5c11bc196..ed99e61f18e1 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c @@ -2142,6 +2142,7 @@ static void smu_v14_0_2_init_msg_ctl(struct smu_context *smu) static ssize_t smu_v14_0_2_get_gpu_metrics(struct smu_context *smu, void **table) { + uint32_t mp1_ver = amdgpu_ip_version(smu->adev, MP1_HWIP, 0); struct gpu_metrics_v1_3 *gpu_metrics = (struct gpu_metrics_v1_3 *)smu_driver_table_ptr( smu, SMU_DRIVER_TABLE_GPU_METRICS); @@ -2171,6 +2172,8 @@ static ssize_t smu_v14_0_2_get_gpu_metrics(struct smu_context *smu, metrics->Vcn1ActivityPercentage); gpu_metrics->average_socket_power = metrics->AverageSocketPower; + if (mp1_ver == IP_VERSION(14, 0, 3) && smu->smc_fw_version >= 0x00685000) + gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; if (metrics->AverageGfxActivity <= SMU_14_0_2_BUSY_THRESHOLD) gpu_metrics->average_gfxclk_frequency = metrics->AverageGfxclkFrequencyPostDs; From 63e19ef3ddab806c472748c825f4dc88dcd994e8 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 25 Aug 2026 23:21:53 -0400 Subject: [PATCH 1213/1317] drm/amd/display: Atomize IRQ register read/modify/write ops [Why] The OTG_GLOBAL_SYNC_STATUS register controls various HW IRQ sources for the output timing generator (OTG). VUPDATE_NO_LOCK is one of them. To enable the IRQ, driver sets the VUPDATE_NO_LOCK_EN bit in the GLOBAL_SYNC_STATUS register. To ack the IRQ after it fires, the driver sets the VUPDATE_NO_LOCK_CLEAR bit in the same GLOBAL_SYNC_STATUS register. The bit sets are done through read/modify/write operations, which are not atomic. Thus, the following race is possible: Thread A: IRQ handler: *HW IRQ fires* # IRQ disable val = read(GLOBAL_SYNC_STATUS) unset(val, VUPDATE_NO_LOCK_EN) write(val, GLOBAL_SYNC_STATUS) # ACK reads VUPDATE_NO_LOCK_EN unset val1 = read(GLOBAL_SYNC_STATUS) set(val1, VUPDATE_NO_LOCK_CLEAR) # IRQ enable val = read(GLOBAL_SYNC_STATUS) set(val, VUPDATE_NO_LOCK_EN) write(val, GLOBAL_SYNC_STATUS) # BAD! clears VUPDATE_NO_LOCK_EN write(val1, GLOBAL_SYNC_STATUS) Regarding the tagged Fixes: change, it appears the change made this race more likely to occur. Since VUPDATE_NO_LOCK is now the sole IRQ source for vblank handling, a single race on high refresh panels can lead to a time out. [How] The GLOBAL_SYNC_STATUS register is only one example, other IRQ control registers also share the same scheme. On top of GLOBAL_SYNC_STATUS, let's clean up those as well. To keep things simple, Let's atomize the IRQ rmw ops via a single driver-wide spinlock. Due to the small scope of this lock, it is unlikely to cause noticeable overhead on top of all the existing locking within the IRQ set/handle paths. Since DM is responsible for locking, wrap dc_interrupt_set/ack with the spinlock in the new amdgpu_dm_irq_set/ack functions. Migrate/drop all references in DM to dc_interrupt_set/ack to use amdgpu_dm_irq_set/ack instead. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5616 Fixes: c87e6635d2db ("drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock") Reviewed-by: Mario Limonciello Signed-off-by: Leo Li Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 70de0a0216583a53c946155f8c8adedfdca6b4e7) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 12 ++++ .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 3 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 3 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 64 +++++++++++-------- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 28 ++++++++ .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 16 +++-- .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 8 ++- .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 34 +++++----- 9 files changed, 117 insertions(+), 55 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 08b8605029ab..36d2f86f000a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1420,7 +1420,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, if (acrtc && state->stream_status[i].plane_count != 0 && amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; - rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; + rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; if (rc) drm_warn(adev_to_drm(adev), "Failed to %s pflip interrupts\n", enable ? "enable" : "disable"); @@ -1444,7 +1444,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, /* During gpu-reset we disable and then enable vblank irq, so * don't use amdgpu_irq_get/put() to avoid refcount change. */ - if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) + if (!amdgpu_dm_irq_set(adev, irq_source, enable)) drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); } else if (acrtc && state->stream_status[i].plane_count != 0) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 3524931451c8..881c8d1c3cc0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -552,6 +552,18 @@ struct amdgpu_display_manager { struct common_irq_params vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; + /** + * @irq_reg_lock: + * + * Serializes the read-modify-writes of the HW interrupt control + * registers. Several interrupt sources share one register - e.g. the + * enable and clear bits of both VSTARTUP (vblank) and VUPDATE_NO_LOCK + * live in OTG_GLOBAL_SYNC_STATUS. Therefore, enabling one source must + * not race with acking another. Held only across amdgpu_dm_irq_set() + * and amdgpu_dm_irq_ack(). + */ + spinlock_t irq_reg_lock; + /** * @dmub_trace_params: * diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 62eac6e65334..1d941be73561 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -31,6 +31,7 @@ #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" #include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_irq.h" #include "amdgpu_dm_plane.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_debugfs.h" @@ -91,7 +92,7 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst; - rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; + rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n", acrtc->crtc_id, enable ? "en" : "dis", rc); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 298de7b75ca8..ced8b3d2d762 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1439,12 +1439,13 @@ void dm_helpers_free_gpu_mem( bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable) { + struct amdgpu_device *adev = ctx->driver_context; enum dc_irq_source irq_source; bool ret; irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; - ret = dc_interrupt_set(ctx->dc, irq_source, enable); + ret = amdgpu_dm_irq_set(adev, irq_source, enable); DRM_DEBUG_DRIVER("Dmub trace irq %sabling: r=%d\n", enable ? "en" : "dis", ret); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index d0239a3de2e1..74a8735168aa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -396,6 +396,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) DRM_DEBUG_KMS("DM_IRQ\n"); spin_lock_init(&adev->dm.irq_handler_list_table_lock); + spin_lock_init(&adev->dm.irq_reg_lock); adev->dm.irq_wq = alloc_workqueue("amdgpu_dm_irq", WQ_UNBOUND | WQ_HIGHPRI, 0); @@ -530,7 +531,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) */ for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6RX; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; - dc_interrupt_set(adev->dm.dc, src, false); + amdgpu_dm_irq_set(adev, src, false); DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -568,7 +569,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, true); + amdgpu_dm_irq_set(adev, src, true); } DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -594,7 +595,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, true); + amdgpu_dm_irq_set(adev, src, true); } DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -690,6 +691,23 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, } EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); +bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, + bool enable) +{ + guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); + + return dc_interrupt_set(adev->dm.dc, src, enable); +} +EXPORT_IF_KUNIT(amdgpu_dm_irq_set); + +void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src) +{ + guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); + + dc_interrupt_ack(adev->dm.dc, src); +} +EXPORT_IF_KUNIT(amdgpu_dm_irq_ack); + /** * amdgpu_dm_irq_handler - Generic DM IRQ handler * @adev: amdgpu base driver device containing the DM device @@ -710,7 +728,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev, entry->src_id, entry->src_data[0]); - dc_interrupt_ack(adev->dm.dc, src); + amdgpu_dm_irq_ack(adev, src); /* Call high irq work immediately */ amdgpu_dm_irq_immediate_work(adev, src); @@ -750,7 +768,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, src, st); + amdgpu_dm_irq_set(adev, src, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_hpd_irq_state); @@ -785,7 +803,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) dc_allow_idle_optimizations(dc, false); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } @@ -842,7 +860,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *a enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_outbox_irq_state); @@ -870,7 +888,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *ad enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_trace_irq_state); @@ -937,9 +955,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_set_irq_funcs); void amdgpu_dm_outbox_init(struct amdgpu_device *adev) { - dc_interrupt_set(adev->dm.dc, - DC_IRQ_SOURCE_DMCUB_OUTBOX, - true); + amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); } EXPORT_IF_KUNIT(amdgpu_dm_outbox_init); @@ -962,7 +978,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) /* First, clear all hpd and hpdrx interrupts */ for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) { - if (!dc_interrupt_set(adev->dm.dc, i, false)) + if (!amdgpu_dm_irq_set(adev, i, false)) drm_err(dev, "Failed to clear hpd(rx) source=%d on init\n", i); } @@ -991,7 +1007,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) * of dm. Note that only hpd interrupt types are registered with * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on * hpd_rx isn't available. DM currently controls hpd_rx - * explicitly with dc_interrupt_set() + * explicitly with amdgpu_dm_irq_set() */ if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; @@ -1000,23 +1016,21 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) * and what bios reports as the # of connectors with hpd * sources. Since the # of hpd source types registered * with base driver == mode_info.num_hpd, we have to - * fallback to dc_interrupt_set for the remaining types. + * fallback to amdgpu_dm_irq_set for the remaining types. */ if (irq_type < adev->mode_info.num_hpd) { if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", dc_link->irq_source_hpd); } else { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd, - true); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, + true); } } if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd_rx, - true); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, + true); } } drm_connector_list_iter_end(&iter); @@ -1061,16 +1075,14 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", dc_link->irq_source_hpd); } else { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd, - false); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, + false); } } if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd_rx, - false); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, + false); } } drm_connector_list_iter_end(&iter); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index 4c200a9614a7..bc16ecc67329 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -89,6 +89,34 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, enum dc_irq_source irq_source, void *ih_index); +/** + * amdgpu_dm_irq_set - enable or disable a DC interrupt source. + * + * @adev: AMD DRM device + * @src: DC interrupt source to toggle + * @enable: true to enable the source, false to disable it + * + * DM-wide replacement for dc_interrupt_set(). As locking is DM's + * responsibility, this is a thin wrapper serializes the underlying + * read-modify-write against the other interrupt sources sharing HW control + * registers with @src, so DM must never call dc_interrupt_set() directly. + * + * Returns: true if the source was toggled. + */ +bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, + bool enable); + +/** + * amdgpu_dm_irq_ack - acknowledge a DC interrupt source. + * + * @adev: AMD DRM device + * @src: DC interrupt source to acknowledge + * + * DM-wide replacement for dc_interrupt_ack(), serialized the same way as + * amdgpu_dm_irq_set(). + */ +void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src); + void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); void amdgpu_dm_outbox_init(struct amdgpu_device *adev); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index 0d998f204250..ae0f4da96252 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -436,7 +436,7 @@ static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test) * dm_test_crtc_set_vupdate_irq_dc_busy - Test vupdate irq when DC rejects request * @test: The KUnit test context * - * With an OTG instance assigned but no DC attached, dc_interrupt_set() returns + * With an OTG instance assigned but no DC attached, amdgpu_dm_irq_set() returns * false and the function must report the request as busy (-EBUSY). */ static void dm_test_crtc_set_vupdate_irq_dc_busy(struct kunit *test) @@ -453,12 +453,12 @@ static void dm_test_crtc_set_vupdate_irq_dc_busy(struct kunit *test) acrtc->base.dev = &adev->ddev; acrtc->otg_inst = 0; - /* adev->dm.dc is NULL, so dc_interrupt_set() returns false. */ + /* adev->dm.dc is NULL, so amdgpu_dm_irq_set() returns false. */ KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), -EBUSY); } -/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +/* Per-source funcs let amdgpu_dm_irq_set() succeed without register access. */ static bool dm_test_vupdate_irq_src_set(struct irq_service *irq_service, const struct irq_source_info *info, bool enable) @@ -477,7 +477,7 @@ static struct irq_source_info_funcs dm_test_vupdate_irq_src_funcs = { .ack = dm_test_vupdate_irq_src_ack, }; -/* A .set that fails so dc_interrupt_set() reports the source as busy. */ +/* A .set that fails so amdgpu_dm_irq_set() reports the source as busy. */ static bool dm_test_vupdate_irq_src_set_busy(struct irq_service *irq_service, const struct irq_source_info *info, bool enable) @@ -519,7 +519,9 @@ static void dm_test_crtc_set_vupdate_irq_enable(struct kunit *test) irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); - /* Populate the per-source info table so dc_interrupt_set() succeeds. */ + /* + * Populate the per-source info table so amdgpu_dm_irq_set() succeeds. + */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); @@ -1018,7 +1020,9 @@ static void dm_test_crtc_enable_vblank_vupdate_busy(struct kunit *test) irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); - /* Per-source .set fails so dc_interrupt_set() reports the source busy. */ + /* + * Per-source .set fails so amdgpu_dm_irq_set() reports the source busy. + */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 82e0c984693c..639512bea275 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -2442,17 +2442,21 @@ static void dm_test_is_dp_sink_present_null_priv(struct kunit *test) * dm_test_dmub_outbox_interrupt_control_null_dc - Test outbox irq control with NULL dc * @test: The KUnit test context * - * dc_interrupt_set() is NULL-safe and returns false when dc is NULL, so the + * amdgpu_dm_irq_set() is NULL-safe and returns false when dc is NULL, so the * helper returns false without touching real interrupt hardware. */ static void dm_test_dmub_outbox_interrupt_control_null_dc(struct kunit *test) { + struct amdgpu_device *adev; struct dc_context *ctx; + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; - /* ctx->dc is NULL → dc_interrupt_set returns false */ + /* adev->dm.dc is NULL → amdgpu_dm_irq_set returns false */ KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, true)); KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, false)); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 861ee9eaa032..95322d8c8613 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -268,7 +268,7 @@ static bool dm_test_irq_src_ack(struct irq_service *irq_service, return true; } -/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +/* Per-source funcs let amdgpu_dm_irq_set() succeed without register access. */ static struct irq_source_info_funcs dm_test_irq_src_funcs = { .set = dm_test_irq_src_set, .ack = dm_test_irq_src_ack, @@ -290,7 +290,7 @@ static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); /* - * Populate the per-source info table so dc_interrupt_set()/_ack() + * Populate the per-source info table so amdgpu_dm_irq_set()/_ack() * succeed without touching hardware registers. */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, @@ -1227,7 +1227,7 @@ static void dm_test_irq_suspend_empty(struct kunit *test) KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); /* - * With no registered handlers the HW dc_interrupt_set() calls are + * With no registered handlers the amdgpu_dm_irq_set() calls are * skipped, so suspend must complete without touching the (absent) DC. */ amdgpu_dm_irq_suspend(adev); @@ -1275,11 +1275,11 @@ static void dm_test_irq_resume_late_empty(struct kunit *test) } /** - * dm_test_irq_suspend_registered - Test suspend reaches the dc_interrupt_set path + * dm_test_irq_suspend_registered - Test suspend reaches the irq set path * @test: The KUnit test context * * Registers a low-context HPD handler so the handler list is non-empty, - * forcing amdgpu_dm_irq_suspend() to call dc_interrupt_set() (NULL-safe with + * forcing amdgpu_dm_irq_suspend() to call amdgpu_dm_irq_set() (NULL-safe with * no DC) and flush_work() on the registered handler. */ static void dm_test_irq_suspend_registered(struct kunit *test) @@ -1330,11 +1330,11 @@ static void dm_test_irq_suspend_disables_polling(struct kunit *test) } /** - * dm_test_irq_resume_early_registered - Test early resume reaches dc_interrupt_set + * dm_test_irq_resume_early_registered - Test early resume reaches irq set * @test: The KUnit test context * * Registers a low-context HPD RX handler so early resume calls - * dc_interrupt_set() for the short-pulse interrupt source. + * amdgpu_dm_irq_set() for the short-pulse interrupt source. */ static void dm_test_irq_resume_early_registered(struct kunit *test) { @@ -1358,10 +1358,10 @@ static void dm_test_irq_resume_early_registered(struct kunit *test) } /** - * dm_test_irq_resume_late_registered - Test late resume reaches dc_interrupt_set + * dm_test_irq_resume_late_registered - Test late resume reaches irq set * @test: The KUnit test context * - * Registers a low-context HPD handler so late resume calls dc_interrupt_set() + * Registers a low-context HPD handler so late resume calls amdgpu_dm_irq_set() * for the HPD interrupt source. */ static void dm_test_irq_resume_late_registered(struct kunit *test) @@ -1592,7 +1592,7 @@ static void dm_test_set_crtc_irq_state_enable(struct kunit *test) /* * otg_inst >= 0 computes the irq source and reaches the NULL-safe - * dc_interrupt_set(); the ips_support branch is skipped (dc == NULL). + * amdgpu_dm_irq_set(); the ips_support branch is skipped (dc == NULL). */ acrtc->otg_inst = 3; adev->mode_info.crtcs[0] = acrtc; @@ -1671,8 +1671,8 @@ static void dm_test_set_vupdate_irq_state_enable(struct kunit *test) * * With a non-NULL DC that advertises IPS support and currently allows idle * optimizations, dm_irq_state() must call dc_allow_idle_optimizations() before - * dc_interrupt_set(). disable_idle_power_optimizations makes that call a safe - * early return, and per-source stub funcs let dc_interrupt_set() succeed. + * amdgpu_dm_irq_set(). disable_idle_power_optimizations makes that call a safe + * early return, and per-source stub funcs let amdgpu_dm_irq_set() succeed. */ static void dm_test_set_crtc_irq_state_allows_idle(struct kunit *test) { @@ -1891,7 +1891,7 @@ static void dm_test_set_hpd_irq_state_null_dc(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - /* dc_interrupt_set() is a no-op when dc is NULL, so both states + /* amdgpu_dm_irq_set() is a no-op when dc is NULL, so both states * return 0 without dereferencing the (absent) DC. */ KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, @@ -1951,7 +1951,7 @@ static void dm_test_outbox_init_null_dc(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - /* Single dc_interrupt_set() call must be skipped when dc is NULL. */ + /* Single amdgpu_dm_irq_set() call must be skipped when dc is NULL. */ amdgpu_dm_outbox_init(adev); } @@ -1969,7 +1969,7 @@ static void dm_test_hpd_init_empty_connectors(struct kunit *test) /* * With an empty connector list the per-connector loop is skipped and - * the initial clear loop relies on dc_interrupt_set() being a no-op + * the initial clear loop relies on amdgpu_dm_irq_set() being a no-op * for a NULL dc, so init must complete without touching the DC. */ amdgpu_dm_hpd_init(adev); @@ -2004,7 +2004,7 @@ static void dm_test_hpd_init_fini_with_connectors(struct kunit *test) /* * num_hpd = 0 forces irq_type >= num_hpd so the loop takes the HW - * fallback (dc_interrupt_set()) instead of amdgpu_irq_get(); with a + * fallback (amdgpu_dm_irq_set()) instead of amdgpu_irq_get(); with a * NULL dc that fallback is a safe no-op. */ adev->mode_info.num_hpd = 0; @@ -2090,7 +2090,7 @@ static void dm_test_hpd_init_fini_irq_ref(struct kunit *test) /* * num_hpd >= 1 makes irq_type (0) < num_hpd, so the loop takes the * amdgpu_irq_get()/amdgpu_irq_put() branch instead of the - * dc_interrupt_set() fallback. The mock device has irq.installed == + * amdgpu_dm_irq_set() fallback. The mock device has irq.installed == * false, so both calls fail early with -ENOENT (logging an error) * without touching the base-driver irq state. */ From 5f28bb1c2cd9dcdb76a20d61b3ea069b85893c59 Mon Sep 17 00:00:00 2001 From: Vladimir Marioukhine Date: Wed, 12 Aug 2026 13:19:46 -0400 Subject: [PATCH 1214/1317] drm/amdkfd: implement restore_mqd callbacks for GFX12/12.1 kfd_mqd_manager_v12.c (GFX 12.0) and kfd_mqd_manager_v12_1.c (GFX 12.1) do not implement restore_mqd callbacks, leaving the function pointers NULL and causing CRIU restore to return -EOPNOTSUPP on GFX12. Implement restore_mqd for both compute and SDMA queues in kfd_mqd_manager_v12.c and kfd_mqd_manager_v12_1.c, modeled after the GFX 11 implementation with the following improvements: - update cp_mqd_base_addr_lo/hi to the newly allocated MQD address, fixing a pre-existing gap shared with v11 where the in-MQD copy still pointed at the old checkpoint-time address after restore - memset the full allocation before memcpy for compute queues to avoid stale data in the GTT sub-allocator tail; SDMA MQDs use sizeof(*m) since they are packed at mqd_size stride in a shared BO checkpoint_mqd registration is deferred to a follow-up patch that also implements get_checkpoint_info, so that checkpoint and restore are enabled together as a complete and testable unit. Note: GFX12.1 restore handles XCC0 only. Multi-XCC CRIU restore is currently unreachable due to a separate validation issue in kfd_criu_restore_queue(). A pr_warn_once() is emitted if a multi-XCC device is encountered. Signed-off-by: Vladimir Marioukhine Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit b1f9601237d050f5df478464cf51bf1fff29a256) Cc: stable@vger.kernel.org --- .../drm/amd/amdkfd/kfd_device_queue_manager.c | 7 +- .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 59 ++++++++++++++++ .../drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 68 +++++++++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 9811e4e10291..2f78395a0c31 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -770,10 +770,12 @@ static int create_queue_nocpsch(struct device_queue_manager *dqm, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; if (qd && !mqd_mgr->restore_mqd) { - pr_debug("restore_mqd not implemented for this GPU\n"); + pr_debug("restore_mqd not implemented for queue type %d\n", + q->properties.type); retval = -EOPNOTSUPP; goto deallocate_vmid; } + if (q->properties.type == KFD_QUEUE_TYPE_COMPUTE) { retval = allocate_hqd(dqm, q); if (retval) @@ -2250,7 +2252,8 @@ static int create_queue_cpsch(struct device_queue_manager *dqm, struct queue *q, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; if (qd && !mqd_mgr->restore_mqd) { - pr_debug("restore_mqd not implemented for this GPU\n"); + pr_debug("restore_mqd not implemented for queue type %d\n", + q->properties.type); retval = -EOPNOTSUPP; goto out_deallocate_doorbell; } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c index 7c387fa90076..63f25a60baa5 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -380,6 +380,63 @@ static int debugfs_show_mqd_sdma(struct seq_file *m, void *data) #endif +static void restore_mqd(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, const void *mqd_src, + const void *ctl_stack_src, const u32 ctl_stack_size) +{ + u64 addr; + struct v12_compute_mqd *m; + + m = (struct v12_compute_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + /* Update MQD base address to the newly allocated location */ + m->cp_mqd_base_addr_lo = lower_32_bits(addr); + m->cp_mqd_base_addr_hi = upper_32_bits(addr); + + m->cp_hqd_pq_doorbell_control &= + ~CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK; + m->cp_hqd_pq_doorbell_control |= + qp->doorbell_off << CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + pr_debug("cp_hqd_pq_doorbell_control 0x%x\n", m->cp_hqd_pq_doorbell_control); + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + +static void restore_mqd_sdma(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, + const void *mqd_src, + const void *ctl_stack_src, + const u32 ctl_stack_size) +{ + u64 addr; + struct v12_sdma_mqd *m; + + m = (struct v12_sdma_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + m->sdmax_rlcx_doorbell_offset = + qp->doorbell_off << SDMA0_QUEUE0_DOORBELL_OFFSET__OFFSET__SHIFT; + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, struct kfd_node *dev) { @@ -407,6 +464,7 @@ struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, mqd->mqd_size = sizeof(struct v12_compute_mqd); mqd->get_wave_state = get_wave_state; mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; #endif @@ -453,6 +511,7 @@ struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_sdma; mqd->mqd_size = sizeof(struct v12_sdma_mqd); mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_sdma; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd_sdma; #endif diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index 431a940f91f3..708bbb08c089 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -641,6 +641,72 @@ static int debugfs_show_mqd_sdma(struct seq_file *m, void *data) #endif +static void restore_mqd_v12_1(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, const void *mqd_src, + const void *ctl_stack_src, const u32 ctl_stack_size) +{ + u64 addr; + struct v12_1_compute_mqd *m; + + /* + * GFX12.1 is multi-XCC capable but this restore handles XCC0 only. + * Multi-XCC CRIU restore is currently unreachable because + * kfd_criu_restore_queue() validates against unscaled mqd_size. + */ + if (NUM_XCC(mm->dev->xcc_mask) > 1) + pr_warn_once("GFX12.1 multi-XCC CRIU restore not fully supported\n"); + + m = (struct v12_1_compute_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size) * + NUM_XCC(mm->dev->xcc_mask)); + memcpy(m, mqd_src, sizeof(*m)); + + /* Update MQD base address to the newly allocated location */ + m->cp_mqd_base_addr_lo = lower_32_bits(addr); + m->cp_mqd_base_addr_hi = upper_32_bits(addr); + + m->cp_hqd_pq_doorbell_control &= + ~CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK; + m->cp_hqd_pq_doorbell_control |= + qp->doorbell_off << CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + pr_debug("cp_hqd_pq_doorbell_control 0x%x\n", m->cp_hqd_pq_doorbell_control); + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + +static void restore_mqd_sdma_v12_1(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, + const void *mqd_src, + const void *ctl_stack_src, + const u32 ctl_stack_size) +{ + u64 addr; + struct v12_sdma_mqd *m; + + m = (struct v12_sdma_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + m->sdmax_rlcx_doorbell_offset = + qp->doorbell_off << SDMA0_SDMA_QUEUE0_DOORBELL_OFFSET__OFFSET__SHIFT; + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, struct kfd_node *dev) { @@ -668,6 +734,7 @@ struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, mqd->mqd_size = sizeof(struct v12_1_compute_mqd); mqd->get_wave_state = get_wave_state_v12_1; mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_v12_1; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; #endif @@ -714,6 +781,7 @@ struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_sdma; mqd->mqd_size = sizeof(struct v12_sdma_mqd); mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_sdma_v12_1; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd_sdma; #endif From 636139603b99d2e3a18a46cf3f8d39313ce8042e Mon Sep 17 00:00:00 2001 From: Mike Lothian Date: Sat, 12 Sep 2026 00:29:08 +0100 Subject: [PATCH 1215/1317] drm/amdgpu: hold a runtime PM reference for P2P dma-buf attachments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_dma_buf_map() adds VRAM to the allowed domains for a peer2peer attachment. GTT is only a fallback placement when VRAM is preferred, so ttm_bo_validate() migrates the buffer from GTT into VRAM. While the exporting device is runtime suspended its SDMA rings are down and the move fails: amdgpu: Move buffer fallback to memcpy unavailable An importer on a second GPU reaches this holding no runtime PM reference on the exporter, e.g. a compositor on the APU submitting a frame that references a buffer exported by an idle dGPU: amdgpu_cs_ioctl -> amdgpu_cs_parser_bos -> amdgpu_cs_bo_validate -> ttm_bo_validate -> amdgpu_bo_move -> dma_buf_map_attachment -> amdgpu_dma_buf_map -> ttm_bo_validate -> amdgpu_bo_move Pinning a dma-buf into VRAM has the same requirement, which commit 030631e97b20 ("drm/amdgpu: revert "take runtime pm reference when we attach a buffer" v2") called out as the one case that would need the reference back. Take it in attach and drop it in detach. pm_runtime_get_if_active() never resumes the device, so it cannot deadlock against the reservation taken during resume, which is why the old pm_runtime_get_sync() had to go. If the device is not active, clear peer2peer instead: the buffer then stays in GTT, which remains accessible while the GPU is powered down. If runtime PM is disabled, take a plain reference so the put in detach stays balanced. Fixes: 030631e97b20 ("drm/amdgpu: revert "take runtime pm reference when we attach a buffer" v2") Suggested-by: Christian König Reviewed-by: Christian König Signed-off-by: Mike Lothian Assisted-by: Claude:Opus-5 [Claude Code] Signed-off-by: Alex Deucher (cherry picked from commit 062ff15e30a48d14fb7d7558eba84f8dc97197f0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c | 43 ++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c index b33c300e26e2..9adf3eed8822 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c @@ -43,6 +43,7 @@ #include #include #include +#include static const struct dma_buf_attach_ops amdgpu_dma_buf_attach_ops; @@ -100,15 +101,54 @@ static int amdgpu_dma_buf_attach(struct dma_buf *dmabuf, pci_p2pdma_distance(adev->pdev, attach->dev, false) < 0) attach->peer2peer = false; + /* + * Only allow P2P while the exporter is active, and keep it active + * until detach. With runtime PM disabled take a plain reference so + * the put in detach stays balanced. + */ + if (attach->peer2peer) { + struct device *dev = adev_to_drm(adev)->dev; + int ret = pm_runtime_get_if_active(dev); + + if (!ret) + attach->peer2peer = false; + else if (ret < 0) + pm_runtime_get_noresume(dev); + } + r = dma_resv_lock(bo->tbo.base.resv, NULL); if (r) - return r; + goto err_pm_put; amdgpu_vm_bo_update_shared(bo); dma_resv_unlock(bo->tbo.base.resv); return 0; + +err_pm_put: + if (attach->peer2peer) + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); + return r; +} + +/** + * amdgpu_dma_buf_detach - &dma_buf_ops.detach implementation + * + * @dmabuf: DMA-buf where we remove the attachment from + * @attach: the attachment to remove + * + * Drop the runtime PM reference taken in amdgpu_dma_buf_attach(). + */ +static void amdgpu_dma_buf_detach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attach) +{ + struct drm_gem_object *obj = dmabuf->priv; + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); + + if (attach->peer2peer) + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); } /** @@ -350,6 +390,7 @@ static void amdgpu_dma_buf_vunmap(struct dma_buf *dma_buf, struct iosys_map *map const struct dma_buf_ops amdgpu_dmabuf_ops = { .attach = amdgpu_dma_buf_attach, + .detach = amdgpu_dma_buf_detach, .pin = amdgpu_dma_buf_pin, .unpin = amdgpu_dma_buf_unpin, .map_dma_buf = amdgpu_dma_buf_map, From 723d4dc628d764b19cf9efca14b82cca5ff020c9 Mon Sep 17 00:00:00 2001 From: Dmitriy Chumachenko Date: Mon, 14 Sep 2026 17:33:03 +0300 Subject: [PATCH 1216/1317] drm/amdgpu: check ras and obj before dereference nbio_v7_9_handle_ras_controller_intr_no_bifring() dereferences ras and obj without checking either for NULL. Both amdgpu_ras_get_context() and amdgpu_ras_find_obj() can return NULL, e.g. during the window between adev->nbio.ras being set (early in amdgpu_ras_init(), by design, to enable the fatal-error interrupt as soon as possible) and the PCIE_BIF ras object actually being created in RAS late_init. Any interrupt in that window crashes in hard-IRQ context. This is analogous to commit d190b459b2a4 ("drm/amdgpu: the warning dereferencing obj for nbio_v7_4"), which fixed the same issue in the nbio_v7_4 handler. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7692e1ee2446 ("drm/amdgpu: add RAS fatal error handler for NBIO v7.9") Reviewed-by: Tao Zhou Signed-off-by: Dmitriy Chumachenko Signed-off-by: Alex Deucher (cherry picked from commit c7071767a50a32ed727cf800ac84372429e3b4b3) --- drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c b/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c index bdfd2917e3ca..def02993b7cf 100644 --- a/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c +++ b/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c @@ -535,7 +535,7 @@ static void nbio_v7_9_handle_ras_controller_intr_no_bifring(struct amdgpu_device RAS_CNTLR_INTERRUPT_CLEAR, 1); WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_DOORBELL_INT_CNTL, bif_doorbell_intr_cntl); - if (!ras->disable_ras_err_cnt_harvest) { + if (ras && !ras->disable_ras_err_cnt_harvest && obj) { /* * clear error status after ras_controller_intr * according to hw team and count ue number From 04de4007d32385b8b6a5dd72bff3146dfdc592c3 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 10:59:45 -0500 Subject: [PATCH 1217/1317] drm/amdgpu: Fix GPU PCIe link capability reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit eb53125a7ad9 ("drm/amd: Add dedicated helper for amdgpu_device_find_parent()") made amdgpu_device_gpu_bandwidth() query the first device outside the dGPU. That is the host side of the physical link, not the GPU side. As a result, the ASIC and platform capability masks can both be based on the host port. drm_amdgpu_info_device then exposes the host capabilities to userspace, such as Gen5 x16 for a Gen4 x8 GPU. Cache both ends of the physical link during device initialization. Use link_dev for the GPU capability and link_partner for the platform capability and _PR3 detection. Reported-by: "Marek Olšák" Closes: https://lore.kernel.org/amd-gfx/CAAxE2A4VhsAzzO1QjBjUg+NgnbD04ZzMyN6xsUJxjKJHH6hxiw@mail.gmail.com/ Suggested-by: Lijo Lazar Fixes: eb53125a7ad9 ("drm/amd: Add dedicated helper for amdgpu_device_find_parent()") Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 7ea6a47224e2c6e89a3a682d7fbaace4817a55aa) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 3 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 48 ++++++++-------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 7974f9b7944f..a9c6f5d4a639 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -621,6 +621,9 @@ enum amdgpu_enforce_isolation_mode { struct amdgpu_device { struct device *dev; struct pci_dev *pdev; + /* The two ends of the physical PCIe link outside the device. */ + struct pci_dev *link_dev; + struct pci_dev *link_partner; struct drm_device ddev; #ifdef CONFIG_DRM_AMD_ACP diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 104d1d2cbad9..933804349dbf 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1954,18 +1954,17 @@ static void amdgpu_uid_fini(struct amdgpu_device *adev) adev->uid_info = NULL; } -static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) +static void amdgpu_device_init_pcie_links(struct amdgpu_device *adev) { - struct pci_dev *parent = adev->pdev; + adev->link_dev = adev->pdev; + adev->link_partner = pci_upstream_bridge(adev->link_dev); - /* skip upstream/downstream switches internal to dGPU */ - while ((parent = pci_upstream_bridge(parent))) { - if (parent->vendor == PCI_VENDOR_ID_ATI) - continue; - break; + /* Skip upstream/downstream switches internal to the dGPU. */ + while (adev->link_partner && + adev->link_partner->vendor == PCI_VENDOR_ID_ATI) { + adev->link_dev = adev->link_partner; + adev->link_partner = pci_upstream_bridge(adev->link_dev); } - - return parent; } /** @@ -1981,7 +1980,6 @@ static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) { struct amdgpu_ip_block *ip_block; - struct pci_dev *parent; bool total, skip_bios, early_full_gpu_access = false; uint32_t bios_flags; int i, r; @@ -2077,10 +2075,9 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) !dev_is_removable(&adev->pdev->dev)) adev->flags |= AMD_IS_PX; - if (!(adev->flags & AMD_IS_APU)) { - parent = amdgpu_device_find_parent(adev); - adev->has_pr3 = parent ? pci_pr3_present(parent) : false; - } + if (!(adev->flags & AMD_IS_APU)) + adev->has_pr3 = adev->link_partner && + pci_pr3_present(adev->link_partner); adev->pm.pp_feature = amdgpu_pp_feature_mask; if (amdgpu_sriov_vf(adev) || sched_policy == KFD_SCHED_POLICY_NO_HWS) @@ -3776,6 +3773,7 @@ int amdgpu_device_init(struct amdgpu_device *adev, adev->shutdown = false; adev->flags = flags; + amdgpu_device_init_pcie_links(adev); if (amdgpu_force_asic_type >= 0 && amdgpu_force_asic_type < CHIP_LAST) adev->asic_type = amdgpu_force_asic_type; @@ -5872,11 +5870,9 @@ static void amdgpu_device_partner_bandwidth(struct amdgpu_device *adev, *width = PCIE_LNK_WIDTH_UNKNOWN; if (amdgpu_device_pcie_dynamic_switching_supported(adev)) { - struct pci_dev *parent = amdgpu_device_find_parent(adev); - - if (parent) { - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); + if (adev->link_partner) { + *speed = pcie_get_speed_cap(adev->link_partner); + *width = pcie_get_width_cap(adev->link_partner); } } else { /* use the current speeds rather than max if switching is not supported */ @@ -5898,21 +5894,11 @@ static void amdgpu_device_gpu_bandwidth(struct amdgpu_device *adev, enum pci_bus_speed *speed, enum pcie_link_width *width) { - struct pci_dev *parent = adev->pdev; - if (!speed || !width) return; - /* use the device itself */ - *speed = pcie_get_speed_cap(adev->pdev); - *width = pcie_get_width_cap(adev->pdev); - - /* use the link outside the device */ - parent = amdgpu_device_find_parent(adev); - if (parent) { - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); - } + *speed = pcie_get_speed_cap(adev->link_dev); + *width = pcie_get_width_cap(adev->link_dev); } /** From c883d0a132d430ef7ebb23fd94323be94d0fbdb8 Mon Sep 17 00:00:00 2001 From: David Francis Date: Wed, 5 Aug 2026 09:51:35 -0400 Subject: [PATCH 1218/1317] drm/amdkfd: Avoid integer underflow with ffs in EOP ring size calc The low 6 bits of cp_hqd_eop_control store the base-2 logarithm of the EOP ring size. This was calculated as ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1 But ffs can in theory return 1 or 0, so this could underflow (although in practice the ring buffer size cannot be less than 4096). Change this to ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4) using properties of logarithms. Reviewed-by: Kent Russell Signed-off-by: David Francis Signed-off-by: Alex Deucher (cherry picked from commit 4f18c56630383c14bfc6b2d65f88f2f895d2121a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c index e034da638c07..4f8a8a1a6186 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c @@ -204,7 +204,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c index 350fcbbba4b2..bf015dc5b868 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c @@ -242,7 +242,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c index 63f25a60baa5..6ea09b031caf 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -217,7 +217,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index 708bbb08c089..c709db0210ce 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -295,7 +295,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = From 8ee521b8b189799e361d4233c5180ba56656d4d4 Mon Sep 17 00:00:00 2001 From: David Francis Date: Wed, 5 Aug 2026 09:16:51 -0400 Subject: [PATCH 1219/1317] drm/amdkfd: Avoid integer underflow in EOP ring size calculation. The low 6 bits of cp_hqd_eop_control store the base-2 logarithm of the EOP ring size. This was calculated as order_base_2(q->eop_ring_buffer_size / 4) - 1 But order_base_2 can in theory return 0, so this could underflow (although in practice the ring buffer size cannot be less than 4096). Change this to order_base_2(q->eop_ring_buffer_size / 8) using properties of logarithms. Also add to the above comment to make the mathematics more clear. Reviewed-by: Kent Russell Signed-off-by: David Francis Signed-off-by: Alex Deucher (cherry picked from commit f0f43fcf8b2b3a924cad9444340921c96ed5f634) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 6 +++++- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c index b95720198e28..6e6bc1ec0b64 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -285,6 +285,10 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, 1 << CP_HQD_IB_CONTROL__IB_EXE_DISABLE__SHIFT; /* + * The lowest 6 bits of eop_control store the EOP ring size. If + * their value is X, the ring size is 2^(X + 1) dwords, or + * 2^(X + 3) bytes. + * * HW does not clamp this field correctly. Maximum EOP queue size * is constrained by per-SE EOP done signal count, which is 8-bit. * Limit is 0xFF EOP entries (= 0x7F8 dwords). CP will not submit @@ -296,7 +300,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? - min(0xA, order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; + min(0xA, order_base_2(q->eop_ring_buffer_size / 8)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c index 60b87a500698..029572548c14 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c @@ -208,6 +208,9 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, mtype << CP_HQD_IB_CONTROL__MTYPE__SHIFT; /* + * The lowest 6 bits of eop_control store the EOP ring size. If + * their value is X, the ring size is 2^(X + 1) dwords, or + * 2^(X + 3) bytes. * HW does not clamp this field correctly. Maximum EOP queue size * is constrained by per-SE EOP done signal count, which is 8-bit. * Limit is 0xFF EOP entries (= 0x7F8 dwords). CP will not submit @@ -215,7 +218,7 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control |= q->eop_ring_buffer_size ? min(0xA, - order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; + order_base_2(q->eop_ring_buffer_size / 8)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = From 0d2f4cfa564355fcbbc71498fd8ec09243036109 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Tue, 15 Sep 2026 14:07:39 +0530 Subject: [PATCH 1220/1317] drm/amd/display: Fix NULL dereference in dcn50/dcn60 init_hw dc->clk_mgr is checked for NULL earlier in dcn50_init_hw() and dcn60_init_hw(), but dcn50_initialize_min_clocks() and dcn401_initialize_min_clocks() are called without any guard, causing Smatch to report potential NULL dereferences. Guard both call sites with the same pattern used throughout both functions: if (dc->clk_mgr && dc->clk_mgr->funcs) Also fix dcn50_initialize_min_clocks() which calls get_dispclk_from_dentist without checking the function pointer, unlike the dcn401 equivalent which guards that call. Fix kernel-doc in dcn60_hwseq.c by adding missing parameter descriptions for @probe in dcn60_update_probe_status() and @type in is_probe_measurement_type_for_hubbub(). Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reported-by: Dan Carpenter Cc: Aurabindo Pillai Cc: Ivan Lipski Cc: Dan Wheeler Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Signed-off-by: Srinivasan Shanmugam Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 325c9a827cdd748e126eafeadaffc556204773d2) --- drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c | 6 ++++-- drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c index a7f8fd03faea..e549556b9679 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c @@ -67,7 +67,8 @@ static void dcn50_initialize_min_clocks(struct dc *dc) * audio corruption. Read current DISPCLK from DENTIST and request the same * freq to ensure that the timing is valid and unchanged. */ - clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr); + if (dc->clk_mgr->funcs->get_dispclk_from_dentist) + clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr); } clocks->ref_dtbclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].dtbclk_mhz * 1000; clocks->fclk_p_state_change_support = true; @@ -639,7 +640,8 @@ void dcn50_init_hw(struct dc *dc) dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, !dc->res_pool->hubbub->ctx->dc->debug.disable_stutter); - dcn50_initialize_min_clocks(dc); + if (dc->clk_mgr && dc->clk_mgr->funcs) + dcn50_initialize_min_clocks(dc); /* On HW init, allow idle optimizations after pipes have been turned off. * diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c index 72c2d3ca52f6..61ad6efa7a74 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c @@ -643,7 +643,8 @@ void dcn60_init_hw(struct dc *dc) dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, !dc->res_pool->hubbub->ctx->dc->debug.disable_stutter); - dcn401_initialize_min_clocks(dc); + if (dc->clk_mgr && dc->clk_mgr->funcs) + dcn401_initialize_min_clocks(dc); /* On HW init, allow idle optimizations after pipes have been turned off. * @@ -1001,6 +1002,7 @@ static void dcn60_build_hubbub_perfmon_sequence( /** * dcn60_update_probe_status - Set the valid flag on a latched probe result. * @status: result sink whose u was written by the GET BLS step during execute + * @probe: current probe state used to determine measurement type and validity */ static void dcn60_update_probe_status(struct dc_probe_status *status) { @@ -1024,6 +1026,7 @@ static void dcn60_update_probe_status(struct dc_probe_status *status) /** * is_probe_measurement_type_for_hubbub - Returns true if the probe type is * served by the hubbub perfmon block on DCN60. + * @type: the probe measurement type to classify */ static bool is_probe_measurement_type_for_hubbub(enum dc_probe_type type) { From 7f9caa70aef0950e06d395ca0035831214d88187 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 12:51:24 -0500 Subject: [PATCH 1221/1317] drm/amdgpu: Skip KFD mapping clear before initialization amdgpu_amdkfd_clear_kfd_mapping() assumes that a non-NULL kfd_dev has a fully populated node array. This is not true when KFD device initialization fails after probe. For example, kgd2kfd_device_init() sets num_nodes before checking PCIe atomics support. On Polaris systems without the required atomics, it returns before allocating nodes[0], but the kfd_dev remains attached to the amdgpu device. A later GPU reset then dereferences nodes[0]->id. Require the authoritative KFD initialization flag before walking the node array, matching the existing KFD reset and teardown paths. Fixes: 70cadefcc616 ("drm/amdgpu: unmap all user mappings of framebuffer and doorbell before mode1 reset") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5833 Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 4ac1835823c47903fbb278bbf474773c46f59edc) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 816d8817f0b2..054870e9078d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -330,7 +330,7 @@ void amdgpu_amdkfd_clear_kfd_mapping(struct amdgpu_device *adev) struct kfd_dev *kfd = adev->kfd.dev; unsigned int i; - if (!kfd) + if (!kfd || !kfd->init_complete) return; for (i = 0; i < kfd->num_nodes; i++) { From 5155002b03b24ba3ef91c5c313b8cf0171b24904 Mon Sep 17 00:00:00 2001 From: Chengjun Yao Date: Tue, 8 Sep 2026 10:15:43 +0800 Subject: [PATCH 1222/1317] drm/amdgpu: fix rmmio iounmap skipped on device removal amdgpu_pci_remove() calls drm_dev_unplug() before fini_sw(), so drm_dev_enter() is already false there and the iounmap() guarded by it is skipped. This .remove path runs on both hot-unplug and plain rmmod, so the register BAR ioremap mapping leaks one instance per unload. Unmap rmmio unconditionally (guard only on non-NULL) and drop the now unused idx. Fixes: 62d5f9f7110a ("drm/amdgpu: Unmap MMIO mappings when device is not unplugged") Signed-off-by: Chengjun Yao Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher (cherry picked from commit dd6f86a97260e5207d3329ad03aa89fdad61b1e6) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 933804349dbf..9269e780feb7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4335,7 +4335,7 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) void amdgpu_device_fini_sw(struct amdgpu_device *adev) { - int i, idx; + int i; bool px; amdgpu_device_ip_fini(adev); @@ -4377,11 +4377,9 @@ void amdgpu_device_fini_sw(struct amdgpu_device *adev) if ((adev->pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA) vga_client_unregister(adev->pdev); - if (drm_dev_enter(adev_to_drm(adev), &idx)) { - + if (adev->rmmio) { iounmap(adev->rmmio); adev->rmmio = NULL; - drm_dev_exit(idx); } if (IS_ENABLED(CONFIG_PERF_EVENTS)) From 2ac2fe765ef475f409616ac0b57c4a3922749b0f Mon Sep 17 00:00:00 2001 From: Francis Marlou Pacaro Date: Fri, 4 Sep 2026 08:20:21 +0800 Subject: [PATCH 1223/1317] drm/amd/display: fix MALL hysteresis timer underflow at high refresh rates dcn30_apply_idle_power_optimizations() derives the MALL frame cache hysteresis timer with tmr_delay = (uint32_t)(div_u64(..., denom) - 64LL); div_u64() returns a u64, so when the quotient is smaller than 64 the subtraction wraps instead of going negative and tmr_delay ends up huge. The loop that follows tries to squeeze it into the 6 bit register field by doubling denom, but that only makes the quotient smaller, so tmr_delay can never converge. tmr_scale is bumped past 3 and the function gives up with /* Delay exceeds range of hysteresis timer */ ASSERT(false); even though the requested delay is too *short* to encode, not too long. With mall_additional_timer_percent left at its default of 0, the quotient drops below 64 once the refresh rate used for the calculation goes above ~243 Hz. Every DCN 3.0 display above that loses MALL static screen entirely and splats a WARN once per boot. Reproduced on Navi 23 (RX 6600) driving 1920x1080, resetting /sys/kernel/debug/clear_warn_once between modes: refresh MALL ASSERT 144 Hz enabled no 240 Hz enabled no 280 Hz skipped yes 360 Hz skipped yes Commit 3bb68cec4db8 ("drm/amd/display: Add Overflow check to skip MALL") already covered the other end of the range, where a large stutter period makes the delay too long to encode. Cover the short end by clamping to 0, which selects the shortest hysteresis the register can express, 65.28us * 64 = ~4.18ms. That is marginally longer than what the formula asks for at these refresh rates, and erring long is the safe direction: it only delays MALL entry, it can never enter early. The numerator does not change between iterations, only denom does, so compute it once and keep both call sites inside 100 columns. The genuinely out of range case at very low refresh rates still reaches the ASSERT, which is where it belongs. Fixes: 52f2e83e2fe5 ("drm/amdgpu/display: add MALL support (v2)") Signed-off-by: Francis Marlou Pacaro Reviewed-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit 387550e53e1405f1f960b62b22f8783db17c8e1d) --- .../gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index cb163902e12e..d669b47af120 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -1064,10 +1064,12 @@ bool dcn30_apply_idle_power_optimizations(struct dc *dc, bool enable) */ unsigned int denom = refresh_hz * 6528; unsigned int stutter_period = dc->current_state->perf_params.stutter_period_us; + uint64_t num = (1000000LL + 2 * stutter_period * refresh_hz) * + (100LL + dc->debug.mall_additional_timer_percent); + uint64_t tmr_ticks; - tmr_delay = (uint32_t)(div_u64(((1000000LL + 2 * stutter_period * refresh_hz) * - (100LL + dc->debug.mall_additional_timer_percent) + denom - 1), - denom) - 64LL); + tmr_ticks = div_u64(num + denom - 1, denom); + tmr_delay = tmr_ticks > 64 ? (uint32_t)(tmr_ticks - 64) : 0; /* In some cases the stutter period is really big (tiny modes) in these * cases MALL cant be enabled, So skip these cases to avoid a ASSERT() @@ -1089,9 +1091,8 @@ bool dcn30_apply_idle_power_optimizations(struct dc *dc, bool enable) } denom *= 2; - tmr_delay = (uint32_t)(div_u64(((1000000LL + 2 * stutter_period * refresh_hz) * - (100LL + dc->debug.mall_additional_timer_percent) + denom - 1), - denom) - 64LL); + tmr_ticks = div_u64(num + denom - 1, denom); + tmr_delay = tmr_ticks > 64 ? (uint32_t)(tmr_ticks - 64) : 0; } /* Copy HW cursor */ From 3565893cc72cdf6b795cf6a33e7ff9605322334d Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Wed, 16 Sep 2026 05:16:38 +0000 Subject: [PATCH 1224/1317] btrfs: clear free space tree creation state on rebuild failure btrfs_rebuild_free_space_tree() sets BTRFS_FS_CREATING_FREE_SPACE_TREE before rebuilding the free space tree. Several error paths return without clearing this flag. The transaction restart failure path can leave the flag set on a live filesystem, causing delayed reference processing to be skipped. Clear it on all free space tree rebuild failure paths. Keep BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED set, since a failed rebuild leaves the free space tree untrusted. Callers must fall back to extent-tree caching. Fixes: 882af9f13e83 ("btrfs: handle free space tree rebuild in multiple transactions") CC: stable@vger.kernel.org # 6.14+ Assisted-by: LLM Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Guanghui Yang <3497809730@qq.com> Signed-off-by: David Sterba --- fs/btrfs/free-space-tree.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/free-space-tree.c b/fs/btrfs/free-space-tree.c index 1b3d82ae3de8..b7a4a6ade30f 100644 --- a/fs/btrfs/free-space-tree.c +++ b/fs/btrfs/free-space-tree.c @@ -1353,7 +1353,7 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); btrfs_end_transaction(trans); - return ret; + goto out_clear; } node = rb_first_cached(&fs_info->block_group_cache_tree); @@ -1371,14 +1371,16 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); btrfs_end_transaction(trans); - return ret; + goto out_clear; } next: if (btrfs_should_end_transaction(trans)) { btrfs_end_transaction(trans); trans = btrfs_start_transaction(free_space_root, 1); - if (IS_ERR(trans)) - return PTR_ERR(trans); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + goto out_clear; + } } node = rb_next(node); } @@ -1390,6 +1392,10 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) ret = btrfs_commit_transaction(trans); clear_bit(BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED, &fs_info->flags); return ret; + +out_clear: + clear_bit(BTRFS_FS_CREATING_FREE_SPACE_TREE, &fs_info->flags); + return ret; } static int __add_block_group_free_space(struct btrfs_trans_handle *trans, From 97fcd34aa9fd73cefe3120ac9a82ca9d7763922f Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 15:43:41 +0100 Subject: [PATCH 1225/1317] btrfs: abort transaction on failure to update inode for hole punching and reflinking If we fail to update the inode we error out without aborting the transaction, which can result in a persistent inconsistency if after the failure the transaction is committed, as we have dropped file extent items from a range and either punched a hole or insert a new file extent item for that range (for reflinks). So add the missing transaction abort. Fixes: 2aaa66558172 ("Btrfs: add hole punching") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/file.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index 20e15dc30bfb..f978c6524aa0 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -2509,8 +2509,10 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode, inode_set_ctime_current(&inode->vfs_inode)); ret = btrfs_update_inode(trans, inode); - if (ret) + if (unlikely(ret)) { + btrfs_abort_transaction(trans, ret); break; + } btrfs_end_transaction(trans); btrfs_btree_balance_dirty(fs_info); From aeab4c62875748ecfd390a47ac1d91ea7c9a6abb Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 16:49:37 +0100 Subject: [PATCH 1226/1317] btrfs: check if there is space for chunk item when validating sys chunk array We checked if have enough remaining space for a key before dereferencing a key, but we then dereference a chunk item, to get the number of stripes, without checking if there is space for the item. So add a check to see if there is enough space for a chunk item before dereferencing the item to extract the stripe count. Fixes: 2a9bb78cfd36 ("btrfs: validate system chunk array at btrfs_validate_super()") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 819727460bcf..dc7ad92876c0 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -2357,6 +2357,10 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, key.type, cur); return -EUCLEAN; } + + if (unlikely(cur + sizeof(*chunk) > sys_array_size)) + goto short_read; + chunk = (struct btrfs_chunk *)(sb->sys_chunk_array + cur); num_stripes = btrfs_stack_chunk_num_stripes(chunk); if (unlikely(cur + btrfs_chunk_item_size(num_stripes) > sys_array_size)) From b797b52e88a66598a972111b02ef13edd8d03ed2 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sat, 12 Sep 2026 18:12:06 +0930 Subject: [PATCH 1227/1317] btrfs: add "/dev/root" exception for device path update [BEHAVIOR CHANGE] Since commit 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan"), users with btrfs rootfs but without an initramfs are complaining that grub2 can no longer detect the rootfs device: /usr/sbin/grub-probe: error: cannot find a device for / (is /dev mounted?). [CAUSE] Although using btrfs without an initramfs is not recommended (if a new device is added to the rootfs, the system can no longer boot, as there is no way to register all devices), there is still a minority of users doing this. If there is no initramfs but the rootfs is on a block-device-based filesystem, the kernel boot sequence initializes a minimal ramfs/tmpfs, creates "/dev/root" with the proper device number for the rootfs, and then invokes mount using "/dev/root". That's why the end user will get the mount output: /dev/root on / rw To be honest, this is a user space problem: no one should trust the device path shown in mount, only the device number. E.g. one can even use "/proc/self/fd/*" to mount an fs, and that proc path will be registered, and no one else can mount that fs using that path. Before commit 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan"), btrfs had an internal path lookup workaround to address such weird paths, it works by checking if the existing device path can still resolve to the device number. But that path resolution is deadlock prone, thus it's replaced by a simple devt check. This works fine in most cases, as a btrfs device is registered by udev at boot time, thus all paths are sane. However this will not work for systems without an initramfs, causing the unreachable "/dev/root" path to exist forever without a way to rename it. [WORKAROUND] Add an exception to the device path rename requirement. If the device has the name "/dev/root", we know it's booted without an initramfs, and only for that case we allow device path update. And if someone intentionally created "/dev/root" after boot, the existing devt checks will reject that weird name as usual. This should satisfy the minority of users, and still keep most of the existing guards preventing unexpected/unnecessary device path updates. Fixes: 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan") Link: https://lore.kernel.org/linux-btrfs/CAKLYgeL7nrA4nXcewdv9Fqg_s=3GS=vmoypnEiZBKQ7rySZFuQ@mail.gmail.com/ Link: https://lore.kernel.org/linux-btrfs/dfbe1e27-dab8-4d55-8cf3-0b28eeac5df4@gmail.com/ Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 427aa8e24fc3..7bbc8e745e6b 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -740,6 +740,36 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; } +static bool should_rename_device(const struct btrfs_device *dev) +{ + bool ret; + const char *old_name; + + rcu_read_lock(); + old_name = rcu_dereference(dev->name); + /* + * For systems booted without an initramfs, the rootfs has the device + * name "/dev/root". + * + * Although using btrfs without an initramfs is not recommended (if a + * new device is added to the rootfs, the system can no longer boot, as + * there is no way to register all devices), there is still a minority + * of users doing this. + * + * And after the system is up, a later device scan on the real block + * device file will never get this device's name updated, as the + * device->devt is still the same. + * + * Here we add one and only one exception for "/dev/root", to allow the + * device name to be updated even if the new path points to the same + * block device. + */ + ret = (strcmp(old_name, "/dev/root") == 0); + rcu_read_unlock(); + + return ret; +} + /* * Add new device to list of registered devices * @@ -860,7 +890,8 @@ static noinline struct btrfs_device *device_list_add(const char *path, MAJOR(path_devt), MINOR(path_devt), current->comm, task_pid_nr(current)); - } else if (!device->name || device->devt != path_devt) { + } else if (!device->name || device->devt != path_devt || + should_rename_device(device)) { const char *old_name; /* From 72de4807ba84da485dda1a91572d66da9149e95a Mon Sep 17 00:00:00 2001 From: Anand Jain Date: Sun, 13 Sep 2026 02:06:28 +0800 Subject: [PATCH 1228/1317] btrfs: derive f_fsid with dev_t only when temp_fsid is active Commit c2a74ed0494c ("btrfs: derive f_fsid from on-disk fsid and dev_t") mixed dev_t into f_fsid for all single-device setups to avoid f_fsid collisions with cloned filesystems. However, doing this unconditionally breaks backward compatibility. statfs(2) f_fsid changes after a kernel upgrade, and also can shift across reboots or dev re-attaches as dev_t values change. Fix this by only mixing dev_t when temp_fsid is active. This means for non-temp_fsid setups or the original mount, we use the old method of deriving fsid based on the UUID. So in the case of a cloned Btrfs filesystem, we won't be able to maintain the same fsid across mount recycle if the mount order changes. Reported-by: Dave Hansen Link: https://lore.kernel.org/linux-btrfs/be0c08f5-2f31-40f5-8a3b-f2f58b3e00ff@intel.com Fixes: c2a74ed0494c ("btrfs: derive f_fsid from on-disk fsid and dev_t") CC: stable@vger.kernel.org # 7.2 Signed-off-by: Anand Jain Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/super.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index 464129b1b0d4..ddb620ac241b 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -1836,8 +1836,12 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf) f_fsid.val[0] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root) >> 32; f_fsid.val[1] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root); - /* Hash dev_t to avoid f_fsid collision with cloned filesystems. */ - if (fs_info->fs_devices->total_devices == 1) { + /* + * Hash dev_t to avoid f_fsid collisions with cloned filesystems. + * Only do this when a clone is present so the original filesystem + * (mounted first) maintains backward-compatible f_fsid behavior. + */ + if (fs_info->fs_devices->temp_fsid) { __kernel_fsid_t dev_fsid = u64_to_fsid(huge_encode_dev(fs_info->fs_devices->latest_dev->bdev->bd_dev)); From 40c2096961b4e8f48d1fc17406a90c5a36ac0a8d Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:19:20 +0200 Subject: [PATCH 1229/1317] bpf: Verify global subprogs in each sleepability context Global subprograms are verified independently with a fresh verifier root. do_check_common() currently seeds that root's in_sleepable state from the program, even though a global subprogram can also run from callbacks whose execution context differs from the program's main entry point. In particular, workqueue and task-work callbacks are sleepable even when the containing program is not. A global subprogram of that program is therefore verified as non-sleepable, making in_rcu_cs() true and allowing loads of RCU-protected kptrs to produce trusted MEM_RCU pointers. The same subprogram can then be called from a sleepable callback without a classic RCU reader. It can retain such a pointer while the object is freed and use it after free. The verifier's execution-context predicates are complementary. A state is sleepable only when in_sleepable is set and no RCU, preemption, IRQ, or lock region is active. Each condition which prevents sleeping also provides RCU protection, while in_rcu_cs() treats a non-sleepable state as implicitly protected. Use this relationship to represent a global subprogram caller with only the result of in_sleepable_context(). A protected sleepable caller is normalized to in_sleepable=false at the independent verification root. This both prevents sleepable operations and makes in_rcu_cs() true without copying caller-owned lock state. Track only the contexts in which each global subprogram is actually reached. Verify it once if all reachable calls use the same context, and twice only if both sleepable and non-sleepable calls reach it. Calls found while verifying globals or asynchronous callbacks mark further contexts for checking. Repeat the existing subprogram walk until all called contexts have been verified; unreachable global calls remain unchecked. Accumulate instruction counts over those verification passes. Preserve the total recorded before each pass, since path accounting has already added this pass's synchronous instructions and its root total must also include asynchronous subprograms. This makes an unprotected callback verify the global subprogram as sleepable, turning its RCU-protected kptr load into an untrusted pointer. Protected callers and global subprograms which do not depend on implicit RCU protection remain valid. Fixes: 81f1d7a583fa ("bpf: wq: add bpf_wq_set_callback_impl") Fixes: 38aa7003e369 ("bpf: task work scheduling kfuncs") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260914131923.2544250-2-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf.h | 5 +-- kernel/bpf/verifier.c | 71 ++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index e57af902560c..1d2676782d70 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1651,8 +1651,9 @@ static inline void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags struct bpf_func_info_aux { u16 linkage; bool unreliable; - bool called : 1; - bool verified : 1; + /* Indexed by in_sleepable. */ + bool called[2]; + bool verified[2]; }; enum bpf_jit_poke_reason { diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index ddba53eaa333..5d7080c260d8 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9931,6 +9931,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (err == -EFAULT) return err; if (bpf_subprog_is_global(env, subprog)) { + struct bpf_func_info_aux *sub_aux = subprog_aux(env, subprog); const char *sub_name = bpf_subprog_name(env, subprog); const char *operation; bool returns_void; @@ -9962,11 +9963,10 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (env->log.level & BPF_LOG_LEVEL) verbose(env, "Func#%d ('%s') is global and assumed valid.\n", subprog, sub_name); + sub_aux->called[in_sleepable_context(env)] = true; returns_void = subprog_returns_void(env, subprog); if (env->subprog_info[subprog].changes_pkt_data) clear_all_pkt_pointers(env); - /* mark global subprog for verifying after main prog */ - subprog_aux(env, subprog)->called = true; if (returns_void) bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED); else @@ -10784,11 +10784,7 @@ int bpf_get_helper_proto(struct bpf_verifier_env *env, int func_id, /* Check if we're in a sleepable context. */ static inline bool in_sleepable_context(struct bpf_verifier_env *env) { - return !env->cur_state->active_rcu_locks && - !env->cur_state->active_preempt_locks && - !env->cur_state->active_locks && - !env->cur_state->active_irq_id && - in_sleepable(env); + return !in_rcu_cs(env); } static const char *non_sleepable_context_description(struct bpf_verifier_env *env) @@ -19447,13 +19443,14 @@ static void free_states(struct bpf_verifier_env *env) } } -static int do_check_common(struct bpf_verifier_env *env, int subprog) +static int do_check_common(struct bpf_verifier_env *env, int subprog, bool is_sleepable) { bool pop_log = !(env->log.level & BPF_LOG_LEVEL2); struct bpf_subprog_info *sub = subprog_info(env, subprog); struct bpf_prog_aux *aux = env->prog->aux; struct bpf_verifier_state *state; struct bpf_reg_state *regs; + u32 old_insns_total = sub->insns_total; u32 insn_processed = env->insn_processed; int ret, i; @@ -19466,7 +19463,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog) state->curframe = 0; state->speculative = false; state->branches = 1; - state->in_sleepable = env->prog->sleepable; + state->in_sleepable = is_sleepable; state->frame[0] = kzalloc_obj(struct bpf_func_state, GFP_KERNEL_ACCOUNT); if (!state->frame[0]) { kfree(state); @@ -19607,8 +19604,10 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog) * not accounted as callees by account_current_path(). * Accumulate their total counts as total counts of the main or * global subprog hosting the async call. + * Start from the saved total of earlier contexts: adding to the current + * total would count this pass's synchronous paths twice. */ - env->subprog_info[subprog].insns_total = env->insn_processed - insn_processed; + sub->insns_total = old_insns_total + (env->insn_processed - insn_processed); return ret; } @@ -19636,14 +19635,19 @@ static int do_check_subprogs(struct bpf_verifier_env *env) { struct bpf_prog_aux *aux = env->prog->aux; struct bpf_func_info_aux *sub_aux; - int i, ret, new_cnt; + int context, i, ret, new_cnt; if (!aux->func_info) return 0; - /* exception callback is presumed to be always called */ - if (env->exception_callback_subprog) - subprog_aux(env, env->exception_callback_subprog)->called = true; + /* + * Callbacks cannot throw, so the exception callback always runs in the + * main program's context. It is presumed to be always called. + */ + if (env->exception_callback_subprog) { + sub_aux = subprog_aux(env, env->exception_callback_subprog); + sub_aux->called[env->prog->sleepable] = true; + } again: new_cnt = 0; @@ -19652,29 +19656,28 @@ static int do_check_subprogs(struct bpf_verifier_env *env) continue; sub_aux = subprog_aux(env, i); - if (!sub_aux->called || sub_aux->verified) - continue; + for (context = 0; context < ARRAY_SIZE(sub_aux->called); context++) { + if (!sub_aux->called[context] || sub_aux->verified[context]) + continue; - env->insn_idx = env->subprog_info[i].start; - WARN_ON_ONCE(env->insn_idx == 0); - ret = do_check_common(env, i); - if (ret) { - return ret; - } else if (env->log.level & BPF_LOG_LEVEL) { - verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n", - i, bpf_subprog_name(env, i)); + env->insn_idx = env->subprog_info[i].start; + WARN_ON_ONCE(env->insn_idx == 0); + ret = do_check_common(env, i, context); + if (ret) + return ret; + if (env->log.level & BPF_LOG_LEVEL) + verbose(env, "Func#%d ('%s') is safe for any args " + "that match its prototype\n", + i, bpf_subprog_name(env, i)); + + sub_aux->verified[context] = true; + new_cnt++; } - - /* We verified new global subprog, it might have called some - * more global subprogs that we haven't verified yet, so we - * need to do another pass over subprogs to verify those. - */ - sub_aux->verified = true; - new_cnt++; } - /* We can't loop forever as we verify at least one global subprog on - * each pass. + /* + * We can't loop forever as each pass verifies at least one new context, + * and there are only two contexts per global subprog. */ if (new_cnt) goto again; @@ -19687,7 +19690,7 @@ static int do_check_main(struct bpf_verifier_env *env) int ret; env->insn_idx = 0; - ret = do_check_common(env, 0); + ret = do_check_common(env, 0, env->prog->sleepable); if (!ret) env->prog->aux->stack_depth = env->subprog_info[0].stack_depth; return ret; From a452e729b7be317837bb2157d5d88aa3de9873e6 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:19:21 +0200 Subject: [PATCH 1230/1317] selftests/bpf: Test global subprog callback contexts Exercise global subprogram verification from workqueue callbacks, which can run in a sleepable context even when the containing program is not sleepable. An unprotected callback must not let the global subprogram use implicit RCU protection inherited from the program. Add a negative case which loads an RCU-protected task kptr in a global subprogram reached from a workqueue callback. It fails on an unfixed kernel because the program is incorrectly accepted. Also cover a workqueue callback protected by an explicit RCU read-side critical section, where the same global subprogram remains valid. Call the same harmless global subprogram directly from the main program and from an unprotected callback. Mark it __weak __noinline so both calls survive optimization, and check that its instruction statistics account for both verification contexts. This also verifies that global calls from callbacks are not rejected wholesale. Workqueue callbacks return zero explicitly after the global call, as required by their contract. Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260914131923.2544250-3-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../bpf/progs/verifier_async_cb_context.c | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c index e0926767bbd3..1b653bfb63eb 100644 --- a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c +++ b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c @@ -9,6 +9,11 @@ char _license[] SEC("license") = "GPL"; +struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym; +void bpf_task_release(struct task_struct *p) __ksym; +void bpf_rcu_read_lock(void) __ksym; +void bpf_rcu_read_unlock(void) __ksym; + /* Timer tests */ struct timer_elem { @@ -164,6 +169,7 @@ int syscall_btf_find_prog(void *ctx) struct wq_elem { struct bpf_wq w; + struct task_struct __kptr *task; }; struct { @@ -217,6 +223,106 @@ int wq_sleepable_prog(void *ctx) return 0; } +__noinline int wq_global_acquire(void) +{ + struct task_struct *task, *acquired; + struct wq_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + task = val->task; + if (!task) + return 0; + + acquired = bpf_task_acquire(task); + if (acquired) + bpf_task_release(acquired); + return 0; +} + +static int wq_global_rcu_cb(void *map, int *key, void *value) +{ + wq_global_acquire(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__failure __msg("R1 must be a rcu pointer") +int wq_global_rcu_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_rcu_cb, 0); + return 0; +} + +static int wq_global_rcu_lock_cb(void *map, int *key, void *value) +{ + bpf_rcu_read_lock(); + wq_global_acquire(); + bpf_rcu_read_unlock(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__success +int wq_global_rcu_lock_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + /* Verify the same global subprog in non-sleepable and protected contexts. */ + wq_global_acquire(); + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_rcu_lock_cb, 0); + return 0; +} + +__weak __noinline int wq_global_no_rcu(void) +{ + return 0; +} + +static int wq_global_no_rcu_cb(void *map, int *key, void *value) +{ + wq_global_no_rcu(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__success __log_level(4) +__msg("subprog {{[0-9]+}} (wq_global_no_rcu) global insns_self 4 insns_total 4 stack 0") +int wq_global_no_rcu_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + /* Verify the same global in non-sleepable and unprotected contexts. */ + wq_global_no_rcu(); + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_no_rcu_cb, 0); + return 0; +} + /* Task work tests */ struct task_work_elem { From 70504de0bb627848667207bec7ccfd647deb8814 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Fri, 11 Sep 2026 00:18:24 +0800 Subject: [PATCH 1231/1317] xsk: Use a 32-bit compare in xsk_map_gen_lookup xsk_map_gen_lookup() loads a u32 key and compares it with max_entries using BPF_JMP_IMM. BPF immediates are sign-extended to 64 bits, so a max_entries value of 0x80000000 or higher becomes a threshold larger than every zero-extended 32-bit key. An out-of-range index then skips the bounds check and the generated lookup reads past xsk_map[]. Compare with BPF_JMP32_IMM so the check stays in 32-bit unsigned range. Fixes: e65650f291ee ("bpf: Implement map_gen_lookup() callback for XSKMAP") Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Alexei Starovoitov Reviewed-by: Emil Tsalapatis Link: https://patch.msgid.link/7d2cb8e8dfaa9eb8fdff85156987a60960787dc3.1789056660.git.zhilinz@nebusec.ai Signed-off-by: Eduard Zingerman --- net/xdp/xskmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/xdp/xskmap.c b/net/xdp/xskmap.c index 3bff346308d0..bf00d6463c19 100644 --- a/net/xdp/xskmap.c +++ b/net/xdp/xskmap.c @@ -124,7 +124,7 @@ static int xsk_map_gen_lookup(struct bpf_map *map, struct bpf_insn *insn_buf) struct bpf_insn *insn = insn_buf; *insn++ = BPF_LDX_MEM(BPF_W, ret, index, 0); - *insn++ = BPF_JMP_IMM(BPF_JGE, ret, map->max_entries, 5); + *insn++ = BPF_JMP32_IMM(BPF_JGE, ret, map->max_entries, 5); *insn++ = BPF_ALU64_IMM(BPF_LSH, ret, ilog2(sizeof(struct xsk_sock *))); *insn++ = BPF_ALU64_IMM(BPF_ADD, mp, offsetof(struct xsk_map, xsk_map)); *insn++ = BPF_ALU64_REG(BPF_ADD, ret, mp); From 05762c5bc1cfdcac36747994fde2c04387a457f1 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:52 -0500 Subject: [PATCH 1232/1317] smb: client: fix next_buffer UAF and NextCommand bounds in compound PDUs Fix several related bounds checking and pointer lifecycle issues in receive_encrypted_standard()'s handling of compound encrypted frames: - Clear next_buffer after assigning it to server->bigbuf. A stale next_buffer pointer can lead to a use-after-free on subsequent error paths. - Update pdu_length to the decrypted plaintext size (buf_size). Using the pre-decryption length allows NextCommand to point into stale ciphertext residue. - Reject next_cmd values smaller than MID_HEADER_SIZE(server). - Fix an integer overflow in the upper bound check by verifying pdu_length - next_cmd < MID_HEADER_SIZE(server), ensuring the trailing slice is large enough for a header. Fixes: b24df3e30cbf ("cifs: update receive_encrypted_standard to handle compounded responses") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index cb4fd09f996e..fcf7033889c7 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -5365,6 +5365,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, length = decrypt_raw_data(server, buf, buf_size, NULL, false); if (length) return length; + pdu_length = buf_size; next_is_large = server->large_buf; one_more: @@ -5377,8 +5378,15 @@ receive_encrypted_standard(struct TCP_Server_Info *server, } if (next_cmd) { - if (WARN_ON_ONCE(next_cmd > pdu_length)) + if (next_cmd < MID_HEADER_SIZE(server) || + next_cmd > pdu_length || + pdu_length - next_cmd < MID_HEADER_SIZE(server)) { + unsigned int max_next = pdu_length > (unsigned int)MID_HEADER_SIZE(server) ? + pdu_length - (unsigned int)MID_HEADER_SIZE(server) : 0; + cifs_server_dbg(VFS, "invalid NextCommand offset %u out of range [%zu, %u]\n", + next_cmd, MID_HEADER_SIZE(server), max_next); return -1; + } if (next_is_large) next_buffer = (char *)cifs_buf_get(); else @@ -5414,6 +5422,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, server->bigbuf = buf = next_buffer; else server->smallbuf = buf = next_buffer; + next_buffer = NULL; goto one_more; } else if (ret != 0) { /* From b4694f269e66dfcd66991446375285723b6957bd Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:53 -0500 Subject: [PATCH 1233/1317] smb: client: validate minimum PDU size before smb2_get_data_area_len() __smb2_calc_size() calls smb2_get_data_area_len(), which reads command-specific struct fields to locate the data area. However, smb2_check_message() only validates StructureSize2, meaning a truncated response could cause smb2_get_data_area_len() to read out-of-bounds. Replace has_smb2_data_area[] with smb2_min_pdu_len[], which is now used to indicate both whether a command's response has a data area and the size of that fixed response struct. A non-zero entry means the command has a data area, and is the minimum length required before the struct is read. For each command with a data area, PDUs shorter than this minimum size are rejected instead of parsed. The minimum is not applied to SMB2 error responses, which carry only the 9-byte error body, the same exemption the StructureSize2 check above it already makes. STATUS_MORE_PROCESSING_REQUIRED is treated as a normal reply, since an in-progress SESSION_SETUP response carries a full body and a security blob. Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2misc.c | 69 ++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 9068175e57cd..0cfe60ae42c3 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -85,6 +85,36 @@ static const __le16 smb2_rsp_struct_sizes[NUMBER_OF_SMB2_COMMANDS] = { /* SMB2_OPLOCK_BREAK */ cpu_to_le16(24) }; +/* + * Minimum received PDU size for commands whose response carries a + * variable-length data area. A non-zero entry marks the command as + * having one, and gives the length smb2_check_message() requires + * before smb2_get_data_area_len() reads the offset and length fields + * out of the fixed response struct. + */ +static const size_t smb2_min_pdu_len[NUMBER_OF_SMB2_COMMANDS] = { + /* SMB2_NEGOTIATE */ sizeof(struct smb2_negotiate_rsp), + /* SMB2_SESSION_SETUP */ sizeof(struct smb2_sess_setup_rsp), + /* SMB2_LOGOFF */ 0, + /* SMB2_TREE_CONNECT */ 0, + /* SMB2_TREE_DISCONNECT */ 0, + /* SMB2_CREATE */ sizeof(struct smb2_create_rsp), + /* SMB2_CLOSE */ 0, + /* SMB2_FLUSH */ 0, + /* SMB2_READ */ sizeof(struct smb2_read_rsp), + /* SMB2_WRITE */ 0, + /* SMB2_LOCK */ 0, + /* SMB2_IOCTL */ sizeof(struct smb2_ioctl_rsp), + /* SMB2_CANCEL */ 0, + /* SMB2_ECHO */ 0, + /* SMB2_QUERY_DIRECTORY */ sizeof(struct smb2_query_directory_rsp), + /* SMB2_CHANGE_NOTIFY */ sizeof(struct smb2_change_notify_rsp), + /* SMB2_QUERY_INFO */ sizeof(struct smb2_query_info_rsp), + /* SMB2_SET_INFO */ 0, + /* SMB2_OPLOCK_BREAK */ 0, +}; + +#define smb2_has_data_area(cmd) (smb2_min_pdu_len[cmd] != 0) #define SMB311_NEGPROT_BASE_SIZE (sizeof(struct smb2_hdr) + sizeof(struct smb2_negotiate_rsp)) static __u32 get_neg_ctxt_len(struct smb2_hdr *hdr, __u32 len, @@ -233,6 +263,16 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, } } + if ((shdr->Status == STATUS_SUCCESS || + shdr->Status == STATUS_MORE_PROCESSING_REQUIRED || + pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE) && + smb2_has_data_area(command) && + len < smb2_min_pdu_len[command]) { + cifs_server_dbg(VFS, "SMB2 command %d response too short: %u < %zu\n", + command, len, smb2_min_pdu_len[command]); + return 1; + } + have_data = false; data_area_overlap = false; calc_len = __smb2_calc_size(buf, &have_data, &data_area_overlap); @@ -298,33 +338,6 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, return 0; } -/* - * The size of the variable area depends on the offset and length fields - * located in different fields for various SMB2 responses. SMB2 responses - * with no variable length info, show an offset of zero for the offset field. - */ -static const bool has_smb2_data_area[NUMBER_OF_SMB2_COMMANDS] = { - /* SMB2_NEGOTIATE */ true, - /* SMB2_SESSION_SETUP */ true, - /* SMB2_LOGOFF */ false, - /* SMB2_TREE_CONNECT */ false, - /* SMB2_TREE_DISCONNECT */ false, - /* SMB2_CREATE */ true, - /* SMB2_CLOSE */ false, - /* SMB2_FLUSH */ false, - /* SMB2_READ */ true, - /* SMB2_WRITE */ false, - /* SMB2_LOCK */ false, - /* SMB2_IOCTL */ true, - /* SMB2_CANCEL */ false, /* BB CHECK this not listed in documentation */ - /* SMB2_ECHO */ false, - /* SMB2_QUERY_DIRECTORY */ true, - /* SMB2_CHANGE_NOTIFY */ true, - /* SMB2_QUERY_INFO */ true, - /* SMB2_SET_INFO */ false, - /* SMB2_OPLOCK_BREAK */ false -}; - /* * Returns the pointer to the beginning of the data area. Length of the data * area and the offset to it (from the beginning of the smb are also returned. @@ -451,7 +464,7 @@ __smb2_calc_size(void *buf, bool *have_data, bool *data_area_overlap) */ len += le16_to_cpu(pdu->StructureSize2); - if (has_smb2_data_area[le16_to_cpu(shdr->Command)] == false) + if (!smb2_has_data_area(le16_to_cpu(shdr->Command))) goto calc_size_exit; smb2_get_data_area_len(&offset, &data_length, shdr); From f73726b83e4756fdaa099e1bc1143293bd57ad79 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:54 -0500 Subject: [PATCH 1234/1317] smb: client: fix server->total_read for compound encrypted PDUs In receive_encrypted_standard(), server->total_read is left at the full decrypted frame size when walking sub-PDUs of a compound encrypted frame. As a result, cifs_handle_standard() passes this full size to smb2_check_message(), causing the PDU length guards to incorrectly validate the entire compound frame instead of the current sub-PDU. This allows truncated non-last sub-PDUs to bypass length validation, leading to out-of-bounds reads in smb2_get_data_area_len(). Fix this by setting server->total_read to the true length of the current sub-PDU: next_cmd for non-last sub-PDUs, and the remaining pdu_length for the last one. Fixes: b24df3e30cbf ("cifs: update receive_encrypted_standard to handle compounded responses") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index fcf7033889c7..7f2177f6fc01 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -5371,6 +5371,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, one_more: shdr = (struct smb2_hdr *)buf; next_cmd = le32_to_cpu(shdr->NextCommand); + server->total_read = next_cmd ? next_cmd : pdu_length; if (*num_mids >= MAX_COMPOUND) { cifs_server_dbg(VFS, "too many PDUs in compound\n"); From e83330c55edc0c3ac08aa6c95e49e4694c65523b Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:55 -0500 Subject: [PATCH 1235/1317] smb: client: fix missing lower-bound check on DFS referral string offsets parse_dfs_referrals() checks that DfsPathOffset and NetworkAddressOffset do not exceed the buffer end, but fails to check that they don't point inside the referral header itself. If a server provides an offset smaller than sizeof(struct dfs_referral_level_3), the derived string pointer overlaps with the struct fields, causing cifs_strndup_from_utf16() to interpret header data as UTF-16 strings. Fix this by enforcing that string offsets are at least sizeof(*ref). Fixes: 4ecce920e13a ("CIFS: move DFS response parsing out of SMB1 code") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/misc.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index 945194fe7a97..05168284f205 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -788,7 +788,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags); /* copy DfsPath */ - if (le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) { + if (le16_to_cpu(ref->DfsPathOffset) < sizeof(*ref) || + le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) { + cifs_dbg(VFS, "%s: DfsPathOffset %u out of range [%zu, %td]\n", + __func__, le16_to_cpu(ref->DfsPathOffset), + sizeof(*ref), data_end - (char *)ref); rc = -EINVAL; goto parse_DFS_referrals_exit; } @@ -802,7 +806,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, } /* copy link target UNC */ - if (le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) { + if (le16_to_cpu(ref->NetworkAddressOffset) < sizeof(*ref) || + le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) { + cifs_dbg(VFS, "%s: NetworkAddressOffset %u out of range [%zu, %td]\n", + __func__, le16_to_cpu(ref->NetworkAddressOffset), + sizeof(*ref), data_end - (char *)ref); rc = -EINVAL; goto parse_DFS_referrals_exit; } From 1b3221bb121079ad79a1f3c3aa360ba649832e7a Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:56 -0500 Subject: [PATCH 1236/1317] smb: client: reject short Next offsets in parse_server_interfaces() In parse_server_interfaces(), the server-supplied Next offset is validated against bytes_left, but not against the size of the interface structure itself. A small, non-zero Next value can pass the bounds check but advance the pointer by less than sizeof(*p). This causes the next iteration of the loop to read misaligned, overlapping structure fields. Fix this by ensuring the Next offset is at least sizeof(*p). Fixes: 7d34ec36abb8 ("smb3: fix for slab out of bounds on mount to ksmbd") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 7f2177f6fc01..bda940cb3784 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -785,9 +785,9 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, break; } /* Validate that Next doesn't point beyond the buffer */ - if (next > bytes_left) { - cifs_dbg(VFS, "%s: invalid Next pointer %zu > %zd\n", - __func__, next, bytes_left); + if (next < sizeof(*p) || next > bytes_left) { + cifs_dbg(VFS, "%s: invalid Next pointer %zu out of range [%zu, %zd]\n", + __func__, next, sizeof(*p), bytes_left); rc = -EINVAL; goto out; } From eeb5ef6083e1cefa2ef75041b5597ff228b8d7bb Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:57 -0500 Subject: [PATCH 1237/1317] smb: client: fix OOB struct field reads in move_smb2_ea_to_cifs() In move_smb2_ea_to_cifs(), the while (src_size > 0) loop condition is insufficient. It allows iteration to continue even if the remaining src_size is too small to contain a complete smb2_ea_info structure. Consequently, reads of ea_name_length and ea_value_length can occur out-of-bounds. Fix this by ensuring src_size >= sizeof(*src) before attempting to read any structure fields. Additionally, reject any next_entry_offset that is smaller than sizeof(*src) or that would advance the pointer beyond the available buffer. Note that for calls where the server returns a malformed EA list, the error returned to userspace changes from -ENODATA (getxattr) or -ERANGE (listxattr) to -EIO. This correctly signals a server protocol error rather than misleadingly indicating "attribute not present" or "output buffer too small". Fixes: 95907fea4fd8 ("cifs: Add support for reading attributes on SMB2+") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 25 +++++++++++++++++-------- fs/smb/client/trace.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index bda940cb3784..ee3c98e3f316 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1053,8 +1053,9 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size, char *name, *value; size_t buf_size = dst_size; size_t name_len, value_len, user_name_len; + u32 next_off; - while (src_size > 0) { + while (src_size >= sizeof(*src)) { name_len = (size_t)src->ea_name_length; value_len = (size_t)le16_to_cpu(src->ea_value_length); @@ -1110,14 +1111,22 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size, if (!src->next_entry_offset) break; - if (src_size < le32_to_cpu(src->next_entry_offset)) { - /* stop before overrun buffer */ - rc = -ERANGE; - break; + next_off = le32_to_cpu(src->next_entry_offset); + if (next_off < sizeof(*src) || src_size < next_off) { + cifs_dbg(FYI, "EA next_entry_offset %u out of range [%zu, %zu]\n", + next_off, sizeof(*src), src_size); + rc = smb_EIO2(smb_eio_trace_ea_next_offset, + next_off, src_size); + goto out; + } + src_size -= next_off; + src = (void *)((char *)src + next_off); + if (src_size > 0 && src_size < sizeof(*src)) { + cifs_dbg(FYI, "EA next_entry_offset %u left truncated entry (%zu bytes)\n", + next_off, src_size); + rc = smb_EIO2(smb_eio_trace_ea_next_offset, next_off, src_size); + goto out; } - src_size -= le32_to_cpu(src->next_entry_offset); - src = (void *)((char *)src + - le32_to_cpu(src->next_entry_offset)); } /* didn't find the named attribute */ diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index b442cccd1530..bb8d0197cb54 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -27,6 +27,7 @@ EM(smb_eio_trace_copychunk_overcopy_c, "copychunk_overcopy_c") \ EM(smb_eio_trace_create_rsp_too_small, "create_rsp_too_small") \ EM(smb_eio_trace_dfsref_no_rsp, "dfsref_no_rsp") \ + EM(smb_eio_trace_ea_next_offset, "ea_next_offset") \ EM(smb_eio_trace_ea_overrun, "ea_overrun") \ EM(smb_eio_trace_extract_will_pin, "extract_will_pin") \ EM(smb_eio_trace_forced_shutdown, "forced_shutdown") \ From b09d092eb24ad0110f16a9b7c1ed5d2a0c1733dc Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:58 -0500 Subject: [PATCH 1238/1317] smb: client: fix missing iov bounds check in parse_posix_sids() In parse_posix_sids(), sidsbuf_end is calculated using the server-supplied out_len without being validated against the actual length of the received iov (iov_len). If a server provides an inflated out_len, sidsbuf_end will point past the end of the iov. This defeats the bounds guards in posix_info_sid_size(), allowing out-of-bounds reads into adjacent kernel memory. Fix this by rejecting responses where the calculated sidsbuf_end would exceed the received iov boundaries or cause pointer wraparound. Fixes: a90f37e3d7ac ("smb: client: parse owner/group when creating reparse points") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 96063e355186..13fe8e3b48f3 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -77,6 +77,17 @@ static int parse_posix_sids(struct cifs_open_info_data *data, sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len; sidsbuf_end = sidsbuf + out_len - qi_len; + if (sidsbuf_end < sidsbuf) { + cifs_dbg(VFS, "%s: server-supplied out_len %u caused pointer wraparound\n", + __func__, out_len); + return -EINVAL; + } + if (sidsbuf_end > (u8 *)rsp_iov->iov_base + rsp_iov->iov_len) { + cifs_dbg(VFS, "%s: server-supplied out_len %u overruns iov by %td bytes\n", + __func__, out_len, + sidsbuf_end - ((u8 *)rsp_iov->iov_base + rsp_iov->iov_len)); + return -EINVAL; + } owner_len = posix_info_sid_size(sidsbuf, sidsbuf_end); if (owner_len == -1) From 4775c3b7a597907e0b97556c7986fda238a377ae Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:59 -0500 Subject: [PATCH 1239/1317] smb: client: fix potential OOB read in smb3_enum_snapshots() If snapshot_array_size is smaller than GMT_TOKEN_SIZE, smb3_enum_snapshots() sets ret_data_len to sizeof(struct smb_snapshot_array) without verifying the actual length of the server's reply. Because SMB2_ioctl() places no lower bound on the server-supplied OutputCount and allocates retbuf to exactly that length, a short reply results in ret_data_len exceeding the size of retbuf. The subsequent copy_to_user() then reads past the end of retbuf, leaking adjacent slab memory to userspace. The subsequent clamp check is ineffective as it only reduces ret_data_len. Fix this by rejecting replies shorter than sizeof(struct smb_snapshot_array) with -EIO. Note that the bound is set to the 12-byte struct size rather than the 16-byte MIN_SNAPSHOT_ARRAY_SIZE defined in MS-SMB2 3.3.5.15.1, because 12 bytes is exactly what copy_to_user() attempts to read. Fixes: e02789a53d71 ("smb3: enumerating snapshots was leaving part of the data off end") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index ee3c98e3f316..3464470d3297 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2463,8 +2463,14 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon, * and retry the ioctl again with larger array size sufficient * to hold all of the snapshot GMT tokens on the second try. */ - if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) + if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) { + if (ret_data_len < sizeof(struct smb_snapshot_array)) { + rc = -EIO; + kfree(retbuf); + return rc; + } ret_data_len = sizeof(struct smb_snapshot_array); + } /* * We return struct SRV_SNAPSHOT_ARRAY, followed by From 5f0306e731e2f46e91419eae57eee3a241c055e0 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:34:00 -0500 Subject: [PATCH 1240/1317] smb: client: fix reparse buffer bounds in cifs_query_reparse_point() In cifs_query_reparse_point(), the start >= end check before casting to struct reparse_data_buffer * only ensures the start pointer is within the response. It fails to verify that there is enough space remaining for the fixed 8-byte header of the structure. If a server provides a DataOffset that leaves less than 8 bytes remaining, the check passes, but subsequent reads of ReparseTag and ReparseDataLength will occur out-of-bounds. Fix this by ensuring the remaining space is at least the size of the reparse_data_buffer structure before accessing its fields. Fixes: 56e84c64fc25 ("cifs: Fix validation of SMB1 query reparse point response") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index f9aff0712794..6dddbd84b93b 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -3080,7 +3080,7 @@ int cifs_query_reparse_point(const unsigned int xid, end = 2 + get_bcc(&io_rsp->hdr) + (__u8 *)&io_rsp->ByteCount; start = (__u8 *)&io_rsp->hdr.Protocol + data_offset; - if (start >= end) { + if (start >= end || (size_t)(end - start) < sizeof(*buf)) { rc = smb_EIO2(smb_eio_trace_qreparse_data_area, (unsigned long)start - (unsigned long)io_rsp, (unsigned long)end - (unsigned long)io_rsp); From 3d743adf090cd4c9a2120c1e02b0482e88aa0d2d Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 17 Sep 2026 16:10:15 +0200 Subject: [PATCH 1241/1317] spi: fsl-qspi: Reprogram the clock rate when the operation frequency changes fsl_qspi_select_mem() returns early when the chip select has not changed, which happens before it reaches clk_set_rate(). Since the rate is now taken from the spi-mem operation rather than from the SPI device, the controller honours op->max_freq exactly once per chip select and ignores it for every operation after that. q->selected is only reset to -1 in fsl_qspi_default_setup(), i.e. at probe and on resume, so on the common single chip select board the very first operation latches a rate that all subsequent operations inherit, whatever frequency they asked for. This results in operations being issued with the wrong frequency. Cache the operation frequency the clock was programmed for next to the selected chip select, and redo the clock setup when either changes. Fixes: 2438db5253eb ("spi: fsl-qspi: Support per spi-mem operation frequency switches") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Acked-by: Han Xu Link: https://patch.msgid.link/20260917-fsl-qspi-freq-op-fix-v1-1-5fbe6b02f738@kontron.de Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-qspi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 57358851029b..d2c2090442f8 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -289,6 +289,7 @@ struct fsl_qspi { struct pm_qos_request pm_qos_req; struct device *dev; int selected; + u32 selected_freq; u32 memmap_phy; }; @@ -551,7 +552,8 @@ static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi, unsigned long rate = op->max_freq; int ret; - if (q->selected == spi_get_chipselect(spi, 0)) + if (q->selected == spi_get_chipselect(spi, 0) && + q->selected_freq == op->max_freq) return; if (needs_4x_clock(q)) @@ -571,6 +573,7 @@ static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi, } q->selected = spi_get_chipselect(spi, 0); + q->selected_freq = op->max_freq; fsl_qspi_invalidate(q); } From c9dc7d730319ad64b51570c5387f1fee7b07b510 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 13 Aug 2026 17:50:03 +0800 Subject: [PATCH 1242/1317] PCI: imx6: Move clock enable after core reset assertion Commit 610fa91d9863 ("PCI: imx6: Assert PERST# before enabling regulators") inadvertently moved clock enablement before core reset assertion, breaking PCI device initialization on i.MX6Q Apalis platforms with ASM1061/ASM1062 SATA controllers connected: imx6q-pcie 1ffc000.pcie: host bridge /soc/pcie@1ffc000 ranges: imx6q-pcie 1ffc000.pcie: IO 0x0001f80000..0x0001f8ffff -> 0x0000000000 imx6q-pcie 1ffc000.pcie: MEM 0x0001000000..0x0001efffff -> 0x0001000000 imx6q-pcie 1ffc000.pcie: config reg[1] 0x01f00000 == cpu 0x01f00000 imx6q-pcie 1ffc000.pcie: iATU: unroll F, 4 ob, 4 ib, align 64K, limit 4G imx6q-pcie 1ffc000.pcie: Link: Only Gen1 is enabled imx6q-pcie 1ffc000.pcie: Link failed to come up. LTSSM: POLL_CONFIG imx6q-pcie 1ffc000.pcie: probe with driver imx6q-pcie failed with error -110 NOTE: It is not 100% clear if the issue is specific to the ASM1061/ASM1062 device or on the specific power-up sequence (reset vs cold-power-on). To fix this regression, restore the original sequence where clocks are enabled after asserting core reset and configuring the controller type. Fixes: 610fa91d9863 ("PCI: imx6: Assert PERST# before enabling regulators") Reported-by: Leonardo Costa Closes: https://lore.kernel.org/all/bl7i3obu2clzsgeoct2a4mtfhv6typcjdqmgneropf3hpgwve6@n2m5uhlduw57/T/#u Reported-by: Franz Schnyder Closes: https://lore.kernel.org/all/t65y5d54axtksbfs7r4olcefqhwm6m4dz3njgnrnf7fcotj74i@o7avoznlafbj/ Signed-off-by: Richard Zhu Signed-off-by: Manivannan Sadhasivam [bhelgaas: move to pci/for-linus for v7.3] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org # 7.2+ Link: https://patch.msgid.link/20260813095003.356062-1-hongxing.zhu@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 39790e66b98d..f7a2eb257c16 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1394,12 +1394,6 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) } } - ret = imx_pcie_clk_enable(imx_pcie); - if (ret) { - dev_err(dev, "unable to enable pcie clocks: %d\n", ret); - goto err_pwrctrl_power_off; - } - if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) { pp->bridge->enable_device = imx_pcie_enable_device; pp->bridge->disable_device = imx_pcie_disable_device; @@ -1415,6 +1409,12 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) imx_pcie_configure_type(imx_pcie); + ret = imx_pcie_clk_enable(imx_pcie); + if (ret) { + dev_err(dev, "unable to enable pcie clocks: %d\n", ret); + goto err_pwrctrl_power_off; + } + if (imx_pcie->phy) { ret = phy_init(imx_pcie->phy); if (ret) { From 50e80e2bb5e2be8515205b9c496b9640ddefa434 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Tue, 15 Sep 2026 01:02:07 +0800 Subject: [PATCH 1243/1317] bpf: Skip unsettled links in link iterator bpf_link_prime() inserts a link into link_idr before anon_inode_getfile() succeeds and before bpf_link_settle() publishes the ID in link->id. bpf_link_by_id() treats such an ID-zero link as unsettled, but the link iterator takes a reference without this check. If anon_inode_getfile() then fails, the creator removes the ID and frees its still-private link directly. The iterator is left with a dangling reference and its next bpf_link_put() accesses freed memory. Treat ID-zero entries as transient in bpf_link_get_curr_or_next(), just as bpf_link_by_id() does. BUG: KASAN: slab-use-after-free in bpf_link_put Write of size 8 by task exp/384 Call Trace: bpf_link_put kernel/bpf/syscall.c:3372 bpf_link_seq_next kernel/bpf/link_iter.c:33 bpf_seq_read kernel/bpf/bpf_iter.c:158 vfs_read fs/read_write.c:572 ksys_read fs/read_write.c:716 do_syscall_64 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe arch/x86/entry/entry_64.S:121 Kernel panic - not syncing: KASAN: panic_on_warn set ... Fixes: 9f8836127308 ("bpf: Add bpf_link iterator") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Signed-off-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20260914170206.170723-2-bestswngs@gmail.com --- kernel/bpf/syscall.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 853b47f81384..244a939b9d2d 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -6042,7 +6042,10 @@ struct bpf_link *bpf_link_get_curr_or_next(u32 *id) again: link = idr_get_next(&link_idr, id); if (link) { - link = bpf_link_inc_not_zero(link); + if (link->id) + link = bpf_link_inc_not_zero(link); + else + link = ERR_PTR(-EAGAIN); if (IS_ERR(link)) { (*id)++; goto again; From 717e0a25036b6c92cecace30913b2d874a4c22b8 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:34:39 +0000 Subject: [PATCH 1244/1317] cifs: Fix server use-after-free in cifs_chan_skip_or_disable() When a secondary channel is no longer supported by the server, cifs_chan_skip_or_disable() drops the channel reference with cifs_put_tcp_session() and then continues to use the server pointer by calling cifs_signal_cifsd_for_reconnect() on it and reading its primary_server pointer. cifs_put_tcp_session() can drop the last reference of the channel and tear it down, so both the channel and the primary server (whose reference is also dropped by cifs_put_tcp_session()) can be freed before they are signaled for reconnect. Signal the channel and the primary server and capture the primary server pointer before dropping the channel reference with cifs_put_tcp_session(). Fixes: f591062bdbf4 ("cifs: handle servers that still advertise multichannel after disabling") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index dea05aeb53a1..880ce12f50c4 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -189,18 +189,19 @@ cifs_chan_skip_or_disable(struct cifs_ses *ses, spin_unlock(&ses->chan_lock); /* - * the above reference of server by channel - * needs to be dropped without holding chan_lock - * as cifs_put_tcp_session takes a higher lock - * i.e. cifs_tcp_ses_lock + * signal the channel and its primary server to + * reconnect before dropping the above reference of + * server by channel, which is done without holding + * chan_lock as cifs_put_tcp_session takes a higher + * lock i.e. cifs_tcp_ses_lock */ - cifs_put_tcp_session(server, from_reconnect); - cifs_signal_cifsd_for_reconnect(server, false); /* mark primary server as needing reconnect */ pserver = server->primary_server; cifs_signal_cifsd_for_reconnect(pserver, false); + + cifs_put_tcp_session(server, from_reconnect); skip_terminate: return -EHOSTDOWN; } From e7d3e2f46dd5a69046e6d95a0f189155a5516b93 Mon Sep 17 00:00:00 2001 From: "Chang S. Bae" Date: Wed, 16 Sep 2026 22:59:39 +0000 Subject: [PATCH 1245/1317] x86/microcode/intel: Reject problematic loading on Granite Rapids systems Microcode updates can usually jump revisions. However, there is an erratum on Granite Rapids systems. If they "jump over" revision 0x1000405, they result in an #MC. Avoid it. Signed-off-by: Chang S. Bae Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Dave Hansen Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916225939.1144524-1-chang.seok.bae@intel.com --- arch/x86/kernel/cpu/microcode/intel.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/x86/kernel/cpu/microcode/intel.c b/arch/x86/kernel/cpu/microcode/intel.c index 1142183c950c..9f09d388ed20 100644 --- a/arch/x86/kernel/cpu/microcode/intel.c +++ b/arch/x86/kernel/cpu/microcode/intel.c @@ -309,6 +309,26 @@ static void save_microcode_patch(struct microcode_intel *patch) pr_err("Unable to allocate microcode memory size: %u\n", size); } +static bool revision_is_safe(struct cpu_signature *sig, u32 rev) +{ + u32 vfm = IFM(x86_family(sig->sig), x86_model(sig->sig)); + + /* + * Erratum GNR98 can cause #MCs if "jumping over" revision 0x1000405. + * Avoid the jumps. + */ + if (vfm == INTEL_GRANITERAPIDS_X && + x86_stepping(sig->sig) == 1 && + sig->pf & 0x95 && + sig->rev < 0x1000405 && + rev > 0x1000405) { + pr_err_once("Erratum GNR98: skipping revision 0x%x.\n", rev); + return false; + } + + return true; +} + /* Scan blob for microcode matching the boot CPUs family, model, stepping */ static __init struct microcode_intel *scan_microcode(void *data, size_t size, struct ucode_cpu_info *uci, @@ -330,6 +350,9 @@ static __init struct microcode_intel *scan_microcode(void *data, size_t size, if (!intel_find_matching_signature(data, &uci->cpu_sig)) continue; + if (!revision_is_safe(&uci->cpu_sig, mc_header->rev)) + continue; + /* * For saving the early microcode, find the matching revision which * was loaded on the BSP. @@ -878,6 +901,9 @@ static enum ucode_state parse_microcode_blobs(int cpu, struct iov_iter *iter) if (!intel_find_matching_signature(mc, &uci->cpu_sig)) continue; + if (!revision_is_safe(&uci->cpu_sig, mc_header.rev)) + continue; + is_safe = ucode_validate_minrev(&mc_header); if (force_minrev && !is_safe) continue; From 261b61d3735b042ae25634f795c4540be0fc140c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:09 +0200 Subject: [PATCH 1246/1317] bpf: Make post-verification instruction rewrites killable After do_check() returns, the verifier runs several instruction rewrite passes. Some of them patch or remove one instruction at a time. Each operation moves the remaining instruction and auxiliary-data arrays and adjusts all branch offsets, making the overall work quadratic in the program length. A privileged loader can submit 131072 unconditional jumps by zero followed by a valid return. Verification finishes quickly, but bpf_opt_remove_nops() then spends a long time removing each jump separately. Since this post-verification work neither checks for signals nor reschedules, a pending SIGKILL cannot terminate the task until the rewrite finishes. Make bpf_patch_insn_data() and verifier_remove_insns() common cancellation and rescheduling points. These helpers run from BPF_PROG_LOAD process context, and bpf_patch_insn_data() can already sleep while reallocating auxiliary data. Report interrupted constant blinding as -EINTR and propagate it through both JIT paths, including kernels that permit interpreter fallback. Other blinding failures retain the existing fallback behavior. This does not reduce the quadratic cost of the rewrite passes, but it makes the work preemptible and allows a killed loader to be torn down promptly. Fixes: 52875a04f4b2 ("bpf: verifier: remove dead code") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-2-memxor@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/core.c | 21 +++++++++++++++++---- kernel/bpf/fixups.c | 24 ++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 8b294dfc1ad4..2e3bf8113ae9 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -1619,6 +1620,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp * fix it up here on error. */ bpf_jit_prog_release_other(prog, clone); + if (env && fatal_signal_pending(current)) + return ERR_PTR(-EINTR); return IS_ERR(tmp) ? tmp : ERR_PTR(-ENOMEM); } @@ -2636,11 +2639,14 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc orig_prog = prog; prog = bpf_jit_blind_constants(env, prog); /* - * If blinding was requested and we failed during blinding, we must fall - * back to the interpreter. + * Fall back to the interpreter after blinding failures, except when + * the loader was killed. */ - if (IS_ERR(prog)) + if (IS_ERR(prog)) { + if (PTR_ERR(prog) == -EINTR) + return prog; goto out_restore; + } prog = bpf_int_jit_compile(env, prog); if (prog->jited) { @@ -2659,6 +2665,8 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp, int *err) { + struct bpf_prog *jit_prog; + /* In case of BPF to BPF calls, verifier did all the prep * work with regards to JITing, etc. */ @@ -2681,7 +2689,12 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct if (*err) return fp; - fp = bpf_prog_jit_compile(env, fp); + jit_prog = bpf_prog_jit_compile(env, fp); + if (IS_ERR(jit_prog)) { + *err = PTR_ERR(jit_prog); + return fp; + } + fp = jit_prog; bpf_prog_jit_attempt_done(fp); if (!fp->jited && jit_needed) { *err = -ENOTSUPP; diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c index 52d3cec33672..d6f83521fc78 100644 --- a/kernel/bpf/fixups.c +++ b/kernel/bpf/fixups.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "disasm.h" @@ -306,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len) } } +/* + * Some post-verification instruction rewriting passes require an + * O(prog->len) operation per instruction. Keep their shared primitives + * killable and preemptible. + */ +static bool bpf_rewrite_must_abort(void) +{ + if (fatal_signal_pending(current)) + return true; + cond_resched(); + return false; +} + struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, const struct bpf_insn *patch, u32 len) { struct bpf_prog *new_prog; struct bpf_insn_aux_data *new_data = NULL; + if (bpf_rewrite_must_abort()) + return NULL; + if (len > 1) { new_data = vrealloc(env->insn_aux_data, array_size(env->prog->len + len - 1, @@ -523,6 +540,9 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt) unsigned int orig_prog_len = env->prog->len; int err; + if (bpf_rewrite_must_abort()) + return -EINTR; + if (bpf_prog_is_offloaded(env->prog->aux)) bpf_prog_offload_remove_insns(env, off, cnt); @@ -1356,7 +1376,7 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env) } prog = bpf_jit_blind_constants(env, prog); if (IS_ERR(prog)) { - err = -ENOMEM; + err = PTR_ERR(prog); prog = orig_prog; goto out_restore; } @@ -1433,7 +1453,7 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env) err = bpf_jit_subprogs(env); if (err == 0) return 0; - if (err == -EFAULT) + if (err == -EFAULT || err == -EINTR) return err; } #ifndef CONFIG_BPF_JIT_ALWAYS_ON From fd16449a9b3b31a8f18944c2f0e29e4e218ca2cf Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:10 +0200 Subject: [PATCH 1247/1317] bpf: Preserve packet pointer class displacement in regsafe() regsafe() maps packet pointer IDs between states and checks that each current register range is a subset of the corresponding explored register range. It does not, however, preserve the displacement between registers that share a packet pointer ID. This is unsound because packet range is shared by ID. A bounds check on one class member updates every member, and a later access can consume the range through another member. Commit 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") folded the fixed pointer offset into r64 and removed the old off equality check, so two individually narrower registers can prune even when their displacement has changed. The explored path can then license an out-of-bounds packet access on the pruned path. Require matching range bases for packet pointers with an ID. Together with the existing ID mapping, this preserves the displacement between members of each packet-pointer class without adding per-ID state. Packet pointers without an ID remain unaffected. Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-3-memxor@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/states.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index 66fb11b6c6a7..6c88ad95b63b 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -635,6 +635,9 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, /* id relations must be preserved */ if (!check_ids(rold->id, rcur->id, idmap)) return false; + /* Preserve displacements between pointers sharing an ID. */ + if (rold->id && rold->r64.base != rcur->r64.base) + return false; /* new val must satisfy old val knowledge */ return range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off); From 2059d9af54f0d66deb237aa75d2ed28648df05a1 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:11 +0200 Subject: [PATCH 1248/1317] selftests/bpf: Test packet pointer class displacement pruning Add two paths whose packet pointer ranges are individually compatible at a join but whose members have different relative displacements. The first path proves an eight-byte access through one member. On the second path, the same guard only proves that the access starts before data_end. An affected verifier prunes the second path and accepts the program. With packet pointer class displacement preserved, it explores that path and rejects the out-of-bounds access. Read the unknown offset and branch selector directly from XDP context fields, and force state checkpoints so the pruning attempt does not depend on the verifier checkpoint heuristics. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://patch.msgid.link/20260917233222.2542500-4-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../progs/verifier_xdp_direct_packet_access.c | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c index 0b86d95a4133..9866bc154194 100644 --- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c @@ -1719,4 +1719,39 @@ l0_%=: r0 = 0; \ : __clobber_all); } +SEC("xdp") +__description("XDP pkt regsafe preserves packet pointer class displacement") +__failure __msg("R2 min value is outside of the allowed memory range") +__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ) +__naked void pkt_regsafe_class_displacement(void) +{ + asm volatile (" \ + r8 = *(u32 *)(r1 + %[xdp_md_data_end]); \ + r9 = *(u32 *)(r1 + %[xdp_md_data]); \ + r4 = *(u32 *)(r1 + %[xdp_md_rx_queue_index]); \ + r4 &= 15; \ + r0 = *(u32 *)(r1 + %[xdp_md_ingress_ifindex]); \ + if r0 != 0 goto l0_%=; \ + r2 = r9; \ + r2 += r4; \ + r3 = r2; \ + r3 += 8; \ + goto l1_%=; \ +l0_%=: r4 &= 3; \ + r4 += 8; \ + r2 = r9; \ + r2 += r4; \ + r3 = r2; \ +l1_%=: if r3 > r8 goto l2_%=; \ + r0 = *(u64 *)(r2 + 0); \ +l2_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(xdp_md_data, offsetof(struct xdp_md, data)), + __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end)), + __imm_const(xdp_md_rx_queue_index, offsetof(struct xdp_md, rx_queue_index)), + __imm_const(xdp_md_ingress_ifindex, offsetof(struct xdp_md, ingress_ifindex)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; From c26e97721b172163042b98572fead234797f2c3c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:12 +0200 Subject: [PATCH 1249/1317] bpf: Apply CO-RE relocations before subprogram validation check_subprogs() verifies that each subprogram ends in an exit or an unconditional jump before in-kernel CO-RE relocations are applied. An unresolved relocation can then replace that terminal instruction with an invalid helper call. The resulting fall-through into another subprogram breaks the CFG invariant used by postorder and stack liveness analysis, which can write past their per-subprogram arrays. Apply CO-RE relocations immediately after preparing the program BTF, before subprogram discovery and validation. Keep func_info and line_info validation after subprogram discovery because those records depend on the complete subprogram layout. Reject an ldimm64 first slot at the end of the instruction stream before CO-RE can inspect its missing second slot. check_subprogs() previously rejected this form before relocation processing because it is not a valid subprogram terminator. Moving CO-RE ahead of check_subprogs() removes that implicit protection, so perform an explicit check before applying relocations. Include core_relo_cnt when deciding whether to prepare program BTF. A load that supplied only CO-RE relocation metadata previously skipped both BTF setup and relocation processing. Fixes: fbd94c7afcf9 ("bpf: Pass a set of bpf_core_relo-s to prog_load command.") Suggested-by: Andrii Nakryiko Suggested-by: Alexei Starovoitov Suggested-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-5-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf_verifier.h | 2 ++ kernel/bpf/check_btf.c | 12 ++++-------- kernel/bpf/verifier.c | 12 +++++++++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 36b65797877d..bba5a727c651 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -1197,6 +1197,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id) int bpf_prepare_btf_info(struct bpf_verifier_env *env, const union bpf_attr *attr, bpfptr_t uattr); +int bpf_check_core_relo(struct bpf_verifier_env *env, + const union bpf_attr *attr, bpfptr_t uattr); int bpf_check_btf_info(struct bpf_verifier_env *env, const union bpf_attr *attr, bpfptr_t uattr); diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c index 0e8b3ccc7a5b..4c1ed842f661 100644 --- a/kernel/bpf/check_btf.c +++ b/kernel/bpf/check_btf.c @@ -338,9 +338,9 @@ static int check_btf_line(struct bpf_verifier_env *env, #define MIN_CORE_RELO_SIZE sizeof(struct bpf_core_relo) #define MAX_CORE_RELO_SIZE MAX_FUNCINFO_REC_SIZE -static int check_core_relo(struct bpf_verifier_env *env, - const union bpf_attr *attr, - bpfptr_t uattr) +int bpf_check_core_relo(struct bpf_verifier_env *env, + const union bpf_attr *attr, + bpfptr_t uattr) { u32 i, nr_core_relo, ncopy, expected_size, rec_size; struct bpf_core_relo core_relo = {}; @@ -414,7 +414,7 @@ int bpf_prepare_btf_info(struct bpf_verifier_env *env, struct btf *btf; int err; - if (!attr->func_info_cnt && !attr->line_info_cnt) { + if (!attr->func_info_cnt && !attr->line_info_cnt && !attr->core_relo_cnt) { if (check_abnormal_return(env)) return -EINVAL; return 0; @@ -455,9 +455,5 @@ int bpf_check_btf_info(struct bpf_verifier_env *env, if (err) return err; - err = check_core_relo(env, attr, uattr); - if (err) - return err; - return 0; } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 5d7080c260d8..33161dc64568 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -21199,6 +21199,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, ret = bpf_diag_init(env); if (ret) goto err_prep; + if (env->prog->insnsi[env->prog->len - 1].code == (BPF_LD | BPF_IMM | BPF_DW)) { + verbose(env, "invalid bpf_ld_imm64 insn\n"); + ret = -EINVAL; + goto err_prep; + } if (env->signature) { ret = bpf_prog_calc_tag(env->prog); if (ret < 0) @@ -21274,6 +21279,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, if (ret < 0) goto skip_full_check; + /* Apply CO-RE before validating the program's instruction layout. */ + ret = bpf_check_core_relo(env, attr, uattr); + if (ret < 0) + goto skip_full_check; + /* Discover all subprograms before validating their layout and BTF. */ ret = add_subprogs(env); if (ret < 0) @@ -21283,7 +21293,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, if (ret < 0) goto skip_full_check; - /* Validate BTF against the complete subprogram layout and apply CO-RE. */ + /* Validate BTF against the complete subprogram layout. */ ret = bpf_check_btf_info(env, attr, uattr); if (ret < 0) goto skip_full_check; From 968ee7c06b62f1ac4597c351a45c2219a678a458 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:13 +0200 Subject: [PATCH 1250/1317] selftests/bpf: Test early in-kernel CO-RE relocation Add a raw program load with CO-RE relocation metadata but no func_info or line_info. Place the relocation in dead code and require the poisoning log, proving that the kernel processes standalone CO-RE metadata instead of silently skipping it. Also give a subprogram a relocatable immediate as its terminal instruction. Require the relocation's poisoning log before check_subprogs() rejects the resulting fall-through. With the old ordering, check_subprogs() rejects the original terminal instruction before CO-RE can emit the substitution log, so the test continues to distinguish the ordering after relocation target validation is tightened. Submit a trailing ldimm64 first slot with CO-RE metadata and require the early structural diagnostic. This exercises the check that protects relocation processing instead of the later regular instruction validation. Load the standalone instruction stream without relocation metadata first to ensure that CO-RE processing causes its poisoning diagnostic. Encode the fixed BTF metadata directly with the selftest BTF helpers. Suggested-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-6-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/core_reloc_raw.c | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c index a18d3680fb16..bb19e49dd87d 100644 --- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c @@ -14,6 +14,138 @@ static char log[16 * 1024]; +static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt, + struct bpf_func_info *funcs, int func_cnt, + int enum_id, int access_str_off, int insn_idx, + bool relocate) +{ + struct bpf_core_relo relo = { + .insn_off = insn_idx * sizeof(struct bpf_insn), + .type_id = enum_id, + .access_str_off = access_str_off, + .kind = BPF_CORE_ENUMVAL_VALUE, + }; + union bpf_attr attr = { + .prog_type = BPF_PROG_TYPE_SOCKET_FILTER, + .insn_cnt = insn_cnt, + .insns = (__u64)insns, + .license = (__u64)"GPL", + .log_buf = (__u64)log, + .log_size = sizeof(log), + .log_level = 2, + .prog_btf_fd = btf_fd, + .func_info_rec_size = sizeof(struct bpf_func_info), + .func_info = (__u64)funcs, + .func_info_cnt = func_cnt, + }; + + if (relocate) { + attr.core_relo_cnt = 1; + attr.core_relos = (__u64)&relo; + attr.core_relo_rec_size = sizeof(relo); + } + memset(log, 0, sizeof(log)); + return sys_bpf_prog_load(&attr, sizeof(attr), 1); +} + +static void test_early_core_relo(void) +{ + struct test_btf { + struct btf_header hdr; + __u32 types[18]; + char strings[64]; + } raw_btf = { + .hdr = { + .magic = BTF_MAGIC, + .version = BTF_VERSION, + .hdr_len = sizeof(struct btf_header), + .type_off = 0, + .type_len = sizeof(raw_btf.types), + .str_off = offsetof(struct test_btf, strings) - + offsetof(struct test_btf, types), + .str_len = sizeof(raw_btf.strings), + }, + .types = { + BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4), /* [1] int */ + BTF_FUNC_PROTO_ENC(1, 0), /* [2] int (*)(void) */ + BTF_FUNC_ENC(5, 2), /* [3] main_fn */ + BTF_FUNC_ENC(13, 2), /* [4] sub_fn */ + BTF_TYPE_ENC(20, BTF_INFO_ENC(BTF_KIND_ENUM, 0, 1), 4), /* [5] enum */ + BTF_ENUM_ENC(45, 0), /* value = 0 */ + }, + .strings = "\0int\0main_fn\0sub_fn\0core_relo_poison_missing\0value\0" "0", + }; + struct bpf_func_info funcs[] = { + { .insn_off = 0, .type_id = 3 }, + { .insn_off = 3, .type_id = 4 }, + }; + struct bpf_insn core_only[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + struct bpf_insn subprog[] = { + BPF_CALL_REL(2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + struct bpf_insn truncated_ldimm64[] = { + BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, 0, 0, 0, 0), + }; + int access_str_off = 51; /* offset of "0" */ + int enum_id = 5; + int btf_fd, prog_fd = -1; + + btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), NULL); + if (!ASSERT_GE(btf_fd, 0, "btf_load")) + goto cleanup; + + if (test__start_subtest("without_func_info")) { + prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0, + enum_id, access_str_off, 2, false); + if (!ASSERT_GE(prog_fd, 0, "control_load")) + goto cleanup; + close(prog_fd); + prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0, + enum_id, access_str_off, 2, true); + if (!ASSERT_GE(prog_fd, 0, "poisoned_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + close(prog_fd); + prog_fd = -1; + } + + if (test__start_subtest("before_subprog_validation")) { + prog_fd = load_core_relo_insns(btf_fd, subprog, ARRAY_SIZE(subprog), funcs, 2, + enum_id, access_str_off, 2, true); + if (!ASSERT_LT(prog_fd, 0, "poisoned_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + ASSERT_HAS_SUBSTR(log, "last insn is not an exit or jmp", "poisoned_load_log"); + } + + if (test__start_subtest("truncated_ldimm64")) { + prog_fd = load_core_relo_insns(btf_fd, truncated_ldimm64, + ARRAY_SIZE(truncated_ldimm64), NULL, 0, + enum_id, access_str_off, 0, true); + if (!ASSERT_LT(prog_fd, 0, "truncated_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "invalid bpf_ld_imm64 insn", "truncated_load_log"); + } + +cleanup: + if (env.verbosity > VERBOSE_NORMAL && log[0]) { + printf("-------- program load log start --------\n"); + printf("%s", log); + printf("-------- program load log end ----------\n"); + } + close(prog_fd); + close(btf_fd); +} + /* Check that verifier rejects BPF program containing relocation * pointing to non-existent BTF type. */ @@ -120,6 +252,7 @@ static void test_bad_local_id(void) void test_core_reloc_raw(void) { + test_early_core_relo(); if (test__start_subtest("bad_local_id")) test_bad_local_id(); } From 394ae398337c5f87e567f6cd63b937fc2b2f6ddc Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:14 +0200 Subject: [PATCH 1251/1317] bpf: Restrict CO-RE poisoning to relocatable instructions CO-RE relocation records can name any instruction offset. When a relocation cannot be resolved, bpf_core_patch_insn() currently poisons its target before checking whether that instruction is a valid relocation target. Malformed metadata can therefore replace jumps, calls, exits, register-source arithmetic, or non-immediate loads instead of failing at the relocation step. Handle poisoning only after the instruction has passed the same class and operand-form checks used for a resolved relocation. Route invalid forms through the existing diagnostic and return a hard error. Keep poisoning supported instructions, including both halves of a plain ldimm64, so an unresolved relocation in dead code remains valid. Extend bpf_core_poison_insn() to poison both halves of ldimm64, and return its status directly from each validated instruction case. This avoids routing the success path through a common label and leaves the helper free to report errors. The shared relocation code applies this restriction to both libbpf and in-kernel CO-RE. Fixes: d7a252708dbc ("libbpf: Improve handling of failed CO-RE relocations") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-7-memxor@gmail.com Signed-off-by: Eduard Zingerman --- tools/lib/bpf/relo_core.c | 58 +++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/tools/lib/bpf/relo_core.c b/tools/lib/bpf/relo_core.c index 8ad2715721cf..2672623a4198 100644 --- a/tools/lib/bpf/relo_core.c +++ b/tools/lib/bpf/relo_core.c @@ -980,23 +980,30 @@ static int bpf_core_calc_relo(const char *prog_name, } /* - * Turn instruction for which CO_RE relocation failed into invalid one with + * Turn instruction for which CO-RE relocation failed into invalid one with * distinct signature. */ -static void bpf_core_poison_insn(const char *prog_name, int relo_idx, - int insn_idx, struct bpf_insn *insn) +static int bpf_core_poison_insn(const char *prog_name, int relo_idx, + struct bpf_insn *insn, int insn_idx) { - pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n", - prog_name, relo_idx, insn_idx); - insn->code = BPF_JMP | BPF_CALL; - insn->dst_reg = 0; - insn->src_reg = 0; - insn->off = 0; - /* if this instruction is reachable (not a dead code), - * verifier will complain with the following message: - * invalid func unknown#195896080 - */ - insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */ + int insn_cnt = is_ldimm64_insn(insn) ? 2 : 1; + int i; + + for (i = 0; i < insn_cnt; i++) { + pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n", + prog_name, relo_idx, insn_idx + i); + insn[i].code = BPF_JMP | BPF_CALL; + insn[i].dst_reg = 0; + insn[i].src_reg = 0; + insn[i].off = 0; + /* + * If this instruction is reachable (not dead code), the verifier + * will complain with "invalid func unknown#195896080". + */ + insn[i].imm = 195896080; /* => 0xbad2310 => "bad relo" */ + } + + return 0; } static int insn_bpf_size_to_bytes(struct bpf_insn *insn) @@ -1047,17 +1054,6 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, class = BPF_CLASS(insn->code); - if (res->poison) { -poison: - /* poison second part of ldimm64 to avoid confusing error from - * verifier about "unknown opcode 00" - */ - if (is_ldimm64_insn(insn)) - bpf_core_poison_insn(prog_name, relo_idx, insn_idx + 1, insn + 1); - bpf_core_poison_insn(prog_name, relo_idx, insn_idx, insn); - return 0; - } - orig_val = res->orig_val; new_val = res->new_val; @@ -1065,7 +1061,9 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, case BPF_ALU: case BPF_ALU64: if (BPF_SRC(insn->code) != BPF_K) - return -EINVAL; + goto bad_insn; + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); if (res->validate && insn->imm != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %d, exp %llu -> %llu\n", prog_name, relo_idx, @@ -1082,6 +1080,8 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, case BPF_LDX: case BPF_ST: case BPF_STX: + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); if (res->validate && insn->off != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDX/ST/STX) value: got %d, exp %llu -> %llu\n", prog_name, relo_idx, insn_idx, insn->off, (unsigned long long)orig_val, @@ -1097,7 +1097,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) accesses field incorrectly. " "Make sure you are accessing pointers, unsigned integers, or fields of matching type and size.\n", prog_name, relo_idx, insn_idx); - goto poison; + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); } orig_val = insn->off; @@ -1140,6 +1140,9 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, return -EINVAL; } + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); + imm = (__u32)insn[0].imm | ((__u64)insn[1].imm << 32); if (res->validate && imm != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDIMM64) value: got %llu, exp %llu -> %llu\n", @@ -1157,6 +1160,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, break; } default: +bad_insn: pr_warn("prog '%s': relo #%d: trying to relocate unrecognized insn #%d, code:0x%x, src:0x%x, dst:0x%x, off:0x%x, imm:0x%x\n", prog_name, relo_idx, insn_idx, insn->code, (unsigned)insn->src_reg, (unsigned)insn->dst_reg, (unsigned)insn->off, (unsigned)insn->imm); From 3440505aca926e726a26c0fd30356454334322bd Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:15 +0200 Subject: [PATCH 1252/1317] selftests/bpf: Test CO-RE instruction poisoning restrictions Add raw CO-RE relocations that fail to resolve their target enum value. Place each supported and unsupported instruction form in dead code. Unsupported targets must fail relocation with a diagnostic even when they are unreachable. Supported ALU immediates, memory accesses, and ldimm64 instructions must still be poisoned and removed as dead code, allowing the program to load. Check that both halves of ldimm64 are poisoned. Load every instruction stream without relocations first to ensure that rejection is caused by the relocation rather than the original program. Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-8-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/core_reloc_raw.c | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c index bb19e49dd87d..51f42b02a267 100644 --- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c @@ -50,6 +50,28 @@ static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt static void test_early_core_relo(void) { + static const char unrecognized[] = "trying to relocate unrecognized insn #2"; + static const struct { + const char *name; + struct bpf_insn insns[2]; + const char *err_msg; + } tests[] = { + { "poison_exit", { BPF_EXIT_INSN() }, unrecognized }, + { "poison_ja", { BPF_JMP_A(1) }, unrecognized }, + { "poison_jmp", { BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized }, + { "poison_jmp32", { BPF_JMP32_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized }, + { "poison_call", { BPF_EMIT_CALL(BPF_FUNC_get_prandom_u32) }, unrecognized }, + { "poison_alu_reg", { BPF_MOV32_REG(BPF_REG_0, BPF_REG_1) }, unrecognized }, + { "poison_alu64_reg", { BPF_MOV64_REG(BPF_REG_0, BPF_REG_1) }, unrecognized }, + { "poison_ld_abs", { BPF_LD_ABS(BPF_W, 0) }, + "insn #2 (LDIMM64) has unexpected form" }, + { "poison_alu_imm", { BPF_MOV32_IMM(BPF_REG_0, 0) } }, + { "poison_alu64_imm", { BPF_MOV64_IMM(BPF_REG_0, 0) } }, + { "poison_ldx", { BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0) } }, + { "poison_st", { BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0) } }, + { "poison_stx", { BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4) } }, + { "poison_ldimm64", { BPF_LD_IMM64(BPF_REG_0, 0) } }, + }; struct test_btf { struct btf_header hdr; __u32 types[18]; @@ -97,7 +119,7 @@ static void test_early_core_relo(void) }; int access_str_off = 51; /* offset of "0" */ int enum_id = 5; - int btf_fd, prog_fd = -1; + int btf_fd, prog_fd = -1, i; btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), NULL); if (!ASSERT_GE(btf_fd, 0, "btf_load")) @@ -136,6 +158,43 @@ static void test_early_core_relo(void) ASSERT_HAS_SUBSTR(log, "invalid bpf_ld_imm64 insn", "truncated_load_log"); } + for (i = 0; i < ARRAY_SIZE(tests); i++) { + struct bpf_insn insns[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1), + tests[i].insns[0], + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + bool is_ldimm64 = insns[2].code == (BPF_LD | BPF_DW | BPF_IMM); + + if (!test__start_subtest(tests[i].name)) + continue; + if (is_ldimm64) { + insns[1].off = 2; + insns[3] = tests[i].insns[1]; + } + prog_fd = load_core_relo_insns(btf_fd, insns, ARRAY_SIZE(insns), funcs, 1, + enum_id, access_str_off, 2, false); + if (!ASSERT_GE(prog_fd, 0, "control_load")) + goto cleanup; + close(prog_fd); + prog_fd = load_core_relo_insns(btf_fd, insns, ARRAY_SIZE(insns), funcs, 1, + enum_id, access_str_off, 2, true); + if (!tests[i].err_msg) { + ASSERT_GE(prog_fd, 0, "dead_poison_load"); + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + if (is_ldimm64) + ASSERT_HAS_SUBSTR(log, "substituting insn #3", "poison_ldimm64_log"); + } else { + ASSERT_LT(prog_fd, 0, "invalid_poison_load"); + ASSERT_HAS_SUBSTR(log, tests[i].err_msg, "invalid_poison_log"); + ASSERT_NULL(strstr(log, "substituting insn"), "invalid_poison_substitution"); + } + close(prog_fd); + prog_fd = -1; + } + cleanup: if (env.verbosity > VERBOSE_NORMAL && log[0]) { printf("-------- program load log start --------\n"); From 71919742c83c32afcccf06a7a28e28f3f55f21a2 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:16 +0200 Subject: [PATCH 1253/1317] bpf: Assign lock identity to callback map values A nested bpf_for_each_map_elem() callback can unlock a different element of the same map: static long inner(void *map, int *key, struct value *v, struct value **outer_value) { bpf_spin_lock(&v->lock); bpf_spin_unlock(&(*outer_value)->lock); return 0; } static long outer(void *map, int *key, struct value *v, void *ctx) { bpf_for_each_map_elem(map, inner, &v, 0); return 0; } Both callback values currently have ID zero and the same map_ptr. process_spin_lock() compares those two fields, so it accepts the unlock even though the two callbacks can receive different map elements. Assign a fresh ID to every callback map value in the for-each, timer/workqueue, and task-work constructors. Copies of one callback argument retain its ID, so locking and unlocking through that argument continues to work. Distinct callbacks also get distinct IDs for single-element arrays, including inner arrays sharing inner_map_meta. Preserve map_uid for every inner-map lookup and compare it through check_ids() during state pruning. This preserves relationships between maps, keys, and values while allowing equivalent states with different lookup IDs to match. It avoids field-specific rules for when an inner map needs an identity. Move map_uid out of the metadata union and next to the other IDs, so register comparisons can use the existing memcmp() ranges and remap the IDs separately. Clear it when resetting a register or converting a map lookup result to a socket pointer. Shrink frameno to u8, which is enough for MAX_CALL_FRAMES, to make room without growing bpf_reg_state. Fixes: d0d78c1df9b1 ("bpf: Allow locking bpf_spin_lock global variables") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-9-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf_verifier.h | 25 +++++++++++++------------ kernel/bpf/states.c | 6 ++++-- kernel/bpf/verifier.c | 13 +++++++++---- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index bba5a727c651..a7202b44ab10 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -46,17 +46,11 @@ struct bpf_reg_state { /* valid when type == PTR_TO_PACKET */ int range; - /* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE | - * PTR_TO_MAP_VALUE_OR_NULL + /* + * For CONST_PTR_TO_MAP, PTR_TO_MAP_KEY, PTR_TO_MAP_VALUE and + * PTR_TO_INSN. */ - struct { - struct bpf_map *map_ptr; - /* To distinguish map lookups from outer map - * the map_uid is non-zero for registers - * pointing to inner maps. - */ - u32 map_uid; - }; + struct bpf_map *map_ptr; /* for PTR_TO_BTF_ID */ struct { @@ -155,13 +149,20 @@ struct bpf_reg_state { * gets parent_id set to the dynptr's id. */ u32 parent_id; - /* Inside the callee two registers can be both PTR_TO_STACK like + /* + * Distinguishes inner-map lookups and their keys and values. Zero for + * other registers. Kept outside the metadata union for ID remapping + * during state comparisons. + */ + u32 map_uid; + /* + * Inside the callee two registers can be both PTR_TO_STACK like * R1=fp-8 and R2=fp-8, but one of them points to this function stack * while another to the caller's stack. To differentiate them 'frameno' * is used which is an index in bpf_verifier_state->frame[] array * pointing to bpf_func_state. */ - u32 frameno; + u8 frameno; /* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */ bool precise; }; diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index 6c88ad95b63b..e4ec007f7fa6 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -491,7 +491,8 @@ static bool regs_exact(const struct bpf_reg_state *rold, { return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 && check_ids(rold->id, rcur->id, idmap) && - check_ids(rold->parent_id, rcur->parent_id, idmap); + check_ids(rold->parent_id, rcur->parent_id, idmap) && + check_ids(rold->map_uid, rcur->map_uid, idmap); } enum exact_level { @@ -616,7 +617,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off) && check_ids(rold->id, rcur->id, idmap) && - check_ids(rold->parent_id, rcur->parent_id, idmap); + check_ids(rold->parent_id, rcur->parent_id, idmap) && + check_ids(rold->map_uid, rcur->map_uid, idmap); case PTR_TO_PACKET_META: case PTR_TO_PACKET: /* We must have at least as much range as the old ptr diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 33161dc64568..02be326f235c 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -1864,6 +1864,7 @@ static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm) offsetof(struct bpf_reg_state, var_off) - sizeof(reg->type)); reg->id = 0; reg->parent_id = 0; + reg->map_uid = 0; ___mark_reg_known(reg, imm); } @@ -1925,17 +1926,18 @@ static void refine_map_lookup_value(struct bpf_reg_state *reg) if (map->inner_map_meta) { reg->type = CONST_PTR_TO_MAP | maybe_null; reg->map_ptr = map->inner_map_meta; - /* transfer reg's id which is unique for every map_lookup_elem + /* + * transfer reg's id which is unique for every map_lookup_elem * as UID of the inner map. */ - if (btf_record_has_field(map->inner_map_meta->record, - BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK)) - reg->map_uid = reg->id; + reg->map_uid = reg->id; } else if (map->map_type == BPF_MAP_TYPE_XSKMAP) { reg->type = PTR_TO_XDP_SOCK | maybe_null; + reg->map_uid = 0; } else if (map->map_type == BPF_MAP_TYPE_SOCKMAP || map->map_type == BPF_MAP_TYPE_SOCKHASH) { reg->type = PTR_TO_SOCKET | maybe_null; + reg->map_uid = 0; } } @@ -10048,6 +10050,7 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr; callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* pointer to stack or null */ callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3]; @@ -10144,6 +10147,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; callee->regs[BPF_REG_3].map_uid = map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); @@ -10262,6 +10266,7 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; callee->regs[BPF_REG_3].map_uid = map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); From 04ae4ffc57a6b7a8215779f0e9c536cacda9f761 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:17 +0200 Subject: [PATCH 1254/1317] selftests/bpf: Check callback map value lock identity Add a verifier test which retains a map value from an outer callback and then acquires a lock through an inner callback value before attempting to release the outer callback value. Both values can denote different elements, so the verifier must reject the mismatched unlock. Also exercise callbacks reached through two inner-map lookups. The lookup results share inner_map_meta but may refer to different one-element arrays, so their callback values must retain distinct lock identities. Extend the existing spin_lock failure table and reuse its array and inner-map fixtures to keep these cases alongside the other lock identity tests. Update the nested callback reference-leak expectation for the extra callback value ID. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://patch.msgid.link/20260917233222.2542500-10-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/cb_refs.c | 2 +- .../selftests/bpf/prog_tests/spin_lock.c | 2 + .../selftests/bpf/progs/test_spin_lock_fail.c | 67 ++++++++++++++++++- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c index 78566b817fd7..490e15e7126d 100644 --- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c +++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c @@ -13,7 +13,7 @@ struct { } cb_refs_tests[] = { { "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" }, { "leak_prog", "Possibly NULL pointer passed to helper R2" }, - { "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */ + { "nested_cb", "Unreleased reference id=5 alloc_insn=2" }, /* alloc_insn=2{4,5} */ { "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */ }; diff --git a/tools/testing/selftests/bpf/prog_tests/spin_lock.c b/tools/testing/selftests/bpf/prog_tests/spin_lock.c index 5c3579438427..e368370262c8 100644 --- a/tools/testing/selftests/bpf/prog_tests/spin_lock.c +++ b/tools/testing/selftests/bpf/prog_tests/spin_lock.c @@ -54,6 +54,8 @@ static struct { { "lock_global_sleepable_helper_subprog", "global function calls are not allowed while holding a lock" }, { "lock_global_sleepable_kfunc_subprog", "global function calls are not allowed while holding a lock" }, { "lock_global_sleepable_subprog_indirect", "global function calls are not allowed while holding a lock" }, + { "callback_value_lock_identity", "bpf_spin_unlock of different lock" }, + { "callback_inner_map_value_lock_identity", "bpf_spin_unlock of different lock" }, }; static int match_regex(const char *pattern, const char *string) diff --git a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c index f678ee6bd7ea..55282f20fa32 100644 --- a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c +++ b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c @@ -14,17 +14,18 @@ struct array_map { __type(key, int); __type(value, struct foo); __uint(max_entries, 1); -} array_map SEC(".maps"); +} array_map SEC(".maps"), array_map_b SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS); - __uint(max_entries, 1); + __uint(max_entries, 2); __type(key, int); __type(value, int); __array(values, struct array_map); } map_of_maps SEC(".maps") = { .values = { [0] = &array_map, + [1] = &array_map_b, }, }; @@ -314,4 +315,66 @@ int lock_global_sleepable_subprog_indirect(struct __sk_buff *ctx) return ret; } +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 2); + __type(key, int); + __type(value, struct foo); +} callback_array_map SEC(".maps"); + +struct callback_ctx { + struct foo *value; +}; + +static long lock_different_value(struct bpf_map *map, int *key, + struct foo *value, struct callback_ctx *ctx) +{ + bpf_spin_lock(&value->lock); + bpf_spin_unlock(&ctx->value->lock); + return 0; +} + +static long nest_lock_different_value(struct bpf_map *map, int *key, + struct foo *value, void *data) +{ + struct callback_ctx ctx = { .value = value }; + + bpf_for_each_map_elem(&callback_array_map, lock_different_value, &ctx, 0); + return 0; +} + +SEC("?tc") +int callback_value_lock_identity(void *ctx) +{ + bpf_for_each_map_elem(&callback_array_map, nest_lock_different_value, NULL, 0); + return 0; +} + +static long nest_lock_different_inner_value(struct bpf_map *map, int *key, + struct foo *value, void *data) +{ + struct callback_ctx ctx = { .value = value }; + int inner_key = 1; + void *inner_map; + + inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key); + if (!inner_map) + return 0; + bpf_for_each_map_elem(inner_map, lock_different_value, &ctx, 0); + return 0; +} + +SEC("?tc") +int callback_inner_map_value_lock_identity(void *ctx) +{ + int inner_key = 0; + void *inner_map; + + inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key); + if (!inner_map) + return 0; + bpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0); + return 0; +} + char _license[] SEC("license") = "GPL"; From b4e875d397da451fb4e9c573ff4b86db53caba05 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:18 +0200 Subject: [PATCH 1255/1317] libbpf: Reject truncated ldimm64 CO-RE relocations CO-RE relocation of an ldimm64 instruction operates on two instruction slots. A malformed BPF ELF can end a function after the first slot and attach a CO-RE relocation to it. libbpf allocates the instruction array according to the function symbol size, so the shared relocation code would then access beyond the allocation. Reject a terminal ldimm64 in libbpf's relocation loop, where the program length is available, before resolving or applying the relocation. Both resolved and unresolved relocations validate the absent second slot, and unresolved relocation poisoning would additionally write past the array. The in-kernel caller is protected by the verifier's early instruction-stream check before it applies CO-RE relocations. Fixes: eacaaed784e2 ("libbpf: Implement enum value-based CO-RE relocations") Reported-by: Sashiko Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/20260914140852.03DA21F0089B@smtp.kernel.org Link: https://patch.msgid.link/20260917233222.2542500-11-memxor@gmail.com Signed-off-by: Eduard Zingerman --- tools/lib/bpf/libbpf.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index b749c01742ee..bfa64ae6c94d 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -6206,6 +6206,13 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path) return -EINVAL; insn = &prog->insns[insn_idx]; + if (is_ldimm64_insn(insn) && (size_t)insn_idx + 1 >= prog->insns_cnt) { + pr_warn("prog '%s': relo #%d: insn #%d (LDIMM64) is truncated\n", + prog->name, i, insn_idx); + err = -EINVAL; + goto out; + } + err = record_relo_core(prog, rec, insn_idx); if (err) { pr_warn("prog '%s': relo #%d: failed to record relocation: %s\n", From a363c62a653cc8b3e21da9545fa4e028ef50f9c3 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Sun, 23 Aug 2026 12:40:51 +0800 Subject: [PATCH 1256/1317] mm/hugetlb: do not dissolve gigantic pages without runtime support dissolve_free_hugetlb_folio() doesn't check hstate_is_gigantic_no_runtime(h) though remove_hugetlb_folio()/ update_and_free_hugetlb_folio() silently bail for such folios, so it frees a still-listed folio and, on vmemmap restore failure, the add_hugetlb_folio() rollback corrupts the free list. Link: https://lore.kernel.org/20260823044118.1097121-2-xialonglong2025@163.com Fixes: 6eb4e88a6d27 ("hugetlb: create remove_hugetlb_page() to separate functionality") Signed-off-by: Longlong Xia Signed-off-by: Andrew Morton Assisted-by: Codex:gpt-5.6-sol Acked-by: Muchun Song Cc: David Hildenbrand Cc: Miaohe Lin Cc: Michal Hocko Cc: Oscar Salvador Cc: --- mm/hugetlb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 4f6f58bf3db6..d28972cd33f5 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1967,6 +1967,15 @@ int dissolve_free_hugetlb_folio(struct folio *folio) struct hstate *h = folio_hstate(folio); bool adjust_surplus = false; + /* + * remove_hugetlb_folio()/update_and_free_hugetlb_folio() bail + * for gigantic hstates without runtime support, so dissolving one + * here would leave it on the free list and, on vmemmap restore + * failure, the add_hugetlb_folio() rollback corrupts that list. + */ + if (hstate_is_gigantic_no_runtime(h)) + goto out; + if (!available_huge_pages(h)) goto out; From 88aed0422f39b22406f35f1e758cea25e7bbcfb5 Mon Sep 17 00:00:00 2001 From: Vinay Belgaumkar Date: Fri, 4 Sep 2026 11:16:24 -0700 Subject: [PATCH 1257/1317] perf: Fix null pointer access in is_include_guest_event() A typical module unload occurring event when there is an active perf connection leads to freeing of the pmu pointer. The call log is something like: .. __pmu_detach_event pmu_detach_event pmu_detach_events perf_pmu_unregister .. __pmu_detach_event() sets event->pmu to null. When the perf connection finally is closed, the following stack trace is observed: Oops: general protection fault, kernel NULL pointer dereference ... RIP: 0010:_free_event+0x3e/0x370 ... Call Trace: ... perf_event_release_kernel+0x260/0x2d0 perf_release+0x12/0x20 A call to mediated_pmu_unaccount_event() inside _free_event() is the root cause of this crash. Adding a check inside is_include_guest_event() ensures we don't accidentally access a null pmu ptr. In addition to this, we will now call mediated_pmu_unaccount_event() before clearing the pmu ptr so that nr_include_guest_events counts are maintained correctly. Fixes: eff95e170275 ("perf: Add APIs to create/release mediated guest vPMUs") Assisted-by: Claude:Claude-Sonnet-5 Signed-off-by: Vinay Belgaumkar Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260904181625.1394082-1-vinay.belgaumkar@intel.com --- kernel/events/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index fe33fe15689d..db7b76d6b68a 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -6350,6 +6350,9 @@ static DEFINE_MUTEX(perf_mediated_pmu_mutex); /* !exclude_guest event of PMU with PERF_PMU_CAP_MEDIATED_VPMU */ static inline bool is_include_guest_event(struct perf_event *event) { + if (!event->pmu) + return false; + if ((event->pmu->capabilities & PERF_PMU_CAP_MEDIATED_VPMU) && !event->attr.exclude_guest) return true; @@ -13002,6 +13005,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event, exclusive_event_destroy(event); module_put(pmu->module); + mediated_pmu_unaccount_event(event); event->pmu = NULL; /* force fault instead of UAF */ } From fe3c73d7bc769e7afc252f867a3421fe168b898d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 15 Sep 2026 20:41:01 +0200 Subject: [PATCH 1258/1317] sched/core: Avoid false migration warning for proxy donors Proxy execution can move a blocked donor's scheduling context to the lock owner's CPU even when the donor is migration-disabled. The donor does not execute there, and its original execution CPU remains recorded in wake_cpu. set_task_cpu() warns unconditionally for migration-disabled tasks, so a subsequent proxy migration or the wakeup path returning the donor home triggers a false positive: moving a blocked scheduling context does not violate the migration-disabled execution context. For example, creating a mutex owner on CPU1 and a migration-disabled waiter on CPU0 can trigger the following warning: proxy_migrate_repro: donor blocking on CPU0 with migration disabled proxy_migrate_repro: donor moved from CPU0 to CPU1 WARNING: kernel/sched/core.c:3389 at set_task_cpu+0x1d3/0x280 ... Call Trace: try_to_wake_up+0x43f/0x780 __mutex_unlock_slowpath+0x330/0x540 owner_fn+0x9f/0xc0 [proxy_migrate_repro] ... proxy_migrate_repro: donor woke on CPU0, task_cpu=0 proxy_migrate_repro: completed Exclude blocked proxy donors from the warning. The proxy wakeup path restores an executable placement before clearing the blocked state. Fixes: b049b81bdff6 ("sched: Handle blocked-waiter migration (and return migration)") Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Acked-by: John Stultz Link: https://patch.msgid.link/20260915184101.2621252-1-arighi@nvidia.com --- kernel/sched/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 7885ff76e69f..0b846a13c628 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -3351,6 +3351,8 @@ void relax_compatible_cpus_allowed_ptr(struct task_struct *p) void set_task_cpu(struct task_struct *p, unsigned int new_cpu) { unsigned int state = READ_ONCE(p->__state); + bool proxy_migrated = sched_proxy_exec() && p->is_blocked && + task_cpu(p) != p->wake_cpu; /* * We should never call set_task_cpu() on a blocked task, @@ -3386,7 +3388,12 @@ void set_task_cpu(struct task_struct *p, unsigned int new_cpu) */ WARN_ON_ONCE(!cpu_online(new_cpu)); - WARN_ON_ONCE(is_migration_disabled(p)); + /* + * Proxy execution can move a blocked task's scheduling context to any + * CPU without moving its migration-disabled execution context. The + * wakeup path will return the task to a CPU where it can execute. + */ + WARN_ON_ONCE(is_migration_disabled(p) && !proxy_migrated); trace_sched_migrate_task(p, new_cpu); From 93f53499d0b945e8ae447f497faf743d60069f61 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 17 Sep 2026 16:09:06 -0700 Subject: [PATCH 1259/1317] x86/fred: Reconstruct the #GP context for rejected INT instructions FRED event delivery does not use the IDT, so the gate DPL check that rejects a user INT n falls to software (Intel FRED specification [1], section 8.3). fred_intx() rejects the same vectors as IDT delivery, but reports a zero error code and the IP after the INT. This breaks the signal ABI. Wine uses the error code to recognize INT 0x2d, so the changed context turns a handled breakpoint into an access violation in Elden Ring. Rewind IP using the instruction length in the augmented SS and synthesize the IDT selector error code, (vector << 3) | 2. Set RF in the saved flags, as the CPU does for a #GP fault. Section 5.2.1 defines the saved vector, instruction length and RF state. The supplied length handles prefixes without reading user memory. Limit the changes to already-rejected software interrupts, preserving the accepted INT3, INT4 and enabled INT80 paths and hardware exceptions. With IA32 emulation disabled, INT 0x80 now reports the same #GP as the DPL 0 gate IDT installs there. The rewound IP also stops fixup_iopl_exception() from inspecting the byte after the INT. Also clear the software event flag. Section 6.2.3 specifies that ERETU with this flag and TF set traps before executing any user instruction. A tracer that suppresses SIGSEGV and resumes with TF set expects the next instruction to run first, as after IRET. The sigreturn path clears the same flag for this reason in prevent_single_step_upon_eretu(). [1] Intel Flexible Return and Event Delivery (FRED) Specification, revision 9.0 (346446-009US), sections 5.2.1, 6.2.3 and 8.3. Fixes: 14619d912b65 ("x86/fred: FRED entry/exit and dispatch code") Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/15745 Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/16132 Reported-by: Paul Gofman Signed-off-by: Matthew Schwartz Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: H. Peter Anvin Link: https://cdrdv2.intel.com/v1/dl/getContent/678938 # [1] Link: https://patch.msgid.link/20260917230907.2080792-2-matthew.schwartz@linux.dev --- arch/x86/entry/entry_fred.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/x86/entry/entry_fred.c b/arch/x86/entry/entry_fred.c index fb3594ddf731..854899bfec5d 100644 --- a/arch/x86/entry/entry_fred.c +++ b/arch/x86/entry/entry_fred.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -71,7 +72,15 @@ static noinstr void fred_intx(struct pt_regs *regs) #endif default: - return exc_general_protection(regs, 0); + /* + * Reconstruct the #GP fault state that IDT delivery would produce. + * Clear the software event flag so ERETU with TF set does not trap + * before the resumed instruction. See prevent_single_step_upon_eretu(). + */ + regs->ip -= regs->fred_ss.insnlen; + regs->flags |= X86_EFLAGS_RF; + regs->fred_ss.swevent = 0; + return exc_general_protection(regs, (regs->fred_ss.vector << 3) | 2); } } From 96443a53bc3ef4b67dab0c497878fc8d56f799f3 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 17 Sep 2026 16:09:07 -0700 Subject: [PATCH 1260/1317] selftests/x86: Check signal state for rejected software interrupts Add a test of the signal ABI for INT instructions in both 32-bit and 64-bit processes. Check the signal number, trap number, error code, si_code, si_addr, instruction pointer and RF/TF state against legacy IDT behavior. Include a 15-byte prefixed INT to check that IP uses the hardware instruction length. Exercise both INT3 encodings, INT4, UD2 and HLT to cover the unchanged trap and fault paths. Run each instruction with TF clear and set. Resume at a known NOP after handling the signal and check that single-stepping traps after the NOP. Also drive INT 0x2d under ptrace, which resumes through the fault frame rather than sigreturn and so exposes a stale FRED software event flag. Start from an INT3 stop, whose FRED frame has no software event flag, instead of the syscall frame of raise(SIGSTOP). Single-step into the INT and check that the fault reports its address. Then suppress SIGSEGV and resume at the NOP, once with PTRACE_SINGLESTEP and once with PTRACE_CONT and TF set. Section 6.2.3 of the Intel FRED specification [1] specifies the immediate single-step trap caused by returning with both that flag and TF set. Check that each trap occurs after the NOP, rather than at its address. Report whether the CPU supports FRED, since a pass looks the same on either entry path. INT 0x80 with IA32 emulation disabled and a 64-bit tracer of a 32-bit tracee are not covered. Both variants pass all 29 checks on a non-FRED AMD host and on Panther Lake with FRED enabled and the fix applied. With the same binaries on unpatched Panther Lake, 16 signal-context checks fail and the first ptrace check reports the IP after the INT. The two dependent ptrace resume checks are not reached. [1] Intel Flexible Return and Event Delivery (FRED) Specification, revision 9.0 (346446-009US), section 6.2.3. Signed-off-by: Matthew Schwartz Signed-off-by: Peter Zijlstra (Intel) Link: https://cdrdv2.intel.com/v1/dl/getContent/678938 # [1] Link: https://patch.msgid.link/20260917230907.2080792-3-matthew.schwartz@linux.dev --- tools/testing/selftests/x86/Makefile | 2 +- tools/testing/selftests/x86/int_signal.c | 311 +++++++++++++++++++++++ 2 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/x86/int_signal.c diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index 434065215d12..d478b13cc8d5 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -13,7 +13,7 @@ CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh "$(CC)" trivial_program.c -no-pie) TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \ check_initial_reg_state sigreturn iopl ioperm \ test_vsyscall mov_ss_trap sigtrap_loop \ - syscall_arg_fault fsgsbase_restore sigaltstack + syscall_arg_fault fsgsbase_restore sigaltstack int_signal TARGETS_C_BOTHBITS += nx_stack TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ diff --git a/tools/testing/selftests/x86/int_signal.c b/tools/testing/selftests/x86/int_signal.c new file mode 100644 index 000000000000..22676dac72b5 --- /dev/null +++ b/tools/testing/selftests/x86/int_signal.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Check the signal context for INT instructions with IDT and FRED entry. */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers.h" + +#ifdef __x86_64__ +#define REG_IP REG_RIP +#define USER_IP rip +#define STACK_PTR "%rsp" +#else +#define REG_IP REG_EIP +#define USER_IP eip +#define STACK_PTR "%esp" +#endif + +/* + * Each instruction has normal and single-step entry points. Resume at the + * NOP after handling its signal, then expect a trace trap after that NOP + * when TF is set. Explicit labels avoid assuming the kernel's saved IP. + */ +#define PROBE(name, insn) \ + extern void name(void); \ + extern void name##_tf(void); \ + extern const char name##_end[], name##_step[]; \ + asm(".pushsection .text\n" \ + ".globl " #name "_tf\n" \ + ".type " #name "_tf, @function\n" \ + #name "_tf:\n" \ + "pushf\n" \ + "orl $0x100, (" STACK_PTR ")\n" \ + "popf\n" \ + ".globl " #name "\n" \ + ".type " #name ", @function\n" \ + #name ":\n" insn "\n" \ + ".globl " #name "_end\n" \ + #name "_end:\nnop\n" \ + ".globl " #name "_step\n" \ + #name "_step:\nret\n" \ + ".size " #name ", .-" #name "\n" \ + ".size " #name "_tf, .-" #name "_tf\n" \ + ".popsection\n") + +PROBE(int1, ".byte 0xcd, 0x01"); +PROBE(int29, ".byte 0xcd, 0x29"); +PROBE(int2c, ".byte 0xcd, 0x2c"); +PROBE(int2d, ".byte 0xcd, 0x2d"); +PROBE(prefixed_int2d, ".byte 0x66, 0xcd, 0x2d"); +PROBE(long_int2d, ".fill 13, 1, 0x2e\n.byte 0xcd, 0x2d"); +PROBE(int81, ".byte 0xcd, 0x81"); +PROBE(intff, ".byte 0xcd, 0xff"); +PROBE(short_int3, ".byte 0xcc"); +PROBE(long_int3, ".byte 0xcd, 0x03"); +PROBE(int4, ".byte 0xcd, 0x04"); +PROBE(ud2, ".byte 0x0f, 0x0b"); +PROBE(hlt, ".byte 0xf4"); + +struct test { + const char *name; + void (*run)(void); + void (*run_tf)(void); + const char *end, *step; + int signo, trap, error, ip_offset, flags, code; +}; + +#define TEST(name, sig, trap, error, offset, flags, code) \ + { #name, name, name##_tf, name##_end, name##_step, \ + sig, trap, error, offset, flags, code } + +#define GP(name, error) \ + TEST(name, SIGSEGV, 13, error, 0, X86_EFLAGS_RF, SI_KERNEL) + +static const struct test tests[] = { + GP(int1, 0x00a), + GP(int29, 0x14a), + GP(int2c, 0x162), + GP(int2d, 0x16a), + GP(prefixed_int2d, 0x16a), + GP(long_int2d, 0x16a), + GP(int81, 0x40a), + GP(intff, 0x7fa), + GP(hlt, 0), + TEST(short_int3, SIGTRAP, 3, 0, 1, 0, SI_KERNEL), + TEST(long_int3, SIGTRAP, 3, 0, 2, 0, SI_KERNEL), + TEST(int4, SIGSEGV, 4, 0, 2, 0, SI_KERNEL), + TEST(ud2, SIGILL, 6, 0, 0, X86_EFLAGS_RF, ILL_ILLOPN), +}; + +static const struct test *active; +static volatile sig_atomic_t seen, signo, trap, error, ip_offset, flags; +static volatile sig_atomic_t code, addr_ok, single_step, stepped, step_ok; + +static void handler(int sig, siginfo_t *info, void *context) +{ + ucontext_t *uc = context; + uintptr_t ip = uc->uc_mcontext.gregs[REG_IP]; + uintptr_t start = (uintptr_t)active->run; + uintptr_t end = (uintptr_t)active->end; + + if (seen && single_step && sig == SIGTRAP) { + if (stepped++) { + ksft_print_msg("%s: second trace trap at %#lx\n", + active->name, (unsigned long)ip); + _exit(KSFT_FAIL); + } + step_ok = ip == (uintptr_t)active->step && + uc->uc_mcontext.gregs[REG_TRAPNO] == 1 && + info->si_code == TRAP_TRACE; + uc->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF; + return; + } + + if (seen || ip < start || ip > end) { + ksft_print_msg("%s: unexpected signal %d at %#lx\n", + active->name, sig, (unsigned long)ip); + _exit(KSFT_FAIL); + } + + signo = sig; + trap = uc->uc_mcontext.gregs[REG_TRAPNO]; + error = uc->uc_mcontext.gregs[REG_ERR]; + ip_offset = ip - start; + flags = uc->uc_mcontext.gregs[REG_EFL] & (X86_EFLAGS_RF | X86_EFLAGS_TF); + code = info->si_code; + /* force_sig() reports no address, force_sig_fault() reports the IP. */ + addr_ok = info->si_addr == (code == SI_KERNEL ? NULL : (void *)ip); + seen = 1; + uc->uc_mcontext.gregs[REG_IP] = end; +} + +static void wait_for_child(pid_t child, int *status) +{ + pid_t ret; + + do { + ret = waitpid(child, status, 0); + } while (ret < 0 && errno == EINTR); + if (ret != child) + ksft_exit_fail_perror("waitpid"); +} + +/* Resume the tracee and check where the next stop lands. */ +static bool resume_to(pid_t child, int *status, int request, int sig, + const void *ip, const char *what) +{ + struct user_regs_struct regs; + + if (ptrace(request, child, 0, 0)) + return false; + wait_for_child(child, status); + if (!WIFSTOPPED(*status)) { + ksft_print_msg("%s: tracee did not stop\n", what); + return false; + } + if (WSTOPSIG(*status) != sig) { + ksft_print_msg("%s: stopped with signal %d, expected %d\n", + what, WSTOPSIG(*status), sig); + return false; + } + if (ptrace(PTRACE_GETREGS, child, 0, ®s)) + return false; + if ((unsigned long)regs.USER_IP != (unsigned long)ip) { + ksft_print_msg("%s: stopped at %#lx, expected %#lx\n", what, + (unsigned long)regs.USER_IP, (unsigned long)ip); + return false; + } + return true; +} + +static bool set_ip(pid_t child, const void *ip, bool tf) +{ + struct user_regs_struct regs; + + if (ptrace(PTRACE_GETREGS, child, 0, ®s)) + return false; + regs.USER_IP = (unsigned long)ip; + if (tf) + regs.eflags |= X86_EFLAGS_TF; + return !ptrace(PTRACE_SETREGS, child, 0, ®s); +} + +/* + * Exercise the tracer paths that resume through the fault frame rather than + * sigreturn. A stale FRED software event flag on that frame traps before the + * NOP executes instead of after it. + */ +static void test_ptrace(void) +{ + bool into = false, step = false, cont = false; + pid_t child; + int status; + + child = fork(); + if (child < 0) + ksft_exit_fail_perror("fork"); + if (!child) { + if (ptrace(PTRACE_TRACEME, 0, 0, 0)) + _exit(KSFT_FAIL); + /* Start from a breakpoint frame, not the syscall frame of raise(). */ + asm volatile("int3"); + _exit(KSFT_FAIL); + } + + wait_for_child(child, &status); + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) + goto out; + if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_EXITKILL)) + goto out; + + /* Single-step into the INT. The fault must report the INT's address. */ + if (!set_ip(child, int2d, false)) + goto out; + into = resume_to(child, &status, PTRACE_SINGLESTEP, SIGSEGV, int2d, + "single-step into INT"); + if (!into) + goto out; + + /* Suppress SIGSEGV and single-step the NOP. */ + if (!set_ip(child, int2d_end, false)) + goto out; + step = resume_to(child, &status, PTRACE_SINGLESTEP, SIGTRAP, int2d_step, + "single-step after INT"); + if (!step) + goto out; + + /* Fault again, then suppress SIGSEGV and continue with TF set. */ + if (!set_ip(child, int2d, false)) + goto out; + if (!resume_to(child, &status, PTRACE_CONT, SIGSEGV, int2d, + "continue to INT")) + goto out; + if (!set_ip(child, int2d_end, true)) + goto out; + cont = resume_to(child, &status, PTRACE_CONT, SIGTRAP, int2d_step, + "continue with TF after INT"); +out: + if (WIFSTOPPED(status)) { + kill(child, SIGKILL); + wait_for_child(child, &status); + } + ksft_test_result(into, "ptrace single-step into INT faults at the INT\n"); + ksft_test_result(step, "ptrace single-step after suppressing SIGSEGV\n"); + ksft_test_result(cont, "ptrace continue with TF after suppressing SIGSEGV\n"); +} + +static bool cpu_has_fred(void) +{ + unsigned int eax, ebx, ecx, edx; + + if (__get_cpuid_max(0, NULL) < 7) + return false; + __cpuid_count(7, 1, eax, ebx, ecx, edx); + return eax & (1 << 17); +} + +int main(void) +{ + unsigned int i, tf; + int expected_flags, ok; + + ksft_print_header(); + ksft_set_plan(2 * ARRAY_SIZE(tests) + 3); + ksft_print_msg("CPU %s FRED\n", cpu_has_fred() ? "supports" : "lacks"); + sethandler(SIGSEGV, handler, 0); + sethandler(SIGTRAP, handler, 0); + sethandler(SIGILL, handler, 0); + + for (tf = 0; tf < 2; tf++) { + for (i = 0; i < ARRAY_SIZE(tests); i++) { + active = &tests[i]; + single_step = tf; + seen = signo = trap = error = ip_offset = flags = 0; + code = addr_ok = stepped = step_ok = 0; + expected_flags = active->flags | (tf ? X86_EFLAGS_TF : 0); + if (tf) + active->run_tf(); + else + active->run(); + + ok = seen && signo == active->signo && trap == active->trap && + error == active->error && ip_offset == active->ip_offset && + flags == expected_flags && code == active->code && addr_ok && + (!tf || (stepped && step_ok)); + ksft_test_result(ok, "%s%s\n", active->name, tf ? " with TF" : ""); + if (!ok) { + ksft_print_msg("got signal=%d trap=%d error=%#x ip=%d\n", + signo, trap, error, ip_offset); + ksft_print_msg("got flags=%#x code=%d addr_ok=%d step_ok=%d\n", + flags, code, addr_ok, step_ok); + ksft_print_msg("expected signal=%d trap=%d error=%#x ip=%d\n", + active->signo, active->trap, active->error, + active->ip_offset); + ksft_print_msg("expected flags=%#x code=%d\n", + expected_flags, active->code); + } + } + } + test_ptrace(); + ksft_finished(); +} From 406aa2b186d3f13a35bc1ad6aff4274917851bc4 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Tue, 15 Sep 2026 13:24:22 +0100 Subject: [PATCH 1261/1317] perf/arm-cmn: Fix multi-filter encoding The current special-case for EVICT_STATE_SEL filtering effectively assigns the "filter" and "filter2" controls in the opposite order from how the CMN S3 r2 TRM states "Filtering is programmed in pmu_hbt_lbt_sel and pmu_evict_state_sel". On reflection, not only does this seem unnecessarily non-obvious to users, but it's also likely to be a problem for scaling to a full multi-filter abstraction in future. There is a logical order to filters based on their bitfield positions in the pmu_event_sel register, which the TRM descriptions allude to, and the cmn_filter_select enum already (almost) follows, so let's fix the UABI to follow suit while it's still unreleased. Fixes: 09178f536bb9 ("perf/arm-cmn: Plumb in new filter types") Signed-off-by: Robin Murphy Reviewed-by: Ilkka Koskinen Reviewed-by: Leo Yan Signed-off-by: Will Deacon --- drivers/perf/arm-cmn.c | 58 +++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index 33ee2be9b386..5378fba916cf 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -305,9 +305,9 @@ enum cmn_filter_select { SEL_NONE, SEL_OCCUP1_ID, SEL_CLASS_OCCUP_ID, - SEL_CBUSY_SNTHROTTLE_SEL, SEL_HBT_LBT_SEL, SEL_SN_HOME_SEL, + SEL_CBUSY_SNTHROTTLE_SEL, SEL_SNP_VC_SEL, SEL_ENHANCED_HBT_LBT_SEL, SEL_EVICT_STATE_SEL, @@ -978,10 +978,14 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj, _CMN_EVENT_HNS(_model, _name##_all, _event, _sel, 0), \ _CMN_EVENT_HNS(_model, _name##_hbt, _event, _sel, 1), \ _CMN_EVENT_HNS(_model, _name##_lbt, _event, _sel, 2) -#define _CMN_EVENT_HNS_HBT2(_model, _name, _event, _fsel1, f1) \ - _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 0), \ - _CMN_EVENT_HNS(_model, _name##_hbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 1), \ - _CMN_EVENT_HNS(_model, _name##_lbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 2) +#define _CMN_EVENT_HNS_EVICT(_model, _name, _event, _fsel1, f1) \ + _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 0), \ + _CMN_EVENT_HNS(_model, _name##_eu, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 1), \ + _CMN_EVENT_HNS(_model, _name##_en, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 2), \ + _CMN_EVENT_HNS(_model, _name##_su, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 3), \ + _CMN_EVENT_HNS(_model, _name##_sn, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 4), \ + _CMN_EVENT_HNS(_model, _name##_mu, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 5), \ + _CMN_EVENT_HNS(_model, _name##_mn, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 6) #define CMN_EVENT_HNS_OCC(_model, _name, _event) \ CMN_EVENT_HN_OCC(_model, hns_##_name, CMN_TYPE_HNS, _event), \ @@ -1014,13 +1018,9 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj, _CMN_EVENT_HNS(CMNS3R2, _name##_ccglcn, _event, SEL_ENHANCED_HBT_LBT_SEL, 5), \ _CMN_EVENT_HNS(CMNS3R2, _name##_ccgrn, _event, SEL_ENHANCED_HBT_LBT_SEL, 6) #define CMN_EVENT_HNS_EVICT(_model, _name, _event) \ - _CMN_EVENT_HNS_HBT2(_model, _name##_all, _event, SEL_EVICT_STATE_SEL, 0), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_eu, _event, SEL_EVICT_STATE_SEL, 1), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_en, _event, SEL_EVICT_STATE_SEL, 2), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_su, _event, SEL_EVICT_STATE_SEL, 3), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_sn, _event, SEL_EVICT_STATE_SEL, 4), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_mu, _event, SEL_EVICT_STATE_SEL, 5), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_mn, _event, SEL_EVICT_STATE_SEL, 6) + _CMN_EVENT_HNS_EVICT(_model, _name##_all, _event, SEL_HBT_LBT_SEL, 0), \ + _CMN_EVENT_HNS_EVICT(_model, _name##_hbt, _event, SEL_HBT_LBT_SEL, 1), \ + _CMN_EVENT_HNS_EVICT(_model, _name##_lbt, _event, SEL_HBT_LBT_SEL, 2) #define CMN_EVENT_HNSR0_HBT(_name, _event) \ _CMN_EVENT_HNS_HBT(CMN700 | CMNS3R01, _name, _event, SEL_HBT_LBT_SEL) @@ -1860,10 +1860,12 @@ static void arm_cmn_val_add_event(struct arm_cmn *cmn, struct arm_cmn_val *val, val->dtm_count[dtm]++; + if (sel == SEL_EVICT_STATE_SEL) { + val->filter[dtm][sel] = CMN_EVENT_FILTER2(event) + 1; + sel = SEL_HBT_LBT_SEL; + } if (sel) val->filter[dtm][sel] = CMN_EVENT_FILTER(event) + 1; - if (sel == SEL_EVICT_STATE_SEL) - val->filter[dtm][SEL_HBT_LBT_SEL] = CMN_EVENT_FILTER2(event) + 1; if (type != CMN_TYPE_WP) continue; @@ -1914,14 +1916,17 @@ static int arm_cmn_validate_group(struct arm_cmn *cmn, struct perf_event *event) if (val->dtm_count[dtm] == CMN_DTM_NUM_COUNTERS) goto done; + if (sel == SEL_EVICT_STATE_SEL) { + if (val->filter[dtm][sel] && + val->filter[dtm][sel] != CMN_EVENT_FILTER2(event) + 1) + goto done; + sel = SEL_HBT_LBT_SEL; + } + if (sel && val->filter[dtm][sel] && val->filter[dtm][sel] != CMN_EVENT_FILTER(event) + 1) goto done; - if (sel == SEL_EVICT_STATE_SEL && val->filter[dtm][SEL_HBT_LBT_SEL] && - val->filter[dtm][SEL_HBT_LBT_SEL] != CMN_EVENT_FILTER2(event) + 1) - goto done; - if (type != CMN_TYPE_WP) continue; @@ -1950,7 +1955,7 @@ static enum cmn_filter_select arm_cmn_event_filter(const struct arm_cmn *cmn, for (int i = 0; i < ARRAY_SIZE(arm_cmn_event_attrs) - 1; i++) { e = container_of(arm_cmn_event_attrs[i], typeof(*e), attr.attr); if (e->model & model && e->type == type && e->eventid == eventid) - return e->filter[0].sel; + return e->filter[1].sel ?: e->filter[0].sel; } return SEL_NONE; } @@ -2081,16 +2086,21 @@ static void arm_cmn_event_clear(struct arm_cmn *cmn, struct perf_event *event, static int arm_cmn_set_event_filter(struct arm_cmn_node *dn, struct perf_event *event) { enum cmn_filter_select fsel = to_cmn_hw(event)->filter_sel; + bool evict_state = fsel == SEL_EVICT_STATE_SEL; int ret = 0; + if (evict_state) { + ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER2(event)); + if (ret) + return ret; + fsel = SEL_HBT_LBT_SEL; + } if (fsel) ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER(event)); - if (fsel == SEL_EVICT_STATE_SEL && !ret) { - ret = arm_cmn_set_event_sel_hi(dn, SEL_HBT_LBT_SEL, CMN_EVENT_FILTER2(event)); - if (ret) - dn->filter[fsel].count--; - } + if (ret && evict_state) + dn->filter[SEL_EVICT_STATE_SEL].count--; + return ret; } From bb756b11ad63832ebee58caf9e8f9381eaecff9f Mon Sep 17 00:00:00 2001 From: Zeng Heng Date: Fri, 11 Sep 2026 09:58:59 +0800 Subject: [PATCH 1262/1317] arm64: io: Reject non-user protection in ioremap_prot() Mapping a stack-top page via /dev/mem with PROT_NONE and then reading that process's /proc//cmdline triggers a spurious WARN in ioremap_prot() through generic_access_phys(): WARNING: ./arch/arm64/include/asm/io.h:275 at generic_access_phys Call trace: generic_access_phys+0x1c8/0x228 (P) __access_remote_vm+0x2b4/0x398 access_remote_vm+0x14/0x30 get_mm_cmdline+0xf8/0x2a0 proc_pid_cmdline_read+0x68/0x120 generic_access_phys() passes the protection derived from the user PTE to ioremap_prot(). On arm64, a PROT_NONE mapping is represented by a present-invalid PTE, so pte_present() still returns true and the protection reaches ioremap_prot(). A PROT_NONE mapping does not have PTE_USER, causing the existing WARN_ON_ONCE() in ioremap_prot() to fire even though this is a valid user mapping. Execute-only mappings have the same issue and must not be readable through this path either. ioremap_prot() should therefore reject protection values without PTE_USER without warning. This makes the access fail cleanly for PROT_NONE and execute-only mappings while retaining the existing user-protection contract. Fixes: 8f098037139b ("arm64: io: Extract user memory type in ioremap_prot()") Signed-off-by: Zeng Heng Reviewed-by: Catalin Marinas Signed-off-by: Will Deacon --- arch/arm64/include/asm/io.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h index 49a7002661a9..9aa0bb08ab60 100644 --- a/arch/arm64/include/asm/io.h +++ b/arch/arm64/include/asm/io.h @@ -280,7 +280,8 @@ static inline void __iomem *ioremap_prot(phys_addr_t phys, size_t size, pgprot_t prot; ptval_t user_prot_val = pgprot_val(user_prot); - if (WARN_ON_ONCE(!(user_prot_val & PTE_USER))) + /* Reject PROT_NONE and exec-only */ + if (!(user_prot_val & PTE_USER)) return NULL; prot = __pgprot_modify(PAGE_KERNEL, PTE_ATTRINDX_MASK, From baaa9b126b875b40ffd9356da52c7c871917e5bb Mon Sep 17 00:00:00 2001 From: Yureka Lilian Date: Fri, 11 Sep 2026 19:02:10 +0200 Subject: [PATCH 1263/1317] arm64: Add override for WFxT Add an override for WFxT support within ID_AA64ISAR2_EL1 to allow it to be disabled using the new arm64.nowfxt command line parameter. This accompanies the idle=nop param introduced in a previous patch series [1] in dealing with misbehaving WFI and WFIT instructions on Apple Silicon SoCs, and eases debugging of other quirky WFxT implementations. Link[1]: https://lore.kernel.org/all/20260804-arm64-idle-param-v3-1-d10f8159062b@cyberchaos.dev/ Suggested-by: Will Deacon Signed-off-by: Yureka Lilian Signed-off-by: Will Deacon --- Documentation/admin-guide/kernel-parameters.txt | 3 +++ arch/arm64/kernel/pi/idreg-override.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd2..0fe9e36ce61a 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -589,6 +589,9 @@ Kernel parameters arm64.nosve [ARM64] Unconditionally disable Scalable Vector Extension support + arm64.nowfxt [ARM64] Unconditionally disable Wait For Event with + Timeout and Wait For Interrupt with Timeout support + ataflop= [HW,M68k] atarimouse= [HW,MOUSE] Atari Mouse diff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c index 274bc72ba98d..acaf56234973 100644 --- a/arch/arm64/kernel/pi/idreg-override.c +++ b/arch/arm64/kernel/pi/idreg-override.c @@ -186,6 +186,7 @@ static const struct ftr_set_desc isar2 __prel64_initconst = { .name = "id_aa64isar2", .override = &id_aa64isar2_override, .fields = { + FIELD("wfxt", ID_AA64ISAR2_EL1_WFxT_SHIFT, NULL), FIELD("gpa3", ID_AA64ISAR2_EL1_GPA3_SHIFT, NULL), FIELD("apa3", ID_AA64ISAR2_EL1_APA3_SHIFT, NULL), FIELD("mops", ID_AA64ISAR2_EL1_MOPS_SHIFT, NULL), @@ -254,6 +255,7 @@ static const struct { "id_aa64isar1.api=0 id_aa64isar1.apa=0 " "id_aa64isar2.gpa3=0 id_aa64isar2.apa3=0" }, { "arm64.nomops", "id_aa64isar2.mops=0" }, + { "arm64.nowfxt", "id_aa64isar2.wfxt=0" }, { "arm64.nomte", "id_aa64pfr1.mte=0" }, { "nokaslr", "arm64_sw.nokaslr=1" }, { "rodata=off", "arm64_sw.rodataoff=1" }, From b7403afb7a5f85073243df10238b3483958ad69e Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 6 Sep 2026 13:14:16 +0100 Subject: [PATCH 1264/1317] arm64: errata: match the target implementation CPU's own MIDR __is_affected_midr_range() is handed the MIDR and REVIDR of one target implementation CPU, but tests the erratum's range with is_midr_in_range(), which re-scans all of target_impl_cpus[] and ignores the @midr argument. The range test is thus constant across the per-CPU loop in is_affected_midr_range() and only answers "is any target CPU in range". Since just the fixed_revs REVIDR check uses the iteration's own registers, an out-of-range target CPU can decide whether a MIDR_FIXED() exemption applies. A VM then enables a workaround whose only in-range CPU is fixed silicon, e.g. erratum 2658417 on a Cortex-A510 r1p1 with REVIDR_EL1[25] set. Factor the range test into __is_midr_in_range(), which takes an explicit MIDR, and use it in __is_affected_midr_range(). Fixes: 86edf6bdcf05 ("smccc/kvm_guest: Enable errata based on implementation CPUs") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Will Deacon --- arch/arm64/kernel/cpu_errata.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index b33dccfafaf8..8ec47d89b45b 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -28,18 +28,21 @@ bool cpu_errata_set_target_impl(u64 num, void *impl_cpus) return true; } +static inline bool __is_midr_in_range(u32 midr, struct midr_range const *range) +{ + return midr_is_cpu_model_range(midr, range->model, + range->rv_min, range->rv_max); +} + static inline bool is_midr_in_range(struct midr_range const *range) { int i; if (!target_impl_cpu_num) - return midr_is_cpu_model_range(read_cpuid_id(), range->model, - range->rv_min, range->rv_max); + return __is_midr_in_range(read_cpuid_id(), range); for (i = 0; i < target_impl_cpu_num; i++) { - if (midr_is_cpu_model_range(target_impl_cpus[i].midr, - range->model, - range->rv_min, range->rv_max)) + if (__is_midr_in_range(target_impl_cpus[i].midr, range)) return true; } return false; @@ -59,7 +62,7 @@ __is_affected_midr_range(const struct arm64_cpu_capabilities *entry, u32 midr, u32 revidr) { const struct arm64_midr_revidr *fix; - if (!is_midr_in_range(&entry->midr_range)) + if (!__is_midr_in_range(midr, &entry->midr_range)) return false; midr &= MIDR_REVISION_MASK | MIDR_VARIANT_MASK; From 4485a01f4df1c9683d8ffe3e4ade6c33c9572d3c Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sun, 13 Sep 2026 15:12:27 +0100 Subject: [PATCH 1265/1317] parisc: unwind: Replace open-coded binary search with bsearch() There is a bug in the binary search where hi can underflow. If addr is less than the first entry, then "hi = mid - 1" will underflow to ULONG_MAX. Then we have an out-of-bounds read. Replace the open-coded binary search with bsearch(). Issue found by an LLM. Signed-off-by: Sean Young Signed-off-by: Helge Deller --- arch/parisc/kernel/unwind.c | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/arch/parisc/kernel/unwind.c b/arch/parisc/kernel/unwind.c index 32103a270a8e..fab9ae22191a 100644 --- a/arch/parisc/kernel/unwind.c +++ b/arch/parisc/kernel/unwind.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -49,27 +50,23 @@ static DEFINE_SPINLOCK(unwind_lock); static struct unwind_table kernel_unwind_table __ro_after_init; static LIST_HEAD(unwind_tables); +static int cmp_unwind_entry(const void *key, const void *elt) +{ + unsigned long addr = (unsigned long)key; + const struct unwind_table_entry *e = elt; + + if (addr < e->region_start) + return -1; + if (addr > e->region_end) + return 1; + return 0; +} + static inline const struct unwind_table_entry * find_unwind_entry_in_table(const struct unwind_table *table, unsigned long addr) { - const struct unwind_table_entry *e = NULL; - unsigned long lo, hi, mid; - - lo = 0; - hi = table->length - 1; - - while (lo <= hi) { - mid = (hi - lo) / 2 + lo; - e = &table->table[mid]; - if (addr < e->region_start) - hi = mid - 1; - else if (addr > e->region_end) - lo = mid + 1; - else - return e; - } - - return NULL; + return bsearch((void *)addr, table->table, table->length, + sizeof(*table->table), cmp_unwind_entry); } static const struct unwind_table_entry * From cb917b1e1c23f6f9b73802cd576b48bd606458c0 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Thu, 17 Sep 2026 20:04:14 -0700 Subject: [PATCH 1266/1317] parisc: remove unused header The last use of arch/parisc/include/asm/compat_ucontext.h was removed in commit d313d4e72df3 ("parisc: remove unused compat_rt_sigframe.h header") but it was left behind. Remove it. Signed-off-by: Ethan Nelson-Moore Signed-off-by: Helge Deller --- arch/parisc/include/asm/compat_ucontext.h | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 arch/parisc/include/asm/compat_ucontext.h diff --git a/arch/parisc/include/asm/compat_ucontext.h b/arch/parisc/include/asm/compat_ucontext.h deleted file mode 100644 index c606f1bc891d..000000000000 --- a/arch/parisc/include/asm/compat_ucontext.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _ASM_PARISC_COMPAT_UCONTEXT_H -#define _ASM_PARISC_COMPAT_UCONTEXT_H - -#include - -/* 32-bit ucontext as seen from an 64-bit kernel */ -struct compat_ucontext { - compat_uint_t uc_flags; - compat_uptr_t uc_link; - compat_stack_t uc_stack; /* struct compat_sigaltstack (12 bytes)*/ - /* FIXME: Pad out to get uc_mcontext to start at an 8-byte aligned boundary */ - compat_uint_t pad[1]; - struct compat_sigcontext uc_mcontext; - compat_sigset_t uc_sigmask; /* mask last for extensibility */ -}; - -#endif /* !_ASM_PARISC_COMPAT_UCONTEXT_H */ From 289e99e7a263c6fd6a5d07d6d8f2156b70f3a9d5 Mon Sep 17 00:00:00 2001 From: Zhenghui Hao Date: Fri, 18 Sep 2026 13:46:49 +0800 Subject: [PATCH 1267/1317] parisc: parse early parameters in setup_arch() parisc is one of the few architectures that does not call parse_early_param() from setup_arch(). That was mostly harmless until commit d49004c5f0c1 ("arch, mm: consolidate initialization of nodes, zones and memory map") moved the consumer of several hugetlb command line parameters into mm_core_init_early(), which runs before the generic parse_early_param() call in start_kernel(). As a result hugepages=, hugepagesz=, default_hugepagesz=, hugetlb_cma= and hugetlb_free_vmemmap= are recorded after they have already been consumed and are silently dropped on parisc. Call parse_early_param() from setup_arch(), after the command line has been set up and the memory inventory has been taken. jump_label_init() must be called first because early parameter handlers may enable or disable static keys. Both functions are safe to call more than once: the generic calls in start_kernel() remain in place and turn into no-ops. Suggested-by: Mike Rapoport (Microsoft) Fixes: d49004c5f0c1 ("arch, mm: consolidate initialization of nodes, zones and memory map") Cc: Signed-off-by: Zhenghui Hao Tested-by: Helge Deller Signed-off-by: Helge Deller --- arch/parisc/kernel/setup.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/arch/parisc/kernel/setup.c b/arch/parisc/kernel/setup.c index d3e17a7a8901..4d3015a411d6 100644 --- a/arch/parisc/kernel/setup.c +++ b/arch/parisc/kernel/setup.c @@ -132,6 +132,18 @@ void __init setup_arch(char **cmdline_p) parisc_cache_init(); paging_init(); + /* + * Parse early parameters before mm_core_init_early() runs. + * Several early_param() handlers only record data that is consumed + * from there - for example hugepages=, hugepagesz=, + * default_hugepagesz=, hugetlb_cma= and hugetlb_free_vmemmap= - so + * the generic parse_early_param() call in start_kernel() is too late + * for them. jump_label_init() must come first, since early param + * handlers may enable or disable static keys. + */ + jump_label_init(); + parse_early_param(); + #ifdef CONFIG_PA11 dma_ops_init(); #endif From 4dd1999783d7d12434006289338373e49492dc96 Mon Sep 17 00:00:00 2001 From: Alexey Klimov Date: Thu, 17 Sep 2026 10:16:42 +0200 Subject: [PATCH 1268/1317] soc: samsung: exynos-pmu: fix use-after-free of interrupt generator node The setup_cpuhp_and_cpuidle() parses the device tree node for the interrupt generation block via of_parse_phandle() and decrements its reference count using of_node_put() immediately after fetching the resource address. However, later the intr_gen_node pointer is passed into of_syscon_register_regmap(). Fix this by declaring intr_gen_node with __free() and removing of_node_put(). Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260513-exynos850-cpuhotplug-v4-0-54fec5f65362@linaro.org?part=3 Fixes: 78b72897a5c8 ("soc: samsung: exynos-pmu: Enable CPU Idle for gs101") Cc: stable@vger.kernel.org Signed-off-by: Alexey Klimov Link: https://patch.msgid.link/20260828-exynos-pmu-cpuhp-idle-fixes-v2-1-06bce6107bd6@linaro.org Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260917081641.72291-2-krzk@kernel.org Signed-off-by: Arnd Bergmann --- drivers/soc/samsung/exynos-pmu.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c index f5fcdde9750e..efccdd63e40e 100644 --- a/drivers/soc/samsung/exynos-pmu.c +++ b/drivers/soc/samsung/exynos-pmu.c @@ -409,13 +409,12 @@ static struct notifier_block exynos_cpupm_reboot_nb = { static int setup_cpuhp_and_cpuidle(struct device *dev) { - struct device_node *intr_gen_node; + struct device_node *intr_gen_node __free(device_node) = + of_parse_phandle(dev->of_node, "google,pmu-intr-gen-syscon", 0); struct resource intrgen_res; void __iomem *virt_addr; int ret, cpu; - intr_gen_node = of_parse_phandle(dev->of_node, - "google,pmu-intr-gen-syscon", 0); if (!intr_gen_node) { /* * To maintain support for older DTs that didn't specify syscon @@ -431,8 +430,6 @@ static int setup_cpuhp_and_cpuidle(struct device *dev) * syscon provided regmap. */ ret = of_address_to_resource(intr_gen_node, 0, &intrgen_res); - of_node_put(intr_gen_node); - virt_addr = devm_ioremap(dev, intrgen_res.start, resource_size(&intrgen_res)); if (!virt_addr) From 7cb575b71ab98194d2e040bded3a7281e089c5ed Mon Sep 17 00:00:00 2001 From: Li Jun Date: Thu, 17 Sep 2026 09:37:10 +0800 Subject: [PATCH 1269/1317] watchdog: da9063: fix suspend/resume handling of HW_RUNNING watchdog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit da9063_wdt_suspend() and da9063_wdt_resume() only check watchdog_active(), when the watchdog is left running by the driver sets WDOG_HW_RUNNING in da9063_wdt_probe() but userspace never opens the device, so WDOG_ACTIVE remains cleared, the wdt_disable() will not be executed in da9063_wdt_suspend. In this case, the suspend callback is a no-op and the watchdog keeps counting during system suspend, leading to an unexpected system reset. Check WDOG_HW_RUNNING and wdd,can fix this issue. Fixes: a7ceca4398bc8 ("watchdog: da9063: optionally disable watchdog during suspend") Cc: stable@vger.kernel.org Signed-off-by: Li Jun Link: https://patch.msgid.link/20260917013710.2754679-1-lijun01@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/da9063_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c index 92e1b78ff481..3703110e82fc 100644 --- a/drivers/watchdog/da9063_wdt.c +++ b/drivers/watchdog/da9063_wdt.c @@ -271,7 +271,7 @@ static int da9063_wdt_suspend(struct device *dev) if (!da9063->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9063_wdt_stop(wdd); return 0; @@ -285,7 +285,7 @@ static int da9063_wdt_resume(struct device *dev) if (!da9063->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9063_wdt_start(wdd); return 0; From 8c7fdc0b4c64d6583fff3e8f7696237a16ff7c29 Mon Sep 17 00:00:00 2001 From: Joshua Hahn Date: Wed, 2 Sep 2026 12:45:20 -0700 Subject: [PATCH 1270/1317] selftests/cgroup: account for zswap shrinker writeback The test_no_invasive_cgroup_shrink selftest checks that when a cgroup has zswapped out more memory than memory.zswap.max, it does not trigger writeback for other cgroups. To do this, it compares the writeback count in a control cgroup and makes sure that it is 0, and then checks the writeback count in an aggressor cgroup who does expect to see writeback. However, when the zswap shrinker is enabled, the victim cgroup can see legitimate writebacks not triggered by the aggressor. In some Meta CI tests, we have seen this failure mode happen. Instead of checking that the victim cgroup has 0 writeback, compare the writeback values before and after the aggressor runs and check that the victim cgroup did not perform any additional writeback. Note that this can still lead to probabilistic failures if writebacks take longer than 5 seconds, but this should fix the systematic failure case and make "not ok test_no_invasive_cgroup_shrink" less likely. Link: https://lore.kernel.org/20260902194521.3652178-1-joshua.hahnjy@gmail.com Fixes: b5ba474f3f51 ("zswap: shrink zswap pool based on memory pressure") Signed-off-by: Joshua Hahn Signed-off-by: Andrew Morton Reported-by: Krush Chavan Suggested-by: Nhat Pham Cc: --- tools/testing/selftests/cgroup/test_zswap.c | 28 +++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c index 609c48f38524..8df54b59513a 100644 --- a/tools/testing/selftests/cgroup/test_zswap.c +++ b/tools/testing/selftests/cgroup/test_zswap.c @@ -20,6 +20,7 @@ static int page_size; #define PATH_ZSWAP "/sys/module/zswap" #define PATH_ZSWAP_ENABLED "/sys/module/zswap/parameters/enabled" +#define PATH_ZSWAP_SHRINKER_ENABLED "/sys/module/zswap/parameters/shrinker_enabled" #define PATH_ZSWAP_STORED_PAGES "/sys/kernel/debug/zswap/stored_pages" static int read_int(const char *path, size_t *value) @@ -444,6 +445,16 @@ static int test_zswap_writeback_disabled(const char *root) return test_zswap_writeback(root, false); } +static bool zswap_shrinker_enabled(void) +{ + char value[2]; + + if (read_text(PATH_ZSWAP_SHRINKER_ENABLED, value, sizeof(value)) <= 0) + return 0; + + return value[0] == 'Y'; +} + /* * When trying to store a memcg page in zswap, if the memcg hits its memory * limit in zswap, writeback should affect only the zswapped pages of that @@ -453,6 +464,7 @@ static int test_no_invasive_cgroup_shrink(const char *root) { int ret = KSFT_FAIL; unsigned int off; + long zswpwb_before, zswpwb_after, zswpwb_target; size_t allocation_size = page_size * 1024; unsigned int nr_pages = allocation_size / page_size; char zswap_max_buf[32], mem_max_buf[32]; @@ -488,6 +500,14 @@ static int test_no_invasive_cgroup_shrink(const char *root) if (cg_read_key_long(zw_group, "memory.stat", "zswapped") < 1) goto out; + /* If the shrinker is enabled, try to let the writebacks finish first */ + if (zswap_shrinker_enabled()) + sleep(5); + + zswpwb_before = get_cg_wb_count(zw_group); + if (zswpwb_before < 0) + goto out; + /* Push wb_group memory into zswap with hard-to-compress data to trigger wb */ if (cg_enter_current(wb_group)) goto out; @@ -500,9 +520,13 @@ static int test_no_invasive_cgroup_shrink(const char *root) getrandom(&wb_allocation[off], page_size/4, 0); } - /* Verify that only zswapped memory from gwb_group has been written back */ - if (wait_for_writeback(wb_group, 5000) > 0 && get_cg_wb_count(zw_group) == 0) + /* Verify that only zswapped memory from wb_group has been written back */ + zswpwb_target = wait_for_writeback(wb_group, 5000); + zswpwb_after = get_cg_wb_count(zw_group); + + if (zswpwb_target > 0 && zswpwb_before == zswpwb_after) ret = KSFT_PASS; + out: cg_enter_current(root); if (zw_group) { From 8d50c2f37bcc766cd5ecde9bbb14c9c27c609f33 Mon Sep 17 00:00:00 2001 From: Haowen Bai Date: Thu, 3 Sep 2026 21:28:54 +0800 Subject: [PATCH 1271/1317] mailmap: update Haowen Bai's email address Map Haowen Bai's former Meizu and current Ugreen addresses to baihaowen88@gmail.com as the canonical public address. Link: https://lore.kernel.org/20260903132854.1930923-1-calvin.bai@ugreen.com Signed-off-by: Haowen Bai Signed-off-by: Andrew Morton --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 90ff4831f592..59518b814b4f 100644 --- a/.mailmap +++ b/.mailmap @@ -355,6 +355,8 @@ Hans Verkuil Hans Verkuil Hans Verkuil Hao Ge +Haowen Bai +Haowen Bai Harry Yoo <42.hyeyoo@gmail.com> Harry Yoo Heiko Carstens From 525c0edc032b3297d0c1056cf1fa20cf1f9e6184 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 4 Sep 2026 10:37:51 +0800 Subject: [PATCH 1272/1317] ocfs2: make ocfs2_calc_xattr_init() return void ocfs2_calc_xattr_init() used to read the default ACL off the parent inode itself, so it could return an error from ocfs2_xattr_get_nolock(). Commit bd7c05fb4a47 ("ocfs2: fix circular locking dependency in ocfs2_init_acl()") moved that lookup before the transaction starts and deleted the error path, but left the now vestigial 'int ret = 0' declaration and both 'return ret' statements behind, along with an unreachable error branch in ocfs2_mknod(). Drop the leftover variable and convert the return type to void, so the callee states that it always succeeds and the caller no longer carries a check that can never trigger. No functional change. Link: https://lore.kernel.org/20260904023751.3703334-1-joseph.qi@linux.alibaba.com Fixes: bd7c05fb4a47 ("ocfs2: fix circular locking dependency in ocfs2_init_acl()") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609040247.8B3lmoqX-lkp@intel.com/ Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao --- fs/ocfs2/namei.c | 9 ++------- fs/ocfs2/xattr.c | 13 +++++-------- fs/ocfs2/xattr.h | 8 ++++---- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index e9c7774ccf91..58c6061ed983 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -336,13 +336,8 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, goto leave; /* calculate meta data/clusters for setting security and acl xattr */ - status = ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, - &xattr_credits, &want_meta, - &acl_state); - if (status < 0) { - mlog_errno(status); - goto leave; - } + ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, &xattr_credits, + &want_meta, &acl_state); /* Reserve a cluster if creating an extent based directory. */ if (S_ISDIR(mode) && !ocfs2_supports_inline_data(osb)) { diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 35bcbb0ff607..bfafe059bedf 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -635,12 +635,11 @@ int ocfs2_calc_security_init(struct inode *dir, return ret; } -int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, - struct ocfs2_security_xattr_info *si, - int *want_clusters, int *xattr_credits, - int *want_meta, struct ocfs2_acl_state *acl_state) +void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state) { - int ret = 0; struct ocfs2_super *osb = OCFS2_SB(dir->i_sb); int s_size = 0, a_size = 0, acl_len = 0, new_clusters; @@ -662,7 +661,7 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, } if (!(s_size + a_size)) - return ret; + return; /* * The max space of security xattr taken inline is @@ -728,8 +727,6 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, } } } - - return ret; } static int ocfs2_xattr_extend_allocation(struct inode *inode, diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h index 5e18513277f1..887cc1a18b1a 100644 --- a/fs/ocfs2/xattr.h +++ b/fs/ocfs2/xattr.h @@ -59,10 +59,10 @@ int ocfs2_calc_security_init(struct inode *, int *, int *, struct ocfs2_alloc_context **); struct ocfs2_acl_state; -int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, - struct ocfs2_security_xattr_info *si, - int *want_clusters, int *xattr_credits, - int *want_meta, struct ocfs2_acl_state *acl_state); +void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state); /* * xattrs can live inside an inode, as part of an external xattr block, From f166586f74dd5d9cbadaabf86ef81c8ddf6cafa7 Mon Sep 17 00:00:00 2001 From: Nathan Gao Date: Thu, 3 Sep 2026 17:28:27 -0700 Subject: [PATCH 1273/1317] mm/damon/ops-common: use a page-aligned address in damon_ptep_mkold() __damon_va_prepare_access_check() picks a random byte address within the region and stores it in r->sampling_addr. damon_va_mkold() passes it into a page table walk, which hands it to damon_ptep_mkold() as the address of the page to sample: damon_va_mkold(mm, r->sampling_addr) damon_va_walk_page_range(mm, addr, addr + 1) damon_mkold_pmd_entry() damon_ptep_mkold(pte, vma, addr) ptep_test_and_clear_young(vma, addr, pte) mmu_notifier_clear_young(mm, addr, addr + PAGE_SIZE) For arm64, before commit 6f0e1142173a ("arm64: mm: support batch clearing of the young flag for large folios"), the contpte helper walked exactly CONT_PTES entries from the aligned-down page table pointer and used @addr only to pass down to each entry, so an unaligned value was harmless: ptep = contpte_align_down(ptep); addr = ALIGN_DOWN(addr, CONT_PTE_SIZE); for (i = 0; i < CONT_PTES; i++, ptep++, addr += PAGE_SIZE) Now the range to walk is derived from @addr instead: end = addr + nr * PAGE_SIZE, rounded up to CONT_PTE_SIZE. For a sample in the last page of a contpte block, the sub-page offset puts end just past the block boundary, so the round-up lands a whole block further and the walk clears PTE_AF in CONT_PTES entries beyond the sampled block. For the last block in a page table page, those entries are past the end of that page, so the walk writes into the page that follows. Triggered by the full 7.1/7.2 kernel selftest suite on arm64 (EC2 c/m6g.4xlarge). The kernel sometimes crashes at or shortly after the DAMON test. What the overrun does depends on the page that happens to follow the page table, so there is no single signature. If that page is read-only, the write faults in the sampling path itself: Unable to handle kernel write to read-only memory at virtual address ffff0003c5d2d000 FSC = 0x0f: level 3 permission fault CM = 0, WnR = 1, TnD = 0, TagAccess = 0 CPU: 10 UID: 0 PID: 3487 Comm: kdamond.2 pc : contpte_test_and_clear_young_ptes+0x70/0xc0 lr : damon_ptep_mkold+0x1e8/0x1f8 Call trace: contpte_test_and_clear_young_ptes+0x70/0xc0 (P) damon_mkold_pmd_entry+0x150/0x170 walk_pmd_range+0x110/0x2b0 walk_pud_range+0x10c/0x208 walk_pgd_range+0x134/0x258 __walk_page_range+0x98/0x1b0 walk_page_range_vma_unsafe+0x90/0x148 walk_page_range_vma+0x28/0x40 damon_va_walk_page_range+0x114/0x2b8 damon_va_prepare_access_checks+0xec/0x1a8 kdamond_fn+0x534/0x770 kthread+0x128/0x138 ret_from_fork+0x10/0x20 Otherwise the page is writable, the PTE_AF clearing succeeds silently and the damage only surfaces later, in whatever happened to own the page, so the backtrace is unrelated to DAMON and differs between runs. Pass a page-aligned address to the ptep_test_and_clear_young() call in damon_ptep_mkold(), which is the only place DAMON can reach contpte_test_and_clear_young_ptes() from. Nothing else sees the aligned address, and r->sampling_addr itself is left as is, so the sampling and region bookkeeping semantics are unchanged. Link: https://lore.kernel.org/20260904002829.116381-1-sj@kernel.org Fixes: 6f0e1142173a ("arm64: mm: support batch clearing of the young flag for large folios") Signed-off-by: Nathan Gao Signed-off-by: SJ Park Signed-off-by: Andrew Morton Reviewed-by: SJ Park Reviewed-by: Baolin Wang Cc: Baolin Wang Cc: David Hildenbrand (Arm) Cc: Ryan Roberts Cc: --- mm/damon/ops-common.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c index fbda70d8ea4d..8fc61d06d358 100644 --- a/mm/damon/ops-common.c +++ b/mm/damon/ops-common.c @@ -61,7 +61,12 @@ void damon_ptep_mkold(pte_t *pte, struct vm_area_struct *vma, unsigned long addr * device aspects. */ if (likely(pte_present(pteval))) - young |= ptep_test_and_clear_young(vma, addr, pte); + /* + * Arch implementation of ptep_test_and_clear_young() may + * require aligned @addr + */ + young |= ptep_test_and_clear_young(vma, PAGE_ALIGN_DOWN(addr), + pte); young |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + PAGE_SIZE); if (young) folio_set_young(folio); From 90179da203ba8b708c84a12a07cd44be0f346334 Mon Sep 17 00:00:00 2001 From: Liew Rui Yan Date: Tue, 8 Sep 2026 06:54:11 -0700 Subject: [PATCH 1274/1317] mm/damon/core: allow esz to be set to zero When the temporal quota goal tuner determines that the goal has been achieved (score >= 10000), it sets esz_bp to zero so that the esz becomes zero. However, damos_set_effective_quota() clamps the esz to min_region_sz when quota->ms is set. This is a minor issue, the main problem is that it doesn't match the description in the documentation, which state that if the goal has already been [over-]achieved, the quota will be set to zero. Fix this by set quota (esz) as minimum as possible. Link: https://lore.kernel.org/20260908135413.97570-1-sj@kernel.org Fixes: 8bbde987c2b8 ("mm/damon/core: disallow time-quota setting zero esz") Signed-off-by: SJ Park Signed-off-by: Liew Rui Yan Signed-off-by: Andrew Morton Reviewed-by: SJ Park Cc: # v7.1.x --- mm/damon/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 644daf5a1656..2b294fb46648 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3091,6 +3091,7 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) struct damos_quota *quota = &s->quota; unsigned long throughput; unsigned long esz = ULONG_MAX; + unsigned long esz_time; if (!quota->ms && list_empty("a->goals)) { quota->esz = quota->sz; @@ -3111,8 +3112,8 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) 1000000, quota->total_charged_ns); else throughput = PAGE_SIZE * 1024; - esz = min(throughput * quota->ms, esz); - esz = max(ctx->min_region_sz, esz); + esz_time = max(throughput * quota->ms, ctx->min_region_sz); + esz = min(esz_time, esz); } if (quota->sz && quota->sz < esz) From 39c0ceedd54557bdc1542de08d22b2ed33e534e4 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 7 Sep 2026 10:03:56 -0700 Subject: [PATCH 1275/1317] mm/damon/vaddr: avoid hw-driven pte updates during damon_hugetlb_mkold() damon_hugetlb_mkold() reads the page table entry into a local variable, unsets the accessed bit in the variable, and updates the page table entry with the updated variable value. If hardware updates the same page table entry in parallel, the hw updates could be lost. For example, hardware-updated dirty bits might be lost. Avoid the parallel updates by clearing the page table entry when reading it together, using huge_ptep_get_and_clear(). If a parallel write to the memory is made after the clearing, the hw will see the page table entry is cleared, trigger page fault and wait until it is handled. The page fault handling will wait for damon_hugetlb_mkold() due to the page table lock. Because hugetlbfs is an in-memory file system and hugetlb pages cannot be reclaimed, no critical issue is expected to my best knowledge. But definitely this is a nasty bug that should be fixed sooner rather than later. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260907170358.100168-1-sj@kernel.org Link: https://lore.kernel.org/20260830160545.98969-1-sj@kernel.org [1] Fixes: 49f4203aae06 ("mm/damon: add access checking for hugetlb pages") Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: Baolin Wang Cc: # 5.17.x --- mm/damon/vaddr.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c index 0648400b2d65..04ee2a2c6a4d 100644 --- a/mm/damon/vaddr.c +++ b/mm/damon/vaddr.c @@ -293,22 +293,29 @@ static int damon_mkold_pmd_entry(pmd_t *pmd, unsigned long addr, } #ifdef CONFIG_HUGETLB_PAGE +static bool damon_hugetlb_ptep_mkold(pte_t *pte, struct mm_struct *mm, + struct vm_area_struct *vma, unsigned long addr, pte_t *entry) +{ + unsigned long psize = huge_page_size(hstate_vma(vma)); + + if (!pte_young(*entry)) + return false; + *entry = huge_ptep_get_and_clear(mm, addr, pte, psize); + *entry = pte_mkold(*entry); + set_huge_pte_at(mm, addr, pte, *entry, psize); + return true; +} + static void damon_hugetlb_mkold(pte_t *pte, struct mm_struct *mm, struct vm_area_struct *vma, unsigned long addr) { bool referenced = false; pte_t entry = huge_ptep_get(mm, addr, pte); struct folio *folio = pfn_folio(pte_pfn(entry)); - unsigned long psize = huge_page_size(hstate_vma(vma)); folio_get(folio); - if (pte_young(entry)) { - referenced = true; - entry = pte_mkold(entry); - set_huge_pte_at(mm, addr, pte, entry, psize); - } - + referenced = damon_hugetlb_ptep_mkold(pte, mm, vma, addr, &entry); if (mmu_notifier_clear_young(mm, addr, addr + huge_page_size(hstate_vma(vma)))) referenced = true; From b3723b596b548c837a766aae3553c14a7b15af2b Mon Sep 17 00:00:00 2001 From: Liew Rui Yan Date: Tue, 8 Sep 2026 06:47:38 -0700 Subject: [PATCH 1276/1317] mm/damon/core: fix unconditionally skip last region Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed. Example: 1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes). 2. Quota is configured to process only 100 bytes per window. 3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100). 4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from. Result: R2 is permanently skipped even though it has never been processed. However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region(). Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split. Link: https://lore.kernel.org/20260908134739.96919-1-sj@kernel.org Fixes: 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions") Signed-off-by: Liew Rui Yan Reviewed-by: SJ Park Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: # v5.16.x --- mm/damon/core.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 2b294fb46648..06a253df8d4b 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2342,36 +2342,39 @@ static bool damos_skip_charged_region(struct damon_target *t, { struct damos_quota *quota = &s->quota; unsigned long sz_to_skip; + bool skip = false; /* Skip previously charged regions */ if (quota->charge_target_from) { if (t != quota->charge_target_from) return true; - if (r == damon_last_region(t)) { - quota->charge_target_from = NULL; - quota->charge_addr_from = 0; - return true; - } if (quota->charge_addr_from && - r->ar.end <= quota->charge_addr_from) - return true; + r->ar.end <= quota->charge_addr_from) { + skip = true; + goto out; + } if (quota->charge_addr_from && r->ar.start < quota->charge_addr_from) { sz_to_skip = ALIGN_DOWN(quota->charge_addr_from - r->ar.start, min_region_sz); if (!sz_to_skip) { - if (damon_sz_region(r) <= min_region_sz) - return true; + if (damon_sz_region(r) <= min_region_sz) { + skip = true; + goto out; + } sz_to_skip = min_region_sz; } damon_split_region_at(t, r, sz_to_skip); - return true; + skip = true; } + } +out: + if (r == damon_last_region(t)) { quota->charge_target_from = NULL; quota->charge_addr_from = 0; } - return false; + return skip; } static void damos_update_stat(struct damos *s, From 9bdad082d44bdcf93716973dcba6be77e8a06e7b Mon Sep 17 00:00:00 2001 From: Jaewook You Date: Mon, 14 Sep 2026 22:23:52 +0900 Subject: [PATCH 1277/1317] mm/hugetlb: preserve mremap address delta when skipping page tables move_hugetlb_page_tables() optimizes mremap() by advancing to the last entry in the page table when the source page table does not exist, either initially or after unsharing a PMD table. The common loop increment then steps to the first entry in the next page table. However, the code advances both the source and destination addresses to the last entries in their respective page tables, which is wrong. The destination address must be advanced only by the same amount as the source address. If the source and destination offsets within their page tables differ, the destination address can be advanced too far, causing follow-up issues. Fix this by advancing the destination address by the source advance distance. With a reproducer, we were able to trigger a kernel panic on x86-64. With this fix in place, we can no longer reproduce the issue. Link: https://lore.kernel.org/20260914132352.472-1-jaewook376@gmail.com Fixes: e95a9851787b ("hugetlb: skip to end of PT page mapping when pte not present") Fixes: 4ddb4d91b82f ("hugetlb: do not update address in huge_pmd_unshare") Signed-off-by: Jaewook You Signed-off-by: Andrew Morton Acked-by: David Hildenbrand (Arm) Cc: Johan Hovold Cc: Muchun Song Cc: Oscar Salvador Cc: Assisted-by: LLM --- mm/hugetlb.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index d28972cd33f5..cea25773a6c9 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -5170,18 +5170,21 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, hugetlb_vma_lock_write(vma); i_mmap_lock_write(mapping); for (; old_addr < old_end; old_addr += sz, new_addr += sz) { + const unsigned long offset_to_last_entry = + (old_addr | last_addr_mask) - old_addr; + src_pte = hugetlb_walk(vma, old_addr, sz); if (!src_pte) { - old_addr |= last_addr_mask; - new_addr |= last_addr_mask; + old_addr += offset_to_last_entry; + new_addr += offset_to_last_entry; continue; } if (huge_pte_none(huge_ptep_get(mm, old_addr, src_pte))) continue; if (huge_pmd_unshare(&tlb, vma, old_addr, src_pte)) { - old_addr |= last_addr_mask; - new_addr |= last_addr_mask; + old_addr += offset_to_last_entry; + new_addr += offset_to_last_entry; continue; } From b6ac0b3f6013c168f22cad97e79967accacb08e1 Mon Sep 17 00:00:00 2001 From: Jinjiang Tu Date: Tue, 8 Sep 2026 20:29:24 +0800 Subject: [PATCH 1278/1317] mm/rmap: fix missing barrier between anon_vma init and vma->anon_vma publish On arm64 server, we find that a task trying to grab the anon_vma lock triggers hungtask. INFO: task main:2354726 blocked for more than 120 seconds. Tainted: G E 5.10.0-0021.aarch64 #1 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:main state:D stack: 0 pid:2354726 ppid:2350673 flags:0x00000a01 Call trace: __switch_to+0x7c/0xbc __schedule+0x3b4/0x8a0 schedule+0x50/0xe0 rwsem_down_write_slowpath+0x3cc/0x6cc down_write+0x60/0x260 __anon_vma_prepare+0x6c/0x210 do_anonymous_page+0x258/0x660 handle_pte_fault+0x188/0x214 __handle_mm_fault+0x1b0/0x380 handle_mm_fault+0xf4/0x284 do_page_fault+0x19c/0x494 do_translation_fault+0xcc/0xf8 do_mem_abort+0x48/0xac el0_da+0x44/0x80 el0_sync_handler+0x88/0xb4 el0_sync+0x160/0x180 After analyzing the vmcore, we found the anon_vma->root->rwsem.count is -1. There is another anon_vma whose anon_vma->root->rwsem.count is 1, the anon_vma->root->rwsem.owner shows the lock is held, but the stack of the task shows the task doesn't hold the anon_vma lock. After adding more debugging info, we found __anon_vma_prepare() reuses anon_vma and triggers the UAF of anon_vma->root due to missing memory barrier, leading to locking and unlocking two different anon_vma->root, thus leading to an anon_vma will never be unlocked, and another anon_vma couldn't be locked anymore. This race requires two adjacent VMAs that are not merged but are anon_vma-compatible (e.g., they differ in VMA_ACCESS_FLAGS that can be changed by mprotect()). Two threads fault on each VMA concurrently, both calling __anon_vma_prepare() with only mmap_lock held for reading. THREAD A THREAD B __anon_vma_prepare __anon_vma_prepare find_mergeable_anon_vma() -> NULL anon_vma = anon_vma_alloc(); anon_vma->root = anon_vma; // the two stores may be reordered vma->anon_vma = anon_vma; // finds A's anon_vma anon_vma = find_mergeable_anon_vma(vma); anon_vma_lock_write(anon_vma); // may still see the old root down_write(&anon_vma->root->rwsem); anon_vma_unlock_write(anon_vma); // see the new root, never unlock old up_write(&anon_vma->root->rwsem); thread A triggers page fault and calls __anon_vma_prepare() to prepare anon_vma for the faulting vma. __anon_vma_prepare() allocates and initializes a new anon_vma, and then publishes it to the vma with a plain store. anon_vma_prepare() only requires the mmap_lock to be held for reading, so two threads can fault on adjacent VMAs at the same time. While thread A publishes a new anon_vma, thread B could find the anon_vma via find_mergeable_anon_vma() and then locks anon_vma->root->rwsem. The store to anon_vma->root in anon_vma_alloc() and the store to vma->anon_vma can be reordered. The anon_vma_lock_write() and spin_lock() only provide acquire semantics, which do not prevent prior stores from being reordered after them. The release semantics of the corresponding spin_unlock() and anon_vma_unlock_write() come too late, the store to vma->anon_vma is already published before they take effect. As a result, thread B can observe the following order: vma->anon_vma = anon_vma; anon_vma->root = anon_vma; The anon_vma slab is SLAB_TYPESAFE_BY_RCU, so a newly allocated anon_vma may reuse memory from a previously freed one. The constructor (anon_vma_ctor) does not reset anon_vma->root, and __put_anon_vma() doesn't clear it either, so the old root value persists until anon_vma_alloc() overwrites it. If that store isn't visible, thread B reads a root that points to the old anon_vma and locks it. As a result, thread B can call anon_vma_lock_write() with the old root, and call anon_vma_unlock_write() with the new root, leading to an anon_vma will never be unlocked, and another anon_vma couldn't be locked anymore (its count is dropped from 0 to -1 due to wrong unlock). To fix it, change the plain store `vma->anon_vma = anon_vma` to store release, so that the fields of anon_vma are visible before anon_vma is published to vma->anon_vma. At read side, the load of anon_vma and anon_vma->root have address dependency. According to Documentation/memory-barriers.txt and some investigations, only Alpha needs address-dependency barriers and it has been handled by READ_ONCE() in reusable_anon_vma(). We reproduced this issue in v5.10 with KSM enabled. The kernel doesn't merge commit cf7e7a3503df ("mm: prevent KSM from breaking VMA merging for new VMAs"), so there are many adjacent VMAs that aren't merged but are compatible for anon_vma. Without this fix, our production environment could reproduce this issue about 2-5 times each month. After adding a smp_mb() before anon_vma_lock_write(anon_vma) in __anon_vma_prepare(), which is different to this patch, this issue hasn't been reproduced for one month. Link: https://lore.kernel.org/20260908122924.554373-1-tujinjiang@huawei.com Fixes: 5c341ee1dfc8 ("mm: track the root (oldest) anon_vma") Signed-off-by: Jinjiang Tu Signed-off-by: Andrew Morton Reviewed-by: Lance Yang Reviewed-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Acked-by: Vlastimil Babka (SUSE) Cc: Minchan Kim Cc: Harry Yoo Cc: Hiroyouki Kamezawa Cc: Jann Horn Cc: Jinjiang Tu Cc: Kefeng Wang Cc: Larry Woodman Cc: Liam R. Howlett Cc: Nanyong Sun Cc: Rik van Riel Cc: --- mm/rmap.c | 6 +++++- mm/vma.c | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mm/rmap.c b/mm/rmap.c index d1819fd69938..f3b21aaa34ee 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -209,7 +209,11 @@ int __anon_vma_prepare(struct vm_area_struct *vma) /* page_table_lock to protect against threads */ spin_lock(&mm->page_table_lock); if (likely(!vma->anon_vma)) { - vma->anon_vma = anon_vma; + /* + * Make anon_vma fields visible before anon_vma is published. + * Paired with an address dependency in reusable_anon_vma(). + */ + smp_store_release(&vma->anon_vma, anon_vma); anon_vma_chain_assign(vma, avc, anon_vma); anon_rmap_tree_insert(avc, anon_vma); anon_vma->num_active_vmas++; diff --git a/mm/vma.c b/mm/vma.c index f29abb30956b..9f0a0acf694a 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -2094,6 +2094,13 @@ static int anon_vma_compatible(struct vm_area_struct *a, struct vm_area_struct * * acceptable for merging, so we can do all of this optimistically. But * we do that READ_ONCE() to make sure that we never re-load the pointer. * + * The READ_ONCE() establishes an address dependency between anon_vma and + * any access to its fields, which pairs with the assignment to + * vma->anon_vma performed with release semantics in __anon_vma_prepare(). + * + * This is especially important as anon_vma's are SLAB_TYPESAFE_BY_RCU so + * accessing an uninitialised anon_vma's fields may result in a UAF. + * * IOW: that the "list_is_singular()" test on the anon_vma_chain only * matters for the 'stable anon_vma' case (ie the thing we want to avoid * is to return an anon_vma that is "complex" due to having gone through @@ -2108,6 +2115,7 @@ static struct anon_vma *reusable_anon_vma(struct vm_area_struct *old, struct vm_area_struct *b) { if (anon_vma_compatible(a, b)) { + /* Paired with a memory barrier in __anon_vma_prepare(). */ struct anon_vma *anon_vma = READ_ONCE(old->anon_vma); if (anon_vma && list_is_singular(&old->anon_vma_chain)) From 44fcc0bfb0874a95cec2c1672f14d426f86dc68f Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Tue, 8 Sep 2026 09:42:12 +0800 Subject: [PATCH 1279/1317] MAINTAINERS: add Baoquan and Baolin as MGLRU reviewers Baoquan and I have been contributing MGLRU patches and helping review MGLRU related patches for some time. We will continue to follow MGLRU changes, so we'd like to be CC'd on MGLRU related patches. Link: https://lore.kernel.org/06e20ef4f603a4ffeafcdbf623ce2936281457bf.1788831480.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Signed-off-by: Andrew Morton Acked-by: Barry Song Acked-by: Qi Zheng Acked-by: Baoquan He Acked-by: Kairui Song Cc: Axel Rasmussen Cc: Shakeel Butt Cc: Wei Xu Cc: Yuanchu Xie --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index c9f866debdf0..a6c0e4bcbc78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17240,6 +17240,8 @@ R: Barry Song R: Axel Rasmussen R: Yuanchu Xie R: Wei Xu +R: Baoquan He +R: Baolin Wang L: linux-mm@kvack.org S: Maintained W: http://www.linux-mm.org From eb64948249781bda35de04feab5a0acc36aa9051 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Thu, 10 Sep 2026 07:28:45 -0700 Subject: [PATCH 1280/1317] mm/damon/core: reset invalid quota->charge_target_from DAMOS can suddenly stop working if a target process that the quota is just fully charged on is terminated. Fix by catching and processing the corner case. When DAMOS quota is fully charged, the target and the region to continue applying the action in the next round is saved in damos_quota->charge_{target,addr}_from. In the next round, DAMOS iterates targets and regions from the beginning. It skips applying the action to the regions until it visits and skips the saved target/region. Virtual address space targets become invalid if the process is terminated. Trying to apply the scheme to invalid target is just a waste of time. Hence commit 6e4930e33329 ("mm/damon/core: fix wasteful CPU calls by skipping non-existent targets") made the logic to skip invalid targets. However, it does skip before the charged target/region skipping/updating. Let's suppose the user runs DAMOS for multiple virtual address spaces with a quota. The quota exceeded in the middle of a virtual address space. And the process of the address space is terminated. Then the charge_target_from points to the invalid target. The pointer update logic is skipped for the invalid target, so the charge_target_from is never updated. DAMOS action to every target/region is skipped. From the user's perspective, it would look like suddenly DAMOS has stopped working. No critical leak or crash can happen. The user could reinstall the scheme. But this makes use of DAMOS under certain setups quite unreliable. When the invalid target is found, further check the corner case and reset the pointer. This issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260910142846.172957-1-sj@kernel.org Link: https://lore.kernel.org/20260830064708.40CA61F000E9@smtp.kernel.org [1] Fixes: 6e4930e33329 ("mm/damon/core: fix wasteful CPU calls by skipping non-existent targets") Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: Enze Li Cc: # 7.0.x --- mm/damon/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 06a253df8d4b..1764620903f2 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3245,8 +3245,15 @@ static void kdamond_apply_schemes(struct damon_ctx *c) max_region_sz = damon_region_sz_limit(c); mutex_lock(&c->walk_control_lock); damon_for_each_target(t, c) { - if (c->ops.target_valid && c->ops.target_valid(t) == false) + if (c->ops.target_valid && c->ops.target_valid(t) == false) { + damon_for_each_scheme(s, c) { + if (s->quota.charge_target_from != t) + continue; + s->quota.charge_target_from = NULL; + s->quota.charge_addr_from = 0; + } continue; + } damos_apply_target(c, t, max_region_sz); } From 407a5d205179a4ab186571b0e16ec42725dc77bc Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 9 Sep 2026 18:01:07 +0000 Subject: [PATCH 1281/1317] writeback: report a Tasks-RCU quiescent state per cgwb drain pass cleanup_offline_cgwbs_workfn() drains a dying cgwb by calling cleanup_offline_cgwb() until it returns false, with a cond_resched() between passes. On a CONFIG_PREEMPTION kernel that cond_resched() does nothing: _cond_resched() is a plain "return 0", and under PREEMPT_DYNAMIC the full and lazy modes disable it. Since commit 7dadeaa6e851 ("sched: Further restrict the preemption modes") those are the only two models on the architectures with PREEMPT_LAZY support, arm64 and x86 among them, so the drain loop never reports a Tasks-RCU quiescent state. A worker draining a cgwb with millions of attached inodes runs for minutes. On a 6.18 arm64 host in lazy mode the cgwb worker drained one dying cgroup's writeback domain for over 11 minutes. A BPF program unlink (bpf_trampoline_unlink_prog -> bpf_trampoline_update -> unregister_ftrace_direct -> ftrace_shutdown -> synchronize_rcu_tasks()) waited on that grace period while holding the trampoline mutex, 42 tasks queued behind it in D state, and the hung task detector fired at 614 s and panicked the host. Any BPF or ftrace detach during a long drain inherits the drain's length. Fix this by calling cond_resched_tasks_rcu_qs() so we do not stall out anybody who calls sycnrhonize_rcu_tasks(). We put this in a do { } while loop because if we have many small cgroups cleanup_offline_cgwb() will return false and we will never call cond_resched_tasks_rcu_qs(), creating the same problem. Link: https://lore.kernel.org/20260909-cgwb-tasks-rcu-qs-v1-1-967a7754771f@toxicpanda.com Fixes: c22d70a162d3 ("writeback, cgroup: release dying cgwbs by switching attached inodes") Signed-off-by: Josef Bacik Signed-off-by: Andrew Morton Link: https://lore.kernel.org/bpf/9d444098-7c03-4163-af12-bd0a79a51443@paulmck-laptop/ Assisted-by: LLM Acked-by: Tejun Heo Reviewed-by: Roman Gushchin Reviewed-by: Jan Kara Acked-by: Lorenzo Stoakes (ARM) Cc: David Hildenbrand Cc: Dennis Zhou Cc: Liam R. Howlett Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport Cc: "Paul E . McKenney" Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: --- mm/backing-dev.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/backing-dev.c b/mm/backing-dev.c index cecbcf9060a6..18e999053bae 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -910,8 +910,9 @@ static void cleanup_offline_cgwbs_workfn(struct work_struct *work) continue; spin_unlock_irq(&cgwb_lock); - while (cleanup_offline_cgwb(wb)) - cond_resched(); + do { + cond_resched_tasks_rcu_qs(); + } while (cleanup_offline_cgwb(wb)); spin_lock_irq(&cgwb_lock); wb_put(wb); From 692dd08e03f4a5523650e055f033f846bee43a0c Mon Sep 17 00:00:00 2001 From: Xu Xin Date: Mon, 7 Sep 2026 14:53:42 +0800 Subject: [PATCH 1282/1317] MAINTAINERS: update Xu Xin's email Now I'm moving to the @linux.dev account, so map my old email addresses and update them to my new address. Link: https://lore.kernel.org/20260907145342529uOGtNYWTAzrSCFnghfgCb@zte.com.cn Signed-off-by: Xu Xin Signed-off-by: Andrew Morton --- .mailmap | 2 ++ MAINTAINERS | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 59518b814b4f..2447811f773a 100644 --- a/.mailmap +++ b/.mailmap @@ -980,6 +980,8 @@ Wesley Cheng Will Deacon Wolfram Sang Wolfram Sang +Xu Xin +Xu Xin xu xin Yakir Yang Yanteng Si Ying Huang diff --git a/MAINTAINERS b/MAINTAINERS index a6c0e4bcbc78..7d12dfac0149 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17192,7 +17192,7 @@ F: tools/testing/selftests/mm/gup_test.c MEMORY MANAGEMENT - KSM (Kernel Samepage Merging) M: Andrew Morton M: David Hildenbrand -R: Xu Xin +R: Xu Xin R: Chengming Zhou L: linux-mm@kvack.org S: Maintained From b0be01f133aa36d2fd12d71c916cb8a47826b4ed Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sun, 30 Aug 2026 11:02:01 +0800 Subject: [PATCH 1283/1317] pinctrl: qcom: nord: Split QUP1 SE2/SE3 into lane-pair functions QUP1 SE2 and SE3 pack all four of their lanes pair-wise onto only two pins each: lanes 0/1 (I2C SDA/SCL) at mux value 2 and lanes 2/3 (UART TX/RX) at mux value 1, on gpio127/gpio128 and gpio129/gpio130 respectively. Both mux values were named "qup1_se2" (respectively "qup1_se3"), so the two distinct lane pairs became indistinguishable. msm_pinmux_set_mux() stops at the first entry matching the requested function, which means mux value 1 was always selected and the I2C lanes could never be muxed out. In practice i2c9 and i2c10 got the UART lanes and did not work, while uart9 and uart10 happened to be muxed correctly. Give each lane pair its own function, following the _01/_23 naming already used for the same hardware arrangement by the shikra, eliza, hawi and maili TLMM drivers. Both functions still cover the full pin pair, so a single pinctrl state per protocol remains sufficient. Drop gpio129/gpio130 from the SE2 group list, since those pins belong to SE3 and were never reachable through the SE2 function. Also rename QUP1 SE2/SE3 functions in the binding doc accordingly. While at it, add missing "gpio", "qup3_se0_mira" and "qup3_se0_mirb" to the binding function enum to get the list complete. Fixes: c24dd0826f06 ("pinctrl: qcom: add the TLMM driver for the Nord platforms") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Shawn Guo Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260830030201.135637-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- .../bindings/pinctrl/qcom,nord-tlmm.yaml | 7 ++-- drivers/pinctrl/qcom/pinctrl-nord.c | 34 +++++++++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml b/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml index 4bb511719f31..56758a85ead8 100644 --- a/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml +++ b/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml @@ -67,7 +67,7 @@ $defs: Specify the alternative function to be configured for the specified pins. - enum: [ aoss_cti, atest_char, atest_usb20, atest_usb21, + enum: [ gpio, aoss_cti, atest_char, atest_usb20, atest_usb21, aud_intfc0_clk, aud_intfc0_data, aud_intfc0_ws, aud_intfc10_clk, aud_intfc10_data, aud_intfc10_ws, aud_intfc1_clk, aud_intfc1_data, aud_intfc1_ws, @@ -98,9 +98,10 @@ $defs: pcie3_clk_req_n, phase_flag, pll_bist_sync, pll_clk_aux, prng_rosc0, prng_rosc1, pwrbrk_i_n, qdss, qdss_cti, qspi, qup0_se0, qup0_se1, qup0_se2, qup0_se3, qup0_se4, qup0_se5, - qup1_se0, qup1_se1, qup1_se3, qup1_se2, qup1_se4, qup1_se5, + qup1_se0, qup1_se1, qup1_se2_01, qup1_se2_23, qup1_se3_01, + qup1_se3_23, qup1_se4, qup1_se5, qup1_se6, qup2_se0, qup2_se1, qup2_se2, qup2_se3, qup2_se4, - qup2_se5, qup2_se6, + qup2_se5, qup2_se6, qup3_se0_mira, qup3_se0_mirb, sailss_ospi, sdc4_clk, sdc4_cmd, sdc4_data, smb_alert, smb_alert_n, smb_clk, smb_dat, tb_trig_sdc4, tmess_prng0, tmess_prng1, tsc_timer, tsense_pwm, usb0_hs, diff --git a/drivers/pinctrl/qcom/pinctrl-nord.c b/drivers/pinctrl/qcom/pinctrl-nord.c index 7c21306e77ff..7f37f8e819ba 100644 --- a/drivers/pinctrl/qcom/pinctrl-nord.c +++ b/drivers/pinctrl/qcom/pinctrl-nord.c @@ -570,8 +570,10 @@ enum nord_functions { msm_mux_qup0_se5, msm_mux_qup1_se0, msm_mux_qup1_se1, - msm_mux_qup1_se2, - msm_mux_qup1_se3, + msm_mux_qup1_se2_01, + msm_mux_qup1_se2_23, + msm_mux_qup1_se3_01, + msm_mux_qup1_se3_23, msm_mux_qup1_se4, msm_mux_qup1_se5, msm_mux_qup1_se6, @@ -1152,11 +1154,19 @@ static const char *const qup1_se1_groups[] = { "gpio123", "gpio124", "gpio125", "gpio126", }; -static const char *const qup1_se2_groups[] = { - "gpio127", "gpio128", "gpio129", "gpio130", +static const char *const qup1_se2_01_groups[] = { + "gpio127", "gpio128", }; -static const char *const qup1_se3_groups[] = { +static const char *const qup1_se2_23_groups[] = { + "gpio127", "gpio128", +}; + +static const char *const qup1_se3_01_groups[] = { + "gpio129", "gpio130", +}; + +static const char *const qup1_se3_23_groups[] = { "gpio129", "gpio130", }; @@ -1428,8 +1438,10 @@ static const struct pinfunction nord_functions[] = { MSM_PIN_FUNCTION(qup0_se5), MSM_PIN_FUNCTION(qup1_se0), MSM_PIN_FUNCTION(qup1_se1), - MSM_PIN_FUNCTION(qup1_se2), - MSM_PIN_FUNCTION(qup1_se3), + MSM_PIN_FUNCTION(qup1_se2_01), + MSM_PIN_FUNCTION(qup1_se2_23), + MSM_PIN_FUNCTION(qup1_se3_01), + MSM_PIN_FUNCTION(qup1_se3_23), MSM_PIN_FUNCTION(qup1_se4), MSM_PIN_FUNCTION(qup1_se5), MSM_PIN_FUNCTION(qup1_se6), @@ -1633,13 +1645,13 @@ static const struct msm_pingroup nord_groups[] = { _, _, _, _, _, _, _), [126] = PINGROUP(126, qup1_se1, qup1_se0, ccu_i2c_scl, mdp1_vsync_out, _, atest_usb20, ddr_pxi, _, _, _, _), - [127] = PINGROUP(127, qup1_se2, qup1_se2, _, atest_usb21, ddr_pxi, + [127] = PINGROUP(127, qup1_se2_23, qup1_se2_01, _, atest_usb21, ddr_pxi, _, _, _, _, _, _), - [128] = PINGROUP(128, qup1_se2, qup1_se2, _, atest_usb20, ddr_pxi, + [128] = PINGROUP(128, qup1_se2_23, qup1_se2_01, _, atest_usb20, ddr_pxi, _, _, _, _, _, _), - [129] = PINGROUP(129, qup1_se3, qup1_se3, ccu_i2c_sda, mdp1_vsync_out, + [129] = PINGROUP(129, qup1_se3_23, qup1_se3_01, ccu_i2c_sda, mdp1_vsync_out, _, atest_usb21, ddr_pxi, _, _, _, _), - [130] = PINGROUP(130, qup1_se3, qup1_se3, ccu_i2c_scl, mdp1_vsync_out, + [130] = PINGROUP(130, qup1_se3_23, qup1_se3_01, ccu_i2c_scl, mdp1_vsync_out, _, atest_usb20, ddr_pxi, _, _, _, _), [131] = PINGROUP(131, qup1_se4, qup1_se6, ccu_i2c_sda, mdp1_vsync_out, _, atest_usb21, ddr_pxi, _, _, _, _), From 447dcff90557748a5ac384aaf5d70b2c7e3b961d Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:43:40 +0800 Subject: [PATCH 1284/1317] pinctrl: qcom: ipq5210: Publish the OF module alias The IPQ5210 TLMM platform driver can be a module and matches through ipq5210_tlmm_of_match. This table is not published for OF modalias matching. Publish the existing table, preserving arch_initcall ordering and the shared MSM pinctrl probe. Fixes: a549fe22376f ("pinctrl: qcom: Introduce IPQ5210 TLMM driver") Signed-off-by: hpp.iscas Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260905134340.67477-1-hppiscas@163.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- drivers/pinctrl/qcom/pinctrl-ipq5210.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pinctrl/qcom/pinctrl-ipq5210.c b/drivers/pinctrl/qcom/pinctrl-ipq5210.c index 827a4ad07a6b..d5b4eb3e7343 100644 --- a/drivers/pinctrl/qcom/pinctrl-ipq5210.c +++ b/drivers/pinctrl/qcom/pinctrl-ipq5210.c @@ -867,6 +867,7 @@ static const struct of_device_id ipq5210_tlmm_of_match[] = { { .compatible = "qcom,ipq5210-tlmm", }, { }, }; +MODULE_DEVICE_TABLE(of, ipq5210_tlmm_of_match); static int ipq5210_tlmm_probe(struct platform_device *pdev) { From 10396a2d6d41d594975b6ece712278570c3c970c Mon Sep 17 00:00:00 2001 From: Holger Dengler Date: Fri, 14 Aug 2026 16:20:10 +0200 Subject: [PATCH 1285/1317] crypto: s390/hmac - Generate intermediate CV for API partial block handling The API partial block handling requires a intermediate chaining value (CV). The internal function hash_data() sets the function code correctly, so also call cpacf_kimd() instruction for intermediate CV generation, as cpacf_klmd() always generate the final hash value. Cc: stable@vger.kernel.org # 6.15+ Fixes: 08811169ac01 ("crypto: s390/hmac - Use API partial block handling") Signed-off-by: Holger Dengler Reviewed-by: Harald Freudenberger Signed-off-by: Herbert Xu --- arch/s390/crypto/hmac_s390.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/s390/crypto/hmac_s390.c b/arch/s390/crypto/hmac_s390.c index f8cd09f341d4..445fa7bbd958 100644 --- a/arch/s390/crypto/hmac_s390.c +++ b/arch/s390/crypto/hmac_s390.c @@ -150,7 +150,10 @@ static int hash_data(const u8 *in, unsigned int inlen, #undef PARAM_INIT - cpacf_klmd(func, ¶m, in, inlen); + if (final) + cpacf_klmd(func, ¶m, in, inlen); + else + cpacf_kimd(func, ¶m, in, inlen); memcpy(digest, ¶m, digestsize); From 63edf5a009ae366369a1b484cd9ae4ee7c51946c Mon Sep 17 00:00:00 2001 From: "Chang S. Bae" Date: Wed, 16 Sep 2026 23:00:03 +0000 Subject: [PATCH 1286/1317] x86/build/64: Prevent native builds from generating EGPR use Omar reports that CONFIG_X86_NATIVE_CPU=y allows builds to opportunistically emit instructions using %r16-%r31 (EGPRs) when the build host supports APX since the commit: ea1dcca1de12 ("x86/kbuild/64: Add the CONFIG_X86_NATIVE_CPU option to locally optimize the kernel with '-march=native'") But the kernel is not yet prepared to use new registers internally. For example, there is no context-switch support for general in-kernel use. Explicitly disable EGPR use when building with -march=native. For C, since GCC 14 and Clang 18, both compilers support suppressing EGPR use with -mno-apx-features=egpr, whose availability can be detected via cc-option. For Rust, pass features=-apxf through the generated JSON to avoid unstable-feature warnings, see https://github.com/rust-lang/rust/issues/139284 Note Rust only accepts the option to disable APX instructions entirely or not. Support for this gating also depends on the Rust/LLVM combination. Rust 1.88 introduced the `apxf` feature option, but versions prior to 1.93 may emit an `apxf` attribute to the backend that only LLVM 23 or later can interpret. Restrict native Rust builds accordingly. Fixes: ea1dcca1de12 ("x86/kbuild/64: Add the CONFIG_X86_NATIVE_CPU option to locally optimize the kernel with '-march=native'") Reported-by: Omar Avelar Signed-off-by: Chang S. Bae Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Nathan Chancellor Acked-by: Miguel Ojeda Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916230003.1144622-1-chang.seok.bae@intel.com --- arch/x86/Kconfig.cpu | 11 +++++++++++ arch/x86/Makefile | 5 +++++ scripts/generate_rust_target.rs | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/arch/x86/Kconfig.cpu b/arch/x86/Kconfig.cpu index e4654388d794..6e7a366f0798 100644 --- a/arch/x86/Kconfig.cpu +++ b/arch/x86/Kconfig.cpu @@ -204,10 +204,21 @@ config CC_HAS_MARCH_NATIVE # usage warnings that only appear wth '-march=native'. depends on CC_IS_GCC || CLANG_VERSION >= 190100 +config RUSTC_HAS_APXF + # The kernel isn't ready for in-kernel APX instructions. Without + # explicit frontend gating of APX, the backend may emit those + # instructions in native builds. + # + # Rust 1.88 added the `apxf` feature option, but versions before 1.93 + # emit an `apxf` target attribute that only LLVM 23+ can interpret. + def_bool (RUSTC_VERSION >= 108800 && RUSTC_LLVM_MAJOR_VERSION >= 23) || \ + RUSTC_VERSION >= 109300 + config X86_NATIVE_CPU bool "Build and optimize for local/native CPU" depends on X86_64 depends on CC_HAS_MARCH_NATIVE + depends on !RUST || RUSTC_HAS_APXF help Optimize for the current CPU used to compile the kernel. Use this option if you intend to build the kernel for your diff --git a/arch/x86/Makefile b/arch/x86/Makefile index 598f178102ee..8af6b80cffdd 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -161,6 +161,11 @@ else ifdef CONFIG_X86_NATIVE_CPU KBUILD_CFLAGS += -march=native + # Prevent the compiler from generating EGPR use. The kernel is + # not yet prepared for general in-kernel use. + KBUILD_CFLAGS += $(call cc-option,-mno-apx-features=egpr) + + # generate_rust_target.rs handles Rust APX gating. KBUILD_RUSTFLAGS += -Ctarget-cpu=native else KBUILD_CFLAGS += -march=x86-64 -mtune=generic diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs index 3bf296581a88..7687b0dd5474 100644 --- a/scripts/generate_rust_target.rs +++ b/scripts/generate_rust_target.rs @@ -224,6 +224,11 @@ fn main() { features += ",+harden-sls-ijmp"; features += ",+harden-sls-ret"; } + if cfg.has("X86_NATIVE_CPU") { + // Prevent the backend from generating APX instructions. The kernel is not yet prepared + // for general in-kernel EGPR use. + features += ",-apxf"; + } ts.push("features", features); ts.push("llvm-target", "x86_64-linux-gnu"); ts.push("supported-sanitizers", ["kcfi", "kernel-address"]); From 8901cee9316d53a5a97398f026c2d59a3043159d Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 19 Sep 2026 03:42:10 +0200 Subject: [PATCH 1287/1317] bpf: Compare stack frames in regs_exact() regs_exact() compares the register state up to id, followed by the ID mappings, but does not compare frameno. The PTR_TO_STACK case in regsafe() checks frameno separately, which is bypassed when exact comparison is requested. Consequently, infinite-loop detection can treat pointers to different stack frames as the same pointer and reject a finite loop. For example, initialize fp-8 to zero in the caller and to one in the callee, then pass the caller's fp-8 to the callee as r1: loop: r0 = *(u64 *)(r1 + 0); if r0 != 0 goto done; r1 = r10; r1 += -8; goto loop; done: exit; The loop terminates after reading the callee's slot on its second iteration. At the loop header, however, the only relevant difference is r1's frameno, so exact comparison incorrectly reports an infinite loop. The same problem occurs when the pointer is spilled to the stack. Move frameno into the type-specific metadata union, ahead of id, so the existing prefix comparison in regs_exact() covers it. Ordinary stack pointers do not use another union member. Iterator and IRQ stack-slot states use their dedicated union views and do not need a frame lookup. This also keeps bpf_reg_state at 80 bytes. Since frameno now shares storage with other pointer metadata, it is only meaningful for PTR_TO_STACK registers. Return NULL from bpf_func() for other register types. process_iter_arg(), get_constant_map_key() and is_dynptr_reg_valid_init() look up the frame before checking the register type and would otherwise index frame[] with a byte of the register's map or BTF pointer. They dereference the frame only after their type check. Move the states_maybe_looping() boundary from frameno to precise after the field relocation. Its prefix comparison continues to cover the complete value state and now includes frameno. Continue to ignore precise. Precision marks control whether pruning may ignore scalar ranges; they do not change the represented values, and exact comparison already compares those ranges unconditionally. Marks can also change through backtracking while an ancestor state is still being explored. Fixes: d5b892fd607a ("bpf: make infinite loop detection in is_state_visited() exact") Reported-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260919014213.1840880-2-memxor@gmail.com --- include/linux/bpf_verifier.h | 23 +++++++++++++++-------- kernel/bpf/states.c | 7 ++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index a7202b44ab10..6fe8e5dc57aa 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -45,6 +45,14 @@ struct bpf_reg_state { union { /* valid when type == PTR_TO_PACKET */ int range; + /* + * Valid when type == PTR_TO_STACK. Inside the callee two registers + * can be both PTR_TO_STACK like R1=fp-8 and R2=fp-8, but one of them + * points to this function stack while another to the caller's stack. + * To differentiate them 'frameno' is used which is an index in + * bpf_verifier_state->frame[] array pointing to bpf_func_state. + */ + u8 frameno; /* * For CONST_PTR_TO_MAP, PTR_TO_MAP_KEY, PTR_TO_MAP_VALUE and @@ -155,14 +163,6 @@ struct bpf_reg_state { * during state comparisons. */ u32 map_uid; - /* - * Inside the callee two registers can be both PTR_TO_STACK like - * R1=fp-8 and R2=fp-8, but one of them points to this function stack - * while another to the caller's stack. To differentiate them 'frameno' - * is used which is an index in bpf_verifier_state->frame[] array - * pointing to bpf_func_state. - */ - u8 frameno; /* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */ bool precise; }; @@ -1239,11 +1239,18 @@ static inline int bpf_get_spi(s32 off) return (-off - 1) / BPF_REG_SIZE; } +/* + * Return the function state a stack pointer register refers to. frameno + * shares storage with other pointer metadata, so return NULL for any + * other register type instead of indexing frame[] with aliased bytes. + */ static inline struct bpf_func_state *bpf_func(struct bpf_verifier_env *env, const struct bpf_reg_state *reg) { struct bpf_verifier_state *cur = env->cur_state; + if (reg->type != PTR_TO_STACK) + return NULL; return cur->frame[reg->frameno]; } diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index e4ec007f7fa6..012b82513a3b 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -644,10 +644,7 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, return range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off); case PTR_TO_STACK: - /* two stack pointers are equal only if they're pointing to - * the same stack frame, since fp-8 in foo != fp-8 in bar - */ - return regs_exact(rold, rcur, idmap) && rold->frameno == rcur->frameno; + return regs_exact(rold, rcur, idmap); case PTR_TO_ARENA: return true; case PTR_TO_INSN: @@ -1126,7 +1123,7 @@ static bool states_maybe_looping(struct bpf_verifier_state *old, fcur = cur->frame[fr]; for (i = 0; i < MAX_BPF_REG; i++) if (memcmp(&fold->regs[i], &fcur->regs[i], - offsetof(struct bpf_reg_state, frameno))) + offsetof(struct bpf_reg_state, precise))) return false; return true; } From 070587848985efcfcd45104c5266ba76a1165f55 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 19 Sep 2026 03:42:11 +0200 Subject: [PATCH 1288/1317] selftests/bpf: Cover frame changes in bounded loops Add a finite loop whose progress is represented only by changing the frame number of a stack pointer. The loop first reads zero from the caller's stack, switches to the same offset in the callee's stack, and exits after reading one on its next iteration. Force frequent checkpoints so the test exercises infinite-loop detection, and check that the program returns one when run. Without the frameno comparison in regs_exact(), the program is rejected with an "infinite loop detected" diagnostic instead of loading successfully. Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Tested-by: Eduard Zingerman Link: https://patch.msgid.link/20260919014213.1840880-3-memxor@gmail.com --- .../selftests/bpf/progs/verifier_loops1.c | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_loops1.c b/tools/testing/selftests/bpf/progs/verifier_loops1.c index d248ce877f14..48a966cda199 100644 --- a/tools/testing/selftests/bpf/progs/verifier_loops1.c +++ b/tools/testing/selftests/bpf/progs/verifier_loops1.c @@ -303,4 +303,40 @@ __naked void maybe_exit_scc_bug1(void) ::: __clobber_all); } +/* + * The loop reads zero from the caller's stack on its first iteration and + * one from the callee's stack on its second iteration. At the loop header, + * only the frame number of the pointer in r1 changes. + */ +static __naked __noinline __used +void loop_stack_frames_reg(void) +{ + asm volatile ( + "*(u64 *)(r10 - 8) = 1;" +"1:" + "r0 = *(u64 *)(r1 + 0);" + "if r0 != 0 goto 2f;" + "r1 = r10;" + "r1 += -8;" + "goto 1b;" +"2:" + "exit;" + ::: __clobber_all); +} + +SEC("xdp") +__description("bounded loop changing stack frame in a register") +__success __retval(1) +__flag(BPF_F_TEST_STATE_FREQ) +__naked void bounded_loop_stack_frames_reg(void) +{ + asm volatile ( + "*(u64 *)(r10 - 8) = 0;" + "r1 = r10;" + "r1 += -8;" + "call loop_stack_frames_reg;" + "exit;" + ::: __clobber_all); +} + char _license[] SEC("license") = "GPL"; From bfc888f04588f591851e95c974954cfca58e6c19 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:24:42 +0200 Subject: [PATCH 1289/1317] bpf: Bound ownership depth through local kptrs and graph roots Program-allocated objects can own other local objects through referenced kptrs. bpf_obj_free_fields() follows those pointers through __bpf_obj_drop_impl() synchronously, before the object storage is freed through RCU. A self-referential local kptr type therefore permits arbitrarily deep object chains, and dropping the head can exhaust the kernel stack. Long acyclic type chains have the same problem. btf_check_and_fixup_fields() still assumes referenced kptrs only point to kernel types and checks ownership through list and rbtree roots only. Its existing rule is sufficient for graph-only cycles: the target of each graph edge must contain a node, so every type in a cycle has both a root and a node. The rule rejects such a type owning another root, breaking every cycle. It also limits graph-only chains to three types, or two if the first type contains a node, and conservatively rejects longer acyclic chains. The missing local-kptr edges, rather than a missed graph-only cycle, are the bug introduced by support for bpf_kptr_xchg() into local kptrs. Replace that restriction with one bounded ownership walk covering graph roots and local referenced kptrs. Run it after all BTF records have been fixed up, reject cycles and paths deeper than eight record-bearing types, and cache each type's suffix depth while checking it against the remaining budget. This also permits the longer acyclic graph-only layouts rejected by the old rule; update their existing BTF tests accordingly. Keep the bound independent of MAX_CALL_FRAMES because recursive destruction can run below a BPF call chain. A plain local pointee without special-field metadata adds only a final non-recursing drop. Non-owning kptrs and kernel-BTF kptrs do not recurse through local records and remain outside the walk. Include local percpu-kptr edges too, although allocation of percpu objects with special fields is currently forbidden, so that relaxing that restriction cannot bypass the ownership bound. btf_check_and_fixup_fields() continues to initialize graph_root.value_rec, including for separately allocated map records. The ownership relationships belong to immutable program BTF and only need validation at BTF load time. Fixes: b0966c724584 ("bpf: Support bpf_kptr_xchg into local kptr") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260914132444.2564218-2-memxor@gmail.com --- kernel/bpf/btf.c | 138 +++++++++++------- .../selftests/bpf/prog_tests/linked_list.c | 4 +- 2 files changed, 90 insertions(+), 52 deletions(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index d67a169ba2bb..d870bc5e50bc 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -4268,13 +4268,10 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec) { int i; - /* There are three types that signify ownership of some other type: - * kptr_ref, bpf_list_head, bpf_rb_root. - * kptr_ref only supports storing kernel types, which can't store - * references to program allocated local types. - * - * Hence we only need to ensure that bpf_{list_head,rb_root} ownership - * does not form cycles. + /* + * Check fields which require the complete BTF and initialize runtime + * metadata. Ownership relationships are validated after every record has + * been fixed up. */ if (IS_ERR_OR_NULL(rec) || !(rec->field_mask & (BPF_GRAPH_ROOT | BPF_UPTR))) return 0; @@ -4305,53 +4302,90 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec) if (!meta) return -EFAULT; rec->fields[i].graph_root.value_rec = meta->record; - - /* We need to set value_rec for all root types, but no need - * to check ownership cycle for a type unless it's also a - * node type. - */ - if (!(rec->field_mask & BPF_GRAPH_NODE)) - continue; - - /* We need to ensure ownership acyclicity among all types. The - * proper way to do it would be to topologically sort all BTF - * IDs based on the ownership edges, since there can be multiple - * bpf_{list_head,rb_node} in a type. Instead, we use the - * following resaoning: - * - * - A type can only be owned by another type in user BTF if it - * has a bpf_{list,rb}_node. Let's call these node types. - * - A type can only _own_ another type in user BTF if it has a - * bpf_{list_head,rb_root}. Let's call these root types. - * - * We ensure that if a type is both a root and node, its - * element types cannot be root types. - * - * To ensure acyclicity: - * - * When A is an root type but not a node, its ownership - * chain can be: - * A -> B -> C - * Where: - * - A is an root, e.g. has bpf_rb_root. - * - B is both a root and node, e.g. has bpf_rb_node and - * bpf_list_head. - * - C is only an root, e.g. has bpf_list_node - * - * When A is both a root and node, some other type already - * owns it in the BTF domain, hence it can not own - * another root type through any of the ownership edges. - * A -> B - * Where: - * - A is both an root and node. - * - B is only an node. - */ - if (meta->record->field_mask & BPF_GRAPH_ROOT) - return -ELOOP; } return 0; } +static int btf_owned_type_idx(const struct btf *btf, struct btf_struct_metas *tab, + const struct btf_field *field) +{ + struct btf_struct_meta *meta; + u32 btf_id; + + if (field->type & BPF_GRAPH_ROOT) { + btf_id = field->graph_root.value_btf_id; + } else if (field->type == BPF_KPTR_REF || field->type == BPF_KPTR_PERCPU) { + if (btf_is_kernel(field->kptr.btf)) + return -ENOENT; + btf_id = field->kptr.btf_id; + } else { + return -ENOENT; + } + + meta = btf_find_struct_meta(btf, btf_id); + if (!meta) + return field->type & BPF_GRAPH_ROOT ? -EFAULT : -ENOENT; + return meta - tab->types; +} + +/* + * Each ownership edge adds kernel frames through bpf_obj_free_fields() and + * __bpf_obj_drop_impl(). Keep the bound deliberately small because object + * destruction can itself run below a BPF call chain. A final pointee without + * special fields is not present in the struct metadata table and adds only a + * non-recursing drop. + */ +#define BTF_MAX_OWNERSHIP_DEPTH 8 + +static int btf_ownership_depth(const struct btf *btf, + struct btf_struct_metas *tab, u8 *depth, + int idx, int depth_left) +{ + const struct btf_record *rec = tab->types[idx].record; + int i, ret, max_depth = 0; + + if (!depth_left) + return -ELOOP; + if (depth[idx]) + goto done; + + for (i = 0; i < rec->cnt; i++) { + ret = btf_owned_type_idx(btf, tab, &rec->fields[i]); + if (ret == -ENOENT) + continue; + if (ret < 0) + return ret; + ret = btf_ownership_depth(btf, tab, depth, ret, depth_left - 1); + if (ret < 0) + return ret; + max_depth = max(max_depth, ret); + } + depth[idx] = max_depth + 1; +done: + return depth[idx] > depth_left ? -ELOOP : depth[idx]; +} + +static int btf_check_ownership_depth(const struct btf *btf, + struct btf_struct_metas *tab) +{ + u8 *depth; + int i, ret = 0; + + depth = kvcalloc(tab->cnt, sizeof(*depth), GFP_KERNEL | __GFP_NOWARN); + if (!depth) + return -ENOMEM; + + for (i = 0; i < tab->cnt; i++) { + ret = btf_ownership_depth(btf, tab, depth, i, + BTF_MAX_OWNERSHIP_DEPTH); + if (ret < 0) + break; + ret = 0; + } + kvfree(depth); + return ret; +} + static void __btf_struct_show(const struct btf *btf, const struct btf_type *t, u32 type_id, void *data, u8 bits_offset, struct btf_show *show) @@ -6044,6 +6078,10 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr, if (err < 0) goto errout_meta; } + + err = btf_check_ownership_depth(btf, struct_meta_tab); + if (err < 0) + goto errout_meta; } err = bpf_log_attr_finalize(attr_log, &env->log); diff --git a/tools/testing/selftests/bpf/prog_tests/linked_list.c b/tools/testing/selftests/bpf/prog_tests/linked_list.c index c3d133c6a00d..52fabbee3dd5 100644 --- a/tools/testing/selftests/bpf/prog_tests/linked_list.c +++ b/tools/testing/selftests/bpf/prog_tests/linked_list.c @@ -714,7 +714,7 @@ static void test_btf(void) break; err = btf__load_into_kernel(btf); - ASSERT_EQ(err, -ELOOP, "check btf"); + ASSERT_EQ(err, 0, "check btf"); btf__free(btf); break; } @@ -773,7 +773,7 @@ static void test_btf(void) break; err = btf__load_into_kernel(btf); - ASSERT_EQ(err, -ELOOP, "check btf"); + ASSERT_EQ(err, 0, "check btf"); btf__free(btf); break; } From 0288ed67482b6e370eb3fa6b07720df435907970 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:24:43 +0200 Subject: [PATCH 1290/1317] selftests/bpf: Check local object ownership depth Build raw program BTF records to exercise local object ownership without constructing a runtime chain deep enough to threaten the kernel stack. Cover referenced-kptr and percpu-kptr self-cycles, a two-type cycle, and a cycle mixing a graph root with a referenced kptr. The existing graph-only check accepts these local-kptr cycles and over-limit kptr chains; the fix rejects them with -ELOOP. Pin the eight-record depth boundary with a terminal plain object. Exercise both parent-first and child-first BTF orders, and a shared suffix reached first through a shorter path. These cases require cached suffix depths to be checked against the remaining depth budget on each path. Check list and rbtree chains of three, four, eight, and nine types. This covers the old graph-only depth boundary and the new explicit bound. The existing linked-list BTF tests still reject pure graph cycles and now accept the longer acyclic layouts previously rejected by the conservative rule. Although bpf_percpu_obj_new() currently rejects types with special fields, require the percpu cycle to fail at BTF load so future support cannot bypass the ownership bound. Keep a positive control for a non-owning kptr, which remains outside the ownership graph. Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260914132444.2564218-3-memxor@gmail.com --- .../bpf/prog_tests/local_kptr_ownership.c | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c diff --git a/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c b/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c new file mode 100644 index 000000000000..a487aa68f2ee --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include +#include +#include + +#define SPIN_LOCK 2 +#define LIST_HEAD 3 +#define LIST_NODE 4 +/* Keep in sync with BTF_MAX_OWNERSHIP_DEPTH. */ +#define MAX_OWNERSHIP_DEPTH 8 + +static struct btf *init_btf(void) +{ + struct btf *btf; + int id; + + btf = btf__new_empty(); + if (!ASSERT_OK_PTR(btf, "btf__new_empty")) + return NULL; + id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED); + if (!ASSERT_EQ(id, 1, "btf__add_int")) + goto err_out; + id = btf__add_struct(btf, "bpf_spin_lock", 4); + if (!ASSERT_EQ(id, SPIN_LOCK, "btf__add_struct bpf_spin_lock")) + goto err_out; + id = btf__add_struct(btf, "bpf_list_head", 16); + if (!ASSERT_EQ(id, LIST_HEAD, "btf__add_struct bpf_list_head")) + goto err_out; + id = btf__add_struct(btf, "bpf_list_node", 24); + if (!ASSERT_EQ(id, LIST_NODE, "btf__add_struct bpf_list_node")) + goto err_out; + return btf; + +err_out: + btf__free(btf); + return NULL; +} + +static int add_local_kptr(struct btf *btf, int pointee_id, const char *tag) +{ + int id; + + id = btf__add_type_tag(btf, tag, pointee_id); + if (!ASSERT_GT(id, 0, "btf__add_type_tag")) + return id; + id = btf__add_ptr(btf, id); + ASSERT_GT(id, 0, "btf__add_ptr"); + return id; +} + +static void test_self_cycle(const char *tag, int expected_err) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 7, tag); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "self_cycle", 8); + if (!ASSERT_EQ(id, 7, "btf__add_struct self_cycle")) + goto out; + err = btf__add_field(btf, "next", 6, 0, 0); + if (!ASSERT_OK(err, "btf__add_field self_cycle::next")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +static void test_aba_cycle(void) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 10, "kptr"); + if (id <= 0) + goto out; + id = add_local_kptr(btf, 9, "kptr"); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "cycle_a", 8); + if (!ASSERT_EQ(id, 9, "btf__add_struct cycle_a")) + goto out; + err = btf__add_field(btf, "b", 6, 0, 0); + if (!ASSERT_OK(err, "btf__add_field cycle_a::b")) + goto out; + id = btf__add_struct(btf, "cycle_b", 8); + if (!ASSERT_EQ(id, 10, "btf__add_struct cycle_b")) + goto out; + err = btf__add_field(btf, "a", 8, 0, 0); + if (!ASSERT_OK(err, "btf__add_field cycle_b::a")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, -ELOOP, "check btf"); +out: + btf__free(btf); +} + +static void test_mixed_cycle(void) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 7, "kptr"); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "mixed_owner", 20); + if (!ASSERT_EQ(id, 7, "btf__add_struct mixed_owner")) + goto out; + err = btf__add_field(btf, "root", LIST_HEAD, 0, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_owner::root")) + goto out; + err = btf__add_field(btf, "lock", SPIN_LOCK, 128, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_owner::lock")) + goto out; + id = btf__add_decl_tag(btf, "contains:mixed_node:node", 7, 0); + if (!ASSERT_EQ(id, 8, "btf__add_decl_tag mixed_owner")) + goto out; + id = btf__add_struct(btf, "mixed_node", 32); + if (!ASSERT_EQ(id, 9, "btf__add_struct mixed_node")) + goto out; + err = btf__add_field(btf, "node", LIST_NODE, 0, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_node::node")) + goto out; + err = btf__add_field(btf, "owner", 6, 192, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_node::owner")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, -ELOOP, "check btf"); +out: + btf__free(btf); +} + +static void test_acyclic_depth(int depth, bool child_first, bool shared_suffix, int expected_err) +{ + int ptr_id[MAX_OWNERSHIP_DEPTH + 1]; + int first_struct_id; + struct btf *btf; + int id, err, i, n, pointee_id; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + first_struct_id = 5 + 2 * depth; + for (i = 0; i < depth; i++) { + if (i == depth - 1) + pointee_id = first_struct_id + depth; + else + pointee_id = first_struct_id + (child_first ? depth - 2 - i : i + 1); + ptr_id[i] = add_local_kptr(btf, pointee_id, "kptr"); + if (ptr_id[i] <= 0) + goto out; + } + for (n = 0; n < depth; n++) { + char name[32]; + int offset = 0; + + i = child_first ? depth - 1 - n : n; + snprintf(name, sizeof(name), "owner_%d", i); + id = btf__add_struct(btf, name, shared_suffix && !i ? 16 : 8); + if (!ASSERT_EQ(id, first_struct_id + n, "btf__add_struct owner")) + goto out; + if (shared_suffix && !i) { + /* + * Visit the shared suffix through the shorter path before + * reaching it again with less remaining depth. + */ + err = btf__add_field(btf, "suffix", ptr_id[1], 0, 0); + if (!ASSERT_OK(err, "btf__add_field owner::suffix")) + goto out; + offset = 64; + } + err = btf__add_field(btf, "next", ptr_id[i], offset, 0); + if (!ASSERT_OK(err, "btf__add_field owner::next")) + goto out; + } + id = btf__add_struct(btf, "plain_leaf", 4); + if (!ASSERT_EQ(id, first_struct_id + depth, "btf__add_struct plain_leaf")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +static void test_graph_depth(bool rbtree, int depth, int expected_err) +{ + int root_type = LIST_HEAD, node_type = LIST_NODE, node_size = 24; + int id, err, i, lock_off, root_off, size; + struct btf *btf; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + if (rbtree) { + root_type = btf__add_struct(btf, "bpf_rb_root", 16); + if (!ASSERT_GT(root_type, 0, "btf__add_struct bpf_rb_root")) + goto out; + node_type = btf__add_struct(btf, "bpf_rb_node", 32); + if (!ASSERT_GT(node_type, 0, "btf__add_struct bpf_rb_node")) + goto out; + node_size = 32; + } + + for (i = 0; i < depth; i++) { + char name[32], tag[64]; + + lock_off = i ? node_size : 0; + root_off = lock_off + 8; + size = i == depth - 1 ? node_size : root_off + 16; + snprintf(name, sizeof(name), "graph_owner_%d", i); + id = btf__add_struct(btf, name, size); + if (!ASSERT_GT(id, 0, "btf__add_struct graph_owner")) + goto out; + if (i) { + err = btf__add_field(btf, "node", node_type, 0, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::node")) + goto out; + } + if (i == depth - 1) + continue; + err = btf__add_field(btf, "lock", SPIN_LOCK, lock_off * 8, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::lock")) + goto out; + err = btf__add_field(btf, "root", root_type, root_off * 8, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::root")) + goto out; + snprintf(tag, sizeof(tag), "contains:graph_owner_%d:node", i + 1); + err = btf__add_decl_tag(btf, tag, id, i ? 2 : 1); + if (!ASSERT_GT(err, 0, "btf__add_decl_tag graph_owner")) + goto out; + } + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +void test_local_kptr_ownership(void) +{ + if (test__start_subtest("self_cycle")) + test_self_cycle("kptr", -ELOOP); + if (test__start_subtest("untrusted_self_cycle")) + test_self_cycle("kptr_untrusted", 0); + if (test__start_subtest("percpu_self_cycle")) + test_self_cycle("percpu_kptr", -ELOOP); + if (test__start_subtest("ABA_cycle")) + test_aba_cycle(); + if (test__start_subtest("mixed_graph_root_cycle")) + test_mixed_cycle(); + if (test__start_subtest("max_acyclic")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, false, 0); + if (test__start_subtest("too_deep_acyclic")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, false, -ELOOP); + if (test__start_subtest("max_acyclic_child_first")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, true, false, 0); + if (test__start_subtest("too_deep_acyclic_child_first")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, true, false, -ELOOP); + if (test__start_subtest("max_acyclic_shared_suffix")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, true, 0); + if (test__start_subtest("too_deep_acyclic_shared_suffix")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, true, -ELOOP); + if (test__start_subtest("list_three_types")) + test_graph_depth(false, 3, 0); + if (test__start_subtest("list_four_types")) + test_graph_depth(false, 4, 0); + if (test__start_subtest("list_max_depth")) + test_graph_depth(false, MAX_OWNERSHIP_DEPTH, 0); + if (test__start_subtest("list_too_deep")) + test_graph_depth(false, MAX_OWNERSHIP_DEPTH + 1, -ELOOP); + if (test__start_subtest("rbtree_three_types")) + test_graph_depth(true, 3, 0); + if (test__start_subtest("rbtree_four_types")) + test_graph_depth(true, 4, 0); + if (test__start_subtest("rbtree_max_depth")) + test_graph_depth(true, MAX_OWNERSHIP_DEPTH, 0); + if (test__start_subtest("rbtree_too_deep")) + test_graph_depth(true, MAX_OWNERSHIP_DEPTH + 1, -ELOOP); +} From c21eaa72f02fc6e85621cbe09d303d8fb8bd39cd Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Wed, 16 Sep 2026 20:48:30 +0200 Subject: [PATCH 1291/1317] posix-cpu-timers: Prevent freeing a timer which is queued on the expiry list Kijo analyzed another race in the POSIX CPU timer code: Commit bf635681c906 converted cpu_timer::firing from a tristate value to a boolean. This lost the distinction between "not owned by the firing list" and "still owned, but delivery was canceled". The resulting race is: expiry handler timer_settime() timer_delete() -------------- --------------- -------------- collect timer onto private firing list firing = true observes firing = true firing = false return TIMER_RETRY wait for handler observes firing = false finish deletion unhash and free timer resume list traversal read freed elist.next -> UAF The firing bit is clearly the wrong indicator since that commit. Check whether the timer is queued on the expiry list or not instead. If it is queued clear the firing bit to prevent signal delivery as before and return TIMER_RETRY so the caller unlocks the timer which allows the expiry code to make progress and remove it from the list. Fixes: bf635681c906 ("posix-cpu-timers: Cleanup the firing logic") Reported-by: Kijo Park Debugged-by: Kijo Park Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org --- kernel/time/posix-cpu-timers.c | 40 +++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index d73d31c7994f..0bf4fcd969c8 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -408,6 +408,7 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer) new_timer->kclock = &clock_posix_cpu; timerqueue_init(&new_timer->it.cpu.node); + INIT_LIST_HEAD(&new_timer->it.cpu.elist); new_timer->it.cpu.pid = get_pid(pid); rcu_read_unlock(); return 0; @@ -565,6 +566,24 @@ static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned l return NULL; } +/* + * If the timer is queued on the expiry list, then it cannot be dequeued because + * the firing list is not protected by sighand->lock. The delivery path is + * waiting for the timer lock. So go back, unlock and retry. + */ +static bool posix_cpu_timer_on_expiry_list(struct k_itimer *timer) +{ + if (list_empty(&timer->it.cpu.elist)) + return false; + + /* + * Prevent signal delivery as there is no point in delivering a signal + * which is made obsolete right away. + */ + timer->it.cpu.firing = false; + return true; +} + /* * Clean up a CPU-clock timer that is about to be destroyed. * This is called from timer deletion with the timer already locked. @@ -580,18 +599,10 @@ static int posix_cpu_timer_del(struct k_itimer *timer) p = timer_lock_sighand(timer, &flags); if (likely(p)) { - if (timer->it.cpu.firing) { - /* - * Prevent signal delivery. The timer cannot be dequeued - * because it is on the firing list which is not protected - * by sighand->lock. The delivery path is waiting for - * the timer lock. So go back, unlock and retry. - */ - timer->it.cpu.firing = false; + if (posix_cpu_timer_on_expiry_list(timer)) ret = TIMER_RETRY; - } else { + else disarm_timer(timer, p); - } unlock_task_sighand(p, &flags); } @@ -731,14 +742,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, /* Retrieve the current expiry time before disarming the timer */ old_expires = cpu_timer_getexpires(ctmr); - if (unlikely(timer->it.cpu.firing)) { - /* - * Prevent signal delivery. The timer cannot be dequeued - * because it is on the firing list which is not protected - * by sighand->lock. The delivery path is waiting for - * the timer lock. So go back, unlock and retry. - */ - timer->it.cpu.firing = false; + if (posix_cpu_timer_on_expiry_list(timer)) { ret = TIMER_RETRY; } else { cpu_timer_dequeue(ctmr); From a11212910cf09b2fe8db9afa41ef60c4f81879c5 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Sat, 19 Sep 2026 16:45:02 +0800 Subject: [PATCH 1292/1317] bpf: Check params size before reading reserved fields bpf_crypto_ctx_create() is a kfunc whose second argument is declared with the __sz annotation, so the verifier only guarantees that params__sz bytes of params are valid. The function nevertheless reads params->reserved[0] and params->reserved[1] (offsets 14 and 15) before comparing params__sz against the size of struct bpf_crypto_params, so a BPF program can pass a shorter buffer and have the kernel read past the region that was validated for it. Move the size check in front of the reserved field reads. Fixes: 3e1c6f35409f ("bpf: make common crypto API for TC/XDP programs") Reported-by: Vega Signed-off-by: Yuqi Xu Signed-off-by: Alexei Starovoitov Reviewed-by: Ren Wei Link: https://patch.msgid.link/4f3ab4b03e79017e215521743996555439bf0bb3.1789802413.git.xuyuqiabc@gmail.com --- kernel/bpf/crypto.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/crypto.c b/kernel/bpf/crypto.c index 51f89cecefb4..3f3fe2450fc6 100644 --- a/kernel/bpf/crypto.c +++ b/kernel/bpf/crypto.c @@ -149,8 +149,9 @@ bpf_crypto_ctx_create(const struct bpf_crypto_params *params, u32 params__sz, const struct bpf_crypto_type *type; struct bpf_crypto_ctx *ctx; - if (!params || params->reserved[0] || params->reserved[1] || - params__sz != sizeof(struct bpf_crypto_params)) { + if (!params || + params__sz != sizeof(struct bpf_crypto_params) || + params->reserved[0] || params->reserved[1]) { *err = -EINVAL; return NULL; } From f7eeb1af8537b05953fb1c88ab8b59d94059a381 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:01 +0800 Subject: [PATCH 1293/1317] i2c: at91: release DMA channels on remove and probe error at91_twi_configure_dma() requests exclusive tx/rx DMA channels, but nothing ever releases them on driver detach, and the probe error path after the channels are acquired (i2c_add_numbered_adapter() failure) returns without releasing them either, because the remove callback is not invoked after a failed probe. Move the release into a helper, call it from the existing configure-failure path, the adapter-registration failure path, and at91_twi_remove(). Fixes: 60937b2cdbf9 ("i2c: at91: add dma support") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Cc: # v3.8+ Acked-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-1-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-at91-core.c | 3 +++ drivers/i2c/busses/i2c-at91-master.c | 12 +++++++++++- drivers/i2c/busses/i2c-at91.h | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-at91-core.c b/drivers/i2c/busses/i2c-at91-core.c index b64adef778d4..8ca4556d9664 100644 --- a/drivers/i2c/busses/i2c-at91-core.c +++ b/drivers/i2c/busses/i2c-at91-core.c @@ -255,6 +255,7 @@ static int at91_twi_probe(struct platform_device *pdev) if (rc) { pm_runtime_disable(dev->dev); pm_runtime_set_suspended(dev->dev); + at91_twi_dma_release(dev); return rc; } @@ -270,6 +271,8 @@ static void at91_twi_remove(struct platform_device *pdev) i2c_del_adapter(&dev->adapter); + at91_twi_dma_release(dev); + pm_runtime_disable(dev->dev); pm_runtime_set_suspended(dev->dev); } diff --git a/drivers/i2c/busses/i2c-at91-master.c b/drivers/i2c/busses/i2c-at91-master.c index 894cedbca99f..68238cc8aee0 100644 --- a/drivers/i2c/busses/i2c-at91-master.c +++ b/drivers/i2c/busses/i2c-at91-master.c @@ -817,11 +817,21 @@ static int at91_twi_configure_dma(struct at91_twi_dev *dev, u32 phy_addr) error: if (ret != -EPROBE_DEFER) dev_info(dev->dev, "can't get DMA channel, continue without DMA support\n"); + at91_twi_dma_release(dev); + return ret; +} + +void at91_twi_dma_release(struct at91_twi_dev *dev) +{ + struct at91_twi_dma *dma = &dev->dma; + if (dma->chan_rx) dma_release_channel(dma->chan_rx); if (dma->chan_tx) dma_release_channel(dma->chan_tx); - return ret; + dma->chan_rx = NULL; + dma->chan_tx = NULL; + dev->use_dma = false; } static int at91_init_twi_recovery_gpio(struct platform_device *pdev, diff --git a/drivers/i2c/busses/i2c-at91.h b/drivers/i2c/busses/i2c-at91.h index 942e9c3973bb..d68fcbbc3e0f 100644 --- a/drivers/i2c/busses/i2c-at91.h +++ b/drivers/i2c/busses/i2c-at91.h @@ -172,6 +172,7 @@ void at91_twi_irq_restore(struct at91_twi_dev *dev); void at91_init_twi_bus(struct at91_twi_dev *dev); void at91_init_twi_bus_master(struct at91_twi_dev *dev); +void at91_twi_dma_release(struct at91_twi_dev *dev); int at91_twi_probe_master(struct platform_device *pdev, u32 phy_addr, struct at91_twi_dev *dev); From e9f03b9625e2eeaca357b065c92d5b14064a1583 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:02 +0800 Subject: [PATCH 1294/1317] i2c: imx: release DMA channels on probe error i2c_imx_dma_request() acquires exclusive tx/rx DMA channels and is optional: on errors other than -EPROBE_DEFER the driver falls back to PIO mode and probe continues. If i2c_add_numbered_adapter() then fails, probe returns through clk_notifier_unregister without releasing the channels, because the remove callback is not invoked after a failed probe. Release the channels on the probe error path, mirroring i2c_imx_remove(). Fixes: ce1a78840ff7 ("i2c: imx: add DMA support for freescale i2c driver") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Cc: # v3.19+ Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-2-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-imx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 4dc3df6a41a4..ff5a91158df9 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1880,6 +1880,8 @@ static int i2c_imx_probe(struct platform_device *pdev) clk_notifier_unregister: clk_notifier_unregister(i2c_imx->clk, &i2c_imx->clk_change_nb); + if (i2c_imx->dma) + i2c_imx_dma_free(i2c_imx); free_irq(irq, i2c_imx); rpm_disable: pm_runtime_put_noidle(&pdev->dev); From 268aacb2e2a5c94d08e23194961234d0710c8407 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:03 +0800 Subject: [PATCH 1295/1317] i2c: qcom-geni: release DMA channels on probe error geni_i2c_init() grabs exclusive GPI tx/rx DMA channels when the serial engine runs in GPI mode. If i2c_add_adapter() subsequently fails, probe returns without releasing the channels, because the remove callback is not invoked after a failed probe. The adapter-registration failure path used to release the channels via its err_dma label; that release was dropped when the probe tail was restructured into geni_i2c_init(). Release the channels on the adapter-registration failure path, mirroring geni_i2c_remove(). Fixes: d8d3bb127ad1 ("i2c: qcom-geni: Isolate serial engine setup") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-3-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-qcom-geni.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 00013b41a6f5..e9e41a174c20 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1189,8 +1189,10 @@ static int geni_i2c_probe(struct platform_device *pdev) return ret; ret = i2c_add_adapter(&gi2c->adap); - if (ret) + if (ret) { + release_gpi_dma(gi2c); return dev_err_probe(dev, ret, "Error adding i2c adapter\n"); + } dev_dbg(dev, "Geni-I2C adaptor successfully added\n"); From 7362a1553eb09a8cdf8be7e509bd5309a8342486 Mon Sep 17 00:00:00 2001 From: Liu Zhenlong Date: Wed, 19 Aug 2026 01:57:50 +0800 Subject: [PATCH 1296/1317] i2c: qcom-cci: fix device_node refcount leak in cci_probe()/cci_remove() The of_node_put() matching of_node_get() runs after i2c_del_adapter(), whose trailing memset() zeroes adap->dev and thus adap->dev.of_node, making the put a no-op and leaking the node on every adapter removal and error cleanup. Use a devm action: the pointer is captured at registration, out of reach of that memset(), and devres runs the put once on probe failure and detach, replacing the three manual of_node_put() calls. The setup loop uses the scoped iterator form so the child node is released automatically if devm_add_action_or_reset() fails mid-loop. Suggested-by: Konrad Dybcio Fixes: 02a4a69667a2 ("i2c: qcom-cci: don't put a device tree node before i2c_add_adapter()") Assisted-by: Claude:claude-opus-5 Signed-off-by: Liu Zhenlong Cc: # v5.17+ Reviewed-by: Vladimir Zapolskiy Reviewed-by: Konrad Dybcio Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260818175750.4205-1-dragonliu2018@gmail.com --- drivers/i2c/busses/i2c-qcom-cci.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c index 25b6e4e9e3fa..873e901a23d7 100644 --- a/drivers/i2c/busses/i2c-qcom-cci.c +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -497,10 +497,14 @@ static const struct dev_pm_ops qcom_cci_pm = { SET_RUNTIME_PM_OPS(cci_suspend_runtime, cci_resume_runtime, NULL) }; +static void cci_put_of_node(void *data) +{ + of_node_put(data); +} + static int cci_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *child; struct resource *r; struct cci *cci; int ret, i; @@ -516,7 +520,7 @@ static int cci_probe(struct platform_device *pdev) if (!cci->data) return -ENOENT; - for_each_available_child_of_node(dev->of_node, child) { + for_each_available_child_of_node_scoped(dev->of_node, child) { struct cci_master *master; u32 idx; @@ -537,6 +541,9 @@ static int cci_probe(struct platform_device *pdev) master->adap.algo = &cci_algo; master->adap.dev.parent = dev; master->adap.dev.of_node = of_node_get(child); + ret = devm_add_action_or_reset(dev, cci_put_of_node, child); + if (ret) + return ret; master->master = idx; master->cci = cci; @@ -606,10 +613,8 @@ static int cci_probe(struct platform_device *pdev) continue; ret = i2c_add_adapter(&cci->master[i].adap); - if (ret < 0) { - of_node_put(cci->master[i].adap.dev.of_node); + if (ret < 0) goto error_i2c; - } } return 0; @@ -617,10 +622,8 @@ static int cci_probe(struct platform_device *pdev) error_i2c: for (--i ; i >= 0; i--) { - if (cci->master[i].cci) { + if (cci->master[i].cci) i2c_del_adapter(&cci->master[i].adap); - of_node_put(cci->master[i].adap.dev.of_node); - } } disable_clocks: cci_disable_clocks(cci); @@ -636,7 +639,6 @@ static void cci_remove(struct platform_device *pdev) for (i = 0; i < cci->data->num_masters; i++) { if (cci->master[i].cci) { i2c_del_adapter(&cci->master[i].adap); - of_node_put(cci->master[i].adap.dev.of_node); cci_halt(cci, i); } } From 6a5719cc3ef2e4d9857cc4ae18e6db09d59a8cc9 Mon Sep 17 00:00:00 2001 From: Daniel J Blueman Date: Sun, 20 Sep 2026 16:16:08 +0200 Subject: [PATCH 1297/1317] net: qrtr: resend HELLO on MHI resume Since the MHI HELLO exchange was relocated, it is sent only at device registration. During a suspend-resume cycle, the firmware in WiFi cards such as WCN7850 indefinitely waits for another HELLO, triggering: ath12k_wifi7_pci 0004:01:00.0: timeout while waiting for restart complete ath12k_wifi7_pci 0004:01:00.0: failed to resume core: -110 Fix this by triggering the handshake from resume_early in the MHI transport. Validated on Qualcomm X1E-801800 on Lenovo Slim 7x across 10 suspend-resume cycles. Fixes: 544d85de4dc2 ("net: qrtr: Send HELLO message on endpoint register") Signed-off-by: Daniel J Blueman Reviewed-by: Manivannan Sadhasivam Reported-by: Jeff Johnson Link: https://lore.kernel.org/all/6257c447-788d-4362-851e-0d552bcf7c56@oss.qualcomm.com/ Tested-by: Jeff Johnson Reported-by: Vlastimil Babka (SUSE) Link: https://lore.kernel.org/all/ab1491bb-cca5-4145-ac7d-31c966abf7b4@suse.com/ Tested-by: Vlastimil Babka (SUSE) Reported-by: Takashi Iwai Link: https://lore.kernel.org/all/87a4plsg4w.wl-tiwai@suse.de/ Tested-by: Takashi Iwai Signed-off-by: Thorsten Leemhuis Signed-off-by: Linus Torvalds --- net/qrtr/af_qrtr.c | 13 +++++++++++++ net/qrtr/mhi.c | 9 ++++++++- net/qrtr/qrtr.h | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index 78347c937af7..e7b3647424b8 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -623,6 +623,19 @@ static void qrtr_hello_work(struct work_struct *work) qrtr_port_put(ctrl); } +/* Trigger the HELLO handshake after the remote has been reset, eg on resume */ +void qrtr_endpoint_hello(struct qrtr_endpoint *ep) +{ + struct qrtr_node *node = ep->node; + + mutex_lock(&node->ep_lock); + node->hello_sent = false; + mutex_unlock(&node->ep_lock); + + schedule_delayed_work(&node->say_hello, 0); +} +EXPORT_SYMBOL_GPL(qrtr_endpoint_hello); + /** * qrtr_endpoint_register() - register a new endpoint * @ep: endpoint to register diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c index 3990da1a65dc..e9a4bb92ce76 100644 --- a/net/qrtr/mhi.c +++ b/net/qrtr/mhi.c @@ -183,6 +183,7 @@ static int __maybe_unused qcom_mhi_qrtr_pm_suspend_late(struct device *dev) static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev) { struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev); + struct qrtr_mhi_dev *qdev = dev_get_drvdata(dev); enum mhi_state state; int rc; @@ -201,7 +202,13 @@ static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev) return rc; } - return qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); + rc = qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); + if (rc) + return rc; + + qrtr_endpoint_hello(&qdev->ep); + + return 0; } static const struct dev_pm_ops qcom_mhi_qrtr_pm_ops = { diff --git a/net/qrtr/qrtr.h b/net/qrtr/qrtr.h index 3f2d28696062..de2de69a6199 100644 --- a/net/qrtr/qrtr.h +++ b/net/qrtr/qrtr.h @@ -27,6 +27,8 @@ int qrtr_endpoint_register(struct qrtr_endpoint *ep, unsigned int nid); void qrtr_endpoint_unregister(struct qrtr_endpoint *ep); +void qrtr_endpoint_hello(struct qrtr_endpoint *ep); + int qrtr_endpoint_post(struct qrtr_endpoint *ep, const void *data, size_t len); int qrtr_ns_init(void); From 93f51579e7df248780214094418f205253383cc5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 20 Sep 2026 13:48:15 -0700 Subject: [PATCH 1298/1317] Linux 7.3-rc4 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0f1b80100b47..751a08643bf8 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc3 +EXTRAVERSION = -rc4 NAME = Baby Opossum Posse # *DOCUMENTATION* From ae2c5bf969573708cd5b6bb6393631255d83c3fe Mon Sep 17 00:00:00 2001 From: Prathamesh Shete Date: Wed, 16 Sep 2026 06:51:32 +0000 Subject: [PATCH 1299/1317] pinctrl: tegra238: Fix register bank for AON pin groups The AON pin controller has a single register region and therefore, the bank defined in the tegra238_functions[] and tegra238_aon_groups[] for the AON pin groups must be 0. However, commit 25cac7292d49 ("pinctrl: tegra: Add Tegra238 pinmux driver") incorrectly specified the bank for these pins as 1 and not 0. This means that in the tegra_pinctrl_probe() function we use an invalid index when accessing the pmx->regs[] array which causes an incorrect address to be used for accessing the pinmux registers. Fix this by correcting the bank for the AON pin groups. Fixes: 25cac7292d49 ("pinctrl: tegra: Add Tegra238 pinmux driver") Signed-off-by: Prathamesh Shete Signed-off-by: Linus Walleij --- drivers/pinctrl/tegra/pinctrl-tegra238.c | 204 +++++++++++------------ 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/drivers/pinctrl/tegra/pinctrl-tegra238.c b/drivers/pinctrl/tegra/pinctrl-tegra238.c index ec482365f14f..40aba285944e 100644 --- a/drivers/pinctrl/tegra/pinctrl-tegra238.c +++ b/drivers/pinctrl/tegra/pinctrl-tegra238.c @@ -1744,57 +1744,57 @@ static const char * const tegra238_functions[] = { #define drive_sdmmc1_dat0_pu2 DRV_PINGROUP_ENTRY_Y(0x8034, 28, 2, 30, 2, -1, -1, -1, -1, 0) #define drive_ufs0_rst_n_pv1 DRV_PINGROUP_ENTRY_Y(0x11004, 12, 5, 24, 5, -1, -1, -1, -1, 0) #define drive_ufs0_ref_clk_pv0 DRV_PINGROUP_ENTRY_Y(0x1100c, 12, 5, 24, 5, -1, -1, -1, -1, 0) -#define drive_batt_oc_paa4 DRV_PINGROUP_ENTRY_Y(0x1024, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_bootv_ctl_n_paa0 DRV_PINGROUP_ENTRY_Y(0x102c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_vcomp_alert_paa2 DRV_PINGROUP_ENTRY_Y(0x105c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_hdmi_cec_pbb0 DRV_PINGROUP_ENTRY_Y(0x1064, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_touch_clk_pdd3 DRV_PINGROUP_ENTRY_Y(0x106c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart3_rx_pcc6 DRV_PINGROUP_ENTRY_Y(0x1074, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart3_tx_pcc5 DRV_PINGROUP_ENTRY_Y(0x107c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen8_i2c_sda_pdd2 DRV_PINGROUP_ENTRY_Y(0x1084, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen8_i2c_scl_pdd1 DRV_PINGROUP_ENTRY_Y(0x108c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_mosi_pcc2 DRV_PINGROUP_ENTRY_Y(0x1094, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen2_i2c_scl_pcc7 DRV_PINGROUP_ENTRY_Y(0x109c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_cs0_pcc3 DRV_PINGROUP_ENTRY_Y(0x10a4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen2_i2c_sda_pdd0 DRV_PINGROUP_ENTRY_Y(0x10ac, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_sck_pcc0 DRV_PINGROUP_ENTRY_Y(0x10b4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_miso_pcc1 DRV_PINGROUP_ENTRY_Y(0x10bc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio49_pee2 DRV_PINGROUP_ENTRY_Y(0x10c4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio50_pee4 DRV_PINGROUP_ENTRY_Y(0x10cc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio82_pee3 DRV_PINGROUP_ENTRY_Y(0x10d4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio71_pff2 DRV_PINGROUP_ENTRY_Y(0x10dc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio76_pff7 DRV_PINGROUP_ENTRY_Y(0x10e4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio74_pff5 DRV_PINGROUP_ENTRY_Y(0x10ec, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio00_paa1 DRV_PINGROUP_ENTRY_Y(0x10f4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio19_pdd6 DRV_PINGROUP_ENTRY_Y(0x10fc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio86_phh3 DRV_PINGROUP_ENTRY_Y(0x1104, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio72_pff3 DRV_PINGROUP_ENTRY_Y(0x110c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio77_pgg0 DRV_PINGROUP_ENTRY_Y(0x1114, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio80_pff6 DRV_PINGROUP_ENTRY_Y(0x111c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio84_pgg1 DRV_PINGROUP_ENTRY_Y(0x1124, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio83_pee5 DRV_PINGROUP_ENTRY_Y(0x112c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio73_pff4 DRV_PINGROUP_ENTRY_Y(0x1134, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio70_pff1 DRV_PINGROUP_ENTRY_Y(0x113c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio04_paa5 DRV_PINGROUP_ENTRY_Y(0x1144, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio85_pgg6 DRV_PINGROUP_ENTRY_Y(0x114c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio69_pff0 DRV_PINGROUP_ENTRY_Y(0x1154, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio25_paa6 DRV_PINGROUP_ENTRY_Y(0x115c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio26_paa7 DRV_PINGROUP_ENTRY_Y(0x1164, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_tx_pgg7 DRV_PINGROUP_ENTRY_Y(0x116c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_rx_phh0 DRV_PINGROUP_ENTRY_Y(0x1174, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_tx_pgg2 DRV_PINGROUP_ENTRY_Y(0x117c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_rx_pgg3 DRV_PINGROUP_ENTRY_Y(0x1184, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_cts_pgg5 DRV_PINGROUP_ENTRY_Y(0x118c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_rts_pgg4 DRV_PINGROUP_ENTRY_Y(0x1194, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_cts_phh2 DRV_PINGROUP_ENTRY_Y(0x119c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_rts_phh1 DRV_PINGROUP_ENTRY_Y(0x11a4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm7_pee1 DRV_PINGROUP_ENTRY_Y(0x11ac, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm2_pdd7 DRV_PINGROUP_ENTRY_Y(0x11b4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm3_pee0 DRV_PINGROUP_ENTRY_Y(0x11bc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm1_paa3 DRV_PINGROUP_ENTRY_Y(0x11c4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_cs1_pcc4 DRV_PINGROUP_ENTRY_Y(0x11cc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_dmic1_clk_pdd4 DRV_PINGROUP_ENTRY_Y(0x11d4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_dmic1_dat_pdd5 DRV_PINGROUP_ENTRY_Y(0x11dc, 12, 5, 20, 5, -1, -1, -1, -1, 1) +#define drive_batt_oc_paa4 DRV_PINGROUP_ENTRY_Y(0x1024, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_bootv_ctl_n_paa0 DRV_PINGROUP_ENTRY_Y(0x102c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_vcomp_alert_paa2 DRV_PINGROUP_ENTRY_Y(0x105c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_hdmi_cec_pbb0 DRV_PINGROUP_ENTRY_Y(0x1064, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_touch_clk_pdd3 DRV_PINGROUP_ENTRY_Y(0x106c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart3_rx_pcc6 DRV_PINGROUP_ENTRY_Y(0x1074, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart3_tx_pcc5 DRV_PINGROUP_ENTRY_Y(0x107c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen8_i2c_sda_pdd2 DRV_PINGROUP_ENTRY_Y(0x1084, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen8_i2c_scl_pdd1 DRV_PINGROUP_ENTRY_Y(0x108c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_mosi_pcc2 DRV_PINGROUP_ENTRY_Y(0x1094, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen2_i2c_scl_pcc7 DRV_PINGROUP_ENTRY_Y(0x109c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_cs0_pcc3 DRV_PINGROUP_ENTRY_Y(0x10a4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen2_i2c_sda_pdd0 DRV_PINGROUP_ENTRY_Y(0x10ac, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_sck_pcc0 DRV_PINGROUP_ENTRY_Y(0x10b4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_miso_pcc1 DRV_PINGROUP_ENTRY_Y(0x10bc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio49_pee2 DRV_PINGROUP_ENTRY_Y(0x10c4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio50_pee4 DRV_PINGROUP_ENTRY_Y(0x10cc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio82_pee3 DRV_PINGROUP_ENTRY_Y(0x10d4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio71_pff2 DRV_PINGROUP_ENTRY_Y(0x10dc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio76_pff7 DRV_PINGROUP_ENTRY_Y(0x10e4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio74_pff5 DRV_PINGROUP_ENTRY_Y(0x10ec, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio00_paa1 DRV_PINGROUP_ENTRY_Y(0x10f4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio19_pdd6 DRV_PINGROUP_ENTRY_Y(0x10fc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio86_phh3 DRV_PINGROUP_ENTRY_Y(0x1104, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio72_pff3 DRV_PINGROUP_ENTRY_Y(0x110c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio77_pgg0 DRV_PINGROUP_ENTRY_Y(0x1114, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio80_pff6 DRV_PINGROUP_ENTRY_Y(0x111c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio84_pgg1 DRV_PINGROUP_ENTRY_Y(0x1124, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio83_pee5 DRV_PINGROUP_ENTRY_Y(0x112c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio73_pff4 DRV_PINGROUP_ENTRY_Y(0x1134, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio70_pff1 DRV_PINGROUP_ENTRY_Y(0x113c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio04_paa5 DRV_PINGROUP_ENTRY_Y(0x1144, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio85_pgg6 DRV_PINGROUP_ENTRY_Y(0x114c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio69_pff0 DRV_PINGROUP_ENTRY_Y(0x1154, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio25_paa6 DRV_PINGROUP_ENTRY_Y(0x115c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio26_paa7 DRV_PINGROUP_ENTRY_Y(0x1164, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_tx_pgg7 DRV_PINGROUP_ENTRY_Y(0x116c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_rx_phh0 DRV_PINGROUP_ENTRY_Y(0x1174, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_tx_pgg2 DRV_PINGROUP_ENTRY_Y(0x117c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_rx_pgg3 DRV_PINGROUP_ENTRY_Y(0x1184, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_cts_pgg5 DRV_PINGROUP_ENTRY_Y(0x118c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_rts_pgg4 DRV_PINGROUP_ENTRY_Y(0x1194, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_cts_phh2 DRV_PINGROUP_ENTRY_Y(0x119c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_rts_phh1 DRV_PINGROUP_ENTRY_Y(0x11a4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm7_pee1 DRV_PINGROUP_ENTRY_Y(0x11ac, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm2_pdd7 DRV_PINGROUP_ENTRY_Y(0x11b4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm3_pee0 DRV_PINGROUP_ENTRY_Y(0x11bc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm1_paa3 DRV_PINGROUP_ENTRY_Y(0x11c4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_cs1_pcc4 DRV_PINGROUP_ENTRY_Y(0x11cc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_dmic1_clk_pdd4 DRV_PINGROUP_ENTRY_Y(0x11d4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_dmic1_dat_pdd5 DRV_PINGROUP_ENTRY_Y(0x11dc, 12, 5, 20, 5, -1, -1, -1, -1, 0) #define drive_sdmmc1_comp DRV_PINGROUP_ENTRY_N @@ -1961,57 +1961,57 @@ static const struct tegra_pingroup tegra238_groups[] = { }; static const struct tegra_pingroup tegra238_aon_groups[] = { - PINGROUP(bootv_ctl_n_paa0, RSVD0, RSVD1, RSVD2, RSVD3, 0x1028, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio00_paa1, RSVD0, RSVD1, RSVD2, RSVD3, 0x10f0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(vcomp_alert_paa2, SOC_THERM_OC1, RSVD1, RSVD2, RSVD3, 0x1058, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm1_paa3, GP_PWM1, RSVD1, RSVD2, RSVD3, 0x11c0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(batt_oc_paa4, SOC_THERM_OC2, RSVD1, RSVD2, RSVD3, 0x1020, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio04_paa5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1140, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio25_paa6, RSVD0, RSVD1, RSVD2, RSVD3, 0x1158, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio26_paa7, RSVD0, SOC_THERM_OC3, RSVD2, RSVD3, 0x1160, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(hdmi_cec_pbb0, HDMI_CEC, RSVD1, RSVD2, RSVD3, 0x1060, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_sck_pcc0, SPI2_SCK, RSVD1, RSVD2, RSVD3, 0x10b0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_miso_pcc1, SPI2_DIN, RSVD1, RSVD2, RSVD3, 0x10b8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_mosi_pcc2, SPI2_DOUT, RSVD1, RSVD2, RSVD3, 0x1090, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_cs0_pcc3, SPI2_CS0, RSVD1, RSVD2, RSVD3, 0x10a0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_cs1_pcc4, SPI2_CS1, RSVD1, RSVD2, RSVD3, 0x11c8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(uart3_tx_pcc5, UARTC_TXD, RSVD1, RSVD2, RSVD3, 0x1078, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart3_rx_pcc6, UARTC_RXD, RSVD1, RSVD2, RSVD3, 0x1070, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen2_i2c_scl_pcc7, I2C2_CLK, RSVD1, RSVD2, RSVD3, 0x1098, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen2_i2c_sda_pdd0, I2C2_DAT, RSVD1, RSVD2, RSVD3, 0x10a8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen8_i2c_scl_pdd1, I2C8_CLK, RSVD1, RSVD2, RSVD3, 0x1088, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen8_i2c_sda_pdd2, I2C8_DAT, RSVD1, RSVD2, RSVD3, 0x1080, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(touch_clk_pdd3, GP_PWM4, TOUCH_CLK, RSVD2, RSVD3, 0x1068, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(dmic1_clk_pdd4, DMIC1_CLK, RSVD1, DMIC5_CLK, RSVD3, 0x11d0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(dmic1_dat_pdd5, DMIC1_DAT, RSVD1, DMIC5_DAT, RSVD3, 0x11d8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio19_pdd6, RSVD0, WDT_RESET_OUTB, RSVD2, RSVD3, 0x10f8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio49_pee2, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio50_pee4, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio82_pee3, RSVD0, RSVD1, RSVD2, RSVD3, 0x10d0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio71_pff2, PPC_MODE_1, RSVD1, RSVD2, RSVD3, 0x10d8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio76_pff7, RSVD0, RSVD1, TSC_EDGE_OUT0, TSC_EDGE_OUT0A, 0x10e0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio74_pff5, PPC_READY, PPC_I2C_DAT, RSVD2, RSVD3, 0x10e8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio86_phh3, RSVD0, SPI5_CS1, TSC_EDGE_OUT3, TSC_EDGE_OUT0D, 0x1100, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio72_pff3, PPC_MODE_2, RSVD1, RSVD2, RSVD3, 0x1108, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio77_pgg0, RSVD0, RSVD1, TSC_EDGE_OUT1, TSC_EDGE_OUT0B, 0x1110, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio80_pff6, RSVD0, PPC_RST_N, RSVD2, RSVD3, 0x1118, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio84_pgg1, RSVD0, RSVD1, TSC_EDGE_OUT2, TSC_EDGE_OUT0C, 0x1120, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio83_pee5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1128, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio73_pff4, PPC_CC, PPC_I2C_CLK, RSVD2, RSVD3, 0x1130, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio70_pff1, PPC_MODE_0, RSVD1, RSVD2, RSVD3, 0x1138, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio85_pgg6, RSVD0, SPI4_CS1, RSVD2, RSVD3, 0x1148, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio69_pff0, PPC_INT_N, RSVD1, RSVD2, RSVD3, 0x1150, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_tx_pgg7, UARTE_TXD, SPI5_SCK, RSVD2, RSVD3, 0x1168, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_rx_phh0, UARTE_RXD, SPI5_MISO, RSVD2, RSVD3, 0x1170, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_tx_pgg2, UARTB_TXD, SPI4_SCK, RSVD2, RSVD3, 0x1178, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_rx_pgg3, UARTB_RXD, SPI4_MISO, RSVD2, RSVD3, 0x1180, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_cts_pgg5, UARTB_CTS, SPI4_CS0, RSVD2, RSVD3, 0x1188, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_rts_pgg4, UARTB_RTS, SPI4_MOSI, RSVD2, RSVD3, 0x1190, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_cts_phh2, UARTE_CTS, SPI5_CS0, RSVD2, RSVD3, 0x1198, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_rts_phh1, UARTE_RTS, SPI5_MOSI, RSVD2, RSVD3, 0x11a0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm2_pdd7, GP_PWM2, LED_BLINK, RSVD2, RSVD3, 0x11b0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm3_pee0, GP_PWM3, RSVD1, RSVD2, RSVD3, 0x11b8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm7_pee1, GP_PWM7, RSVD1, RSVD2, RSVD3, 0x11a8, 1, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(bootv_ctl_n_paa0, RSVD0, RSVD1, RSVD2, RSVD3, 0x1028, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio00_paa1, RSVD0, RSVD1, RSVD2, RSVD3, 0x10f0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(vcomp_alert_paa2, SOC_THERM_OC1, RSVD1, RSVD2, RSVD3, 0x1058, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm1_paa3, GP_PWM1, RSVD1, RSVD2, RSVD3, 0x11c0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(batt_oc_paa4, SOC_THERM_OC2, RSVD1, RSVD2, RSVD3, 0x1020, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio04_paa5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1140, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio25_paa6, RSVD0, RSVD1, RSVD2, RSVD3, 0x1158, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio26_paa7, RSVD0, SOC_THERM_OC3, RSVD2, RSVD3, 0x1160, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(hdmi_cec_pbb0, HDMI_CEC, RSVD1, RSVD2, RSVD3, 0x1060, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_sck_pcc0, SPI2_SCK, RSVD1, RSVD2, RSVD3, 0x10b0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_miso_pcc1, SPI2_DIN, RSVD1, RSVD2, RSVD3, 0x10b8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_mosi_pcc2, SPI2_DOUT, RSVD1, RSVD2, RSVD3, 0x1090, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_cs0_pcc3, SPI2_CS0, RSVD1, RSVD2, RSVD3, 0x10a0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_cs1_pcc4, SPI2_CS1, RSVD1, RSVD2, RSVD3, 0x11c8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(uart3_tx_pcc5, UARTC_TXD, RSVD1, RSVD2, RSVD3, 0x1078, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart3_rx_pcc6, UARTC_RXD, RSVD1, RSVD2, RSVD3, 0x1070, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen2_i2c_scl_pcc7, I2C2_CLK, RSVD1, RSVD2, RSVD3, 0x1098, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen2_i2c_sda_pdd0, I2C2_DAT, RSVD1, RSVD2, RSVD3, 0x10a8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen8_i2c_scl_pdd1, I2C8_CLK, RSVD1, RSVD2, RSVD3, 0x1088, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen8_i2c_sda_pdd2, I2C8_DAT, RSVD1, RSVD2, RSVD3, 0x1080, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(touch_clk_pdd3, GP_PWM4, TOUCH_CLK, RSVD2, RSVD3, 0x1068, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(dmic1_clk_pdd4, DMIC1_CLK, RSVD1, DMIC5_CLK, RSVD3, 0x11d0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(dmic1_dat_pdd5, DMIC1_DAT, RSVD1, DMIC5_DAT, RSVD3, 0x11d8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio19_pdd6, RSVD0, WDT_RESET_OUTB, RSVD2, RSVD3, 0x10f8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio49_pee2, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio50_pee4, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio82_pee3, RSVD0, RSVD1, RSVD2, RSVD3, 0x10d0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio71_pff2, PPC_MODE_1, RSVD1, RSVD2, RSVD3, 0x10d8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio76_pff7, RSVD0, RSVD1, TSC_EDGE_OUT0, TSC_EDGE_OUT0A, 0x10e0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio74_pff5, PPC_READY, PPC_I2C_DAT, RSVD2, RSVD3, 0x10e8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio86_phh3, RSVD0, SPI5_CS1, TSC_EDGE_OUT3, TSC_EDGE_OUT0D, 0x1100, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio72_pff3, PPC_MODE_2, RSVD1, RSVD2, RSVD3, 0x1108, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio77_pgg0, RSVD0, RSVD1, TSC_EDGE_OUT1, TSC_EDGE_OUT0B, 0x1110, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio80_pff6, RSVD0, PPC_RST_N, RSVD2, RSVD3, 0x1118, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio84_pgg1, RSVD0, RSVD1, TSC_EDGE_OUT2, TSC_EDGE_OUT0C, 0x1120, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio83_pee5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1128, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio73_pff4, PPC_CC, PPC_I2C_CLK, RSVD2, RSVD3, 0x1130, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio70_pff1, PPC_MODE_0, RSVD1, RSVD2, RSVD3, 0x1138, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio85_pgg6, RSVD0, SPI4_CS1, RSVD2, RSVD3, 0x1148, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio69_pff0, PPC_INT_N, RSVD1, RSVD2, RSVD3, 0x1150, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_tx_pgg7, UARTE_TXD, SPI5_SCK, RSVD2, RSVD3, 0x1168, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_rx_phh0, UARTE_RXD, SPI5_MISO, RSVD2, RSVD3, 0x1170, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_tx_pgg2, UARTB_TXD, SPI4_SCK, RSVD2, RSVD3, 0x1178, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_rx_pgg3, UARTB_RXD, SPI4_MISO, RSVD2, RSVD3, 0x1180, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_cts_pgg5, UARTB_CTS, SPI4_CS0, RSVD2, RSVD3, 0x1188, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_rts_pgg4, UARTB_RTS, SPI4_MOSI, RSVD2, RSVD3, 0x1190, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_cts_phh2, UARTE_CTS, SPI5_CS0, RSVD2, RSVD3, 0x1198, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_rts_phh1, UARTE_RTS, SPI5_MOSI, RSVD2, RSVD3, 0x11a0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm2_pdd7, GP_PWM2, LED_BLINK, RSVD2, RSVD3, 0x11b0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm3_pee0, GP_PWM3, RSVD1, RSVD2, RSVD3, 0x11b8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm7_pee1, GP_PWM7, RSVD1, RSVD2, RSVD3, 0x11a8, 0, Y, 5, 7, 6, 8, -1, 10, 12), }; static const struct tegra_pinctrl_soc_data tegra238_pinctrl_aon = { From 94b7e3a7e871ae27d4935c76959dfc61829f27f9 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 19 Sep 2026 22:09:20 +0200 Subject: [PATCH 1300/1317] parisc: Increase kernel stack size to 32kb For 64-bit Linux kernels, increase the default kernel stack size (THREAD_SIZE_ORDER) to 32 kB, in order to avoid kernel crashes which have been triggered recently when building the debian vtk9 package with gcc 17: stackcheck: kworker/u128:0 will most likely overflow kernel stack (sp:179a83af0, stk bottom-top:179a80000-179a84000) Kernel panic - not syncing: low stack detected by irq handler - check messages CPU: 2 UID: 0 PID: 30760 Comm: kworker/u128:0 Tainted: G W 6.18.46-dirty #1 NONE Tainted: [W]=WARN Hardware name: 9000/800/rp3440 Workqueue: writeback wb_workfn (flush-259:0) Backtrace: [<000000004022f050>] show_stack+0x70/0x90 [<000000004022378c>] dump_stack_lvl+0x124/0x190 [<000000004022382c>] dump_stack+0x34/0x48 [<000000004020212c>] vpanic+0x204/0x648 [<00000000402025c4>] panic+0x54/0x58 [<0000000040232230>] do_cpu_irq_mask+0x3f8/0x440 [<0000000040227070>] intr_return+0x0/0xc Signed-off-by: Helge Deller Reported-by: John David Anglin Cc: stable@vger.kernel.org # v6.18+ --- arch/parisc/include/asm/thread_info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/parisc/include/asm/thread_info.h b/arch/parisc/include/asm/thread_info.h index b283738bb6da..249370706242 100644 --- a/arch/parisc/include/asm/thread_info.h +++ b/arch/parisc/include/asm/thread_info.h @@ -24,7 +24,7 @@ struct thread_info { /* thread information allocation */ -#ifdef CONFIG_IRQSTACKS +#if defined(CONFIG_IRQSTACKS) && !defined(CONFIG_64BIT) #define THREAD_SIZE_ORDER 2 /* PA-RISC requires at least 16k stack */ #else #define THREAD_SIZE_ORDER 3 /* PA-RISC requires at least 32k stack */ From 79a9172f3ab4ad8392c5e5c8944b9b7710ade620 Mon Sep 17 00:00:00 2001 From: Xu Yunxiang Date: Mon, 21 Sep 2026 05:04:21 +0800 Subject: [PATCH 1301/1317] bpf: Reject non-negative offsets in stack_slot_obj_get_spi() bpf_get_spi() computes (-off - 1) / BPF_REG_SIZE using C division, which truncates toward zero. For off == 0, this produces spi 0, the same index used by the valid stack slot at fp-8. stack_slot_obj_get_spi() currently checks alignment and the resulting spi bounds, but does not reject the non-negative offset itself. It can therefore validate a PTR_TO_STACK register holding fp+0 against an iterator stored at fp-8 even though the runtime receives the actual fp+0 pointer. An effectful iterator kfunc can then interpret memory outside the BPF stack as iterator state. Reject non-negative offsets before converting the offset to an spi. All valid stack objects begin at a negative offset from the frame pointer. Fixes: 06accc8779c1 ("bpf: add support for open-coded iterator loops") Signed-off-by: Xu Yunxiang Signed-off-by: Andrii Nakryiko Reviewed-by: Sun Jian Link: https://lore.kernel.org/bpf/20260920210423.345636-2-xyx2021@mail.ustc.edu.cn --- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 02be326f235c..dd8bb179d39e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -567,7 +567,7 @@ static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, struct bpf_reg_s } off = reg->var_off.value; - if (off % BPF_REG_SIZE) { + if (off >= 0 || off % BPF_REG_SIZE) { verbose(env, "cannot pass in %s at an offset=%d\n", obj_kind, off); return -EINVAL; } From 8244668cbbffc8e242b2c5204a2dea20bfca096c Mon Sep 17 00:00:00 2001 From: Xu Yunxiang Date: Mon, 21 Sep 2026 05:04:22 +0800 Subject: [PATCH 1302/1317] selftests/bpf: Reject iterator destruction through fp+0 Add a verifier regression test that initializes a numeric iterator at fp-8 and attempts to destroy it through fp+0. The verifier must reject the non-negative offset instead of treating it as the initialized stack slot. Check the offset diagnostic to ensure rejection happens at the stack object address check. The numeric iterator destroy operation is a no-op; this test checks verifier rejection and does not run the program. Signed-off-by: Xu Yunxiang Signed-off-by: Andrii Nakryiko Reviewed-by: Sun Jian Link: https://lore.kernel.org/bpf/20260920210423.345636-3-xyx2021@mail.ustc.edu.cn --- .../selftests/bpf/progs/iters_state_safety.c | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/iters_state_safety.c b/tools/testing/selftests/bpf/progs/iters_state_safety.c index 646026430e9b..e5bb9fe6d5e5 100644 --- a/tools/testing/selftests/bpf/progs/iters_state_safety.c +++ b/tools/testing/selftests/bpf/progs/iters_state_safety.c @@ -52,6 +52,28 @@ int create_and_destroy(void *ctx) return 0; } +/* fp+0 is not a stack slot. bpf_get_spi(0) used to alias spi 0 (fp-8). */ +SEC("?raw_tp") +__failure __msg("cannot pass in iter at an offset=0") +int destroy_fp0_fail(void *ctx) +{ + struct bpf_iter_num iter; + + asm volatile ("r1 = %[iter];" + "r2 = 0;" + "r3 = 1000;" + "call %[bpf_iter_num_new];" + /* r10 is fp+0, one byte above the top of the BPF stack */ + "r1 = r10;" + "call %[bpf_iter_num_destroy];" + : + : __imm_ptr(iter), ITER_HELPERS + : __clobber_common + ); + + return 0; +} + SEC("?raw_tp") __failure __msg("Unreleased reference id=1") int create_and_forget_to_destroy_fail(void *ctx) From ed6eec97b534979dcf28b40c389cee57bd6561d4 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:18 +0000 Subject: [PATCH 1303/1317] bpf: Fix bounds check for skb-backed dynptrs The skb_pointer_if_linear() function checks whether a memory region of length len starting at offset off into the skb is in the linear area, and returns a pointer to the region if so. The check currently subtracts between skb_headlen and offset of the check, and since skb_headlen is unsigned the subtraction can underflow. This causes the bounds check to spuriously pass and generate an arbitrary pointer of the form *(skb->data + off). The only user of this helper is currently skb-backed BPF dynptr code. Returning the wrong pointer leads to the dynptr erroneously being backed with invalid memory. Ensure the subtraction cannot underflow, and fail the check if it would. Use u64 arithmetic to also prevent overflow when calculating (skb_headlen(skb) - off) since off is unsigned. Fixes: 6f5a630d7c57 ("bpf, net: Introduce skb_pointer_if_linear().") Reported-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20260922172028.6269-2-emil@etsalapatis.com --- include/linux/skbuff.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 421f6fc45451..c8e21903074c 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -4372,7 +4372,10 @@ skb_header_pointer_careful(const struct sk_buff *skb, int offset, static inline void * __must_check skb_pointer_if_linear(const struct sk_buff *skb, int offset, int len) { - if (likely(skb_headlen(skb) - offset >= len)) + unsigned int uoffset = (unsigned int)offset; + + if (likely(uoffset <= skb_headlen(skb) && + (unsigned int)len <= skb_headlen(skb) - uoffset)) return skb->data + offset; return NULL; } From 4fd72eb9f1c939ce33bb714c6f745a125ac5f40c Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:19 +0000 Subject: [PATCH 1304/1317] selftests/bpf: Test dynptr slices past end of skb Add a selftest to ensure dynptr slices cannot include past the end of the linear area of an skb. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-3-emil@etsalapatis.com --- .../testing/selftests/bpf/prog_tests/dynptr.c | 10 ++++++++++ .../selftests/bpf/progs/dynptr_success.c | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c index 5fda11590708..4396560365e8 100644 --- a/tools/testing/selftests/bpf/prog_tests/dynptr.c +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c @@ -9,6 +9,7 @@ enum test_setup_type { SETUP_SYSCALL_SLEEP, SETUP_SKB_PROG, + SETUP_SKB_PROG_NONLINEAR, SETUP_SKB_PROG_TP, SETUP_XDP_PROG, }; @@ -32,6 +33,7 @@ static struct { {"test_ringbuf", SETUP_SYSCALL_SLEEP}, {"test_skb_readonly", SETUP_SKB_PROG}, {"test_dynptr_skb_data", SETUP_SKB_PROG}, + {"test_dynptr_skb_slice_non_linear", SETUP_SKB_PROG_NONLINEAR}, {"test_dynptr_skb_meta_data", SETUP_SKB_PROG}, {"test_dynptr_skb_meta_flags", SETUP_SKB_PROG}, {"test_adjust", SETUP_SYSCALL_SLEEP}, @@ -94,7 +96,9 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ bpf_link__destroy(link); break; case SETUP_SKB_PROG: + case SETUP_SKB_PROG_NONLINEAR: { + struct __sk_buff ctx = {}; int prog_fd; char buf[64]; @@ -106,6 +110,12 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ .repeat = 1, ); + if (setup_type == SETUP_SKB_PROG_NONLINEAR) { + ctx.data_end = ETH_HLEN + sizeof(struct iphdr); + topts.ctx_in = &ctx; + topts.ctx_size_in = sizeof(ctx); + } + prog_fd = bpf_program__fd(prog); if (!ASSERT_GE(prog_fd, 0, "prog_fd")) goto cleanup; diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c index e0745b6e467e..b668ebd61fc7 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_success.c +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -10,6 +10,7 @@ #include "errno.h" #define PAGE_SIZE_64K 65536 +#define TEST_SKB_LINEAR_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr)) char _license[] SEC("license") = "GPL"; @@ -211,6 +212,25 @@ int test_dynptr_skb_data(struct __sk_buff *skb) return 1; } +SEC("?tc") +int test_dynptr_skb_slice_non_linear(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + void *data; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err = 1; + return 1; + } + + /* Ensure we cannot read past the end of the buffer. */ + data = bpf_dynptr_slice(&ptr, TEST_SKB_LINEAR_SIZE + 1, NULL, 1); + if (data) + err = 2; + + return 1; +} + SEC("?tc") int test_dynptr_skb_meta_data(struct __sk_buff *skb) { From 4a4852376e3a2727ea40e61143d6d7c22bb6dfad Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:20 +0000 Subject: [PATCH 1305/1317] bpf: Fix bpf_sock context code generation Currently, the ctx access code reads the rx_queue_mapping field with either a 4-byte or 2-byte load. The rest of the bits in the register are marked known zero by the verifier. However, the emitted ctx access code places in the register on certain the special value (-1) using BPF_MOV_IMM64, which gets sign-extended to turn on all the bits in the register. By shifting this value right, the program ends up with a value at runtime above what the verifier assumes is possible. Fix this by ensuring the read value is as wide as the assumed size. Use MOV32 instructions instead of MOV64 instructions to keep the upper bits zero as assumed by the verifier. Also properly report the size of the destination variable (the bpf_sock field, 4 bytes) instead of the source (the socket field, 2 bytes). Fixes: c3c16f2ea6d2 ("bpf: Add rx_queue_mapping to bpf_sock") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20260922172028.6269-4-emil@etsalapatis.com --- net/core/filter.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 532405988fd9..70dc621672f2 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -10566,11 +10566,12 @@ u32 bpf_sock_convert_ctx_access(enum bpf_access_type type, target_size)); *insn++ = BPF_JMP_IMM(BPF_JNE, si->dst_reg, NO_QUEUE_MAPPING, 1); - *insn++ = BPF_MOV64_IMM(si->dst_reg, -1); + *insn++ = BPF_MOV32_IMM(si->dst_reg, -1); #else - *insn++ = BPF_MOV64_IMM(si->dst_reg, -1); - *target_size = 2; + *insn++ = BPF_MOV32_IMM(si->dst_reg, -1); #endif + *target_size = sizeof_field(struct bpf_sock, rx_queue_mapping); + break; } From dec0c209a6808fe6de2e4787538e02786a16a4ef Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:21 +0000 Subject: [PATCH 1306/1317] selftests/bpf: Add selftests for rx_queue_mapping context access Add tests to ensure the verifier properly tracks the 0 bit state and width of the rx_queue_mapping field read from struct sock. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-5-emil@etsalapatis.com --- .../selftests/bpf/progs/verifier_sock.c | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_sock.c b/tools/testing/selftests/bpf/progs/verifier_sock.c index 4f2f3209eec8..bf9f6fb6582c 100644 --- a/tools/testing/selftests/bpf/progs/verifier_sock.c +++ b/tools/testing/selftests/bpf/progs/verifier_sock.c @@ -88,6 +88,44 @@ l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_family]); \ : __clobber_all); } +SEC("socket") +__description("skb->sk: sk->rx_queue_mapping [no sign extension]") +__success __success_unpriv __retval(0) +__naked void sk_rx_queue_mapping_no_sign_ext(void) +{ + asm volatile (" \ + r1 = *(u64*)(r1 + %[__sk_buff_sk]); \ + if r1 != 0 goto l0_%=; \ + r0 = 0xdead; \ + exit; \ +l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_rx_queue_mapping]); \ + r0 >>= 32; \ + exit; \ +" : + : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)), + __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping)) + : __clobber_all); +} + +SEC("socket") +__description("skb->sk: sk->rx_queue_mapping [narrow load mask]") +__success __success_unpriv __retval(0) +__naked void sk_rx_queue_mapping_narrow_load_mask(void) +{ + asm volatile (" \ + r1 = *(u64*)(r1 + %[__sk_buff_sk]); \ + if r1 != 0 goto l0_%=; \ + r0 = 0xdead; \ + exit; \ +l0_%=: r0 = *(u16*)(r1 + %[bpf_sock_rx_queue_mapping]); \ + r0 >>= 16; \ + exit; \ +" : + : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)), + __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping)) + : __clobber_all); +} + SEC("cgroup/skb") __description("skb->sk: sk->type [fullsock field]") __failure __msg("invalid sock_common access") From a6c1edfbe240e4377038a0e1d233ad81fde9a21b Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:22 +0000 Subject: [PATCH 1307/1317] bpf: Reject pkt arguments in mutating subprogs The verifier tracks changes in how PTR_TO_PACKET registers' bounds are modified across subprog boundaries. PTR_TO_PACKET registers are actually passed as PTR_TO_MEM, which is assumed valid for the entire call. This is not the case with packet memory, where a pskb_* call may invalidate its memory region. Reject BPF code that passes PTR_TO_PACKET pointers to subprogs that may mutate a packet. We cannot pass the pointer as a true PTR_TO_PACKET because we would also need to somehow pass the PTR_TO_PACKET_META or PTR_TO_PACKET_END to the subprog. Since we cannot avoid representing the pointer in the subprog as PTR_TO_MEM, only permit it if the subprog is guaranteed not to mutate the packet. Fixes: 80f281664f5a ("bpf: Support pointers in global func args") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-6-emil@etsalapatis.com --- kernel/bpf/verifier.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index dd8bb179d39e..7ffbb804184f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9750,6 +9750,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog, if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL, NULL)) return -EINVAL; + /* + * PTR_TO_PACKET get passed as PTR_TO_MEM, preventing + * us from adjusting bounds tracking info. + */ + if ((reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) && + sub->changes_pkt_data) { + bpf_log(log, "%s is a packet pointer, but func#%d may change packet data\n", + reg_arg_name(env, argno), subprog); + return -EINVAL; + } if (!(arg->arg_type & PTR_MAYBE_NULL) && (type_may_be_null(reg->type) || bpf_register_is_null(reg))) { bpf_log(log, "%s is expected to be non-NULL\n", From 1ed69a54d31848618131aaf3311a78adbe78ced0 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:23 +0000 Subject: [PATCH 1308/1317] selftests/bpf: Test rejection of pkt args to mutating subprogs Add a selftests that ensures that PTR_TO_PACKET arguments can only be passed to subprogs that will never adjust the underlying packet memory, and are rejected otherwise. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-7-emil@etsalapatis.com --- .../bpf/progs/verifier_global_ptr_args.c | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c index a3d2af8dc839..dcc2dd46751a 100644 --- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c +++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c @@ -350,4 +350,57 @@ int anything_to_untrusted_mem(void *ctx) return 0; } +struct pkt_arg { + __u64 x; + __u8 pad[56]; +}; + +__weak int subprog_pkt_ptr_no_change(struct pkt_arg *p) +{ + if (!p) + return 0; + + return p->x; +} + +SEC("?tc") +__success +int pkt_ptr_to_global_mem_arg_no_change(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct pkt_arg *p = data; + + if ((void *)(p + 1) > data_end) + return 0; + + return subprog_pkt_ptr_no_change(p); +} + +__weak int subprog_pkt_ptr_changes_data(struct __sk_buff *skb __arg_ctx, + struct pkt_arg *p) +{ + if (!p) + return 0; + + bpf_skb_pull_data(skb, 0); + return p->x; +} + +SEC("?tc") +__failure __log_level(2) +__msg("R2 is a packet pointer, but func#{{[0-9]+}} may change packet data") +__msg("Caller passes invalid args into func#{{[0-9]+}} ('subprog_pkt_ptr_changes_data')") +int pkt_ptr_to_global_mem_arg_changes_data(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct pkt_arg *p = data; + + if ((void *)(p + 1) > data_end) + return 0; + + return subprog_pkt_ptr_changes_data(skb, p); +} + char _license[] SEC("license") = "GPL"; From f85f5917aa2fbd861c72ea71ecb14a2fa915c3f6 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:24 +0000 Subject: [PATCH 1309/1317] bpf: Prevent variable arena/non-arena register contents The verifier marks ALU instructions that include at least one arena operand with needs_zext: These instructions are fixed up after verification to be ALU32 instructions to ensure that the result is a valid offset into an arena. However, different code paths may provide two non-arena 64-bit arguments to the same instruction. The result of the operation in that code path is wrong, since it is now unexpectedly truncated to 32 bits and zero-extended. Add logic to the verifier to ensure every instruction either always has at least one PTR_TO_ARENA argument, or never does. Since needs_zext already tracks the first scenario, add a prevent_zext field in bpf_insn_aux to track the latter. Reject instructions that use arena arguments and have prevent_zext set, or do not have arena arguments and have needs_zext set. Fixes: 6082b6c328b5 ("bpf: Recognize addr_space_cast instruction in the verifier.") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-8-emil@etsalapatis.com --- include/linux/bpf_verifier.h | 1 + kernel/bpf/verifier.c | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 6fe8e5dc57aa..b83ec99f1a13 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -680,6 +680,7 @@ struct bpf_insn_aux_data { bool nospec_result; /* result is unsafe under speculation, nospec must follow */ bool zext_dst; /* this insn zero extends dst reg */ bool needs_zext; /* alu op needs to clear upper bits */ + bool prevent_zext; /* alu op cannot be zext (already used with 64-bit scalars) */ bool non_sleepable; /* helper/kfunc may be called from non-sleepable context */ bool is_iter_next; /* bpf_iter__next() kfunc call */ bool call_with_percpu_alloc_ptr; /* {this,per}_cpu_ptr() with prog percpu alloc */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 7ffbb804184f..41b49c56e123 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -15741,6 +15741,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, struct bpf_reg_state *regs = state->regs, *dst_reg, *src_reg; struct bpf_reg_state *ptr_reg = NULL, off_reg = {0}; bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64); + struct bpf_insn_aux_data *aux = cur_aux(env); u8 opcode = BPF_OP(insn->code); int err; @@ -15752,12 +15753,23 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, /* Case where at least one operand is an arena. */ if (dst_reg->type == PTR_TO_ARENA || (src_reg && src_reg->type == PTR_TO_ARENA)) { - struct bpf_insn_aux_data *aux = cur_aux(env); if (dst_reg->type != PTR_TO_ARENA) *dst_reg = *src_reg; if (BPF_CLASS(insn->code) == BPF_ALU64) { + /* + * Only arena pointers set needs_zext, but doing so + * modifies the instruction at fixup time to an ALU32 + * and makes it unsuitable for 64-bit scalar args. We + * prevent zext from being set if the instruction has + * been previously called with non-arena registers. + */ + if (aux->prevent_zext) { + verbose(env, "same insn cannot be used with and without arena pointer\n"); + return -EINVAL; + } + /* * 32-bit operations zero upper bits automatically. * 64-bit operations need to be converted to 32. @@ -15770,6 +15782,16 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, return 0; } + /* Prevent the instruction from being used with arena pointers (see above). */ + if (env->prog->aux->arena && BPF_CLASS(insn->code) == BPF_ALU64) { + if (aux->needs_zext) { + verbose(env, "same insn cannot be used with and without arena pointer\n"); + return -EINVAL; + } + + aux->prevent_zext = true; + } + if (dst_reg->type != SCALAR_VALUE) ptr_reg = dst_reg; From a9e86dd9de4f933b69f5cca6293fd4c8919224ec Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:25 +0000 Subject: [PATCH 1310/1317] selftests/bpf: Test for mixed arena/nonarena code paths Add a selftest to confirm the verifier rejects ALU operations that return arena or non-arena results depending on code path. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-9-emil@etsalapatis.com --- .../selftests/bpf/progs/verifier_arena.c | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c index 815f342eb4b0..3e33766547c0 100644 --- a/tools/testing/selftests/bpf/progs/verifier_arena.c +++ b/tools/testing/selftests/bpf/progs/verifier_arena.c @@ -561,6 +561,55 @@ int arena_ptr_add_arena_ptr(void *ctx) return 0; } +SEC("syscall") +__failure __msg("same insn cannot be used with and without arena pointer") +int mixed_arena_scalar_alu64_scalar_first(void *ctx) +{ + volatile register __u64 reg asm("r3"); + __u32 pick_arena = bpf_get_prandom_u32(); + + reg = 1ULL << 32; + + if (pick_arena) { + asm volatile ( + "r9 = %[arena] ll;" + "%[reg] = 0;" + "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);" + : [reg] "=r"(reg) + : __imm_addr(arena) + : "r9" + ); + } + + reg += 1; + + return 0; +} + +SEC("syscall") +__failure __msg("same insn cannot be used with and without arena pointer") +int mixed_arena_scalar_alu64_arena_first(void *ctx) +{ + volatile register __u64 reg asm("r3"); + __u32 pick_scalar = bpf_get_prandom_u32(); + + asm volatile ( + "r9 = %[arena] ll;" + "%[reg] = 0;" + "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);" + : [reg] "=r"(reg) + : __imm_addr(arena) + : "r9" + ); + + if (pick_scalar) + reg = 1ULL << 32; + + reg += 1; + + return 0; +} + SEC("syscall") __success __retval(0) int scalar_xor_arena_ptr(void *ctx) From 814a81c842bd88f6bd8a4ce550d560df071a5d03 Mon Sep 17 00:00:00 2001 From: Zhao Gongyi Date: Thu, 17 Sep 2026 20:10:16 +0800 Subject: [PATCH 1311/1317] bpf, sockmap: Reject max_entries > INT_MAX in sock_map_alloc sock_map_alloc() only rejects max_entries == 0 and otherwise allows any u32 value. sock_map_free() then walks the sks[] array with a signed int iterator: int i; for (i = 0; i < stab->map.max_entries; i++) struct sock **psk = &stab->sks[i]; When a SOCKMAP is created with max_entries = 0xffffffff (UINT_MAX), the allocation of 32 GiB can succeed on large-memory hosts. During free the counter reaches 0x80000000, wraps to INT_MIN, is sign-extended by movslq and turned into a ~16 GiB negative offset from stab->sks, pointing far below the allocation. The faulting access is an xchg() write in sock_map_free(). Without KASAN, the same out-of-bounds write can fault on an unmapped vmalloc page or corrupt an unrelated allocation if that vmalloc address is populated. On a KASAN kernel with CONFIG_KASAN_VMALLOC=y, the shadow check for that address hits an unmapped shadow page and oopses first: BUG: unable to handle page fault for address: fffff521b59c5a00 RIP: 0010:kasan_check_range+0x107/0x190 Call Trace: sock_map_free+0x93/0x190 map_create+0x68d/0xb30 __sys_bpf+0x21e/0x2e70 Vmcore confirmed stab->map.max_entries == 0xffffffff, stab->sks == 0xffffc911ace2d000, and the faulting address sks + (s64)INT_MIN * 8 exactly at 0xffffc90dace2d000. The same buggy path is reached on the normal close()/bpf_map_free_deferred() path whenever such a map is destroyed. sock_map_alloc() used to bound its allocation size through bpf_map_charge_init(), but the bound was dropped when rlimit-based memory accounting was removed. Reject max_entries > INT_MAX at creation time so the signed iterator in sock_map_free() never sees a value that would overflow. Triggered by syzkaller and reproduced on both a 6.6-based KASAN kernel and the upstream v7.3-rc2 kernel. Fixes: 0d2c4f964050 ("bpf: Eliminate rlimit-based memory accounting for sockmap and sockhash maps") Signed-off-by: Zhao Gongyi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260917121016.48171-1-zhaogongyi@bytedance.com --- net/core/sock_map.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/sock_map.c b/net/core/sock_map.c index ca49bc7f8687..38df84284328 100644 --- a/net/core/sock_map.c +++ b/net/core/sock_map.c @@ -41,6 +41,7 @@ static struct bpf_map *sock_map_alloc(union bpf_attr *attr) struct bpf_stab *stab; if (attr->max_entries == 0 || + attr->max_entries > INT_MAX || attr->key_size != 4 || (attr->value_size != sizeof(u32) && attr->value_size != sizeof(u64)) || From 6db1ce73e9853f533eb7f413f14ba00f8ec6f80d Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 20 Sep 2026 21:23:04 +0800 Subject: [PATCH 1312/1317] bpf: Reject dev-bound-only programs on other devices __bpf_offload_dev_match() falls back to comparing offdev pointers after an exact netdev mismatch. Bound-only programs normally have NULL offdevs, so unrelated netdevs compare equal. A bound-only program on an offload-registered netdev can instead inherit a real offdev and match a sibling port. With CAP_BPF and CAP_NET_ADMIN, a caller can use bpf(BPF_LINK_CREATE) with a different target ifindex to run metadata kfuncs specialized for the bound driver on the target driver's xdp_buff. Running a veth-bound program on tun reads beyond tun's bare stack xdp_buff as a veth_xdp_buff. Oops: general protection fault, probably for non-canonical address KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] RIP: 0010:veth_xdp_rx_timestamp (drivers/net/veth.c:1673) Call Trace: ... tun_build_skb (drivers/net/tun.c:1739) tun_get_user (drivers/net/tun.c:1856) tun_chr_write_iter (drivers/net/tun.c:2091) vfs_write (fs/read_write.c:595 fs/read_write.c:687) ksys_write (fs/read_write.c:739) do_syscall_64 (arch/x86/entry/syscall_64.c:84) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Kernel panic - not syncing: Fatal exception in interrupt Restrict non-offloaded programs to exact netdev matches and retain the shared-offdev fallback only for genuinely offloaded multi-port programs. Fixes: 2b3486bc2d23 ("bpf: Introduce device-bound XDP programs") Reported-by: Signed-off-by: Weiming Shi Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20260917161335.1020405-2-bestswngs@gmail.com/ Link: https://patch.msgid.link/20260920132303.4109240-3-bestswngs@gmail.com --- kernel/bpf/offload.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/bpf/offload.c b/kernel/bpf/offload.c index 0d6f5569588c..d855399812ee 100644 --- a/kernel/bpf/offload.c +++ b/kernel/bpf/offload.c @@ -698,6 +698,8 @@ static bool __bpf_offload_dev_match(struct bpf_prog *prog, return false; if (offload->netdev == netdev) return true; + if (!bpf_prog_is_offloaded(prog->aux)) + return false; ondev1 = bpf_offload_find_netdev(offload->netdev); ondev2 = bpf_offload_find_netdev(netdev); From 2bc6b218717b9d08f466f88209251d54bc09b207 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 22 Sep 2026 19:14:30 +0100 Subject: [PATCH 1313/1317] arm64/boot: Disable trapping of PMZR_EL0 writes to EL2 __init_el2_fgt2() writes one mask to both HDFGRTR2_EL2 and HDFGWTR2_EL2. PMZR_EL0 is write-only, so its trap bit, nPMZR_EL0, exists only in HDFGWTR2_EL2 and is therefore never set: a PMZR_EL0 write from the host traps to EL2, where the nVHE hypervisor has no handler and BUG()s. The kernel never writes PMZR_EL0, but kernel.perf_user_access=1 has the PMU driver set PMUSERENR_EL0.UEN for a task with a user-read event, so a write from EL0 reaches the trap and takes the host down without a panic message. Accumulate the HDFGWTR2_EL2 bits separately, as __init_el2_fgt() already does for HDFGWTR_EL2, and set nPMZR_EL0 with the other FEAT_PMUv3p9 bits. Fixes: 858c7bfcb35e1 ("arm64/boot: Enable EL2 requirements for FEAT_PMUv3p9") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Anshuman Khandual Reviewed-by: Oliver Upton Signed-off-by: Will Deacon --- Documentation/arch/arm64/booting.rst | 1 + arch/arm64/include/asm/el2_setup.h | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/arch/arm64/booting.rst b/Documentation/arch/arm64/booting.rst index 13ef311dace8..3fea4b14ef7c 100644 --- a/Documentation/arch/arm64/booting.rst +++ b/Documentation/arch/arm64/booting.rst @@ -465,6 +465,7 @@ Before jumping into the kernel, the following conditions must be met: - HDFGWTR2_EL2.nPMICNTR_EL0 (bit 2) must be initialised to 0b1. - HDFGWTR2_EL2.nPMICFILTR_EL0 (bit 3) must be initialised to 0b1. - HDFGWTR2_EL2.nPMUACR_EL1 (bit 4) must be initialised to 0b1. + - HDFGWTR2_EL2.nPMZR_EL0 (bit 21) must be initialised to 0b1. For CPUs with SPE data source filtering (FEAT_SPE_FDS): diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h index aa8ec9df8024..87560d8b254e 100644 --- a/arch/arm64/include/asm/el2_setup.h +++ b/arch/arm64/include/asm/el2_setup.h @@ -418,6 +418,7 @@ b.lt .Lskip_fgt2_\@ mov x0, xzr + mov x2, xzr mrs x1, id_aa64dfr0_el1 ubfx x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4 cmp x1, #ID_AA64DFR0_EL1_PMUVer_V3P9 @@ -426,6 +427,11 @@ orr x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1 + orr x2, x2, #HDFGWTR2_EL2_nPMICNTR_EL0 + orr x2, x2, #HDFGWTR2_EL2_nPMICFILTR_EL0 + orr x2, x2, #HDFGWTR2_EL2_nPMUACR_EL1 + /* PMZR_EL0 is write-only, so it has no read trap to disable */ + orr x2, x2, #HDFGWTR2_EL2_nPMZR_EL0 .Lskip_pmuv3p9_\@: /* If SPE is implemented, */ __spe_vers_imp .Lskip_spefds_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x1 @@ -436,10 +442,11 @@ cbz x1, .Lskip_spefds_\@ /* disable traps of PMSDSFR to EL2. */ orr x0, x0, #HDFGRTR2_EL2_nPMSDSFR_EL1 + orr x2, x2, #HDFGWTR2_EL2_nPMSDSFR_EL1 .Lskip_spefds_\@: msr_s SYS_HDFGRTR2_EL2, x0 - msr_s SYS_HDFGWTR2_EL2, x0 + msr_s SYS_HDFGWTR2_EL2, x2 msr_s SYS_HFGRTR2_EL2, xzr msr_s SYS_HFGWTR2_EL2, xzr msr_s SYS_HFGITR2_EL2, xzr From db762fd96be225bd06161c9631160c755d891693 Mon Sep 17 00:00:00 2001 From: Johan Almbladh Date: Wed, 23 Sep 2026 12:51:57 +0200 Subject: [PATCH 1314/1317] bpf: Fix immediate JMP JEQ/JNE on MIPS32 An addu instruction was emitted instead of addiu, causing the immediate value 1 to be interpreted as register $at. This made the comparison result invalid when the immediate operand was negative. Note that $at is mapped to BPF_REG_AX, which is used for constant blinding. Fix the instruction to use the immediate form. Found with test_bpf on MIPS32r1 emulated by QEMU. Fixes: eb63cfcd2ee8 ("mips, bpf: Add eBPF JIT for 32-bit MIPS") Signed-off-by: Johan Almbladh Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260923105158.3514342-1-johan.almbladh@anyfinetworks.com --- arch/mips/net/bpf_jit_comp32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c index 40a878b672f5..15a2a153dc87 100644 --- a/arch/mips/net/bpf_jit_comp32.c +++ b/arch/mips/net/bpf_jit_comp32.c @@ -1111,7 +1111,7 @@ static void emit_jmp_i64(struct jit_context *ctx, emit(ctx, xor, tmp, lo(dst), tmp); } if (imm < 0) { /* Compare sign extension */ - emit(ctx, addu, MIPS_R_T9, hi(dst), 1); + emit(ctx, addiu, MIPS_R_T9, hi(dst), 1); emit(ctx, or, tmp, tmp, MIPS_R_T9); } else { /* Compare zero extension */ emit(ctx, or, tmp, tmp, hi(dst)); From 8110ba09777873443db286b3cbb89b0e6311c554 Mon Sep 17 00:00:00 2001 From: Johan Almbladh Date: Wed, 23 Sep 2026 12:51:58 +0200 Subject: [PATCH 1315/1317] bpf: Fix BSWAP 32 and 16 on MIPS64 The 16/32-bit byteswap implementations for MIPS64r1 and earlier do not have an explicit zero extension afterwards. The input is first sign-extended to 64 bits, and the byteswap sequence can then leave the result sign-extended depending on the value of the low bits. Add the missing zero-extension. Found with test_bpf on MIPS64r1 emulated by QEMU. Fixes: fbc802de6b10 ("mips, bpf: Add new eBPF JIT for 64-bit MIPS") Signed-off-by: Johan Almbladh Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260923105158.3514342-2-johan.almbladh@anyfinetworks.com --- arch/mips/net/bpf_jit_comp64.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c index fa7e9aa37f49..6681ccac9dd9 100644 --- a/arch/mips/net/bpf_jit_comp64.c +++ b/arch/mips/net/bpf_jit_comp64.c @@ -305,8 +305,7 @@ static void emit_bswap_r64(struct jit_context *ctx, u8 dst, u32 width) case 16: emit_sext(ctx, dst, dst); emit_bswap_r(ctx, dst, width); - if (cpu_has_mips64r2 || cpu_has_mips64r6) - emit_zext(ctx, dst); + emit_zext(ctx, dst); break; } clobber_reg(ctx, dst); From c3a66e5f5bab3912e9f84223c5a982bf4333d5a1 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 24 Sep 2026 19:23:20 +0900 Subject: [PATCH 1316/1317] bpf: Zero-fill other CPUs when BPF_F_CPU creates a per-cpu hash element pcpu_init_value() initializes the per-cpu area of a newly created [lru_]percpu_hash element. The area is recycled, so when the value comes from a BPF program (onallcpus == false) it writes the running CPU's slot and zeroes the rest. bpf_percpu_hash_update() passes onallcpus == true, which delegates to pcpu_copy_value(). pcpu_copy_value() writes only the CPU named in map_flags when BPF_F_CPU is set, so on the create path the other slots keep the recycled element's values: update(k1, 0xdeadc0de, BPF_F_ALL_CPUS) every CPU holds 0xdeadc0de delete(k1) element back on the freelist update(k2, 0xc0ffee, BPF_F_CPU | 0) creates, writes CPU 0 only lookup(k2) CPU 0 0xc0ffee, rest 0xdeadc0de Zero-fill the other CPUs on that arm too. Fixes: c6936161fd55 ("bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_hash and lru_percpu_hash maps") Signed-off-by: Donggeun Yoo Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260924102321.2120434-2-donggeunyoo.kernel@gmail.com --- kernel/bpf/hashtab.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 4f495dcbf670..f9464e566f10 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1054,14 +1054,17 @@ static void pcpu_init_value(struct bpf_htab *htab, void __percpu *pptr, /* When not setting the initial value on all cpus, zero-fill element * values for other cpus. Otherwise, bpf program has no way to ensure * known initial values for cpus other than current one - * (onallcpus=false always when coming from bpf prog). + * (onallcpus=false always when coming from bpf prog, + * map_flags & BPF_F_CPU when coming from syscall but setting + * only one cpu). */ - if (!onallcpus) { - int current_cpu = raw_smp_processor_id(); + if (!onallcpus || (map_flags & BPF_F_CPU)) { + int init_cpu = (map_flags & BPF_F_CPU) ? map_flags >> 32 : + raw_smp_processor_id(); int cpu; for_each_possible_cpu(cpu) { - if (cpu == current_cpu) + if (cpu == init_cpu) copy_map_value(&htab->map, per_cpu_ptr(pptr, cpu), value); else /* Since elem is preallocated, we cannot touch special fields */ zero_map_value(&htab->map, per_cpu_ptr(pptr, cpu)); From 3422808f4e959266ea7790101ac90b8341f48226 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 24 Sep 2026 19:23:21 +0900 Subject: [PATCH 1317/1317] selftests/bpf: Test per-cpu initialization of a BPF_F_CPU created element The existing cpu_flag subtests always prime a key with BPF_F_ALL_CPUS before any BPF_F_CPU write, so the create path is never covered. Add a subtest that creates the element with BPF_F_CPU on a map with max_entries 1, so the key can only reuse the element the previous key released, and check that the CPUs the update did not name read back zero. Run it for PERCPU_HASH preallocated and BPF_F_NO_PREALLOC, whose per-cpu areas come from different allocators, and for LRU_PERCPU_HASH. Under BPF_F_NO_PREALLOC the reuse is only guaranteed on the cpu that ran the delete, so pin the thread across the pair, and name a cpu other than that one in map_flags. Signed-off-by: Donggeun Yoo Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260924102321.2120434-3-donggeunyoo.kernel@gmail.com --- .../selftests/bpf/prog_tests/percpu_alloc.c | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c b/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c index a72ae0b29f6e..7b4a1e24363b 100644 --- a/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c +++ b/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c @@ -1,4 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include #include #include "cgroup_helpers.h" #include "percpu_alloc_array.skel.h" @@ -350,6 +352,103 @@ static void test_lru_percpu_hash_cpu_flag(void) test_percpu_map_cpu_flag(BPF_MAP_TYPE_LRU_PERCPU_HASH); } +/* + * A BPF_F_CPU update that creates an element must zero the value on the other + * cpus, rather than leave them holding whatever the recycled element last + * contained. max_entries is 1 so the second key can only reuse the element + * the first one released. + */ +static void test_percpu_map_cpu_flag_create(enum bpf_map_type map_type, __u32 map_flags) +{ + LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = map_flags); + const u32 stale = 0xDEADC0DE, fresh = 0xC0FFEE; + int nr_cpus, cpu, map_fd, err, key; + int pinned_cpu, value_cpu; + cpu_set_t old_mask, new_mask; + bool restore_mask = false; + u32 value; + u64 flags; + + nr_cpus = libbpf_num_possible_cpus(); + if (!ASSERT_GT(nr_cpus, 0, "libbpf_num_possible_cpus")) + return; + + if (nr_cpus < 2) { + test__skip(); + return; + } + + map_fd = bpf_map_create(map_type, "cpu_flag_create", sizeof(key), sizeof(value), 1, &opts); + if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) + return; + + /* NO_PREALLOC recycles per cpu, so keep the delete and the create on one cpu. */ + err = sched_getaffinity(0, sizeof(old_mask), &old_mask); + if (!ASSERT_OK(err, "sched_getaffinity")) + goto out; + + pinned_cpu = sched_getcpu(); + if (!ASSERT_GE(pinned_cpu, 0, "sched_getcpu")) + goto out; + + CPU_ZERO(&new_mask); + CPU_SET(pinned_cpu, &new_mask); + err = sched_setaffinity(0, sizeof(new_mask), &new_mask); + if (!ASSERT_OK(err, "sched_setaffinity")) + goto out; + restore_mask = true; + + value_cpu = pinned_cpu ? 0 : 1; + + key = 1; + value = stale; + err = bpf_map_update_elem(map_fd, &key, &value, BPF_F_ALL_CPUS); + if (!ASSERT_OK(err, "bpf_map_update_elem all_cpus")) + goto out; + + err = bpf_map_delete_elem(map_fd, &key); + if (!ASSERT_OK(err, "bpf_map_delete_elem")) + goto out; + + key = 2; + value = fresh; + flags = (u64)value_cpu << 32 | BPF_F_CPU; + err = bpf_map_update_elem(map_fd, &key, &value, flags); + if (!ASSERT_OK(err, "bpf_map_update_elem specified cpu")) + goto out; + + for (cpu = 0; cpu < nr_cpus; cpu++) { + value = 0; + flags = (u64)cpu << 32 | BPF_F_CPU; + err = bpf_map_lookup_elem_flags(map_fd, &key, &value, flags); + if (!ASSERT_OK(err, "bpf_map_lookup_elem_flags specified cpu")) + goto out; + if (!ASSERT_EQ(value, cpu == value_cpu ? fresh : 0, "value on specified cpu")) + goto out; + } + +out: + if (restore_mask) + sched_setaffinity(0, sizeof(old_mask), &old_mask); + close(map_fd); +} + +static void test_percpu_hash_cpu_flag_create(void) +{ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, 0); +} + +static void test_percpu_hash_cpu_flag_create_malloc(void) +{ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, BPF_F_NO_PREALLOC); +} + +static void test_lru_percpu_hash_cpu_flag_create(void) +{ + /* lru without prealloc is -ENOTSUPP, so there is no malloc variant */ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_LRU_PERCPU_HASH, 0); +} + static void test_percpu_cgroup_storage_cpu_flag(void) { struct percpu_alloc_array *skel = NULL; @@ -454,6 +553,12 @@ void test_percpu_alloc(void) test_percpu_hash_cpu_flag(); if (test__start_subtest("cpu_flag_lru_percpu_hash")) test_lru_percpu_hash_cpu_flag(); + if (test__start_subtest("cpu_flag_create_percpu_hash")) + test_percpu_hash_cpu_flag_create(); + if (test__start_subtest("cpu_flag_create_percpu_hash_malloc")) + test_percpu_hash_cpu_flag_create_malloc(); + if (test__start_subtest("cpu_flag_create_lru_percpu_hash")) + test_lru_percpu_hash_cpu_flag_create(); if (test__start_subtest("cpu_flag_percpu_cgroup_storage")) test_percpu_cgroup_storage_cpu_flag(); if (test__start_subtest("cpu_flag_array"))