* [PATCH] ksmbd: fix use-after-free after a failed durable reconnect
@ 2026-10-27 15:25 Ian Bridges
0 siblings, 0 replies; only message in thread
From: Ian Bridges @ 2026-10-27 15:25 UTC (permalink / raw)
To: Namjae Jeon
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, Steve French,
Junyi Liu, linux-cifs, linux-kernel, Ian Bridges, stable
ksmbd_reopen_durable_fd() republishes a detached durable handle in the
reconnecting session's file table and takes no reference for that entry.
If smb2_open() then fails, cleanup relies on the final ksmbd_fd_put() to
remove the volatile id.
Two reconnects of the same handle can both take a lookup reference before
either attaches it. If the one that reopens first then fails and drops
its references first, the other request makes the final put through
ksmbd_put_durable_fd(). That frees the handle without removing the first
session's table entry, so tearing that session down writes to freed
memory.
BUG: KASAN: slab-use-after-free in __close_file_table_ids+0x14d/0x740 [ksmbd]
Call Trace:
__close_file_table_ids+0x14d/0x740 [ksmbd]
ksmbd_destroy_file_table+0x76/0x100 [ksmbd]
ksmbd_session_destroy+0xf8/0x480 [ksmbd]
ksmbd_dispose_sessions+0x13e/0x200 [ksmbd]
ksmbd_conn_sessions_cleanup+0x4c3/0x6d0 [ksmbd]
Freed by task 1030:
__ksmbd_close_fd+0x97d/0x1000 [ksmbd]
smb2_open+0x14eb/0xd9f0 [ksmbd]
Commit 3515503322f4 ("ksmbd: fix durable reconnect error path file
lifetime") handles a final put made by the reconnecting request, but not
one made by another holder.
Remove the volatile id under the file-table lock on this error path,
before the references are dropped, as ksmbd_close_fd() does on the
request_open path just above. Removal then no longer depends on which
holder makes the final put. The stale comment in the reconnect cleanup
is updated to match. A successful reconnect is unchanged.
Fixes: c8efcc786146 ("ksmbd: add support for durable handles v1/v2")
Cc: stable@vger.kernel.org
Signed-off-by: Ian Bridges <icb@fastmail.org>
---
Notes:
The bug needs "durable handles = yes" (the default is no, but macOS Time
Machine destinations need it) and a durable handle (v1 or v2) that is
detached and waiting for reconnect. Two things must then overlap. A
reconnect of the handle must fail after ksmbd_reopen_durable_fd()
succeeds. Another holder must take a global-table reference before that
reopen and drop it after the failed reconnect's puts. The other holder can
be a second reconnect of the same handle, and a durable v1
reconnect needs only the persistent id. A durable v2 create with the
same CreateGuid also takes a short-lived reference. These lookups run
before the owner check, so the second request does not need the owner's
credentials.
The affected early error path is reached if ksmbd_override_fsids() fails
or ksmbd_vfs_getattr() returns an error after a successful reopen. The
reproduction forced the getattr failure. Naturally occurring failures
were not tested.
The race dates from c8efcc786146, where a ksmbd_iov_pin_rsp() failure
after the reconnect leaves the same stale id. 1baff47b81f9 ("ksmbd: fix
use-after-free in smb2_open during durable reconnect") moved the lookup
put to the end of smb2_open(), which made a getattr failure a trigger as
well.
A reproducer is available on request.
This fix was developed with LLM assistance (code review and KASAN
reproduction).
Tested on ksmbd-for-next 714f27729121 under KASAN, on a fresh boot. With
only this patch applied, an ordinary durable v2 reconnect succeeds, the
handle reads back the data written before the disconnect and closes
cleanly, and no handle or KASAN report is left (kmemleak shows only one
unrelated object with no ksmbd frames). The race and the getattr failure
were made deterministic with temporary debug hooks. They are a barrier
that lets both reconnects take their lookup reference before either
reopens, a forced getattr failure, and a wait that makes the second
reconnect drop its reference last. That instrumentation is not part of
this patch.
Without the patch, the hooked run triggers the KASAN report above when
the first session is torn down. With it, both reconnects fail with an
error status, the volatile id is already gone before the last put, no
handle is left, and no KASAN report fires over two runs. A single
failing reconnect behaves the same. The injected-failure paths still
emit a pre-existing warning from ksmbd_free_work_struct() that this fix
does not touch.
fs/smb/server/smb2pdu.c | 9 +++++----
fs/smb/server/vfs_cache.c | 28 ++++++++++++++++++++++++++++
fs/smb/server/vfs_cache.h | 2 ++
3 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index b306bc6e21ee..4dc91d4f1a94 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -5628,6 +5628,8 @@ int smb2_open(struct ksmbd_work *work)
ksmbd_close_fd(work, fp->volatile_id);
smb2_complete_request_open(work);
} else {
+ if (dh_info.reconnected && fp == dh_info.fp)
+ ksmbd_unpublish_reopened_fd(work, fp);
ksmbd_fd_put(work, fp);
}
}
@@ -5641,10 +5643,9 @@ int smb2_open(struct ksmbd_work *work)
if (dh_info.reconnected) {
/*
* If reconnect succeeded, fp was republished in the
- * session file table. On a later error, ksmbd_fd_put()
- * above drops the session reference; drop the durable
- * lookup reference through the same session-aware path so
- * final close removes the volatile id before freeing fp.
+ * session file table, and the error path above has
+ * already removed its volatile id. Drop the durable
+ * lookup reference through the same session-aware path.
*/
if (rc && fp == dh_info.fp)
ksmbd_fd_put(work, dh_info.fp);
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 4368e069fd5e..f9079c529d07 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -1074,6 +1074,34 @@ void ksmbd_wake_session_blocked_works(struct ksmbd_session *sess)
read_unlock(&ft->lock);
}
+/**
+ * ksmbd_unpublish_reopened_fd() - remove a reopened durable handle from the
+ * session file table after its CREATE failed
+ * @work: smb work of the failed CREATE
+ * @fp: durable handle republished by ksmbd_reopen_durable_fd()
+ *
+ * The reopen publishes @fp in this session's table without taking a reference
+ * for the entry, so the CREATE error path's puts need not be the last ones.
+ * Another holder of a global-table reference, such as a concurrent reconnect
+ * of the same handle, can drop the last one through ksmbd_put_durable_fd(),
+ * which frees @fp without touching this table. Remove the id here, while
+ * the caller still holds its references.
+ *
+ * Every path that removes the id before the final put also clears
+ * @fp->volatile_id under ft->lock, so a valid id is still @fp's entry.
+ */
+void ksmbd_unpublish_reopened_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
+{
+ struct ksmbd_file_table *ft = &work->sess->file_table;
+
+ write_lock(&ft->lock);
+ if (has_file_id(fp->volatile_id)) {
+ idr_remove(ft->idr, fp->volatile_id);
+ fp->volatile_id = KSMBD_NO_FID;
+ }
+ write_unlock(&ft->lock);
+}
+
int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
{
struct ksmbd_file *fp;
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index c00d92fdf94a..a0239ec178d8 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -205,6 +205,8 @@ 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_session *sess);
int ksmbd_close_fd(struct ksmbd_work *work, u64 id);
+void ksmbd_unpublish_reopened_fd(struct ksmbd_work *work,
+ struct ksmbd_file *fp);
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);
struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id,
base-commit: 714f27729121d59f103ca4dad7b0c2d1c125cd71
--
2.47.3
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-27 15:25 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 15:25 [PATCH] ksmbd: fix use-after-free after a failed durable reconnect Ian Bridges
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®