From: Sasha Levin Date: Thu, 6 Aug 2026 11:35:00 +0000 (-0400) Subject: Fixes for all trees X-Git-Tag: v5.10.263~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5536f5228d2300ee063755fa3acb19ef21b89e9e;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- diff --git a/queue-5.10/bluetooth-sco-fix-uaf-on-sco_sock_timeout.patch b/queue-5.10/bluetooth-sco-fix-uaf-on-sco_sock_timeout.patch new file mode 100644 index 0000000000..db1d6eaa07 --- /dev/null +++ b/queue-5.10/bluetooth-sco-fix-uaf-on-sco_sock_timeout.patch @@ -0,0 +1,124 @@ +From 8ae367233833ab99544779e7e33fd11b7d54dcff Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 21:24:34 +0000 +Subject: Bluetooth: SCO: Fix UAF on sco_sock_timeout + +From: Luiz Augusto von Dentz + +commit 1bf4470a3939c678fb822073e9ea77a0560bc6bb upstream. + +conn->sk maybe have been unlinked/freed while waiting for sco_conn_lock +so this checks if the conn->sk is still valid by checking if it part of +sco_sk_list. + +Reported-by: syzbot+4c0d0c4cde787116d465@syzkaller.appspotmail.com +Tested-by: syzbot+4c0d0c4cde787116d465@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=4c0d0c4cde787116d465 +Fixes: ba316be1b6a0 ("Bluetooth: schedule SCO timeouts with delayed_work") +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Xiangyu Chen +Signed-off-by: He Zhe +Signed-off-by: Greg Kroah-Hartman +[ Resolved trivial conflicts in net/bluetooth/sco.c, removed +extra reference on sk ] +Signed-off-by: Todd Kjos +Signed-off-by: Sasha Levin +--- + include/net/bluetooth/bluetooth.h | 1 + + net/bluetooth/af_bluetooth.c | 22 ++++++++++++++++++++++ + net/bluetooth/sco.c | 17 ++++++++++++----- + 3 files changed, 35 insertions(+), 5 deletions(-) + +diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h +index 43b4386018e26..85bab90a6921c 100644 +--- a/include/net/bluetooth/bluetooth.h ++++ b/include/net/bluetooth/bluetooth.h +@@ -317,6 +317,7 @@ void bt_sock_link(struct bt_sock_list *l, struct sock *s); + void bt_sock_unlink(struct bt_sock_list *l, struct sock *s); + struct sock *bt_sock_alloc(struct net *net, struct socket *sock, + struct proto *prot, int proto, gfp_t prio, int kern); ++bool bt_sock_linked(struct bt_sock_list *l, struct sock *s); + int bt_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, + int flags); + int bt_sock_stream_recvmsg(struct socket *sock, struct msghdr *msg, +diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c +index b983ec52a99a1..16bea865e6d64 100644 +--- a/net/bluetooth/af_bluetooth.c ++++ b/net/bluetooth/af_bluetooth.c +@@ -184,6 +184,28 @@ void bt_sock_unlink(struct bt_sock_list *l, struct sock *sk) + } + EXPORT_SYMBOL(bt_sock_unlink); + ++bool bt_sock_linked(struct bt_sock_list *l, struct sock *s) ++{ ++ struct sock *sk; ++ ++ if (!l || !s) ++ return false; ++ ++ read_lock(&l->lock); ++ ++ sk_for_each(sk, &l->head) { ++ if (s == sk) { ++ read_unlock(&l->lock); ++ return true; ++ } ++ } ++ ++ read_unlock(&l->lock); ++ ++ return false; ++} ++EXPORT_SYMBOL(bt_sock_linked); ++ + void bt_accept_enqueue(struct sock *parent, struct sock *sk, bool bh) + { + const struct cred *old_cred; +diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c +index 01a01d6f01c30..2fbd9c93440ce 100644 +--- a/net/bluetooth/sco.c ++++ b/net/bluetooth/sco.c +@@ -76,6 +76,16 @@ struct sco_pinfo { + #define SCO_CONN_TIMEOUT (HZ * 40) + #define SCO_DISCONN_TIMEOUT (HZ * 2) + ++static struct sock *sco_sock_hold(struct sco_conn *conn) ++{ ++ if (!conn || !bt_sock_linked(&sco_sk_list, conn->sk)) ++ return NULL; ++ ++ sock_hold(conn->sk); ++ ++ return conn->sk; ++} ++ + static void sco_sock_timeout(struct work_struct *work) + { + struct sco_conn *conn = container_of(work, struct sco_conn, +@@ -87,9 +97,7 @@ static void sco_sock_timeout(struct work_struct *work) + sco_conn_unlock(conn); + return; + } +- sk = conn->sk; +- if (sk) +- sock_hold(sk); ++ sk = sco_sock_hold(conn); + sco_conn_unlock(conn); + + if (!sk) +@@ -192,11 +200,10 @@ static void sco_conn_del(struct hci_conn *hcon, int err) + + /* Kill socket */ + sco_conn_lock(conn); +- sk = conn->sk; ++ sk = sco_sock_hold(conn); + sco_conn_unlock(conn); + + if (sk) { +- sock_hold(sk); + bh_lock_sock(sk); + sco_sock_clear_timer(sk); + sco_chan_del(sk, err); +-- +2.53.0 + diff --git a/queue-5.10/bluetooth-sco-fix-use-after-free-in-sco_recv_frame-d.patch b/queue-5.10/bluetooth-sco-fix-use-after-free-in-sco_recv_frame-d.patch new file mode 100644 index 0000000000..a2c214ed45 --- /dev/null +++ b/queue-5.10/bluetooth-sco-fix-use-after-free-in-sco_recv_frame-d.patch @@ -0,0 +1,65 @@ +From ce001ea24bf3c889949f3b75fd27cc0525c901e6 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 21:24:35 +0000 +Subject: Bluetooth: SCO: Fix use-after-free in sco_recv_frame() due to missing + sock_hold + +From: Hyunwoo Kim + +[ Upstream commit 598dbba9919c5e36c54fe1709b557d64120cb94b ] + +sco_recv_frame() reads conn->sk under sco_conn_lock() but immediately +releases the lock without holding a reference to the socket. A concurrent +close() can free the socket between the lock release and the subsequent +sk->sk_state access, resulting in a use-after-free. + +Other functions in the same file (sco_sock_timeout(), sco_conn_del()) +correctly use sco_sock_hold() to safely hold a reference under the lock. + +Fix by using sco_sock_hold() to take a reference before releasing the +lock, and adding sock_put() on all exit paths. + +Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") +Signed-off-by: Hyunwoo Kim +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Todd Kjos +Signed-off-by: Sasha Levin +--- + net/bluetooth/sco.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c +index 2fbd9c93440ce..b49b2d6bb778e 100644 +--- a/net/bluetooth/sco.c ++++ b/net/bluetooth/sco.c +@@ -312,7 +312,7 @@ static void sco_recv_frame(struct sco_conn *conn, struct sk_buff *skb) + struct sock *sk; + + sco_conn_lock(conn); +- sk = conn->sk; ++ sk = sco_sock_hold(conn); + sco_conn_unlock(conn); + + if (!sk) +@@ -321,11 +321,15 @@ static void sco_recv_frame(struct sco_conn *conn, struct sk_buff *skb) + BT_DBG("sk %p len %d", sk, skb->len); + + if (sk->sk_state != BT_CONNECTED) +- goto drop; ++ goto drop_put; + +- if (!sock_queue_rcv_skb(sk, skb)) ++ if (!sock_queue_rcv_skb(sk, skb)) { ++ sock_put(sk); + return; ++ } + ++drop_put: ++ sock_put(sk); + drop: + kfree_skb(skb); + } +-- +2.53.0 + diff --git a/queue-5.10/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch b/queue-5.10/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch new file mode 100644 index 0000000000..e939e26a32 --- /dev/null +++ b/queue-5.10/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch @@ -0,0 +1,125 @@ +From 2e907dc7f60cb68b88bc912c6a0dea8c0a31ce9f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:45:07 +0100 +Subject: mm/huge_memory: unlock i_mmap_rwsem before releasing after-split + folios + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit e923bd21058ea02fd0dcd3549d151d143fd036e5 ] + +__folio_split() keeps dereferencing the mapping after the split: +shmem_uncharge(mapping->host) and remap_page() while the folios are still +frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the +after-split folios have been unlocked and freed. + +Nothing holds an inode reference across that. The split relies on @folio +-- which the beyond-EOF drop loop never removes, as it starts at +folio_next(folio) -- staying locked and in the page cache to hold off +eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read() +runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure() +passes when splitting a poisoned tail of a shmem THP that reaches past +i_size during truncation, it too is gone from the page cache; so once +@folio is unlocked no locked, in-cache folio pins the inode, and a +concurrent final iput() can evict and RCU-free it before +i_mmap_unlock_read() touches i_mmap_rwsem: + + BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790 + i_mmap_unlock_read include/linux/fs.h:537 [inline] + __folio_split+0x732/0x1640 mm/huge_memory.c:4100 + try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675 + memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470 + + Freed by task 4601: + shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177 + evict+0x57f/0xac0 fs/inode.c:870 + +Do every mapping dereference while @folio still pins the inode: drop +i_mmap_rwsem right after remap_page(), before the loop that unlocks and +frees the after-split folios, and clear @mapping so the exit path does not +unlock it again. shmem_uncharge() and remap_page() already run before +that point, so after this nothing past the unlock loop touches the inode +or the mapping. + +This is now a rule the split depends on, alongside keeping @folio frozen +until the page cache is updated: no inode or mapping dereference once the +after-split folios start being unlocked. + +Link: https://lore.kernel.org/20260716095424.471052-1-kirill@shutemov.name +Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()") +Signed-off-by: Kiryl Shutsemau (Meta) +Reported-by: Hao Zhang +Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc +Co-developed-by: Hao Zhang +Signed-off-by: Hao Zhang +Acked-by: David Hildenbrand (Arm) +Reviewed-by: Zi Yan +Reviewed-by: Baolin Wang +Reviewed-by: Miaohe Lin +Cc: Baolin Wang +Cc: Barry Song +Cc: Dev Jain +Cc: Lance Yang +Cc: Liam R. Howlett +Cc: Lorenzo Stoakes +Cc: Naoya Horiguchi +Cc: Nico Pache +Cc: Ryan Roberts +Cc: +Signed-off-by: Andrew Morton + +(cherry picked from commit e923bd21058ea02fd0dcd3549d151d143fd036e5) +[ kas: adapt to the __split_huge_page()/split_huge_page_to_list() + two-function split: pass @mapping into __split_huge_page() and drop it + there, before the loop that frees the after-split subpages while the + head is still locked; the caller then skips its own i_mmap unlock ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/huge_memory.c | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +index fdcf89e3b1d27..f620861d00995 100644 +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -2445,7 +2445,7 @@ static void __split_huge_page_tail(struct page *head, int tail, + } + + static void __split_huge_page(struct page *page, struct list_head *list, +- pgoff_t end, unsigned long flags) ++ pgoff_t end, unsigned long flags, struct address_space *mapping) + { + struct page *head = compound_head(page); + pg_data_t *pgdat = page_pgdat(head); +@@ -2515,6 +2515,16 @@ static void __split_huge_page(struct page *page, struct list_head *list, + split_swap_cluster(entry); + } + ++ /* ++ * Drop the mapping while the head page is still locked and thus pins ++ * the inode. The loop below may free the after-split subpages -- ++ * including the head, when @page is a tail beyond EOF that the split ++ * dropped from the page cache -- which could otherwise let the inode, ++ * and @mapping, be freed before this unlock. ++ */ ++ if (mapping) ++ i_mmap_unlock_read(mapping); ++ + for (i = 0; i < nr; i++) { + struct page *subpage = head + i; + if (subpage == page) +@@ -2746,7 +2756,9 @@ int split_huge_page_to_list(struct page *page, struct list_head *list) + __dec_node_page_state(head, NR_FILE_THPS); + } + +- __split_huge_page(page, list, end, flags); ++ __split_huge_page(page, list, end, flags, mapping); ++ /* __split_huge_page() dropped the i_mmap lock */ ++ mapping = NULL; + ret = 0; + } else { + spin_unlock(&ds_queue->split_queue_lock); +-- +2.53.0 + diff --git a/queue-5.10/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch b/queue-5.10/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch new file mode 100644 index 0000000000..557b9fd01b --- /dev/null +++ b/queue-5.10/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch @@ -0,0 +1,65 @@ +From 04627e32c1d6329676730aaf81d8e9ce7f793b1b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 00:21:01 +0200 +Subject: net: openvswitch: fix skb leak on flow key update failure during + recirculation + +From: Ilya Maximets + +[ Upstream commit e1cf066244dad576221b7123a0e5005967f25a20 ] + +do_execute_actions() returns right away when execute_recirc() fails on +the last action as it assumes this function always takes ownership of +the skb when 'last' is true. But when the flow key update fails, the +function doesn't free the skb and it ends up leaked. + +This is a very unlikely scenario as it requires the packet to become +unparseable by applying a set of actions on a previously parseable skb, +but should be fixed nevertheless. + +Reported by Sashiko. + +Fixes: 971427f353f3 ("openvswitch: Add recirc and hash action.") +Cc: stable@vger.kernel.org +Signed-off-by: Ilya Maximets +Reviewed-by: Aaron Conole +Link: https://patch.msgid.link/20260727181851.306076-2-i.maximets@ovn.org +Signed-off-by: Jakub Kicinski +[OVS drop reasons are not available in 5.10, hence plain kfree_skb()] +Signed-off-by: Ilya Maximets +Signed-off-by: Sasha Levin +--- + net/openvswitch/actions.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c +index ffd56da14c745..99fff9d6596c8 100644 +--- a/net/openvswitch/actions.c ++++ b/net/openvswitch/actions.c +@@ -1100,6 +1100,10 @@ static int execute_masked_set_action(struct sk_buff *skb, + return err; + } + ++/* When 'last' is true, recirc() should always consume the 'skb'. ++ * Otherwise, recirc() should keep 'skb' intact regardless what ++ * actions are executed on recirculation. ++ */ + static int execute_recirc(struct datapath *dp, struct sk_buff *skb, + struct sw_flow_key *key, + const struct nlattr *a, bool last) +@@ -1110,8 +1114,11 @@ static int execute_recirc(struct datapath *dp, struct sk_buff *skb, + int err; + + err = ovs_flow_key_update(skb, key); +- if (err) ++ if (err) { ++ if (last) ++ kfree_skb(skb); + return err; ++ } + } + BUG_ON(!is_flow_key_valid(key)); + +-- +2.53.0 + diff --git a/queue-5.10/series b/queue-5.10/series index c75747cfae..c2315769f4 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -300,3 +300,7 @@ drm-amdgpu-cap-gtt-size-to-physical-ram-on-apus.patch drm-vmwgfx-validate-draw_primitives-header-size-before-division.patch drm-vmwgfx-bound-dma-command-body-size-against-suffix-pointer.patch hid-logitech-dj-fix-maxfield-check-in-dj-short-report-validation.patch +mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch +bluetooth-sco-fix-uaf-on-sco_sock_timeout.patch +bluetooth-sco-fix-use-after-free-in-sco_recv_frame-d.patch +net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch diff --git a/queue-5.15/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch b/queue-5.15/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch new file mode 100644 index 0000000000..25466bce5f --- /dev/null +++ b/queue-5.15/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch @@ -0,0 +1,125 @@ +From a95a0d61d69266ff55ffb79e44797c7d0d65cfc4 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:45:12 +0100 +Subject: mm/huge_memory: unlock i_mmap_rwsem before releasing after-split + folios + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit e923bd21058ea02fd0dcd3549d151d143fd036e5 ] + +__folio_split() keeps dereferencing the mapping after the split: +shmem_uncharge(mapping->host) and remap_page() while the folios are still +frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the +after-split folios have been unlocked and freed. + +Nothing holds an inode reference across that. The split relies on @folio +-- which the beyond-EOF drop loop never removes, as it starts at +folio_next(folio) -- staying locked and in the page cache to hold off +eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read() +runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure() +passes when splitting a poisoned tail of a shmem THP that reaches past +i_size during truncation, it too is gone from the page cache; so once +@folio is unlocked no locked, in-cache folio pins the inode, and a +concurrent final iput() can evict and RCU-free it before +i_mmap_unlock_read() touches i_mmap_rwsem: + + BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790 + i_mmap_unlock_read include/linux/fs.h:537 [inline] + __folio_split+0x732/0x1640 mm/huge_memory.c:4100 + try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675 + memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470 + + Freed by task 4601: + shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177 + evict+0x57f/0xac0 fs/inode.c:870 + +Do every mapping dereference while @folio still pins the inode: drop +i_mmap_rwsem right after remap_page(), before the loop that unlocks and +frees the after-split folios, and clear @mapping so the exit path does not +unlock it again. shmem_uncharge() and remap_page() already run before +that point, so after this nothing past the unlock loop touches the inode +or the mapping. + +This is now a rule the split depends on, alongside keeping @folio frozen +until the page cache is updated: no inode or mapping dereference once the +after-split folios start being unlocked. + +Link: https://lore.kernel.org/20260716095424.471052-1-kirill@shutemov.name +Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()") +Signed-off-by: Kiryl Shutsemau (Meta) +Reported-by: Hao Zhang +Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc +Co-developed-by: Hao Zhang +Signed-off-by: Hao Zhang +Acked-by: David Hildenbrand (Arm) +Reviewed-by: Zi Yan +Reviewed-by: Baolin Wang +Reviewed-by: Miaohe Lin +Cc: Baolin Wang +Cc: Barry Song +Cc: Dev Jain +Cc: Lance Yang +Cc: Liam R. Howlett +Cc: Lorenzo Stoakes +Cc: Naoya Horiguchi +Cc: Nico Pache +Cc: Ryan Roberts +Cc: +Signed-off-by: Andrew Morton + +(cherry picked from commit e923bd21058ea02fd0dcd3549d151d143fd036e5) +[ kas: adapt to the __split_huge_page()/split_huge_page_to_list() + two-function split: pass @mapping into __split_huge_page() and drop it + there, before the loop that frees the after-split subpages while the + head is still locked; the caller then skips its own i_mmap unlock ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/huge_memory.c | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +index 934c08de74f42..94398e024d3a3 100644 +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -2403,7 +2403,7 @@ static void __split_huge_page_tail(struct page *head, int tail, + } + + static void __split_huge_page(struct page *page, struct list_head *list, +- pgoff_t end) ++ pgoff_t end, struct address_space *mapping) + { + struct page *head = compound_head(page); + struct lruvec *lruvec; +@@ -2476,6 +2476,16 @@ static void __split_huge_page(struct page *page, struct list_head *list, + split_swap_cluster(entry); + } + ++ /* ++ * Drop the mapping while the head page is still locked and thus pins ++ * the inode. The loop below may free the after-split subpages -- ++ * including the head, when @page is a tail beyond EOF that the split ++ * dropped from the page cache -- which could otherwise let the inode, ++ * and @mapping, be freed before this unlock. ++ */ ++ if (mapping) ++ i_mmap_unlock_read(mapping); ++ + for (i = 0; i < nr; i++) { + struct page *subpage = head + i; + if (subpage == page) +@@ -2717,7 +2727,9 @@ int split_huge_page_to_list(struct page *page, struct list_head *list) + } + } + +- __split_huge_page(page, list, end); ++ __split_huge_page(page, list, end, mapping); ++ /* __split_huge_page() dropped the i_mmap lock */ ++ mapping = NULL; + ret = 0; + } else { + spin_unlock(&ds_queue->split_queue_lock); +-- +2.53.0 + diff --git a/queue-5.15/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch b/queue-5.15/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch new file mode 100644 index 0000000000..94db0ba0fa --- /dev/null +++ b/queue-5.15/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch @@ -0,0 +1,65 @@ +From 062bb2e0b8209517023291e58d6bd09078a5fa1d Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 00:20:07 +0200 +Subject: net: openvswitch: fix skb leak on flow key update failure during + recirculation + +From: Ilya Maximets + +[ Upstream commit e1cf066244dad576221b7123a0e5005967f25a20 ] + +do_execute_actions() returns right away when execute_recirc() fails on +the last action as it assumes this function always takes ownership of +the skb when 'last' is true. But when the flow key update fails, the +function doesn't free the skb and it ends up leaked. + +This is a very unlikely scenario as it requires the packet to become +unparseable by applying a set of actions on a previously parseable skb, +but should be fixed nevertheless. + +Reported by Sashiko. + +Fixes: 971427f353f3 ("openvswitch: Add recirc and hash action.") +Cc: stable@vger.kernel.org +Signed-off-by: Ilya Maximets +Reviewed-by: Aaron Conole +Link: https://patch.msgid.link/20260727181851.306076-2-i.maximets@ovn.org +Signed-off-by: Jakub Kicinski +[OVS drop reasons are not available in 5.15, hence plain kfree_skb()] +Signed-off-by: Ilya Maximets +Signed-off-by: Sasha Levin +--- + net/openvswitch/actions.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c +index 3a69ed9d21926..6fd22d3632ba3 100644 +--- a/net/openvswitch/actions.c ++++ b/net/openvswitch/actions.c +@@ -1107,6 +1107,10 @@ static int execute_masked_set_action(struct sk_buff *skb, + return err; + } + ++/* When 'last' is true, recirc() should always consume the 'skb'. ++ * Otherwise, recirc() should keep 'skb' intact regardless what ++ * actions are executed on recirculation. ++ */ + static int execute_recirc(struct datapath *dp, struct sk_buff *skb, + struct sw_flow_key *key, + const struct nlattr *a, bool last) +@@ -1117,8 +1121,11 @@ static int execute_recirc(struct datapath *dp, struct sk_buff *skb, + int err; + + err = ovs_flow_key_update(skb, key); +- if (err) ++ if (err) { ++ if (last) ++ kfree_skb(skb); + return err; ++ } + } + BUG_ON(!is_flow_key_valid(key)); + +-- +2.53.0 + diff --git a/queue-5.15/series b/queue-5.15/series index a7a10458dc..139cb65a17 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -359,3 +359,5 @@ drm-amdgpu-cap-gtt-size-to-physical-ram-on-apus.patch drm-vmwgfx-validate-draw_primitives-header-size-before-division.patch drm-vmwgfx-bound-dma-command-body-size-against-suffix-pointer.patch hid-logitech-dj-fix-maxfield-check-in-dj-short-report-validation.patch +mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch +net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch diff --git a/queue-6.1/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch b/queue-6.1/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch new file mode 100644 index 0000000000..1d36a3dfed --- /dev/null +++ b/queue-6.1/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch @@ -0,0 +1,125 @@ +From 0ce3e869cfb8e4c1f8133f2a6e0f3138b2af6c63 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:45:16 +0100 +Subject: mm/huge_memory: unlock i_mmap_rwsem before releasing after-split + folios + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit e923bd21058ea02fd0dcd3549d151d143fd036e5 ] + +__folio_split() keeps dereferencing the mapping after the split: +shmem_uncharge(mapping->host) and remap_page() while the folios are still +frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the +after-split folios have been unlocked and freed. + +Nothing holds an inode reference across that. The split relies on @folio +-- which the beyond-EOF drop loop never removes, as it starts at +folio_next(folio) -- staying locked and in the page cache to hold off +eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read() +runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure() +passes when splitting a poisoned tail of a shmem THP that reaches past +i_size during truncation, it too is gone from the page cache; so once +@folio is unlocked no locked, in-cache folio pins the inode, and a +concurrent final iput() can evict and RCU-free it before +i_mmap_unlock_read() touches i_mmap_rwsem: + + BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790 + i_mmap_unlock_read include/linux/fs.h:537 [inline] + __folio_split+0x732/0x1640 mm/huge_memory.c:4100 + try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675 + memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470 + + Freed by task 4601: + shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177 + evict+0x57f/0xac0 fs/inode.c:870 + +Do every mapping dereference while @folio still pins the inode: drop +i_mmap_rwsem right after remap_page(), before the loop that unlocks and +frees the after-split folios, and clear @mapping so the exit path does not +unlock it again. shmem_uncharge() and remap_page() already run before +that point, so after this nothing past the unlock loop touches the inode +or the mapping. + +This is now a rule the split depends on, alongside keeping @folio frozen +until the page cache is updated: no inode or mapping dereference once the +after-split folios start being unlocked. + +Link: https://lore.kernel.org/20260716095424.471052-1-kirill@shutemov.name +Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()") +Signed-off-by: Kiryl Shutsemau (Meta) +Reported-by: Hao Zhang +Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc +Co-developed-by: Hao Zhang +Signed-off-by: Hao Zhang +Acked-by: David Hildenbrand (Arm) +Reviewed-by: Zi Yan +Reviewed-by: Baolin Wang +Reviewed-by: Miaohe Lin +Cc: Baolin Wang +Cc: Barry Song +Cc: Dev Jain +Cc: Lance Yang +Cc: Liam R. Howlett +Cc: Lorenzo Stoakes +Cc: Naoya Horiguchi +Cc: Nico Pache +Cc: Ryan Roberts +Cc: +Signed-off-by: Andrew Morton + +(cherry picked from commit e923bd21058ea02fd0dcd3549d151d143fd036e5) +[ kas: adapt to the __split_huge_page()/split_huge_page_to_list() + two-function split: pass @mapping into __split_huge_page() and drop it + there, before the loop that frees the after-split subpages while the + head is still locked; the caller then skips its own i_mmap unlock ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/huge_memory.c | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +index 7023bdf489605..153f06e42866c 100644 +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -2516,7 +2516,7 @@ static void __split_huge_page_tail(struct page *head, int tail, + } + + static void __split_huge_page(struct page *page, struct list_head *list, +- pgoff_t end) ++ pgoff_t end, struct address_space *mapping) + { + struct folio *folio = page_folio(page); + struct page *head = &folio->page; +@@ -2594,6 +2594,16 @@ static void __split_huge_page(struct page *page, struct list_head *list, + split_swap_cluster(entry); + } + ++ /* ++ * Drop the mapping while the head page is still locked and thus pins ++ * the inode. The loop below may free the after-split subpages -- ++ * including the head, when @page is a tail beyond EOF that the split ++ * dropped from the page cache -- which could otherwise let the inode, ++ * and @mapping, be freed before this unlock. ++ */ ++ if (mapping) ++ i_mmap_unlock_read(mapping); ++ + for (i = 0; i < nr; i++) { + struct page *subpage = head + i; + if (subpage == page) +@@ -2774,7 +2784,9 @@ int split_huge_page_to_list(struct page *page, struct list_head *list) + } + } + +- __split_huge_page(page, list, end); ++ __split_huge_page(page, list, end, mapping); ++ /* __split_huge_page() dropped the i_mmap lock */ ++ mapping = NULL; + ret = 0; + } else { + spin_unlock(&ds_queue->split_queue_lock); +-- +2.53.0 + diff --git a/queue-6.1/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch b/queue-6.1/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch new file mode 100644 index 0000000000..d296df0f66 --- /dev/null +++ b/queue-6.1/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch @@ -0,0 +1,114 @@ +From 2e486744764f7d972f5f9db98fb69f207706ea73 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:43:30 +0100 +Subject: mm/hugetlb: fix swap entry corruption when clearing uffd-wp at fork() + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit 83abe2fd5b3aeb3123b5408a5a91709c5538fb23 ] + +copy_hugetlb_page_range() clears the uffd-wp bit of migration and hwpoison +entries with huge_pte_clear_uffd_wp(), which operates on the present-PTE +bit position. Swap entries keep the uffd-wp state elsewhere -- the +migration branch reads and sets it with pte_swp_uffd_wp() and +pte_swp_mkuffd_wp() -- and the present-PTE position falls into the swap +payload. On x86-64 it lands in the inverted swap offset, where a +naturally-aligned hugetlb PFN always has the affected bit set, so the +clear advances the encoded PFN by two pages. + +No userfaultfd needs to be involved: the clear is guarded only by the +child VMA not being uffd-wp registered, so a plain fork() with an +in-flight hugetlb migration entry (or a poisoned hugetlb page) corrupts +the entry copied into the child. Instrumenting the clear and forking +after MADV_HWPOISON on a 2MB anon hugetlb page shows: + + offset before=120e00 + offset after =120e02 + +The fallout is mostly latent: rmap walks match migration entries by folio +range and remove_migration_pte() rebuilds the PTE from the folio, so a +within-folio PFN skew heals once migration completes. But any path that +re-encodes the corrupted offset -- e.g. hugetlb_change_protection() +rewriting a writable migration entry via +make_readable_migration_entry(swp_offset(entry)) -- propagates it. + +Migration entries legitimately carry uffd-wp, so clear it with +pte_swp_clear_uffd_wp(), matching copy_nonpresent_pte() and +move_huge_pte(). + +A hwpoison entry, on the other hand, never carries the uffd-wp bit: it is +installed fresh by make_hwpoison_entry() (try_to_unmap_one() does not +preserve uffd-wp on the hwpoison path) and hugetlb_change_protection() +leaves hwpoison entries untouched. There was nothing to clear there, only +the corruption, so drop the clear entirely. + +Link: https://lore.kernel.org/20260708090110.136162-1-kirill@shutemov.name +Fixes: bc70fbf269fd ("mm/hugetlb: handle uffd-wp during fork()") +Signed-off-by: Kiryl Shutsemau +Reported-by: Sashiko AI review +Closes: https://lore.kernel.org/all/20260703140011.99E601F000E9@smtp.kernel.org/ +Suggested-by: David Hildenbrand +Acked-by: David Hildenbrand (Arm) +Assisted-by: Claude:claude-fable-5 +Cc: Muchun Song +Cc: Oscar Salvador +Cc: Peter Xu +Cc: +Signed-off-by: Andrew Morton +(cherry picked from commit 83abe2fd5b3aeb3123b5408a5a91709c5538fb23) +[ kas: 6.1 predates the huge_pte_*uffd_wp() -> pte_swp_*uffd_wp() + conversion in copy_hugetlb_page_range() (commit 5a2f8d22ace4), so apply + the fix inline: convert the migration branch's uffd-wp read and set to + the swap-position helpers too, otherwise the src re-encode + (huge_pte_mkuffd_wp) corrupts the offset the same way; and drop the + hwpoison clear ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/hugetlb.c | 18 ++++++++++-------- + 1 file changed, 10 insertions(+), 8 deletions(-) + +diff --git a/mm/hugetlb.c b/mm/hugetlb.c +index c7683d00584fd..ffe5196b4afa6 100644 +--- a/mm/hugetlb.c ++++ b/mm/hugetlb.c +@@ -5106,14 +5106,16 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, + */ + ; + } else if (unlikely(is_hugetlb_entry_hwpoisoned(entry))) { +- bool uffd_wp = huge_pte_uffd_wp(entry); +- +- if (!userfaultfd_wp(dst_vma) && uffd_wp) +- entry = huge_pte_clear_uffd_wp(entry); ++ /* ++ * A hwpoison entry never carries the uffd-wp bit: it is ++ * installed fresh by make_hwpoison_entry() and ++ * hugetlb_change_protection() leaves it untouched, so ++ * there is nothing to clear for the child. ++ */ + set_huge_pte_at(dst, addr, dst_pte, entry); + } else if (unlikely(is_hugetlb_entry_migration(entry))) { + swp_entry_t swp_entry = pte_to_swp_entry(entry); +- bool uffd_wp = huge_pte_uffd_wp(entry); ++ bool uffd_wp = pte_swp_uffd_wp(entry); + + if (!is_readable_migration_entry(swp_entry) && cow) { + /* +@@ -5124,11 +5126,11 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, + swp_offset(swp_entry)); + entry = swp_entry_to_pte(swp_entry); + if (userfaultfd_wp(src_vma) && uffd_wp) +- entry = huge_pte_mkuffd_wp(entry); ++ entry = pte_swp_mkuffd_wp(entry); + set_huge_pte_at(src, addr, src_pte, entry); + } +- if (!userfaultfd_wp(dst_vma) && uffd_wp) +- entry = huge_pte_clear_uffd_wp(entry); ++ if (!userfaultfd_wp(dst_vma)) ++ entry = pte_swp_clear_uffd_wp(entry); + set_huge_pte_at(dst, addr, dst_pte, entry); + } else if (unlikely(is_pte_marker(entry))) { + /* +-- +2.53.0 + diff --git a/queue-6.1/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch b/queue-6.1/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch new file mode 100644 index 0000000000..55b8ec7c4e --- /dev/null +++ b/queue-6.1/net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch @@ -0,0 +1,65 @@ +From bad565cef9e3e755e0b5724fc62aee47764a878f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 00:19:18 +0200 +Subject: net: openvswitch: fix skb leak on flow key update failure during + recirculation + +From: Ilya Maximets + +[ Upstream commit e1cf066244dad576221b7123a0e5005967f25a20 ] + +do_execute_actions() returns right away when execute_recirc() fails on +the last action as it assumes this function always takes ownership of +the skb when 'last' is true. But when the flow key update fails, the +function doesn't free the skb and it ends up leaked. + +This is a very unlikely scenario as it requires the packet to become +unparseable by applying a set of actions on a previously parseable skb, +but should be fixed nevertheless. + +Reported by Sashiko. + +Fixes: 971427f353f3 ("openvswitch: Add recirc and hash action.") +Cc: stable@vger.kernel.org +Signed-off-by: Ilya Maximets +Reviewed-by: Aaron Conole +Link: https://patch.msgid.link/20260727181851.306076-2-i.maximets@ovn.org +Signed-off-by: Jakub Kicinski +[OVS drop reasons are not available in 6.1, hence plain kfree_skb()] +Signed-off-by: Ilya Maximets +Signed-off-by: Sasha Levin +--- + net/openvswitch/actions.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c +index db4b6929d5bc5..679409953ac50 100644 +--- a/net/openvswitch/actions.c ++++ b/net/openvswitch/actions.c +@@ -1107,6 +1107,10 @@ static int execute_masked_set_action(struct sk_buff *skb, + return err; + } + ++/* When 'last' is true, recirc() should always consume the 'skb'. ++ * Otherwise, recirc() should keep 'skb' intact regardless what ++ * actions are executed on recirculation. ++ */ + static int execute_recirc(struct datapath *dp, struct sk_buff *skb, + struct sw_flow_key *key, + const struct nlattr *a, bool last) +@@ -1117,8 +1121,11 @@ static int execute_recirc(struct datapath *dp, struct sk_buff *skb, + int err; + + err = ovs_flow_key_update(skb, key); +- if (err) ++ if (err) { ++ if (last) ++ kfree_skb(skb); + return err; ++ } + } + BUG_ON(!is_flow_key_valid(key)); + +-- +2.53.0 + diff --git a/queue-6.1/series b/queue-6.1/series index e8150aa85b..2775ac515f 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -475,3 +475,6 @@ drm-vmwgfx-bound-dma-command-body-size-against-suffix-pointer.patch hid-logitech-dj-fix-maxfield-check-in-dj-short-report-validation.patch ata-libahci_platform-do-not-set-mask_port_map-when-not-needed.patch ata-ahci-make-ahci_ignore_port-handle-empty-mask_port_map.patch +mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch +mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch +net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch diff --git a/queue-6.12/fs-proc-task_mmu-fix-pagemap_scan-written-state-for-.patch b/queue-6.12/fs-proc-task_mmu-fix-pagemap_scan-written-state-for-.patch new file mode 100644 index 0000000000..8c0f59a468 --- /dev/null +++ b/queue-6.12/fs-proc-task_mmu-fix-pagemap_scan-written-state-for-.patch @@ -0,0 +1,89 @@ +From 486c83bce0b2e7e4b4be509134aa2fd91ad47952 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:43:49 +0100 +Subject: fs/proc/task_mmu: fix PAGEMAP_SCAN written state for unpopulated ptes + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit 07b4377bdbe74a3ec0c8da5849d014f70e003384 ] + +PAGEMAP_SCAN reports an unpopulated pte differently depending on which +path serves the request. The PAGE_IS_WRITTEN fast path in +pagemap_scan_pmd_entry() reports a pte_none as written (and, under +PM_SCAN_WP_MATCHING, arms a marker); pagemap_page_category() returns 0 for +the same pte_none. A request that cannot take the fast path (an extra +category bit, category_anyof_mask or category_inverted) therefore reports +the pte as clean and skips arming it. + +A range that was populated and then MADV_DONTNEED'd reads as written via +one mask and clean via another, and in the latter case is not re-armed for +the next round -- an incremental-dump consumer (e.g. CRIU) using a richer +mask drops the zapped range and stops tracking writes to it. + +Report pte_none as written in pagemap_page_category() too. A pte_none +carries no uffd-wp marker, i.e. it is not write-protected -- the same +condition under which the present and swap cases already report +PAGE_IS_WRITTEN. The fast path applies no VMA test, so neither does this. + +The hugetlb and fully-unpopulated-PMD (no page table) scans have no +PAGE_IS_WRITTEN fast path, so they do not exhibit the per-entry divergence +and are left unchanged. + +Add a pagemap_ioctl selftest that populates a range, drops it with +MADV_DONTNEED, and checks that the fast path and the generic +(category_anyof_mask) path both report every page written. + +Link: https://lore.kernel.org/20260707151349.92143-1-kirill@shutemov.name +Fixes: 12f6b01a0bcb ("fs/proc/task_mmu: add fast paths to get/clear PAGE_IS_WRITTEN flag") +Signed-off-by: Kiryl Shutsemau +Cc: Muhammad Usama Anjum +Cc: David Hildenbrand +Cc: Jann Horn +Cc: Liam R. Howlett +Cc: Lorenzo Stoakes +Cc: Michal Hocko +Cc: Mike Rapoport +Cc: Pedro Falcato +Cc: Peter Xu +Cc: Shuah Khan +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Assisted-by: Claude:claude-fable-5 +Cc: +Signed-off-by: Andrew Morton +(cherry picked from commit 07b4377bdbe74a3ec0c8da5849d014f70e003384) +[ kas: fix only; selftest hunk dropped (pagemap_ioctl.c differs on this tree) ] + +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + fs/proc/task_mmu.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c +index a521f0faa0a49..44dc442b4f0e7 100644 +--- a/fs/proc/task_mmu.c ++++ b/fs/proc/task_mmu.c +@@ -2097,6 +2097,19 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, + { + unsigned long categories = 0; + ++ if (pte_none(pte)) { ++ /* ++ * An unpopulated pte carries no uffd-wp marker, i.e. it is not ++ * write-protected, the same condition under which the present ++ * and swap cases below report PAGE_IS_WRITTEN. Report it here ++ * too so this generic path agrees with the PAGE_IS_WRITTEN fast ++ * path in pagemap_scan_pmd_entry(), which reports pte_none as ++ * written and, under PM_SCAN_WP_MATCHING, arms a marker. The ++ * fast path applies no VMA test, so neither does this. ++ */ ++ return PAGE_IS_WRITTEN; ++ } ++ + if (pte_present(pte)) { + struct page *page; + +-- +2.53.0 + diff --git a/queue-6.12/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch b/queue-6.12/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch new file mode 100644 index 0000000000..67f26d930d --- /dev/null +++ b/queue-6.12/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch @@ -0,0 +1,125 @@ +From 8d90b9228590d70236b730657e5d97713aea9ddc Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:45:22 +0100 +Subject: mm/huge_memory: unlock i_mmap_rwsem before releasing after-split + folios + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit e923bd21058ea02fd0dcd3549d151d143fd036e5 ] + +__folio_split() keeps dereferencing the mapping after the split: +shmem_uncharge(mapping->host) and remap_page() while the folios are still +frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the +after-split folios have been unlocked and freed. + +Nothing holds an inode reference across that. The split relies on @folio +-- which the beyond-EOF drop loop never removes, as it starts at +folio_next(folio) -- staying locked and in the page cache to hold off +eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read() +runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure() +passes when splitting a poisoned tail of a shmem THP that reaches past +i_size during truncation, it too is gone from the page cache; so once +@folio is unlocked no locked, in-cache folio pins the inode, and a +concurrent final iput() can evict and RCU-free it before +i_mmap_unlock_read() touches i_mmap_rwsem: + + BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790 + i_mmap_unlock_read include/linux/fs.h:537 [inline] + __folio_split+0x732/0x1640 mm/huge_memory.c:4100 + try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675 + memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470 + + Freed by task 4601: + shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177 + evict+0x57f/0xac0 fs/inode.c:870 + +Do every mapping dereference while @folio still pins the inode: drop +i_mmap_rwsem right after remap_page(), before the loop that unlocks and +frees the after-split folios, and clear @mapping so the exit path does not +unlock it again. shmem_uncharge() and remap_page() already run before +that point, so after this nothing past the unlock loop touches the inode +or the mapping. + +This is now a rule the split depends on, alongside keeping @folio frozen +until the page cache is updated: no inode or mapping dereference once the +after-split folios start being unlocked. + +Link: https://lore.kernel.org/20260716095424.471052-1-kirill@shutemov.name +Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()") +Signed-off-by: Kiryl Shutsemau (Meta) +Reported-by: Hao Zhang +Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc +Co-developed-by: Hao Zhang +Signed-off-by: Hao Zhang +Acked-by: David Hildenbrand (Arm) +Reviewed-by: Zi Yan +Reviewed-by: Baolin Wang +Reviewed-by: Miaohe Lin +Cc: Baolin Wang +Cc: Barry Song +Cc: Dev Jain +Cc: Lance Yang +Cc: Liam R. Howlett +Cc: Lorenzo Stoakes +Cc: Naoya Horiguchi +Cc: Nico Pache +Cc: Ryan Roberts +Cc: +Signed-off-by: Andrew Morton + +(cherry picked from commit e923bd21058ea02fd0dcd3549d151d143fd036e5) +[ kas: adapt to the __split_huge_page()/split_huge_page_to_list_to_order() + two-function split: pass @mapping into __split_huge_page() and drop it + there, before the loop that frees the after-split subpages while the + head is still locked; the caller then skips its own i_mmap unlock ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/huge_memory.c | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +index e60c21b924644..b2daa1767d900 100644 +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -3206,7 +3206,7 @@ static void __split_huge_page_tail(struct folio *folio, int tail, + } + + static void __split_huge_page(struct page *page, struct list_head *list, +- pgoff_t end, unsigned int new_order) ++ pgoff_t end, unsigned int new_order, struct address_space *mapping) + { + struct folio *folio = page_folio(page); + /* Scan poisoned pages when split a poisoned folio to large folios */ +@@ -3303,6 +3303,16 @@ static void __split_huge_page(struct page *page, struct list_head *list, + if (new_order) + page = compound_head(page); + ++ /* ++ * Drop the mapping while the head page is still locked and thus pins ++ * the inode. The loop below may free the after-split subpages -- ++ * including the head, when @page is a tail beyond EOF that the split ++ * dropped from the page cache -- which could otherwise let the inode, ++ * and @mapping, be freed before this unlock. ++ */ ++ if (mapping) ++ i_mmap_unlock_read(mapping); ++ + for (i = 0; i < nr; i += new_nr) { + struct page *subpage = head + i; + struct folio *new_folio = page_folio(subpage); +@@ -3580,7 +3590,9 @@ int split_huge_page_to_list_to_order(struct page *page, struct list_head *list, + mod_mthp_stat(order, MTHP_STAT_NR_ANON, -1); + mod_mthp_stat(new_order, MTHP_STAT_NR_ANON, 1 << (order - new_order)); + } +- __split_huge_page(page, list, end, new_order); ++ __split_huge_page(page, list, end, new_order, mapping); ++ /* __split_huge_page() dropped the i_mmap lock */ ++ mapping = NULL; + ret = 0; + } else { + spin_unlock(&ds_queue->split_queue_lock); +-- +2.53.0 + diff --git a/queue-6.12/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch b/queue-6.12/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch new file mode 100644 index 0000000000..0ab77d2db0 --- /dev/null +++ b/queue-6.12/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch @@ -0,0 +1,96 @@ +From 9add7c1fec764f2d02a469ece9fc4b76408c6edd Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:43:43 +0100 +Subject: mm/hugetlb: fix swap entry corruption when clearing uffd-wp at fork() + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit 83abe2fd5b3aeb3123b5408a5a91709c5538fb23 ] + +copy_hugetlb_page_range() clears the uffd-wp bit of migration and hwpoison +entries with huge_pte_clear_uffd_wp(), which operates on the present-PTE +bit position. Swap entries keep the uffd-wp state elsewhere -- the +migration branch reads and sets it with pte_swp_uffd_wp() and +pte_swp_mkuffd_wp() -- and the present-PTE position falls into the swap +payload. On x86-64 it lands in the inverted swap offset, where a +naturally-aligned hugetlb PFN always has the affected bit set, so the +clear advances the encoded PFN by two pages. + +No userfaultfd needs to be involved: the clear is guarded only by the +child VMA not being uffd-wp registered, so a plain fork() with an +in-flight hugetlb migration entry (or a poisoned hugetlb page) corrupts +the entry copied into the child. Instrumenting the clear and forking +after MADV_HWPOISON on a 2MB anon hugetlb page shows: + + offset before=120e00 + offset after =120e02 + +The fallout is mostly latent: rmap walks match migration entries by folio +range and remove_migration_pte() rebuilds the PTE from the folio, so a +within-folio PFN skew heals once migration completes. But any path that +re-encodes the corrupted offset -- e.g. hugetlb_change_protection() +rewriting a writable migration entry via +make_readable_migration_entry(swp_offset(entry)) -- propagates it. + +Migration entries legitimately carry uffd-wp, so clear it with +pte_swp_clear_uffd_wp(), matching copy_nonpresent_pte() and +move_huge_pte(). + +A hwpoison entry, on the other hand, never carries the uffd-wp bit: it is +installed fresh by make_hwpoison_entry() (try_to_unmap_one() does not +preserve uffd-wp on the hwpoison path) and hugetlb_change_protection() +leaves hwpoison entries untouched. There was nothing to clear there, only +the corruption, so drop the clear entirely. + +Link: https://lore.kernel.org/20260708090110.136162-1-kirill@shutemov.name +Fixes: bc70fbf269fd ("mm/hugetlb: handle uffd-wp during fork()") +Signed-off-by: Kiryl Shutsemau +Reported-by: Sashiko AI review +Closes: https://lore.kernel.org/all/20260703140011.99E601F000E9@smtp.kernel.org/ +Suggested-by: David Hildenbrand +Acked-by: David Hildenbrand (Arm) +Assisted-by: Claude:claude-fable-5 +Cc: Muchun Song +Cc: Oscar Salvador +Cc: Peter Xu +Cc: +Signed-off-by: Andrew Morton +(cherry picked from commit 83abe2fd5b3aeb3123b5408a5a91709c5538fb23) +[ kas: adapt to the pre-softleaf idiom ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/hugetlb.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/mm/hugetlb.c b/mm/hugetlb.c +index 2fb6e45a1ea0d..e910ed0c43d0b 100644 +--- a/mm/hugetlb.c ++++ b/mm/hugetlb.c +@@ -5281,8 +5281,12 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, + */ + ; + } else if (unlikely(is_hugetlb_entry_hwpoisoned(entry))) { +- if (!userfaultfd_wp(dst_vma)) +- entry = huge_pte_clear_uffd_wp(entry); ++ /* ++ * A hwpoison entry never carries the uffd-wp bit: it is ++ * installed fresh by make_hwpoison_entry() and ++ * hugetlb_change_protection() leaves it untouched, so ++ * there is nothing to clear for the child. ++ */ + set_huge_pte_at(dst, addr, dst_pte, entry, sz); + } else if (unlikely(is_hugetlb_entry_migration(entry))) { + swp_entry_t swp_entry = pte_to_swp_entry(entry); +@@ -5301,7 +5305,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, + set_huge_pte_at(src, addr, src_pte, entry, sz); + } + if (!userfaultfd_wp(dst_vma)) +- entry = huge_pte_clear_uffd_wp(entry); ++ entry = pte_swp_clear_uffd_wp(entry); + set_huge_pte_at(dst, addr, dst_pte, entry, sz); + } else if (unlikely(is_pte_marker(entry))) { + pte_marker marker = copy_pte_marker( +-- +2.53.0 + diff --git a/queue-6.12/series b/queue-6.12/series index 84862f6fdb..33c2465854 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -283,3 +283,6 @@ drm-xe-rtp-toggle-deny-bit-to-de-whitelist-oa-regs.patch drm-xe-rtp-de-whitelist-oa-registers-for-all-hwe-s-f.patch drm-xe-oa-de-whitelist-oa-registers-on-oa-stream-ope.patch drm-xe-rtp-ensure-locking-ref-counting-for-oa-whitel.patch +mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch +fs-proc-task_mmu-fix-pagemap_scan-written-state-for-.patch +mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch diff --git a/queue-6.18/file-add-fd_-add-prepare.patch b/queue-6.18/file-add-fd_-add-prepare.patch new file mode 100644 index 0000000000..bac12ee40d --- /dev/null +++ b/queue-6.18/file-add-fd_-add-prepare.patch @@ -0,0 +1,234 @@ +From e5f42b8df94634180b4fd5dc4ae634c984f03cca Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 01:39:52 +0800 +Subject: file: add FD_{ADD,PREPARE}() + +From: Christian Brauner + +[ Upstream commit 011703a9acd76edc7c85d80dbccb6e50dba53aad ] + +I've been playing with this to allow for moderately flexible usage of +the get_unused_fd_flags() + create file + fd_install() pattern that's +used quite extensively. + +How callers allocate files is really heterogenous so it's not really +convenient to fold them into a single class. It's possibe to split them +into subclasses like for anon inodes. I think that's not necessarily +nice as well. + +My take is to add two primites: +(1) FD_ADD() the simple cases a file is installed: + + fd = FD_ADD(O_CLOEXEC, open_file(some, args))); + if (fd >= 0) + kvm_get_kvm(vcpu->kvm); + return fd; + +(2) FD_PREPARE() that captures all the cases where access to fd or file + or additional work before publishing the fd is needed: + + FD_PREPARE(fdf, open_flag, file_open_handle(&path, open_flag)); + if (fdf.err) + return fdf.err; + + if (copy_to_user(/* something something */)) + return -EFAULT; + + return fd_publish(fdf); + +I've converted all of the easy cases over to it and it gets rid of an +aweful lot of convoluted cleanup logic. + +It's centered around struct fd_prepare. FD_PREPARE() encapsulates all of +allocation and cleanup logic and must be followed by a call to +fd_publish() which associates the fd with the file and installs it into +the callers fdtable. If fd_publish() isn't called both are deallocated. + +It mandates a specific order namely that first we allocate the fd and +then instantiate the file. But that shouldn't be a problem nearly +everyone I've converted uses this exact pattern anyway. + +There's a bunch of additional cases where it would be easy to convert +them to this pattern. For example, the whole sync file stuff in dma +currently retains the containing structure of the file instead of the +file itself even though it's only used to allocate files. Changing that +would make it fall into the FD_PREPARE() pattern easily. I've not done +that work yet. + +There's room for extending this in a way that wed'd have subclasses for +some particularly often use patterns but as I said I'm not even sure +that's worth it. + +Link: https://patch.msgid.link/20251123-work-fd-prepare-v4-1-b6efa1706cfd@kernel.org +Signed-off-by: Christian Brauner +(cherry picked from commit 011703a9acd76edc7c85d80dbccb6e50dba53aad) +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + include/linux/cleanup.h | 7 +++ + include/linux/file.h | 126 ++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 133 insertions(+) + +diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h +index a1194e44b5276..bacefa0f1512f 100644 +--- a/include/linux/cleanup.h ++++ b/include/linux/cleanup.h +@@ -261,6 +261,10 @@ const volatile void * __must_check_fn(const volatile void *val) + * CLASS(name, var)(args...): + * declare the variable @var as an instance of the named class + * ++ * CLASS_INIT(name, var, init_expr): ++ * declare the variable @var as an instance of the named class with ++ * custom initialization expression. ++ * + * Ex. + * + * DEFINE_CLASS(fdget, struct fd, fdput(_T), fdget(fd), int fd) +@@ -290,6 +294,9 @@ static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \ + class_##_name##_t var __cleanup(class_##_name##_destructor) = \ + class_##_name##_constructor + ++#define CLASS_INIT(_name, _var, _init_expr) \ ++ class_##_name##_t _var __cleanup(class_##_name##_destructor) = (_init_expr) ++ + #define __scoped_class(_name, var, _label, args...) \ + for (CLASS(_name, var)(args); ; ({ goto _label; })) \ + if (0) { \ +diff --git a/include/linux/file.h b/include/linux/file.h +index af1768d934a01..cf389fde9bc28 100644 +--- a/include/linux/file.h ++++ b/include/linux/file.h +@@ -127,4 +127,130 @@ extern void __fput_sync(struct file *); + + extern unsigned int sysctl_nr_open_min, sysctl_nr_open_max; + ++/* ++ * fd_prepare: Combined fd + file allocation cleanup class. ++ * @err: Error code to indicate if allocation succeeded. ++ * @__fd: Allocated fd (may not be accessed directly) ++ * @__file: Allocated struct file pointer (may not be accessed directly) ++ * ++ * Allocates an fd and a file together. On error paths, automatically cleans ++ * up whichever resource was successfully allocated. Allows flexible file ++ * allocation with different functions per usage. ++ * ++ * Do not use directly. ++ */ ++struct fd_prepare { ++ s32 err; ++ s32 __fd; /* do not access directly */ ++ struct file *__file; /* do not access directly */ ++}; ++ ++/* Typedef for fd_prepare cleanup guards. */ ++typedef struct fd_prepare class_fd_prepare_t; ++ ++/* ++ * Accessors for fd_prepare class members. ++ * _Generic() is used for zero-cost type safety. ++ */ ++#define fd_prepare_fd(_fdf) \ ++ (_Generic((_fdf), struct fd_prepare: (_fdf).__fd)) ++ ++#define fd_prepare_file(_fdf) \ ++ (_Generic((_fdf), struct fd_prepare: (_fdf).__file)) ++ ++/* Do not use directly. */ ++static inline void class_fd_prepare_destructor(const struct fd_prepare *fdf) ++{ ++ if (unlikely(fdf->err)) { ++ if (likely(fdf->__fd >= 0)) ++ put_unused_fd(fdf->__fd); ++ if (unlikely(!IS_ERR_OR_NULL(fdf->__file))) ++ fput(fdf->__file); ++ } ++} ++ ++/* Do not use directly. */ ++static inline int class_fd_prepare_lock_err(const struct fd_prepare *fdf) ++{ ++ if (unlikely(fdf->err)) ++ return fdf->err; ++ if (unlikely(fdf->__fd < 0)) ++ return fdf->__fd; ++ if (unlikely(IS_ERR(fdf->__file))) ++ return PTR_ERR(fdf->__file); ++ if (unlikely(!fdf->__file)) ++ return -ENOMEM; ++ return 0; ++} ++ ++/* ++ * __FD_PREPARE_INIT - Helper to initialize fd_prepare class. ++ * @_fd_flags: flags for get_unused_fd_flags() ++ * @_file_owned: expression that returns struct file * ++ * ++ * Returns a struct fd_prepare with fd, file, and err set. ++ * If fd allocation fails, fd will be negative and err will be set. If ++ * fd succeeds but file_init_expr fails, file will be ERR_PTR and err ++ * will be set. The err field is the single source of truth for error ++ * checking. ++ */ ++#define __FD_PREPARE_INIT(_fd_flags, _file_owned) \ ++ ({ \ ++ struct fd_prepare fdf = { \ ++ .__fd = get_unused_fd_flags((_fd_flags)), \ ++ }; \ ++ if (likely(fdf.__fd >= 0)) \ ++ fdf.__file = (_file_owned); \ ++ fdf.err = ACQUIRE_ERR(fd_prepare, &fdf); \ ++ fdf; \ ++ }) ++ ++/* ++ * FD_PREPARE - Macro to declare and initialize an fd_prepare variable. ++ * ++ * Declares and initializes an fd_prepare variable with automatic ++ * cleanup. No separate scope required - cleanup happens when variable ++ * goes out of scope. ++ * ++ * @_fdf: name of struct fd_prepare variable to define ++ * @_fd_flags: flags for get_unused_fd_flags() ++ * @_file_owned: struct file to take ownership of (can be expression) ++ */ ++#define FD_PREPARE(_fdf, _fd_flags, _file_owned) \ ++ CLASS_INIT(fd_prepare, _fdf, __FD_PREPARE_INIT(_fd_flags, _file_owned)) ++ ++/* ++ * fd_publish - Publish prepared fd and file to the fd table. ++ * @_fdf: struct fd_prepare variable ++ */ ++#define fd_publish(_fdf) \ ++ ({ \ ++ struct fd_prepare *fdp = &(_fdf); \ ++ VFS_WARN_ON_ONCE(fdp->err); \ ++ VFS_WARN_ON_ONCE(fdp->__fd < 0); \ ++ VFS_WARN_ON_ONCE(IS_ERR_OR_NULL(fdp->__file)); \ ++ fd_install(fdp->__fd, fdp->__file); \ ++ fdp->__fd; \ ++ }) ++ ++/* Do not use directly. */ ++#define __FD_ADD(_fdf, _fd_flags, _file_owned) \ ++ ({ \ ++ FD_PREPARE(_fdf, _fd_flags, _file_owned); \ ++ s32 ret = _fdf.err; \ ++ if (likely(!ret)) \ ++ ret = fd_publish(_fdf); \ ++ ret; \ ++ }) ++ ++/* ++ * FD_ADD - Allocate and install an fd and file in one step. ++ * @_fd_flags: flags for get_unused_fd_flags() ++ * @_file_owned: struct file to take ownership of ++ * ++ * Returns the allocated fd number, or negative error code on failure. ++ */ ++#define FD_ADD(_fd_flags, _file_owned) \ ++ __FD_ADD(__UNIQUE_ID(fd_prepare), _fd_flags, _file_owned) ++ + #endif /* __LINUX_FILE_H */ +-- +2.53.0 + diff --git a/queue-6.18/file-ensure-cleanup.patch b/queue-6.18/file-ensure-cleanup.patch new file mode 100644 index 0000000000..1e981a6936 --- /dev/null +++ b/queue-6.18/file-ensure-cleanup.patch @@ -0,0 +1,61 @@ +From 32def2a8a0e486faf15c5ef99561fbfd944d631f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 01:39:54 +0800 +Subject: file: ensure cleanup + +From: Christian Brauner + +[ Upstream commit d2ea4d254d04a89e17504af0230c7268e3cac6bf ] + +Brown paper bag time. This is a silly oversight where I missed to drop +the error condition checking to ensure we clean up on early error +returns. I have an internal unit testset coming up for this which will +catch all such issues going forward. + +Reported-by: Chris Mason +Reported-by: Jeff Layton +Fixes: 011703a9acd7 ("file: add FD_{ADD,PREPARE}()") +Signed-off-by: Christian Brauner +Reviewed-by: Jeff Layton +Signed-off-by: Linus Torvalds +(cherry picked from commit d2ea4d254d04a89e17504af0230c7268e3cac6bf) +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + include/linux/file.h | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +diff --git a/include/linux/file.h b/include/linux/file.h +index cf389fde9bc28..27484b444d315 100644 +--- a/include/linux/file.h ++++ b/include/linux/file.h +@@ -161,12 +161,10 @@ typedef struct fd_prepare class_fd_prepare_t; + /* Do not use directly. */ + static inline void class_fd_prepare_destructor(const struct fd_prepare *fdf) + { +- if (unlikely(fdf->err)) { +- if (likely(fdf->__fd >= 0)) +- put_unused_fd(fdf->__fd); +- if (unlikely(!IS_ERR_OR_NULL(fdf->__file))) +- fput(fdf->__file); +- } ++ if (unlikely(fdf->__fd >= 0)) ++ put_unused_fd(fdf->__fd); ++ if (unlikely(!IS_ERR_OR_NULL(fdf->__file))) ++ fput(fdf->__file); + } + + /* Do not use directly. */ +@@ -230,7 +228,8 @@ static inline int class_fd_prepare_lock_err(const struct fd_prepare *fdf) + VFS_WARN_ON_ONCE(fdp->__fd < 0); \ + VFS_WARN_ON_ONCE(IS_ERR_OR_NULL(fdp->__file)); \ + fd_install(fdp->__fd, fdp->__file); \ +- fdp->__fd; \ ++ retain_and_null_ptr(fdp->__file); \ ++ take_fd(fdp->__fd); \ + }) + + /* Do not use directly. */ +-- +2.53.0 + diff --git a/queue-6.18/fs-proc-task_mmu-fix-pagemap_scan-written-state-for-.patch b/queue-6.18/fs-proc-task_mmu-fix-pagemap_scan-written-state-for-.patch new file mode 100644 index 0000000000..7c3dd38129 --- /dev/null +++ b/queue-6.18/fs-proc-task_mmu-fix-pagemap_scan-written-state-for-.patch @@ -0,0 +1,89 @@ +From b00f0ed2dd3731ff3df4a0dc787009696f55346a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:43:52 +0100 +Subject: fs/proc/task_mmu: fix PAGEMAP_SCAN written state for unpopulated ptes + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit 07b4377bdbe74a3ec0c8da5849d014f70e003384 ] + +PAGEMAP_SCAN reports an unpopulated pte differently depending on which +path serves the request. The PAGE_IS_WRITTEN fast path in +pagemap_scan_pmd_entry() reports a pte_none as written (and, under +PM_SCAN_WP_MATCHING, arms a marker); pagemap_page_category() returns 0 for +the same pte_none. A request that cannot take the fast path (an extra +category bit, category_anyof_mask or category_inverted) therefore reports +the pte as clean and skips arming it. + +A range that was populated and then MADV_DONTNEED'd reads as written via +one mask and clean via another, and in the latter case is not re-armed for +the next round -- an incremental-dump consumer (e.g. CRIU) using a richer +mask drops the zapped range and stops tracking writes to it. + +Report pte_none as written in pagemap_page_category() too. A pte_none +carries no uffd-wp marker, i.e. it is not write-protected -- the same +condition under which the present and swap cases already report +PAGE_IS_WRITTEN. The fast path applies no VMA test, so neither does this. + +The hugetlb and fully-unpopulated-PMD (no page table) scans have no +PAGE_IS_WRITTEN fast path, so they do not exhibit the per-entry divergence +and are left unchanged. + +Add a pagemap_ioctl selftest that populates a range, drops it with +MADV_DONTNEED, and checks that the fast path and the generic +(category_anyof_mask) path both report every page written. + +Link: https://lore.kernel.org/20260707151349.92143-1-kirill@shutemov.name +Fixes: 12f6b01a0bcb ("fs/proc/task_mmu: add fast paths to get/clear PAGE_IS_WRITTEN flag") +Signed-off-by: Kiryl Shutsemau +Cc: Muhammad Usama Anjum +Cc: David Hildenbrand +Cc: Jann Horn +Cc: Liam R. Howlett +Cc: Lorenzo Stoakes +Cc: Michal Hocko +Cc: Mike Rapoport +Cc: Pedro Falcato +Cc: Peter Xu +Cc: Shuah Khan +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Assisted-by: Claude:claude-fable-5 +Cc: +Signed-off-by: Andrew Morton +(cherry picked from commit 07b4377bdbe74a3ec0c8da5849d014f70e003384) +[ kas: fix only; selftest hunk dropped (pagemap_ioctl.c differs on this tree). Add the pte_none early-return before the pte_present branch ] + +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + fs/proc/task_mmu.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c +index 0cec16f9e9afd..48455e2a66a1e 100644 +--- a/fs/proc/task_mmu.c ++++ b/fs/proc/task_mmu.c +@@ -2326,6 +2326,19 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, + { + unsigned long categories = 0; + ++ if (pte_none(pte)) { ++ /* ++ * An unpopulated pte carries no uffd-wp marker, i.e. it is not ++ * write-protected, the same condition under which the present ++ * and swap cases below report PAGE_IS_WRITTEN. Report it here ++ * too so this generic path agrees with the PAGE_IS_WRITTEN fast ++ * path in pagemap_scan_pmd_entry(), which reports pte_none as ++ * written and, under PM_SCAN_WP_MATCHING, arms a marker. The ++ * fast path applies no VMA test, so neither does this. ++ */ ++ return PAGE_IS_WRITTEN; ++ } ++ + if (pte_present(pte)) { + struct page *page; + +-- +2.53.0 + diff --git a/queue-6.18/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch b/queue-6.18/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch new file mode 100644 index 0000000000..85f85294c2 --- /dev/null +++ b/queue-6.18/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch @@ -0,0 +1,103 @@ +From 76cf830b745a7ee00b18b549fc5bb4b75f3692e3 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:45:25 +0100 +Subject: mm/huge_memory: unlock i_mmap_rwsem before releasing after-split + folios + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit e923bd21058ea02fd0dcd3549d151d143fd036e5 ] + +__folio_split() keeps dereferencing the mapping after the split: +shmem_uncharge(mapping->host) and remap_page() while the folios are still +frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the +after-split folios have been unlocked and freed. + +Nothing holds an inode reference across that. The split relies on @folio +-- which the beyond-EOF drop loop never removes, as it starts at +folio_next(folio) -- staying locked and in the page cache to hold off +eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read() +runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure() +passes when splitting a poisoned tail of a shmem THP that reaches past +i_size during truncation, it too is gone from the page cache; so once +@folio is unlocked no locked, in-cache folio pins the inode, and a +concurrent final iput() can evict and RCU-free it before +i_mmap_unlock_read() touches i_mmap_rwsem: + + BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790 + i_mmap_unlock_read include/linux/fs.h:537 [inline] + __folio_split+0x732/0x1640 mm/huge_memory.c:4100 + try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675 + memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470 + + Freed by task 4601: + shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177 + evict+0x57f/0xac0 fs/inode.c:870 + +Do every mapping dereference while @folio still pins the inode: drop +i_mmap_rwsem right after remap_page(), before the loop that unlocks and +frees the after-split folios, and clear @mapping so the exit path does not +unlock it again. shmem_uncharge() and remap_page() already run before +that point, so after this nothing past the unlock loop touches the inode +or the mapping. + +This is now a rule the split depends on, alongside keeping @folio frozen +until the page cache is updated: no inode or mapping dereference once the +after-split folios start being unlocked. + +Link: https://lore.kernel.org/20260716095424.471052-1-kirill@shutemov.name +Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()") +Signed-off-by: Kiryl Shutsemau (Meta) +Reported-by: Hao Zhang +Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc +Co-developed-by: Hao Zhang +Signed-off-by: Hao Zhang +Acked-by: David Hildenbrand (Arm) +Reviewed-by: Zi Yan +Reviewed-by: Baolin Wang +Reviewed-by: Miaohe Lin +Cc: Baolin Wang +Cc: Barry Song +Cc: Dev Jain +Cc: Lance Yang +Cc: Liam R. Howlett +Cc: Lorenzo Stoakes +Cc: Naoya Horiguchi +Cc: Nico Pache +Cc: Ryan Roberts +Cc: +Signed-off-by: Andrew Morton +(cherry picked from commit e923bd21058ea02fd0dcd3549d151d143fd036e5) + +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/huge_memory.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +index c08aa3db96431..bd3093a6cc628 100644 +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -3871,6 +3871,18 @@ static int __folio_split(struct folio *folio, unsigned int new_order, + remap_flags = RMP_USE_SHARED_ZEROPAGE; + remap_page(folio, 1 << order, remap_flags); + ++ /* ++ * Drop the mapping while the inode is still pinned. @folio stays ++ * locked and present in the page cache until the loop below, so ++ * eviction cannot free the inode yet; @lock_at is not enough, it may ++ * be a tail beyond EOF that the split already dropped from the page ++ * cache. Nothing past this point may touch the inode or the mapping. ++ */ ++ if (mapping) { ++ i_mmap_unlock_read(mapping); ++ mapping = NULL; ++ } ++ + /* + * Unlock all after-split folios except the one containing + * @lock_at page. If @folio is not split, it will be kept locked. +-- +2.53.0 + diff --git a/queue-6.18/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch b/queue-6.18/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch new file mode 100644 index 0000000000..d7bd4c51f3 --- /dev/null +++ b/queue-6.18/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch @@ -0,0 +1,96 @@ +From a66eaa861ea42be82959100491e3c75cb3ba849c Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:43:46 +0100 +Subject: mm/hugetlb: fix swap entry corruption when clearing uffd-wp at fork() + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit 83abe2fd5b3aeb3123b5408a5a91709c5538fb23 ] + +copy_hugetlb_page_range() clears the uffd-wp bit of migration and hwpoison +entries with huge_pte_clear_uffd_wp(), which operates on the present-PTE +bit position. Swap entries keep the uffd-wp state elsewhere -- the +migration branch reads and sets it with pte_swp_uffd_wp() and +pte_swp_mkuffd_wp() -- and the present-PTE position falls into the swap +payload. On x86-64 it lands in the inverted swap offset, where a +naturally-aligned hugetlb PFN always has the affected bit set, so the +clear advances the encoded PFN by two pages. + +No userfaultfd needs to be involved: the clear is guarded only by the +child VMA not being uffd-wp registered, so a plain fork() with an +in-flight hugetlb migration entry (or a poisoned hugetlb page) corrupts +the entry copied into the child. Instrumenting the clear and forking +after MADV_HWPOISON on a 2MB anon hugetlb page shows: + + offset before=120e00 + offset after =120e02 + +The fallout is mostly latent: rmap walks match migration entries by folio +range and remove_migration_pte() rebuilds the PTE from the folio, so a +within-folio PFN skew heals once migration completes. But any path that +re-encodes the corrupted offset -- e.g. hugetlb_change_protection() +rewriting a writable migration entry via +make_readable_migration_entry(swp_offset(entry)) -- propagates it. + +Migration entries legitimately carry uffd-wp, so clear it with +pte_swp_clear_uffd_wp(), matching copy_nonpresent_pte() and +move_huge_pte(). + +A hwpoison entry, on the other hand, never carries the uffd-wp bit: it is +installed fresh by make_hwpoison_entry() (try_to_unmap_one() does not +preserve uffd-wp on the hwpoison path) and hugetlb_change_protection() +leaves hwpoison entries untouched. There was nothing to clear there, only +the corruption, so drop the clear entirely. + +Link: https://lore.kernel.org/20260708090110.136162-1-kirill@shutemov.name +Fixes: bc70fbf269fd ("mm/hugetlb: handle uffd-wp during fork()") +Signed-off-by: Kiryl Shutsemau +Reported-by: Sashiko AI review +Closes: https://lore.kernel.org/all/20260703140011.99E601F000E9@smtp.kernel.org/ +Suggested-by: David Hildenbrand +Acked-by: David Hildenbrand (Arm) +Assisted-by: Claude:claude-fable-5 +Cc: Muchun Song +Cc: Oscar Salvador +Cc: Peter Xu +Cc: +Signed-off-by: Andrew Morton +(cherry picked from commit 83abe2fd5b3aeb3123b5408a5a91709c5538fb23) +[ kas: adapt to the pre-softleaf idiom ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/hugetlb.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/mm/hugetlb.c b/mm/hugetlb.c +index 0b3fd6943836e..5515870b5b155 100644 +--- a/mm/hugetlb.c ++++ b/mm/hugetlb.c +@@ -5634,8 +5634,12 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, + */ + ; + } else if (unlikely(is_hugetlb_entry_hwpoisoned(entry))) { +- if (!userfaultfd_wp(dst_vma)) +- entry = huge_pte_clear_uffd_wp(entry); ++ /* ++ * A hwpoison entry never carries the uffd-wp bit: it is ++ * installed fresh by make_hwpoison_entry() and ++ * hugetlb_change_protection() leaves it untouched, so ++ * there is nothing to clear for the child. ++ */ + set_huge_pte_at(dst, addr, dst_pte, entry, sz); + } else if (unlikely(is_hugetlb_entry_migration(entry))) { + swp_entry_t swp_entry = pte_to_swp_entry(entry); +@@ -5654,7 +5658,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, + set_huge_pte_at(src, addr, src_pte, entry, sz); + } + if (!userfaultfd_wp(dst_vma)) +- entry = huge_pte_clear_uffd_wp(entry); ++ entry = pte_swp_clear_uffd_wp(entry); + set_huge_pte_at(dst, addr, dst_pte, entry, sz); + } else if (unlikely(is_pte_marker(entry))) { + pte_marker marker = copy_pte_marker( +-- +2.53.0 + diff --git a/queue-6.18/net-handshake-close-the-submit-side-sock_hold-race.patch b/queue-6.18/net-handshake-close-the-submit-side-sock_hold-race.patch new file mode 100644 index 0000000000..3e3870f685 --- /dev/null +++ b/queue-6.18/net-handshake-close-the-submit-side-sock_hold-race.patch @@ -0,0 +1,83 @@ +From 0fa09a2c127b52c59e46b760a0871b8b23296ec5 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 01:40:04 +0800 +Subject: net/handshake: Close the submit-side sock_hold race + +From: Chuck Lever + +[ Upstream commit 5da98f55b13173c08f003011b76531b25c821c07 ] + +handshake_req_submit() publishes the request via +handshake_req_hash_add() and __add_pending_locked(), drops +hn_lock, and calls handshake_genl_notify() (which can sleep) +before taking sock_hold() on req->hr_sk. A fast tlshd ACCEPT +followed by DONE can drive handshake_complete()'s sock_put() +into the window between the spin_unlock and the late +sock_hold(); on a system where the consumer's fd held the +only sk reference, the late sock_hold() then operates on an +sk whose refcount has reached zero. + +The preceding two patches install an explicit file reference +on struct handshake_req. That file pins sock->file, which +pins the embedded struct socket, which defers inet_release()'s +sock_put(). As long as hr_file is held, sk cannot reach refcount +zero from the consumer side, and the submit-side sock_hold() +with its matching sock_put() calls in handshake_complete() and +handshake_req_cancel() is now redundant. + +Drop all three. The file reference already keeps each request's +socket alive, and the lifetime story is contained in a single +get_file()/fput() pair. + +Fixes: 3b3009ea8abb ("net/handshake: Create a NETLINK service for handling handshake requests") +Signed-off-by: Chuck Lever +Reviewed-by: Hannes Reinecke +Link: https://patch.msgid.link/20260525-handshake-file-pin-v3-6-66c616906ead@oracle.com +Signed-off-by: Paolo Abeni +(cherry picked from commit 5da98f55b13173c08f003011b76531b25c821c07) +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + net/handshake/request.c | 12 ------------ + 1 file changed, 12 deletions(-) + +diff --git a/net/handshake/request.c b/net/handshake/request.c +index 4bf98793491aa..035bb2d7471c9 100644 +--- a/net/handshake/request.c ++++ b/net/handshake/request.c +@@ -301,13 +301,6 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req, + goto out_err; + } + +- /* +- * Pin struct sock so sk_destruct does not run until the +- * handshake completion path releases it; struct socket is +- * held separately via hr_file above. +- */ +- sock_hold(req->hr_sk); +- + trace_handshake_submit(net, req, req->hr_sk); + return 0; + +@@ -337,9 +330,6 @@ void handshake_complete(struct handshake_req *req, int status, + trace_handshake_complete(net, req, sk, status); + req->hr_proto->hp_done(req, status, info); + +- /* Handshake request is no longer pending */ +- sock_put(sk); +- + fput(file); + } + } +@@ -387,8 +377,6 @@ bool handshake_req_cancel(struct sock *sk) + out_true: + trace_handshake_cancel(net, req, sk); + +- /* Handshake request is no longer pending */ +- sock_put(sk); + fput(req->hr_file); + return true; + } +-- +2.53.0 + diff --git a/queue-6.18/net-handshake-convert-handshake_nl_accept_doit-to-fd.patch b/queue-6.18/net-handshake-convert-handshake_nl_accept_doit-to-fd.patch new file mode 100644 index 0000000000..a4c2794d5f --- /dev/null +++ b/queue-6.18/net-handshake-convert-handshake_nl_accept_doit-to-fd.patch @@ -0,0 +1,79 @@ +From 9cb400cb6a52c1935e2fcf5f86d2114fdc5678fa Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 01:39:56 +0800 +Subject: net/handshake: convert handshake_nl_accept_doit() to FD_PREPARE() + +From: Christian Brauner + +[ Upstream commit fe67b063f687d8f32c466778aad57508bc1c618a ] + +Link: https://patch.msgid.link/20251123-work-fd-prepare-v4-27-b6efa1706cfd@kernel.org +Signed-off-by: Christian Brauner +(cherry picked from commit fe67b063f687d8f32c466778aad57508bc1c618a) +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + net/handshake/netlink.c | 38 ++++++++++++++++++-------------------- + 1 file changed, 18 insertions(+), 20 deletions(-) + +diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c +index d8211e0ba75c6..7b1d42607ae68 100644 +--- a/net/handshake/netlink.c ++++ b/net/handshake/netlink.c +@@ -93,7 +93,7 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info) + struct handshake_net *hn = handshake_pernet(net); + struct handshake_req *req = NULL; + struct socket *sock; +- int class, fd, err; ++ int class, err; + + err = -EOPNOTSUPP; + if (!hn) +@@ -106,27 +106,25 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info) + + err = -EAGAIN; + req = handshake_req_next(hn, class); +- if (!req) +- goto out_status; +- +- sock = req->hr_sk->sk_socket; +- fd = get_unused_fd_flags(O_CLOEXEC); +- if (fd < 0) { +- err = fd; +- goto out_complete; +- } +- +- err = req->hr_proto->hp_accept(req, info, fd); +- if (err) { +- put_unused_fd(fd); +- goto out_complete; ++ if (req) { ++ sock = req->hr_sk->sk_socket; ++ ++ FD_PREPARE(fdf, O_CLOEXEC, sock->file); ++ if (fdf.err) { ++ err = fdf.err; ++ goto out_complete; ++ } ++ ++ get_file(sock->file); /* FD_PREPARE() consumes a reference. */ ++ err = req->hr_proto->hp_accept(req, info, fd_prepare_fd(fdf)); ++ if (err) ++ goto out_complete; /* Automatic cleanup handles fput */ ++ ++ trace_handshake_cmd_accept(net, req, req->hr_sk, fd_prepare_fd(fdf)); ++ fd_publish(fdf); ++ return 0; + } + +- fd_install(fd, get_file(sock->file)); +- +- trace_handshake_cmd_accept(net, req, req->hr_sk, fd); +- return 0; +- + out_complete: + handshake_complete(req, -EIO, NULL); + out_status: +-- +2.53.0 + diff --git a/queue-6.18/net-handshake-drain-pending-requests-at-net-namespac.patch b/queue-6.18/net-handshake-drain-pending-requests-at-net-namespac.patch new file mode 100644 index 0000000000..c29d84d822 --- /dev/null +++ b/queue-6.18/net-handshake-drain-pending-requests-at-net-namespac.patch @@ -0,0 +1,114 @@ +From 1be2f0670e21e2e3612655a8aac4366305a98a1e Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 01:40:06 +0800 +Subject: net/handshake: Drain pending requests at net namespace exit + +From: Chuck Lever + +[ Upstream commit ea5fe6a73ca57e5150b8a38b341aef2636eb72f0 ] + +The arguments to list_splice_init() in handshake_net_exit() are +reversed. The call moves the local empty "requests" list onto +hn->hn_requests, leaving the local list empty, so the subsequent +drain loop runs zero iterations. Pending handshake requests that +had not yet been accepted are not torn down when the net namespace +is destroyed; each one keeps a reference on a socket file and on +the handshake_req allocation. + +Pass the source and destination in the documented order +(list_splice_init(list, head) moves list onto head) so the pending +list is transferred to the local scratch list and drained through +handshake_complete(). + +Fixing the splice direction exposes a list-corruption race. After +the splice each req->hr_list still has non-empty link pointers, +threading the stack-local scratch list rather than hn_requests. +A concurrent handshake_req_cancel() -- for example, from sunrpc's +TLS timeout on a kernel socket whose netns reference was not +taken -- finds the request through the rhashtable, calls +remove_pending(), and sees !list_empty(&req->hr_list). +__remove_pending_locked() then list_del_init()s an entry off the +scratch list while the drain iterates, corrupting it. The same +call arriving after the drain loop has run list_del() on an +entry hits LIST_POISON instead. + +Have remove_pending() check HANDSHAKE_F_NET_DRAINING under +hn_lock and report not-found when drain is in progress. The +drain has already taken ownership; handshake_complete()'s existing +test_and_set on HANDSHAKE_F_REQ_COMPLETED still arbitrates +between drain and cancel for who calls the consumer's hp_done. Use +list_del_init() rather than list_del() in the drain so req->hr_list +does not carry LIST_POISON after drain releases the entry. + +The DRAINING guard in remove_pending() makes cancel return false, +but cancel still falls through to test_and_set_bit on +HANDSHAKE_F_REQ_COMPLETED and drops the request's hr_file reference. +Without another pin, if that is the last reference, sk_destruct frees +the request while it is still linked on the drain loop's local list. +Pin each request's hr_file under hn_lock before releasing the list, +and drop that drain pin after the loop finishes with the request. + +Fixes: 3b3009ea8abb ("net/handshake: Create a NETLINK service for handling handshake requests") +Signed-off-by: Chuck Lever +Reviewed-by: Hannes Reinecke +Link: https://patch.msgid.link/20260525-handshake-file-pin-v3-8-66c616906ead@oracle.com +Signed-off-by: Paolo Abeni +(cherry picked from commit ea5fe6a73ca57e5150b8a38b341aef2636eb72f0) +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + net/handshake/netlink.c | 10 ++++++++-- + net/handshake/request.c | 5 ++++- + 2 files changed, 12 insertions(+), 3 deletions(-) + +diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c +index 21d6cbd52fcdb..3fd4fef9bab1a 100644 +--- a/net/handshake/netlink.c ++++ b/net/handshake/netlink.c +@@ -201,13 +201,19 @@ static void __net_exit handshake_net_exit(struct net *net) + */ + spin_lock_bh(&hn->hn_lock); + set_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags); +- list_splice_init(&requests, &hn->hn_requests); ++ list_splice_init(&hn->hn_requests, &requests); ++ list_for_each_entry(req, &requests, hr_list) ++ get_file(req->hr_file); + spin_unlock_bh(&hn->hn_lock); + + while (!list_empty(&requests)) { ++ struct file *file; ++ + req = list_first_entry(&requests, struct handshake_req, hr_list); +- list_del(&req->hr_list); ++ file = req->hr_file; ++ list_del_init(&req->hr_list); + handshake_complete(req, -ETIMEDOUT, NULL); ++ fput(file); + } + } + +diff --git a/net/handshake/request.c b/net/handshake/request.c +index 035bb2d7471c9..d4a3a50f8a3b5 100644 +--- a/net/handshake/request.c ++++ b/net/handshake/request.c +@@ -163,13 +163,16 @@ static void __remove_pending_locked(struct handshake_net *hn, + * otherwise %false. + * + * If @req was on a pending list, it has not yet been accepted. ++ * Returns %false when the net namespace is draining; the drain ++ * loop has taken ownership of the pending list. + */ + static bool remove_pending(struct handshake_net *hn, struct handshake_req *req) + { + bool ret = false; + + spin_lock_bh(&hn->hn_lock); +- if (!list_empty(&req->hr_list)) { ++ if (!test_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags) && ++ !list_empty(&req->hr_list)) { + __remove_pending_locked(hn, req); + ret = true; + } +-- +2.53.0 + diff --git a/queue-6.18/net-handshake-fix-null-ptr-deref-in-handshake_comple.patch b/queue-6.18/net-handshake-fix-null-ptr-deref-in-handshake_comple.patch new file mode 100644 index 0000000000..6827967d71 --- /dev/null +++ b/queue-6.18/net-handshake-fix-null-ptr-deref-in-handshake_comple.patch @@ -0,0 +1,72 @@ +From fb2046a110c035c0978a943ecc883d241feaae96 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 01:39:58 +0800 +Subject: net/handshake: Fix null-ptr-deref in handshake_complete() + +From: Wang Liang + +[ Upstream commit dabac51b8102e1643d8e297a8e6948dab190aa4c ] + +A null pointer dereference in handshake_complete() was observed [1]. + +When handshake_req_next() return NULL in handshake_nl_accept_doit(), +function handshake_complete() will be called unexpectedly which triggers +this crash. Fix it by goto out_status when req is NULL. + +[1] +Oops: general protection fault, probably for non-canonical address 0xdffffc0000000005: 0000 [#1] SMP KASAN PTI +RIP: 0010:handshake_complete+0x36/0x2b0 net/handshake/request.c:288 +Call Trace: + + handshake_nl_accept_doit+0x32d/0x7e0 net/handshake/netlink.c:129 + genl_family_rcv_msg_doit+0x204/0x300 net/netlink/genetlink.c:1115 + genl_family_rcv_msg+0x436/0x670 net/netlink/genetlink.c:1195 + genl_rcv_msg+0xcc/0x170 net/netlink/genetlink.c:1210 + netlink_rcv_skb+0x14c/0x430 net/netlink/af_netlink.c:2550 + genl_rcv+0x2d/0x40 net/netlink/genetlink.c:1219 + netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline] + netlink_unicast+0x878/0xb20 net/netlink/af_netlink.c:1344 + netlink_sendmsg+0x897/0xd70 net/netlink/af_netlink.c:1894 + sock_sendmsg_nosec net/socket.c:727 [inline] + __sock_sendmsg net/socket.c:742 [inline] + ____sys_sendmsg+0xa39/0xbf0 net/socket.c:2592 + ___sys_sendmsg+0x121/0x1c0 net/socket.c:2646 + __sys_sendmsg+0x155/0x200 net/socket.c:2678 + do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] + do_syscall_64+0x5f/0x350 arch/x86/entry/syscall_64.c:94 + entry_SYSCALL_64_after_hwframe+0x76/0x7e + + +Fixes: fe67b063f687 ("net/handshake: convert handshake_nl_accept_doit() to FD_PREPARE()") +Reviewed-by: Chuck Lever +Reported-by: Dan Carpenter +Closes: https://lore.kernel.org/kernel-tls-handshake/aScekpuOYHRM9uOd@morisot.1015granger.net/T/#m7cfa5c11efc626d77622b2981591197a2acdd65e +Signed-off-by: Wang Liang +Reviewed-by: Simon Horman +Link: https://patch.msgid.link/20251212012723.4111831-1-wangliang74@huawei.com +Signed-off-by: Paolo Abeni + +(cherry picked from commit dabac51b8102e1643d8e297a8e6948dab190aa4c) +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + net/handshake/netlink.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c +index 7b1d42607ae68..039344979de93 100644 +--- a/net/handshake/netlink.c ++++ b/net/handshake/netlink.c +@@ -126,7 +126,8 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info) + } + + out_complete: +- handshake_complete(req, -EIO, NULL); ++ if (req) ++ handshake_complete(req, -EIO, NULL); + out_status: + trace_handshake_cmd_accept_err(net, req, NULL, err); + return err; +-- +2.53.0 + diff --git a/queue-6.18/net-handshake-hand-off-the-pinned-file-reference-to-.patch b/queue-6.18/net-handshake-hand-off-the-pinned-file-reference-to-.patch new file mode 100644 index 0000000000..60d0b4d649 --- /dev/null +++ b/queue-6.18/net-handshake-hand-off-the-pinned-file-reference-to-.patch @@ -0,0 +1,153 @@ +From 6f64110126105e5e5f8ed16c0cdc45c12e735498 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 01:40:02 +0800 +Subject: net/handshake: hand off the pinned file reference to accept_doit + +From: Chuck Lever + +[ Upstream commit f4251190e58b209999c1ba9e6d2976136a1be055 ] + +handshake_req_next() removes the request from the per-net +pending list and drops hn_lock before handshake_nl_accept_doit() +reads req->hr_sk->sk_socket and dereferences sock->file (once in +FD_PREPARE() and again in get_file()). In that window a +consumer running tls_handshake_cancel() followed by sockfd_put() +(svc_sock_free) or __fput_sync() (xs_reset_transport) releases +sock->file. sock_release() then runs sock_orphan(), zeroing +sk_socket, and frees the struct socket. The accept-side code +either reads NULL through sk_socket or chases freed memory. + +The submit-side sock_hold() does not prevent this. sk_refcnt +protects struct sock, but struct socket and sock->file are +independently refcounted via the file descriptor the consumer +owns. Pinning sk leaves sock and sock->file unprotected. + +Retarget the accept-side dereferences at req->hr_file, which was +pinned at submit time, instead of req->hr_sk->sk_socket->file. +Pinning on its own is not sufficient: a consumer that cancels +between handshake_req_next() returning and accept_doit reaching +FD_PREPARE() takes the !remove_pending() branch in +handshake_req_cancel() and drops hr_file before the accept side +takes its own reference. Hand off an additional file reference +inside handshake_req_next(), under hn_lock, so the accept side +operates on a reference that no concurrent handshake_req_cancel() +can revoke. FD_PREPARE() consumes that handed-off reference, +either by transferring it to the new fd in fd_publish() or by +dropping it in the cleanup destructor on error; the explicit +get_file() that previously balanced FD_PREPARE() is therefore +redundant and goes away. + +Update handshake_req_cancel_test2 and _test3 to simulate the +FD_PREPARE() consumption with an fput() so the kunit file-count +assertions stay balanced. + +Reported-by: Chris Mason +Fixes: 3b3009ea8abb ("net/handshake: Create a NETLINK service for handling handshake requests") +Signed-off-by: Chuck Lever +Reviewed-by: Hannes Reinecke +Link: https://patch.msgid.link/20260525-handshake-file-pin-v3-5-66c616906ead@oracle.com +Signed-off-by: Paolo Abeni +(cherry picked from commit f4251190e58b209999c1ba9e6d2976136a1be055) +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + net/handshake/handshake-test.c | 8 ++++++++ + net/handshake/netlink.c | 7 ++----- + net/handshake/request.c | 18 ++++++++++++++++++ + 3 files changed, 28 insertions(+), 5 deletions(-) + +diff --git a/net/handshake/handshake-test.c b/net/handshake/handshake-test.c +index df3948e807a0f..9cc7a95f41207 100644 +--- a/net/handshake/handshake-test.c ++++ b/net/handshake/handshake-test.c +@@ -375,6 +375,10 @@ static void handshake_req_cancel_test2(struct kunit *test) + /* Pretend to accept this request */ + next = handshake_req_next(hn, HANDSHAKE_HANDLER_CLASS_TLSHD); + KUNIT_ASSERT_PTR_EQ(test, req, next); ++ /* Simulate FD_PREPARE() consuming the file reference handed ++ * off by handshake_req_next(); see handshake_nl_accept_doit(). ++ */ ++ fput(filp); + + /* Act */ + result = handshake_req_cancel(sock->sk); +@@ -417,6 +421,10 @@ static void handshake_req_cancel_test3(struct kunit *test) + /* Pretend to accept this request */ + next = handshake_req_next(hn, HANDSHAKE_HANDLER_CLASS_TLSHD); + KUNIT_ASSERT_PTR_EQ(test, req, next); ++ /* Simulate FD_PREPARE() consuming the file reference handed ++ * off by handshake_req_next(); see handshake_nl_accept_doit(). ++ */ ++ fput(filp); + + /* Pretend to complete this request */ + handshake_complete(next, -ETIMEDOUT, NULL); +diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c +index 1a5821eb71847..21d6cbd52fcdb 100644 +--- a/net/handshake/netlink.c ++++ b/net/handshake/netlink.c +@@ -92,7 +92,6 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info) + struct net *net = sock_net(skb->sk); + struct handshake_net *hn = handshake_pernet(net); + struct handshake_req *req = NULL; +- struct socket *sock; + int class, err; + + err = -EOPNOTSUPP; +@@ -107,15 +106,13 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info) + err = -EAGAIN; + req = handshake_req_next(hn, class); + if (req) { +- sock = req->hr_sk->sk_socket; +- +- FD_PREPARE(fdf, O_CLOEXEC, sock->file); ++ FD_PREPARE(fdf, O_CLOEXEC, req->hr_file); + if (fdf.err) { ++ fput(req->hr_file); /* drop ref from handshake_req_next() */ + err = fdf.err; + goto out_complete; + } + +- get_file(sock->file); /* FD_PREPARE() consumes a reference. */ + err = req->hr_proto->hp_accept(req, info, fd_prepare_fd(fdf)); + if (err) + goto out_complete; /* Automatic cleanup handles fput */ +diff --git a/net/handshake/request.c b/net/handshake/request.c +index 35bc6290e1203..4bf98793491aa 100644 +--- a/net/handshake/request.c ++++ b/net/handshake/request.c +@@ -178,6 +178,17 @@ static bool remove_pending(struct handshake_net *hn, struct handshake_req *req) + return ret; + } + ++/** ++ * handshake_req_next - Return the next queued handshake request ++ * @hn: per-net handshake state ++ * @class: handler class to match ++ * ++ * On a non-NULL return, the caller owns an extra reference ++ * on @req->hr_file. FD_PREPARE() consumes it on success; on ++ * the FD_PREPARE() failure path the caller must fput() it. ++ * ++ * Return: pointer to a removed handshake_req, or NULL. ++ */ + struct handshake_req *handshake_req_next(struct handshake_net *hn, int class) + { + struct handshake_req *req, *pos; +@@ -188,6 +199,13 @@ struct handshake_req *handshake_req_next(struct handshake_net *hn, int class) + if (pos->hr_proto->hp_handler_class != class) + continue; + __remove_pending_locked(hn, pos); ++ /* Hand off a file reference to the accept side under ++ * hn_lock. A concurrent handshake_req_cancel() can drop ++ * hr_file before accept reaches FD_PREPARE(); this extra ++ * reference keeps the file alive until FD_PREPARE() takes ++ * ownership. ++ */ ++ get_file(pos->hr_file); + req = pos; + break; + } +-- +2.53.0 + diff --git a/queue-6.18/net-handshake-take-a-long-lived-file-reference-at-su.patch b/queue-6.18/net-handshake-take-a-long-lived-file-reference-at-su.patch new file mode 100644 index 0000000000..7abeee0e75 --- /dev/null +++ b/queue-6.18/net-handshake-take-a-long-lived-file-reference-at-su.patch @@ -0,0 +1,188 @@ +From 0a8bac33dc7ecc2c0f44829dec6e9d231fc8b61b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 01:40:00 +0800 +Subject: net/handshake: Take a long-lived file reference at submit + +From: Chuck Lever + +[ Upstream commit 09dba37eee70d0596e26645015f1aa95a9848e9d ] + +handshake_nl_accept_doit() needs the file pointer backing +req->hr_sk->sk_socket to survive the window between +handshake_req_next() and the subsequent FD_PREPARE() and get_file(). +The submit-side sock_hold() does not provide that. sk_refcnt keeps +struct sock alive, but struct socket is owned by sock->file: when +the consumer fputs the last file reference, sock_release() tears +the socket down regardless of any sock_hold. + +Add an hr_file pointer to struct handshake_req and acquire an +explicit reference on sock->file during handshake_req_submit(). +handshake_complete() and handshake_req_cancel() release the +reference on the completion-bit-winning path. + +The submit error path must also release the file reference, but +after rhashtable insertion a concurrent handshake_req_cancel() can +discover the request and race the error path. Gate the error-path +cleanup -- sk_destruct restoration, fput, and request destruction +-- with test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED), the same +serialization handshake_complete() and handshake_req_cancel() +already use. When cancel has already claimed ownership, the submit +error path returns without touching the request; socket teardown +handles final destruction. + +The accept-side dereferences are not yet retargeted; that change +comes in the next patch. + +Signed-off-by: Chuck Lever +Link: https://patch.msgid.link/20260525-handshake-file-pin-v3-4-66c616906ead@oracle.com +Signed-off-by: Paolo Abeni +(cherry picked from commit 09dba37eee70d0596e26645015f1aa95a9848e9d) +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + net/handshake/handshake.h | 2 ++ + net/handshake/netlink.c | 6 ------ + net/handshake/request.c | 42 ++++++++++++++++++++++++++++++++------- + 3 files changed, 37 insertions(+), 13 deletions(-) + +diff --git a/net/handshake/handshake.h b/net/handshake/handshake.h +index 2289b0e274f40..da61cadd1ad3e 100644 +--- a/net/handshake/handshake.h ++++ b/net/handshake/handshake.h +@@ -24,6 +24,7 @@ enum hn_flags_bits { + HANDSHAKE_F_NET_DRAINING, + }; + ++struct file; + struct handshake_proto; + + /* One handshake request */ +@@ -32,6 +33,7 @@ struct handshake_req { + struct rhash_head hr_rhash; + unsigned long hr_flags; + const struct handshake_proto *hr_proto; ++ struct file *hr_file; + struct sock *hr_sk; + void (*hr_odestruct)(struct sock *sk); + +diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c +index 039344979de93..1a5821eb71847 100644 +--- a/net/handshake/netlink.c ++++ b/net/handshake/netlink.c +@@ -210,12 +210,6 @@ static void __net_exit handshake_net_exit(struct net *net) + while (!list_empty(&requests)) { + req = list_first_entry(&requests, struct handshake_req, hr_list); + list_del(&req->hr_list); +- +- /* +- * Requests on this list have not yet been +- * accepted, so they do not have an fd to put. +- */ +- + handshake_complete(req, -ETIMEDOUT, NULL); + } + } +diff --git a/net/handshake/request.c b/net/handshake/request.c +index 62efb7e32730e..35bc6290e1203 100644 +--- a/net/handshake/request.c ++++ b/net/handshake/request.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + + #include +@@ -215,9 +216,16 @@ EXPORT_SYMBOL_IF_KUNIT(handshake_req_next); + * A zero return value from handshake_req_submit() means that + * exactly one subsequent completion callback is guaranteed. + * +- * A negative return value from handshake_req_submit() means that +- * no completion callback will be done and that @req has been +- * destroyed. ++ * A negative return value from handshake_req_submit() guarantees that ++ * no completion callback will occur and that @req is no longer owned by ++ * the caller. If cancellation wins the completion race after the request ++ * has been published, final destruction is deferred until socket teardown. ++ * ++ * The caller must hold a reference on @sock->file for the duration ++ * of this call. Once the request is published to the accept side, a ++ * concurrent completion or cancellation may release the request's pin on ++ * @sock->file; the caller's reference is what keeps @sock->sk valid until ++ * handshake_req_submit() returns. + */ + int handshake_req_submit(struct socket *sock, struct handshake_req *req, + gfp_t flags) +@@ -236,6 +244,14 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req, + kfree(req); + return -EINVAL; + } ++ ++ /* ++ * Pin sock->file for the lifetime of the request so the ++ * accept side does not race a consumer that releases the ++ * socket while a handshake is pending. ++ */ ++ req->hr_file = get_file(sock->file); ++ + req->hr_odestruct = req->hr_sk->sk_destruct; + req->hr_sk->sk_destruct = handshake_sk_destruct; + +@@ -267,7 +283,11 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req, + goto out_err; + } + +- /* Prevent socket release while a handshake request is pending */ ++ /* ++ * Pin struct sock so sk_destruct does not run until the ++ * handshake completion path releases it; struct socket is ++ * held separately via hr_file above. ++ */ + sock_hold(req->hr_sk); + + trace_handshake_submit(net, req, req->hr_sk); +@@ -276,10 +296,13 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req, + out_unlock: + spin_unlock_bh(&hn->hn_lock); + out_err: +- /* Restore original destructor so socket teardown still runs on failure */ +- req->hr_sk->sk_destruct = req->hr_odestruct; + trace_handshake_submit_err(net, req, req->hr_sk, ret); +- handshake_req_destroy(req); ++ if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { ++ /* Restore original destructor so socket teardown still runs. */ ++ req->hr_sk->sk_destruct = req->hr_odestruct; ++ fput(req->hr_file); ++ handshake_req_destroy(req); ++ } + return ret; + } + EXPORT_SYMBOL(handshake_req_submit); +@@ -291,11 +314,15 @@ void handshake_complete(struct handshake_req *req, int status, + struct net *net = sock_net(sk); + + if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { ++ struct file *file = req->hr_file; ++ + trace_handshake_complete(net, req, sk, status); + req->hr_proto->hp_done(req, status, info); + + /* Handshake request is no longer pending */ + sock_put(sk); ++ ++ fput(file); + } + } + EXPORT_SYMBOL_IF_KUNIT(handshake_complete); +@@ -344,6 +371,7 @@ bool handshake_req_cancel(struct sock *sk) + + /* Handshake request is no longer pending */ + sock_put(sk); ++ fput(req->hr_file); + return true; + } + EXPORT_SYMBOL(handshake_req_cancel); +-- +2.53.0 + diff --git a/queue-6.18/series b/queue-6.18/series index c07a0f977b..57cb4f2302 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -351,3 +351,14 @@ drm-xe-rtp-toggle-deny-bit-to-de-whitelist-oa-regs.patch drm-xe-rtp-de-whitelist-oa-registers-for-all-hwe-s-f.patch drm-xe-oa-de-whitelist-oa-registers-on-oa-stream-ope.patch drm-xe-rtp-ensure-locking-ref-counting-for-oa-whitel.patch +mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch +fs-proc-task_mmu-fix-pagemap_scan-written-state-for-.patch +mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch +file-add-fd_-add-prepare.patch +file-ensure-cleanup.patch +net-handshake-convert-handshake_nl_accept_doit-to-fd.patch +net-handshake-fix-null-ptr-deref-in-handshake_comple.patch +net-handshake-take-a-long-lived-file-reference-at-su.patch +net-handshake-hand-off-the-pinned-file-reference-to-.patch +net-handshake-close-the-submit-side-sock_hold-race.patch +net-handshake-drain-pending-requests-at-net-namespac.patch diff --git a/queue-6.6/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch b/queue-6.6/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch new file mode 100644 index 0000000000..8a7e776e19 --- /dev/null +++ b/queue-6.6/mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch @@ -0,0 +1,125 @@ +From f072d6a25d426a9f4efc380fea6b5167d612e499 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:45:19 +0100 +Subject: mm/huge_memory: unlock i_mmap_rwsem before releasing after-split + folios + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit e923bd21058ea02fd0dcd3549d151d143fd036e5 ] + +__folio_split() keeps dereferencing the mapping after the split: +shmem_uncharge(mapping->host) and remap_page() while the folios are still +frozen/locked, and i_mmap_unlock_read(mapping) at the very end, after the +after-split folios have been unlocked and freed. + +Nothing holds an inode reference across that. The split relies on @folio +-- which the beyond-EOF drop loop never removes, as it starts at +folio_next(folio) -- staying locked and in the page cache to hold off +eviction. But the unlock loop unlocks @folio before i_mmap_unlock_read() +runs. If the caller's @lock_at is a tail beyond EOF, as memory_failure() +passes when splitting a poisoned tail of a shmem THP that reaches past +i_size during truncation, it too is gone from the page cache; so once +@folio is unlocked no locked, in-cache folio pins the inode, and a +concurrent final iput() can evict and RCU-free it before +i_mmap_unlock_read() touches i_mmap_rwsem: + + BUG: KASAN: slab-use-after-free in __up_read+0x634/0x790 + i_mmap_unlock_read include/linux/fs.h:537 [inline] + __folio_split+0x732/0x1640 mm/huge_memory.c:4100 + try_to_split_thp_page+0xab/0x390 mm/memory-failure.c:1675 + memory_failure+0x1394/0x26e0 mm/memory-failure.c:2470 + + Freed by task 4601: + shmem_free_in_core_inode+0x54/0xb0 mm/shmem.c:5177 + evict+0x57f/0xac0 fs/inode.c:870 + +Do every mapping dereference while @folio still pins the inode: drop +i_mmap_rwsem right after remap_page(), before the loop that unlocks and +frees the after-split folios, and clear @mapping so the exit path does not +unlock it again. shmem_uncharge() and remap_page() already run before +that point, so after this nothing past the unlock loop touches the inode +or the mapping. + +This is now a rule the split depends on, alongside keeping @folio frozen +until the page cache is updated: no inode or mapping dereference once the +after-split folios start being unlocked. + +Link: https://lore.kernel.org/20260716095424.471052-1-kirill@shutemov.name +Fixes: baa355fd3314 ("thp: file pages support for split_huge_page()") +Signed-off-by: Kiryl Shutsemau (Meta) +Reported-by: Hao Zhang +Closes: https://lore.kernel.org/linux-mm/20260710071344.GA106129@zh-pc +Co-developed-by: Hao Zhang +Signed-off-by: Hao Zhang +Acked-by: David Hildenbrand (Arm) +Reviewed-by: Zi Yan +Reviewed-by: Baolin Wang +Reviewed-by: Miaohe Lin +Cc: Baolin Wang +Cc: Barry Song +Cc: Dev Jain +Cc: Lance Yang +Cc: Liam R. Howlett +Cc: Lorenzo Stoakes +Cc: Naoya Horiguchi +Cc: Nico Pache +Cc: Ryan Roberts +Cc: +Signed-off-by: Andrew Morton + +(cherry picked from commit e923bd21058ea02fd0dcd3549d151d143fd036e5) +[ kas: adapt to the __split_huge_page()/split_huge_page_to_list() + two-function split: pass @mapping into __split_huge_page() and drop it + there, before the loop that frees the after-split subpages while the + head is still locked; the caller then skips its own i_mmap unlock ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/huge_memory.c | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/mm/huge_memory.c b/mm/huge_memory.c +index 4443cc44cbf9f..ff95a802d1582 100644 +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -2489,7 +2489,7 @@ static void __split_huge_page_tail(struct folio *folio, int tail, + } + + static void __split_huge_page(struct page *page, struct list_head *list, +- pgoff_t end) ++ pgoff_t end, struct address_space *mapping) + { + struct folio *folio = page_folio(page); + struct page *head = &folio->page; +@@ -2564,6 +2564,16 @@ static void __split_huge_page(struct page *page, struct list_head *list, + if (folio_test_swapcache(folio)) + split_swap_cluster(folio->swap); + ++ /* ++ * Drop the mapping while the head page is still locked and thus pins ++ * the inode. The loop below may free the after-split subpages -- ++ * including the head, when @page is a tail beyond EOF that the split ++ * dropped from the page cache -- which could otherwise let the inode, ++ * and @mapping, be freed before this unlock. ++ */ ++ if (mapping) ++ i_mmap_unlock_read(mapping); ++ + for (i = 0; i < nr; i++) { + struct page *subpage = head + i; + if (subpage == page) +@@ -2745,7 +2755,9 @@ int split_huge_page_to_list(struct page *page, struct list_head *list) + } + } + +- __split_huge_page(page, list, end); ++ __split_huge_page(page, list, end, mapping); ++ /* __split_huge_page() dropped the i_mmap lock */ ++ mapping = NULL; + ret = 0; + } else { + spin_unlock(&ds_queue->split_queue_lock); +-- +2.53.0 + diff --git a/queue-6.6/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch b/queue-6.6/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch new file mode 100644 index 0000000000..986db512cc --- /dev/null +++ b/queue-6.6/mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch @@ -0,0 +1,96 @@ +From 9a1f6eb0b724013b9b77d262bdcb811830ac20b0 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 14:43:38 +0100 +Subject: mm/hugetlb: fix swap entry corruption when clearing uffd-wp at fork() + +From: Kiryl Shutsemau (Meta) + +[ Upstream commit 83abe2fd5b3aeb3123b5408a5a91709c5538fb23 ] + +copy_hugetlb_page_range() clears the uffd-wp bit of migration and hwpoison +entries with huge_pte_clear_uffd_wp(), which operates on the present-PTE +bit position. Swap entries keep the uffd-wp state elsewhere -- the +migration branch reads and sets it with pte_swp_uffd_wp() and +pte_swp_mkuffd_wp() -- and the present-PTE position falls into the swap +payload. On x86-64 it lands in the inverted swap offset, where a +naturally-aligned hugetlb PFN always has the affected bit set, so the +clear advances the encoded PFN by two pages. + +No userfaultfd needs to be involved: the clear is guarded only by the +child VMA not being uffd-wp registered, so a plain fork() with an +in-flight hugetlb migration entry (or a poisoned hugetlb page) corrupts +the entry copied into the child. Instrumenting the clear and forking +after MADV_HWPOISON on a 2MB anon hugetlb page shows: + + offset before=120e00 + offset after =120e02 + +The fallout is mostly latent: rmap walks match migration entries by folio +range and remove_migration_pte() rebuilds the PTE from the folio, so a +within-folio PFN skew heals once migration completes. But any path that +re-encodes the corrupted offset -- e.g. hugetlb_change_protection() +rewriting a writable migration entry via +make_readable_migration_entry(swp_offset(entry)) -- propagates it. + +Migration entries legitimately carry uffd-wp, so clear it with +pte_swp_clear_uffd_wp(), matching copy_nonpresent_pte() and +move_huge_pte(). + +A hwpoison entry, on the other hand, never carries the uffd-wp bit: it is +installed fresh by make_hwpoison_entry() (try_to_unmap_one() does not +preserve uffd-wp on the hwpoison path) and hugetlb_change_protection() +leaves hwpoison entries untouched. There was nothing to clear there, only +the corruption, so drop the clear entirely. + +Link: https://lore.kernel.org/20260708090110.136162-1-kirill@shutemov.name +Fixes: bc70fbf269fd ("mm/hugetlb: handle uffd-wp during fork()") +Signed-off-by: Kiryl Shutsemau +Reported-by: Sashiko AI review +Closes: https://lore.kernel.org/all/20260703140011.99E601F000E9@smtp.kernel.org/ +Suggested-by: David Hildenbrand +Acked-by: David Hildenbrand (Arm) +Assisted-by: Claude:claude-fable-5 +Cc: Muchun Song +Cc: Oscar Salvador +Cc: Peter Xu +Cc: +Signed-off-by: Andrew Morton +(cherry picked from commit 83abe2fd5b3aeb3123b5408a5a91709c5538fb23) +[ kas: adapt to the pre-softleaf idiom ] +Signed-off-by: Kiryl Shutsemau (Meta) +Signed-off-by: Sasha Levin +--- + mm/hugetlb.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/mm/hugetlb.c b/mm/hugetlb.c +index af4533028f62e..edae641407b96 100644 +--- a/mm/hugetlb.c ++++ b/mm/hugetlb.c +@@ -5112,8 +5112,12 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, + */ + ; + } else if (unlikely(is_hugetlb_entry_hwpoisoned(entry))) { +- if (!userfaultfd_wp(dst_vma)) +- entry = huge_pte_clear_uffd_wp(entry); ++ /* ++ * A hwpoison entry never carries the uffd-wp bit: it is ++ * installed fresh by make_hwpoison_entry() and ++ * hugetlb_change_protection() leaves it untouched, so ++ * there is nothing to clear for the child. ++ */ + set_huge_pte_at(dst, addr, dst_pte, entry, sz); + } else if (unlikely(is_hugetlb_entry_migration(entry))) { + swp_entry_t swp_entry = pte_to_swp_entry(entry); +@@ -5132,7 +5136,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, + set_huge_pte_at(src, addr, src_pte, entry, sz); + } + if (!userfaultfd_wp(dst_vma)) +- entry = huge_pte_clear_uffd_wp(entry); ++ entry = pte_swp_clear_uffd_wp(entry); + set_huge_pte_at(dst, addr, dst_pte, entry, sz); + } else if (unlikely(is_pte_marker(entry))) { + pte_marker marker = copy_pte_marker( +-- +2.53.0 + diff --git a/queue-6.6/series b/queue-6.6/series index 7d45e9410b..b286c4f48e 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -206,3 +206,5 @@ hid-logitech-dj-fix-maxfield-check-in-dj-short-report-validation.patch ata-libahci_platform-do-not-set-mask_port_map-when-not-needed.patch ata-ahci-make-ahci_ignore_port-handle-empty-mask_port_map.patch iommu-sva-move-x86-disable-check-before-allocation.patch +mm-hugetlb-fix-swap-entry-corruption-when-clearing-u.patch +mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch