--- /dev/null
+From d39d8a6c2379e9f391af9bc1c6fce5075486a060 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 11:56:02 +0200
+Subject: Revert "s390/cio: Fix device lifecycle handling in
+ css_alloc_subchannel()"
+
+From: Ben Hutchings <benh@debian.org>
+
+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 <benh@debian.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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
+
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
--- /dev/null
+From df02035dd06f9a8af94302103c27f8e9326bdfb5 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+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 <sashal@kernel.org>
+---
+ 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
+
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
--- /dev/null
+From 5390425a65397770f0e1255a10f323f81db5d7f7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+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 <sashal@kernel.org>
+---
+ 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
+
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
--- /dev/null
+From d9d1e44a9ea2866f804b4ef6a16a64f07f7a7a79 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 13:45:49 +0800
+Subject: af_unix: Give up GC if MSG_PEEK intervened.
+
+From: Kuniyuki Iwashima <kuniyu@google.com>
+
+[ 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 <sysroot314@gmail.com>
+Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
+Link: https://patch.msgid.link/20260311054043.1231316-1-kuniyu@google.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ Using include/net/af_unix.h instead of net/unix/af_unix.h on 6.12.y ]
+Signed-off-by: Leon Chen <leonchen.oss@139.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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
+
--- /dev/null
+From 54a726c3b0af08e332f6f3a69ddad5883bac8a60 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 09:13:07 +0100
+Subject: drm/imagination: Synchronize interrupts before suspending the GPU
+
+From: Alessio Belle <alessio.belle@imgtec.com>
+
+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 <alessio.belle@imgtec.com>
+Reviewed-by: Matt Coster <matt.coster@imgtec.com>
+Link: https://patch.msgid.link/20260310-drain-irqs-before-suspend-v1-1-bf4f9ed68e75@imgtec.com
+Signed-off-by: Matt Coster <matt.coster@imgtec.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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
+
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
--- /dev/null
+From 626a870bbcfaf2d62354fbb8f09f7044e19eee27 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 13:47:44 +0800
+Subject: af_unix: Give up GC if MSG_PEEK intervened.
+
+From: Kuniyuki Iwashima <kuniyu@google.com>
+
+[ 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 <sysroot314@gmail.com>
+Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
+Link: https://patch.msgid.link/20260311054043.1231316-1-kuniyu@google.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ Using include/net/af_unix.h instead of net/unix/af_unix.h on 6.6 ]
+Signed-off-by: Leon Chen <leonchen.oss@139.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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
+
--- /dev/null
+From 7fc3e7059ed541e35a9f7fd9798b258e66309ae4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 15:58:40 +0800
+Subject: ksmbd: add durable scavenger timer
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+[ 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 <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+[ Minor context conflict resolved. ]
+Signed-off-by: Alva Lan <alvalan9@foxmail.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 <linux/filelock.h>
+ #include <linux/slab.h>
+ #include <linux/vmalloc.h>
++#include <linux/kthread.h>
++#include <linux/freezer.h>
+
+ #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
+
--- /dev/null
+From 2801aa302595b58eb021a9cffe4c8247edacf0d3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 15:58:39 +0800
+Subject: ksmbd: avoid reclaiming expired durable opens by the client
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+[ 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 <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Alva Lan <alvalan9@foxmail.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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
+
--- /dev/null
+From 83fd305811e5ad402a2f4d2343c54a31f0267721 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 15:58:42 +0800
+Subject: ksmbd: close durable scavenger races against m_fp_list lookups
+
+From: DaeMyung Kang <charsyam@gmail.com>
+
+[ 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 <charsyam@gmail.com>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Alva Lan <alvalan9@foxmail.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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
+
--- /dev/null
+From c6d5bef08e38756c8b868eade3c0c7e9d09c64b5 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 26 May 2026 15:58:41 +0800
+Subject: ksmbd: validate owner of durable handle on reconnect
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+[ 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 <d.ornaghi97@gmail.com>
+Reported-by: Navaneeth K <knavaneeth786@gmail.com>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+[ Minor context conflict resolved. ]
+Signed-off-by: Alva Lan <alvalan9@foxmail.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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
+
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