From c43345cf7f8fbf68197ee51193850032d2584935 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Tue, 26 May 2026 09:38:12 -0400 Subject: [PATCH] Fixes for all trees Signed-off-by: Sasha Levin --- ...fix-device-lifecycle-handling-in-css.patch | 36 ++ queue-5.10/series | 1 + ...fix-device-lifecycle-handling-in-css.patch | 29 ++ queue-5.15/series | 1 + ...fix-device-lifecycle-handling-in-css.patch | 29 ++ queue-6.1/series | 1 + ...ix-give-up-gc-if-msg_peek-intervened.patch | 258 +++++++++++++ ...synchronize-interrupts-before-suspen.patch | 121 +++++++ queue-6.12/series | 2 + ...ix-give-up-gc-if-msg_peek-intervened.patch | 258 +++++++++++++ .../ksmbd-add-durable-scavenger-timer.patch | 340 ++++++++++++++++++ ...aiming-expired-durable-opens-by-the-.patch | 64 ++++ ...ble-scavenger-races-against-m_fp_lis.patch | 261 ++++++++++++++ ...owner-of-durable-handle-on-reconnect.patch | 340 ++++++++++++++++++ queue-6.6/series | 5 + 15 files changed, 1746 insertions(+) create mode 100644 queue-5.10/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch create mode 100644 queue-5.15/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch create mode 100644 queue-6.1/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch create mode 100644 queue-6.12/af_unix-give-up-gc-if-msg_peek-intervened.patch create mode 100644 queue-6.12/drm-imagination-synchronize-interrupts-before-suspen.patch create mode 100644 queue-6.6/af_unix-give-up-gc-if-msg_peek-intervened.patch create mode 100644 queue-6.6/ksmbd-add-durable-scavenger-timer.patch create mode 100644 queue-6.6/ksmbd-avoid-reclaiming-expired-durable-opens-by-the-.patch create mode 100644 queue-6.6/ksmbd-close-durable-scavenger-races-against-m_fp_lis.patch create mode 100644 queue-6.6/ksmbd-validate-owner-of-durable-handle-on-reconnect.patch diff --git a/queue-5.10/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch b/queue-5.10/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch new file mode 100644 index 0000000000..153f2d429b --- /dev/null +++ b/queue-5.10/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch @@ -0,0 +1,36 @@ +From d39d8a6c2379e9f391af9bc1c6fce5075486a060 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 11:56:02 +0200 +Subject: Revert "s390/cio: Fix device lifecycle handling in + css_alloc_subchannel()" + +From: Ben Hutchings + +This reverts commit 2b2ad7ad4a28ffdb9f94e6d979b88a5b12b71681, which +was commit f65c75b0b9b5a390bc3beadcde0a6fbc3ad118f7 upstream. The +order of initialisation and error paths in this function are +substantially different in 5.10 and this backport did not take that +into account. + +Signed-off-by: Ben Hutchings +Signed-off-by: Sasha Levin +--- + drivers/s390/cio/css.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c +index e5e20ea850aad..cf2c3c4c590f9 100644 +--- a/drivers/s390/cio/css.c ++++ b/drivers/s390/cio/css.c +@@ -241,7 +241,7 @@ struct subchannel *css_alloc_subchannel(struct subchannel_id schid, + return sch; + + err: +- put_device(&sch->dev); ++ kfree(sch); + return ERR_PTR(ret); + } + +-- +2.53.0 + diff --git a/queue-5.10/series b/queue-5.10/series index 86f1c0855e..43d5bc7ec4 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -529,3 +529,4 @@ s390-debug-reject-zero-length-input-before-trimming-.patch selftests-lib.mk-also-install-config-and-settings.patch revert-x86-vdso-fix-output-operand-size-of-rdpid.patch net-dsa-sja1105-fix-kasan-out-of-bounds-warning-in-s.patch +revert-s390-cio-fix-device-lifecycle-handling-in-css.patch diff --git a/queue-5.15/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch b/queue-5.15/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch new file mode 100644 index 0000000000..bd993e0bd6 --- /dev/null +++ b/queue-5.15/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch @@ -0,0 +1,29 @@ +From df02035dd06f9a8af94302103c27f8e9326bdfb5 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 09:03:16 -0400 +Subject: Revert "s390/cio: Fix device lifecycle handling in + css_alloc_subchannel()" + +This reverts commit b1d4e6fb241672850296956c4d782a69363a3807. + +Signed-off-by: Sasha Levin +--- + drivers/s390/cio/css.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c +index 4c3fde0bd5512..3c499136af657 100644 +--- a/drivers/s390/cio/css.c ++++ b/drivers/s390/cio/css.c +@@ -247,7 +247,7 @@ struct subchannel *css_alloc_subchannel(struct subchannel_id schid, + err_lock: + kfree(sch->lock); + err: +- put_device(&sch->dev); ++ kfree(sch); + return ERR_PTR(ret); + } + +-- +2.53.0 + diff --git a/queue-5.15/series b/queue-5.15/series index c8a17212b9..064c46271e 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -678,3 +678,4 @@ revert-x86-vdso-fix-output-operand-size-of-rdpid.patch net-dsa-sja1105-fix-kasan-out-of-bounds-warning-in-s.patch wifi-mac80211-check-tdls-flag-in-ieee80211_tdls_oper.patch kvm-x86-acquire-srcu-in-kvm_get_mp_state-to-protect-.patch +revert-s390-cio-fix-device-lifecycle-handling-in-css.patch diff --git a/queue-6.1/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch b/queue-6.1/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch new file mode 100644 index 0000000000..533e800cf2 --- /dev/null +++ b/queue-6.1/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch @@ -0,0 +1,29 @@ +From 5390425a65397770f0e1255a10f323f81db5d7f7 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 09:03:28 -0400 +Subject: Revert "s390/cio: Fix device lifecycle handling in + css_alloc_subchannel()" + +This reverts commit fd295a75d828c11acfcc6869c2a12cdaaf9b7722. + +Signed-off-by: Sasha Levin +--- + drivers/s390/cio/css.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c +index 85c1734ebfe88..98a14c1f3d672 100644 +--- a/drivers/s390/cio/css.c ++++ b/drivers/s390/cio/css.c +@@ -247,7 +247,7 @@ struct subchannel *css_alloc_subchannel(struct subchannel_id schid, + err_lock: + kfree(sch->lock); + err: +- put_device(&sch->dev); ++ kfree(sch); + return ERR_PTR(ret); + } + +-- +2.53.0 + diff --git a/queue-6.1/series b/queue-6.1/series index 55ebc556d7..f64db0c71d 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -834,3 +834,4 @@ io_uring-prevent-opcode-speculation.patch s390-debug-reject-zero-length-input-before-trimming-.patch wifi-mac80211-check-tdls-flag-in-ieee80211_tdls_oper.patch revert-x86-vdso-fix-output-operand-size-of-rdpid.patch +revert-s390-cio-fix-device-lifecycle-handling-in-css.patch diff --git a/queue-6.12/af_unix-give-up-gc-if-msg_peek-intervened.patch b/queue-6.12/af_unix-give-up-gc-if-msg_peek-intervened.patch new file mode 100644 index 0000000000..e10e23614e --- /dev/null +++ b/queue-6.12/af_unix-give-up-gc-if-msg_peek-intervened.patch @@ -0,0 +1,258 @@ +From d9d1e44a9ea2866f804b4ef6a16a64f07f7a7a79 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 13:45:49 +0800 +Subject: af_unix: Give up GC if MSG_PEEK intervened. + +From: Kuniyuki Iwashima + +[ Upstream commit e5b31d988a41549037b8d8721a3c3cae893d8670 ] + +Igor Ushakov reported that GC purged the receive queue of +an alive socket due to a race with MSG_PEEK with a nice repro. + +This is the exact same issue previously fixed by commit +cbcf01128d0a ("af_unix: fix garbage collect vs MSG_PEEK"). + +After GC was replaced with the current algorithm, the cited +commit removed the locking dance in unix_peek_fds() and +reintroduced the same issue. + +The problem is that MSG_PEEK bumps a file refcount without +interacting with GC. + +Consider an SCC containing sk-A and sk-B, where sk-A is +close()d but can be recv()ed via sk-B. + +The bad thing happens if sk-A is recv()ed with MSG_PEEK from +sk-B and sk-B is close()d while GC is checking unix_vertex_dead() +for sk-A and sk-B. + + GC thread User thread + --------- ----------- + unix_vertex_dead(sk-A) + -> true <------. + \ + `------ recv(sk-B, MSG_PEEK) + invalidate !! -> sk-A's file refcount : 1 -> 2 + + close(sk-B) + -> sk-B's file refcount : 2 -> 1 + unix_vertex_dead(sk-B) + -> true + +Initially, sk-A's file refcount is 1 by the inflight fd in sk-B +recvq. GC thinks sk-A is dead because the file refcount is the +same as the number of its inflight fds. + +However, sk-A's file refcount is bumped silently by MSG_PEEK, +which invalidates the previous evaluation. + +At this moment, sk-B's file refcount is 2; one by the open fd, +and one by the inflight fd in sk-A. The subsequent close() +releases one refcount by the former. + +Finally, GC incorrectly concludes that both sk-A and sk-B are dead. + +One option is to restore the locking dance in unix_peek_fds(), +but we can resolve this more elegantly thanks to the new algorithm. + +The point is that the issue does not occur without the subsequent +close() and we actually do not need to synchronise MSG_PEEK with +the dead SCC detection. + +When the issue occurs, close() and GC touch the same file refcount. +If GC sees the refcount being decremented by close(), it can just +give up garbage-collecting the SCC. + +Therefore, we only need to signal the race during MSG_PEEK with +a proper memory barrier to make it visible to the GC. + +Let's use seqcount_t to notify GC when MSG_PEEK occurs and let +it defer the SCC to the next run. + +This way no locking is needed on the MSG_PEEK side, and we can +avoid imposing a penalty on every MSG_PEEK unnecessarily. + +Note that we can retry within unix_scc_dead() if MSG_PEEK is +detected, but we do not do so to avoid hung task splat from +abusive MSG_PEEK calls. + +Fixes: 118f457da9ed ("af_unix: Remove lock dance in unix_peek_fds().") +Reported-by: Igor Ushakov +Signed-off-by: Kuniyuki Iwashima +Link: https://patch.msgid.link/20260311054043.1231316-1-kuniyu@google.com +Signed-off-by: Jakub Kicinski +[ Using include/net/af_unix.h instead of net/unix/af_unix.h on 6.12.y ] +Signed-off-by: Leon Chen +Signed-off-by: Sasha Levin +--- + include/net/af_unix.h | 1 + + net/unix/af_unix.c | 2 ++ + net/unix/garbage.c | 79 ++++++++++++++++++++++++++++--------------- + 3 files changed, 54 insertions(+), 28 deletions(-) + +diff --git a/include/net/af_unix.h b/include/net/af_unix.h +index 63129c79b8cbc..8cacc5290d8bb 100644 +--- a/include/net/af_unix.h ++++ b/include/net/af_unix.h +@@ -23,6 +23,7 @@ void unix_del_edges(struct scm_fp_list *fpl); + void unix_update_edges(struct unix_sock *receiver); + int unix_prepare_fpl(struct scm_fp_list *fpl); + void unix_destroy_fpl(struct scm_fp_list *fpl); ++void unix_peek_fpl(struct scm_fp_list *fpl); + void unix_gc(void); + void wait_for_unix_gc(struct scm_fp_list *fpl); + +diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c +index 77976f36c4aa4..9c5a812bd6813 100644 +--- a/net/unix/af_unix.c ++++ b/net/unix/af_unix.c +@@ -1878,6 +1878,8 @@ static void unix_detach_fds(struct scm_cookie *scm, struct sk_buff *skb) + static void unix_peek_fds(struct scm_cookie *scm, struct sk_buff *skb) + { + scm->fp = scm_fp_dup(UNIXCB(skb).fp); ++ ++ unix_peek_fpl(scm->fp); + } + + static void unix_destruct_scm(struct sk_buff *skb) +diff --git a/net/unix/garbage.c b/net/unix/garbage.c +index 66fd606c43f45..1cdb54c61619f 100644 +--- a/net/unix/garbage.c ++++ b/net/unix/garbage.c +@@ -306,6 +306,25 @@ void unix_destroy_fpl(struct scm_fp_list *fpl) + unix_free_vertices(fpl); + } + ++static bool gc_in_progress; ++static seqcount_t unix_peek_seq = SEQCNT_ZERO(unix_peek_seq); ++ ++void unix_peek_fpl(struct scm_fp_list *fpl) ++{ ++ static DEFINE_SPINLOCK(unix_peek_lock); ++ ++ if (!fpl || !fpl->count_unix) ++ return; ++ ++ if (!READ_ONCE(gc_in_progress)) ++ return; ++ ++ /* Invalidate the final refcnt check in unix_vertex_dead(). */ ++ spin_lock(&unix_peek_lock); ++ raw_write_seqcount_barrier(&unix_peek_seq); ++ spin_unlock(&unix_peek_lock); ++} ++ + static bool unix_vertex_dead(struct unix_vertex *vertex) + { + struct unix_edge *edge; +@@ -339,6 +358,36 @@ static bool unix_vertex_dead(struct unix_vertex *vertex) + return true; + } + ++static LIST_HEAD(unix_visited_vertices); ++static unsigned long unix_vertex_grouped_index = UNIX_VERTEX_INDEX_MARK2; ++ ++static bool unix_scc_dead(struct list_head *scc, bool fast) ++{ ++ struct unix_vertex *vertex; ++ bool scc_dead = true; ++ unsigned int seq; ++ ++ seq = read_seqcount_begin(&unix_peek_seq); ++ ++ list_for_each_entry_reverse(vertex, scc, scc_entry) { ++ /* Don't restart DFS from this vertex. */ ++ list_move_tail(&vertex->entry, &unix_visited_vertices); ++ ++ /* Mark vertex as off-stack for __unix_walk_scc(). */ ++ if (!fast) ++ vertex->index = unix_vertex_grouped_index; ++ ++ if (scc_dead) ++ scc_dead = unix_vertex_dead(vertex); ++ } ++ ++ /* If MSG_PEEK intervened, defer this SCC to the next round. */ ++ if (read_seqcount_retry(&unix_peek_seq, seq)) ++ return false; ++ ++ return scc_dead; ++} ++ + static void unix_collect_skb(struct list_head *scc, struct sk_buff_head *hitlist) + { + struct unix_vertex *vertex; +@@ -392,9 +441,6 @@ static bool unix_scc_cyclic(struct list_head *scc) + return false; + } + +-static LIST_HEAD(unix_visited_vertices); +-static unsigned long unix_vertex_grouped_index = UNIX_VERTEX_INDEX_MARK2; +- + static void __unix_walk_scc(struct unix_vertex *vertex, unsigned long *last_index, + struct sk_buff_head *hitlist) + { +@@ -460,9 +506,7 @@ static void __unix_walk_scc(struct unix_vertex *vertex, unsigned long *last_inde + } + + if (vertex->index == vertex->scc_index) { +- struct unix_vertex *v; + struct list_head scc; +- bool scc_dead = true; + + /* SCC finalised. + * +@@ -471,18 +515,7 @@ static void __unix_walk_scc(struct unix_vertex *vertex, unsigned long *last_inde + */ + __list_cut_position(&scc, &vertex_stack, &vertex->scc_entry); + +- list_for_each_entry_reverse(v, &scc, scc_entry) { +- /* Don't restart DFS from this vertex in unix_walk_scc(). */ +- list_move_tail(&v->entry, &unix_visited_vertices); +- +- /* Mark vertex as off-stack. */ +- v->index = unix_vertex_grouped_index; +- +- if (scc_dead) +- scc_dead = unix_vertex_dead(v); +- } +- +- if (scc_dead) { ++ if (unix_scc_dead(&scc, false)) { + unix_collect_skb(&scc, hitlist); + } else { + if (unix_vertex_max_scc_index < vertex->scc_index) +@@ -530,19 +563,11 @@ static void unix_walk_scc_fast(struct sk_buff_head *hitlist) + while (!list_empty(&unix_unvisited_vertices)) { + struct unix_vertex *vertex; + struct list_head scc; +- bool scc_dead = true; + + vertex = list_first_entry(&unix_unvisited_vertices, typeof(*vertex), entry); + list_add(&scc, &vertex->scc_entry); + +- list_for_each_entry_reverse(vertex, &scc, scc_entry) { +- list_move_tail(&vertex->entry, &unix_visited_vertices); +- +- if (scc_dead) +- scc_dead = unix_vertex_dead(vertex); +- } +- +- if (scc_dead) ++ if (unix_scc_dead(&scc, true)) + unix_collect_skb(&scc, hitlist); + else if (!unix_graph_maybe_cyclic) + unix_graph_maybe_cyclic = unix_scc_cyclic(&scc); +@@ -553,8 +578,6 @@ static void unix_walk_scc_fast(struct sk_buff_head *hitlist) + list_replace_init(&unix_visited_vertices, &unix_unvisited_vertices); + } + +-static bool gc_in_progress; +- + static void __unix_gc(struct work_struct *work) + { + struct sk_buff_head hitlist; +-- +2.53.0 + diff --git a/queue-6.12/drm-imagination-synchronize-interrupts-before-suspen.patch b/queue-6.12/drm-imagination-synchronize-interrupts-before-suspen.patch new file mode 100644 index 0000000000..cf43c3d26e --- /dev/null +++ b/queue-6.12/drm-imagination-synchronize-interrupts-before-suspen.patch @@ -0,0 +1,121 @@ +From 54a726c3b0af08e332f6f3a69ddad5883bac8a60 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 09:13:07 +0100 +Subject: drm/imagination: Synchronize interrupts before suspending the GPU + +From: Alessio Belle + +commit 2d7f05cddf4c268cc36256a2476946041dbdd36d upstream. + +The runtime PM suspend callback doesn't know whether the IRQ handler is +in progress on a different CPU core and doesn't wait for it to finish. + +Depending on timing, the IRQ handler could be running while the GPU is +suspended, leading to it being killed when trying to access GPU +registers. See example signature below. + +In a power off sequence initiated by the runtime PM suspend callback, +wait for any IRQ handlers in progress on other CPU cores to finish, by +calling synchronize_irq(). + +This version of the patch contains only the part of the upstream commit +that applies to 6.12; the rest was a revert of code added in 6.16. +The second paragraph above is different because on 6.12 this kind of bug +doesn't seem to crash the entire kernel, only the IRQ handler, leaving +the driver unusable in practice. + +The crash signature below is also different, both because of the above, +and because there was no support for TI AM68 SK in 6.12. + +Example signature on a TI AM62 SK platform: + + [ 7827.189088] Internal error: synchronous external abort: 0000000096000010 [#1] PREEMPT SMP + [ 7827.197311] Modules linked in: + [ 7827.222015] CPU: 0 UID: 0 PID: 461 Comm: irq/405-gpu Tainted: G M 6.12.90 #5 + [ 7827.230461] Tainted: [M]=MACHINE_CHECK + [ 7827.234203] Hardware name: Texas Instruments AM625 SK (DT) + [ 7827.239682] pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) + [ 7827.246637] pc : pvr_device_irq_thread_handler+0x64/0x180 [powervr] + [ 7827.252941] lr : irq_thread_fn+0x2c/0xa8 + [ 7827.256872] sp : ffff800082d8bd50 + [ 7827.260179] x29: ffff800082d8bd70 x28: ffff8000800ce374 x27: ffff800081829cc0 + [ 7827.267328] x26: ffff000004701e80 x25: ffff000005b884ac x24: ffff000005bd5780 + [ 7827.274472] x23: ffff00000da40bc0 x22: ffff00000da40ba0 x21: ffff800082d8bd58 + [ 7827.281614] x20: ffff00000da40000 x19: ffff000004701e80 x18: 08000000c6af9003 + [ 7827.288750] x17: 0000000000000010 x16: 0000000000000068 x15: 0df234008df66400 + [ 7827.295886] x14: 0000000000000000 x13: 000005c68f6e7191 x12: 000000000000025e + [ 7827.303020] x11: 00000000000000c0 x10: 0000000000000ac0 x9 : ffff800082d8bd00 + [ 7827.310157] x8 : ffff000005bd62a0 x7 : ffff000077261380 x6 : 00000000000005c6 + [ 7827.317292] x5 : 000000000000425e x4 : 0000000000000000 x3 : 0000000000000000 + [ 7827.324428] x2 : 00000000000008a8 x1 : ffff800082d608a8 x0 : ffff000005bd5780 + [ 7827.331568] Call trace: + [ 7827.334011] pvr_device_irq_thread_handler+0x64/0x180 [powervr] + [ 7827.339954] irq_thread_fn+0x2c/0xa8 + [ 7827.343530] irq_thread+0x16c/0x2f4 + [ 7827.347019] kthread+0x110/0x114 + [ 7827.350248] ret_from_fork+0x10/0x20 + [ 7827.353834] Code: f9446682 f943c281 b9404442 8b020021 (b9400021) + [ 7827.359921] ---[ end trace 0000000000000000 ]--- + [ 7827.364820] genirq: exiting task "irq/405-gpu" (461) is an active IRQ thread (irq 405) + [ 8011.230278] powervr fd00000.gpu: Job timeout + [ 8011.230350] powervr fd00000.gpu: Job timeout + [ 8011.230426] powervr fd00000.gpu: Job timeout + +Fixes: cc1aeedb98ad ("drm/imagination: Implement firmware infrastructure and META FW support") +Fixes: 96822d38ff57 ("drm/imagination: Handle Rogue safety event IRQs") +Cc: stable@vger.kernel.org +Signed-off-by: Alessio Belle +Reviewed-by: Matt Coster +Link: https://patch.msgid.link/20260310-drain-irqs-before-suspend-v1-1-bf4f9ed68e75@imgtec.com +Signed-off-by: Matt Coster +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/imagination/pvr_power.c | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c +index bf4cf8426f913..077d2651798c8 100644 +--- a/drivers/gpu/drm/imagination/pvr_power.c ++++ b/drivers/gpu/drm/imagination/pvr_power.c +@@ -84,7 +84,7 @@ pvr_power_request_pwr_off(struct pvr_device *pvr_dev) + } + + static int +-pvr_power_fw_disable(struct pvr_device *pvr_dev, bool hard_reset) ++pvr_power_fw_disable(struct pvr_device *pvr_dev, bool hard_reset, bool rpm_suspend) + { + if (!hard_reset) { + int err; +@@ -100,6 +100,11 @@ pvr_power_fw_disable(struct pvr_device *pvr_dev, bool hard_reset) + return err; + } + ++ if (rpm_suspend) { ++ /* Wait for late processing of GPU or firmware IRQs in other cores */ ++ synchronize_irq(pvr_dev->irq); ++ } ++ + return pvr_fw_stop(pvr_dev); + } + +@@ -243,7 +248,7 @@ pvr_power_device_suspend(struct device *dev) + return -EIO; + + if (pvr_dev->fw_dev.booted) { +- err = pvr_power_fw_disable(pvr_dev, false); ++ err = pvr_power_fw_disable(pvr_dev, false, true); + if (err) + goto err_drm_dev_exit; + } +@@ -425,7 +430,7 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) + queues_disabled = true; + } + +- err = pvr_power_fw_disable(pvr_dev, hard_reset); ++ err = pvr_power_fw_disable(pvr_dev, hard_reset, false); + if (!err) { + if (hard_reset) { + pvr_dev->fw_dev.booted = false; +-- +2.53.0 + diff --git a/queue-6.12/series b/queue-6.12/series index 8f81d2a62d..1123323a05 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -19,3 +19,5 @@ sched-deadline-fix-dl_server-getting-stuck.patch sched-deadline-fix-dl_server-behaviour.patch sched-deadline-stop-dl_server-before-cpu-goes-offlin.patch ksmbd-close-durable-scavenger-races-against-m_fp_lis.patch +af_unix-give-up-gc-if-msg_peek-intervened.patch +drm-imagination-synchronize-interrupts-before-suspen.patch diff --git a/queue-6.6/af_unix-give-up-gc-if-msg_peek-intervened.patch b/queue-6.6/af_unix-give-up-gc-if-msg_peek-intervened.patch new file mode 100644 index 0000000000..56cb39ab21 --- /dev/null +++ b/queue-6.6/af_unix-give-up-gc-if-msg_peek-intervened.patch @@ -0,0 +1,258 @@ +From 626a870bbcfaf2d62354fbb8f09f7044e19eee27 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 13:47:44 +0800 +Subject: af_unix: Give up GC if MSG_PEEK intervened. + +From: Kuniyuki Iwashima + +[ Upstream commit e5b31d988a41549037b8d8721a3c3cae893d8670 ] + +Igor Ushakov reported that GC purged the receive queue of +an alive socket due to a race with MSG_PEEK with a nice repro. + +This is the exact same issue previously fixed by commit +cbcf01128d0a ("af_unix: fix garbage collect vs MSG_PEEK"). + +After GC was replaced with the current algorithm, the cited +commit removed the locking dance in unix_peek_fds() and +reintroduced the same issue. + +The problem is that MSG_PEEK bumps a file refcount without +interacting with GC. + +Consider an SCC containing sk-A and sk-B, where sk-A is +close()d but can be recv()ed via sk-B. + +The bad thing happens if sk-A is recv()ed with MSG_PEEK from +sk-B and sk-B is close()d while GC is checking unix_vertex_dead() +for sk-A and sk-B. + + GC thread User thread + --------- ----------- + unix_vertex_dead(sk-A) + -> true <------. + \ + `------ recv(sk-B, MSG_PEEK) + invalidate !! -> sk-A's file refcount : 1 -> 2 + + close(sk-B) + -> sk-B's file refcount : 2 -> 1 + unix_vertex_dead(sk-B) + -> true + +Initially, sk-A's file refcount is 1 by the inflight fd in sk-B +recvq. GC thinks sk-A is dead because the file refcount is the +same as the number of its inflight fds. + +However, sk-A's file refcount is bumped silently by MSG_PEEK, +which invalidates the previous evaluation. + +At this moment, sk-B's file refcount is 2; one by the open fd, +and one by the inflight fd in sk-A. The subsequent close() +releases one refcount by the former. + +Finally, GC incorrectly concludes that both sk-A and sk-B are dead. + +One option is to restore the locking dance in unix_peek_fds(), +but we can resolve this more elegantly thanks to the new algorithm. + +The point is that the issue does not occur without the subsequent +close() and we actually do not need to synchronise MSG_PEEK with +the dead SCC detection. + +When the issue occurs, close() and GC touch the same file refcount. +If GC sees the refcount being decremented by close(), it can just +give up garbage-collecting the SCC. + +Therefore, we only need to signal the race during MSG_PEEK with +a proper memory barrier to make it visible to the GC. + +Let's use seqcount_t to notify GC when MSG_PEEK occurs and let +it defer the SCC to the next run. + +This way no locking is needed on the MSG_PEEK side, and we can +avoid imposing a penalty on every MSG_PEEK unnecessarily. + +Note that we can retry within unix_scc_dead() if MSG_PEEK is +detected, but we do not do so to avoid hung task splat from +abusive MSG_PEEK calls. + +Fixes: 118f457da9ed ("af_unix: Remove lock dance in unix_peek_fds().") +Reported-by: Igor Ushakov +Signed-off-by: Kuniyuki Iwashima +Link: https://patch.msgid.link/20260311054043.1231316-1-kuniyu@google.com +Signed-off-by: Jakub Kicinski +[ Using include/net/af_unix.h instead of net/unix/af_unix.h on 6.6 ] +Signed-off-by: Leon Chen +Signed-off-by: Sasha Levin +--- + include/net/af_unix.h | 1 + + net/unix/af_unix.c | 2 ++ + net/unix/garbage.c | 79 ++++++++++++++++++++++++++++--------------- + 3 files changed, 54 insertions(+), 28 deletions(-) + +diff --git a/include/net/af_unix.h b/include/net/af_unix.h +index b6eedf7650da5..d2fa6d9f1e97b 100644 +--- a/include/net/af_unix.h ++++ b/include/net/af_unix.h +@@ -23,6 +23,7 @@ void unix_del_edges(struct scm_fp_list *fpl); + void unix_update_edges(struct unix_sock *receiver); + int unix_prepare_fpl(struct scm_fp_list *fpl); + void unix_destroy_fpl(struct scm_fp_list *fpl); ++void unix_peek_fpl(struct scm_fp_list *fpl); + void unix_gc(void); + void wait_for_unix_gc(struct scm_fp_list *fpl); + +diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c +index 87908ae74efb3..c621f00902752 100644 +--- a/net/unix/af_unix.c ++++ b/net/unix/af_unix.c +@@ -1848,6 +1848,8 @@ static void unix_detach_fds(struct scm_cookie *scm, struct sk_buff *skb) + static void unix_peek_fds(struct scm_cookie *scm, struct sk_buff *skb) + { + scm->fp = scm_fp_dup(UNIXCB(skb).fp); ++ ++ unix_peek_fpl(scm->fp); + } + + static void unix_destruct_scm(struct sk_buff *skb) +diff --git a/net/unix/garbage.c b/net/unix/garbage.c +index 66fd606c43f45..1cdb54c61619f 100644 +--- a/net/unix/garbage.c ++++ b/net/unix/garbage.c +@@ -306,6 +306,25 @@ void unix_destroy_fpl(struct scm_fp_list *fpl) + unix_free_vertices(fpl); + } + ++static bool gc_in_progress; ++static seqcount_t unix_peek_seq = SEQCNT_ZERO(unix_peek_seq); ++ ++void unix_peek_fpl(struct scm_fp_list *fpl) ++{ ++ static DEFINE_SPINLOCK(unix_peek_lock); ++ ++ if (!fpl || !fpl->count_unix) ++ return; ++ ++ if (!READ_ONCE(gc_in_progress)) ++ return; ++ ++ /* Invalidate the final refcnt check in unix_vertex_dead(). */ ++ spin_lock(&unix_peek_lock); ++ raw_write_seqcount_barrier(&unix_peek_seq); ++ spin_unlock(&unix_peek_lock); ++} ++ + static bool unix_vertex_dead(struct unix_vertex *vertex) + { + struct unix_edge *edge; +@@ -339,6 +358,36 @@ static bool unix_vertex_dead(struct unix_vertex *vertex) + return true; + } + ++static LIST_HEAD(unix_visited_vertices); ++static unsigned long unix_vertex_grouped_index = UNIX_VERTEX_INDEX_MARK2; ++ ++static bool unix_scc_dead(struct list_head *scc, bool fast) ++{ ++ struct unix_vertex *vertex; ++ bool scc_dead = true; ++ unsigned int seq; ++ ++ seq = read_seqcount_begin(&unix_peek_seq); ++ ++ list_for_each_entry_reverse(vertex, scc, scc_entry) { ++ /* Don't restart DFS from this vertex. */ ++ list_move_tail(&vertex->entry, &unix_visited_vertices); ++ ++ /* Mark vertex as off-stack for __unix_walk_scc(). */ ++ if (!fast) ++ vertex->index = unix_vertex_grouped_index; ++ ++ if (scc_dead) ++ scc_dead = unix_vertex_dead(vertex); ++ } ++ ++ /* If MSG_PEEK intervened, defer this SCC to the next round. */ ++ if (read_seqcount_retry(&unix_peek_seq, seq)) ++ return false; ++ ++ return scc_dead; ++} ++ + static void unix_collect_skb(struct list_head *scc, struct sk_buff_head *hitlist) + { + struct unix_vertex *vertex; +@@ -392,9 +441,6 @@ static bool unix_scc_cyclic(struct list_head *scc) + return false; + } + +-static LIST_HEAD(unix_visited_vertices); +-static unsigned long unix_vertex_grouped_index = UNIX_VERTEX_INDEX_MARK2; +- + static void __unix_walk_scc(struct unix_vertex *vertex, unsigned long *last_index, + struct sk_buff_head *hitlist) + { +@@ -460,9 +506,7 @@ static void __unix_walk_scc(struct unix_vertex *vertex, unsigned long *last_inde + } + + if (vertex->index == vertex->scc_index) { +- struct unix_vertex *v; + struct list_head scc; +- bool scc_dead = true; + + /* SCC finalised. + * +@@ -471,18 +515,7 @@ static void __unix_walk_scc(struct unix_vertex *vertex, unsigned long *last_inde + */ + __list_cut_position(&scc, &vertex_stack, &vertex->scc_entry); + +- list_for_each_entry_reverse(v, &scc, scc_entry) { +- /* Don't restart DFS from this vertex in unix_walk_scc(). */ +- list_move_tail(&v->entry, &unix_visited_vertices); +- +- /* Mark vertex as off-stack. */ +- v->index = unix_vertex_grouped_index; +- +- if (scc_dead) +- scc_dead = unix_vertex_dead(v); +- } +- +- if (scc_dead) { ++ if (unix_scc_dead(&scc, false)) { + unix_collect_skb(&scc, hitlist); + } else { + if (unix_vertex_max_scc_index < vertex->scc_index) +@@ -530,19 +563,11 @@ static void unix_walk_scc_fast(struct sk_buff_head *hitlist) + while (!list_empty(&unix_unvisited_vertices)) { + struct unix_vertex *vertex; + struct list_head scc; +- bool scc_dead = true; + + vertex = list_first_entry(&unix_unvisited_vertices, typeof(*vertex), entry); + list_add(&scc, &vertex->scc_entry); + +- list_for_each_entry_reverse(vertex, &scc, scc_entry) { +- list_move_tail(&vertex->entry, &unix_visited_vertices); +- +- if (scc_dead) +- scc_dead = unix_vertex_dead(vertex); +- } +- +- if (scc_dead) ++ if (unix_scc_dead(&scc, true)) + unix_collect_skb(&scc, hitlist); + else if (!unix_graph_maybe_cyclic) + unix_graph_maybe_cyclic = unix_scc_cyclic(&scc); +@@ -553,8 +578,6 @@ static void unix_walk_scc_fast(struct sk_buff_head *hitlist) + list_replace_init(&unix_visited_vertices, &unix_unvisited_vertices); + } + +-static bool gc_in_progress; +- + static void __unix_gc(struct work_struct *work) + { + struct sk_buff_head hitlist; +-- +2.53.0 + diff --git a/queue-6.6/ksmbd-add-durable-scavenger-timer.patch b/queue-6.6/ksmbd-add-durable-scavenger-timer.patch new file mode 100644 index 0000000000..300d148afe --- /dev/null +++ b/queue-6.6/ksmbd-add-durable-scavenger-timer.patch @@ -0,0 +1,340 @@ +From 7fc3e7059ed541e35a9f7fd9798b258e66309ae4 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 15:58:40 +0800 +Subject: ksmbd: add durable scavenger timer + +From: Namjae Jeon + +[ Upstream commit d484d621d40f4a8b8959008802d79bef3609641b ] + +Launch ksmbd-durable-scavenger kernel thread to scan durable fps that +have not been reclaimed by a client within the configured time. + +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +[ Minor context conflict resolved. ] +Signed-off-by: Alva Lan +Signed-off-by: Sasha Levin +--- + fs/smb/server/mgmt/user_session.c | 2 + + fs/smb/server/server.c | 1 + + fs/smb/server/server.h | 1 + + fs/smb/server/smb2pdu.c | 2 +- + fs/smb/server/smb2pdu.h | 2 + + fs/smb/server/vfs_cache.c | 163 +++++++++++++++++++++++++++++- + fs/smb/server/vfs_cache.h | 2 + + 7 files changed, 167 insertions(+), 6 deletions(-) + +diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c +index 6c7fbd589087e..b4a1bd037b9ee 100644 +--- a/fs/smb/server/mgmt/user_session.c ++++ b/fs/smb/server/mgmt/user_session.c +@@ -164,6 +164,7 @@ void ksmbd_session_destroy(struct ksmbd_session *sess) + + ksmbd_tree_conn_session_logoff(sess); + ksmbd_destroy_file_table(&sess->file_table); ++ ksmbd_launch_ksmbd_durable_scavenger(); + ksmbd_session_rpc_clear_list(sess); + free_channel_list(sess); + kfree(sess->Preauth_HashValue); +@@ -399,6 +400,7 @@ void destroy_previous_session(struct ksmbd_conn *conn, + ksmbd_destroy_file_table(&prev_sess->file_table); + prev_sess->state = SMB2_SESSION_EXPIRED; + ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP); ++ ksmbd_launch_ksmbd_durable_scavenger(); + out: + up_write(&conn->session_lock); + up_write(&sessions_table_lock); +diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c +index 598601a4bf92c..416b14267251d 100644 +--- a/fs/smb/server/server.c ++++ b/fs/smb/server/server.c +@@ -372,6 +372,7 @@ static void server_ctrl_handle_reset(struct server_ctrl_struct *ctrl) + { + ksmbd_ipc_soft_reset(); + ksmbd_conn_transport_destroy(); ++ ksmbd_stop_durable_scavenger(); + server_conf_free(); + server_conf_init(); + WRITE_ONCE(server_conf.state, SERVER_STATE_STARTING_UP); +diff --git a/fs/smb/server/server.h b/fs/smb/server/server.h +index 48bd203abb441..e3a0f6c9c2c35 100644 +--- a/fs/smb/server/server.h ++++ b/fs/smb/server/server.h +@@ -47,6 +47,7 @@ struct ksmbd_server_config { + + char *conf[SERVER_CONF_WORK_GROUP + 1]; + bool bind_interfaces_only; ++ struct task_struct *dh_task; + }; + + extern struct ksmbd_server_config server_conf; +diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c +index d68fe617369e0..66163a464c563 100644 +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -3601,7 +3601,7 @@ int smb2_open(struct ksmbd_work *work) + SMB2_CREATE_GUID_SIZE); + if (dh_info.timeout) + fp->durable_timeout = min(dh_info.timeout, +- 300000); ++ DURABLE_HANDLE_MAX_TIMEOUT); + else + fp->durable_timeout = 60; + } +diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h +index 2821e6c8298f4..ad02d0a376025 100644 +--- a/fs/smb/server/smb2pdu.h ++++ b/fs/smb/server/smb2pdu.h +@@ -75,6 +75,8 @@ struct create_durable_req_v2 { + __u8 CreateGuid[16]; + } __packed; + ++#define DURABLE_HANDLE_MAX_TIMEOUT 300000 ++ + struct create_durable_reconn_req { + struct create_context_hdr ccontext; + __u8 Name[8]; +diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c +index 729758697c129..913c2a8d2b0ee 100644 +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -8,6 +8,8 @@ + #include + #include + #include ++#include ++#include + + #include "glob.h" + #include "vfs_cache.h" +@@ -17,6 +19,7 @@ + #include "mgmt/tree_connect.h" + #include "mgmt/user_session.h" + #include "smb_common.h" ++#include "server.h" + + #define S_DEL_PENDING 1 + #define S_DEL_ON_CLS 2 +@@ -31,6 +34,10 @@ static struct ksmbd_file_table global_ft; + static atomic_long_t fd_limit; + static struct kmem_cache *filp_cache; + ++static bool durable_scavenger_running; ++static DEFINE_MUTEX(durable_scavenger_lock); ++static wait_queue_head_t dh_wq; ++ + void ksmbd_set_fd_limit(unsigned long limit) + { + limit = min(limit, get_max_files()); +@@ -316,9 +323,16 @@ static void __ksmbd_remove_durable_fd(struct ksmbd_file *fp) + if (!has_file_id(fp->persistent_id)) + return; + +- write_lock(&global_ft.lock); + idr_remove(global_ft.idr, fp->persistent_id); ++} ++ ++static void ksmbd_remove_durable_fd(struct ksmbd_file *fp) ++{ ++ write_lock(&global_ft.lock); ++ __ksmbd_remove_durable_fd(fp); + write_unlock(&global_ft.lock); ++ if (waitqueue_active(&dh_wq)) ++ wake_up(&dh_wq); + } + + static void __ksmbd_remove_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp) +@@ -341,7 +355,7 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp) + struct ksmbd_lock *smb_lock, *tmp_lock; + + fd_limit_close(); +- __ksmbd_remove_durable_fd(fp); ++ ksmbd_remove_durable_fd(fp); + if (ft) + __ksmbd_remove_fd(ft, fp); + +@@ -754,6 +768,142 @@ static bool tree_conn_fd_check(struct ksmbd_tree_connect *tcon, + return fp->tcon != tcon; + } + ++static bool ksmbd_durable_scavenger_alive(void) ++{ ++ mutex_lock(&durable_scavenger_lock); ++ if (!durable_scavenger_running) { ++ mutex_unlock(&durable_scavenger_lock); ++ return false; ++ } ++ mutex_unlock(&durable_scavenger_lock); ++ ++ if (kthread_should_stop()) ++ return false; ++ ++ if (idr_is_empty(global_ft.idr)) ++ return false; ++ ++ return true; ++} ++ ++static void ksmbd_scavenger_dispose_dh(struct list_head *head) ++{ ++ while (!list_empty(head)) { ++ struct ksmbd_file *fp; ++ ++ fp = list_first_entry(head, struct ksmbd_file, node); ++ list_del_init(&fp->node); ++ __ksmbd_close_fd(NULL, fp); ++ } ++} ++ ++static int ksmbd_durable_scavenger(void *dummy) ++{ ++ struct ksmbd_file *fp = NULL; ++ unsigned int id; ++ unsigned int min_timeout = 1; ++ bool found_fp_timeout; ++ LIST_HEAD(scavenger_list); ++ unsigned long remaining_jiffies; ++ ++ __module_get(THIS_MODULE); ++ ++ set_freezable(); ++ while (ksmbd_durable_scavenger_alive()) { ++ if (try_to_freeze()) ++ continue; ++ ++ found_fp_timeout = false; ++ ++ remaining_jiffies = wait_event_timeout(dh_wq, ++ ksmbd_durable_scavenger_alive() == false, ++ __msecs_to_jiffies(min_timeout)); ++ if (remaining_jiffies) ++ min_timeout = jiffies_to_msecs(remaining_jiffies); ++ else ++ min_timeout = DURABLE_HANDLE_MAX_TIMEOUT; ++ ++ write_lock(&global_ft.lock); ++ idr_for_each_entry(global_ft.idr, fp, id) { ++ if (!fp->durable_timeout) ++ continue; ++ ++ if (atomic_read(&fp->refcount) > 1 || ++ fp->conn) ++ continue; ++ ++ found_fp_timeout = true; ++ if (fp->durable_scavenger_timeout <= ++ jiffies_to_msecs(jiffies)) { ++ __ksmbd_remove_durable_fd(fp); ++ list_add(&fp->node, &scavenger_list); ++ } else { ++ unsigned long durable_timeout; ++ ++ durable_timeout = ++ fp->durable_scavenger_timeout - ++ jiffies_to_msecs(jiffies); ++ ++ if (min_timeout > durable_timeout) ++ min_timeout = durable_timeout; ++ } ++ } ++ write_unlock(&global_ft.lock); ++ ++ ksmbd_scavenger_dispose_dh(&scavenger_list); ++ ++ if (found_fp_timeout == false) ++ break; ++ } ++ ++ mutex_lock(&durable_scavenger_lock); ++ durable_scavenger_running = false; ++ mutex_unlock(&durable_scavenger_lock); ++ ++ module_put(THIS_MODULE); ++ ++ return 0; ++} ++ ++void ksmbd_launch_ksmbd_durable_scavenger(void) ++{ ++ if (!(server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE)) ++ return; ++ ++ mutex_lock(&durable_scavenger_lock); ++ if (durable_scavenger_running == true) { ++ mutex_unlock(&durable_scavenger_lock); ++ return; ++ } ++ ++ durable_scavenger_running = true; ++ ++ server_conf.dh_task = kthread_run(ksmbd_durable_scavenger, ++ (void *)NULL, "ksmbd-durable-scavenger"); ++ if (IS_ERR(server_conf.dh_task)) ++ pr_err("cannot start conn thread, err : %ld\n", ++ PTR_ERR(server_conf.dh_task)); ++ mutex_unlock(&durable_scavenger_lock); ++} ++ ++void ksmbd_stop_durable_scavenger(void) ++{ ++ if (!(server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE)) ++ return; ++ ++ mutex_lock(&durable_scavenger_lock); ++ if (!durable_scavenger_running) { ++ mutex_unlock(&durable_scavenger_lock); ++ return; ++ } ++ ++ durable_scavenger_running = false; ++ if (waitqueue_active(&dh_wq)) ++ wake_up(&dh_wq); ++ mutex_unlock(&durable_scavenger_lock); ++ kthread_stop(server_conf.dh_task); ++} ++ + static bool session_fd_check(struct ksmbd_tree_connect *tcon, + struct ksmbd_file *fp) + { +@@ -823,11 +973,12 @@ void ksmbd_free_global_file_table(void) + unsigned int id; + + idr_for_each_entry(global_ft.idr, fp, id) { +- __ksmbd_remove_durable_fd(fp); +- kmem_cache_free(filp_cache, fp); ++ ksmbd_remove_durable_fd(fp); ++ __ksmbd_close_fd(NULL, fp); + } + +- ksmbd_destroy_file_table(&global_ft); ++ idr_destroy(global_ft.idr); ++ kfree(global_ft.idr); + } + + int ksmbd_validate_name_reconnect(struct ksmbd_share_config *share, +@@ -934,6 +1085,8 @@ int ksmbd_init_file_cache(void) + if (!filp_cache) + goto out; + ++ init_waitqueue_head(&dh_wq); ++ + return 0; + + out: +diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h +index f2ab1514e81a7..b0f6d0f94cb8d 100644 +--- a/fs/smb/server/vfs_cache.h ++++ b/fs/smb/server/vfs_cache.h +@@ -153,6 +153,8 @@ struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid); + struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry); + unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp); + struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp); ++void ksmbd_launch_ksmbd_durable_scavenger(void); ++void ksmbd_stop_durable_scavenger(void); + void ksmbd_close_tree_conn_fds(struct ksmbd_work *work); + void ksmbd_close_session_fds(struct ksmbd_work *work); + int ksmbd_close_inode_fds(struct ksmbd_work *work, struct inode *inode); +-- +2.53.0 + diff --git a/queue-6.6/ksmbd-avoid-reclaiming-expired-durable-opens-by-the-.patch b/queue-6.6/ksmbd-avoid-reclaiming-expired-durable-opens-by-the-.patch new file mode 100644 index 0000000000..e2761c2855 --- /dev/null +++ b/queue-6.6/ksmbd-avoid-reclaiming-expired-durable-opens-by-the-.patch @@ -0,0 +1,64 @@ +From 2801aa302595b58eb021a9cffe4c8247edacf0d3 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 15:58:39 +0800 +Subject: ksmbd: avoid reclaiming expired durable opens by the client + +From: Namjae Jeon + +[ Upstream commit 520da3c488c5bb177871634e713eb8a106082e6b ] + +The expired durable opens should not be reclaimed by client. +This patch add ->durable_scavenger_timeout to fp and check it in +ksmbd_lookup_durable_fd(). + +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Alva Lan +Signed-off-by: Sasha Levin +--- + fs/smb/server/vfs_cache.c | 9 ++++++++- + fs/smb/server/vfs_cache.h | 1 + + 2 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c +index eacc6ef41db03..729758697c129 100644 +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -515,7 +515,10 @@ struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id) + struct ksmbd_file *fp; + + fp = __ksmbd_lookup_fd(&global_ft, id); +- if (fp && fp->conn) { ++ if (fp && (fp->conn || ++ (fp->durable_scavenger_timeout && ++ (fp->durable_scavenger_timeout < ++ jiffies_to_msecs(jiffies))))) { + ksmbd_put_durable_fd(fp); + fp = NULL; + } +@@ -784,6 +787,10 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon, + fp->tcon = NULL; + fp->volatile_id = KSMBD_NO_FID; + ++ if (fp->durable_timeout) ++ fp->durable_scavenger_timeout = ++ jiffies_to_msecs(jiffies) + fp->durable_timeout; ++ + return true; + } + +diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h +index 5a225e7055f19..f2ab1514e81a7 100644 +--- a/fs/smb/server/vfs_cache.h ++++ b/fs/smb/server/vfs_cache.h +@@ -101,6 +101,7 @@ struct ksmbd_file { + struct list_head lock_list; + + int durable_timeout; ++ int durable_scavenger_timeout; + + /* if ls is happening on directory, below is valid*/ + struct ksmbd_readdir_data readdir_data; +-- +2.53.0 + diff --git a/queue-6.6/ksmbd-close-durable-scavenger-races-against-m_fp_lis.patch b/queue-6.6/ksmbd-close-durable-scavenger-races-against-m_fp_lis.patch new file mode 100644 index 0000000000..b79dac49ac --- /dev/null +++ b/queue-6.6/ksmbd-close-durable-scavenger-races-against-m_fp_lis.patch @@ -0,0 +1,261 @@ +From 83fd305811e5ad402a2f4d2343c54a31f0267721 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 15:58:42 +0800 +Subject: ksmbd: close durable scavenger races against m_fp_list lookups + +From: DaeMyung Kang + +[ Upstream commit bf736184d063da1a552ffeff0481813599a182cc ] + +ksmbd_durable_scavenger() has two related races against any walker +that iterates f_ci->m_fp_list, including ksmbd_lookup_fd_inode() +(used by ksmbd_vfs_rename) and the share-mode checks in +fs/smb/server/smb_common.c. + +(1) fp->node list-head reuse. Durable-preserved handles can remain +linked on f_ci->m_fp_list after session teardown so share-mode checks +still see them while the handle is reconnectable. The scavenger +collected expired handles by adding fp->node to a local +scavenger_list after removing them from the global durable idr. +Because fp->node is the same list_head used by m_fp_list, +list_add(&fp->node, &scavenger_list) overwrites the m_fp_list links +and corrupts both lists. CONFIG_DEBUG_LIST can report this on the +share-mode walk path. + +(2) Refcount race against m_fp_list walkers. The scavenger qualifies +an expired durable handle with atomic_read(&fp->refcount) > 1 and +fp->conn under global_ft.lock, removes fp from global_ft, then drops +global_ft.lock before unlinking fp from m_fp_list and freeing it. +During that gap fp is still linked on m_fp_list with f_state == +FP_INITED. ksmbd_lookup_fd_inode() under m_lock read calls +ksmbd_fp_get() (atomic_inc_not_zero on refcount that is still 1) and +takes a live reference; the scavenger then unlinks and frees fp +while the holder owns a reference, leading to UAF on the holder's +subsequent ksmbd_fd_put() and on any field reads performed by a +concurrent share-mode walker that iterates m_fp_list without taking +ksmbd_fp_get() (smb_check_perm_dleases-like paths). + +Fix both: + + * Stop reusing fp->node as a scavenger-private list node. Remove + one expired handle from global_ft under global_ft.lock, take an + explicit transient reference, drop the lock, unlink fp->node + from m_fp_list under f_ci->m_lock, then drop both the durable + lifetime and transient references with atomic_sub_and_test(2, + &fp->refcount). If the scavenger is the last putter the close + runs there; otherwise an in-flight holder that already raced + through the m_fp_list lookup owns the final close via its + ksmbd_fd_put() path. The one-at-a-time disposal can rescan the + durable idr when multiple handles expire in the same pass, but + durable scavenging is a background expiration path and the final + full scan recomputes min_timeout before the next wait. + + * Clear fp->persistent_id inside __ksmbd_remove_durable_fd() right + after idr_remove(), so a delayed final close from a holder that + snatched fp does not re-issue idr_remove() on a persistent id + that idr_alloc_cyclic() in ksmbd_open_durable_fd() may have + already handed out to a brand-new durable handle. + + * Bypass the per-conn open_files_count decrement in + __put_fd_final() when fp is detached from any session table + (fp->conn cleared by session_fd_check() at durable preserve -- + paired with the volatile_id clear at unpublish, so checking + fp->conn alone is sufficient). The walker that owns the final + close runs from an unrelated work->conn whose + stats.open_files_count never tracked this durable fp; without + this guard the holder would underflow that unrelated counter. + +The two races are folded into one patch because patch (1) alone +cleans up the corrupted list but leaves a deterministic UAF window +for m_fp_list walkers that the transient-reference and +persistent_id discipline in (2) close; bisecting onto an +intermediate state would land on a UAF that pre-patch chaos merely +made less reproducible. + +Validation: + * CONFIG_DEBUG_LIST coverage for the list_head reuse path. + * KASAN-enabled direct SMB2 durable-handle coverage that exercised + ksmbd_durable_scavenger() and non-NULL ksmbd_lookup_fd_inode() + returns while durable handles expired under concurrent rename + lookups, with no KASAN, UAF, list-corruption, ODEBUG, or WARNING + reports. + * checkpatch --strict + * make -j$(nproc) M=fs/smb/server + +Fixes: d484d621d40f ("ksmbd: add durable scavenger timer") +Signed-off-by: DaeMyung Kang +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Alva Lan +Signed-off-by: Sasha Levin +--- + fs/smb/server/vfs_cache.c | 104 ++++++++++++++++++++++++++++---------- + 1 file changed, 77 insertions(+), 27 deletions(-) + +diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c +index 544387c9a6f4b..8faa5d97f7e1e 100644 +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -325,6 +325,14 @@ static void __ksmbd_remove_durable_fd(struct ksmbd_file *fp) + return; + + idr_remove(global_ft.idr, fp->persistent_id); ++ /* ++ * Clear persistent_id so a later __ksmbd_close_fd() that runs from a ++ * delayed putter (e.g. when a concurrent ksmbd_lookup_fd_inode() ++ * walker held the final reference) does not re-issue idr_remove() on ++ * an id that idr_alloc_cyclic() may have already handed out to a new ++ * durable handle. ++ */ ++ fp->persistent_id = KSMBD_NO_FID; + } + + static void ksmbd_remove_durable_fd(struct ksmbd_file *fp) +@@ -417,6 +425,20 @@ static struct ksmbd_file *__ksmbd_lookup_fd(struct ksmbd_file_table *ft, + + static void __put_fd_final(struct ksmbd_work *work, struct ksmbd_file *fp) + { ++ /* ++ * Detached durable fp -- session_fd_check() cleared fp->conn at ++ * preserve, so this fp is no longer tracked by any conn's ++ * stats.open_files_count. This happens when ++ * ksmbd_scavenger_dispose_dh() hands the final close off to an ++ * m_fp_list walker (e.g. ksmbd_lookup_fd_inode()) whose work->conn ++ * is unrelated to the conn that originally opened the handle; close ++ * via the NULL-ft path so we do not underflow that unrelated ++ * counter. ++ */ ++ if (!fp->conn) { ++ __ksmbd_close_fd(NULL, fp); ++ return; ++ } + __ksmbd_close_fd(&work->sess->file_table, fp); + atomic_dec(&work->conn->stats.open_files_count); + } +@@ -792,24 +814,37 @@ static bool ksmbd_durable_scavenger_alive(void) + return true; + } + +-static void ksmbd_scavenger_dispose_dh(struct list_head *head) ++static void ksmbd_scavenger_dispose_dh(struct ksmbd_file *fp) + { +- while (!list_empty(head)) { +- struct ksmbd_file *fp; ++ /* ++ * Durable-preserved fp can remain linked on f_ci->m_fp_list for ++ * share-mode checks. Unlink it before final close; fp->node is not ++ * available as a scavenger-private list node because re-adding it to ++ * another list corrupts m_fp_list. ++ */ ++ down_write(&fp->f_ci->m_lock); ++ list_del_init(&fp->node); ++ up_write(&fp->f_ci->m_lock); + +- fp = list_first_entry(head, struct ksmbd_file, node); +- list_del_init(&fp->node); ++ /* ++ * Drop both the durable lifetime reference and the transient reference ++ * taken by the scavenger under global_ft.lock. If a concurrent ++ * ksmbd_lookup_fd_inode() (or any other m_fp_list walker) snatched fp ++ * before the unlink above, that holder owns the final close via ++ * ksmbd_fd_put() -> __ksmbd_close_fd(). Otherwise the scavenger is ++ * the last putter and finalises fp here. ++ */ ++ if (atomic_sub_and_test(2, &fp->refcount)) + __ksmbd_close_fd(NULL, fp); +- } + } + + static int ksmbd_durable_scavenger(void *dummy) + { + struct ksmbd_file *fp = NULL; ++ struct ksmbd_file *expired_fp; + unsigned int id; + unsigned int min_timeout = 1; + bool found_fp_timeout; +- LIST_HEAD(scavenger_list); + unsigned long remaining_jiffies; + + __module_get(THIS_MODULE); +@@ -819,8 +854,6 @@ static int ksmbd_durable_scavenger(void *dummy) + if (try_to_freeze()) + continue; + +- found_fp_timeout = false; +- + remaining_jiffies = wait_event_timeout(dh_wq, + ksmbd_durable_scavenger_alive() == false, + __msecs_to_jiffies(min_timeout)); +@@ -829,23 +862,39 @@ static int ksmbd_durable_scavenger(void *dummy) + else + min_timeout = DURABLE_HANDLE_MAX_TIMEOUT; + +- write_lock(&global_ft.lock); +- idr_for_each_entry(global_ft.idr, fp, id) { +- if (!fp->durable_timeout) +- continue; +- +- if (atomic_read(&fp->refcount) > 1 || +- fp->conn) +- continue; +- +- found_fp_timeout = true; +- if (fp->durable_scavenger_timeout <= +- jiffies_to_msecs(jiffies)) { +- __ksmbd_remove_durable_fd(fp); +- list_add(&fp->node, &scavenger_list); +- } else { ++ do { ++ expired_fp = NULL; ++ found_fp_timeout = false; ++ ++ write_lock(&global_ft.lock); ++ idr_for_each_entry(global_ft.idr, fp, id) { + unsigned long durable_timeout; + ++ if (!fp->durable_timeout) ++ continue; ++ ++ if (atomic_read(&fp->refcount) > 1 || ++ fp->conn) ++ continue; ++ ++ found_fp_timeout = true; ++ if (fp->durable_scavenger_timeout <= ++ jiffies_to_msecs(jiffies)) { ++ __ksmbd_remove_durable_fd(fp); ++ /* ++ * Take a transient reference so fp ++ * cannot be freed by an in-flight ++ * ksmbd_lookup_fd_inode() that found ++ * it through f_ci->m_fp_list while we ++ * drop global_ft.lock and reach the ++ * m_fp_list unlink in ++ * ksmbd_scavenger_dispose_dh(). ++ */ ++ atomic_inc(&fp->refcount); ++ expired_fp = fp; ++ break; ++ } ++ + durable_timeout = + fp->durable_scavenger_timeout - + jiffies_to_msecs(jiffies); +@@ -853,10 +902,11 @@ static int ksmbd_durable_scavenger(void *dummy) + if (min_timeout > durable_timeout) + min_timeout = durable_timeout; + } +- } +- write_unlock(&global_ft.lock); ++ write_unlock(&global_ft.lock); + +- ksmbd_scavenger_dispose_dh(&scavenger_list); ++ if (expired_fp) ++ ksmbd_scavenger_dispose_dh(expired_fp); ++ } while (expired_fp); + + if (found_fp_timeout == false) + break; +-- +2.53.0 + diff --git a/queue-6.6/ksmbd-validate-owner-of-durable-handle-on-reconnect.patch b/queue-6.6/ksmbd-validate-owner-of-durable-handle-on-reconnect.patch new file mode 100644 index 0000000000..5ad294a21b --- /dev/null +++ b/queue-6.6/ksmbd-validate-owner-of-durable-handle-on-reconnect.patch @@ -0,0 +1,340 @@ +From c6d5bef08e38756c8b868eade3c0c7e9d09c64b5 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 26 May 2026 15:58:41 +0800 +Subject: ksmbd: validate owner of durable handle on reconnect + +From: Namjae Jeon + +[ Upstream commit 49110a8ce654bbe56bef7c5e44cce31f4b102b8a ] + +Currently, ksmbd does not verify if the user attempting to reconnect +to a durable handle is the same user who originally opened the file. +This allows any authenticated user to hijack an orphaned durable handle +by predicting or brute-forcing the persistent ID. + +According to MS-SMB2, the server MUST verify that the SecurityContext +of the reconnect request matches the SecurityContext associated with +the existing open. +Add a durable_owner structure to ksmbd_file to store the original opener's +UID, GID, and account name. and catpure the owner information when a file +handle becomes orphaned. and implementing ksmbd_vfs_compare_durable_owner() +to validate the identity of the requester during SMB2_CREATE (DHnC). + +Fixes: c8efcc786146 ("ksmbd: add support for durable handles v1/v2") +Reported-by: Davide Ornaghi +Reported-by: Navaneeth K +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +[ Minor context conflict resolved. ] +Signed-off-by: Alva Lan +Signed-off-by: Sasha Levin +--- + fs/smb/server/mgmt/user_session.c | 8 +-- + fs/smb/server/oplock.c | 7 +++ + fs/smb/server/oplock.h | 1 + + fs/smb/server/smb2pdu.c | 3 +- + fs/smb/server/vfs_cache.c | 87 +++++++++++++++++++++++++++---- + fs/smb/server/vfs_cache.h | 12 ++++- + 6 files changed, 103 insertions(+), 15 deletions(-) + +diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c +index b4a1bd037b9ee..d9768f88aabc0 100644 +--- a/fs/smb/server/mgmt/user_session.c ++++ b/fs/smb/server/mgmt/user_session.c +@@ -159,11 +159,10 @@ void ksmbd_session_destroy(struct ksmbd_session *sess) + if (!sess) + return; + ++ ksmbd_tree_conn_session_logoff(sess); ++ ksmbd_destroy_file_table(sess); + if (sess->user) + ksmbd_free_user(sess->user); +- +- ksmbd_tree_conn_session_logoff(sess); +- ksmbd_destroy_file_table(&sess->file_table); + ksmbd_launch_ksmbd_durable_scavenger(); + ksmbd_session_rpc_clear_list(sess); + free_channel_list(sess); +@@ -397,7 +396,8 @@ void destroy_previous_session(struct ksmbd_conn *conn, + ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP); + goto out; + } +- ksmbd_destroy_file_table(&prev_sess->file_table); ++ ++ ksmbd_destroy_file_table(prev_sess); + prev_sess->state = SMB2_SESSION_EXPIRED; + ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP); + ksmbd_launch_ksmbd_durable_scavenger(); +diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c +index c49a75ff5fbb4..8487fca425bdb 100644 +--- a/fs/smb/server/oplock.c ++++ b/fs/smb/server/oplock.c +@@ -1841,6 +1841,7 @@ int smb2_check_durable_oplock(struct ksmbd_conn *conn, + struct ksmbd_share_config *share, + struct ksmbd_file *fp, + struct lease_ctx_info *lctx, ++ struct ksmbd_user *user, + char *name) + { + struct oplock_info *opinfo = opinfo_get(fp); +@@ -1849,6 +1850,12 @@ int smb2_check_durable_oplock(struct ksmbd_conn *conn, + if (!opinfo) + return 0; + ++ if (ksmbd_vfs_compare_durable_owner(fp, user) == false) { ++ ksmbd_debug(SMB, "Durable handle reconnect failed: owner mismatch\n"); ++ ret = -EBADF; ++ goto out; ++ } ++ + if (opinfo->is_lease == false) { + if (lctx) { + pr_err("create context include lease\n"); +diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h +index f8da0bba766ba..e6c4fbe5cf4e0 100644 +--- a/fs/smb/server/oplock.h ++++ b/fs/smb/server/oplock.h +@@ -133,5 +133,6 @@ int smb2_check_durable_oplock(struct ksmbd_conn *conn, + struct ksmbd_share_config *share, + struct ksmbd_file *fp, + struct lease_ctx_info *lctx, ++ struct ksmbd_user *user, + char *name); + #endif /* __KSMBD_OPLOCK_H */ +diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c +index 66163a464c563..04d4a784deaf9 100644 +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -2994,7 +2994,8 @@ int smb2_open(struct ksmbd_work *work) + } + + if (dh_info.reconnected == true) { +- rc = smb2_check_durable_oplock(conn, share, dh_info.fp, lc, name); ++ rc = smb2_check_durable_oplock(conn, share, dh_info.fp, ++ lc, sess->user, name); + if (rc) { + ksmbd_put_durable_fd(dh_info.fp); + goto err_out2; +diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c +index 913c2a8d2b0ee..544387c9a6f4b 100644 +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -18,6 +18,7 @@ + #include "connection.h" + #include "mgmt/tree_connect.h" + #include "mgmt/user_session.h" ++#include "mgmt/user_config.h" + #include "smb_common.h" + #include "server.h" + +@@ -383,6 +384,8 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp) + + if (ksmbd_stream_fd(fp)) + kfree(fp->stream.name); ++ kfree(fp->owner.name); ++ + kmem_cache_free(filp_cache, fp); + } + +@@ -694,11 +697,13 @@ void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp, + } + + static int +-__close_file_table_ids(struct ksmbd_file_table *ft, ++__close_file_table_ids(struct ksmbd_session *sess, + struct ksmbd_tree_connect *tcon, + bool (*skip)(struct ksmbd_tree_connect *tcon, +- struct ksmbd_file *fp)) ++ struct ksmbd_file *fp, ++ struct ksmbd_user *user)) + { ++ struct ksmbd_file_table *ft = &sess->file_table; + struct ksmbd_file *fp; + unsigned int id = 0; + int num = 0; +@@ -711,7 +716,7 @@ __close_file_table_ids(struct ksmbd_file_table *ft, + break; + } + +- if (skip(tcon, fp) || ++ if (skip(tcon, fp, sess->user) || + !atomic_dec_and_test(&fp->refcount)) { + id++; + write_unlock(&ft->lock); +@@ -763,7 +768,8 @@ static inline bool is_reconnectable(struct ksmbd_file *fp) + } + + static bool tree_conn_fd_check(struct ksmbd_tree_connect *tcon, +- struct ksmbd_file *fp) ++ struct ksmbd_file *fp, ++ struct ksmbd_user *user) + { + return fp->tcon != tcon; + } +@@ -904,8 +910,62 @@ void ksmbd_stop_durable_scavenger(void) + kthread_stop(server_conf.dh_task); + } + ++/* ++ * ksmbd_vfs_copy_durable_owner - Copy owner info for durable reconnect ++ * @fp: ksmbd file pointer to store owner info ++ * @user: user pointer to copy from ++ * ++ * This function binds the current user's identity to the file handle ++ * to satisfy MS-SMB2 Step 8 (SecurityContext matching) during reconnect. ++ * ++ * Return: 0 on success, or negative error code on failure ++ */ ++static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp, ++ struct ksmbd_user *user) ++{ ++ if (!user) ++ return -EINVAL; ++ ++ /* Duplicate the user name to ensure identity persistence */ ++ fp->owner.name = kstrdup(user->name, GFP_KERNEL); ++ if (!fp->owner.name) ++ return -ENOMEM; ++ ++ fp->owner.uid = user->uid; ++ fp->owner.gid = user->gid; ++ ++ return 0; ++} ++ ++/** ++ * ksmbd_vfs_compare_durable_owner - Verify if the requester is original owner ++ * @fp: existing ksmbd file pointer ++ * @user: user pointer of the reconnect requester ++ * ++ * Compares the UID, GID, and name of the current requester against the ++ * original owner stored in the file handle. ++ * ++ * Return: true if the user matches, false otherwise ++ */ ++bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp, ++ struct ksmbd_user *user) ++{ ++ if (!user || !fp->owner.name) ++ return false; ++ ++ /* Check if the UID and GID match first (fast path) */ ++ if (fp->owner.uid != user->uid || fp->owner.gid != user->gid) ++ return false; ++ ++ /* Validate the account name to ensure the same SecurityContext */ ++ if (strcmp(fp->owner.name, user->name)) ++ return false; ++ ++ return true; ++} ++ + static bool session_fd_check(struct ksmbd_tree_connect *tcon, +- struct ksmbd_file *fp) ++ struct ksmbd_file *fp, struct ksmbd_user *user) + { + struct ksmbd_inode *ci; + struct oplock_info *op; +@@ -915,6 +975,9 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon, + if (!is_reconnectable(fp)) + return false; + ++ if (ksmbd_vfs_copy_durable_owner(fp, user)) ++ return false; ++ + conn = fp->conn; + ci = fp->f_ci; + down_write(&ci->m_lock); +@@ -946,7 +1009,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon, + + void ksmbd_close_tree_conn_fds(struct ksmbd_work *work) + { +- int num = __close_file_table_ids(&work->sess->file_table, ++ int num = __close_file_table_ids(work->sess, + work->tcon, + tree_conn_fd_check); + +@@ -955,7 +1018,7 @@ void ksmbd_close_tree_conn_fds(struct ksmbd_work *work) + + void ksmbd_close_session_fds(struct ksmbd_work *work) + { +- int num = __close_file_table_ids(&work->sess->file_table, ++ int num = __close_file_table_ids(work->sess, + work->tcon, + session_fd_check); + +@@ -1052,6 +1115,10 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp) + } + up_write(&ci->m_lock); + ++ fp->owner.uid = fp->owner.gid = 0; ++ kfree(fp->owner.name); ++ fp->owner.name = NULL; ++ + return 0; + } + +@@ -1066,12 +1133,14 @@ int ksmbd_init_file_table(struct ksmbd_file_table *ft) + return 0; + } + +-void ksmbd_destroy_file_table(struct ksmbd_file_table *ft) ++void ksmbd_destroy_file_table(struct ksmbd_session *sess) + { ++ struct ksmbd_file_table *ft = &sess->file_table; ++ + if (!ft->idr) + return; + +- __close_file_table_ids(ft, NULL, session_fd_check); ++ __close_file_table_ids(sess, NULL, session_fd_check); + idr_destroy(ft->idr); + kfree(ft->idr); + ft->idr = NULL; +diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h +index b0f6d0f94cb8d..79d85cf4e13b3 100644 +--- a/fs/smb/server/vfs_cache.h ++++ b/fs/smb/server/vfs_cache.h +@@ -67,6 +67,13 @@ enum { + FP_CLOSED + }; + ++/* Owner information for durable handle reconnect */ ++struct durable_owner { ++ unsigned int uid; ++ unsigned int gid; ++ char *name; ++}; ++ + struct ksmbd_file { + struct file *filp; + u64 persistent_id; +@@ -111,6 +118,7 @@ struct ksmbd_file { + bool is_durable; + bool is_persistent; + bool is_resilient; ++ struct durable_owner owner; + }; + + static inline void set_ctx_actor(struct dir_context *ctx, +@@ -137,7 +145,7 @@ static inline bool ksmbd_stream_fd(struct ksmbd_file *fp) + } + + int ksmbd_init_file_table(struct ksmbd_file_table *ft); +-void ksmbd_destroy_file_table(struct ksmbd_file_table *ft); ++void ksmbd_destroy_file_table(struct ksmbd_session *sess); + int ksmbd_close_fd(struct ksmbd_work *work, u64 id); + struct ksmbd_file *ksmbd_lookup_fd_fast(struct ksmbd_work *work, u64 id); + struct ksmbd_file *ksmbd_lookup_foreign_fd(struct ksmbd_work *work, u64 id); +@@ -163,6 +171,8 @@ void ksmbd_free_global_file_table(void); + void ksmbd_set_fd_limit(unsigned long limit); + void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp, + unsigned int state); ++bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp, ++ struct ksmbd_user *user); + + /* + * INODE hash +-- +2.53.0 + diff --git a/queue-6.6/series b/queue-6.6/series index 88e063ebc2..377ae91b44 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -8,3 +8,8 @@ driver-core-platform-use-generic-driver_override-inf.patch s390-debug-reject-zero-length-input-before-trimming-.patch wifi-mac80211-check-tdls-flag-in-ieee80211_tdls_oper.patch revert-x86-vdso-fix-output-operand-size-of-rdpid.patch +ksmbd-avoid-reclaiming-expired-durable-opens-by-the-.patch +ksmbd-add-durable-scavenger-timer.patch +ksmbd-validate-owner-of-durable-handle-on-reconnect.patch +ksmbd-close-durable-scavenger-races-against-m_fp_lis.patch +af_unix-give-up-gc-if-msg_peek-intervened.patch -- 2.47.3