From: Sasha Levin Date: Thu, 30 Jul 2026 02:29:33 +0000 (-0400) Subject: Fixes for all trees X-Git-Tag: v5.10.262~5 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=9c930e9495d921cdcdcd4b73556eb4eb8f1de2eb;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- diff --git a/queue-5.10/openvswitch-fix-gso-userspace-truncation-underflow.patch b/queue-5.10/openvswitch-fix-gso-userspace-truncation-underflow.patch new file mode 100644 index 0000000000..137b75a8c9 --- /dev/null +++ b/queue-5.10/openvswitch-fix-gso-userspace-truncation-underflow.patch @@ -0,0 +1,193 @@ +From 9d87e1aadc0dbf4645c36d0ae3d795e176411384 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 23:01:46 +0200 +Subject: openvswitch: fix GSO userspace truncation underflow + +From: Kyle Zeng + +[ Upstream commit 4032f8ed10fcb84d41c508dfb04be96589f78dfe ] + +OVS_ACTION_ATTR_TRUNC currently stores a delta from the original skb +length in OVS_CB(skb)->cutlen. When a later userspace action segments a +GSO skb, queue_gso_packets() reuses that delta for each smaller segment. +A segment can then reach queue_userspace_packet() with cutlen greater +than skb->len, underflowing the length passed to skb_zerocopy(). + +Store the maximum preserved length instead and bound each consumer +against the current skb length. Use U32_MAX as the no-truncation +sentinel so the value remains valid if skb geometry changes before a +consumer handles it. + +Fixes: f2a4d086ed4c ("openvswitch: Add packet truncation support.") +Cc: stable@vger.kernel.org +Assisted-by: Codex:gpt-5.5 +Signed-off-by: Kyle Zeng +Reviewed-by: Ilya Maximets +Reviewed-by: Aaron Conole +Link: https://patch.msgid.link/20260707221635.27489-1-kylebot@openai.com +Signed-off-by: Paolo Abeni +[5.10.y supports neither OVS_ACTION_ATTR_PSAMPLE nor OVS drop reasons] +Signed-off-by: Ilya Maximets +Signed-off-by: Sasha Levin +--- + net/openvswitch/actions.c | 15 +++++---------- + net/openvswitch/datapath.c | 25 ++++++++++++++----------- + net/openvswitch/datapath.h | 2 +- + net/openvswitch/vport.c | 2 +- + 4 files changed, 21 insertions(+), 23 deletions(-) + +diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c +index 3b3cc6ea274f8c..4995e617ca032f 100644 +--- a/net/openvswitch/actions.c ++++ b/net/openvswitch/actions.c +@@ -855,12 +855,8 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port, + u16 mru = OVS_CB(skb)->mru; + u32 cutlen = OVS_CB(skb)->cutlen; + +- if (unlikely(cutlen > 0)) { +- if (skb->len - cutlen > ovs_mac_header_len(key)) +- pskb_trim(skb, skb->len - cutlen); +- else +- pskb_trim(skb, ovs_mac_header_len(key)); +- } ++ if (unlikely(cutlen < skb->len)) ++ pskb_trim(skb, max(cutlen, ovs_mac_header_len(key))); + + if (likely(!mru || + (skb->len <= mru + vport->dev->hard_header_len))) { +@@ -1232,22 +1228,21 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, + clone = skb_clone(skb, GFP_ATOMIC); + if (clone) + do_output(dp, clone, port, key); +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + break; + } + + case OVS_ACTION_ATTR_TRUNC: { + struct ovs_action_trunc *trunc = nla_data(a); + +- if (skb->len > trunc->max_len) +- OVS_CB(skb)->cutlen = skb->len - trunc->max_len; ++ OVS_CB(skb)->cutlen = trunc->max_len; + break; + } + + case OVS_ACTION_ATTR_USERSPACE: + output_userspace(dp, skb, key, a, attr, + len, OVS_CB(skb)->cutlen); +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + break; + + case OVS_ACTION_ATTR_HASH: +diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c +index 5fb74fbcb2f382..b985e6696b4251 100644 +--- a/net/openvswitch/datapath.c ++++ b/net/openvswitch/datapath.c +@@ -240,7 +240,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) + upcall.cmd = OVS_PACKET_CMD_MISS; + upcall.portid = ovs_vport_find_upcall_portid(p, skb); + upcall.mru = OVS_CB(skb)->mru; +- error = ovs_dp_upcall(dp, skb, key, &upcall, 0); ++ error = ovs_dp_upcall(dp, skb, key, &upcall, U32_MAX); + switch (error) { + case 0: + case -EAGAIN: +@@ -400,7 +400,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + struct sk_buff *nskb = NULL; + struct sk_buff *user_skb = NULL; /* to be queued to userspace */ + struct nlattr *nla; +- size_t len; ++ size_t msg_size; ++ size_t skb_len; + unsigned int hlen; + int err, dp_ifindex; + u64 hash; +@@ -421,7 +422,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + skb = nskb; + } + +- if (nla_attr_size(skb->len) > USHRT_MAX) { ++ skb_len = min(skb->len, cutlen); ++ if (nla_attr_size(skb_len) > USHRT_MAX) { + err = -EFBIG; + goto out; + } +@@ -436,13 +438,13 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + * padding logic. Only perform zerocopy if padding is not required. + */ + if (dp->user_features & OVS_DP_F_UNALIGNED) +- hlen = skb_zerocopy_headlen(skb); ++ hlen = min(skb_zerocopy_headlen(skb), cutlen); + else +- hlen = skb->len; ++ hlen = skb_len; + +- len = upcall_msg_size(upcall_info, hlen - cutlen, +- OVS_CB(skb)->acts_origlen); +- user_skb = genlmsg_new(len, GFP_ATOMIC); ++ msg_size = upcall_msg_size(upcall_info, hlen, ++ OVS_CB(skb)->acts_origlen); ++ user_skb = genlmsg_new(msg_size, GFP_ATOMIC); + if (!user_skb) { + err = -ENOMEM; + goto out; +@@ -503,7 +505,7 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + } + + /* Add OVS_PACKET_ATTR_LEN when packet is truncated */ +- if (cutlen > 0 && ++ if (skb_len < skb->len && + nla_put_u32(user_skb, OVS_PACKET_ATTR_LEN, skb->len)) { + err = -ENOBUFS; + goto out; +@@ -528,9 +530,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + err = -ENOBUFS; + goto out; + } +- nla->nla_len = nla_attr_size(skb->len - cutlen); ++ nla->nla_len = nla_attr_size(skb_len); + +- err = skb_zerocopy(user_skb, skb, skb->len - cutlen, hlen); ++ err = skb_zerocopy(user_skb, skb, skb_len, hlen); + if (err) + goto out; + +@@ -587,6 +589,7 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info) + packet->ignore_df = 1; + } + OVS_CB(packet)->mru = mru; ++ OVS_CB(packet)->cutlen = U32_MAX; + + if (a[OVS_PACKET_ATTR_HASH]) { + hash = nla_get_u64(a[OVS_PACKET_ATTR_HASH]); +diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h +index 38f7d3e66ca613..62e7ad32f83764 100644 +--- a/net/openvswitch/datapath.h ++++ b/net/openvswitch/datapath.h +@@ -96,7 +96,7 @@ struct datapath { + * @mru: The maximum received fragement size; 0 if the packet is not + * fragmented. + * @acts_origlen: The netlink size of the flow actions applied to this skb. +- * @cutlen: The number of bytes from the packet end to be removed. ++ * @cutlen: The number of bytes in the packet to preserve on output. + */ + struct ovs_skb_cb { + struct vport *input_vport; +diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c +index da733b92ae8a76..ced707ff6dd971 100644 +--- a/net/openvswitch/vport.c ++++ b/net/openvswitch/vport.c +@@ -436,7 +436,7 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, + + OVS_CB(skb)->input_vport = vport; + OVS_CB(skb)->mru = 0; +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) { + u32 mark; + +-- +2.53.0 + diff --git a/queue-5.10/series b/queue-5.10/series index 53f2b53f78..339f1739a3 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -187,3 +187,6 @@ drm-amdgpu-gfx8-drop-unecessary-bug_on.patch drm-amdgpu-gfx9-replace-bug_on-with-warn_on.patch drm-amdgpu-fix-division-by-zero-with-invalid-uvd-dimensions.patch drm-amdgpu-invoke-pm_genpd_remove-before-freeing-genpd.patch +tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch +wifi-mt76-mt7615-drop-txrx_notify-on-non-mmio-buses.patch +openvswitch-fix-gso-userspace-truncation-underflow.patch diff --git a/queue-5.10/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch b/queue-5.10/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch new file mode 100644 index 0000000000..d01ebd7349 --- /dev/null +++ b/queue-5.10/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch @@ -0,0 +1,124 @@ +From 6c98e5c4b49db2cd1f1b3c8c572c719f7a661d1d Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 15:16:02 +0300 +Subject: tipc: fix use-after-free of the discoverer in tipc_disc_rcv() + +From: Weiming Shi + +commit 1579342d71133da7f00daa02c75cebec7372097b upstream. + +bearer_disable() frees b->disc with tipc_disc_delete()'s plain kfree(), +but tipc_disc_rcv() still dereferences b->disc in RX softirq under +rcu_read_lock() (tipc_udp_recv -> tipc_rcv -> tipc_disc_rcv). + +L2 bearers are safe thanks to the synchronize_net() in +tipc_disable_l2_media(), but the UDP bearer defers that call to the +cleanup_bearer() workqueue, so the discoverer is freed with no grace +period: + + BUG: KASAN: slab-use-after-free in tipc_disc_rcv (net/tipc/discover.c:149) + Read of size 8 at addr ffff88802348b728 by task poc_tipc/184 + + tipc_disc_rcv (net/tipc/discover.c:149) + tipc_rcv (net/tipc/node.c:2126) + tipc_udp_recv (net/tipc/udp_media.c:391) + udp_rcv (net/ipv4/udp.c:2643) + ip_local_deliver_finish (net/ipv4/ip_input.c:241) + + Freed by task 181: + kfree (mm/slub.c:6565) + bearer_disable (net/tipc/bearer.c:418) + tipc_nl_bearer_disable (net/tipc/bearer.c:1001) + +The bearer is freed with kfree_rcu(); free the discoverer the same way. +Add an rcu_head to struct tipc_discoverer and free it and its skb from an +RCU callback. + +Because the RCU callback (tipc_disc_free_rcu) lives in module text, a +call_rcu() that is still pending when the tipc module is unloaded would +invoke a freed function. Add an rcu_barrier() to tipc_exit() after the +bearer subsystem has been torn down, so all pending discoverer callbacks +have run before the module text goes away. + +Reachable from an unprivileged user namespace: the TIPCv2 genl family is +netnsok and its bearer commands have no GENL_ADMIN_PERM. Needs CONFIG_TIPC +and CONFIG_TIPC_MEDIA_UDP. + +Fixes: 25b0b9c4e835 ("tipc: handle collisions of 32-bit node address hash values") +Reported-by: Xiang Mei +Signed-off-by: Weiming Shi +Reviewed-by: Tung Nguyen +Link: https://patch.msgid.link/20260617135744.3383175-3-bestswngs@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Alexander Martyniuk +Signed-off-by: Sasha Levin +--- + net/tipc/core.c | 5 +++++ + net/tipc/discover.c | 14 ++++++++++++-- + 2 files changed, 17 insertions(+), 2 deletions(-) + +diff --git a/net/tipc/core.c b/net/tipc/core.c +index 7724499f516e9e..4f01691aea3a40 100644 +--- a/net/tipc/core.c ++++ b/net/tipc/core.c +@@ -220,6 +220,11 @@ static void __exit tipc_exit(void) + unregister_pernet_device(&tipc_net_ops); + tipc_unregister_sysctl(); + ++ /* TODO: Wait for all timers that called call_rcu() to finish before ++ * calling rcu_barrier(). ++ */ ++ rcu_barrier(); ++ + pr_info("Deactivated\n"); + } + +diff --git a/net/tipc/discover.c b/net/tipc/discover.c +index 2730310249e3ca..28665de32245a5 100644 +--- a/net/tipc/discover.c ++++ b/net/tipc/discover.c +@@ -58,6 +58,7 @@ + * @skb: request message to be (repeatedly) sent + * @timer: timer governing period between requests + * @timer_intv: current interval between requests (in ms) ++ * @rcu: RCU head for deferred freeing + */ + struct tipc_discoverer { + u32 bearer_id; +@@ -69,6 +70,7 @@ struct tipc_discoverer { + struct sk_buff *skb; + struct timer_list timer; + unsigned long timer_intv; ++ struct rcu_head rcu; + }; + + /** +@@ -381,6 +383,15 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b, + return 0; + } + ++static void tipc_disc_free_rcu(struct rcu_head *rp) ++{ ++ struct tipc_discoverer *d = container_of(rp, struct tipc_discoverer, ++ rcu); ++ ++ kfree_skb(d->skb); ++ kfree(d); ++} ++ + /** + * tipc_disc_delete - destroy object sending periodic link setup requests + * @d: ptr to link duest structure +@@ -388,8 +399,7 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b, + void tipc_disc_delete(struct tipc_discoverer *d) + { + del_timer_sync(&d->timer); +- kfree_skb(d->skb); +- kfree(d); ++ call_rcu(&d->rcu, tipc_disc_free_rcu); + } + + /** +-- +2.53.0 + diff --git a/queue-5.10/wifi-mt76-mt7615-drop-txrx_notify-on-non-mmio-buses.patch b/queue-5.10/wifi-mt76-mt7615-drop-txrx_notify-on-non-mmio-buses.patch new file mode 100644 index 0000000000..1f1e6304d0 --- /dev/null +++ b/queue-5.10/wifi-mt76-mt7615-drop-txrx_notify-on-non-mmio-buses.patch @@ -0,0 +1,52 @@ +From 67b6439e6dcbe407b489c24d78362f8c7ad8c12a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 08:00:42 -0700 +Subject: wifi: mt76: mt7615: drop TXRX_NOTIFY on non-mmio buses + +From: Devin Wittmayer + +commit 39afc46c0243d10b7795e6e6cf4ae91f41732120 upstream. + +PKT_TYPE_TXRX_NOTIFY is an mmio-only event, but mt7615_rx_check() and +mt7615_queue_rx_skb() dispatch it to mt7615_mac_tx_free() on every bus. +mt7615_mac_tx_free() cleans the DMA tx queues with +mt76_queue_tx_cleanup(), which calls queue_ops->tx_cleanup(). Only the +mmio queue ops implement that callback; on the mt7663 USB and SDIO +buses it is NULL, so a TXRX_NOTIFY there calls a NULL pointer in the RX +worker. Same defect as the mt7921 and mt7925 patches in this series. + +Drop the event on non-mmio buses via mt76_is_mmio(), as in +commit 5683e1488aa9 ("wifi: mt76: connac: do not check WED status for +non-mmio devices"). + +Fixes: eb99cc95c3b6 ("mt76: mt7615: introduce mt7663u support") +Cc: stable@vger.kernel.org +Signed-off-by: Devin Wittmayer +Link: https://patch.msgid.link/20260627191336.20223-4-lucid_duck@justthetip.ca +Signed-off-by: Felix Fietkau +[ No mt7615_rx_check() on this tree, so only the mt7615_queue_rx_skb() + hunk applies, and mt7615_mac_tx_free() takes the skb rather than + (data, len), so the guard frees it here. ] +Signed-off-by: Sasha Levin +--- + drivers/net/wireless/mediatek/mt76/mt7615/mac.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c +index 4364f73b501da8..0c9201e073b271 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c +@@ -1492,6 +1492,10 @@ void mt7615_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, + dev_kfree_skb(skb); + break; + case PKT_TYPE_TXRX_NOTIFY: ++ if (!mt76_is_mmio(mdev)) { ++ dev_kfree_skb(skb); ++ break; ++ } + mt7615_mac_tx_free(dev, skb); + break; + case PKT_TYPE_RX_EVENT: +-- +2.53.0 + diff --git a/queue-5.15/openvswitch-fix-gso-userspace-truncation-underflow.patch b/queue-5.15/openvswitch-fix-gso-userspace-truncation-underflow.patch new file mode 100644 index 0000000000..c75bea5599 --- /dev/null +++ b/queue-5.15/openvswitch-fix-gso-userspace-truncation-underflow.patch @@ -0,0 +1,193 @@ +From b0db12307f8fc57d1cc5d61de932d4b9a723da43 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 23:00:20 +0200 +Subject: openvswitch: fix GSO userspace truncation underflow + +From: Kyle Zeng + +[ Upstream commit 4032f8ed10fcb84d41c508dfb04be96589f78dfe ] + +OVS_ACTION_ATTR_TRUNC currently stores a delta from the original skb +length in OVS_CB(skb)->cutlen. When a later userspace action segments a +GSO skb, queue_gso_packets() reuses that delta for each smaller segment. +A segment can then reach queue_userspace_packet() with cutlen greater +than skb->len, underflowing the length passed to skb_zerocopy(). + +Store the maximum preserved length instead and bound each consumer +against the current skb length. Use U32_MAX as the no-truncation +sentinel so the value remains valid if skb geometry changes before a +consumer handles it. + +Fixes: f2a4d086ed4c ("openvswitch: Add packet truncation support.") +Cc: stable@vger.kernel.org +Assisted-by: Codex:gpt-5.5 +Signed-off-by: Kyle Zeng +Reviewed-by: Ilya Maximets +Reviewed-by: Aaron Conole +Link: https://patch.msgid.link/20260707221635.27489-1-kylebot@openai.com +Signed-off-by: Paolo Abeni +[5.15.y supports neither OVS_ACTION_ATTR_PSAMPLE nor OVS drop reasons] +Signed-off-by: Ilya Maximets +Signed-off-by: Sasha Levin +--- + net/openvswitch/actions.c | 15 +++++---------- + net/openvswitch/datapath.c | 25 ++++++++++++++----------- + net/openvswitch/datapath.h | 2 +- + net/openvswitch/vport.c | 2 +- + 4 files changed, 21 insertions(+), 23 deletions(-) + +diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c +index aa240953d7669f..6d6d82ed5bd172 100644 +--- a/net/openvswitch/actions.c ++++ b/net/openvswitch/actions.c +@@ -856,12 +856,8 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port, + u16 mru = OVS_CB(skb)->mru; + u32 cutlen = OVS_CB(skb)->cutlen; + +- if (unlikely(cutlen > 0)) { +- if (skb->len - cutlen > ovs_mac_header_len(key)) +- pskb_trim(skb, skb->len - cutlen); +- else +- pskb_trim(skb, ovs_mac_header_len(key)); +- } ++ if (unlikely(cutlen < skb->len)) ++ pskb_trim(skb, max(cutlen, ovs_mac_header_len(key))); + + if (likely(!mru || + (skb->len <= mru + vport->dev->hard_header_len))) { +@@ -1242,22 +1238,21 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, + clone = skb_clone(skb, GFP_ATOMIC); + if (clone) + do_output(dp, clone, port, key); +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + break; + } + + case OVS_ACTION_ATTR_TRUNC: { + struct ovs_action_trunc *trunc = nla_data(a); + +- if (skb->len > trunc->max_len) +- OVS_CB(skb)->cutlen = skb->len - trunc->max_len; ++ OVS_CB(skb)->cutlen = trunc->max_len; + break; + } + + case OVS_ACTION_ATTR_USERSPACE: + output_userspace(dp, skb, key, a, attr, + len, OVS_CB(skb)->cutlen); +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + break; + + case OVS_ACTION_ATTR_HASH: +diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c +index 0202111cda3531..e9ff6b87af7010 100644 +--- a/net/openvswitch/datapath.c ++++ b/net/openvswitch/datapath.c +@@ -250,7 +250,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) + upcall.portid = ovs_vport_find_upcall_portid(p, skb); + + upcall.mru = OVS_CB(skb)->mru; +- error = ovs_dp_upcall(dp, skb, key, &upcall, 0); ++ error = ovs_dp_upcall(dp, skb, key, &upcall, U32_MAX); + switch (error) { + case 0: + case -EAGAIN: +@@ -413,7 +413,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + struct sk_buff *nskb = NULL; + struct sk_buff *user_skb = NULL; /* to be queued to userspace */ + struct nlattr *nla; +- size_t len; ++ size_t msg_size; ++ size_t skb_len; + unsigned int hlen; + int err, dp_ifindex; + u64 hash; +@@ -434,7 +435,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + skb = nskb; + } + +- if (nla_attr_size(skb->len) > USHRT_MAX) { ++ skb_len = min(skb->len, cutlen); ++ if (nla_attr_size(skb_len) > USHRT_MAX) { + err = -EFBIG; + goto out; + } +@@ -449,13 +451,13 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + * padding logic. Only perform zerocopy if padding is not required. + */ + if (dp->user_features & OVS_DP_F_UNALIGNED) +- hlen = skb_zerocopy_headlen(skb); ++ hlen = min(skb_zerocopy_headlen(skb), cutlen); + else +- hlen = skb->len; ++ hlen = skb_len; + +- len = upcall_msg_size(upcall_info, hlen - cutlen, +- OVS_CB(skb)->acts_origlen); +- user_skb = genlmsg_new(len, GFP_ATOMIC); ++ msg_size = upcall_msg_size(upcall_info, hlen, ++ OVS_CB(skb)->acts_origlen); ++ user_skb = genlmsg_new(msg_size, GFP_ATOMIC); + if (!user_skb) { + err = -ENOMEM; + goto out; +@@ -516,7 +518,7 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + } + + /* Add OVS_PACKET_ATTR_LEN when packet is truncated */ +- if (cutlen > 0 && ++ if (skb_len < skb->len && + nla_put_u32(user_skb, OVS_PACKET_ATTR_LEN, skb->len)) { + err = -ENOBUFS; + goto out; +@@ -541,9 +543,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + err = -ENOBUFS; + goto out; + } +- nla->nla_len = nla_attr_size(skb->len - cutlen); ++ nla->nla_len = nla_attr_size(skb_len); + +- err = skb_zerocopy(user_skb, skb, skb->len - cutlen, hlen); ++ err = skb_zerocopy(user_skb, skb, skb_len, hlen); + if (err) + goto out; + +@@ -600,6 +602,7 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info) + packet->ignore_df = 1; + } + OVS_CB(packet)->mru = mru; ++ OVS_CB(packet)->cutlen = U32_MAX; + + if (a[OVS_PACKET_ATTR_HASH]) { + hash = nla_get_u64(a[OVS_PACKET_ATTR_HASH]); +diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h +index fcfe6cb464417e..86a47998a359a6 100644 +--- a/net/openvswitch/datapath.h ++++ b/net/openvswitch/datapath.h +@@ -114,7 +114,7 @@ struct datapath { + * @mru: The maximum received fragement size; 0 if the packet is not + * fragmented. + * @acts_origlen: The netlink size of the flow actions applied to this skb. +- * @cutlen: The number of bytes from the packet end to be removed. ++ * @cutlen: The number of bytes in the packet to preserve on output. + */ + struct ovs_skb_cb { + struct vport *input_vport; +diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c +index 25197e14c123bd..a2a9a602dd254d 100644 +--- a/net/openvswitch/vport.c ++++ b/net/openvswitch/vport.c +@@ -438,7 +438,7 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, + + OVS_CB(skb)->input_vport = vport; + OVS_CB(skb)->mru = 0; +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) { + u32 mark; + +-- +2.53.0 + diff --git a/queue-5.15/series b/queue-5.15/series index 7b3c90c883..7259ea0136 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -230,3 +230,6 @@ drm-amdgpu-gfx8-drop-unecessary-bug_on.patch drm-amdgpu-gfx9-replace-bug_on-with-warn_on.patch drm-amdgpu-fix-division-by-zero-with-invalid-uvd-dimensions.patch drm-amdgpu-invoke-pm_genpd_remove-before-freeing-genpd.patch +tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch +wifi-mt76-mt7615-drop-txrx_notify-on-non-mmio-buses.patch +openvswitch-fix-gso-userspace-truncation-underflow.patch diff --git a/queue-5.15/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch b/queue-5.15/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch new file mode 100644 index 0000000000..c62abbb014 --- /dev/null +++ b/queue-5.15/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch @@ -0,0 +1,124 @@ +From b82dbf5b0a5f904d428e938b5580869b98ad80d7 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 15:16:03 +0300 +Subject: tipc: fix use-after-free of the discoverer in tipc_disc_rcv() + +From: Weiming Shi + +commit 1579342d71133da7f00daa02c75cebec7372097b upstream. + +bearer_disable() frees b->disc with tipc_disc_delete()'s plain kfree(), +but tipc_disc_rcv() still dereferences b->disc in RX softirq under +rcu_read_lock() (tipc_udp_recv -> tipc_rcv -> tipc_disc_rcv). + +L2 bearers are safe thanks to the synchronize_net() in +tipc_disable_l2_media(), but the UDP bearer defers that call to the +cleanup_bearer() workqueue, so the discoverer is freed with no grace +period: + + BUG: KASAN: slab-use-after-free in tipc_disc_rcv (net/tipc/discover.c:149) + Read of size 8 at addr ffff88802348b728 by task poc_tipc/184 + + tipc_disc_rcv (net/tipc/discover.c:149) + tipc_rcv (net/tipc/node.c:2126) + tipc_udp_recv (net/tipc/udp_media.c:391) + udp_rcv (net/ipv4/udp.c:2643) + ip_local_deliver_finish (net/ipv4/ip_input.c:241) + + Freed by task 181: + kfree (mm/slub.c:6565) + bearer_disable (net/tipc/bearer.c:418) + tipc_nl_bearer_disable (net/tipc/bearer.c:1001) + +The bearer is freed with kfree_rcu(); free the discoverer the same way. +Add an rcu_head to struct tipc_discoverer and free it and its skb from an +RCU callback. + +Because the RCU callback (tipc_disc_free_rcu) lives in module text, a +call_rcu() that is still pending when the tipc module is unloaded would +invoke a freed function. Add an rcu_barrier() to tipc_exit() after the +bearer subsystem has been torn down, so all pending discoverer callbacks +have run before the module text goes away. + +Reachable from an unprivileged user namespace: the TIPCv2 genl family is +netnsok and its bearer commands have no GENL_ADMIN_PERM. Needs CONFIG_TIPC +and CONFIG_TIPC_MEDIA_UDP. + +Fixes: 25b0b9c4e835 ("tipc: handle collisions of 32-bit node address hash values") +Reported-by: Xiang Mei +Signed-off-by: Weiming Shi +Reviewed-by: Tung Nguyen +Link: https://patch.msgid.link/20260617135744.3383175-3-bestswngs@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Alexander Martyniuk +Signed-off-by: Sasha Levin +--- + net/tipc/core.c | 5 +++++ + net/tipc/discover.c | 14 ++++++++++++-- + 2 files changed, 17 insertions(+), 2 deletions(-) + +diff --git a/net/tipc/core.c b/net/tipc/core.c +index 434e70eabe0812..1ddecea1df6e91 100644 +--- a/net/tipc/core.c ++++ b/net/tipc/core.c +@@ -218,6 +218,11 @@ static void __exit tipc_exit(void) + unregister_pernet_device(&tipc_net_ops); + tipc_unregister_sysctl(); + ++ /* TODO: Wait for all timers that called call_rcu() to finish before ++ * calling rcu_barrier(). ++ */ ++ rcu_barrier(); ++ + pr_info("Deactivated\n"); + } + +diff --git a/net/tipc/discover.c b/net/tipc/discover.c +index e8dcdf267c0c3f..835ff27f8ea88c 100644 +--- a/net/tipc/discover.c ++++ b/net/tipc/discover.c +@@ -58,6 +58,7 @@ + * @skb: request message to be (repeatedly) sent + * @timer: timer governing period between requests + * @timer_intv: current interval between requests (in ms) ++ * @rcu: RCU head for deferred freeing + */ + struct tipc_discoverer { + u32 bearer_id; +@@ -69,6 +70,7 @@ struct tipc_discoverer { + struct sk_buff *skb; + struct timer_list timer; + unsigned long timer_intv; ++ struct rcu_head rcu; + }; + + /** +@@ -382,6 +384,15 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b, + return 0; + } + ++static void tipc_disc_free_rcu(struct rcu_head *rp) ++{ ++ struct tipc_discoverer *d = container_of(rp, struct tipc_discoverer, ++ rcu); ++ ++ kfree_skb(d->skb); ++ kfree(d); ++} ++ + /** + * tipc_disc_delete - destroy object sending periodic link setup requests + * @d: ptr to link dest structure +@@ -389,8 +400,7 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b, + void tipc_disc_delete(struct tipc_discoverer *d) + { + del_timer_sync(&d->timer); +- kfree_skb(d->skb); +- kfree(d); ++ call_rcu(&d->rcu, tipc_disc_free_rcu); + } + + /** +-- +2.53.0 + diff --git a/queue-5.15/wifi-mt76-mt7615-drop-txrx_notify-on-non-mmio-buses.patch b/queue-5.15/wifi-mt76-mt7615-drop-txrx_notify-on-non-mmio-buses.patch new file mode 100644 index 0000000000..37d386581c --- /dev/null +++ b/queue-5.15/wifi-mt76-mt7615-drop-txrx_notify-on-non-mmio-buses.patch @@ -0,0 +1,52 @@ +From 7676b51964a6e4efee9154f091be9a3bc32ce328 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 08:00:56 -0700 +Subject: wifi: mt76: mt7615: drop TXRX_NOTIFY on non-mmio buses + +From: Devin Wittmayer + +commit 39afc46c0243d10b7795e6e6cf4ae91f41732120 upstream. + +PKT_TYPE_TXRX_NOTIFY is an mmio-only event, but mt7615_rx_check() and +mt7615_queue_rx_skb() dispatch it to mt7615_mac_tx_free() on every bus. +mt7615_mac_tx_free() cleans the DMA tx queues with +mt76_queue_tx_cleanup(), which calls queue_ops->tx_cleanup(). Only the +mmio queue ops implement that callback; on the mt7663 USB and SDIO +buses it is NULL, so a TXRX_NOTIFY there calls a NULL pointer in the RX +worker. Same defect as the mt7921 and mt7925 patches in this series. + +Drop the event on non-mmio buses via mt76_is_mmio(), as in +commit 5683e1488aa9 ("wifi: mt76: connac: do not check WED status for +non-mmio devices"). + +Fixes: eb99cc95c3b6 ("mt76: mt7615: introduce mt7663u support") +Cc: stable@vger.kernel.org +Signed-off-by: Devin Wittmayer +Link: https://patch.msgid.link/20260627191336.20223-4-lucid_duck@justthetip.ca +Signed-off-by: Felix Fietkau +[ No mt7615_rx_check() on this tree, so only the mt7615_queue_rx_skb() + hunk applies, and mt7615_mac_tx_free() takes the skb rather than + (data, len), so the guard frees it here. ] +Signed-off-by: Sasha Levin +--- + drivers/net/wireless/mediatek/mt76/mt7615/mac.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c +index 2f0ba8a75d71bc..ee90ec27f09faa 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c +@@ -1601,6 +1601,10 @@ void mt7615_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q, + dev_kfree_skb(skb); + break; + case PKT_TYPE_TXRX_NOTIFY: ++ if (!mt76_is_mmio(mdev)) { ++ dev_kfree_skb(skb); ++ break; ++ } + mt7615_mac_tx_free(dev, skb); + break; + case PKT_TYPE_RX_EVENT: +-- +2.53.0 + diff --git a/queue-6.1/bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch b/queue-6.1/bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch new file mode 100644 index 0000000000..9efcaad298 --- /dev/null +++ b/queue-6.1/bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch @@ -0,0 +1,39 @@ +From df55355b80d46547f9ff07aeacfcd505dffa9763 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 23:11:51 +0800 +Subject: bpf: drop bpf_lsm_getselfattr from hook list + +From: Wentao Guan + +Backport ("bpf, lsm: Add disabled BPF LSM hook list") for v6.1.y bring the +warning "WARN: resolve_btfids: unresolved symbol bpf_lsm_getselfattr". + +The lsm_getselfattr from commit a04a1198088a +("LSM: syscalls for current process attributes"), no need to backport +the huge patch, simply drop the entry to fix the noise. + +This is a fix for stable v6.1.178 backport commit, so no upstream commit. + +Fixes: 0562ae02a6c4 ("bpf, lsm: Add disabled BPF LSM hook list") +Link: https://lore.kernel.org/stable/20260728225520.stable-0003@kernel.org/ +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + kernel/bpf/bpf_lsm.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c +index 4bb8c363c0bfde..61e93253b6b5e7 100644 +--- a/kernel/bpf/bpf_lsm.c ++++ b/kernel/bpf/bpf_lsm.c +@@ -42,7 +42,6 @@ BTF_ID(func, bpf_lsm_inode_need_killpriv) + BTF_ID(func, bpf_lsm_inode_getsecurity) + BTF_ID(func, bpf_lsm_inode_listsecurity) + BTF_ID(func, bpf_lsm_inode_copy_up_xattr) +-BTF_ID(func, bpf_lsm_getselfattr) + BTF_ID(func, bpf_lsm_getprocattr) + BTF_ID(func, bpf_lsm_setprocattr) + #ifdef CONFIG_KEYS +-- +2.53.0 + diff --git a/queue-6.1/exfat-validate-cluster-allocation-bits-of-the-alloca.patch b/queue-6.1/exfat-validate-cluster-allocation-bits-of-the-alloca.patch new file mode 100644 index 0000000000..440af3ed54 --- /dev/null +++ b/queue-6.1/exfat-validate-cluster-allocation-bits-of-the-alloca.patch @@ -0,0 +1,116 @@ +From 976527ea433873bda8166a340ddf53c55edb5e57 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 20:53:13 +0000 +Subject: exfat: validate cluster allocation bits of the allocation bitmap + +From: Namjae Jeon + +[ Upstream commit 79c1587b6cda74deb0c86fc7ba194b92958c793c ] + +syzbot created an exfat image with cluster bits not set for the allocation +bitmap. exfat-fs reads and uses the allocation bitmap without checking +this. The problem is that if the start cluster of the allocation bitmap +is 6, cluster 6 can be allocated when creating a directory with mkdir. +exfat zeros out this cluster in exfat_mkdir, which can delete existing +entries. This can reallocate the allocated entries. In addition, +the allocation bitmap is also zeroed out, so cluster 6 can be reallocated. +This patch adds exfat_test_bitmap_range to validate that clusters used for +the allocation bitmap are correctly marked as in-use. + +Reported-by: syzbot+a725ab460fc1def9896f@syzkaller.appspotmail.com +Tested-by: syzbot+a725ab460fc1def9896f@syzkaller.appspotmail.com +Reviewed-by: Yuezhang Mo +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +[Adapted to 6.1: replaced __le_long/lel_to_cpu word-level bitmap access + with per-bit test_bit_le() calls, as __le_long and lel_to_cpu do not + exist in 6.1. Uses same test_bit_le API as rest of exfat bitmap code.] +Signed-off-by: Jay Wang +Signed-off-by: Sasha Levin +--- + fs/exfat/balloc.c | 54 ++++++++++++++++++++++++++++++++++++----------- + 1 file changed, 42 insertions(+), 12 deletions(-) + +diff --git a/fs/exfat/balloc.c b/fs/exfat/balloc.c +index 32209acd51be4f..2d4fe3d754bbc5 100644 +--- a/fs/exfat/balloc.c ++++ b/fs/exfat/balloc.c +@@ -45,12 +45,37 @@ static const unsigned char used_bit[] = { + /* + * Allocation Bitmap Management Functions + */ ++static bool exfat_test_bitmap_range(struct super_block *sb, unsigned int clu, ++ unsigned int count) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned int start = clu; ++ unsigned int end = clu + count; ++ unsigned int ent_idx, i, b; ++ ++ if (!is_valid_cluster(sbi, start) || !is_valid_cluster(sbi, end - 1)) ++ return false; ++ ++ while (start < end) { ++ ent_idx = CLUSTER_TO_BITMAP_ENT(start); ++ i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); ++ b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); ++ ++ if (!test_bit_le(b, sbi->vol_amap[i]->b_data)) ++ return false; ++ ++ start++; ++ } ++ ++ return true; ++} ++ + static int exfat_allocate_bitmap(struct super_block *sb, + struct exfat_dentry *ep) + { + struct exfat_sb_info *sbi = EXFAT_SB(sb); + long long map_size; +- unsigned int i, need_map_size; ++ unsigned int i, j, need_map_size; + sector_t sector; + + sbi->map_clu = le32_to_cpu(ep->dentry.bitmap.start_clu); +@@ -77,20 +102,25 @@ static int exfat_allocate_bitmap(struct super_block *sb, + sector = exfat_cluster_to_sector(sbi, sbi->map_clu); + for (i = 0; i < sbi->map_sectors; i++) { + sbi->vol_amap[i] = sb_bread(sb, sector + i); +- if (!sbi->vol_amap[i]) { +- /* release all buffers and free vol_amap */ +- int j = 0; +- +- while (j < i) +- brelse(sbi->vol_amap[j++]); +- +- kvfree(sbi->vol_amap); +- sbi->vol_amap = NULL; +- return -EIO; +- } ++ if (!sbi->vol_amap[i]) ++ goto err_out; + } + ++ if (exfat_test_bitmap_range(sb, sbi->map_clu, ++ EXFAT_B_TO_CLU_ROUND_UP(map_size, sbi)) == false) ++ goto err_out; ++ + return 0; ++ ++err_out: ++ j = 0; ++ /* release all buffers and free vol_amap */ ++ while (j < i) ++ brelse(sbi->vol_amap[j++]); ++ ++ kvfree(sbi->vol_amap); ++ sbi->vol_amap = NULL; ++ return -EIO; + } + + int exfat_load_bitmap(struct super_block *sb) +-- +2.53.0 + diff --git a/queue-6.1/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch b/queue-6.1/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch new file mode 100644 index 0000000000..5f61e240bf --- /dev/null +++ b/queue-6.1/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch @@ -0,0 +1,261 @@ +From 5c2d836210e8692514117e112d8e66ca5c39d4e2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 08:26:55 -0700 +Subject: fscrypt: Avoid dynamic allocation in fscrypt_get_devices() + +From: Eric Biggers + +commit 6fe4e4b8259e1330945b5f3c9476e08473b8e0e8 upstream. + +When a blk_crypto_key starts being used or is evicted, fs/crypto/ calls +fscrypt_get_devices() to get the filesystem's list of block devices, +then iterates over them and calls blk_crypto_config_supported(), +blk_crypto_start_using_key(), or blk_crypto_evict_key() on each one. + +Currently, the block device pointers are placed in a dynamically +allocated array. This dynamic allocation is problematic because: + +- It can fail, especially at the fscrypt_destroy_inline_crypt_key() call + site when it's invoked for inode eviction under direct reclaim. + +- fscrypt_destroy_inline_crypt_key() doesn't handle the failure. It + just zeroizes and frees the blk_crypto_key without calling + blk_crypto_evict_key(). That causes a use-after-free. + +For now, let's fix this in the straightforward and easily-backportable +way by switching to an on-stack array. Currently the fscrypt +multi-device functionality is used only by f2fs, which has a hardcoded +limit of 8 block devices. An on-stack array works fine for that. + +(Of course, this solution won't scale up to large number of block +devices. For that we'd need a different solution, like moving the block +device iteration into the filesystem. Or in the case of btrfs, which +will only support blk-crypto-fallback, we should make it just call +blk-crypto-fallback directly, so the block devices won't be needed.) + +Fixes: 22e9947a4b2b ("fscrypt: stop holding extra request_queue references") +Cc: stable@vger.kernel.org +Reported-by: Sashiko +Closes: https://sashiko.dev/#/patchset/20260713023708.9245-1-ebiggers%40kernel.org +Reviewed-by: Christoph Hellwig +Link: https://patch.msgid.link/20260719055602.78828-1-ebiggers@kernel.org +Signed-off-by: Eric Biggers +Signed-off-by: Sasha Levin +--- + fs/crypto/inline_crypt.c | 57 ++++++++++++++-------------------------- + fs/f2fs/super.c | 25 ++++++++++-------- + include/linux/fscrypt.h | 18 +++++++------ + 3 files changed, 44 insertions(+), 56 deletions(-) + +diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c +index 8bfb3ce864766e..47645c5539bc84 100644 +--- a/fs/crypto/inline_crypt.c ++++ b/fs/crypto/inline_crypt.c +@@ -21,22 +21,14 @@ + + #include "fscrypt_private.h" + +-static struct block_device **fscrypt_get_devices(struct super_block *sb, +- unsigned int *num_devs) ++static unsigned int ++fscrypt_get_devices(struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]) + { +- struct block_device **devs; +- +- if (sb->s_cop->get_devices) { +- devs = sb->s_cop->get_devices(sb, num_devs); +- if (devs) +- return devs; +- } +- devs = kmalloc(sizeof(*devs), GFP_KERNEL); +- if (!devs) +- return ERR_PTR(-ENOMEM); ++ if (sb->s_cop->get_devices) ++ return sb->s_cop->get_devices(sb, devs); + devs[0] = sb->s_bdev; +- *num_devs = 1; +- return devs; ++ return 1; + } + + static unsigned int fscrypt_get_dun_bytes(const struct fscrypt_info *ci) +@@ -95,7 +87,7 @@ int fscrypt_select_encryption_impl(struct fscrypt_info *ci) + const struct inode *inode = ci->ci_inode; + struct super_block *sb = inode->i_sb; + struct blk_crypto_config crypto_cfg; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + +@@ -132,20 +124,15 @@ int fscrypt_select_encryption_impl(struct fscrypt_info *ci) + crypto_cfg.data_unit_size = sb->s_blocksize; + crypto_cfg.dun_bytes = fscrypt_get_dun_bytes(ci); + +- devs = fscrypt_get_devices(sb, &num_devs); +- if (IS_ERR(devs)) +- return PTR_ERR(devs); +- ++ num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) { + if (!blk_crypto_config_supported(devs[i], &crypto_cfg)) +- goto out_free_devs; ++ return 0; + } + + fscrypt_log_blk_crypto_impl(ci->ci_mode, devs, num_devs, &crypto_cfg); + + ci->ci_inlinecrypt = true; +-out_free_devs: +- kfree(devs); + + return 0; + } +@@ -158,7 +145,7 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, + struct super_block *sb = inode->i_sb; + enum blk_crypto_mode_num crypto_mode = ci->ci_mode->blk_crypto_mode; + struct blk_crypto_key *blk_key; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + int err; +@@ -175,17 +162,12 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, + } + + /* Start using blk-crypto on all the filesystem's block devices. */ +- devs = fscrypt_get_devices(sb, &num_devs); +- if (IS_ERR(devs)) { +- err = PTR_ERR(devs); +- goto fail; +- } ++ num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) { + err = blk_crypto_start_using_key(devs[i], blk_key); + if (err) + break; + } +- kfree(devs); + if (err) { + fscrypt_err(inode, "error %d starting to use blk-crypto", err); + goto fail; +@@ -209,20 +191,21 @@ void fscrypt_destroy_inline_crypt_key(struct super_block *sb, + struct fscrypt_prepared_key *prep_key) + { + struct blk_crypto_key *blk_key = prep_key->blk_key; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + + if (!blk_key) + return; + +- /* Evict the key from all the filesystem's block devices. */ +- devs = fscrypt_get_devices(sb, &num_devs); +- if (!IS_ERR(devs)) { +- for (i = 0; i < num_devs; i++) +- blk_crypto_evict_key(devs[i], blk_key); +- kfree(devs); +- } ++ /* ++ * Evict the key from all the filesystem's block devices. ++ * This *must* be done before the key is freed. ++ */ ++ num_devs = fscrypt_get_devices(sb, devs); ++ for (i = 0; i < num_devs; i++) ++ blk_crypto_evict_key(devs[i], blk_key); ++ + kfree_sensitive(blk_key); + } + +diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c +index 7c8e05a81a0c63..9d1b5699f5bf1a 100644 +--- a/fs/f2fs/super.c ++++ b/fs/f2fs/super.c +@@ -3126,24 +3126,27 @@ static void f2fs_get_ino_and_lblk_bits(struct super_block *sb, + *lblk_bits_ret = 8 * sizeof(block_t); + } + +-static struct block_device **f2fs_get_devices(struct super_block *sb, +- unsigned int *num_devs) ++static unsigned int ++f2fs_get_devices(struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]) + { + struct f2fs_sb_info *sbi = F2FS_SB(sb); +- struct block_device **devs; ++ int ndevs; + int i; + +- if (!f2fs_is_multi_device(sbi)) +- return NULL; ++ static_assert(MAX_DEVICES <= FSCRYPT_MAX_DEVICES); + +- devs = kmalloc_array(sbi->s_ndevs, sizeof(*devs), GFP_KERNEL); +- if (!devs) +- return ERR_PTR(-ENOMEM); ++ if (!f2fs_is_multi_device(sbi)) { ++ devs[0] = sb->s_bdev; ++ return 1; ++ } ++ ndevs = sbi->s_ndevs; ++ if (WARN_ON_ONCE(ndevs > FSCRYPT_MAX_DEVICES)) ++ ndevs = FSCRYPT_MAX_DEVICES; + +- for (i = 0; i < sbi->s_ndevs; i++) ++ for (i = 0; i < ndevs; i++) + devs[i] = FDEV(i).bdev; +- *num_devs = sbi->s_ndevs; +- return devs; ++ return ndevs; + } + + static const struct fscrypt_operations f2fs_cryptops = { +diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h +index 4f5f8a65121328..fe842c9af9dcb8 100644 +--- a/include/linux/fscrypt.h ++++ b/include/linux/fscrypt.h +@@ -57,6 +57,9 @@ struct fscrypt_name { + /* Maximum value for the third parameter of fscrypt_operations.set_context(). */ + #define FSCRYPT_SET_CONTEXT_MAX_SIZE 40 + ++/* Maximum supported number of block devices per filesystem */ ++#define FSCRYPT_MAX_DEVICES 8 ++ + #ifdef CONFIG_FS_ENCRYPTION + + /* +@@ -161,21 +164,20 @@ struct fscrypt_operations { + int *ino_bits_ret, int *lblk_bits_ret); + + /* +- * Return an array of pointers to the block devices to which the +- * filesystem may write encrypted file contents, NULL if the filesystem +- * only has a single such block device, or an ERR_PTR() on error. ++ * Retrieve the list of block devices to which the filesystem may write ++ * encrypted file contents. + * +- * On successful non-NULL return, *num_devs is set to the number of +- * devices in the returned array. The caller must free the returned +- * array using kfree(). ++ * This writes the block_device pointers to @devs and returns the count ++ * (between 1 and FSCRYPT_MAX_DEVICES inclusively). + * + * If the filesystem can use multiple block devices (other than block + * devices that aren't used for encrypted file contents, such as + * external journal devices), and wants to support inline encryption, + * then it must implement this function. Otherwise it's not needed. + */ +- struct block_device **(*get_devices)(struct super_block *sb, +- unsigned int *num_devs); ++ unsigned int (*get_devices)( ++ struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]); + }; + + static inline struct fscrypt_info *fscrypt_get_info(const struct inode *inode) +-- +2.53.0 + diff --git a/queue-6.1/i40e-remove-read-access-to-debugfs-files.patch b/queue-6.1/i40e-remove-read-access-to-debugfs-files.patch new file mode 100644 index 0000000000..a4a7dfcacb --- /dev/null +++ b/queue-6.1/i40e-remove-read-access-to-debugfs-files.patch @@ -0,0 +1,288 @@ +From 11a4c956410a51d8317bdb2c6d10c7ff735103f0 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 21:21:11 +0000 +Subject: i40e: remove read access to debugfs files + +From: Jacob Keller + +[ Upstream commit 9fcdb1c3c4ba134434694c001dbff343f1ffa319 ] + +The 'command' and 'netdev_ops' debugfs files are a legacy debugging +interface supported by the i40e driver since its early days by commit +02e9c290814c ("i40e: debugfs interface"). + +Both of these debugfs files provide a read handler which is mostly useless, +and which is implemented with questionable logic. They both use a static +256 byte buffer which is initialized to the empty string. In the case of +the 'command' file this buffer is literally never used and simply wastes +space. In the case of the 'netdev_ops' file, the last command written is +saved here. + +On read, the files contents are presented as the name of the device +followed by a colon and then the contents of their respective static +buffer. For 'command' this will always be ": ". For 'netdev_ops', +this will be ": ". But note the buffer is +shared between all devices operated by this module. At best, it is mostly +meaningless information, and at worse it could be accessed simultaneously +as there doesn't appear to be any locking mechanism. + +We have also recently received multiple reports for both read functions +about their use of snprintf and potential overflow that could result in +reading arbitrary kernel memory. For the 'command' file, this is +definitely impossible, since the static buffer is always zero and never +written to. For the 'netdev_ops' file, it does appear to be possible, if +the user carefully crafts the command input, it will be copied into the +buffer, which could be large enough to cause snprintf to truncate, which +then causes the copy_to_user to read beyond the length of the buffer +allocated by kzalloc. + +A minimal fix would be to replace snprintf() with scnprintf() which would +cap the return to the number of bytes written, preventing an overflow. A +more involved fix would be to drop the mostly useless static buffers, +saving 512 bytes and modifying the read functions to stop needing those as +input. + +Instead, lets just completely drop the read access to these files. These +are debug interfaces exposed as part of debugfs, and I don't believe that +dropping read access will break any script, as the provided output is +pretty useless. You can find the netdev name through other more standard +interfaces, and the 'netdev_ops' interface can easily result in garbage if +you issue simultaneous writes to multiple devices at once. + +In order to properly remove the i40e_dbg_netdev_ops_buf, we need to +refactor its write function to avoid using the static buffer. Instead, use +the same logic as the i40e_dbg_command_write, with an allocated buffer. +Update the code to use this instead of the static buffer, and ensure we +free the buffer on exit. This fixes simultaneous writes to 'netdev_ops' on +multiple devices, and allows us to remove the now unused static buffer +along with removing the read access. + +Fixes: 02e9c290814c ("i40e: debugfs interface") +Reported-by: Kunwu Chan +Closes: https://lore.kernel.org/intel-wired-lan/20231208031950.47410-1-chentao@kylinos.cn/ +Reported-by: Wang Haoran +Closes: https://lore.kernel.org/all/CANZ3JQRRiOdtfQJoP9QM=6LS1Jto8PGBGw6y7-TL=BcnzHQn1Q@mail.gmail.com/ +Reported-by: Amir Mohammad Jahangirzad +Closes: https://lore.kernel.org/all/20250722115017.206969-1-a.jahangirzad@gmail.com/ +Signed-off-by: Jacob Keller +Reviewed-by: Dawid Osuchowski +Reviewed-by: Aleksandr Loktionov +Reviewed-by: Simon Horman +Reviewed-by: Kunwu Chan +Tested-by: Rinitha S (A Contingent worker at Intel) +Signed-off-by: Tony Nguyen +[Adapted to 6.1: context conflict due to 6.1 using pf->vsi[pf->lan_vsi] + vs i40e_pf_get_main_vsi(pf) in the removed read functions; resolution + is simply to delete the 6.1 version of those functions.] +Signed-off-by: Jay Wang +Signed-off-by: Sasha Levin +--- + .../net/ethernet/intel/i40e/i40e_debugfs.c | 121 +++--------------- + 1 file changed, 19 insertions(+), 102 deletions(-) + +diff --git a/drivers/net/ethernet/intel/i40e/i40e_debugfs.c b/drivers/net/ethernet/intel/i40e/i40e_debugfs.c +index b438cf846c41b2..5b59db6a5439cd 100644 +--- a/drivers/net/ethernet/intel/i40e/i40e_debugfs.c ++++ b/drivers/net/ethernet/intel/i40e/i40e_debugfs.c +@@ -57,47 +57,6 @@ static struct i40e_veb *i40e_dbg_find_veb(struct i40e_pf *pf, int seid) + * setup, adding or removing filters, or other things. Many of + * these will be useful for some forms of unit testing. + **************************************************************/ +-static char i40e_dbg_command_buf[256] = ""; +- +-/** +- * i40e_dbg_command_read - read for command datum +- * @filp: the opened file +- * @buffer: where to write the data for the user to read +- * @count: the size of the user's buffer +- * @ppos: file position offset +- **/ +-static ssize_t i40e_dbg_command_read(struct file *filp, char __user *buffer, +- size_t count, loff_t *ppos) +-{ +- struct i40e_pf *pf = filp->private_data; +- int bytes_not_copied; +- int buf_size = 256; +- char *buf; +- int len; +- +- /* don't allow partial reads */ +- if (*ppos != 0) +- return 0; +- if (count < buf_size) +- return -ENOSPC; +- +- buf = kzalloc(buf_size, GFP_KERNEL); +- if (!buf) +- return -ENOSPC; +- +- len = snprintf(buf, buf_size, "%s: %s\n", +- pf->vsi[pf->lan_vsi]->netdev->name, +- i40e_dbg_command_buf); +- +- bytes_not_copied = copy_to_user(buffer, buf, len); +- kfree(buf); +- +- if (bytes_not_copied) +- return -EFAULT; +- +- *ppos = len; +- return len; +-} + + static char *i40e_filter_state_string[] = { + "INVALID", +@@ -1636,7 +1595,6 @@ static ssize_t i40e_dbg_command_write(struct file *filp, + static const struct file_operations i40e_dbg_command_fops = { + .owner = THIS_MODULE, + .open = simple_open, +- .read = i40e_dbg_command_read, + .write = i40e_dbg_command_write, + }; + +@@ -1645,47 +1603,6 @@ static const struct file_operations i40e_dbg_command_fops = { + * The netdev_ops entry in debugfs is for giving the driver commands + * to be executed from the netdev operations. + **************************************************************/ +-static char i40e_dbg_netdev_ops_buf[256] = ""; +- +-/** +- * i40e_dbg_netdev_ops_read - read for netdev_ops datum +- * @filp: the opened file +- * @buffer: where to write the data for the user to read +- * @count: the size of the user's buffer +- * @ppos: file position offset +- **/ +-static ssize_t i40e_dbg_netdev_ops_read(struct file *filp, char __user *buffer, +- size_t count, loff_t *ppos) +-{ +- struct i40e_pf *pf = filp->private_data; +- int bytes_not_copied; +- int buf_size = 256; +- char *buf; +- int len; +- +- /* don't allow partal reads */ +- if (*ppos != 0) +- return 0; +- if (count < buf_size) +- return -ENOSPC; +- +- buf = kzalloc(buf_size, GFP_KERNEL); +- if (!buf) +- return -ENOSPC; +- +- len = snprintf(buf, buf_size, "%s: %s\n", +- pf->vsi[pf->lan_vsi]->netdev->name, +- i40e_dbg_netdev_ops_buf); +- +- bytes_not_copied = copy_to_user(buffer, buf, len); +- kfree(buf); +- +- if (bytes_not_copied) +- return -EFAULT; +- +- *ppos = len; +- return len; +-} + + /** + * i40e_dbg_netdev_ops_write - write into netdev_ops datum +@@ -1699,35 +1616,36 @@ static ssize_t i40e_dbg_netdev_ops_write(struct file *filp, + size_t count, loff_t *ppos) + { + struct i40e_pf *pf = filp->private_data; ++ char *cmd_buf, *buf_tmp; + int bytes_not_copied; + struct i40e_vsi *vsi; +- char *buf_tmp; + int vsi_seid; + int i, cnt; + + /* don't allow partial writes */ + if (*ppos != 0) + return 0; +- if (count >= sizeof(i40e_dbg_netdev_ops_buf)) +- return -ENOSPC; + +- memset(i40e_dbg_netdev_ops_buf, 0, sizeof(i40e_dbg_netdev_ops_buf)); +- bytes_not_copied = copy_from_user(i40e_dbg_netdev_ops_buf, +- buffer, count); +- if (bytes_not_copied) ++ cmd_buf = kzalloc(count + 1, GFP_KERNEL); ++ if (!cmd_buf) ++ return count; ++ bytes_not_copied = copy_from_user(cmd_buf, buffer, count); ++ if (bytes_not_copied) { ++ kfree(cmd_buf); + return -EFAULT; +- i40e_dbg_netdev_ops_buf[count] = '\0'; ++ } ++ cmd_buf[count] = '\0'; + +- buf_tmp = strchr(i40e_dbg_netdev_ops_buf, '\n'); ++ buf_tmp = strchr(cmd_buf, '\n'); + if (buf_tmp) { + *buf_tmp = '\0'; +- count = buf_tmp - i40e_dbg_netdev_ops_buf + 1; ++ count = buf_tmp - cmd_buf + 1; + } + +- if (strncmp(i40e_dbg_netdev_ops_buf, "change_mtu", 10) == 0) { ++ if (strncmp(cmd_buf, "change_mtu", 10) == 0) { + int mtu; + +- cnt = sscanf(&i40e_dbg_netdev_ops_buf[11], "%i %i", ++ cnt = sscanf(&cmd_buf[11], "%i %i", + &vsi_seid, &mtu); + if (cnt != 2) { + dev_info(&pf->pdev->dev, "change_mtu \n"); +@@ -1749,8 +1667,8 @@ static ssize_t i40e_dbg_netdev_ops_write(struct file *filp, + dev_info(&pf->pdev->dev, "Could not acquire RTNL - please try again\n"); + } + +- } else if (strncmp(i40e_dbg_netdev_ops_buf, "set_rx_mode", 11) == 0) { +- cnt = sscanf(&i40e_dbg_netdev_ops_buf[11], "%i", &vsi_seid); ++ } else if (strncmp(cmd_buf, "set_rx_mode", 11) == 0) { ++ cnt = sscanf(&cmd_buf[11], "%i", &vsi_seid); + if (cnt != 1) { + dev_info(&pf->pdev->dev, "set_rx_mode \n"); + goto netdev_ops_write_done; +@@ -1770,8 +1688,8 @@ static ssize_t i40e_dbg_netdev_ops_write(struct file *filp, + dev_info(&pf->pdev->dev, "Could not acquire RTNL - please try again\n"); + } + +- } else if (strncmp(i40e_dbg_netdev_ops_buf, "napi", 4) == 0) { +- cnt = sscanf(&i40e_dbg_netdev_ops_buf[4], "%i", &vsi_seid); ++ } else if (strncmp(cmd_buf, "napi", 4) == 0) { ++ cnt = sscanf(&cmd_buf[4], "%i", &vsi_seid); + if (cnt != 1) { + dev_info(&pf->pdev->dev, "napi \n"); + goto netdev_ops_write_done; +@@ -1789,21 +1707,20 @@ static ssize_t i40e_dbg_netdev_ops_write(struct file *filp, + dev_info(&pf->pdev->dev, "napi called\n"); + } + } else { +- dev_info(&pf->pdev->dev, "unknown command '%s'\n", +- i40e_dbg_netdev_ops_buf); ++ dev_info(&pf->pdev->dev, "unknown command '%s'\n", cmd_buf); + dev_info(&pf->pdev->dev, "available commands\n"); + dev_info(&pf->pdev->dev, " change_mtu \n"); + dev_info(&pf->pdev->dev, " set_rx_mode \n"); + dev_info(&pf->pdev->dev, " napi \n"); + } + netdev_ops_write_done: ++ kfree(cmd_buf); + return count; + } + + static const struct file_operations i40e_dbg_netdev_ops_fops = { + .owner = THIS_MODULE, + .open = simple_open, +- .read = i40e_dbg_netdev_ops_read, + .write = i40e_dbg_netdev_ops_write, + }; + +-- +2.53.0 + diff --git a/queue-6.1/ipv6-ndisc-fix-null-deref-in-accept_untracked_na.patch b/queue-6.1/ipv6-ndisc-fix-null-deref-in-accept_untracked_na.patch new file mode 100644 index 0000000000..137aaa3d1d --- /dev/null +++ b/queue-6.1/ipv6-ndisc-fix-null-deref-in-accept_untracked_na.patch @@ -0,0 +1,88 @@ +From 6b56124b745d9b091b88ee8d29fa815be08bacca Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 12:08:08 +0300 +Subject: ipv6: ndisc: fix NULL deref in accept_untracked_na() + +From: Weiming Shi + +commit d186e942365acece7c56d39da05dd63bf95b280a upstream. + +accept_untracked_na() re-fetches the inet6_dev with __in6_dev_get(dev) +and dereferences idev->cnf.accept_untracked_na without a NULL check, +even though its only caller ndisc_recv_na() already fetched and +NULL-checked idev for the same device. + +Both reads of dev->ip6_ptr run in the same RCU read-side critical +section, but a concurrent addrconf_ifdown() can clear dev->ip6_ptr +between them: lowering the MTU below IPV6_MIN_MTU calls addrconf_ifdown() +without the synchronize_net() that orders the unregister path, so the +re-fetch returns NULL and oopses: + + BUG: KASAN: null-ptr-deref in ndisc_recv_na (net/ipv6/ndisc.c:974) + Read of size 4 at addr 0000000000000364 + Call Trace: + + ndisc_recv_na (net/ipv6/ndisc.c:974) + icmpv6_rcv (net/ipv6/icmp.c:1193) + ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:479) + ip6_input_finish (net/ipv6/ip6_input.c:534) + ip6_input (net/ipv6/ip6_input.c:545) + ip6_mc_input (net/ipv6/ip6_input.c:635) + ipv6_rcv (net/ipv6/ip6_input.c:351) + + +It is reachable by an unprivileged user via a network namespace. + +Pass the caller's already validated idev instead of re-fetching it; the +idev stays alive for the whole RCU critical section, so it is safe even +after dev->ip6_ptr has been cleared. + +Fixes: aaa5f515b16b ("net: ipv6: new accept_untracked_na option to accept na only if in-network") +Reported-by: Xiang Mei +Signed-off-by: Weiming Shi +Reviewed-by: Jiayuan Chen +Link: https://patch.msgid.link/20260617065512.2529757-2-bestswngs@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Alexander Martyniuk +Signed-off-by: Sasha Levin +--- + net/ipv6/ndisc.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c +index f1c4c4dbefb0c9..85f7798d3e55c4 100644 +--- a/net/ipv6/ndisc.c ++++ b/net/ipv6/ndisc.c +@@ -972,10 +972,8 @@ static void ndisc_recv_ns(struct sk_buff *skb) + in6_dev_put(idev); + } + +-static int accept_untracked_na(struct net_device *dev, struct in6_addr *saddr) ++static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr) + { +- struct inet6_dev *idev = __in6_dev_get(dev); +- + switch (idev->cnf.accept_untracked_na) { + case 0: /* Don't accept untracked na (absent in neighbor cache) */ + return 0; +@@ -985,7 +983,7 @@ static int accept_untracked_na(struct net_device *dev, struct in6_addr *saddr) + * same subnet as an address configured on the interface that + * received the na + */ +- return !!ipv6_chk_prefix(saddr, dev); ++ return !!ipv6_chk_prefix(saddr, idev->dev); + default: + return 0; + } +@@ -1086,7 +1084,7 @@ static void ndisc_recv_na(struct sk_buff *skb) + */ + new_state = msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE; + if (!neigh && lladdr && idev && idev->cnf.forwarding) { +- if (accept_untracked_na(dev, saddr)) { ++ if (accept_untracked_na(idev, saddr)) { + neigh = neigh_create(&nd_tbl, &msg->target, dev); + new_state = NUD_STALE; + } +-- +2.53.0 + diff --git a/queue-6.1/openvswitch-fix-gso-userspace-truncation-underflow.patch b/queue-6.1/openvswitch-fix-gso-userspace-truncation-underflow.patch new file mode 100644 index 0000000000..61d2d91ac3 --- /dev/null +++ b/queue-6.1/openvswitch-fix-gso-userspace-truncation-underflow.patch @@ -0,0 +1,193 @@ +From 6284f2aba2f71a8e190a32193f09eee6d5550590 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 22:58:53 +0200 +Subject: openvswitch: fix GSO userspace truncation underflow + +From: Kyle Zeng + +[ Upstream commit 4032f8ed10fcb84d41c508dfb04be96589f78dfe ] + +OVS_ACTION_ATTR_TRUNC currently stores a delta from the original skb +length in OVS_CB(skb)->cutlen. When a later userspace action segments a +GSO skb, queue_gso_packets() reuses that delta for each smaller segment. +A segment can then reach queue_userspace_packet() with cutlen greater +than skb->len, underflowing the length passed to skb_zerocopy(). + +Store the maximum preserved length instead and bound each consumer +against the current skb length. Use U32_MAX as the no-truncation +sentinel so the value remains valid if skb geometry changes before a +consumer handles it. + +Fixes: f2a4d086ed4c ("openvswitch: Add packet truncation support.") +Cc: stable@vger.kernel.org +Assisted-by: Codex:gpt-5.5 +Signed-off-by: Kyle Zeng +Reviewed-by: Ilya Maximets +Reviewed-by: Aaron Conole +Link: https://patch.msgid.link/20260707221635.27489-1-kylebot@openai.com +Signed-off-by: Paolo Abeni +[6.1.y supports neither OVS_ACTION_ATTR_PSAMPLE nor OVS drop reasons] +Signed-off-by: Ilya Maximets +Signed-off-by: Sasha Levin +--- + net/openvswitch/actions.c | 15 +++++---------- + net/openvswitch/datapath.c | 25 ++++++++++++++----------- + net/openvswitch/datapath.h | 2 +- + net/openvswitch/vport.c | 2 +- + 4 files changed, 21 insertions(+), 23 deletions(-) + +diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c +index f7cc87e67d3c8c..18e419affb5727 100644 +--- a/net/openvswitch/actions.c ++++ b/net/openvswitch/actions.c +@@ -856,12 +856,8 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port, + u16 mru = OVS_CB(skb)->mru; + u32 cutlen = OVS_CB(skb)->cutlen; + +- if (unlikely(cutlen > 0)) { +- if (skb->len - cutlen > ovs_mac_header_len(key)) +- pskb_trim(skb, skb->len - cutlen); +- else +- pskb_trim(skb, ovs_mac_header_len(key)); +- } ++ if (unlikely(cutlen < skb->len)) ++ pskb_trim(skb, max(cutlen, ovs_mac_header_len(key))); + + if (likely(!mru || + (skb->len <= mru + vport->dev->hard_header_len))) { +@@ -1242,22 +1238,21 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, + clone = skb_clone(skb, GFP_ATOMIC); + if (clone) + do_output(dp, clone, port, key); +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + break; + } + + case OVS_ACTION_ATTR_TRUNC: { + struct ovs_action_trunc *trunc = nla_data(a); + +- if (skb->len > trunc->max_len) +- OVS_CB(skb)->cutlen = skb->len - trunc->max_len; ++ OVS_CB(skb)->cutlen = trunc->max_len; + break; + } + + case OVS_ACTION_ATTR_USERSPACE: + output_userspace(dp, skb, key, a, attr, + len, OVS_CB(skb)->cutlen); +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + break; + + case OVS_ACTION_ATTR_HASH: +diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c +index 0c0d89470145a1..1181807f2b5020 100644 +--- a/net/openvswitch/datapath.c ++++ b/net/openvswitch/datapath.c +@@ -251,7 +251,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) + upcall.portid = ovs_vport_find_upcall_portid(p, skb); + + upcall.mru = OVS_CB(skb)->mru; +- error = ovs_dp_upcall(dp, skb, key, &upcall, 0); ++ error = ovs_dp_upcall(dp, skb, key, &upcall, U32_MAX); + switch (error) { + case 0: + case -EAGAIN: +@@ -414,7 +414,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + struct sk_buff *nskb = NULL; + struct sk_buff *user_skb = NULL; /* to be queued to userspace */ + struct nlattr *nla; +- size_t len; ++ size_t msg_size; ++ size_t skb_len; + unsigned int hlen; + int err, dp_ifindex; + u64 hash; +@@ -435,7 +436,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + skb = nskb; + } + +- if (nla_attr_size(skb->len) > USHRT_MAX) { ++ skb_len = min(skb->len, cutlen); ++ if (nla_attr_size(skb_len) > USHRT_MAX) { + err = -EFBIG; + goto out; + } +@@ -450,13 +452,13 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + * padding logic. Only perform zerocopy if padding is not required. + */ + if (dp->user_features & OVS_DP_F_UNALIGNED) +- hlen = skb_zerocopy_headlen(skb); ++ hlen = min(skb_zerocopy_headlen(skb), cutlen); + else +- hlen = skb->len; ++ hlen = skb_len; + +- len = upcall_msg_size(upcall_info, hlen - cutlen, +- OVS_CB(skb)->acts_origlen); +- user_skb = genlmsg_new(len, GFP_ATOMIC); ++ msg_size = upcall_msg_size(upcall_info, hlen, ++ OVS_CB(skb)->acts_origlen); ++ user_skb = genlmsg_new(msg_size, GFP_ATOMIC); + if (!user_skb) { + err = -ENOMEM; + goto out; +@@ -517,7 +519,7 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + } + + /* Add OVS_PACKET_ATTR_LEN when packet is truncated */ +- if (cutlen > 0 && ++ if (skb_len < skb->len && + nla_put_u32(user_skb, OVS_PACKET_ATTR_LEN, skb->len)) { + err = -ENOBUFS; + goto out; +@@ -542,9 +544,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + err = -ENOBUFS; + goto out; + } +- nla->nla_len = nla_attr_size(skb->len - cutlen); ++ nla->nla_len = nla_attr_size(skb_len); + +- err = skb_zerocopy(user_skb, skb, skb->len - cutlen, hlen); ++ err = skb_zerocopy(user_skb, skb, skb_len, hlen); + if (err) + goto out; + +@@ -601,6 +603,7 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info) + packet->ignore_df = 1; + } + OVS_CB(packet)->mru = mru; ++ OVS_CB(packet)->cutlen = U32_MAX; + + if (a[OVS_PACKET_ATTR_HASH]) { + hash = nla_get_u64(a[OVS_PACKET_ATTR_HASH]); +diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h +index 0cd29971a907ca..88156a677f22c5 100644 +--- a/net/openvswitch/datapath.h ++++ b/net/openvswitch/datapath.h +@@ -114,7 +114,7 @@ struct datapath { + * @mru: The maximum received fragement size; 0 if the packet is not + * fragmented. + * @acts_origlen: The netlink size of the flow actions applied to this skb. +- * @cutlen: The number of bytes from the packet end to be removed. ++ * @cutlen: The number of bytes in the packet to preserve on output. + */ + struct ovs_skb_cb { + struct vport *input_vport; +diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c +index 5d7af559a20bed..e83aa6417ffde0 100644 +--- a/net/openvswitch/vport.c ++++ b/net/openvswitch/vport.c +@@ -438,7 +438,7 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, + + OVS_CB(skb)->input_vport = vport; + OVS_CB(skb)->mru = 0; +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) { + u32 mark; + +-- +2.53.0 + diff --git a/queue-6.1/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch b/queue-6.1/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch new file mode 100644 index 0000000000..370dcf1e22 --- /dev/null +++ b/queue-6.1/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch @@ -0,0 +1,287 @@ +From f4f4500df13d067b00911d2a51da6096ce02f97e Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 09:38:09 +0800 +Subject: RISC-V: KVM: Serialize virtual interrupt pending state updates + +From: Xie Bo + +commit d024a0a7879e6f37c0152aacf6d8e37b214a1738 upstream. + +KVM RISC-V tracks guest local interrupt state with two bitmaps: + + - irqs_pending: interrupts that should be visible to the guest + - irqs_pending_mask: interrupts whose pending state changed + +The current code updates those bitmaps with independent atomic bitops +and assumes a multiple-producer, single-consumer protocol. That model +does not actually hold. + +kvm_riscv_vcpu_sync_interrupts() is not a pure consumer. When the guest +changes guest-visible HVIP state, sync_interrupts() writes both +irqs_pending and irqs_pending_mask to reflect the new guest state back +into KVM state. As a result, irqs_pending and irqs_pending_mask form a +single logical state transition, but they are not updated atomically as +a pair. + +This allows a race where a newly injected interrupt is lost. For +example: + + CPU0 CPU1 + ---- ---- + kvm_riscv_vcpu_set_interrupt(VS_SOFT) + set_bit(VS_SOFT, irqs_pending) + kvm_riscv_vcpu_sync_interrupts() + sees guest-cleared HVIP.VSSIP + sets irqs_pending_mask + clear_bit(IRQ_VS_SOFT, irqs_pending) + set_bit(VS_SOFT, irqs_pending_mask) + kvm_vcpu_kick() + +After that interleaving, a later flush can update HVIP without VSSIP +even though a new virtual interrupt was injected. In practice, the +guest can remain blocked in WFI with work pending. + +The same pending/mask protocol is shared by VS soft interrupts, PMU +overflow delivery, and AIA high interrupt synchronization, so the race +is not limited to one interrupt source. + +Fix this by serializing all updates to irqs_pending and irqs_pending_mask +with a per-vCPU raw spinlock. This keeps the pending bit and the dirty +mask as one state transition across: + + - set/unset interrupt + - guest HVIP sync + - interrupt flush to guest CSR state + - vCPU reset + - AIA CSR writes that clear dirty state + +Use non-atomic bitmap operations while holding the lock. Hold the lock +across the AIA sync, flush, and pending checks as well, so both bitmap +words share the same serialization domain. + +This intentionally replaces the existing lockless protocol instead of +trying to repair it with additional barriers. The problem is not memory +ordering on a single field; it is that two separate bitmaps encode one +shared state machine while both producers and sync paths can modify +them. A per-vCPU raw spinlock keeps the fix small, local, and suitable +for backporting. + +Fixes: cce69aff689e ("RISC-V: KVM: Implement VCPU interrupts and requests handling") +Cc: stable@vger.kernel.org +Signed-off-by: Xie Bo +Reviewed-by: Anup Patel +Link: https://lore.kernel.org/r/20260715020359.1521354-2-xb@ultrarisc.com +Signed-off-by: Anup Patel + +[ bo: Adapt to the scalar interrupt state in 6.1.y and its CSR one-reg + helpers in vcpu.c. Drop AIA and PMU overflow handling, which are not + present in this tree. ] + +Signed-off-by: Sasha Levin +--- + arch/riscv/include/asm/kvm_host.h | 10 ++-- + arch/riscv/kvm/vcpu.c | 78 ++++++++++++++++++++++--------- + 2 files changed, 60 insertions(+), 28 deletions(-) + +diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h +index dbbf43d5262348..dc5e553e136992 100644 +--- a/arch/riscv/include/asm/kvm_host.h ++++ b/arch/riscv/include/asm/kvm_host.h +@@ -192,12 +192,12 @@ struct kvm_vcpu_arch { + /* + * VCPU interrupts + * +- * We have a lockless approach for tracking pending VCPU interrupts +- * implemented using atomic bitops. The irqs_pending bitmap represent +- * pending interrupts whereas irqs_pending_mask represent bits changed +- * in irqs_pending. Our approach is modeled around multiple producer +- * and single consumer problem where the consumer is the VCPU itself. ++ * The irqs_pending field represents pending interrupts whereas ++ * irqs_pending_mask represents bits changed in irqs_pending. Updates ++ * to these fields are serialized so vcpu interrupt sync/flush cannot ++ * drop a newly injected interrupt while syncing guest-visible HVIP. + */ ++ raw_spinlock_t irqs_pending_lock; + unsigned long irqs_pending; + unsigned long irqs_pending_mask; + +diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c +index 5174ef54ad1d9e..c6afbf0a9916fb 100644 +--- a/arch/riscv/kvm/vcpu.c ++++ b/arch/riscv/kvm/vcpu.c +@@ -112,6 +112,7 @@ static void kvm_riscv_reset_vcpu(struct kvm_vcpu *vcpu) + struct kvm_vcpu_csr *reset_csr = &vcpu->arch.guest_reset_csr; + struct kvm_cpu_context *cntx = &vcpu->arch.guest_context; + struct kvm_cpu_context *reset_cntx = &vcpu->arch.guest_reset_context; ++ unsigned long flags; + bool loaded; + + /** +@@ -134,8 +135,10 @@ static void kvm_riscv_reset_vcpu(struct kvm_vcpu *vcpu) + + kvm_riscv_vcpu_timer_reset(vcpu); + +- WRITE_ONCE(vcpu->arch.irqs_pending, 0); +- WRITE_ONCE(vcpu->arch.irqs_pending_mask, 0); ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ vcpu->arch.irqs_pending = 0; ++ vcpu->arch.irqs_pending_mask = 0; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + vcpu->arch.hfence_head = 0; + vcpu->arch.hfence_tail = 0; +@@ -173,6 +176,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) + + /* Setup VCPU hfence queue */ + spin_lock_init(&vcpu->arch.hfence_lock); ++ raw_spin_lock_init(&vcpu->arch.irqs_pending_lock); + + /* Setup reset state of shadow SSTATUS and HSTATUS CSRs */ + cntx = &vcpu->arch.guest_reset_context; +@@ -444,7 +448,7 @@ static int kvm_riscv_vcpu_set_reg_csr(struct kvm_vcpu *vcpu, + unsigned long reg_num = reg->id & ~(KVM_REG_ARCH_MASK | + KVM_REG_SIZE_MASK | + KVM_REG_RISCV_CSR); +- unsigned long reg_val; ++ unsigned long flags, reg_val; + + if (KVM_REG_SIZE(reg->id) != sizeof(unsigned long)) + return -EINVAL; +@@ -461,8 +465,11 @@ static int kvm_riscv_vcpu_set_reg_csr(struct kvm_vcpu *vcpu, + + ((unsigned long *)csr)[reg_num] = reg_val; + +- if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) +- WRITE_ONCE(vcpu->arch.irqs_pending_mask, 0); ++ if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ vcpu->arch.irqs_pending_mask = 0; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); ++ } + + return 0; + } +@@ -679,19 +686,26 @@ void kvm_riscv_vcpu_flush_interrupts(struct kvm_vcpu *vcpu) + { + struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr; + unsigned long mask, val; ++ unsigned long flags; + +- if (READ_ONCE(vcpu->arch.irqs_pending_mask)) { +- mask = xchg_acquire(&vcpu->arch.irqs_pending_mask, 0); +- val = READ_ONCE(vcpu->arch.irqs_pending) & mask; ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ ++ mask = vcpu->arch.irqs_pending_mask; ++ if (mask) { ++ vcpu->arch.irqs_pending_mask = 0; ++ val = vcpu->arch.irqs_pending & mask; + + csr->hvip &= ~mask; + csr->hvip |= val; + } ++ ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + } + + void kvm_riscv_vcpu_sync_interrupts(struct kvm_vcpu *vcpu) + { + unsigned long hvip; ++ unsigned long flags; + struct kvm_vcpu_arch *v = &vcpu->arch; + struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr; + +@@ -700,32 +714,40 @@ void kvm_riscv_vcpu_sync_interrupts(struct kvm_vcpu *vcpu) + + /* Sync-up HVIP.VSSIP bit changes does by Guest */ + hvip = csr_read(CSR_HVIP); ++ ++ raw_spin_lock_irqsave(&v->irqs_pending_lock, flags); ++ + if ((csr->hvip ^ hvip) & (1UL << IRQ_VS_SOFT)) { + if (hvip & (1UL << IRQ_VS_SOFT)) { +- if (!test_and_set_bit(IRQ_VS_SOFT, +- &v->irqs_pending_mask)) +- set_bit(IRQ_VS_SOFT, &v->irqs_pending); ++ if (!__test_and_set_bit(IRQ_VS_SOFT, ++ &v->irqs_pending_mask)) ++ __set_bit(IRQ_VS_SOFT, &v->irqs_pending); + } else { +- if (!test_and_set_bit(IRQ_VS_SOFT, +- &v->irqs_pending_mask)) +- clear_bit(IRQ_VS_SOFT, &v->irqs_pending); ++ if (!__test_and_set_bit(IRQ_VS_SOFT, ++ &v->irqs_pending_mask)) ++ __clear_bit(IRQ_VS_SOFT, &v->irqs_pending); + } + } + ++ raw_spin_unlock_irqrestore(&v->irqs_pending_lock, flags); ++ + /* Sync-up timer CSRs */ + kvm_riscv_vcpu_timer_sync(vcpu); + } + + int kvm_riscv_vcpu_set_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + { ++ unsigned long flags; ++ + if (irq != IRQ_VS_SOFT && + irq != IRQ_VS_TIMER && + irq != IRQ_VS_EXT) + return -EINVAL; + +- set_bit(irq, &vcpu->arch.irqs_pending); +- smp_mb__before_atomic(); +- set_bit(irq, &vcpu->arch.irqs_pending_mask); ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ __set_bit(irq, &vcpu->arch.irqs_pending); ++ __set_bit(irq, &vcpu->arch.irqs_pending_mask); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + kvm_vcpu_kick(vcpu); + +@@ -734,24 +756,34 @@ int kvm_riscv_vcpu_set_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + + int kvm_riscv_vcpu_unset_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + { ++ unsigned long flags; ++ + if (irq != IRQ_VS_SOFT && + irq != IRQ_VS_TIMER && + irq != IRQ_VS_EXT) + return -EINVAL; + +- clear_bit(irq, &vcpu->arch.irqs_pending); +- smp_mb__before_atomic(); +- set_bit(irq, &vcpu->arch.irqs_pending_mask); ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ __clear_bit(irq, &vcpu->arch.irqs_pending); ++ __set_bit(irq, &vcpu->arch.irqs_pending_mask); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + return 0; + } + + bool kvm_riscv_vcpu_has_interrupts(struct kvm_vcpu *vcpu, unsigned long mask) + { +- unsigned long ie = ((vcpu->arch.guest_csr.vsie & VSIP_VALID_MASK) +- << VSIP_TO_HVIP_SHIFT) & mask; ++ unsigned long flags; ++ unsigned long ie; ++ bool ret; ++ ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ ie = ((vcpu->arch.guest_csr.vsie & VSIP_VALID_MASK) ++ << VSIP_TO_HVIP_SHIFT) & mask; ++ ret = vcpu->arch.irqs_pending & ie; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + +- return (READ_ONCE(vcpu->arch.irqs_pending) & ie) ? true : false; ++ return ret; + } + + void kvm_riscv_vcpu_power_off(struct kvm_vcpu *vcpu) +-- +2.53.0 + diff --git a/queue-6.1/series b/queue-6.1/series index e2e0c4a7b5..4e274c9486 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -285,3 +285,11 @@ drm-amdgpu-gfx9-replace-bug_on-with-warn_on.patch drm-amdgpu-vce-fix-integer-overflow-in-image-size.patch drm-amdgpu-fix-division-by-zero-with-invalid-uvd-dimensions.patch drm-amdgpu-invoke-pm_genpd_remove-before-freeing-genpd.patch +risc-v-kvm-serialize-virtual-interrupt-pending-state.patch +i40e-remove-read-access-to-debugfs-files.patch +ipv6-ndisc-fix-null-deref-in-accept_untracked_na.patch +tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch +openvswitch-fix-gso-userspace-truncation-underflow.patch +fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch +exfat-validate-cluster-allocation-bits-of-the-alloca.patch +bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch diff --git a/queue-6.1/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch b/queue-6.1/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch new file mode 100644 index 0000000000..00fc48b96b --- /dev/null +++ b/queue-6.1/tipc-fix-use-after-free-of-the-discoverer-in-tipc_di.patch @@ -0,0 +1,124 @@ +From 8d8ea431d77715f6baa5166db1ce2581be317524 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 15:16:03 +0300 +Subject: tipc: fix use-after-free of the discoverer in tipc_disc_rcv() + +From: Weiming Shi + +commit 1579342d71133da7f00daa02c75cebec7372097b upstream. + +bearer_disable() frees b->disc with tipc_disc_delete()'s plain kfree(), +but tipc_disc_rcv() still dereferences b->disc in RX softirq under +rcu_read_lock() (tipc_udp_recv -> tipc_rcv -> tipc_disc_rcv). + +L2 bearers are safe thanks to the synchronize_net() in +tipc_disable_l2_media(), but the UDP bearer defers that call to the +cleanup_bearer() workqueue, so the discoverer is freed with no grace +period: + + BUG: KASAN: slab-use-after-free in tipc_disc_rcv (net/tipc/discover.c:149) + Read of size 8 at addr ffff88802348b728 by task poc_tipc/184 + + tipc_disc_rcv (net/tipc/discover.c:149) + tipc_rcv (net/tipc/node.c:2126) + tipc_udp_recv (net/tipc/udp_media.c:391) + udp_rcv (net/ipv4/udp.c:2643) + ip_local_deliver_finish (net/ipv4/ip_input.c:241) + + Freed by task 181: + kfree (mm/slub.c:6565) + bearer_disable (net/tipc/bearer.c:418) + tipc_nl_bearer_disable (net/tipc/bearer.c:1001) + +The bearer is freed with kfree_rcu(); free the discoverer the same way. +Add an rcu_head to struct tipc_discoverer and free it and its skb from an +RCU callback. + +Because the RCU callback (tipc_disc_free_rcu) lives in module text, a +call_rcu() that is still pending when the tipc module is unloaded would +invoke a freed function. Add an rcu_barrier() to tipc_exit() after the +bearer subsystem has been torn down, so all pending discoverer callbacks +have run before the module text goes away. + +Reachable from an unprivileged user namespace: the TIPCv2 genl family is +netnsok and its bearer commands have no GENL_ADMIN_PERM. Needs CONFIG_TIPC +and CONFIG_TIPC_MEDIA_UDP. + +Fixes: 25b0b9c4e835 ("tipc: handle collisions of 32-bit node address hash values") +Reported-by: Xiang Mei +Signed-off-by: Weiming Shi +Reviewed-by: Tung Nguyen +Link: https://patch.msgid.link/20260617135744.3383175-3-bestswngs@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Alexander Martyniuk +Signed-off-by: Sasha Levin +--- + net/tipc/core.c | 5 +++++ + net/tipc/discover.c | 14 ++++++++++++-- + 2 files changed, 17 insertions(+), 2 deletions(-) + +diff --git a/net/tipc/core.c b/net/tipc/core.c +index 434e70eabe0812..1ddecea1df6e91 100644 +--- a/net/tipc/core.c ++++ b/net/tipc/core.c +@@ -218,6 +218,11 @@ static void __exit tipc_exit(void) + unregister_pernet_device(&tipc_net_ops); + tipc_unregister_sysctl(); + ++ /* TODO: Wait for all timers that called call_rcu() to finish before ++ * calling rcu_barrier(). ++ */ ++ rcu_barrier(); ++ + pr_info("Deactivated\n"); + } + +diff --git a/net/tipc/discover.c b/net/tipc/discover.c +index e8dcdf267c0c3f..835ff27f8ea88c 100644 +--- a/net/tipc/discover.c ++++ b/net/tipc/discover.c +@@ -58,6 +58,7 @@ + * @skb: request message to be (repeatedly) sent + * @timer: timer governing period between requests + * @timer_intv: current interval between requests (in ms) ++ * @rcu: RCU head for deferred freeing + */ + struct tipc_discoverer { + u32 bearer_id; +@@ -69,6 +70,7 @@ struct tipc_discoverer { + struct sk_buff *skb; + struct timer_list timer; + unsigned long timer_intv; ++ struct rcu_head rcu; + }; + + /** +@@ -382,6 +384,15 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b, + return 0; + } + ++static void tipc_disc_free_rcu(struct rcu_head *rp) ++{ ++ struct tipc_discoverer *d = container_of(rp, struct tipc_discoverer, ++ rcu); ++ ++ kfree_skb(d->skb); ++ kfree(d); ++} ++ + /** + * tipc_disc_delete - destroy object sending periodic link setup requests + * @d: ptr to link dest structure +@@ -389,8 +400,7 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b, + void tipc_disc_delete(struct tipc_discoverer *d) + { + del_timer_sync(&d->timer); +- kfree_skb(d->skb); +- kfree(d); ++ call_rcu(&d->rcu, tipc_disc_free_rcu); + } + + /** +-- +2.53.0 + diff --git a/queue-6.12/bpf-fix-same-register-dst-src-oob-read-and-pointer-l.patch b/queue-6.12/bpf-fix-same-register-dst-src-oob-read-and-pointer-l.patch new file mode 100644 index 0000000000..39c82c461a --- /dev/null +++ b/queue-6.12/bpf-fix-same-register-dst-src-oob-read-and-pointer-l.patch @@ -0,0 +1,86 @@ +From ea0e1a87bc601b21d6366ff7d41e3132d11c4944 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 20:57:44 +0000 +Subject: bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops + +From: Jiayuan Chen + +[ Upstream commit 10f86a2a5c91fc4c4d001960f1c21abe52545ef6 ] + +When a BPF sock_ops program accesses ctx fields with dst_reg == src_reg, +the SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD() macros fail to zero the +destination register in the !fullsock / !locked_tcp_sock path. + +Both macros borrow a temporary register to check is_fullsock / +is_locked_tcp_sock when dst_reg == src_reg, because dst_reg holds the +ctx pointer. When the check is false (e.g., TCP_NEW_SYN_RECV state with +a request_sock), dst_reg should be zeroed but is not, leaving the stale +ctx pointer: + + - SOCK_OPS_GET_SK: dst_reg retains the ctx pointer, passes NULL checks + as PTR_TO_SOCKET_OR_NULL, and can be used as a bogus socket pointer, + leading to stack-out-of-bounds access in helpers like + bpf_skc_to_tcp6_sock(). + + - SOCK_OPS_GET_FIELD: dst_reg retains the ctx pointer which the + verifier believes is a SCALAR_VALUE, leaking a kernel pointer. + +Fix both macros by: + - Changing JMP_A(1) to JMP_A(2) in the fullsock path to skip the + added instruction. + - Adding BPF_MOV64_IMM(si->dst_reg, 0) after the temp register + restore in the !fullsock path, placed after the restore because + dst_reg == src_reg means we need src_reg intact to read ctx->temp. + +Fixes: fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case") +Fixes: 84f44df664e9 ("bpf: sock_ops sk access may stomp registers when dst_reg = src_reg") +Reported-by: Quan Sun <2022090917019@std.uestc.edu.cn> +Reported-by: Yinhao Hu +Reported-by: Kaiyan Mei +Reported-by: Dongliang Mu +Reviewed-by: Emil Tsalapatis +Closes: https://lore.kernel.org/bpf/6fe1243e-149b-4d3b-99c7-fcc9e2f75787@std.uestc.edu.cn/T/#u +Signed-off-by: Jiayuan Chen +Acked-by: Martin KaFai Lau +Link: https://patch.msgid.link/20260407022720.162151-2-jiayuan.chen@linux.dev +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Jay Wang +Signed-off-by: Sasha Levin +--- + net/core/filter.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/net/core/filter.c b/net/core/filter.c +index f54e7152d68798..90e396423eb7af 100644 +--- a/net/core/filter.c ++++ b/net/core/filter.c +@@ -10483,10 +10483,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type, + si->dst_reg, si->dst_reg, \ + offsetof(OBJ, OBJ_FIELD)); \ + if (si->dst_reg == si->src_reg) { \ +- *insn++ = BPF_JMP_A(1); \ ++ *insn++ = BPF_JMP_A(2); \ + *insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \ + offsetof(struct bpf_sock_ops_kern, \ + temp)); \ ++ *insn++ = BPF_MOV64_IMM(si->dst_reg, 0); \ + } \ + } while (0) + +@@ -10520,10 +10521,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type, + si->dst_reg, si->src_reg, \ + offsetof(struct bpf_sock_ops_kern, sk));\ + if (si->dst_reg == si->src_reg) { \ +- *insn++ = BPF_JMP_A(1); \ ++ *insn++ = BPF_JMP_A(2); \ + *insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \ + offsetof(struct bpf_sock_ops_kern, \ + temp)); \ ++ *insn++ = BPF_MOV64_IMM(si->dst_reg, 0); \ + } \ + } while (0) + +-- +2.53.0 + diff --git a/queue-6.12/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch b/queue-6.12/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch new file mode 100644 index 0000000000..720945e3cf --- /dev/null +++ b/queue-6.12/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch @@ -0,0 +1,107 @@ +From f3abd7090f53979185c1aa4dcc6a1f5b5d55c098 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 23:33:40 -0700 +Subject: drm/amd/display: Fix DTB DTO updates breaking live pixel rate sources + +From: Harry Wentland + +commit 76a2db58e95e328007043f54ac3c7336ccbee440 upstream. + +dcn32_update_clocks_update_dtb_dto() and its dcn35 counterpart reprogram +the DTB DTO of every timing generator in the context whenever the DTBCLK +reference changes, passing a zeroed pixel rate and never setting +is_hdmi. Both dccg set_dtbclk_dto() implementations treat a zero pixel +rate as a disable request. On dcn32 that branch drives PIPE_DTO_SRC_SEL +to the DP DTO source, so a timing generator actively scanning out an +HDMI stream has its pixel rate source re-muxed out from under the live +raster and the OTG stops on the spot. On dcn35 it clears +DTBCLK_DTO_ENABLE and restores DTBCLK_Pn clock gating, which does the +same to a live 128b/132b stream. + +Two displays where only one runs a 128b/132b link hit this reliably. +is_dtbclk_required() holds the DTBCLK reference high while both are +active, and the moment the 128b/132b stream is torn down (compositor +switch, display disable, hot-unplug) the next safe_to_lower pass drops +the reference to the lowest DPM level and the DTO walk freezes the +surviving screen. On Navi31 the DAL mailbox then goes deaf on the +DISPCLK hard-min that follows the walk in dcn32_update_clocks(), +stranding both SMU mailboxes until reboot. + +Set is_hdmi for HDMI and DVI signals so the disable path leaves the +pixel rate source selection on the HDMI path, and pass the real pixel +rate for 128b/132b streams so a reference change rescales their DTO +instead of disabling it. + +Fixes: 128c1ca0303f ("drm/amd/display: Update DTBCLK for DCN32") +Fixes: 8774029f76b9 ("drm/amd/display: Add DCN35 CLK_MGR") +Signed-off-by: Harry Wentland +Reviewed-by: Fangzhi Zuo +Tested-by: Dan Wheeler +Signed-off-by: Alex Deucher +[ mschwartz: dcn32 and dcn35 clk_mgr hunks only. The rest is HDMI FRL + enablement, absent before 7.2, so the FRL conditions and the + req_audio_dtbclk_khz assignment they guard are dropped and the + FRL-centric changelog is rewritten. Added the pipe_ctx->stream check + the new dereferences need. ] +Signed-off-by: Matthew Schwartz +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c | 9 ++++++++- + .../gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c | 9 ++++++++- + 2 files changed, 16 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c +index 084994c650c4c9..285689d7ecc4ea 100644 +--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c ++++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c +@@ -276,13 +276,20 @@ static void dcn32_update_clocks_update_dtb_dto(struct clk_mgr_internal *clk_mgr, + struct dtbclk_dto_params dto_params = {0}; + + /* use mask to program DTO once per tg */ +- if (pipe_ctx->stream_res.tg && ++ if (pipe_ctx->stream && pipe_ctx->stream_res.tg && + !(tg_mask & (1 << pipe_ctx->stream_res.tg->inst))) { + tg_mask |= (1 << pipe_ctx->stream_res.tg->inst); + + dto_params.otg_inst = pipe_ctx->stream_res.tg->inst; + dto_params.ref_dtbclk_khz = ref_dtbclk_khz; + ++ if (dccg->ctx->dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) ++ dto_params.pixclk_khz = pipe_ctx->stream->timing.pix_clk_100hz / 10; ++ ++ if (dc_is_hdmi_signal(pipe_ctx->stream->signal) || ++ dc_is_dvi_signal(pipe_ctx->stream->signal)) ++ dto_params.is_hdmi = true; ++ + dccg->funcs->set_dtbclk_dto(clk_mgr->dccg, &dto_params); + //dccg->funcs->set_audio_dtbclk_dto(clk_mgr->dccg, &dto_params); + } +diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c +index 33ee7e81b1b939..05e4dbc7fb8398 100644 +--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c ++++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c +@@ -247,13 +247,20 @@ static void dcn35_update_clocks_update_dtb_dto(struct clk_mgr_internal *clk_mgr, + struct dtbclk_dto_params dto_params = {0}; + + /* use mask to program DTO once per tg */ +- if (pipe_ctx->stream_res.tg && ++ if (pipe_ctx->stream && pipe_ctx->stream_res.tg && + !(tg_mask & (1 << pipe_ctx->stream_res.tg->inst))) { + tg_mask |= (1 << pipe_ctx->stream_res.tg->inst); + + dto_params.otg_inst = pipe_ctx->stream_res.tg->inst; + dto_params.ref_dtbclk_khz = ref_dtbclk_khz; + ++ if (dccg->ctx->dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) ++ dto_params.pixclk_khz = pipe_ctx->stream->timing.pix_clk_100hz / 10; ++ ++ if (dc_is_hdmi_signal(pipe_ctx->stream->signal) || ++ dc_is_dvi_signal(pipe_ctx->stream->signal)) ++ dto_params.is_hdmi = true; ++ + dccg->funcs->set_dtbclk_dto(clk_mgr->dccg, &dto_params); + //dccg->funcs->set_audio_dtbclk_dto(clk_mgr->dccg, &dto_params); + } +-- +2.53.0 + diff --git a/queue-6.12/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch b/queue-6.12/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch new file mode 100644 index 0000000000..22959d22f1 --- /dev/null +++ b/queue-6.12/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch @@ -0,0 +1,261 @@ +From c370485aebc4396b217143cdbeecdc4bc06b5c51 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 08:21:27 -0700 +Subject: fscrypt: Avoid dynamic allocation in fscrypt_get_devices() + +From: Eric Biggers + +commit 6fe4e4b8259e1330945b5f3c9476e08473b8e0e8 upstream. + +When a blk_crypto_key starts being used or is evicted, fs/crypto/ calls +fscrypt_get_devices() to get the filesystem's list of block devices, +then iterates over them and calls blk_crypto_config_supported(), +blk_crypto_start_using_key(), or blk_crypto_evict_key() on each one. + +Currently, the block device pointers are placed in a dynamically +allocated array. This dynamic allocation is problematic because: + +- It can fail, especially at the fscrypt_destroy_inline_crypt_key() call + site when it's invoked for inode eviction under direct reclaim. + +- fscrypt_destroy_inline_crypt_key() doesn't handle the failure. It + just zeroizes and frees the blk_crypto_key without calling + blk_crypto_evict_key(). That causes a use-after-free. + +For now, let's fix this in the straightforward and easily-backportable +way by switching to an on-stack array. Currently the fscrypt +multi-device functionality is used only by f2fs, which has a hardcoded +limit of 8 block devices. An on-stack array works fine for that. + +(Of course, this solution won't scale up to large number of block +devices. For that we'd need a different solution, like moving the block +device iteration into the filesystem. Or in the case of btrfs, which +will only support blk-crypto-fallback, we should make it just call +blk-crypto-fallback directly, so the block devices won't be needed.) + +Fixes: 22e9947a4b2b ("fscrypt: stop holding extra request_queue references") +Cc: stable@vger.kernel.org +Reported-by: Sashiko +Closes: https://sashiko.dev/#/patchset/20260713023708.9245-1-ebiggers%40kernel.org +Reviewed-by: Christoph Hellwig +Link: https://patch.msgid.link/20260719055602.78828-1-ebiggers@kernel.org +Signed-off-by: Eric Biggers +Signed-off-by: Sasha Levin +--- + fs/crypto/inline_crypt.c | 57 ++++++++++++++-------------------------- + fs/f2fs/super.c | 25 ++++++++++-------- + include/linux/fscrypt.h | 18 +++++++------ + 3 files changed, 44 insertions(+), 56 deletions(-) + +diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c +index 4b1f10f1cbd85e..0a216d20e6950d 100644 +--- a/fs/crypto/inline_crypt.c ++++ b/fs/crypto/inline_crypt.c +@@ -21,22 +21,14 @@ + + #include "fscrypt_private.h" + +-static struct block_device **fscrypt_get_devices(struct super_block *sb, +- unsigned int *num_devs) ++static unsigned int ++fscrypt_get_devices(struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]) + { +- struct block_device **devs; +- +- if (sb->s_cop->get_devices) { +- devs = sb->s_cop->get_devices(sb, num_devs); +- if (devs) +- return devs; +- } +- devs = kmalloc(sizeof(*devs), GFP_KERNEL); +- if (!devs) +- return ERR_PTR(-ENOMEM); ++ if (sb->s_cop->get_devices) ++ return sb->s_cop->get_devices(sb, devs); + devs[0] = sb->s_bdev; +- *num_devs = 1; +- return devs; ++ return 1; + } + + static unsigned int fscrypt_get_dun_bytes(const struct fscrypt_inode_info *ci) +@@ -94,7 +86,7 @@ int fscrypt_select_encryption_impl(struct fscrypt_inode_info *ci) + const struct inode *inode = ci->ci_inode; + struct super_block *sb = inode->i_sb; + struct blk_crypto_config crypto_cfg; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + +@@ -131,20 +123,15 @@ int fscrypt_select_encryption_impl(struct fscrypt_inode_info *ci) + crypto_cfg.data_unit_size = 1U << ci->ci_data_unit_bits; + crypto_cfg.dun_bytes = fscrypt_get_dun_bytes(ci); + +- devs = fscrypt_get_devices(sb, &num_devs); +- if (IS_ERR(devs)) +- return PTR_ERR(devs); +- ++ num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) { + if (!blk_crypto_config_supported(devs[i], &crypto_cfg)) +- goto out_free_devs; ++ return 0; + } + + fscrypt_log_blk_crypto_impl(ci->ci_mode, devs, num_devs, &crypto_cfg); + + ci->ci_inlinecrypt = true; +-out_free_devs: +- kfree(devs); + + return 0; + } +@@ -157,7 +144,7 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, + struct super_block *sb = inode->i_sb; + enum blk_crypto_mode_num crypto_mode = ci->ci_mode->blk_crypto_mode; + struct blk_crypto_key *blk_key; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + int err; +@@ -175,17 +162,12 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, + } + + /* Start using blk-crypto on all the filesystem's block devices. */ +- devs = fscrypt_get_devices(sb, &num_devs); +- if (IS_ERR(devs)) { +- err = PTR_ERR(devs); +- goto fail; +- } ++ num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) { + err = blk_crypto_start_using_key(devs[i], blk_key); + if (err) + break; + } +- kfree(devs); + if (err) { + fscrypt_err(inode, "error %d starting to use blk-crypto", err); + goto fail; +@@ -203,20 +185,21 @@ void fscrypt_destroy_inline_crypt_key(struct super_block *sb, + struct fscrypt_prepared_key *prep_key) + { + struct blk_crypto_key *blk_key = prep_key->blk_key; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + + if (!blk_key) + return; + +- /* Evict the key from all the filesystem's block devices. */ +- devs = fscrypt_get_devices(sb, &num_devs); +- if (!IS_ERR(devs)) { +- for (i = 0; i < num_devs; i++) +- blk_crypto_evict_key(devs[i], blk_key); +- kfree(devs); +- } ++ /* ++ * Evict the key from all the filesystem's block devices. ++ * This *must* be done before the key is freed. ++ */ ++ num_devs = fscrypt_get_devices(sb, devs); ++ for (i = 0; i < num_devs; i++) ++ blk_crypto_evict_key(devs[i], blk_key); ++ + kfree_sensitive(blk_key); + } + +diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c +index 1bce35d6f4e253..8ceffecf28c80c 100644 +--- a/fs/f2fs/super.c ++++ b/fs/f2fs/super.c +@@ -3282,24 +3282,27 @@ static bool f2fs_has_stable_inodes(struct super_block *sb) + return true; + } + +-static struct block_device **f2fs_get_devices(struct super_block *sb, +- unsigned int *num_devs) ++static unsigned int ++f2fs_get_devices(struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]) + { + struct f2fs_sb_info *sbi = F2FS_SB(sb); +- struct block_device **devs; ++ int ndevs; + int i; + +- if (!f2fs_is_multi_device(sbi)) +- return NULL; ++ static_assert(MAX_DEVICES <= FSCRYPT_MAX_DEVICES); + +- devs = kmalloc_array(sbi->s_ndevs, sizeof(*devs), GFP_KERNEL); +- if (!devs) +- return ERR_PTR(-ENOMEM); ++ if (!f2fs_is_multi_device(sbi)) { ++ devs[0] = sb->s_bdev; ++ return 1; ++ } ++ ndevs = sbi->s_ndevs; ++ if (WARN_ON_ONCE(ndevs > FSCRYPT_MAX_DEVICES)) ++ ndevs = FSCRYPT_MAX_DEVICES; + +- for (i = 0; i < sbi->s_ndevs; i++) ++ for (i = 0; i < ndevs; i++) + devs[i] = FDEV(i).bdev; +- *num_devs = sbi->s_ndevs; +- return devs; ++ return ndevs; + } + + static const struct fscrypt_operations f2fs_cryptops = { +diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h +index 772f822dc6b82e..3cb1e449d8cfc1 100644 +--- a/include/linux/fscrypt.h ++++ b/include/linux/fscrypt.h +@@ -57,6 +57,9 @@ struct fscrypt_name { + /* Maximum value for the third parameter of fscrypt_operations.set_context(). */ + #define FSCRYPT_SET_CONTEXT_MAX_SIZE 40 + ++/* Maximum supported number of block devices per filesystem */ ++#define FSCRYPT_MAX_DEVICES 8 ++ + #ifdef CONFIG_FS_ENCRYPTION + + /* Crypto operations for filesystems */ +@@ -175,21 +178,20 @@ struct fscrypt_operations { + bool (*has_stable_inodes)(struct super_block *sb); + + /* +- * Return an array of pointers to the block devices to which the +- * filesystem may write encrypted file contents, NULL if the filesystem +- * only has a single such block device, or an ERR_PTR() on error. ++ * Retrieve the list of block devices to which the filesystem may write ++ * encrypted file contents. + * +- * On successful non-NULL return, *num_devs is set to the number of +- * devices in the returned array. The caller must free the returned +- * array using kfree(). ++ * This writes the block_device pointers to @devs and returns the count ++ * (between 1 and FSCRYPT_MAX_DEVICES inclusively). + * + * If the filesystem can use multiple block devices (other than block + * devices that aren't used for encrypted file contents, such as + * external journal devices), and wants to support inline encryption, + * then it must implement this function. Otherwise it's not needed. + */ +- struct block_device **(*get_devices)(struct super_block *sb, +- unsigned int *num_devs); ++ unsigned int (*get_devices)( ++ struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]); + }; + + int fscrypt_d_revalidate(struct dentry *dentry, unsigned int flags); +-- +2.53.0 + diff --git a/queue-6.12/gve-fix-rx-queue-stall-on-alloc-failure.patch b/queue-6.12/gve-fix-rx-queue-stall-on-alloc-failure.patch new file mode 100644 index 0000000000..75cb55c349 --- /dev/null +++ b/queue-6.12/gve-fix-rx-queue-stall-on-alloc-failure.patch @@ -0,0 +1,160 @@ +From 08992b716aca4b7af5b7cba422443014751e8aca Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 14:21:22 -0700 +Subject: gve: fix Rx queue stall on alloc failure + +From: Eddie Phillips + +commit b65352a1bac64442ad95e64f385b40ccb9f1b0db upstream. + +When the system is under extreme memory pressure, page allocations can +fail during the Rx buffer refill loop. If the number of buffers posted +to hardware falls below a critical low threshold and the refill loop +exits due to allocation failures, the queue can stall: + +1. The device drops incoming packets because there are no descriptors. +2. Since no packets are processed, no Rx completions are generated. +3. Because no completions occur, NAPI is never scheduled, preventing + the refill loop from running again even after memory is freed. + +This results in a permanent queue stall. + +Resolve this by introducing a starvation recovery timer for each Rx queue. +If the number of buffers posted to hardware falls below a critical low +threshold, start a timer to periodically reschedule NAPI. Once NAPI runs +and successfully refills the queue above the threshold, the timer is +not rescheduled. + +The threshold is set to 32 because a single maximum-sized Receive Segment +Coalescing (RSC) packet can consume up to 19 descriptors in the Rx path. +Lower thresholds (such as 8 or 16) would be insufficient to process a +complete maximum-sized RSC packet, risking packet drops or unexpected +hardware behavior under memory pressure. Setting the threshold to 32 +guarantees a safe margin to handle at least one full RSC packet. + +Cc: stable@vger.kernel.org +Fixes: 9b8dd5e5ea48 ("gve: DQO: Add RX path") +Reviewed-by: Jordan Rhee +Signed-off-by: Eddie Phillips +Signed-off-by: Harshitha Ramamurthy +Reviewed-by: Przemek Kitszel +Link: https://patch.msgid.link/20260709211906.3322883-1-hramamurthy@google.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + drivers/net/ethernet/google/gve/gve.h | 3 ++ + drivers/net/ethernet/google/gve/gve_rx_dqo.c | 36 ++++++++++++++++++++ + 2 files changed, 39 insertions(+) + +diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h +index 8ddd366d9fde54..e19d722e8e9a18 100644 +--- a/drivers/net/ethernet/google/gve/gve.h ++++ b/drivers/net/ethernet/google/gve/gve.h +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -38,6 +39,7 @@ + + /* Interval to schedule a stats report update, 20000ms. */ + #define GVE_STATS_REPORT_TIMER_PERIOD 20000 ++#define GVE_RX_NAPI_RESCHED_MS 20 /* msecs */ + + /* Numbers of NIC tx/rx stats in stats report. */ + #define NIC_TX_STATS_REPORT_NUM 0 +@@ -311,6 +313,7 @@ struct gve_rx_ring { + struct xdp_rxq_info xsk_rxq; + struct xsk_buff_pool *xsk_pool; + struct page_frag_cache page_cache; /* Page cache to allocate XDP frames */ ++ struct timer_list starvation_timer; /* for queue starvation recovery */ + }; + + /* A TX desc ring entry */ +diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c +index 1154c1d8f66f05..2f7f31e4797cd2 100644 +--- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c ++++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c +@@ -199,6 +199,16 @@ static int gve_alloc_page_dqo(struct gve_rx_ring *rx, + return 0; + } + ++static void gve_rx_starvation_timer(struct timer_list *t) ++{ ++ struct gve_rx_ring *rx = from_timer(rx, t, starvation_timer); ++ struct gve_priv *priv = rx->gve; ++ struct gve_notify_block *block; ++ ++ block = &priv->ntfy_blocks[rx->ntfy_id]; ++ napi_schedule(&block->napi); ++} ++ + static void gve_rx_free_hdr_bufs(struct gve_priv *priv, struct gve_rx_ring *rx) + { + struct device *hdev = &priv->pdev->dev; +@@ -290,10 +300,13 @@ static void gve_rx_reset_ring_dqo(struct gve_priv *priv, int idx) + void gve_rx_stop_ring_dqo(struct gve_priv *priv, int idx) + { + int ntfy_idx = gve_rx_idx_to_ntfy(priv, idx); ++ struct gve_rx_ring *rx = &priv->rx[idx]; + + if (!gve_rx_was_added_to_block(priv, idx)) + return; + ++ timer_shutdown_sync(&rx->starvation_timer); ++ + gve_remove_napi(priv, ntfy_idx); + gve_rx_remove_from_block(priv, idx); + gve_rx_reset_ring_dqo(priv, idx); +@@ -371,8 +384,10 @@ static int gve_rx_alloc_hdr_bufs(struct gve_priv *priv, struct gve_rx_ring *rx, + void gve_rx_start_ring_dqo(struct gve_priv *priv, int idx) + { + int ntfy_idx = gve_rx_idx_to_ntfy(priv, idx); ++ struct gve_rx_ring *rx = &priv->rx[idx]; + + gve_rx_add_to_block(priv, idx); ++ timer_setup(&rx->starvation_timer, gve_rx_starvation_timer, 0); + gve_add_napi(priv, ntfy_idx, gve_napi_poll_dqo); + } + +@@ -511,6 +526,7 @@ void gve_rx_post_buffers_dqo(struct gve_rx_ring *rx) + struct gve_rx_compl_queue_dqo *complq = &rx->dqo.complq; + struct gve_rx_buf_queue_dqo *bufq = &rx->dqo.bufq; + struct gve_priv *priv = rx->gve; ++ u32 num_bufs_avail_to_hw; + u32 num_avail_slots; + u32 num_full_slots; + u32 num_posted = 0; +@@ -555,6 +571,26 @@ void gve_rx_post_buffers_dqo(struct gve_rx_ring *rx) + } + + rx->fill_cnt += num_posted; ++ ++ /* If the queue has fewer than GVE_RX_BUF_THRESH_DQO descriptors ++ * visible to the hardware, the hardware is in danger of starving ++ * and cannot trigger interrupts. ++ * ++ * We use a threshold of 32 because a single maximum-sized RSC ++ * packet can consume up to 19 descriptors in the Rx path. Lower ++ * thresholds (e.g., 8 or 16) would be unsafe as they could cause ++ * the device to drop/stall on a maximum-sized RSC packet. ++ * ++ * Start the timer to periodically reschedule NAPI and recover. ++ */ ++ num_bufs_avail_to_hw = ++ ((bufq->tail & ~(GVE_RX_BUF_THRESH_DQO - 1)) - ++ bufq->head) & bufq->mask; ++ ++ if (num_bufs_avail_to_hw < GVE_RX_BUF_THRESH_DQO) { ++ mod_timer(&rx->starvation_timer, ++ jiffies + msecs_to_jiffies(GVE_RX_NAPI_RESCHED_MS)); ++ } + } + + static void gve_try_recycle_buf(struct gve_priv *priv, struct gve_rx_ring *rx, +-- +2.53.0 + diff --git a/queue-6.12/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch b/queue-6.12/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch new file mode 100644 index 0000000000..34e8ffd211 --- /dev/null +++ b/queue-6.12/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch @@ -0,0 +1,105 @@ +From 81d8d44182357233d4b17914d1436ddcef521cd0 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 22 Jul 2026 20:45:51 +0800 +Subject: io_uring/rw: fix missing ERESTARTSYS conversion in read paths + +From: Yitang Yang + +commit ab05caca123c6d0b41850b7c05b246e4dca4a770 upstream. + +Both read and write may receive internal restart error codes from +the filesystem layer and should be converted to -EINTR. However, +when multishot read support was added, the error code normalization +was lost for both io_read() and io_read_mshot(). + +Extract the conversion into io_fixup_restart_res() and apply it +in all three locations: io_rw_done(), io_read(), and io_read_mshot(). + +Fixes: a08d195b586a ("io_uring/rw: split io_read() into a helper") +Cc: stable@vger.kernel.org +Signed-off-by: Yitang Yang +Link: https://patch.msgid.link/20260722124551.130563-1-yi1tang.yang@gmail.com +Signed-off-by: Jens Axboe +Signed-off-by: Sasha Levin +--- + io_uring/rw.c | 42 +++++++++++++++++++++++++----------------- + 1 file changed, 25 insertions(+), 17 deletions(-) + +diff --git a/io_uring/rw.c b/io_uring/rw.c +index a744cb5d235a6a..a74664c4a05efe 100644 +--- a/io_uring/rw.c ++++ b/io_uring/rw.c +@@ -560,6 +560,24 @@ static void io_complete_rw_iopoll(struct kiocb *kiocb, long res) + smp_store_release(&req->iopoll_completed, 1); + } + ++static inline ssize_t io_fixup_restart_res(ssize_t ret) ++{ ++ switch (ret) { ++ case -ERESTARTSYS: ++ case -ERESTARTNOINTR: ++ case -ERESTARTNOHAND: ++ case -ERESTART_RESTARTBLOCK: ++ /* ++ * We can't just restart the syscall, since previously ++ * submitted sqes may already be in progress. Just fail ++ * this IO with EINTR. ++ */ ++ return -EINTR; ++ default: ++ return ret; ++ } ++} ++ + static inline void io_rw_done(struct kiocb *kiocb, ssize_t ret) + { + /* IO was queued async, completion will happen later */ +@@ -567,21 +585,8 @@ static inline void io_rw_done(struct kiocb *kiocb, ssize_t ret) + return; + + /* transform internal restart error codes */ +- if (unlikely(ret < 0)) { +- switch (ret) { +- case -ERESTARTSYS: +- case -ERESTARTNOINTR: +- case -ERESTARTNOHAND: +- case -ERESTART_RESTARTBLOCK: +- /* +- * We can't just restart the syscall, since previously +- * submitted sqes may already be in progress. Just fail +- * this IO with EINTR. +- */ +- ret = -EINTR; +- break; +- } +- } ++ if (unlikely(ret < 0)) ++ ret = io_fixup_restart_res(ret); + + INDIRECT_CALL_2(kiocb->ki_complete, io_complete_rw_iopoll, + io_complete_rw, kiocb, ret); +@@ -961,7 +966,8 @@ int io_read(struct io_kiocb *req, unsigned int issue_flags) + + if (req->flags & REQ_F_BUFFERS_COMMIT) + io_kbuf_recycle(req, sel.buf_list, issue_flags); +- return ret; ++ ++ return io_fixup_restart_res(ret); + } + + int io_read_mshot(struct io_kiocb *req, unsigned int issue_flags) +@@ -995,8 +1001,10 @@ int io_read_mshot(struct io_kiocb *req, unsigned int issue_flags) + return -EAGAIN; + } else if (ret <= 0) { + io_kbuf_recycle(req, sel.buf_list, issue_flags); +- if (ret < 0) ++ if (ret < 0) { ++ ret = io_fixup_restart_res(ret); + req_set_fail(req); ++ } + } else if (!(req->flags & REQ_F_APOLL_MULTISHOT)) { + cflags = io_put_kbuf(req, ret, sel.buf_list); + } else { +-- +2.53.0 + diff --git a/queue-6.12/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch b/queue-6.12/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch new file mode 100644 index 0000000000..3363a32fe2 --- /dev/null +++ b/queue-6.12/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch @@ -0,0 +1,108 @@ +From d61d1255f5ee090b6c70083ca1d9b0029c81c41f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:26 +0800 +Subject: ksmbd: bound DACL dedup walk to copied ACEs + +From: Namjae Jeon + +commit 58d97fcd0bf1aee694e244cc28635b9df95b543b upstream. + +set_ntacl_dacl() can stop copying ACEs before consuming the full input +DACL when size accounting overflows. + +When that happens, num_aces reflects only the ACEs that were actually +copied into the output DACL, but set_posix_acl_entries_dacl() still +receives nt_num_aces and uses it to walk the existing ACE array during +dedup. + +That makes the dedup walk scan past the copied ACE array and inspect +buffer tail that does not contain valid ACEs. + +Split the two meanings currently carried by the NT ACE count. Pass the +number of copied NT ACEs to bound the dedup walk, and preserve the +original "input DACL had NT ACEs" state separately for the +Everyone/default ACL fallback. + +This keeps the dedup walk aligned with the ACEs that are actually +present in the rebuilt DACL. + +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index ca0e4aab6dd87f..b0677e095c3292 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -595,7 +595,8 @@ static void parse_dacl(struct mnt_idmap *idmap, + static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + struct smb_ace *pndace, + struct smb_fattr *fattr, u16 *num_aces, +- u16 *size, u32 nt_aces_num) ++ u16 *size, u16 existing_nt_aces, ++ bool had_nt_aces) + { + struct posix_acl_entry *pace; + struct smb_sid *sid; +@@ -627,14 +628,14 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + + gid = posix_acl_gid_translate(idmap, pace); + id_to_sid(gid, SIDUNIX_GROUP, sid); +- } else if (pace->e_tag == ACL_OTHER && !nt_aces_num) { ++ } else if (pace->e_tag == ACL_OTHER && !had_nt_aces) { + smb_copy_sid(sid, &sid_everyone); + } else { + kfree(sid); + continue; + } + ntace = pndace; +- for (j = 0; j < nt_aces_num; j++) { ++ for (j = 0; j < existing_nt_aces; j++) { + if (ntace->sid.sub_auth[ntace->sid.num_subauth - 1] == + sid->sub_auth[sid->num_subauth - 1]) + goto pass_same_sid; +@@ -678,7 +679,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + kfree(sid); + } + +- if (nt_aces_num) ++ if (had_nt_aces) + return; + + posix_default_acl: +@@ -732,6 +733,7 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + { + struct smb_ace *ntace, *pndace; + u16 nt_num_aces = le16_to_cpu(nt_dacl->num_aces), num_aces = 0; ++ u16 copied_nt_aces; + unsigned short size = 0; + int i; + +@@ -765,8 +767,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + } + } + ++ copied_nt_aces = num_aces; + set_posix_acl_entries_dacl(idmap, pndace, fattr, +- &num_aces, &size, nt_num_aces); ++ &num_aces, &size, copied_nt_aces, ++ nt_num_aces != 0); + pndacl->num_aces = cpu_to_le16(num_aces); + pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); + } +@@ -784,7 +788,7 @@ static void set_mode_dacl(struct mnt_idmap *idmap, + + if (fattr->cf_acls) { + set_posix_acl_entries_dacl(idmap, pndace, fattr, +- &num_aces, &size, num_aces); ++ &num_aces, &size, num_aces, false); + goto out; + } + +-- +2.53.0 + diff --git a/queue-6.12/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch b/queue-6.12/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch new file mode 100644 index 0000000000..ef0e971d83 --- /dev/null +++ b/queue-6.12/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch @@ -0,0 +1,91 @@ +From 1bab68be16153e851ad5a153a0806fd3133a5934 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:24 +0800 +Subject: ksmbd: restore DACL size on check_add_overflow() to avoid malformed + ACL + +From: Wentao Guan + +commit bbf0a8e931204ecdab494a88d43b0a24a04285c5 upstream. + +check_add_overflow() unconditionally writes the truncated sum into *d +even on overflow, per its contract in include/linux/overflow.h. +The four check_add_overflow() guards in set_posix_acl_entries_dacl() +and set_ntacl_dacl() break out of the ACE-building loops on overflow, +but the truncated *size is then consumed downstream at the end of +set_ntacl_dacl(): + + pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); + +This produces an on-wire NT ACL whose pndacl->size under-reports the +bytes actually written by the preceding fill_ace_for_sid()/memcpy() +calls, yielding a malformed ACL that can trigger out-of-bounds reads +when re-parsed by clients or ksmbd itself. + +Restore *size to its pre-addition value on each overflow branch (via +`*size -= ace_sz` / `size -= nt_ace_size`) so that after the break, +*size once again holds the cumulative size of the successfully-written +ACEs. The committed ACL is then truncated-but-self-consistent rather +than malformed. + +The ksmbd DACL builders are the only check_add_overflow() sites found +where an overflow path breaks out of a loop and the destination value +is consumed afterward. The other nearby break-style cases either +return -EINVAL on overflow (transport_ipc.c) or break without +consuming the overflowed destination value afterward (buildid.c). + +Fixes: 299f962c0b02 ("ksmbd: use check_add_overflow() to prevent u16 DACL size overflow") +Assisted-by: atomcode:glm-5.2 +Assisted-by: Codex:gpt-5.5 +Cc: stable@vger.kernel.org +Signed-off-by: Wentao Guan +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index 173469d112f101..ca0e4aab6dd87f 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -649,6 +649,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, flags, + pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -663,6 +664,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, + 0x03, pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -708,6 +710,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, 0x0b, + pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -750,8 +753,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + goto next_ace; + + memcpy((char *)pndace + size, ntace, nt_ace_size); +- if (check_add_overflow(size, nt_ace_size, &size)) ++ if (check_add_overflow(size, nt_ace_size, &size)) { ++ size -= nt_ace_size; + break; ++ } + num_aces++; + + next_ace: +-- +2.53.0 + diff --git a/queue-6.12/ksmbd-validate-ace-size-against-sid-sub-authorities.patch b/queue-6.12/ksmbd-validate-ace-size-against-sid-sub-authorities.patch new file mode 100644 index 0000000000..027cfa523f --- /dev/null +++ b/queue-6.12/ksmbd-validate-ace-size-against-sid-sub-authorities.patch @@ -0,0 +1,66 @@ +From 113d16c1d1d164a09d9bbf13ec5161040bd6153f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:29 +0800 +Subject: ksmbd: validate ACE size against SID sub-authorities + +From: Namjae Jeon + +commit 5152c6d49e3fd4e9f2e857c57527aead752f1f87 upstream. + +set_ntacl_dacl() validates sid.num_subauth before copying an ACE, but +does not verify that the declared ACE size contains all sub-authorities +described by that field. An undersized ACE can therefore be copied +and later make the POSIX ACL deduplication walk inspect data beyond +the copied ACE boundary. + +The existing initial bound check is also too small. It only ensures +that the ACE size field is accessible before set_ntacl_dacl() reads +sid.num_subauth farther into the input buffer. + +Require enough input for the fixed SID header before accessing +num_subauth, reject ACEs smaller than that header, and skip ACEs +whose declared size cannot contain the complete SID. This makes the +validation consistent with the other ACE walk paths. + +Reported-by: LocalHost +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index b0677e095c3292..d3d0a22620f96d 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -743,15 +743,22 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + for (i = 0; i < nt_num_aces; i++) { + unsigned short nt_ace_size; + +- if (offsetof(struct smb_ace, access_req) > aces_size) ++ if (aces_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + + nt_ace_size = le16_to_cpu(ntace->size); +- if (nt_ace_size > aces_size) ++ if (nt_ace_size > aces_size || ++ nt_ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + + if (ntace->sid.num_subauth == 0 || +- ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES || ++ nt_ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE + ++ sizeof(__le32) * ++ ntace->sid.num_subauth) + goto next_ace; + + memcpy((char *)pndace + size, ntace, nt_ace_size); +-- +2.53.0 + diff --git a/queue-6.12/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch b/queue-6.12/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch new file mode 100644 index 0000000000..02d4f9e338 --- /dev/null +++ b/queue-6.12/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch @@ -0,0 +1,75 @@ +From 2b3dbbd54bbfe19417d782eecbad72514b3244f0 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:22 +0800 +Subject: ksmbd: validate num_subauth when copying ACE in set_ntacl_dacl + +From: Haofeng Li + +commit 47f0b34f6bc98ed85bfdc293e8f3e432ec24958d upstream. + +set_ntacl_dacl() copies each ACE from the attacker-controlled stored +security descriptor verbatim into the response DACL without checking +sid.num_subauth. The ACE bytes (including an unchecked num_subauth) +originate from an authenticated SMB2_SET_INFO(SecInfo=DACL) that is +stored raw via ksmbd_vfs_set_sd_xattr(); parse_dacl() rejects a bad ACE +with `break` rather than an error, so parse_sec_desc() still returns +success and the malformed SD reaches the xattr intact. + +On a subsequent SMB2_QUERY_INFO(SecInfo=DACL) for an inode carrying a +POSIX access ACL, build_sec_desc() -> set_ntacl_dacl() -> +set_posix_acl_entries_dacl() walks the copied ACEs and reads + + ntace->sid.sub_auth[ntace->sid.num_subauth - 1] + +with num_subauth taken straight from the stored SD. Since sub_auth[] +is fixed at SID_MAX_SUB_AUTHORITIES (15), a crafted num_subauth (e.g. +255) drives an out-of-bounds heap read of ~1 KB with an offset fully +controlled by an authenticated client. + +The sibling functions already gate this field: + parse_dacl() -- num_subauth == 0 || > SID_MAX_SUB_AUTHORITIES + parse_sid() -- num_subauth > SID_MAX_SUB_AUTHORITIES + smb_copy_sid() -- min_t(u8, num_subauth, SID_MAX_SUB_AUTHORITIES) +set_ntacl_dacl() is the lone inconsistent path that omits the check. + +Add the same num_subauth validation in set_ntacl_dacl() before copying +the ACE, matching the gate already enforced by parse_dacl(). + +Signed-off-by: Haofeng Li +Reviewed-by: ChenXiaoSong +Suggested-by: Namjae Jeon +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index b09bc8d9389a2a..173469d112f101 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -745,12 +745,18 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + if (nt_ace_size > aces_size) + break; + ++ if (ntace->sid.num_subauth == 0 || ++ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ goto next_ace; ++ + memcpy((char *)pndace + size, ntace, nt_ace_size); + if (check_add_overflow(size, nt_ace_size, &size)) + break; ++ num_aces++; ++ ++next_ace: + aces_size -= nt_ace_size; + ntace = (struct smb_ace *)((char *)ntace + nt_ace_size); +- num_aces++; + } + } + +-- +2.53.0 + diff --git a/queue-6.12/net-pcs-xpcs-fix-sgmii-state-reading.patch b/queue-6.12/net-pcs-xpcs-fix-sgmii-state-reading.patch new file mode 100644 index 0000000000..501a498388 --- /dev/null +++ b/queue-6.12/net-pcs-xpcs-fix-sgmii-state-reading.patch @@ -0,0 +1,119 @@ +From 7883170aa7261bc2f85784d1a61623c6463a888e Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:14:26 +0800 +Subject: net: pcs: xpcs: fix SGMII state reading + +From: Coia Prant + +commit def9a4745e105145133e442dd8a1c126caf0f553 upstream. + +Commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode") +added a path in xpcs_get_state_c37_sgmii() that reads speed/duplex from +BMCR after AN completes. However, BMCR does not reflect the negotiated +result on the hardware where this has been tested: + +- On RK3568 (MAC side SGMII), BMCR returns a fixed hardware reset value +- Wangxun engineer Jiawen Wu confirmed that on their side, "BMCR looks + like it only wants to be return as 0" [0] + +The correct information is available in CL37_ANSGM_STS, which contains +the actual link status and negotiated speed/duplex. + +This bug was previously masked by phylink core, which overrides the PCS +link state with the PHY state when a PHY is present: + + /* If we have a phy, the "up" state is the union of both the + * PHY and the MAC + */ + if (phy) + link_state.link &= pl->phy_state.link; + +Thus, when the link is down, the PHY's link_down state is applied on top +of whatever the PCS reports, hiding the broken PCS state reading path. + +Modify xpcs_get_state_c37_sgmii() to: +1. Read link state from CL37_ANSGM_STS +2. If link is up, report speed/duplex from CL37_ANSGM_STS +3. Remove the broken BMCR reading path entirely + +Also properly set state->an_complete to reflect the AN completion status, +and clear CL37_ANCMPLT_INTR when link is down to avoid stale state. + +[0] https://lore.kernel.org/all/000c01dd1593$2ac0b0f0$804212d0$@trustnetic.com/ + +Fixes: 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode") +Cc: stable@vger.kernel.org +Tested-by: Jiawen Wu +Signed-off-by: Coia Prant +Tested-by: Maxime Chevallier +Reviewed-by: Maxime Chevallier +Link: https://patch.msgid.link/20260717074324.3250043-2-coiaprant@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + drivers/net/pcs/pcs-xpcs.c | 32 +++++++------------------------- + 1 file changed, 7 insertions(+), 25 deletions(-) + +diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c +index 82463f9d50c85c..0152f820c1bbe5 100644 +--- a/drivers/net/pcs/pcs-xpcs.c ++++ b/drivers/net/pcs/pcs-xpcs.c +@@ -973,6 +973,7 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, + + /* Reset link_state */ + state->link = false; ++ state->an_complete = false; + state->speed = SPEED_UNKNOWN; + state->duplex = DUPLEX_UNKNOWN; + state->pause = 0; +@@ -984,6 +985,8 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, + if (ret < 0) + return ret; + ++ state->an_complete = ret & DW_VR_MII_AN_STS_C37_ANCMPLT_INTR; ++ + if (ret & DW_VR_MII_C37_ANSGM_SP_LNKSTS) { + int speed_value; + +@@ -1002,34 +1005,13 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, + state->duplex = DUPLEX_FULL; + else + state->duplex = DUPLEX_HALF; +- } else if (ret == DW_VR_MII_AN_STS_C37_ANCMPLT_INTR) { +- int speed, duplex; +- +- state->link = true; +- +- speed = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1); +- if (speed < 0) +- return speed; +- +- speed &= SGMII_SPEED_SS13 | SGMII_SPEED_SS6; +- if (speed == SGMII_SPEED_SS6) +- state->speed = SPEED_1000; +- else if (speed == SGMII_SPEED_SS13) +- state->speed = SPEED_100; +- else if (speed == 0) +- state->speed = SPEED_10; +- +- duplex = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_ADVERTISE); +- if (duplex < 0) +- return duplex; + +- if (duplex & DW_FULL_DUPLEX) +- state->duplex = DUPLEX_FULL; +- else if (duplex & DW_HALF_DUPLEX) +- state->duplex = DUPLEX_HALF; ++ return 0; ++ } + ++ /* Clear AN complete status or interrupt */ ++ if (state->an_complete) + xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0); +- } + + return 0; + } +-- +2.53.0 + diff --git a/queue-6.12/net-qrtr-ns-raise-node-count-limit-to-512.patch b/queue-6.12/net-qrtr-ns-raise-node-count-limit-to-512.patch new file mode 100644 index 0000000000..f0db2b1a4a --- /dev/null +++ b/queue-6.12/net-qrtr-ns-raise-node-count-limit-to-512.patch @@ -0,0 +1,42 @@ +From fd0dd4e746e330ebc74b67549536bc6eb82de15a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 17:04:11 +0200 +Subject: net: qrtr: ns: Raise node count limit to 512 + +From: Youssef Samir + +commit ff194cffd586cbd4cc49eccb002c65f2a902a277 upstream. + +The current node limit of 64 breaks the functionality for a number of AI200 +deployments that have up to 384 nodes. Raise the limit to 512. + +Fixes: 27d5e84e810b ("net: qrtr: ns: Limit the total number of nodes") +Cc: stable@vger.kernel.org +Signed-off-by: Youssef Samir +Link: https://patch.msgid.link/20260713145901.212396-1-youssef.abdulrahman@oss.qualcomm.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Youssef Samir +Signed-off-by: Sasha Levin +--- + net/qrtr/ns.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/net/qrtr/ns.c b/net/qrtr/ns.c +index a10e8de4f7e105..e8e3ba1b3b8239 100644 +--- a/net/qrtr/ns.c ++++ b/net/qrtr/ns.c +@@ -85,9 +85,9 @@ struct qrtr_node { + /* Max nodes limit is chosen based on the current platform requirements. + * If the requirement changes in the future, this value can be increased. + */ +-#define QRTR_NS_MAX_NODES 64 ++#define QRTR_NS_MAX_NODES 512 + +-static u8 node_count; ++static u16 node_count; + + static struct qrtr_node *node_get(unsigned int node_id) + { +-- +2.53.0 + diff --git a/queue-6.12/series b/queue-6.12/series index f8400713a8..f9017c1670 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -449,3 +449,14 @@ drm-amdgpu-fix-division-by-zero-with-invalid-uvd-dimensions.patch drm-amdgpu-invoke-pm_genpd_remove-before-freeing-genpd.patch drm-amdgpu-fix-aperture-mapping-leak.patch drm-amd-pm-fix-smu13-power-limit-range-calculation.patch +bpf-fix-same-register-dst-src-oob-read-and-pointer-l.patch +net-qrtr-ns-raise-node-count-limit-to-512.patch +ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch +ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch +ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch +ksmbd-validate-ace-size-against-sid-sub-authorities.patch +fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch +drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch +io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch +net-pcs-xpcs-fix-sgmii-state-reading.patch +gve-fix-rx-queue-stall-on-alloc-failure.patch diff --git a/queue-6.18/bpf-fix-same-register-dst-src-oob-read-and-pointer-l.patch b/queue-6.18/bpf-fix-same-register-dst-src-oob-read-and-pointer-l.patch new file mode 100644 index 0000000000..09602c42fb --- /dev/null +++ b/queue-6.18/bpf-fix-same-register-dst-src-oob-read-and-pointer-l.patch @@ -0,0 +1,86 @@ +From 9cfb9b37e6091da08fa39cfcdaf9952b6a9f4f9a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 20:26:58 +0000 +Subject: bpf: Fix same-register dst/src OOB read and pointer leak in sock_ops + +From: Jiayuan Chen + +[ Upstream commit 10f86a2a5c91fc4c4d001960f1c21abe52545ef6 ] + +When a BPF sock_ops program accesses ctx fields with dst_reg == src_reg, +the SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD() macros fail to zero the +destination register in the !fullsock / !locked_tcp_sock path. + +Both macros borrow a temporary register to check is_fullsock / +is_locked_tcp_sock when dst_reg == src_reg, because dst_reg holds the +ctx pointer. When the check is false (e.g., TCP_NEW_SYN_RECV state with +a request_sock), dst_reg should be zeroed but is not, leaving the stale +ctx pointer: + + - SOCK_OPS_GET_SK: dst_reg retains the ctx pointer, passes NULL checks + as PTR_TO_SOCKET_OR_NULL, and can be used as a bogus socket pointer, + leading to stack-out-of-bounds access in helpers like + bpf_skc_to_tcp6_sock(). + + - SOCK_OPS_GET_FIELD: dst_reg retains the ctx pointer which the + verifier believes is a SCALAR_VALUE, leaking a kernel pointer. + +Fix both macros by: + - Changing JMP_A(1) to JMP_A(2) in the fullsock path to skip the + added instruction. + - Adding BPF_MOV64_IMM(si->dst_reg, 0) after the temp register + restore in the !fullsock path, placed after the restore because + dst_reg == src_reg means we need src_reg intact to read ctx->temp. + +Fixes: fd09af010788 ("bpf: sock_ops ctx access may stomp registers in corner case") +Fixes: 84f44df664e9 ("bpf: sock_ops sk access may stomp registers when dst_reg = src_reg") +Reported-by: Quan Sun <2022090917019@std.uestc.edu.cn> +Reported-by: Yinhao Hu +Reported-by: Kaiyan Mei +Reported-by: Dongliang Mu +Reviewed-by: Emil Tsalapatis +Closes: https://lore.kernel.org/bpf/6fe1243e-149b-4d3b-99c7-fcc9e2f75787@std.uestc.edu.cn/T/#u +Signed-off-by: Jiayuan Chen +Acked-by: Martin KaFai Lau +Link: https://patch.msgid.link/20260407022720.162151-2-jiayuan.chen@linux.dev +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Jay Wang +Signed-off-by: Sasha Levin +--- + net/core/filter.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/net/core/filter.c b/net/core/filter.c +index 42385e79043131..332f4986376f11 100644 +--- a/net/core/filter.c ++++ b/net/core/filter.c +@@ -10539,10 +10539,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type, + si->dst_reg, si->dst_reg, \ + offsetof(OBJ, OBJ_FIELD)); \ + if (si->dst_reg == si->src_reg) { \ +- *insn++ = BPF_JMP_A(1); \ ++ *insn++ = BPF_JMP_A(2); \ + *insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \ + offsetof(struct bpf_sock_ops_kern, \ + temp)); \ ++ *insn++ = BPF_MOV64_IMM(si->dst_reg, 0); \ + } \ + } while (0) + +@@ -10576,10 +10577,11 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type, + si->dst_reg, si->src_reg, \ + offsetof(struct bpf_sock_ops_kern, sk));\ + if (si->dst_reg == si->src_reg) { \ +- *insn++ = BPF_JMP_A(1); \ ++ *insn++ = BPF_JMP_A(2); \ + *insn++ = BPF_LDX_MEM(BPF_DW, reg, si->src_reg, \ + offsetof(struct bpf_sock_ops_kern, \ + temp)); \ ++ *insn++ = BPF_MOV64_IMM(si->dst_reg, 0); \ + } \ + } while (0) + +-- +2.53.0 + diff --git a/queue-6.18/dm-verity-fec-fix-reading-parity-bytes-split-across-.patch b/queue-6.18/dm-verity-fec-fix-reading-parity-bytes-split-across-.patch new file mode 100644 index 0000000000..c5237e69a2 --- /dev/null +++ b/queue-6.18/dm-verity-fec-fix-reading-parity-bytes-split-across-.patch @@ -0,0 +1,185 @@ +From 9e736361849fce698d2af96ba009e236b6c26cd7 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 22:19:07 -0700 +Subject: dm-verity-fec: fix reading parity bytes split across blocks (take 3) + +From: Eric Biggers + +commit 430a05cb926f6bdf53e81460a2c3a553257f3f61 upstream. + +fec_decode_bufs() assumes that the parity bytes of the first RS codeword +it decodes are never split across parity blocks. + +This assumption is false. Consider v->fec->block_size == 4096 && +v->fec->roots == 17 && fio->nbufs == 1, for example. In that case, each +call to fec_decode_bufs() consumes v->fec->roots * (fio->nbufs << +DM_VERITY_FEC_BUF_RS_BITS) = 272 parity bytes. + +Considering that the parity data for each message block starts on a +block boundary, the byte alignment in the parity data will iterate +through 272*i mod 4096 until the 3 parity blocks have been consumed. On +the 16th call (i=15), the alignment will be 4080 bytes into the first +block. Only 16 bytes remain in that block, but 17 parity bytes will be +needed. The code reads out-of-bounds from the parity block buffer. + +Fortunately this doesn't normally happen, since it can occur only for +certain non-default values of fec_roots *and* when the maximum number of +buffers couldn't be allocated due to low memory. For example with +block_size=4096 only the following cases are affected: + + fec_roots=17: nbufs in [1, 3, 5, 15] + fec_roots=19: nbufs in [1, 229] + fec_roots=21: nbufs in [1, 3, 5, 13, 15, 39, 65, 195] + fec_roots=23: nbufs in [1, 89] + +Regardless, fix it by refactoring how the parity blocks are read. + +Fixes: 6df90c02bae4 ("dm-verity FEC: Fix RS FEC repair for roots unaligned to block size (take 2)") +Cc: stable@vger.kernel.org +Signed-off-by: Eric Biggers +Signed-off-by: Mikulas Patocka +Signed-off-by: Sasha Levin +--- + drivers/md/dm-verity-fec.c | 100 ++++++++++++++++--------------------- + 1 file changed, 44 insertions(+), 56 deletions(-) + +diff --git a/drivers/md/dm-verity-fec.c b/drivers/md/dm-verity-fec.c +index 03e59b85132aa7..a8fbf97b97517f 100644 +--- a/drivers/md/dm-verity-fec.c ++++ b/drivers/md/dm-verity-fec.c +@@ -39,36 +39,6 @@ static inline u64 fec_interleave(struct dm_verity *v, u64 offset) + return offset + mod * (v->fec->rounds << v->data_dev_block_bits); + } + +-/* +- * Read error-correcting codes for the requested RS block. Returns a pointer +- * to the data block. Caller is responsible for releasing buf. +- */ +-static u8 *fec_read_parity(struct dm_verity *v, u64 rsb, int index, +- unsigned int *offset, unsigned int par_buf_offset, +- struct dm_buffer **buf, unsigned short ioprio) +-{ +- u64 position, block, rem; +- u8 *res; +- +- /* We have already part of parity bytes read, skip to the next block */ +- if (par_buf_offset) +- index++; +- +- position = (index + rsb) * v->fec->roots; +- block = div64_u64_rem(position, v->fec->io_size, &rem); +- *offset = par_buf_offset ? 0 : (unsigned int)rem; +- +- res = dm_bufio_read_with_ioprio(v->fec->bufio, block, buf, ioprio); +- if (IS_ERR(res)) { +- DMERR("%s: FEC %llu: parity read failed (block %llu): %ld", +- v->data_dev->name, (unsigned long long)rsb, +- (unsigned long long)block, PTR_ERR(res)); +- *buf = NULL; +- } +- +- return res; +-} +- + /* Loop over each preallocated buffer slot. */ + #define fec_for_each_prealloc_buffer(__i) \ + for (__i = 0; __i < DM_VERITY_FEC_BUF_PREALLOC; __i++) +@@ -116,15 +86,29 @@ static int fec_decode_bufs(struct dm_verity *v, struct dm_verity_io *io, + { + int r, corrected = 0, res; + struct dm_buffer *buf; +- unsigned int n, i, j, offset, par_buf_offset = 0; ++ unsigned int n, i, j, parity_pos, to_copy; + uint16_t par_buf[DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN]; + u8 *par, *block; ++ u64 parity_block; + struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size); + +- par = fec_read_parity(v, rsb, block_offset, &offset, +- par_buf_offset, &buf, bio->bi_ioprio); +- if (IS_ERR(par)) ++ /* ++ * Compute the index of the first parity block that will be needed and ++ * the starting position in that block. Then read that block. ++ * ++ * io_size is always a power of 2, but roots might not be. Note that ++ * when it's not, a codeword's parity bytes can span a block boundary. ++ */ ++ parity_block = (rsb + block_offset) * v->fec->roots; ++ parity_pos = parity_block & (v->fec->io_size - 1); ++ parity_block >>= v->data_dev_block_bits; ++ par = dm_bufio_read_with_ioprio(v->fec->bufio, parity_block, &buf, ++ bio->bi_ioprio); ++ if (IS_ERR(par)) { ++ DMERR("%s: FEC %llu: parity read failed (block %llu): %ld", ++ v->data_dev->name, rsb, parity_block, PTR_ERR(par)); + return PTR_ERR(par); ++ } + + /* + * Decode the RS blocks we have in bufs. Each RS block results in +@@ -132,8 +116,32 @@ static int fec_decode_bufs(struct dm_verity *v, struct dm_verity_io *io, + */ + fec_for_each_buffer_rs_block(fio, n, i) { + block = fec_buffer_rs_block(v, fio, n, i); +- for (j = 0; j < v->fec->roots - par_buf_offset; j++) +- par_buf[par_buf_offset + j] = par[offset + j]; ++ ++ /* ++ * Copy the next 'roots' parity bytes to 'par_buf', reading ++ * another parity block if needed. ++ */ ++ to_copy = min(v->fec->io_size - parity_pos, v->fec->roots); ++ for (j = 0; j < to_copy; j++) ++ par_buf[j] = par[parity_pos++]; ++ if (to_copy < v->fec->roots) { ++ parity_block++; ++ parity_pos = 0; ++ ++ dm_bufio_release(buf); ++ par = dm_bufio_read_with_ioprio(v->fec->bufio, ++ parity_block, &buf, ++ bio->bi_ioprio); ++ if (IS_ERR(par)) { ++ DMERR("%s: FEC %llu: parity read failed (block %llu): %ld", ++ v->data_dev->name, rsb, parity_block, ++ PTR_ERR(par)); ++ return PTR_ERR(par); ++ } ++ for (; j < v->fec->roots; j++) ++ par_buf[j] = par[parity_pos++]; ++ } ++ + /* Decode an RS block using Reed-Solomon */ + res = decode_rs8(fio->rs, block, par_buf, v->fec->rsn, + NULL, neras, fio->erasures, 0, NULL); +@@ -148,26 +156,6 @@ static int fec_decode_bufs(struct dm_verity *v, struct dm_verity_io *io, + block_offset++; + if (block_offset >= 1 << v->data_dev_block_bits) + goto done; +- +- /* Read the next block when we run out of parity bytes */ +- offset += (v->fec->roots - par_buf_offset); +- /* Check if parity bytes are split between blocks */ +- if (offset < v->fec->io_size && (offset + v->fec->roots) > v->fec->io_size) { +- par_buf_offset = v->fec->io_size - offset; +- for (j = 0; j < par_buf_offset; j++) +- par_buf[j] = par[offset + j]; +- offset += par_buf_offset; +- } else +- par_buf_offset = 0; +- +- if (offset >= v->fec->io_size) { +- dm_bufio_release(buf); +- +- par = fec_read_parity(v, rsb, block_offset, &offset, +- par_buf_offset, &buf, bio->bi_ioprio); +- if (IS_ERR(par)) +- return PTR_ERR(par); +- } + } + done: + r = corrected; +-- +2.53.0 + diff --git a/queue-6.18/dm-verity-fec-fix-the-size-of-dm_verity_fec_io-erasu.patch b/queue-6.18/dm-verity-fec-fix-the-size-of-dm_verity_fec_io-erasu.patch new file mode 100644 index 0000000000..36244f5e4b --- /dev/null +++ b/queue-6.18/dm-verity-fec-fix-the-size-of-dm_verity_fec_io-erasu.patch @@ -0,0 +1,43 @@ +From 1be9e43a1d32e34c4a864b389c956c19023c88ec Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 22:19:06 -0700 +Subject: dm-verity-fec: fix the size of dm_verity_fec_io::erasures + +From: Eric Biggers + +commit a7fca324d7d90f7b139d4d32747c83a629fdb446 upstream. + +At most 25 entries in dm_verity_fec_io::erasures are used: the maximum +number of FEC roots plus one. Therefore, set the array size +accordingly. This reduces the size of dm_verity_fec_io by 912 bytes. + +Note: a later commit introduces a constant DM_VERITY_FEC_MAX_ROOTS, +which allows the size to be more clearly expressed as +DM_VERITY_FEC_MAX_ROOTS + 1. This commit just fixes the size first. + +Fixes: a739ff3f543a ("dm verity: add support for forward error correction") +Cc: stable@vger.kernel.org +Signed-off-by: Eric Biggers +Signed-off-by: Mikulas Patocka +Signed-off-by: Sasha Levin +--- + drivers/md/dm-verity-fec.h | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/md/dm-verity-fec.h b/drivers/md/dm-verity-fec.h +index ec37e607cb3f09..90a0af3f35d31a 100644 +--- a/drivers/md/dm-verity-fec.h ++++ b/drivers/md/dm-verity-fec.h +@@ -50,7 +50,8 @@ struct dm_verity_fec { + /* per-bio data */ + struct dm_verity_fec_io { + struct rs_control *rs; /* Reed-Solomon state */ +- int erasures[DM_VERITY_FEC_MAX_RSN]; /* erasures for decode_rs8 */ ++ /* erasures for decode_rs8 */ ++ int erasures[DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN + 1]; + u8 *bufs[DM_VERITY_FEC_BUF_MAX]; /* bufs for deinterleaving */ + unsigned int nbufs; /* number of buffers allocated */ + u8 *output; /* buffer for corrected output */ +-- +2.53.0 + diff --git a/queue-6.18/dm-verity-fec-replace-max-min-_rsn-with-min-max-_roo.patch b/queue-6.18/dm-verity-fec-replace-max-min-_rsn-with-min-max-_roo.patch new file mode 100644 index 0000000000..9bdba64900 --- /dev/null +++ b/queue-6.18/dm-verity-fec-replace-max-min-_rsn-with-min-max-_roo.patch @@ -0,0 +1,75 @@ +From fe297b15822c386e22dde9e44294a8b284030c08 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 22:19:08 -0700 +Subject: dm-verity-fec: replace {MAX,MIN}_RSN with {MIN,MAX}_ROOTS + +From: Eric Biggers + +commit 82fbd6a3e29a329d439690cd7ccc4162c9cd8db6 upstream. + +Every time DM_VERITY_FEC_{MAX,MIN}_RSN are used, they are subtracted +from DM_VERITY_FEC_RSM to get the bounds on the number of roots. +Therefore, replace these with {MIN,MAX}_ROOTS constants which are more +directly useful. (Note the inversion, where MAX_RSN maps to MIN_ROOTS +and MIN_RSN maps to MAX_ROOTS.) No functional change. + +Signed-off-by: Eric Biggers +Signed-off-by: Mikulas Patocka +Signed-off-by: Sasha Levin +--- + drivers/md/dm-verity-fec.c | 6 +++--- + drivers/md/dm-verity-fec.h | 7 +++---- + 2 files changed, 6 insertions(+), 7 deletions(-) + +diff --git a/drivers/md/dm-verity-fec.c b/drivers/md/dm-verity-fec.c +index a8fbf97b97517f..cebcc8fd25d700 100644 +--- a/drivers/md/dm-verity-fec.c ++++ b/drivers/md/dm-verity-fec.c +@@ -87,7 +87,7 @@ static int fec_decode_bufs(struct dm_verity *v, struct dm_verity_io *io, + int r, corrected = 0, res; + struct dm_buffer *buf; + unsigned int n, i, j, parity_pos, to_copy; +- uint16_t par_buf[DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN]; ++ uint16_t par_buf[DM_VERITY_FEC_MAX_ROOTS]; + u8 *par, *block; + u64 parity_block; + struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size); +@@ -605,8 +605,8 @@ int verity_fec_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, + + } else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_ROOTS)) { + if (sscanf(arg_value, "%hhu%c", &num_c, &dummy) != 1 || !num_c || +- num_c < (DM_VERITY_FEC_RSM - DM_VERITY_FEC_MAX_RSN) || +- num_c > (DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN)) { ++ num_c < DM_VERITY_FEC_MIN_ROOTS || ++ num_c > DM_VERITY_FEC_MAX_ROOTS) { + ti->error = "Invalid " DM_VERITY_OPT_FEC_ROOTS; + return -EINVAL; + } +diff --git a/drivers/md/dm-verity-fec.h b/drivers/md/dm-verity-fec.h +index 90a0af3f35d31a..b3460103e0e109 100644 +--- a/drivers/md/dm-verity-fec.h ++++ b/drivers/md/dm-verity-fec.h +@@ -13,8 +13,8 @@ + + /* Reed-Solomon(M, N) parameters */ + #define DM_VERITY_FEC_RSM 255 +-#define DM_VERITY_FEC_MAX_RSN 253 +-#define DM_VERITY_FEC_MIN_RSN 231 /* ~10% space overhead */ ++#define DM_VERITY_FEC_MIN_ROOTS 2 /* RS(255, 253): ~0.8% space overhead */ ++#define DM_VERITY_FEC_MAX_ROOTS 24 /* RS(255, 231): ~10% space overhead */ + + /* buffers for deinterleaving and decoding */ + #define DM_VERITY_FEC_BUF_PREALLOC 1 /* buffers to preallocate */ +@@ -50,8 +50,7 @@ struct dm_verity_fec { + /* per-bio data */ + struct dm_verity_fec_io { + struct rs_control *rs; /* Reed-Solomon state */ +- /* erasures for decode_rs8 */ +- int erasures[DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN + 1]; ++ int erasures[DM_VERITY_FEC_MAX_ROOTS + 1]; /* erasures for decode_rs8 */ + u8 *bufs[DM_VERITY_FEC_BUF_MAX]; /* bufs for deinterleaving */ + unsigned int nbufs; /* number of buffers allocated */ + u8 *output; /* buffer for corrected output */ +-- +2.53.0 + diff --git a/queue-6.18/dm-verity-fix-buffer-overflow-in-fec-calculation.patch b/queue-6.18/dm-verity-fix-buffer-overflow-in-fec-calculation.patch new file mode 100644 index 0000000000..c0c8d19525 --- /dev/null +++ b/queue-6.18/dm-verity-fix-buffer-overflow-in-fec-calculation.patch @@ -0,0 +1,76 @@ +From e4db88b48fb6a9ca5ee904c1b6cd92839dbbfa4c Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 22:19:09 -0700 +Subject: dm-verity: fix buffer overflow in FEC calculation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Mikulas Patocka + +commit 31d6e6c0ba8d5a7bd59660035a089307100c5e8e upstream. + +There's a buffer overflow in dm-verity-fec: + +if (neras && *neras <= v->fec->roots) + fio->erasures[(*neras)++] = i; + +This allows *neras to reach roots + 1 (the post-increment pushes it past +roots). This value is then passed as no_eras to decode_rs8(). Inside the +RS decoder (lib/reed_solomon/decode_rs.c:113-121), the erasure locator +polynomial loop writes lambda[j] where j can reach nroots + 1 — one +element past the end of lambda[] (which is sized nroots + 1, valid +indices 0..nroots). The out-of-bounds write lands on syn[0], corrupting +the syndrome buffer. + +Signed-off-by: Mikulas Patocka +Assisted-by: Claude:claude-opus-4-6 +Cc: stable@vger.kernel.org +Fixes: a739ff3f543a ("dm verity: add support for forward error correction") +Reviewed-by: Sami Tolvanen +Signed-off-by: Mikulas Patocka +Signed-off-by: Eric Biggers +Signed-off-by: Sasha Levin +--- + drivers/md/dm-verity-fec.c | 4 ++-- + drivers/md/dm-verity-fec.h | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/drivers/md/dm-verity-fec.c b/drivers/md/dm-verity-fec.c +index cebcc8fd25d700..d1e4fbc5a21d9b 100644 +--- a/drivers/md/dm-verity-fec.c ++++ b/drivers/md/dm-verity-fec.c +@@ -250,7 +250,7 @@ static int fec_read_bufs(struct dm_verity *v, struct dm_verity_io *io, + (unsigned long long)block, PTR_ERR(bbuf)); + + /* assume the block is corrupted */ +- if (neras && *neras <= v->fec->roots) ++ if (neras && *neras < v->fec->roots) + fio->erasures[(*neras)++] = i; + + continue; +@@ -268,7 +268,7 @@ static int fec_read_bufs(struct dm_verity *v, struct dm_verity_io *io, + * skip if we have already found the theoretical + * maximum number (i.e. fec->roots) of erasures + */ +- if (neras && *neras <= v->fec->roots && ++ if (neras && *neras < v->fec->roots && + fec_is_erasure(v, io, want_digest, bbuf)) + fio->erasures[(*neras)++] = i; + } +diff --git a/drivers/md/dm-verity-fec.h b/drivers/md/dm-verity-fec.h +index b3460103e0e109..8552b5d3c91527 100644 +--- a/drivers/md/dm-verity-fec.h ++++ b/drivers/md/dm-verity-fec.h +@@ -50,7 +50,7 @@ struct dm_verity_fec { + /* per-bio data */ + struct dm_verity_fec_io { + struct rs_control *rs; /* Reed-Solomon state */ +- int erasures[DM_VERITY_FEC_MAX_ROOTS + 1]; /* erasures for decode_rs8 */ ++ int erasures[DM_VERITY_FEC_MAX_ROOTS]; /* erasures for decode_rs8 */ + u8 *bufs[DM_VERITY_FEC_BUF_MAX]; /* bufs for deinterleaving */ + unsigned int nbufs; /* number of buffers allocated */ + u8 *output; /* buffer for corrected output */ +-- +2.53.0 + diff --git a/queue-6.18/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch b/queue-6.18/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch new file mode 100644 index 0000000000..ebbf776411 --- /dev/null +++ b/queue-6.18/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch @@ -0,0 +1,107 @@ +From 4085c33cb874858a1fdcc8df79dc6342fa2c7c9f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 23:33:40 -0700 +Subject: drm/amd/display: Fix DTB DTO updates breaking live pixel rate sources + +From: Harry Wentland + +commit 76a2db58e95e328007043f54ac3c7336ccbee440 upstream. + +dcn32_update_clocks_update_dtb_dto() and its dcn35 counterpart reprogram +the DTB DTO of every timing generator in the context whenever the DTBCLK +reference changes, passing a zeroed pixel rate and never setting +is_hdmi. Both dccg set_dtbclk_dto() implementations treat a zero pixel +rate as a disable request. On dcn32 that branch drives PIPE_DTO_SRC_SEL +to the DP DTO source, so a timing generator actively scanning out an +HDMI stream has its pixel rate source re-muxed out from under the live +raster and the OTG stops on the spot. On dcn35 it clears +DTBCLK_DTO_ENABLE and restores DTBCLK_Pn clock gating, which does the +same to a live 128b/132b stream. + +Two displays where only one runs a 128b/132b link hit this reliably. +is_dtbclk_required() holds the DTBCLK reference high while both are +active, and the moment the 128b/132b stream is torn down (compositor +switch, display disable, hot-unplug) the next safe_to_lower pass drops +the reference to the lowest DPM level and the DTO walk freezes the +surviving screen. On Navi31 the DAL mailbox then goes deaf on the +DISPCLK hard-min that follows the walk in dcn32_update_clocks(), +stranding both SMU mailboxes until reboot. + +Set is_hdmi for HDMI and DVI signals so the disable path leaves the +pixel rate source selection on the HDMI path, and pass the real pixel +rate for 128b/132b streams so a reference change rescales their DTO +instead of disabling it. + +Fixes: 128c1ca0303f ("drm/amd/display: Update DTBCLK for DCN32") +Fixes: 8774029f76b9 ("drm/amd/display: Add DCN35 CLK_MGR") +Signed-off-by: Harry Wentland +Reviewed-by: Fangzhi Zuo +Tested-by: Dan Wheeler +Signed-off-by: Alex Deucher +[ mschwartz: dcn32 and dcn35 clk_mgr hunks only. The rest is HDMI FRL + enablement, absent before 7.2, so the FRL conditions and the + req_audio_dtbclk_khz assignment they guard are dropped and the + FRL-centric changelog is rewritten. Added the pipe_ctx->stream check + the new dereferences need. ] +Signed-off-by: Matthew Schwartz +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c | 9 ++++++++- + .../gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c | 9 ++++++++- + 2 files changed, 16 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c +index 7da7b41bd09256..b98d946a8d2ee6 100644 +--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c ++++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c +@@ -276,13 +276,20 @@ static void dcn32_update_clocks_update_dtb_dto(struct clk_mgr_internal *clk_mgr, + struct dtbclk_dto_params dto_params = {0}; + + /* use mask to program DTO once per tg */ +- if (pipe_ctx->stream_res.tg && ++ if (pipe_ctx->stream && pipe_ctx->stream_res.tg && + !(tg_mask & (1 << pipe_ctx->stream_res.tg->inst))) { + tg_mask |= (1 << pipe_ctx->stream_res.tg->inst); + + dto_params.otg_inst = pipe_ctx->stream_res.tg->inst; + dto_params.ref_dtbclk_khz = ref_dtbclk_khz; + ++ if (dccg->ctx->dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) ++ dto_params.pixclk_khz = pipe_ctx->stream->timing.pix_clk_100hz / 10; ++ ++ if (dc_is_hdmi_signal(pipe_ctx->stream->signal) || ++ dc_is_dvi_signal(pipe_ctx->stream->signal)) ++ dto_params.is_hdmi = true; ++ + dccg->funcs->set_dtbclk_dto(clk_mgr->dccg, &dto_params); + //dccg->funcs->set_audio_dtbclk_dto(clk_mgr->dccg, &dto_params); + } +diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c +index 817a0253d10e52..d1785a5c7f85fd 100644 +--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c ++++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c +@@ -264,13 +264,20 @@ static void dcn35_update_clocks_update_dtb_dto(struct clk_mgr_internal *clk_mgr, + struct dtbclk_dto_params dto_params = {0}; + + /* use mask to program DTO once per tg */ +- if (pipe_ctx->stream_res.tg && ++ if (pipe_ctx->stream && pipe_ctx->stream_res.tg && + !(tg_mask & (1 << pipe_ctx->stream_res.tg->inst))) { + tg_mask |= (1 << pipe_ctx->stream_res.tg->inst); + + dto_params.otg_inst = pipe_ctx->stream_res.tg->inst; + dto_params.ref_dtbclk_khz = ref_dtbclk_khz; + ++ if (dccg->ctx->dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) ++ dto_params.pixclk_khz = pipe_ctx->stream->timing.pix_clk_100hz / 10; ++ ++ if (dc_is_hdmi_signal(pipe_ctx->stream->signal) || ++ dc_is_dvi_signal(pipe_ctx->stream->signal)) ++ dto_params.is_hdmi = true; ++ + dccg->funcs->set_dtbclk_dto(clk_mgr->dccg, &dto_params); + //dccg->funcs->set_audio_dtbclk_dto(clk_mgr->dccg, &dto_params); + } +-- +2.53.0 + diff --git a/queue-6.18/drm-xe-uapi-reject-coh_none-pat-index-for-cpu_addr_m.patch b/queue-6.18/drm-xe-uapi-reject-coh_none-pat-index-for-cpu_addr_m.patch new file mode 100644 index 0000000000..43924472b2 --- /dev/null +++ b/queue-6.18/drm-xe-uapi-reject-coh_none-pat-index-for-cpu_addr_m.patch @@ -0,0 +1,75 @@ +From 821812229a4be5ee65ccc501052d8727f2937cfc Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 14:37:59 +0000 +Subject: drm/xe/uapi: Reject coh_none PAT index for CPU_ADDR_MIRROR + +From: Jia Yao + +commit 4d58d7535e826a3175527b6174502f0db319d7f6 upstream. + +Add validation in xe_vm_bind_ioctl() to reject PAT indices +with XE_COH_NONE coherency mode when used with +DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR. + +CPU address mirror mappings use system memory that is CPU +cached, which makes them incompatible with COH_NONE PAT +indices. Allowing COH_NONE with CPU cached buffers is a +security risk, as the GPU may bypass CPU caches and read +stale sensitive data from DRAM. + +Although CPU_ADDR_MIRROR does not create an immediate +mapping, the backing system memory is still CPU cached. +Apply the same PAT coherency restrictions as +DRM_XE_VM_BIND_OP_MAP_USERPTR. + +v2: +- Correct fix tag + +v6: +- No change + +v7: +- Correct fix tag + +v8: +- Rebase + +v9: +- Limit the restrictions to iGPU + +v10: +- Just add the iGPU logic but keep dGPU logic + +Fixes: b43e864af0d4 ("drm/xe/uapi: Add DRM_XE_VM_BIND_FLAG_CPU_ADDR_MIRROR") +Cc: # v6.15+ +Cc: Shuicheng Lin +Cc: Mathew Alwin +Cc: Michal Mrozek +Cc: Matthew Brost +Cc: Matthew Auld +Signed-off-by: Jia Yao +Reviewed-by: Matthew Auld +Acked-by: Michal Mrozek +Signed-off-by: Matthew Auld +Link: https://patch.msgid.link/20260417055917.2027459-3-jia.yao@intel.com +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/xe/xe_vm.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c +index b00da90e399135..ed0c8acd0fe27c 100644 +--- a/drivers/gpu/drm/xe/xe_vm.c ++++ b/drivers/gpu/drm/xe/xe_vm.c +@@ -3393,6 +3393,8 @@ static int vm_bind_ioctl_check_args(struct xe_device *xe, struct xe_vm *vm, + op == DRM_XE_VM_BIND_OP_MAP_USERPTR) || + XE_IOCTL_DBG(xe, coh_mode == XE_COH_NONE && + op == DRM_XE_VM_BIND_OP_MAP_USERPTR) || ++ XE_IOCTL_DBG(xe, !IS_DGFX(xe) && coh_mode == XE_COH_NONE && ++ is_cpu_addr_mirror) || + XE_IOCTL_DBG(xe, op == DRM_XE_VM_BIND_OP_MAP_USERPTR && + !IS_ENABLED(CONFIG_DRM_GPUSVM)) || + XE_IOCTL_DBG(xe, obj && +-- +2.53.0 + diff --git a/queue-6.18/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch b/queue-6.18/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch new file mode 100644 index 0000000000..9c75ed599e --- /dev/null +++ b/queue-6.18/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch @@ -0,0 +1,261 @@ +From 9fd3191eb8cae8af20fd863d7e121c8d49c98c78 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 08:19:05 -0700 +Subject: fscrypt: Avoid dynamic allocation in fscrypt_get_devices() + +From: Eric Biggers + +commit 6fe4e4b8259e1330945b5f3c9476e08473b8e0e8 upstream. + +When a blk_crypto_key starts being used or is evicted, fs/crypto/ calls +fscrypt_get_devices() to get the filesystem's list of block devices, +then iterates over them and calls blk_crypto_config_supported(), +blk_crypto_start_using_key(), or blk_crypto_evict_key() on each one. + +Currently, the block device pointers are placed in a dynamically +allocated array. This dynamic allocation is problematic because: + +- It can fail, especially at the fscrypt_destroy_inline_crypt_key() call + site when it's invoked for inode eviction under direct reclaim. + +- fscrypt_destroy_inline_crypt_key() doesn't handle the failure. It + just zeroizes and frees the blk_crypto_key without calling + blk_crypto_evict_key(). That causes a use-after-free. + +For now, let's fix this in the straightforward and easily-backportable +way by switching to an on-stack array. Currently the fscrypt +multi-device functionality is used only by f2fs, which has a hardcoded +limit of 8 block devices. An on-stack array works fine for that. + +(Of course, this solution won't scale up to large number of block +devices. For that we'd need a different solution, like moving the block +device iteration into the filesystem. Or in the case of btrfs, which +will only support blk-crypto-fallback, we should make it just call +blk-crypto-fallback directly, so the block devices won't be needed.) + +Fixes: 22e9947a4b2b ("fscrypt: stop holding extra request_queue references") +Cc: stable@vger.kernel.org +Reported-by: Sashiko +Closes: https://sashiko.dev/#/patchset/20260713023708.9245-1-ebiggers%40kernel.org +Reviewed-by: Christoph Hellwig +Link: https://patch.msgid.link/20260719055602.78828-1-ebiggers@kernel.org +Signed-off-by: Eric Biggers +Signed-off-by: Sasha Levin +--- + fs/crypto/inline_crypt.c | 57 ++++++++++++++-------------------------- + fs/f2fs/super.c | 25 ++++++++++-------- + include/linux/fscrypt.h | 18 +++++++------ + 3 files changed, 44 insertions(+), 56 deletions(-) + +diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c +index 645cc493607294..500397ca8a26fa 100644 +--- a/fs/crypto/inline_crypt.c ++++ b/fs/crypto/inline_crypt.c +@@ -22,22 +22,14 @@ + + #include "fscrypt_private.h" + +-static struct block_device **fscrypt_get_devices(struct super_block *sb, +- unsigned int *num_devs) ++static unsigned int ++fscrypt_get_devices(struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]) + { +- struct block_device **devs; +- +- if (sb->s_cop->get_devices) { +- devs = sb->s_cop->get_devices(sb, num_devs); +- if (devs) +- return devs; +- } +- devs = kmalloc(sizeof(*devs), GFP_KERNEL); +- if (!devs) +- return ERR_PTR(-ENOMEM); ++ if (sb->s_cop->get_devices) ++ return sb->s_cop->get_devices(sb, devs); + devs[0] = sb->s_bdev; +- *num_devs = 1; +- return devs; ++ return 1; + } + + static unsigned int fscrypt_get_dun_bytes(const struct fscrypt_inode_info *ci) +@@ -96,7 +88,7 @@ int fscrypt_select_encryption_impl(struct fscrypt_inode_info *ci, + const struct inode *inode = ci->ci_inode; + struct super_block *sb = inode->i_sb; + struct blk_crypto_config crypto_cfg; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + +@@ -135,20 +127,15 @@ int fscrypt_select_encryption_impl(struct fscrypt_inode_info *ci, + crypto_cfg.key_type = is_hw_wrapped_key ? + BLK_CRYPTO_KEY_TYPE_HW_WRAPPED : BLK_CRYPTO_KEY_TYPE_RAW; + +- devs = fscrypt_get_devices(sb, &num_devs); +- if (IS_ERR(devs)) +- return PTR_ERR(devs); +- ++ num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) { + if (!blk_crypto_config_supported(devs[i], &crypto_cfg)) +- goto out_free_devs; ++ return 0; + } + + fscrypt_log_blk_crypto_impl(ci->ci_mode, devs, num_devs, &crypto_cfg); + + ci->ci_inlinecrypt = true; +-out_free_devs: +- kfree(devs); + + return 0; + } +@@ -164,7 +151,7 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, + enum blk_crypto_key_type key_type = is_hw_wrapped ? + BLK_CRYPTO_KEY_TYPE_HW_WRAPPED : BLK_CRYPTO_KEY_TYPE_RAW; + struct blk_crypto_key *blk_key; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + int err; +@@ -182,17 +169,12 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, + } + + /* Start using blk-crypto on all the filesystem's block devices. */ +- devs = fscrypt_get_devices(sb, &num_devs); +- if (IS_ERR(devs)) { +- err = PTR_ERR(devs); +- goto fail; +- } ++ num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) { + err = blk_crypto_start_using_key(devs[i], blk_key); + if (err) + break; + } +- kfree(devs); + if (err) { + fscrypt_err(inode, "error %d starting to use blk-crypto", err); + goto fail; +@@ -210,20 +192,21 @@ void fscrypt_destroy_inline_crypt_key(struct super_block *sb, + struct fscrypt_prepared_key *prep_key) + { + struct blk_crypto_key *blk_key = prep_key->blk_key; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + + if (!blk_key) + return; + +- /* Evict the key from all the filesystem's block devices. */ +- devs = fscrypt_get_devices(sb, &num_devs); +- if (!IS_ERR(devs)) { +- for (i = 0; i < num_devs; i++) +- blk_crypto_evict_key(devs[i], blk_key); +- kfree(devs); +- } ++ /* ++ * Evict the key from all the filesystem's block devices. ++ * This *must* be done before the key is freed. ++ */ ++ num_devs = fscrypt_get_devices(sb, devs); ++ for (i = 0; i < num_devs; i++) ++ blk_crypto_evict_key(devs[i], blk_key); ++ + kfree_sensitive(blk_key); + } + +diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c +index f6b75ce11d1c1a..6f787cbeefd096 100644 +--- a/fs/f2fs/super.c ++++ b/fs/f2fs/super.c +@@ -3659,24 +3659,27 @@ static bool f2fs_has_stable_inodes(struct super_block *sb) + return true; + } + +-static struct block_device **f2fs_get_devices(struct super_block *sb, +- unsigned int *num_devs) ++static unsigned int ++f2fs_get_devices(struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]) + { + struct f2fs_sb_info *sbi = F2FS_SB(sb); +- struct block_device **devs; ++ int ndevs; + int i; + +- if (!f2fs_is_multi_device(sbi)) +- return NULL; ++ static_assert(MAX_DEVICES <= FSCRYPT_MAX_DEVICES); + +- devs = kmalloc_array(sbi->s_ndevs, sizeof(*devs), GFP_KERNEL); +- if (!devs) +- return ERR_PTR(-ENOMEM); ++ if (!f2fs_is_multi_device(sbi)) { ++ devs[0] = sb->s_bdev; ++ return 1; ++ } ++ ndevs = sbi->s_ndevs; ++ if (WARN_ON_ONCE(ndevs > FSCRYPT_MAX_DEVICES)) ++ ndevs = FSCRYPT_MAX_DEVICES; + +- for (i = 0; i < sbi->s_ndevs; i++) ++ for (i = 0; i < ndevs; i++) + devs[i] = FDEV(i).bdev; +- *num_devs = sbi->s_ndevs; +- return devs; ++ return ndevs; + } + + static const struct fscrypt_operations f2fs_cryptops = { +diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h +index 516aba5b858b54..c009d4afe91857 100644 +--- a/include/linux/fscrypt.h ++++ b/include/linux/fscrypt.h +@@ -57,6 +57,9 @@ struct fscrypt_name { + /* Maximum value for the third parameter of fscrypt_operations.set_context(). */ + #define FSCRYPT_SET_CONTEXT_MAX_SIZE 40 + ++/* Maximum supported number of block devices per filesystem */ ++#define FSCRYPT_MAX_DEVICES 8 ++ + #ifdef CONFIG_FS_ENCRYPTION + + /* Crypto operations for filesystems */ +@@ -181,21 +184,20 @@ struct fscrypt_operations { + bool (*has_stable_inodes)(struct super_block *sb); + + /* +- * Return an array of pointers to the block devices to which the +- * filesystem may write encrypted file contents, NULL if the filesystem +- * only has a single such block device, or an ERR_PTR() on error. ++ * Retrieve the list of block devices to which the filesystem may write ++ * encrypted file contents. + * +- * On successful non-NULL return, *num_devs is set to the number of +- * devices in the returned array. The caller must free the returned +- * array using kfree(). ++ * This writes the block_device pointers to @devs and returns the count ++ * (between 1 and FSCRYPT_MAX_DEVICES inclusively). + * + * If the filesystem can use multiple block devices (other than block + * devices that aren't used for encrypted file contents, such as + * external journal devices), and wants to support inline encryption, + * then it must implement this function. Otherwise it's not needed. + */ +- struct block_device **(*get_devices)(struct super_block *sb, +- unsigned int *num_devs); ++ unsigned int (*get_devices)( ++ struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]); + }; + + int fscrypt_d_revalidate(struct inode *dir, const struct qstr *name, +-- +2.53.0 + diff --git a/queue-6.18/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch b/queue-6.18/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch new file mode 100644 index 0000000000..65321d553b --- /dev/null +++ b/queue-6.18/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch @@ -0,0 +1,108 @@ +From 39af2ca655959402ad7f642cd73c1efba607da83 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:26 +0800 +Subject: ksmbd: bound DACL dedup walk to copied ACEs + +From: Namjae Jeon + +commit 58d97fcd0bf1aee694e244cc28635b9df95b543b upstream. + +set_ntacl_dacl() can stop copying ACEs before consuming the full input +DACL when size accounting overflows. + +When that happens, num_aces reflects only the ACEs that were actually +copied into the output DACL, but set_posix_acl_entries_dacl() still +receives nt_num_aces and uses it to walk the existing ACE array during +dedup. + +That makes the dedup walk scan past the copied ACE array and inspect +buffer tail that does not contain valid ACEs. + +Split the two meanings currently carried by the NT ACE count. Pass the +number of copied NT ACEs to bound the dedup walk, and preserve the +original "input DACL had NT ACEs" state separately for the +Everyone/default ACL fallback. + +This keeps the dedup walk aligned with the ACEs that are actually +present in the rebuilt DACL. + +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index ca0e4aab6dd87f..b0677e095c3292 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -595,7 +595,8 @@ static void parse_dacl(struct mnt_idmap *idmap, + static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + struct smb_ace *pndace, + struct smb_fattr *fattr, u16 *num_aces, +- u16 *size, u32 nt_aces_num) ++ u16 *size, u16 existing_nt_aces, ++ bool had_nt_aces) + { + struct posix_acl_entry *pace; + struct smb_sid *sid; +@@ -627,14 +628,14 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + + gid = posix_acl_gid_translate(idmap, pace); + id_to_sid(gid, SIDUNIX_GROUP, sid); +- } else if (pace->e_tag == ACL_OTHER && !nt_aces_num) { ++ } else if (pace->e_tag == ACL_OTHER && !had_nt_aces) { + smb_copy_sid(sid, &sid_everyone); + } else { + kfree(sid); + continue; + } + ntace = pndace; +- for (j = 0; j < nt_aces_num; j++) { ++ for (j = 0; j < existing_nt_aces; j++) { + if (ntace->sid.sub_auth[ntace->sid.num_subauth - 1] == + sid->sub_auth[sid->num_subauth - 1]) + goto pass_same_sid; +@@ -678,7 +679,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + kfree(sid); + } + +- if (nt_aces_num) ++ if (had_nt_aces) + return; + + posix_default_acl: +@@ -732,6 +733,7 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + { + struct smb_ace *ntace, *pndace; + u16 nt_num_aces = le16_to_cpu(nt_dacl->num_aces), num_aces = 0; ++ u16 copied_nt_aces; + unsigned short size = 0; + int i; + +@@ -765,8 +767,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + } + } + ++ copied_nt_aces = num_aces; + set_posix_acl_entries_dacl(idmap, pndace, fattr, +- &num_aces, &size, nt_num_aces); ++ &num_aces, &size, copied_nt_aces, ++ nt_num_aces != 0); + pndacl->num_aces = cpu_to_le16(num_aces); + pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); + } +@@ -784,7 +788,7 @@ static void set_mode_dacl(struct mnt_idmap *idmap, + + if (fattr->cf_acls) { + set_posix_acl_entries_dacl(idmap, pndace, fattr, +- &num_aces, &size, num_aces); ++ &num_aces, &size, num_aces, false); + goto out; + } + +-- +2.53.0 + diff --git a/queue-6.18/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch b/queue-6.18/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch new file mode 100644 index 0000000000..40937899d0 --- /dev/null +++ b/queue-6.18/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch @@ -0,0 +1,91 @@ +From fa6bae8f4562d2aaea7639b95e8badb13808e45b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:24 +0800 +Subject: ksmbd: restore DACL size on check_add_overflow() to avoid malformed + ACL + +From: Wentao Guan + +commit bbf0a8e931204ecdab494a88d43b0a24a04285c5 upstream. + +check_add_overflow() unconditionally writes the truncated sum into *d +even on overflow, per its contract in include/linux/overflow.h. +The four check_add_overflow() guards in set_posix_acl_entries_dacl() +and set_ntacl_dacl() break out of the ACE-building loops on overflow, +but the truncated *size is then consumed downstream at the end of +set_ntacl_dacl(): + + pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); + +This produces an on-wire NT ACL whose pndacl->size under-reports the +bytes actually written by the preceding fill_ace_for_sid()/memcpy() +calls, yielding a malformed ACL that can trigger out-of-bounds reads +when re-parsed by clients or ksmbd itself. + +Restore *size to its pre-addition value on each overflow branch (via +`*size -= ace_sz` / `size -= nt_ace_size`) so that after the break, +*size once again holds the cumulative size of the successfully-written +ACEs. The committed ACL is then truncated-but-self-consistent rather +than malformed. + +The ksmbd DACL builders are the only check_add_overflow() sites found +where an overflow path breaks out of a loop and the destination value +is consumed afterward. The other nearby break-style cases either +return -EINVAL on overflow (transport_ipc.c) or break without +consuming the overflowed destination value afterward (buildid.c). + +Fixes: 299f962c0b02 ("ksmbd: use check_add_overflow() to prevent u16 DACL size overflow") +Assisted-by: atomcode:glm-5.2 +Assisted-by: Codex:gpt-5.5 +Cc: stable@vger.kernel.org +Signed-off-by: Wentao Guan +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index 173469d112f101..ca0e4aab6dd87f 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -649,6 +649,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, flags, + pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -663,6 +664,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, + 0x03, pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -708,6 +710,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, 0x0b, + pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -750,8 +753,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + goto next_ace; + + memcpy((char *)pndace + size, ntace, nt_ace_size); +- if (check_add_overflow(size, nt_ace_size, &size)) ++ if (check_add_overflow(size, nt_ace_size, &size)) { ++ size -= nt_ace_size; + break; ++ } + num_aces++; + + next_ace: +-- +2.53.0 + diff --git a/queue-6.18/ksmbd-validate-ace-size-against-sid-sub-authorities.patch b/queue-6.18/ksmbd-validate-ace-size-against-sid-sub-authorities.patch new file mode 100644 index 0000000000..db3a9671f9 --- /dev/null +++ b/queue-6.18/ksmbd-validate-ace-size-against-sid-sub-authorities.patch @@ -0,0 +1,66 @@ +From 3837abbd7fee312caf7a7f1d59a930820304bcea Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:29 +0800 +Subject: ksmbd: validate ACE size against SID sub-authorities + +From: Namjae Jeon + +commit 5152c6d49e3fd4e9f2e857c57527aead752f1f87 upstream. + +set_ntacl_dacl() validates sid.num_subauth before copying an ACE, but +does not verify that the declared ACE size contains all sub-authorities +described by that field. An undersized ACE can therefore be copied +and later make the POSIX ACL deduplication walk inspect data beyond +the copied ACE boundary. + +The existing initial bound check is also too small. It only ensures +that the ACE size field is accessible before set_ntacl_dacl() reads +sid.num_subauth farther into the input buffer. + +Require enough input for the fixed SID header before accessing +num_subauth, reject ACEs smaller than that header, and skip ACEs +whose declared size cannot contain the complete SID. This makes the +validation consistent with the other ACE walk paths. + +Reported-by: LocalHost +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index b0677e095c3292..d3d0a22620f96d 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -743,15 +743,22 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + for (i = 0; i < nt_num_aces; i++) { + unsigned short nt_ace_size; + +- if (offsetof(struct smb_ace, access_req) > aces_size) ++ if (aces_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + + nt_ace_size = le16_to_cpu(ntace->size); +- if (nt_ace_size > aces_size) ++ if (nt_ace_size > aces_size || ++ nt_ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + + if (ntace->sid.num_subauth == 0 || +- ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES || ++ nt_ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE + ++ sizeof(__le32) * ++ ntace->sid.num_subauth) + goto next_ace; + + memcpy((char *)pndace + size, ntace, nt_ace_size); +-- +2.53.0 + diff --git a/queue-6.18/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch b/queue-6.18/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch new file mode 100644 index 0000000000..24102fd941 --- /dev/null +++ b/queue-6.18/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch @@ -0,0 +1,75 @@ +From 7ea8988d4f8530f73e1f589a9dfd0e31ed0557d4 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:22 +0800 +Subject: ksmbd: validate num_subauth when copying ACE in set_ntacl_dacl + +From: Haofeng Li + +commit 47f0b34f6bc98ed85bfdc293e8f3e432ec24958d upstream. + +set_ntacl_dacl() copies each ACE from the attacker-controlled stored +security descriptor verbatim into the response DACL without checking +sid.num_subauth. The ACE bytes (including an unchecked num_subauth) +originate from an authenticated SMB2_SET_INFO(SecInfo=DACL) that is +stored raw via ksmbd_vfs_set_sd_xattr(); parse_dacl() rejects a bad ACE +with `break` rather than an error, so parse_sec_desc() still returns +success and the malformed SD reaches the xattr intact. + +On a subsequent SMB2_QUERY_INFO(SecInfo=DACL) for an inode carrying a +POSIX access ACL, build_sec_desc() -> set_ntacl_dacl() -> +set_posix_acl_entries_dacl() walks the copied ACEs and reads + + ntace->sid.sub_auth[ntace->sid.num_subauth - 1] + +with num_subauth taken straight from the stored SD. Since sub_auth[] +is fixed at SID_MAX_SUB_AUTHORITIES (15), a crafted num_subauth (e.g. +255) drives an out-of-bounds heap read of ~1 KB with an offset fully +controlled by an authenticated client. + +The sibling functions already gate this field: + parse_dacl() -- num_subauth == 0 || > SID_MAX_SUB_AUTHORITIES + parse_sid() -- num_subauth > SID_MAX_SUB_AUTHORITIES + smb_copy_sid() -- min_t(u8, num_subauth, SID_MAX_SUB_AUTHORITIES) +set_ntacl_dacl() is the lone inconsistent path that omits the check. + +Add the same num_subauth validation in set_ntacl_dacl() before copying +the ACE, matching the gate already enforced by parse_dacl(). + +Signed-off-by: Haofeng Li +Reviewed-by: ChenXiaoSong +Suggested-by: Namjae Jeon +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index b09bc8d9389a2a..173469d112f101 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -745,12 +745,18 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + if (nt_ace_size > aces_size) + break; + ++ if (ntace->sid.num_subauth == 0 || ++ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ goto next_ace; ++ + memcpy((char *)pndace + size, ntace, nt_ace_size); + if (check_add_overflow(size, nt_ace_size, &size)) + break; ++ num_aces++; ++ ++next_ace: + aces_size -= nt_ace_size; + ntace = (struct smb_ace *)((char *)ntace + nt_ace_size); +- num_aces++; + } + } + +-- +2.53.0 + diff --git a/queue-6.18/net-qrtr-ns-raise-node-count-limit-to-512.patch b/queue-6.18/net-qrtr-ns-raise-node-count-limit-to-512.patch new file mode 100644 index 0000000000..25b23db7a9 --- /dev/null +++ b/queue-6.18/net-qrtr-ns-raise-node-count-limit-to-512.patch @@ -0,0 +1,41 @@ +From 18d67bbb41d94a682b51e189b14ad14cc2dbf220 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 17:02:11 +0200 +Subject: net: qrtr: ns: Raise node count limit to 512 + +From: Youssef Samir + +commit ff194cffd586cbd4cc49eccb002c65f2a902a277 upstream. + +The current node limit of 64 breaks the functionality for a number of AI200 +deployments that have up to 384 nodes. Raise the limit to 512. + +Fixes: 27d5e84e810b ("net: qrtr: ns: Limit the total number of nodes") +Cc: stable@vger.kernel.org +Signed-off-by: Youssef Samir +Link: https://patch.msgid.link/20260713145901.212396-1-youssef.abdulrahman@oss.qualcomm.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + net/qrtr/ns.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/net/qrtr/ns.c b/net/qrtr/ns.c +index a10e8de4f7e105..e8e3ba1b3b8239 100644 +--- a/net/qrtr/ns.c ++++ b/net/qrtr/ns.c +@@ -85,9 +85,9 @@ struct qrtr_node { + /* Max nodes limit is chosen based on the current platform requirements. + * If the requirement changes in the future, this value can be increased. + */ +-#define QRTR_NS_MAX_NODES 64 ++#define QRTR_NS_MAX_NODES 512 + +-static u8 node_count; ++static u16 node_count; + + static struct qrtr_node *node_get(unsigned int node_id) + { +-- +2.53.0 + diff --git a/queue-6.18/series b/queue-6.18/series index 37e96a8159..1d45ec6d6c 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -595,3 +595,17 @@ drm-amdgpu-invoke-pm_genpd_remove-before-freeing-genpd.patch drm-amdgpu-fix-aperture-mapping-leak.patch drm-amd-pm-fix-smu13-power-limit-range-calculation.patch drm-amdgpu-fix-check-in-amdgpu_hmm_invalidate_gfx.patch +bpf-fix-same-register-dst-src-oob-read-and-pointer-l.patch +dm-verity-fec-fix-the-size-of-dm_verity_fec_io-erasu.patch +dm-verity-fec-fix-reading-parity-bytes-split-across-.patch +dm-verity-fec-replace-max-min-_rsn-with-min-max-_roo.patch +dm-verity-fix-buffer-overflow-in-fec-calculation.patch +drm-xe-uapi-reject-coh_none-pat-index-for-cpu_addr_m.patch +ublk-wait-on-ublk_dev_ready-instead-of-ub-completion.patch +net-qrtr-ns-raise-node-count-limit-to-512.patch +ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch +ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch +ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch +ksmbd-validate-ace-size-against-sid-sub-authorities.patch +fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch +drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch diff --git a/queue-6.18/ublk-wait-on-ublk_dev_ready-instead-of-ub-completion.patch b/queue-6.18/ublk-wait-on-ublk_dev_ready-instead-of-ub-completion.patch new file mode 100644 index 0000000000..71e724ab1d --- /dev/null +++ b/queue-6.18/ublk-wait-on-ublk_dev_ready-instead-of-ub-completion.patch @@ -0,0 +1,190 @@ +From a6ec00fd3b4005da76c38cbcf2c9309f8ad59c2b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 09:48:06 -0500 +Subject: ublk: wait on ublk_dev_ready() instead of ub->completion + +From: Ming Lei + +commit 432a9b2780c0a01caf547bd1fc2fcf28aeb8d173 upstream. + +ub->completion is only re-armed by a successful START_USER_RECOVERY. If +the ublk server sends END_USER_RECOVERY without one - e.g. its START +failed with -EBUSY and the error was ignored - the wait is satisfied by +the stale completion of the previous recovery cycle, and the device is +marked LIVE and the requeue list kicked while the FETCH stream is still +running and ubq->canceling is still set. The kick redispatches a +previously requeued request, __ublk_queue_rq_common() sees ->canceling +and parks it again via __ublk_abort_rq(), and after the last FETCH +clears ->canceling nothing ever kicks the requeue list again: the +request is stranded there while holding its tag. If it is the flush +machinery's flush_rq, every subsequent fsync piles up in uninterruptible +sleep and teardown hangs on tag draining. This matches a report of a +lost PREFLUSH with ext4 on top of ublk after daemon crash recovery. + +ub->completion is an edge-triggered latch used as a proxy for the level +condition "every queue has fetched all I/O commands", which can regress +(F_BATCH's UNPREP, daemon death) and whose re-arm can be skipped. Drop +it and wait on the real condition instead: the new helper +ublk_wait_dev_ready_and_lock() waits on ublk_dev_ready() via +wait_var_event_interruptible(), woken from ublk_mark_io_ready(), then +re-checks it under ub->mutex, waiting again on regression, and returns +with the mutex held and readiness guaranteed. + +Readiness becomes true in the same ub->mutex critical section that +clears the last queue's ->canceling, so END_USER_RECOVERY marks the +device LIVE and kicks the requeue list strictly after ->canceling +clears. The wait stays interruptible, so a server whose daemon died can +still be signalled out. For ublk_ctrl_start_dev() this replaces the +fail-fast -EINVAL on an F_BATCH ready->UNPREP regression with waiting +until the device is ready again. + +Reported-by: George Salisbury +Fixes: 728cbac5fe21 ("ublk: move device reset into ublk_ch_release()") +Cc: stable@vger.kernel.org +Signed-off-by: Ming Lei +Link: https://patch.msgid.link/20260719134540.120269-1-tom.leiming@gmail.com +Signed-off-by: Jens Axboe + +[ 6.18.y tracks readiness per fetched I/O command in ->nr_io_ready rather + than per queue in ->nr_queue_ready, so wait and wake on that counter; + ->canceling is cleared by ublk_reset_io_flags(), which stays in + ublk_mark_io_ready(). UBLK_F_BATCH_IO does not exist here, so + ublk_ctrl_start_dev() has no F_BATCH readiness re-check to replace, and + readiness can only regress via ublk_reset_ch_dev() on daemon death. ] + +Signed-off-by: Sasha Levin +--- + drivers/block/ublk_drv.c | 52 ++++++++++++++++++++++++++++------------ + 1 file changed, 37 insertions(+), 15 deletions(-) + +diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c +index c339222513b03c..cb31e96f01cdd6 100644 +--- a/drivers/block/ublk_drv.c ++++ b/drivers/block/ublk_drv.c +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -26,7 +27,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -230,7 +230,6 @@ struct ublk_device { + + struct ublk_params params; + +- struct completion completion; + u32 nr_io_ready; + bool unprivileged_daemons; + struct mutex cancel_mutex; +@@ -2150,9 +2149,13 @@ static void ublk_mark_io_ready(struct ublk_device *ub) + + ub->nr_io_ready++; + if (ublk_dev_ready(ub)) { +- /* now we are ready for handling ublk io request */ ++ /* ++ * now we are ready for handling ublk io request, clear ++ * device-level canceling flag and wake ublk_dev_ready() ++ * waiters ++ */ + ublk_reset_io_flags(ub); +- complete_all(&ub->completion); ++ wake_up_var(&ub->nr_io_ready); + } + } + +@@ -2829,7 +2832,6 @@ static int ublk_init_queues(struct ublk_device *ub) + goto fail; + } + +- init_completion(&ub->completion); + return 0; + + fail: +@@ -2966,6 +2968,26 @@ static bool ublk_validate_user_pid(struct ublk_device *ub, pid_t ublksrv_pid) + return ub->ublksrv_tgid == ublksrv_pid; + } + ++/* ++ * Wait until all queues have fetched their I/O commands, and return with ++ * ub->mutex held and readiness guaranteed: then every queue's ->canceling ++ * is cleared. Ready may regress between wakeup and mutex_lock() (daemon ++ * death), so re-check it under the mutex and wait again. ++ */ ++static int ublk_wait_dev_ready_and_lock(struct ublk_device *ub) ++{ ++ while (true) { ++ if (wait_var_event_interruptible(&ub->nr_io_ready, ++ ublk_dev_ready(ub))) ++ return -EINTR; ++ ++ mutex_lock(&ub->mutex); ++ if (ublk_dev_ready(ub)) ++ return 0; ++ mutex_unlock(&ub->mutex); ++ } ++} ++ + static int ublk_ctrl_start_dev(struct ublk_device *ub, + const struct ublksrv_ctrl_cmd *header) + { +@@ -3031,13 +3053,13 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, + lim.max_segments = ub->params.seg.max_segments; + } + +- if (wait_for_completion_interruptible(&ub->completion) != 0) ++ if (ublk_wait_dev_ready_and_lock(ub)) + return -EINTR; + +- if (!ublk_validate_user_pid(ub, ublksrv_pid)) +- return -EINVAL; +- +- mutex_lock(&ub->mutex); ++ if (!ublk_validate_user_pid(ub, ublksrv_pid)) { ++ ret = -EINVAL; ++ goto out_unlock; ++ } + if (ub->dev_info.state == UBLK_S_DEV_LIVE || + test_bit(UB_STATE_USED, &ub->state)) { + ret = -EEXIST; +@@ -3549,7 +3571,6 @@ static int ublk_ctrl_start_recovery(struct ublk_device *ub, + goto out_unlock; + } + pr_devel("%s: start recovery for dev id %d.\n", __func__, header->dev_id); +- init_completion(&ub->completion); + ret = 0; + out_unlock: + mutex_unlock(&ub->mutex); +@@ -3565,16 +3586,17 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub, + pr_devel("%s: Waiting for all FETCH_REQs, dev id %d...\n", __func__, + header->dev_id); + +- if (wait_for_completion_interruptible(&ub->completion)) ++ if (ublk_wait_dev_ready_and_lock(ub)) + return -EINTR; + + pr_devel("%s: All FETCH_REQs received, dev id %d\n", __func__, + header->dev_id); + +- if (!ublk_validate_user_pid(ub, ublksrv_pid)) +- return -EINVAL; ++ if (!ublk_validate_user_pid(ub, ublksrv_pid)) { ++ ret = -EINVAL; ++ goto out_unlock; ++ } + +- mutex_lock(&ub->mutex); + if (ublk_nosrv_should_stop_dev(ub)) + goto out_unlock; + +-- +2.53.0 + diff --git a/queue-6.6/bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch b/queue-6.6/bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch new file mode 100644 index 0000000000..b0b2628c52 --- /dev/null +++ b/queue-6.6/bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch @@ -0,0 +1,39 @@ +From cd1ca230d9660cad7b34efb020f95b7f97ffe49a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 22:51:57 +0800 +Subject: bpf: drop bpf_lsm_getselfattr from hook list + +From: Wentao Guan + +Backport ("bpf, lsm: Add disabled BPF LSM hook list") for v6.6.y bring the +warning "WARN: resolve_btfids: unresolved symbol bpf_lsm_getselfattr". + +The lsm_getselfattr from commit a04a1198088a +("LSM: syscalls for current process attributes"), no need to backport +the huge patch, simply drop the entry to fix the noise. + +This is a fix for stable v6.6.145 backport commit, so no upstream commit. + +Fixes: b3b4719429d5 ("bpf, lsm: Add disabled BPF LSM hook list") +Link: https://lore.kernel.org/stable/20260728225520.stable-0003@kernel.org/ +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + kernel/bpf/bpf_lsm.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c +index 147c3a8ad4e8e0..984e944a0221d9 100644 +--- a/kernel/bpf/bpf_lsm.c ++++ b/kernel/bpf/bpf_lsm.c +@@ -42,7 +42,6 @@ BTF_ID(func, bpf_lsm_inode_need_killpriv) + BTF_ID(func, bpf_lsm_inode_getsecurity) + BTF_ID(func, bpf_lsm_inode_listsecurity) + BTF_ID(func, bpf_lsm_inode_copy_up_xattr) +-BTF_ID(func, bpf_lsm_getselfattr) + BTF_ID(func, bpf_lsm_getprocattr) + BTF_ID(func, bpf_lsm_setprocattr) + #ifdef CONFIG_KEYS +-- +2.53.0 + diff --git a/queue-6.6/drm-amd-display-fix-dcn32-dtb-dto-update-breaking-li.patch b/queue-6.6/drm-amd-display-fix-dcn32-dtb-dto-update-breaking-li.patch new file mode 100644 index 0000000000..06ec350900 --- /dev/null +++ b/queue-6.6/drm-amd-display-fix-dcn32-dtb-dto-update-breaking-li.patch @@ -0,0 +1,78 @@ +From 56bf217ba747159cefeccef17c74ca38e2523140 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 23:34:52 -0700 +Subject: drm/amd/display: Fix dcn32 DTB DTO update breaking live pixel rate + sources + +From: Harry Wentland + +commit 76a2db58e95e328007043f54ac3c7336ccbee440 upstream. + +dcn32_update_clocks_update_dtb_dto() reprograms the DTB DTO of every +timing generator in the context whenever the DTBCLK reference changes, +passing a zeroed pixel rate and never setting is_hdmi. +dccg32_set_dtbclk_dto() treats a zero pixel rate as a disable request, +and that branch drives PIPE_DTO_SRC_SEL to the DP DTO source. A timing +generator actively scanning out an HDMI stream therefore has its pixel +rate source re-muxed out from under the live raster, and the OTG stops +on the spot. + +Two displays where only one runs a 128b/132b link hit this reliably. +is_dtbclk_required() holds the DTBCLK reference high while both are +active, and the moment the 128b/132b stream is torn down (compositor +switch, display disable, hot-unplug) the next safe_to_lower pass drops +the reference to the lowest DPM level and the DTO walk freezes the +surviving screen. On Navi31 the DAL mailbox then goes deaf on the +DISPCLK hard-min that follows the walk in dcn32_update_clocks(), +stranding both SMU mailboxes until reboot. + +Set is_hdmi for HDMI and DVI signals so the disable path leaves the +pixel rate source selection on the HDMI path, and pass the real pixel +rate for 128b/132b streams so a reference change rescales their DTO +instead of disabling it. + +Fixes: 128c1ca0303f ("drm/amd/display: Update DTBCLK for DCN32") +Signed-off-by: Harry Wentland +Reviewed-by: Fangzhi Zuo +Tested-by: Dan Wheeler +Signed-off-by: Alex Deucher +[ mschwartz: dcn32 clk_mgr hunk only, as 6.6.y predates the dcn35 + clk_mgr. The rest is HDMI FRL enablement, absent before 7.2, so the + FRL conditions and the req_audio_dtbclk_khz assignment they guard are + dropped and the FRL-centric changelog is rewritten. Added the + pipe_ctx->stream check the new dereferences need. ] +Signed-off-by: Matthew Schwartz +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c +index 1c5ae4d62e37b7..65344d0c7c10d2 100644 +--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c ++++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c +@@ -255,13 +255,20 @@ static void dcn32_update_clocks_update_dtb_dto(struct clk_mgr_internal *clk_mgr, + struct dtbclk_dto_params dto_params = {0}; + + /* use mask to program DTO once per tg */ +- if (pipe_ctx->stream_res.tg && ++ if (pipe_ctx->stream && pipe_ctx->stream_res.tg && + !(tg_mask & (1 << pipe_ctx->stream_res.tg->inst))) { + tg_mask |= (1 << pipe_ctx->stream_res.tg->inst); + + dto_params.otg_inst = pipe_ctx->stream_res.tg->inst; + dto_params.ref_dtbclk_khz = ref_dtbclk_khz; + ++ if (dccg->ctx->dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) ++ dto_params.pixclk_khz = pipe_ctx->stream->timing.pix_clk_100hz / 10; ++ ++ if (dc_is_hdmi_signal(pipe_ctx->stream->signal) || ++ dc_is_dvi_signal(pipe_ctx->stream->signal)) ++ dto_params.is_hdmi = true; ++ + dccg->funcs->set_dtbclk_dto(clk_mgr->dccg, &dto_params); + //dccg->funcs->set_audio_dtbclk_dto(clk_mgr->dccg, &dto_params); + } +-- +2.53.0 + diff --git a/queue-6.6/exfat-validate-cluster-allocation-bits-of-the-alloca.patch b/queue-6.6/exfat-validate-cluster-allocation-bits-of-the-alloca.patch new file mode 100644 index 0000000000..9843c94dd0 --- /dev/null +++ b/queue-6.6/exfat-validate-cluster-allocation-bits-of-the-alloca.patch @@ -0,0 +1,116 @@ +From 7d72d3d196ecfb3fbf6c226afe2f16602710edef Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 20:49:38 +0000 +Subject: exfat: validate cluster allocation bits of the allocation bitmap + +From: Namjae Jeon + +[ Upstream commit 79c1587b6cda74deb0c86fc7ba194b92958c793c ] + +syzbot created an exfat image with cluster bits not set for the allocation +bitmap. exfat-fs reads and uses the allocation bitmap without checking +this. The problem is that if the start cluster of the allocation bitmap +is 6, cluster 6 can be allocated when creating a directory with mkdir. +exfat zeros out this cluster in exfat_mkdir, which can delete existing +entries. This can reallocate the allocated entries. In addition, +the allocation bitmap is also zeroed out, so cluster 6 can be reallocated. +This patch adds exfat_test_bitmap_range to validate that clusters used for +the allocation bitmap are correctly marked as in-use. + +Reported-by: syzbot+a725ab460fc1def9896f@syzkaller.appspotmail.com +Tested-by: syzbot+a725ab460fc1def9896f@syzkaller.appspotmail.com +Reviewed-by: Yuezhang Mo +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +[Adapted to 6.6: replaced __le_long/lel_to_cpu word-level bitmap access + with per-bit test_bit_le() calls, as __le_long and lel_to_cpu do not + exist in 6.6. Uses same test_bit_le API as rest of exfat bitmap code.] +Signed-off-by: Jay Wang +Signed-off-by: Sasha Levin +--- + fs/exfat/balloc.c | 54 ++++++++++++++++++++++++++++++++++++----------- + 1 file changed, 42 insertions(+), 12 deletions(-) + +diff --git a/fs/exfat/balloc.c b/fs/exfat/balloc.c +index 32209acd51be4f..2d4fe3d754bbc5 100644 +--- a/fs/exfat/balloc.c ++++ b/fs/exfat/balloc.c +@@ -45,12 +45,37 @@ static const unsigned char used_bit[] = { + /* + * Allocation Bitmap Management Functions + */ ++static bool exfat_test_bitmap_range(struct super_block *sb, unsigned int clu, ++ unsigned int count) ++{ ++ struct exfat_sb_info *sbi = EXFAT_SB(sb); ++ unsigned int start = clu; ++ unsigned int end = clu + count; ++ unsigned int ent_idx, i, b; ++ ++ if (!is_valid_cluster(sbi, start) || !is_valid_cluster(sbi, end - 1)) ++ return false; ++ ++ while (start < end) { ++ ent_idx = CLUSTER_TO_BITMAP_ENT(start); ++ i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx); ++ b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); ++ ++ if (!test_bit_le(b, sbi->vol_amap[i]->b_data)) ++ return false; ++ ++ start++; ++ } ++ ++ return true; ++} ++ + static int exfat_allocate_bitmap(struct super_block *sb, + struct exfat_dentry *ep) + { + struct exfat_sb_info *sbi = EXFAT_SB(sb); + long long map_size; +- unsigned int i, need_map_size; ++ unsigned int i, j, need_map_size; + sector_t sector; + + sbi->map_clu = le32_to_cpu(ep->dentry.bitmap.start_clu); +@@ -77,20 +102,25 @@ static int exfat_allocate_bitmap(struct super_block *sb, + sector = exfat_cluster_to_sector(sbi, sbi->map_clu); + for (i = 0; i < sbi->map_sectors; i++) { + sbi->vol_amap[i] = sb_bread(sb, sector + i); +- if (!sbi->vol_amap[i]) { +- /* release all buffers and free vol_amap */ +- int j = 0; +- +- while (j < i) +- brelse(sbi->vol_amap[j++]); +- +- kvfree(sbi->vol_amap); +- sbi->vol_amap = NULL; +- return -EIO; +- } ++ if (!sbi->vol_amap[i]) ++ goto err_out; + } + ++ if (exfat_test_bitmap_range(sb, sbi->map_clu, ++ EXFAT_B_TO_CLU_ROUND_UP(map_size, sbi)) == false) ++ goto err_out; ++ + return 0; ++ ++err_out: ++ j = 0; ++ /* release all buffers and free vol_amap */ ++ while (j < i) ++ brelse(sbi->vol_amap[j++]); ++ ++ kvfree(sbi->vol_amap); ++ sbi->vol_amap = NULL; ++ return -EIO; + } + + int exfat_load_bitmap(struct super_block *sb) +-- +2.53.0 + diff --git a/queue-6.6/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch b/queue-6.6/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch new file mode 100644 index 0000000000..cad13097e9 --- /dev/null +++ b/queue-6.6/fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch @@ -0,0 +1,261 @@ +From 82bbfd4286c9a388cf4faef124a349bfe147b994 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 08:25:39 -0700 +Subject: fscrypt: Avoid dynamic allocation in fscrypt_get_devices() + +From: Eric Biggers + +commit 6fe4e4b8259e1330945b5f3c9476e08473b8e0e8 upstream. + +When a blk_crypto_key starts being used or is evicted, fs/crypto/ calls +fscrypt_get_devices() to get the filesystem's list of block devices, +then iterates over them and calls blk_crypto_config_supported(), +blk_crypto_start_using_key(), or blk_crypto_evict_key() on each one. + +Currently, the block device pointers are placed in a dynamically +allocated array. This dynamic allocation is problematic because: + +- It can fail, especially at the fscrypt_destroy_inline_crypt_key() call + site when it's invoked for inode eviction under direct reclaim. + +- fscrypt_destroy_inline_crypt_key() doesn't handle the failure. It + just zeroizes and frees the blk_crypto_key without calling + blk_crypto_evict_key(). That causes a use-after-free. + +For now, let's fix this in the straightforward and easily-backportable +way by switching to an on-stack array. Currently the fscrypt +multi-device functionality is used only by f2fs, which has a hardcoded +limit of 8 block devices. An on-stack array works fine for that. + +(Of course, this solution won't scale up to large number of block +devices. For that we'd need a different solution, like moving the block +device iteration into the filesystem. Or in the case of btrfs, which +will only support blk-crypto-fallback, we should make it just call +blk-crypto-fallback directly, so the block devices won't be needed.) + +Fixes: 22e9947a4b2b ("fscrypt: stop holding extra request_queue references") +Cc: stable@vger.kernel.org +Reported-by: Sashiko +Closes: https://sashiko.dev/#/patchset/20260713023708.9245-1-ebiggers%40kernel.org +Reviewed-by: Christoph Hellwig +Link: https://patch.msgid.link/20260719055602.78828-1-ebiggers@kernel.org +Signed-off-by: Eric Biggers +Signed-off-by: Sasha Levin +--- + fs/crypto/inline_crypt.c | 57 ++++++++++++++-------------------------- + fs/f2fs/super.c | 25 ++++++++++-------- + include/linux/fscrypt.h | 18 +++++++------ + 3 files changed, 44 insertions(+), 56 deletions(-) + +diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c +index 8bfb3ce864766e..47645c5539bc84 100644 +--- a/fs/crypto/inline_crypt.c ++++ b/fs/crypto/inline_crypt.c +@@ -21,22 +21,14 @@ + + #include "fscrypt_private.h" + +-static struct block_device **fscrypt_get_devices(struct super_block *sb, +- unsigned int *num_devs) ++static unsigned int ++fscrypt_get_devices(struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]) + { +- struct block_device **devs; +- +- if (sb->s_cop->get_devices) { +- devs = sb->s_cop->get_devices(sb, num_devs); +- if (devs) +- return devs; +- } +- devs = kmalloc(sizeof(*devs), GFP_KERNEL); +- if (!devs) +- return ERR_PTR(-ENOMEM); ++ if (sb->s_cop->get_devices) ++ return sb->s_cop->get_devices(sb, devs); + devs[0] = sb->s_bdev; +- *num_devs = 1; +- return devs; ++ return 1; + } + + static unsigned int fscrypt_get_dun_bytes(const struct fscrypt_info *ci) +@@ -95,7 +87,7 @@ int fscrypt_select_encryption_impl(struct fscrypt_info *ci) + const struct inode *inode = ci->ci_inode; + struct super_block *sb = inode->i_sb; + struct blk_crypto_config crypto_cfg; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + +@@ -132,20 +124,15 @@ int fscrypt_select_encryption_impl(struct fscrypt_info *ci) + crypto_cfg.data_unit_size = sb->s_blocksize; + crypto_cfg.dun_bytes = fscrypt_get_dun_bytes(ci); + +- devs = fscrypt_get_devices(sb, &num_devs); +- if (IS_ERR(devs)) +- return PTR_ERR(devs); +- ++ num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) { + if (!blk_crypto_config_supported(devs[i], &crypto_cfg)) +- goto out_free_devs; ++ return 0; + } + + fscrypt_log_blk_crypto_impl(ci->ci_mode, devs, num_devs, &crypto_cfg); + + ci->ci_inlinecrypt = true; +-out_free_devs: +- kfree(devs); + + return 0; + } +@@ -158,7 +145,7 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, + struct super_block *sb = inode->i_sb; + enum blk_crypto_mode_num crypto_mode = ci->ci_mode->blk_crypto_mode; + struct blk_crypto_key *blk_key; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + int err; +@@ -175,17 +162,12 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, + } + + /* Start using blk-crypto on all the filesystem's block devices. */ +- devs = fscrypt_get_devices(sb, &num_devs); +- if (IS_ERR(devs)) { +- err = PTR_ERR(devs); +- goto fail; +- } ++ num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) { + err = blk_crypto_start_using_key(devs[i], blk_key); + if (err) + break; + } +- kfree(devs); + if (err) { + fscrypt_err(inode, "error %d starting to use blk-crypto", err); + goto fail; +@@ -209,20 +191,21 @@ void fscrypt_destroy_inline_crypt_key(struct super_block *sb, + struct fscrypt_prepared_key *prep_key) + { + struct blk_crypto_key *blk_key = prep_key->blk_key; +- struct block_device **devs; ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]; + unsigned int num_devs; + unsigned int i; + + if (!blk_key) + return; + +- /* Evict the key from all the filesystem's block devices. */ +- devs = fscrypt_get_devices(sb, &num_devs); +- if (!IS_ERR(devs)) { +- for (i = 0; i < num_devs; i++) +- blk_crypto_evict_key(devs[i], blk_key); +- kfree(devs); +- } ++ /* ++ * Evict the key from all the filesystem's block devices. ++ * This *must* be done before the key is freed. ++ */ ++ num_devs = fscrypt_get_devices(sb, devs); ++ for (i = 0; i < num_devs; i++) ++ blk_crypto_evict_key(devs[i], blk_key); ++ + kfree_sensitive(blk_key); + } + +diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c +index c018d548e16348..95adc2c4c3c087 100644 +--- a/fs/f2fs/super.c ++++ b/fs/f2fs/super.c +@@ -3248,24 +3248,27 @@ static void f2fs_get_ino_and_lblk_bits(struct super_block *sb, + *lblk_bits_ret = 8 * sizeof(block_t); + } + +-static struct block_device **f2fs_get_devices(struct super_block *sb, +- unsigned int *num_devs) ++static unsigned int ++f2fs_get_devices(struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]) + { + struct f2fs_sb_info *sbi = F2FS_SB(sb); +- struct block_device **devs; ++ int ndevs; + int i; + +- if (!f2fs_is_multi_device(sbi)) +- return NULL; ++ static_assert(MAX_DEVICES <= FSCRYPT_MAX_DEVICES); + +- devs = kmalloc_array(sbi->s_ndevs, sizeof(*devs), GFP_KERNEL); +- if (!devs) +- return ERR_PTR(-ENOMEM); ++ if (!f2fs_is_multi_device(sbi)) { ++ devs[0] = sb->s_bdev; ++ return 1; ++ } ++ ndevs = sbi->s_ndevs; ++ if (WARN_ON_ONCE(ndevs > FSCRYPT_MAX_DEVICES)) ++ ndevs = FSCRYPT_MAX_DEVICES; + +- for (i = 0; i < sbi->s_ndevs; i++) ++ for (i = 0; i < ndevs; i++) + devs[i] = FDEV(i).bdev; +- *num_devs = sbi->s_ndevs; +- return devs; ++ return ndevs; + } + + static const struct fscrypt_operations f2fs_cryptops = { +diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h +index c895b12737a193..e55ba9f1e38398 100644 +--- a/include/linux/fscrypt.h ++++ b/include/linux/fscrypt.h +@@ -57,6 +57,9 @@ struct fscrypt_name { + /* Maximum value for the third parameter of fscrypt_operations.set_context(). */ + #define FSCRYPT_SET_CONTEXT_MAX_SIZE 40 + ++/* Maximum supported number of block devices per filesystem */ ++#define FSCRYPT_MAX_DEVICES 8 ++ + #ifdef CONFIG_FS_ENCRYPTION + + /* +@@ -161,21 +164,20 @@ struct fscrypt_operations { + int *ino_bits_ret, int *lblk_bits_ret); + + /* +- * Return an array of pointers to the block devices to which the +- * filesystem may write encrypted file contents, NULL if the filesystem +- * only has a single such block device, or an ERR_PTR() on error. ++ * Retrieve the list of block devices to which the filesystem may write ++ * encrypted file contents. + * +- * On successful non-NULL return, *num_devs is set to the number of +- * devices in the returned array. The caller must free the returned +- * array using kfree(). ++ * This writes the block_device pointers to @devs and returns the count ++ * (between 1 and FSCRYPT_MAX_DEVICES inclusively). + * + * If the filesystem can use multiple block devices (other than block + * devices that aren't used for encrypted file contents, such as + * external journal devices), and wants to support inline encryption, + * then it must implement this function. Otherwise it's not needed. + */ +- struct block_device **(*get_devices)(struct super_block *sb, +- unsigned int *num_devs); ++ unsigned int (*get_devices)( ++ struct super_block *sb, ++ struct block_device *devs[FSCRYPT_MAX_DEVICES]); + }; + + static inline struct fscrypt_info *fscrypt_get_info(const struct inode *inode) +-- +2.53.0 + diff --git a/queue-6.6/i40e-remove-read-access-to-debugfs-files.patch b/queue-6.6/i40e-remove-read-access-to-debugfs-files.patch new file mode 100644 index 0000000000..08f3df6df0 --- /dev/null +++ b/queue-6.6/i40e-remove-read-access-to-debugfs-files.patch @@ -0,0 +1,288 @@ +From 675b9cae644aa0ce18e99b2ecd989911ce957023 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 21:07:15 +0000 +Subject: i40e: remove read access to debugfs files + +From: Jacob Keller + +[ Upstream commit 9fcdb1c3c4ba134434694c001dbff343f1ffa319 ] + +The 'command' and 'netdev_ops' debugfs files are a legacy debugging +interface supported by the i40e driver since its early days by commit +02e9c290814c ("i40e: debugfs interface"). + +Both of these debugfs files provide a read handler which is mostly useless, +and which is implemented with questionable logic. They both use a static +256 byte buffer which is initialized to the empty string. In the case of +the 'command' file this buffer is literally never used and simply wastes +space. In the case of the 'netdev_ops' file, the last command written is +saved here. + +On read, the files contents are presented as the name of the device +followed by a colon and then the contents of their respective static +buffer. For 'command' this will always be ": ". For 'netdev_ops', +this will be ": ". But note the buffer is +shared between all devices operated by this module. At best, it is mostly +meaningless information, and at worse it could be accessed simultaneously +as there doesn't appear to be any locking mechanism. + +We have also recently received multiple reports for both read functions +about their use of snprintf and potential overflow that could result in +reading arbitrary kernel memory. For the 'command' file, this is +definitely impossible, since the static buffer is always zero and never +written to. For the 'netdev_ops' file, it does appear to be possible, if +the user carefully crafts the command input, it will be copied into the +buffer, which could be large enough to cause snprintf to truncate, which +then causes the copy_to_user to read beyond the length of the buffer +allocated by kzalloc. + +A minimal fix would be to replace snprintf() with scnprintf() which would +cap the return to the number of bytes written, preventing an overflow. A +more involved fix would be to drop the mostly useless static buffers, +saving 512 bytes and modifying the read functions to stop needing those as +input. + +Instead, lets just completely drop the read access to these files. These +are debug interfaces exposed as part of debugfs, and I don't believe that +dropping read access will break any script, as the provided output is +pretty useless. You can find the netdev name through other more standard +interfaces, and the 'netdev_ops' interface can easily result in garbage if +you issue simultaneous writes to multiple devices at once. + +In order to properly remove the i40e_dbg_netdev_ops_buf, we need to +refactor its write function to avoid using the static buffer. Instead, use +the same logic as the i40e_dbg_command_write, with an allocated buffer. +Update the code to use this instead of the static buffer, and ensure we +free the buffer on exit. This fixes simultaneous writes to 'netdev_ops' on +multiple devices, and allows us to remove the now unused static buffer +along with removing the read access. + +Fixes: 02e9c290814c ("i40e: debugfs interface") +Reported-by: Kunwu Chan +Closes: https://lore.kernel.org/intel-wired-lan/20231208031950.47410-1-chentao@kylinos.cn/ +Reported-by: Wang Haoran +Closes: https://lore.kernel.org/all/CANZ3JQRRiOdtfQJoP9QM=6LS1Jto8PGBGw6y7-TL=BcnzHQn1Q@mail.gmail.com/ +Reported-by: Amir Mohammad Jahangirzad +Closes: https://lore.kernel.org/all/20250722115017.206969-1-a.jahangirzad@gmail.com/ +Signed-off-by: Jacob Keller +Reviewed-by: Dawid Osuchowski +Reviewed-by: Aleksandr Loktionov +Reviewed-by: Simon Horman +Reviewed-by: Kunwu Chan +Tested-by: Rinitha S (A Contingent worker at Intel) +Signed-off-by: Tony Nguyen +[Adapted to 6.6: context conflict due to 6.6 using pf->vsi[pf->lan_vsi] + vs i40e_pf_get_main_vsi(pf) in the removed read functions; resolution + is simply to delete the 6.6 version of those functions.] +Signed-off-by: Jay Wang +Signed-off-by: Sasha Levin +--- + .../net/ethernet/intel/i40e/i40e_debugfs.c | 121 +++--------------- + 1 file changed, 19 insertions(+), 102 deletions(-) + +diff --git a/drivers/net/ethernet/intel/i40e/i40e_debugfs.c b/drivers/net/ethernet/intel/i40e/i40e_debugfs.c +index a2fca58a91c332..35ce578dd6f350 100644 +--- a/drivers/net/ethernet/intel/i40e/i40e_debugfs.c ++++ b/drivers/net/ethernet/intel/i40e/i40e_debugfs.c +@@ -58,47 +58,6 @@ static struct i40e_veb *i40e_dbg_find_veb(struct i40e_pf *pf, int seid) + * setup, adding or removing filters, or other things. Many of + * these will be useful for some forms of unit testing. + **************************************************************/ +-static char i40e_dbg_command_buf[256] = ""; +- +-/** +- * i40e_dbg_command_read - read for command datum +- * @filp: the opened file +- * @buffer: where to write the data for the user to read +- * @count: the size of the user's buffer +- * @ppos: file position offset +- **/ +-static ssize_t i40e_dbg_command_read(struct file *filp, char __user *buffer, +- size_t count, loff_t *ppos) +-{ +- struct i40e_pf *pf = filp->private_data; +- int bytes_not_copied; +- int buf_size = 256; +- char *buf; +- int len; +- +- /* don't allow partial reads */ +- if (*ppos != 0) +- return 0; +- if (count < buf_size) +- return -ENOSPC; +- +- buf = kzalloc(buf_size, GFP_KERNEL); +- if (!buf) +- return -ENOSPC; +- +- len = snprintf(buf, buf_size, "%s: %s\n", +- pf->vsi[pf->lan_vsi]->netdev->name, +- i40e_dbg_command_buf); +- +- bytes_not_copied = copy_to_user(buffer, buf, len); +- kfree(buf); +- +- if (bytes_not_copied) +- return -EFAULT; +- +- *ppos = len; +- return len; +-} + + static char *i40e_filter_state_string[] = { + "INVALID", +@@ -1637,7 +1596,6 @@ static ssize_t i40e_dbg_command_write(struct file *filp, + static const struct file_operations i40e_dbg_command_fops = { + .owner = THIS_MODULE, + .open = simple_open, +- .read = i40e_dbg_command_read, + .write = i40e_dbg_command_write, + }; + +@@ -1646,47 +1604,6 @@ static const struct file_operations i40e_dbg_command_fops = { + * The netdev_ops entry in debugfs is for giving the driver commands + * to be executed from the netdev operations. + **************************************************************/ +-static char i40e_dbg_netdev_ops_buf[256] = ""; +- +-/** +- * i40e_dbg_netdev_ops_read - read for netdev_ops datum +- * @filp: the opened file +- * @buffer: where to write the data for the user to read +- * @count: the size of the user's buffer +- * @ppos: file position offset +- **/ +-static ssize_t i40e_dbg_netdev_ops_read(struct file *filp, char __user *buffer, +- size_t count, loff_t *ppos) +-{ +- struct i40e_pf *pf = filp->private_data; +- int bytes_not_copied; +- int buf_size = 256; +- char *buf; +- int len; +- +- /* don't allow partal reads */ +- if (*ppos != 0) +- return 0; +- if (count < buf_size) +- return -ENOSPC; +- +- buf = kzalloc(buf_size, GFP_KERNEL); +- if (!buf) +- return -ENOSPC; +- +- len = snprintf(buf, buf_size, "%s: %s\n", +- pf->vsi[pf->lan_vsi]->netdev->name, +- i40e_dbg_netdev_ops_buf); +- +- bytes_not_copied = copy_to_user(buffer, buf, len); +- kfree(buf); +- +- if (bytes_not_copied) +- return -EFAULT; +- +- *ppos = len; +- return len; +-} + + /** + * i40e_dbg_netdev_ops_write - write into netdev_ops datum +@@ -1700,35 +1617,36 @@ static ssize_t i40e_dbg_netdev_ops_write(struct file *filp, + size_t count, loff_t *ppos) + { + struct i40e_pf *pf = filp->private_data; ++ char *cmd_buf, *buf_tmp; + int bytes_not_copied; + struct i40e_vsi *vsi; +- char *buf_tmp; + int vsi_seid; + int i, cnt; + + /* don't allow partial writes */ + if (*ppos != 0) + return 0; +- if (count >= sizeof(i40e_dbg_netdev_ops_buf)) +- return -ENOSPC; + +- memset(i40e_dbg_netdev_ops_buf, 0, sizeof(i40e_dbg_netdev_ops_buf)); +- bytes_not_copied = copy_from_user(i40e_dbg_netdev_ops_buf, +- buffer, count); +- if (bytes_not_copied) ++ cmd_buf = kzalloc(count + 1, GFP_KERNEL); ++ if (!cmd_buf) ++ return count; ++ bytes_not_copied = copy_from_user(cmd_buf, buffer, count); ++ if (bytes_not_copied) { ++ kfree(cmd_buf); + return -EFAULT; +- i40e_dbg_netdev_ops_buf[count] = '\0'; ++ } ++ cmd_buf[count] = '\0'; + +- buf_tmp = strchr(i40e_dbg_netdev_ops_buf, '\n'); ++ buf_tmp = strchr(cmd_buf, '\n'); + if (buf_tmp) { + *buf_tmp = '\0'; +- count = buf_tmp - i40e_dbg_netdev_ops_buf + 1; ++ count = buf_tmp - cmd_buf + 1; + } + +- if (strncmp(i40e_dbg_netdev_ops_buf, "change_mtu", 10) == 0) { ++ if (strncmp(cmd_buf, "change_mtu", 10) == 0) { + int mtu; + +- cnt = sscanf(&i40e_dbg_netdev_ops_buf[11], "%i %i", ++ cnt = sscanf(&cmd_buf[11], "%i %i", + &vsi_seid, &mtu); + if (cnt != 2) { + dev_info(&pf->pdev->dev, "change_mtu \n"); +@@ -1750,8 +1668,8 @@ static ssize_t i40e_dbg_netdev_ops_write(struct file *filp, + dev_info(&pf->pdev->dev, "Could not acquire RTNL - please try again\n"); + } + +- } else if (strncmp(i40e_dbg_netdev_ops_buf, "set_rx_mode", 11) == 0) { +- cnt = sscanf(&i40e_dbg_netdev_ops_buf[11], "%i", &vsi_seid); ++ } else if (strncmp(cmd_buf, "set_rx_mode", 11) == 0) { ++ cnt = sscanf(&cmd_buf[11], "%i", &vsi_seid); + if (cnt != 1) { + dev_info(&pf->pdev->dev, "set_rx_mode \n"); + goto netdev_ops_write_done; +@@ -1771,8 +1689,8 @@ static ssize_t i40e_dbg_netdev_ops_write(struct file *filp, + dev_info(&pf->pdev->dev, "Could not acquire RTNL - please try again\n"); + } + +- } else if (strncmp(i40e_dbg_netdev_ops_buf, "napi", 4) == 0) { +- cnt = sscanf(&i40e_dbg_netdev_ops_buf[4], "%i", &vsi_seid); ++ } else if (strncmp(cmd_buf, "napi", 4) == 0) { ++ cnt = sscanf(&cmd_buf[4], "%i", &vsi_seid); + if (cnt != 1) { + dev_info(&pf->pdev->dev, "napi \n"); + goto netdev_ops_write_done; +@@ -1790,21 +1708,20 @@ static ssize_t i40e_dbg_netdev_ops_write(struct file *filp, + dev_info(&pf->pdev->dev, "napi called\n"); + } + } else { +- dev_info(&pf->pdev->dev, "unknown command '%s'\n", +- i40e_dbg_netdev_ops_buf); ++ dev_info(&pf->pdev->dev, "unknown command '%s'\n", cmd_buf); + dev_info(&pf->pdev->dev, "available commands\n"); + dev_info(&pf->pdev->dev, " change_mtu \n"); + dev_info(&pf->pdev->dev, " set_rx_mode \n"); + dev_info(&pf->pdev->dev, " napi \n"); + } + netdev_ops_write_done: ++ kfree(cmd_buf); + return count; + } + + static const struct file_operations i40e_dbg_netdev_ops_fops = { + .owner = THIS_MODULE, + .open = simple_open, +- .read = i40e_dbg_netdev_ops_read, + .write = i40e_dbg_netdev_ops_write, + }; + +-- +2.53.0 + diff --git a/queue-6.6/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch b/queue-6.6/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch new file mode 100644 index 0000000000..1245d3acf4 --- /dev/null +++ b/queue-6.6/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch @@ -0,0 +1,92 @@ +From f2ae29fc341747d7cbe529eb045dc4ae7744dcb4 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 22 Jul 2026 20:45:51 +0800 +Subject: io_uring/rw: fix missing ERESTARTSYS conversion in read paths + +From: Yitang Yang + +commit ab05caca123c6d0b41850b7c05b246e4dca4a770 upstream. + +Both read and write may receive internal restart error codes from +the filesystem layer and should be converted to -EINTR. However, +when multishot read support was added, the error code normalization +was lost for both io_read() and io_read_mshot(). + +Extract the conversion into io_fixup_restart_res() and apply it +in all three locations: io_rw_done(), io_read(), and io_read_mshot(). + +[ 6.6 doesn't have multishot read, drop the io_read_mshot() hunk and + adapt io_rw_done() to the older calling convention ] + +Fixes: a08d195b586a ("io_uring/rw: split io_read() into a helper") +Cc: stable@vger.kernel.org +Signed-off-by: Yitang Yang +Link: https://patch.msgid.link/20260722124551.130563-1-yi1tang.yang@gmail.com +Signed-off-by: Jens Axboe +Signed-off-by: Sasha Levin +--- + io_uring/rw.c | 28 +++++++++++++++++++--------- + 1 file changed, 19 insertions(+), 9 deletions(-) + +diff --git a/io_uring/rw.c b/io_uring/rw.c +index 4ff3442ac2eeea..f2ab967b95072a 100644 +--- a/io_uring/rw.c ++++ b/io_uring/rw.c +@@ -140,27 +140,37 @@ void io_readv_writev_cleanup(struct io_kiocb *req) + kfree(io->free_iovec); + } + +-static inline void io_rw_done(struct kiocb *kiocb, ssize_t ret) ++static inline ssize_t io_fixup_restart_res(ssize_t ret) + { + switch (ret) { +- case -EIOCBQUEUED: +- break; + case -ERESTARTSYS: + case -ERESTARTNOINTR: + case -ERESTARTNOHAND: + case -ERESTART_RESTARTBLOCK: + /* + * We can't just restart the syscall, since previously +- * submitted sqes may already be in progress. Just fail this +- * IO with EINTR. ++ * submitted sqes may already be in progress. Just fail ++ * this IO with EINTR. + */ +- ret = -EINTR; +- fallthrough; ++ return -EINTR; + default: +- kiocb->ki_complete(kiocb, ret); ++ return ret; + } + } + ++static inline void io_rw_done(struct kiocb *kiocb, ssize_t ret) ++{ ++ /* IO was queued async, completion will happen later */ ++ if (ret == -EIOCBQUEUED) ++ return; ++ ++ /* transform internal restart error codes */ ++ if (unlikely(ret < 0)) ++ ret = io_fixup_restart_res(ret); ++ ++ kiocb->ki_complete(kiocb, ret); ++} ++ + static inline loff_t *io_kiocb_update_pos(struct io_kiocb *req) + { + struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw); +@@ -885,7 +895,7 @@ int io_read(struct io_kiocb *req, unsigned int issue_flags) + if (ret >= 0) + return kiocb_done(req, ret, issue_flags); + +- return ret; ++ return io_fixup_restart_res(ret); + } + + static bool io_kiocb_start_write(struct io_kiocb *req, struct kiocb *kiocb) +-- +2.53.0 + diff --git a/queue-6.6/ipv6-ndisc-fix-null-deref-in-accept_untracked_na.patch b/queue-6.6/ipv6-ndisc-fix-null-deref-in-accept_untracked_na.patch new file mode 100644 index 0000000000..1dd894cf67 --- /dev/null +++ b/queue-6.6/ipv6-ndisc-fix-null-deref-in-accept_untracked_na.patch @@ -0,0 +1,88 @@ +From 11af4fe2e0d42d45b8b9784936c18c66b862bf09 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 12:08:56 +0300 +Subject: ipv6: ndisc: fix NULL deref in accept_untracked_na() + +From: Weiming Shi + +commit d186e942365acece7c56d39da05dd63bf95b280a upstream. + +accept_untracked_na() re-fetches the inet6_dev with __in6_dev_get(dev) +and dereferences idev->cnf.accept_untracked_na without a NULL check, +even though its only caller ndisc_recv_na() already fetched and +NULL-checked idev for the same device. + +Both reads of dev->ip6_ptr run in the same RCU read-side critical +section, but a concurrent addrconf_ifdown() can clear dev->ip6_ptr +between them: lowering the MTU below IPV6_MIN_MTU calls addrconf_ifdown() +without the synchronize_net() that orders the unregister path, so the +re-fetch returns NULL and oopses: + + BUG: KASAN: null-ptr-deref in ndisc_recv_na (net/ipv6/ndisc.c:974) + Read of size 4 at addr 0000000000000364 + Call Trace: + + ndisc_recv_na (net/ipv6/ndisc.c:974) + icmpv6_rcv (net/ipv6/icmp.c:1193) + ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:479) + ip6_input_finish (net/ipv6/ip6_input.c:534) + ip6_input (net/ipv6/ip6_input.c:545) + ip6_mc_input (net/ipv6/ip6_input.c:635) + ipv6_rcv (net/ipv6/ip6_input.c:351) + + +It is reachable by an unprivileged user via a network namespace. + +Pass the caller's already validated idev instead of re-fetching it; the +idev stays alive for the whole RCU critical section, so it is safe even +after dev->ip6_ptr has been cleared. + +Fixes: aaa5f515b16b ("net: ipv6: new accept_untracked_na option to accept na only if in-network") +Reported-by: Xiang Mei +Signed-off-by: Weiming Shi +Reviewed-by: Jiayuan Chen +Link: https://patch.msgid.link/20260617065512.2529757-2-bestswngs@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Alexander Martyniuk +Signed-off-by: Sasha Levin +--- + net/ipv6/ndisc.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c +index ee34831f5621a8..342e7066f765f5 100644 +--- a/net/ipv6/ndisc.c ++++ b/net/ipv6/ndisc.c +@@ -973,10 +973,8 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb) + return reason; + } + +-static int accept_untracked_na(struct net_device *dev, struct in6_addr *saddr) ++static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr) + { +- struct inet6_dev *idev = __in6_dev_get(dev); +- + switch (READ_ONCE(idev->cnf.accept_untracked_na)) { + case 0: /* Don't accept untracked na (absent in neighbor cache) */ + return 0; +@@ -986,7 +984,7 @@ static int accept_untracked_na(struct net_device *dev, struct in6_addr *saddr) + * same subnet as an address configured on the interface that + * received the na + */ +- return !!ipv6_chk_prefix(saddr, dev); ++ return !!ipv6_chk_prefix(saddr, idev->dev); + default: + return 0; + } +@@ -1085,7 +1083,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb) + */ + new_state = msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE; + if (!neigh && lladdr && idev && idev->cnf.forwarding) { +- if (accept_untracked_na(dev, saddr)) { ++ if (accept_untracked_na(idev, saddr)) { + neigh = neigh_create(&nd_tbl, &msg->target, dev); + new_state = NUD_STALE; + } +-- +2.53.0 + diff --git a/queue-6.6/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch b/queue-6.6/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch new file mode 100644 index 0000000000..c26e2a3086 --- /dev/null +++ b/queue-6.6/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch @@ -0,0 +1,108 @@ +From 1ad49c043b94f05bad5a928d08c3ff8a77661733 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:26 +0800 +Subject: ksmbd: bound DACL dedup walk to copied ACEs + +From: Namjae Jeon + +commit 58d97fcd0bf1aee694e244cc28635b9df95b543b upstream. + +set_ntacl_dacl() can stop copying ACEs before consuming the full input +DACL when size accounting overflows. + +When that happens, num_aces reflects only the ACEs that were actually +copied into the output DACL, but set_posix_acl_entries_dacl() still +receives nt_num_aces and uses it to walk the existing ACE array during +dedup. + +That makes the dedup walk scan past the copied ACE array and inspect +buffer tail that does not contain valid ACEs. + +Split the two meanings currently carried by the NT ACE count. Pass the +number of copied NT ACEs to bound the dedup walk, and preserve the +original "input DACL had NT ACEs" state separately for the +Everyone/default ACL fallback. + +This keeps the dedup walk aligned with the ACEs that are actually +present in the rebuilt DACL. + +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index 3f0f1d9c98d1b7..3cd0d1e1ddc224 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -595,7 +595,8 @@ static void parse_dacl(struct mnt_idmap *idmap, + static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + struct smb_ace *pndace, + struct smb_fattr *fattr, u16 *num_aces, +- u16 *size, u32 nt_aces_num) ++ u16 *size, u16 existing_nt_aces, ++ bool had_nt_aces) + { + struct posix_acl_entry *pace; + struct smb_sid *sid; +@@ -627,14 +628,14 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + + gid = posix_acl_gid_translate(idmap, pace); + id_to_sid(gid, SIDUNIX_GROUP, sid); +- } else if (pace->e_tag == ACL_OTHER && !nt_aces_num) { ++ } else if (pace->e_tag == ACL_OTHER && !had_nt_aces) { + smb_copy_sid(sid, &sid_everyone); + } else { + kfree(sid); + continue; + } + ntace = pndace; +- for (j = 0; j < nt_aces_num; j++) { ++ for (j = 0; j < existing_nt_aces; j++) { + if (ntace->sid.sub_auth[ntace->sid.num_subauth - 1] == + sid->sub_auth[sid->num_subauth - 1]) + goto pass_same_sid; +@@ -678,7 +679,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + kfree(sid); + } + +- if (nt_aces_num) ++ if (had_nt_aces) + return; + + posix_default_acl: +@@ -732,6 +733,7 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + { + struct smb_ace *ntace, *pndace; + u16 nt_num_aces = le16_to_cpu(nt_dacl->num_aces), num_aces = 0; ++ u16 copied_nt_aces; + unsigned short size = 0; + int i; + +@@ -765,8 +767,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + } + } + ++ copied_nt_aces = num_aces; + set_posix_acl_entries_dacl(idmap, pndace, fattr, +- &num_aces, &size, nt_num_aces); ++ &num_aces, &size, copied_nt_aces, ++ nt_num_aces != 0); + pndacl->num_aces = cpu_to_le16(num_aces); + pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); + } +@@ -784,7 +788,7 @@ static void set_mode_dacl(struct mnt_idmap *idmap, + + if (fattr->cf_acls) { + set_posix_acl_entries_dacl(idmap, pndace, fattr, +- &num_aces, &size, num_aces); ++ &num_aces, &size, num_aces, false); + goto out; + } + +-- +2.53.0 + diff --git a/queue-6.6/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch b/queue-6.6/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch new file mode 100644 index 0000000000..f477109e77 --- /dev/null +++ b/queue-6.6/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch @@ -0,0 +1,91 @@ +From cd4adb90f76a16e5a378a07f7aa7e8f03fedeaee Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:24 +0800 +Subject: ksmbd: restore DACL size on check_add_overflow() to avoid malformed + ACL + +From: Wentao Guan + +commit bbf0a8e931204ecdab494a88d43b0a24a04285c5 upstream. + +check_add_overflow() unconditionally writes the truncated sum into *d +even on overflow, per its contract in include/linux/overflow.h. +The four check_add_overflow() guards in set_posix_acl_entries_dacl() +and set_ntacl_dacl() break out of the ACE-building loops on overflow, +but the truncated *size is then consumed downstream at the end of +set_ntacl_dacl(): + + pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); + +This produces an on-wire NT ACL whose pndacl->size under-reports the +bytes actually written by the preceding fill_ace_for_sid()/memcpy() +calls, yielding a malformed ACL that can trigger out-of-bounds reads +when re-parsed by clients or ksmbd itself. + +Restore *size to its pre-addition value on each overflow branch (via +`*size -= ace_sz` / `size -= nt_ace_size`) so that after the break, +*size once again holds the cumulative size of the successfully-written +ACEs. The committed ACL is then truncated-but-self-consistent rather +than malformed. + +The ksmbd DACL builders are the only check_add_overflow() sites found +where an overflow path breaks out of a loop and the destination value +is consumed afterward. The other nearby break-style cases either +return -EINVAL on overflow (transport_ipc.c) or break without +consuming the overflowed destination value afterward (buildid.c). + +Fixes: 299f962c0b02 ("ksmbd: use check_add_overflow() to prevent u16 DACL size overflow") +Assisted-by: atomcode:glm-5.2 +Assisted-by: Codex:gpt-5.5 +Cc: stable@vger.kernel.org +Signed-off-by: Wentao Guan +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index e87f25f4e672fb..3f0f1d9c98d1b7 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -649,6 +649,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, flags, + pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -663,6 +664,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, + 0x03, pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -708,6 +710,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, 0x0b, + pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -750,8 +753,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + goto next_ace; + + memcpy((char *)pndace + size, ntace, nt_ace_size); +- if (check_add_overflow(size, nt_ace_size, &size)) ++ if (check_add_overflow(size, nt_ace_size, &size)) { ++ size -= nt_ace_size; + break; ++ } + num_aces++; + + next_ace: +-- +2.53.0 + diff --git a/queue-6.6/ksmbd-validate-ace-size-against-sid-sub-authorities.patch b/queue-6.6/ksmbd-validate-ace-size-against-sid-sub-authorities.patch new file mode 100644 index 0000000000..0bad391ced --- /dev/null +++ b/queue-6.6/ksmbd-validate-ace-size-against-sid-sub-authorities.patch @@ -0,0 +1,66 @@ +From be56d8bcc0348506e1bbb8fed3bce258fb11c1b5 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:29 +0800 +Subject: ksmbd: validate ACE size against SID sub-authorities + +From: Namjae Jeon + +commit 5152c6d49e3fd4e9f2e857c57527aead752f1f87 upstream. + +set_ntacl_dacl() validates sid.num_subauth before copying an ACE, but +does not verify that the declared ACE size contains all sub-authorities +described by that field. An undersized ACE can therefore be copied +and later make the POSIX ACL deduplication walk inspect data beyond +the copied ACE boundary. + +The existing initial bound check is also too small. It only ensures +that the ACE size field is accessible before set_ntacl_dacl() reads +sid.num_subauth farther into the input buffer. + +Require enough input for the fixed SID header before accessing +num_subauth, reject ACEs smaller than that header, and skip ACEs +whose declared size cannot contain the complete SID. This makes the +validation consistent with the other ACE walk paths. + +Reported-by: LocalHost +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index 3cd0d1e1ddc224..06936ae521cf00 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -743,15 +743,22 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + for (i = 0; i < nt_num_aces; i++) { + unsigned short nt_ace_size; + +- if (offsetof(struct smb_ace, access_req) > aces_size) ++ if (aces_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + + nt_ace_size = le16_to_cpu(ntace->size); +- if (nt_ace_size > aces_size) ++ if (nt_ace_size > aces_size || ++ nt_ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + + if (ntace->sid.num_subauth == 0 || +- ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES || ++ nt_ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE + ++ sizeof(__le32) * ++ ntace->sid.num_subauth) + goto next_ace; + + memcpy((char *)pndace + size, ntace, nt_ace_size); +-- +2.53.0 + diff --git a/queue-6.6/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch b/queue-6.6/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch new file mode 100644 index 0000000000..1fa57a5a6b --- /dev/null +++ b/queue-6.6/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch @@ -0,0 +1,75 @@ +From 73a19f39cebe5436125d8eeba15075428d2aa2bd Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:22 +0800 +Subject: ksmbd: validate num_subauth when copying ACE in set_ntacl_dacl + +From: Haofeng Li + +commit 47f0b34f6bc98ed85bfdc293e8f3e432ec24958d upstream. + +set_ntacl_dacl() copies each ACE from the attacker-controlled stored +security descriptor verbatim into the response DACL without checking +sid.num_subauth. The ACE bytes (including an unchecked num_subauth) +originate from an authenticated SMB2_SET_INFO(SecInfo=DACL) that is +stored raw via ksmbd_vfs_set_sd_xattr(); parse_dacl() rejects a bad ACE +with `break` rather than an error, so parse_sec_desc() still returns +success and the malformed SD reaches the xattr intact. + +On a subsequent SMB2_QUERY_INFO(SecInfo=DACL) for an inode carrying a +POSIX access ACL, build_sec_desc() -> set_ntacl_dacl() -> +set_posix_acl_entries_dacl() walks the copied ACEs and reads + + ntace->sid.sub_auth[ntace->sid.num_subauth - 1] + +with num_subauth taken straight from the stored SD. Since sub_auth[] +is fixed at SID_MAX_SUB_AUTHORITIES (15), a crafted num_subauth (e.g. +255) drives an out-of-bounds heap read of ~1 KB with an offset fully +controlled by an authenticated client. + +The sibling functions already gate this field: + parse_dacl() -- num_subauth == 0 || > SID_MAX_SUB_AUTHORITIES + parse_sid() -- num_subauth > SID_MAX_SUB_AUTHORITIES + smb_copy_sid() -- min_t(u8, num_subauth, SID_MAX_SUB_AUTHORITIES) +set_ntacl_dacl() is the lone inconsistent path that omits the check. + +Add the same num_subauth validation in set_ntacl_dacl() before copying +the ACE, matching the gate already enforced by parse_dacl(). + +Signed-off-by: Haofeng Li +Reviewed-by: ChenXiaoSong +Suggested-by: Namjae Jeon +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index 420d4e0733e5f1..e87f25f4e672fb 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -745,12 +745,18 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + if (nt_ace_size > aces_size) + break; + ++ if (ntace->sid.num_subauth == 0 || ++ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ goto next_ace; ++ + memcpy((char *)pndace + size, ntace, nt_ace_size); + if (check_add_overflow(size, nt_ace_size, &size)) + break; ++ num_aces++; ++ ++next_ace: + aces_size -= nt_ace_size; + ntace = (struct smb_ace *)((char *)ntace + nt_ace_size); +- num_aces++; + } + } + +-- +2.53.0 + diff --git a/queue-6.6/net-pcs-xpcs-fix-sgmii-state-reading.patch b/queue-6.6/net-pcs-xpcs-fix-sgmii-state-reading.patch new file mode 100644 index 0000000000..8c3917531b --- /dev/null +++ b/queue-6.6/net-pcs-xpcs-fix-sgmii-state-reading.patch @@ -0,0 +1,119 @@ +From 0c878ffa9d0d2c05c9a60d1dd8600343177f7538 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:11:08 +0800 +Subject: net: pcs: xpcs: fix SGMII state reading + +From: Coia Prant + +commit def9a4745e105145133e442dd8a1c126caf0f553 upstream. + +Commit 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode") +added a path in xpcs_get_state_c37_sgmii() that reads speed/duplex from +BMCR after AN completes. However, BMCR does not reflect the negotiated +result on the hardware where this has been tested: + +- On RK3568 (MAC side SGMII), BMCR returns a fixed hardware reset value +- Wangxun engineer Jiawen Wu confirmed that on their side, "BMCR looks + like it only wants to be return as 0" [0] + +The correct information is available in CL37_ANSGM_STS, which contains +the actual link status and negotiated speed/duplex. + +This bug was previously masked by phylink core, which overrides the PCS +link state with the PHY state when a PHY is present: + + /* If we have a phy, the "up" state is the union of both the + * PHY and the MAC + */ + if (phy) + link_state.link &= pl->phy_state.link; + +Thus, when the link is down, the PHY's link_down state is applied on top +of whatever the PCS reports, hiding the broken PCS state reading path. + +Modify xpcs_get_state_c37_sgmii() to: +1. Read link state from CL37_ANSGM_STS +2. If link is up, report speed/duplex from CL37_ANSGM_STS +3. Remove the broken BMCR reading path entirely + +Also properly set state->an_complete to reflect the AN completion status, +and clear CL37_ANCMPLT_INTR when link is down to avoid stale state. + +[0] https://lore.kernel.org/all/000c01dd1593$2ac0b0f0$804212d0$@trustnetic.com/ + +Fixes: 2a22b7ae2fa3 ("net: pcs: xpcs: adapt Wangxun NICs for SGMII mode") +Cc: stable@vger.kernel.org +Tested-by: Jiawen Wu +Signed-off-by: Coia Prant +Tested-by: Maxime Chevallier +Reviewed-by: Maxime Chevallier +Link: https://patch.msgid.link/20260717074324.3250043-2-coiaprant@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + drivers/net/pcs/pcs-xpcs.c | 32 +++++++------------------------- + 1 file changed, 7 insertions(+), 25 deletions(-) + +diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c +index f0f41e86a4fb32..680e2b2610860f 100644 +--- a/drivers/net/pcs/pcs-xpcs.c ++++ b/drivers/net/pcs/pcs-xpcs.c +@@ -994,6 +994,7 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, + + /* Reset link_state */ + state->link = false; ++ state->an_complete = false; + state->speed = SPEED_UNKNOWN; + state->duplex = DUPLEX_UNKNOWN; + state->pause = 0; +@@ -1005,6 +1006,8 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, + if (ret < 0) + return ret; + ++ state->an_complete = ret & DW_VR_MII_AN_STS_C37_ANCMPLT_INTR; ++ + if (ret & DW_VR_MII_C37_ANSGM_SP_LNKSTS) { + int speed_value; + +@@ -1023,34 +1026,13 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, + state->duplex = DUPLEX_FULL; + else + state->duplex = DUPLEX_HALF; +- } else if (ret == DW_VR_MII_AN_STS_C37_ANCMPLT_INTR) { +- int speed, duplex; +- +- state->link = true; +- +- speed = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1); +- if (speed < 0) +- return speed; +- +- speed &= SGMII_SPEED_SS13 | SGMII_SPEED_SS6; +- if (speed == SGMII_SPEED_SS6) +- state->speed = SPEED_1000; +- else if (speed == SGMII_SPEED_SS13) +- state->speed = SPEED_100; +- else if (speed == 0) +- state->speed = SPEED_10; +- +- duplex = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_ADVERTISE); +- if (duplex < 0) +- return duplex; + +- if (duplex & DW_FULL_DUPLEX) +- state->duplex = DUPLEX_FULL; +- else if (duplex & DW_HALF_DUPLEX) +- state->duplex = DUPLEX_HALF; ++ return 0; ++ } + ++ /* Clear AN complete status or interrupt */ ++ if (state->an_complete) + xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0); +- } + + return 0; + } +-- +2.53.0 + diff --git a/queue-6.6/net-qrtr-ns-raise-node-count-limit-to-512.patch b/queue-6.6/net-qrtr-ns-raise-node-count-limit-to-512.patch new file mode 100644 index 0000000000..e34b9e901d --- /dev/null +++ b/queue-6.6/net-qrtr-ns-raise-node-count-limit-to-512.patch @@ -0,0 +1,42 @@ +From 04acf9e4438cd3fc6c3cbcba78e64dd6e2115fd8 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 17:05:32 +0200 +Subject: net: qrtr: ns: Raise node count limit to 512 + +From: Youssef Samir + +commit ff194cffd586cbd4cc49eccb002c65f2a902a277 upstream. + +The current node limit of 64 breaks the functionality for a number of AI200 +deployments that have up to 384 nodes. Raise the limit to 512. + +Fixes: 27d5e84e810b ("net: qrtr: ns: Limit the total number of nodes") +Cc: stable@vger.kernel.org +Signed-off-by: Youssef Samir +Link: https://patch.msgid.link/20260713145901.212396-1-youssef.abdulrahman@oss.qualcomm.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Youssef Samir +Signed-off-by: Sasha Levin +--- + net/qrtr/ns.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/net/qrtr/ns.c b/net/qrtr/ns.c +index ecf49172307f1b..90244ce7e95e7a 100644 +--- a/net/qrtr/ns.c ++++ b/net/qrtr/ns.c +@@ -85,9 +85,9 @@ struct qrtr_node { + /* Max nodes limit is chosen based on the current platform requirements. + * If the requirement changes in the future, this value can be increased. + */ +-#define QRTR_NS_MAX_NODES 64 ++#define QRTR_NS_MAX_NODES 512 + +-static u8 node_count; ++static u16 node_count; + + static struct qrtr_node *node_get(unsigned int node_id) + { +-- +2.53.0 + diff --git a/queue-6.6/openvswitch-fix-gso-userspace-truncation-underflow.patch b/queue-6.6/openvswitch-fix-gso-userspace-truncation-underflow.patch new file mode 100644 index 0000000000..57d26efe07 --- /dev/null +++ b/queue-6.6/openvswitch-fix-gso-userspace-truncation-underflow.patch @@ -0,0 +1,193 @@ +From 338bff8f7214f29c8d7d58e8e0d2b12a32e31d90 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 22:56:07 +0200 +Subject: openvswitch: fix GSO userspace truncation underflow + +From: Kyle Zeng + +[ Upstream commit 4032f8ed10fcb84d41c508dfb04be96589f78dfe ] + +OVS_ACTION_ATTR_TRUNC currently stores a delta from the original skb +length in OVS_CB(skb)->cutlen. When a later userspace action segments a +GSO skb, queue_gso_packets() reuses that delta for each smaller segment. +A segment can then reach queue_userspace_packet() with cutlen greater +than skb->len, underflowing the length passed to skb_zerocopy(). + +Store the maximum preserved length instead and bound each consumer +against the current skb length. Use U32_MAX as the no-truncation +sentinel so the value remains valid if skb geometry changes before a +consumer handles it. + +Fixes: f2a4d086ed4c ("openvswitch: Add packet truncation support.") +Cc: stable@vger.kernel.org +Assisted-by: Codex:gpt-5.5 +Signed-off-by: Kyle Zeng +Reviewed-by: Ilya Maximets +Reviewed-by: Aaron Conole +Link: https://patch.msgid.link/20260707221635.27489-1-kylebot@openai.com +Signed-off-by: Paolo Abeni +[6.6.y and older don't have OVS_ACTION_ATTR_PSAMPLE] +Signed-off-by: Ilya Maximets +Signed-off-by: Sasha Levin +--- + net/openvswitch/actions.c | 15 +++++---------- + net/openvswitch/datapath.c | 25 ++++++++++++++----------- + net/openvswitch/datapath.h | 2 +- + net/openvswitch/vport.c | 2 +- + 4 files changed, 21 insertions(+), 23 deletions(-) + +diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c +index 0ea4fc2a755bfd..793f86e2475559 100644 +--- a/net/openvswitch/actions.c ++++ b/net/openvswitch/actions.c +@@ -861,12 +861,8 @@ static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port, + u16 mru = OVS_CB(skb)->mru; + u32 cutlen = OVS_CB(skb)->cutlen; + +- if (unlikely(cutlen > 0)) { +- if (skb->len - cutlen > ovs_mac_header_len(key)) +- pskb_trim(skb, skb->len - cutlen); +- else +- pskb_trim(skb, ovs_mac_header_len(key)); +- } ++ if (unlikely(cutlen < skb->len)) ++ pskb_trim(skb, max(cutlen, ovs_mac_header_len(key))); + + if (likely(!mru || + (skb->len <= mru + vport->dev->hard_header_len))) { +@@ -1258,22 +1254,21 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, + clone = skb_clone(skb, GFP_ATOMIC); + if (clone) + do_output(dp, clone, port, key); +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + break; + } + + case OVS_ACTION_ATTR_TRUNC: { + struct ovs_action_trunc *trunc = nla_data(a); + +- if (skb->len > trunc->max_len) +- OVS_CB(skb)->cutlen = skb->len - trunc->max_len; ++ OVS_CB(skb)->cutlen = trunc->max_len; + break; + } + + case OVS_ACTION_ATTR_USERSPACE: + output_userspace(dp, skb, key, a, attr, + len, OVS_CB(skb)->cutlen); +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + if (nla_is_last(a, rem)) { + consume_skb(skb); + return 0; +diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c +index 857edc53739332..fb5b72700d82b1 100644 +--- a/net/openvswitch/datapath.c ++++ b/net/openvswitch/datapath.c +@@ -273,7 +273,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) + upcall.portid = ovs_vport_find_upcall_portid(p, skb); + + upcall.mru = OVS_CB(skb)->mru; +- error = ovs_dp_upcall(dp, skb, key, &upcall, 0); ++ error = ovs_dp_upcall(dp, skb, key, &upcall, U32_MAX); + switch (error) { + case 0: + case -EAGAIN: +@@ -438,7 +438,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + struct sk_buff *nskb = NULL; + struct sk_buff *user_skb = NULL; /* to be queued to userspace */ + struct nlattr *nla; +- size_t len; ++ size_t msg_size; ++ size_t skb_len; + unsigned int hlen; + int err, dp_ifindex; + u64 hash; +@@ -459,7 +460,8 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + skb = nskb; + } + +- if (nla_attr_size(skb->len) > USHRT_MAX) { ++ skb_len = min(skb->len, cutlen); ++ if (nla_attr_size(skb_len) > USHRT_MAX) { + err = -EFBIG; + goto out; + } +@@ -474,13 +476,13 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + * padding logic. Only perform zerocopy if padding is not required. + */ + if (dp->user_features & OVS_DP_F_UNALIGNED) +- hlen = skb_zerocopy_headlen(skb); ++ hlen = min(skb_zerocopy_headlen(skb), cutlen); + else +- hlen = skb->len; ++ hlen = skb_len; + +- len = upcall_msg_size(upcall_info, hlen - cutlen, +- OVS_CB(skb)->acts_origlen); +- user_skb = genlmsg_new(len, GFP_ATOMIC); ++ msg_size = upcall_msg_size(upcall_info, hlen, ++ OVS_CB(skb)->acts_origlen); ++ user_skb = genlmsg_new(msg_size, GFP_ATOMIC); + if (!user_skb) { + err = -ENOMEM; + goto out; +@@ -541,7 +543,7 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + } + + /* Add OVS_PACKET_ATTR_LEN when packet is truncated */ +- if (cutlen > 0 && ++ if (skb_len < skb->len && + nla_put_u32(user_skb, OVS_PACKET_ATTR_LEN, skb->len)) { + err = -ENOBUFS; + goto out; +@@ -566,9 +568,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, + err = -ENOBUFS; + goto out; + } +- nla->nla_len = nla_attr_size(skb->len - cutlen); ++ nla->nla_len = nla_attr_size(skb_len); + +- err = skb_zerocopy(user_skb, skb, skb->len - cutlen, hlen); ++ err = skb_zerocopy(user_skb, skb, skb_len, hlen); + if (err) + goto out; + +@@ -625,6 +627,7 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info) + packet->ignore_df = 1; + } + OVS_CB(packet)->mru = mru; ++ OVS_CB(packet)->cutlen = U32_MAX; + + if (a[OVS_PACKET_ATTR_HASH]) { + hash = nla_get_u64(a[OVS_PACKET_ATTR_HASH]); +diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h +index 0cd29971a907ca..88156a677f22c5 100644 +--- a/net/openvswitch/datapath.h ++++ b/net/openvswitch/datapath.h +@@ -114,7 +114,7 @@ struct datapath { + * @mru: The maximum received fragement size; 0 if the packet is not + * fragmented. + * @acts_origlen: The netlink size of the flow actions applied to this skb. +- * @cutlen: The number of bytes from the packet end to be removed. ++ * @cutlen: The number of bytes in the packet to preserve on output. + */ + struct ovs_skb_cb { + struct vport *input_vport; +diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c +index a0a8854e9f19e8..eb0eb57dabe2f9 100644 +--- a/net/openvswitch/vport.c ++++ b/net/openvswitch/vport.c +@@ -503,7 +503,7 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, + + OVS_CB(skb)->input_vport = vport; + OVS_CB(skb)->mru = 0; +- OVS_CB(skb)->cutlen = 0; ++ OVS_CB(skb)->cutlen = U32_MAX; + if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) { + u32 mark; + +-- +2.53.0 + diff --git a/queue-6.6/series b/queue-6.6/series index 0b64a711e3..b836ba01ac 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -347,3 +347,17 @@ drm-dp_mst-handle-torn-down-topology-gracefully-in-drm_dp_mst_topology_queue_pro drm-amdgpu-fix-division-by-zero-with-invalid-uvd-dimensions.patch drm-amdgpu-invoke-pm_genpd_remove-before-freeing-genpd.patch drm-amdgpu-fix-aperture-mapping-leak.patch +i40e-remove-read-access-to-debugfs-files.patch +ipv6-ndisc-fix-null-deref-in-accept_untracked_na.patch +net-qrtr-ns-raise-node-count-limit-to-512.patch +ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch +ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch +ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch +ksmbd-validate-ace-size-against-sid-sub-authorities.patch +openvswitch-fix-gso-userspace-truncation-underflow.patch +fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch +exfat-validate-cluster-allocation-bits-of-the-alloca.patch +drm-amd-display-fix-dcn32-dtb-dto-update-breaking-li.patch +io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch +net-pcs-xpcs-fix-sgmii-state-reading.patch +bpf-drop-bpf_lsm_getselfattr-from-hook-list.patch diff --git a/queue-7.1/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch b/queue-7.1/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch new file mode 100644 index 0000000000..61010a4279 --- /dev/null +++ b/queue-7.1/drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch @@ -0,0 +1,107 @@ +From 6dc84a3cb5f0d80831ad4343995f798c043f66ef Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 28 Jul 2026 23:33:40 -0700 +Subject: drm/amd/display: Fix DTB DTO updates breaking live pixel rate sources + +From: Harry Wentland + +commit 76a2db58e95e328007043f54ac3c7336ccbee440 upstream. + +dcn32_update_clocks_update_dtb_dto() and its dcn35 counterpart reprogram +the DTB DTO of every timing generator in the context whenever the DTBCLK +reference changes, passing a zeroed pixel rate and never setting +is_hdmi. Both dccg set_dtbclk_dto() implementations treat a zero pixel +rate as a disable request. On dcn32 that branch drives PIPE_DTO_SRC_SEL +to the DP DTO source, so a timing generator actively scanning out an +HDMI stream has its pixel rate source re-muxed out from under the live +raster and the OTG stops on the spot. On dcn35 it clears +DTBCLK_DTO_ENABLE and restores DTBCLK_Pn clock gating, which does the +same to a live 128b/132b stream. + +Two displays where only one runs a 128b/132b link hit this reliably. +is_dtbclk_required() holds the DTBCLK reference high while both are +active, and the moment the 128b/132b stream is torn down (compositor +switch, display disable, hot-unplug) the next safe_to_lower pass drops +the reference to the lowest DPM level and the DTO walk freezes the +surviving screen. On Navi31 the DAL mailbox then goes deaf on the +DISPCLK hard-min that follows the walk in dcn32_update_clocks(), +stranding both SMU mailboxes until reboot. + +Set is_hdmi for HDMI and DVI signals so the disable path leaves the +pixel rate source selection on the HDMI path, and pass the real pixel +rate for 128b/132b streams so a reference change rescales their DTO +instead of disabling it. + +Fixes: 128c1ca0303f ("drm/amd/display: Update DTBCLK for DCN32") +Fixes: 8774029f76b9 ("drm/amd/display: Add DCN35 CLK_MGR") +Signed-off-by: Harry Wentland +Reviewed-by: Fangzhi Zuo +Tested-by: Dan Wheeler +Signed-off-by: Alex Deucher +[ mschwartz: dcn32 and dcn35 clk_mgr hunks only. The rest is HDMI FRL + enablement, absent before 7.2, so the FRL conditions and the + req_audio_dtbclk_khz assignment they guard are dropped and the + FRL-centric changelog is rewritten. Added the pipe_ctx->stream check + the new dereferences need. ] +Signed-off-by: Matthew Schwartz +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c | 9 ++++++++- + .../gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c | 9 ++++++++- + 2 files changed, 16 insertions(+), 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c +index fda6cade30a8de..e2787c7084ec07 100644 +--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c ++++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn32/dcn32_clk_mgr.c +@@ -276,13 +276,20 @@ static void dcn32_update_clocks_update_dtb_dto(struct clk_mgr_internal *clk_mgr, + struct dtbclk_dto_params dto_params = {0}; + + /* use mask to program DTO once per tg */ +- if (pipe_ctx->stream_res.tg && ++ if (pipe_ctx->stream && pipe_ctx->stream_res.tg && + !(tg_mask & (1 << pipe_ctx->stream_res.tg->inst))) { + tg_mask |= (1 << pipe_ctx->stream_res.tg->inst); + + dto_params.otg_inst = pipe_ctx->stream_res.tg->inst; + dto_params.ref_dtbclk_khz = ref_dtbclk_khz; + ++ if (dccg->ctx->dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) ++ dto_params.pixclk_khz = pipe_ctx->stream->timing.pix_clk_100hz / 10; ++ ++ if (dc_is_hdmi_signal(pipe_ctx->stream->signal) || ++ dc_is_dvi_signal(pipe_ctx->stream->signal)) ++ dto_params.is_hdmi = true; ++ + dccg->funcs->set_dtbclk_dto(clk_mgr->dccg, &dto_params); + //dccg->funcs->set_audio_dtbclk_dto(clk_mgr->dccg, &dto_params); + } +diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c +index 3f58a1640d64e7..020d43c7fab855 100644 +--- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c ++++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn35/dcn35_clk_mgr.c +@@ -264,13 +264,20 @@ static void dcn35_update_clocks_update_dtb_dto(struct clk_mgr_internal *clk_mgr, + struct dtbclk_dto_params dto_params = {0}; + + /* use mask to program DTO once per tg */ +- if (pipe_ctx->stream_res.tg && ++ if (pipe_ctx->stream && pipe_ctx->stream_res.tg && + !(tg_mask & (1 << pipe_ctx->stream_res.tg->inst))) { + tg_mask |= (1 << pipe_ctx->stream_res.tg->inst); + + dto_params.otg_inst = pipe_ctx->stream_res.tg->inst; + dto_params.ref_dtbclk_khz = ref_dtbclk_khz; + ++ if (dccg->ctx->dc->link_srv->dp_is_128b_132b_signal(pipe_ctx)) ++ dto_params.pixclk_khz = pipe_ctx->stream->timing.pix_clk_100hz / 10; ++ ++ if (dc_is_hdmi_signal(pipe_ctx->stream->signal) || ++ dc_is_dvi_signal(pipe_ctx->stream->signal)) ++ dto_params.is_hdmi = true; ++ + dccg->funcs->set_dtbclk_dto(clk_mgr->dccg, &dto_params); + //dccg->funcs->set_audio_dtbclk_dto(clk_mgr->dccg, &dto_params); + } +-- +2.53.0 + diff --git a/queue-7.1/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch b/queue-7.1/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch new file mode 100644 index 0000000000..408dd6744a --- /dev/null +++ b/queue-7.1/ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch @@ -0,0 +1,108 @@ +From 87374ccb3747b641fbbdd207b5167b97d9f04334 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:26 +0800 +Subject: ksmbd: bound DACL dedup walk to copied ACEs + +From: Namjae Jeon + +commit 58d97fcd0bf1aee694e244cc28635b9df95b543b upstream. + +set_ntacl_dacl() can stop copying ACEs before consuming the full input +DACL when size accounting overflows. + +When that happens, num_aces reflects only the ACEs that were actually +copied into the output DACL, but set_posix_acl_entries_dacl() still +receives nt_num_aces and uses it to walk the existing ACE array during +dedup. + +That makes the dedup walk scan past the copied ACE array and inspect +buffer tail that does not contain valid ACEs. + +Split the two meanings currently carried by the NT ACE count. Pass the +number of copied NT ACEs to bound the dedup walk, and preserve the +original "input DACL had NT ACEs" state separately for the +Everyone/default ACL fallback. + +This keeps the dedup walk aligned with the ACEs that are actually +present in the rebuilt DACL. + +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index 5621c50a805716..f8696399130889 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -595,7 +595,8 @@ static void parse_dacl(struct mnt_idmap *idmap, + static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + struct smb_ace *pndace, + struct smb_fattr *fattr, u16 *num_aces, +- u16 *size, u32 nt_aces_num) ++ u16 *size, u16 existing_nt_aces, ++ bool had_nt_aces) + { + struct posix_acl_entry *pace; + struct smb_sid *sid; +@@ -627,14 +628,14 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + + gid = posix_acl_gid_translate(idmap, pace); + id_to_sid(gid, SIDUNIX_GROUP, sid); +- } else if (pace->e_tag == ACL_OTHER && !nt_aces_num) { ++ } else if (pace->e_tag == ACL_OTHER && !had_nt_aces) { + smb_copy_sid(sid, &sid_everyone); + } else { + kfree(sid); + continue; + } + ntace = pndace; +- for (j = 0; j < nt_aces_num; j++) { ++ for (j = 0; j < existing_nt_aces; j++) { + if (ntace->sid.sub_auth[ntace->sid.num_subauth - 1] == + sid->sub_auth[sid->num_subauth - 1]) + goto pass_same_sid; +@@ -678,7 +679,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + kfree(sid); + } + +- if (nt_aces_num) ++ if (had_nt_aces) + return; + + posix_default_acl: +@@ -732,6 +733,7 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + { + struct smb_ace *ntace, *pndace; + u16 nt_num_aces = le16_to_cpu(nt_dacl->num_aces), num_aces = 0; ++ u16 copied_nt_aces; + unsigned short size = 0; + int i; + +@@ -765,8 +767,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + } + } + ++ copied_nt_aces = num_aces; + set_posix_acl_entries_dacl(idmap, pndace, fattr, +- &num_aces, &size, nt_num_aces); ++ &num_aces, &size, copied_nt_aces, ++ nt_num_aces != 0); + pndacl->num_aces = cpu_to_le16(num_aces); + pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); + } +@@ -784,7 +788,7 @@ static void set_mode_dacl(struct mnt_idmap *idmap, + + if (fattr->cf_acls) { + set_posix_acl_entries_dacl(idmap, pndace, fattr, +- &num_aces, &size, num_aces); ++ &num_aces, &size, num_aces, false); + goto out; + } + +-- +2.53.0 + diff --git a/queue-7.1/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch b/queue-7.1/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch new file mode 100644 index 0000000000..6cc38bbbc0 --- /dev/null +++ b/queue-7.1/ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch @@ -0,0 +1,91 @@ +From e4e52ed15acfded51d3d9ca1324e1ea95f10697f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:24 +0800 +Subject: ksmbd: restore DACL size on check_add_overflow() to avoid malformed + ACL + +From: Wentao Guan + +commit bbf0a8e931204ecdab494a88d43b0a24a04285c5 upstream. + +check_add_overflow() unconditionally writes the truncated sum into *d +even on overflow, per its contract in include/linux/overflow.h. +The four check_add_overflow() guards in set_posix_acl_entries_dacl() +and set_ntacl_dacl() break out of the ACE-building loops on overflow, +but the truncated *size is then consumed downstream at the end of +set_ntacl_dacl(): + + pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); + +This produces an on-wire NT ACL whose pndacl->size under-reports the +bytes actually written by the preceding fill_ace_for_sid()/memcpy() +calls, yielding a malformed ACL that can trigger out-of-bounds reads +when re-parsed by clients or ksmbd itself. + +Restore *size to its pre-addition value on each overflow branch (via +`*size -= ace_sz` / `size -= nt_ace_size`) so that after the break, +*size once again holds the cumulative size of the successfully-written +ACEs. The committed ACL is then truncated-but-self-consistent rather +than malformed. + +The ksmbd DACL builders are the only check_add_overflow() sites found +where an overflow path breaks out of a loop and the destination value +is consumed afterward. The other nearby break-style cases either +return -EINVAL on overflow (transport_ipc.c) or break without +consuming the overflowed destination value afterward (buildid.c). + +Fixes: 299f962c0b02 ("ksmbd: use check_add_overflow() to prevent u16 DACL size overflow") +Assisted-by: atomcode:glm-5.2 +Assisted-by: Codex:gpt-5.5 +Cc: stable@vger.kernel.org +Signed-off-by: Wentao Guan +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index 9c59c8f73b6675..5621c50a805716 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -649,6 +649,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, flags, + pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -663,6 +664,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, + 0x03, pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -708,6 +710,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, + ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, 0x0b, + pace->e_perm, 0777); + if (check_add_overflow(*size, ace_sz, size)) { ++ *size -= ace_sz; + kfree(sid); + break; + } +@@ -750,8 +753,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + goto next_ace; + + memcpy((char *)pndace + size, ntace, nt_ace_size); +- if (check_add_overflow(size, nt_ace_size, &size)) ++ if (check_add_overflow(size, nt_ace_size, &size)) { ++ size -= nt_ace_size; + break; ++ } + num_aces++; + + next_ace: +-- +2.53.0 + diff --git a/queue-7.1/ksmbd-validate-ace-size-against-sid-sub-authorities.patch b/queue-7.1/ksmbd-validate-ace-size-against-sid-sub-authorities.patch new file mode 100644 index 0000000000..518aa8e3bb --- /dev/null +++ b/queue-7.1/ksmbd-validate-ace-size-against-sid-sub-authorities.patch @@ -0,0 +1,66 @@ +From 96ba944f8cd220790a9d69dd1d485181b1bb9752 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:29 +0800 +Subject: ksmbd: validate ACE size against SID sub-authorities + +From: Namjae Jeon + +commit 5152c6d49e3fd4e9f2e857c57527aead752f1f87 upstream. + +set_ntacl_dacl() validates sid.num_subauth before copying an ACE, but +does not verify that the declared ACE size contains all sub-authorities +described by that field. An undersized ACE can therefore be copied +and later make the POSIX ACL deduplication walk inspect data beyond +the copied ACE boundary. + +The existing initial bound check is also too small. It only ensures +that the ACE size field is accessible before set_ntacl_dacl() reads +sid.num_subauth farther into the input buffer. + +Require enough input for the fixed SID header before accessing +num_subauth, reject ACEs smaller than that header, and skip ACEs +whose declared size cannot contain the complete SID. This makes the +validation consistent with the other ACE walk paths. + +Reported-by: LocalHost +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index f8696399130889..0359cd10320269 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -743,15 +743,22 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + for (i = 0; i < nt_num_aces; i++) { + unsigned short nt_ace_size; + +- if (offsetof(struct smb_ace, access_req) > aces_size) ++ if (aces_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + + nt_ace_size = le16_to_cpu(ntace->size); +- if (nt_ace_size > aces_size) ++ if (nt_ace_size > aces_size || ++ nt_ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE) + break; + + if (ntace->sid.num_subauth == 0 || +- ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES || ++ nt_ace_size < offsetof(struct smb_ace, sid) + ++ CIFS_SID_BASE_SIZE + ++ sizeof(__le32) * ++ ntace->sid.num_subauth) + goto next_ace; + + memcpy((char *)pndace + size, ntace, nt_ace_size); +-- +2.53.0 + diff --git a/queue-7.1/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch b/queue-7.1/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch new file mode 100644 index 0000000000..6865fa4e18 --- /dev/null +++ b/queue-7.1/ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch @@ -0,0 +1,75 @@ +From 9f44461c807c77b13dc8adaa63034d2820f02048 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Jul 2026 03:00:22 +0800 +Subject: ksmbd: validate num_subauth when copying ACE in set_ntacl_dacl + +From: Haofeng Li + +commit 47f0b34f6bc98ed85bfdc293e8f3e432ec24958d upstream. + +set_ntacl_dacl() copies each ACE from the attacker-controlled stored +security descriptor verbatim into the response DACL without checking +sid.num_subauth. The ACE bytes (including an unchecked num_subauth) +originate from an authenticated SMB2_SET_INFO(SecInfo=DACL) that is +stored raw via ksmbd_vfs_set_sd_xattr(); parse_dacl() rejects a bad ACE +with `break` rather than an error, so parse_sec_desc() still returns +success and the malformed SD reaches the xattr intact. + +On a subsequent SMB2_QUERY_INFO(SecInfo=DACL) for an inode carrying a +POSIX access ACL, build_sec_desc() -> set_ntacl_dacl() -> +set_posix_acl_entries_dacl() walks the copied ACEs and reads + + ntace->sid.sub_auth[ntace->sid.num_subauth - 1] + +with num_subauth taken straight from the stored SD. Since sub_auth[] +is fixed at SID_MAX_SUB_AUTHORITIES (15), a crafted num_subauth (e.g. +255) drives an out-of-bounds heap read of ~1 KB with an offset fully +controlled by an authenticated client. + +The sibling functions already gate this field: + parse_dacl() -- num_subauth == 0 || > SID_MAX_SUB_AUTHORITIES + parse_sid() -- num_subauth > SID_MAX_SUB_AUTHORITIES + smb_copy_sid() -- min_t(u8, num_subauth, SID_MAX_SUB_AUTHORITIES) +set_ntacl_dacl() is the lone inconsistent path that omits the check. + +Add the same num_subauth validation in set_ntacl_dacl() before copying +the ACE, matching the gate already enforced by parse_dacl(). + +Signed-off-by: Haofeng Li +Reviewed-by: ChenXiaoSong +Suggested-by: Namjae Jeon +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Wentao Guan +Signed-off-by: Sasha Levin +--- + fs/smb/server/smbacl.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c +index fc9937cedb012b..9c59c8f73b6675 100644 +--- a/fs/smb/server/smbacl.c ++++ b/fs/smb/server/smbacl.c +@@ -745,12 +745,18 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, + if (nt_ace_size > aces_size) + break; + ++ if (ntace->sid.num_subauth == 0 || ++ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) ++ goto next_ace; ++ + memcpy((char *)pndace + size, ntace, nt_ace_size); + if (check_add_overflow(size, nt_ace_size, &size)) + break; ++ num_aces++; ++ ++next_ace: + aces_size -= nt_ace_size; + ntace = (struct smb_ace *)((char *)ntace + nt_ace_size); +- num_aces++; + } + } + +-- +2.53.0 + diff --git a/queue-7.1/selftests-drv-net-add-missing-kconfig-for-psp.py.patch b/queue-7.1/selftests-drv-net-add-missing-kconfig-for-psp.py.patch new file mode 100644 index 0000000000..eee9f79578 --- /dev/null +++ b/queue-7.1/selftests-drv-net-add-missing-kconfig-for-psp.py.patch @@ -0,0 +1,52 @@ +From 58072da4f9a6f964a671680c1768ff51dea52f58 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 10 Jul 2026 20:04:46 +0200 +Subject: selftests: drv-net: add missing kconfig for psp.py + +From: Matthieu Baerts (NGI0) + +[ Upstream commit c25dd7439f84cf607e13d6de8cc1c79cd51f56ff ] + +This psp.py selftest was failing on my side when only using the +drivers/net config file on top of the default one -- the recommended way +to execute selftest targets. + +It looks like some kernel config are needed to execute the new tc +commands. + +Note that this was not visible on NIPA, because these tests are executed +with the drivers/net/hw ones, combining the two config files, and the hw +one contains the missing ones. + +Fixes: 3f74d5bb807e ("selftests/net: Add env for container based tests") +Signed-off-by: Matthieu Baerts (NGI0) +Reviewed-by: Wei Wang +Link: https://patch.msgid.link/20260710-net-sft-fix-containers-v1-6-a2915c294ef5@kernel.org +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +--- + tools/testing/selftests/drivers/net/config | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config +index 2309109a94ecea..4df3f6eef2cbac 100644 +--- a/tools/testing/selftests/drivers/net/config ++++ b/tools/testing/selftests/drivers/net/config +@@ -4,11 +4,14 @@ CONFIG_DEBUG_INFO_BTF_MODULES=n + CONFIG_INET_PSP=y + CONFIG_IPV6=y + CONFIG_MACSEC=m ++CONFIG_NET_CLS_ACT=y ++CONFIG_NET_CLS_BPF=y + CONFIG_NETCONSOLE=m + CONFIG_NETCONSOLE_DYNAMIC=y + CONFIG_NETCONSOLE_EXTENDED_LOG=y + CONFIG_NETDEVSIM=m + CONFIG_NET_SCH_ETF=m + CONFIG_NET_SCH_FQ=m ++CONFIG_NET_SCH_INGRESS=y + CONFIG_VLAN_8021Q=m + CONFIG_XDP_SOCKETS=y +-- +2.53.0 + diff --git a/queue-7.1/series b/queue-7.1/series index 23adf83380..1c654fce68 100644 --- a/queue-7.1/series +++ b/queue-7.1/series @@ -716,3 +716,9 @@ drm-amdgpu-reject-mapping-a-reserved-doorbell-to-a-new-queue.patch drm-amdgpu-fix-aperture-mapping-leak.patch drm-amd-pm-fix-smu13-power-limit-range-calculation.patch drm-amdgpu-fix-check-in-amdgpu_hmm_invalidate_gfx.patch +selftests-drv-net-add-missing-kconfig-for-psp.py.patch +ksmbd-validate-num_subauth-when-copying-ace-in-set_n.patch +ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch +ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch +ksmbd-validate-ace-size-against-sid-sub-authorities.patch +drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch