]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
Fixes for all trees
authorSasha Levin <sashal@kernel.org>
Tue, 26 May 2026 13:38:12 +0000 (09:38 -0400)
committerSasha Levin <sashal@kernel.org>
Tue, 26 May 2026 13:38:12 +0000 (09:38 -0400)
Signed-off-by: Sasha Levin <sashal@kernel.org>
15 files changed:
queue-5.10/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch [new file with mode: 0644]
queue-5.10/series
queue-5.15/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch [new file with mode: 0644]
queue-5.15/series
queue-6.1/revert-s390-cio-fix-device-lifecycle-handling-in-css.patch [new file with mode: 0644]
queue-6.1/series
queue-6.12/af_unix-give-up-gc-if-msg_peek-intervened.patch [new file with mode: 0644]
queue-6.12/drm-imagination-synchronize-interrupts-before-suspen.patch [new file with mode: 0644]
queue-6.12/series
queue-6.6/af_unix-give-up-gc-if-msg_peek-intervened.patch [new file with mode: 0644]
queue-6.6/ksmbd-add-durable-scavenger-timer.patch [new file with mode: 0644]
queue-6.6/ksmbd-avoid-reclaiming-expired-durable-opens-by-the-.patch [new file with mode: 0644]
queue-6.6/ksmbd-close-durable-scavenger-races-against-m_fp_lis.patch [new file with mode: 0644]
queue-6.6/ksmbd-validate-owner-of-durable-handle-on-reconnect.patch [new file with mode: 0644]
queue-6.6/series

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 (file)
index 0000000..153f2d4
--- /dev/null
@@ -0,0 +1,36 @@
+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
+
index 86f1c0855e0f4aa8095603182ddeac0d9d3f8962..43d5bc7ec40a0bd4cd43ebdcd94d0a570e383bf6 100644 (file)
@@ -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 (file)
index 0000000..bd993e0
--- /dev/null
@@ -0,0 +1,29 @@
+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
+
index c8a17212b9a9c1acc5cce583f18b109172989018..064c46271e285545e37881dc51073fab09b7a16f 100644 (file)
@@ -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 (file)
index 0000000..533e800
--- /dev/null
@@ -0,0 +1,29 @@
+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
+
index 55ebc556d70bb69c16734722494e33f26781cff4..f64db0c71d1054163f79771a8e155b1d3981f9a6 100644 (file)
@@ -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 (file)
index 0000000..e10e236
--- /dev/null
@@ -0,0 +1,258 @@
+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
+
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 (file)
index 0000000..cf43c3d
--- /dev/null
@@ -0,0 +1,121 @@
+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
+
index 8f81d2a62d3b0e819515ddb806f55c466b14db16..1123323a050f110a66b7ed8d534b68571cf3559d 100644 (file)
@@ -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 (file)
index 0000000..56cb39a
--- /dev/null
@@ -0,0 +1,258 @@
+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
+
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 (file)
index 0000000..300d148
--- /dev/null
@@ -0,0 +1,340 @@
+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
+
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 (file)
index 0000000..e2761c2
--- /dev/null
@@ -0,0 +1,64 @@
+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
+
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 (file)
index 0000000..b79dac4
--- /dev/null
@@ -0,0 +1,261 @@
+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
+
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 (file)
index 0000000..5ad294a
--- /dev/null
@@ -0,0 +1,340 @@
+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
+
index 88e063ebc2248632e49144ab41b6542369f3975f..377ae91b44383fe7d397cd0feeb7bd07f9f13e99 100644 (file)
@@ -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