FAILED: patch "[PATCH] ksmbd: fix app-instance non-durable supersede session UAF" failed to apply to 7.3-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 7.3-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-7.3.y
git checkout FETCH_HEAD
git cherry-pick -x f728cfee6929057087c303d07d7a6a7c61196b48
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026110310-spool-turban-3d3a@gregkh' --subject-prefix 'PATCH 7.3.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From f728cfee6929057087c303d07d7a6a7c61196b48 Mon Sep 17 00:00:00 2001
From: Ian Bridges <[email protected]>
Date: Mon, 26 Oct 2026 00:34:22 -0500
Subject: [PATCH] ksmbd: fix app-instance non-durable supersede session UAF
ksmbd_close_fd_app_instance_id() supersedes a prior open that has the same AppInstanceId. It reaches that open's session through opinfo->sess. This pointer is not refcounted.
Session teardown detaches durable handles before it frees the session. It does not detach a non-durable open. That open still points at the session through opinfo->sess after the session is freed. Another session can supersede the open at the same time. The supersede then takes a write lock through the stale pointer, in the freed session's file table.
BUG: KASAN: slab-use-after-free in _raw_write_lock+0x71/0xd0 Call Trace: _raw_write_lock+0x71/0xd0 ksmbd_close_fd_app_instance_id+0xd9/0x670 [ksmbd] smb2_open+0x3e10/0xd910 [ksmbd] Freed by task 212: ksmbd_session_destroy+0x38d/0x480 [ksmbd]
Fix this by looking the session up by opinfo->session_id. The lookup returns a reference, as smb2_break_collect_channels() does. Recheck that opinfo->sess still points at it before using it.
Do the lookup without m_lock held. destroy_previous_session() takes m_lock under the session table lock, and taking them in the other order here would deadlock.
fs/smb/server/vfs_cache.c +30 -2
@@ -1262,7 +1262,9 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work, struct ksmbd_file *fp, bool *closed) { struct ksmbd_file_table *ft; +struct ksmbd_session *sess;struct oplock_info *opinfo; +u64 session_id;int n_to_drop =0 ; *closed = false;
@@ -1275,10 +1277,35 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,up_read (&fp->f_ci->m_lock); goto out; } +/* +* opinfo->sess is not refcounted, and m_lock does not keep it alive. +* The owner can be torn down while we hold only a reference to fp. +* Look the owner up by id to take a real reference instead. +*/ + session_id = opinfo->session_id; +up_read (&fp->f_ci->m_lock); - ft = &opinfo->sess->file_table; +/* destroy_previous_session() takes m_lock under sessions_table_lock. */ + sess =ksmbd_session_lookup_slowpath (session_id); +if (!sess) + goto out; + +down_read (&fp->f_ci->m_lock); +if (opinfo->sess != sess) { +up_read (&fp->f_ci->m_lock); +ksmbd_user_session_put (sess); + goto out; + } + ft = &sess->file_table;write_lock (&ft->lock); -if (fp->f_state ==FP_INITED &&has_file_id (fp->volatile_id) && +/* +* The table may already be detached by destroy_previous_session(). +* A new session could also reuse the old id and address and pass the +* check above. Only remove fp if this table still publishes it. +*/ +if (ft->idr && fp->f_state ==FP_INITED && +has_file_id (fp->volatile_id) && +idr_find (ft->idr, fp->volatile_id) == fp &&ksmbd_app_instance_id_matches (work, fp)) {idr_remove (ft->idr, fp->volatile_id); fp->volatile_id =KSMBD_NO_FID ;
@@ -1286,6 +1313,7 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work, }write_unlock (&ft->lock);up_read (&fp->f_ci->m_lock); +ksmbd_user_session_put (sess);opinfo_put (opinfo); opinfo =NULL ;
Sasha Levin
maintainerPATCH 1/4 ksmbd: use clear_and_wake_up_bit() directly
From: Daijiamu <[email protected]>
[ Upstream commit 8852052e32df965a76daecbd8cda0ecf950b54df ]
clear_and_wake_up_bit() was introduced by commit 8236b0ae31c8 ("bdi:
wake up concurrent wb_shutdown() callers.") to replace the open-coded clear_bit_unlock() + smp_mb__after_atomic() + wake_up_bit() sequence.
Replace the sequence and drop the now-trivial wake_up_oplock_break() wrapper, calling the helper directly at each call site.
Stable-dep-of: f728cfee6929 ("ksmbd: fix app-instance non-durable supersede session UAF")
--- fs/smb/server/oplock.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-)
fs/smb/server/oplock.c +6 -14
@@ -763,14 +763,6 @@ static bool wait_for_break_ack(struct oplock_info *opinfo)return false; } -static void wake_up_oplock_break (struct oplock_info *opinfo) -{ -clear_bit_unlock (0 , &opinfo->pending_break); -/* memory barrier is needed for wake_up_bit() */ -smp_mb__after_atomic (); -wake_up_bit (&opinfo->pending_break,0 ); -} -static bool oplock_break_set_ack_wait (struct oplock_info *opinfo) {bool ret = false;
@@ -826,13 +818,13 @@ static int oplock_break_pending(struct oplock_info *opinfo, int req_op_level)if (opinfo->level <= req_op_level) {if (opinfo->is_lease == false) { -wake_up_oplock_break (opinfo); +clear_and_wake_up_bit (0 , &opinfo->pending_break);return 1 ; }if (opinfo->o_lease->state != (SMB2_LEASE_HANDLE_CACHING_LE |SMB2_LEASE_READ_CACHING_LE )) { -wake_up_oplock_break (opinfo); +clear_and_wake_up_bit (0 , &opinfo->pending_break);return 1 ; } }
@@ -1260,7 +1252,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci, SMB2_LEASE_HANDLE_CACHING_LE)) {if (!oplock_break_set_ack_wait (brk_opinfo)) {atomic_dec_if_positive (&brk_opinfo->breaking_cnt); -wake_up_oplock_break (brk_opinfo); +clear_and_wake_up_bit (0 , &brk_opinfo->pending_break);return -ENOENT ; } } else
@@ -1309,7 +1301,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci, lease->state != SMB2_LEASE_NONE_LE))) goto again; -wake_up_oplock_break (brk_opinfo); +clear_and_wake_up_bit (0 , &brk_opinfo->pending_break);return err; }else { err =oplock_break_pending (brk_opinfo, req_op_level);
@@ -1319,7 +1311,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci,if (brk_opinfo->level ==SMB2_OPLOCK_LEVEL_BATCH || brk_opinfo->level ==SMB2_OPLOCK_LEVEL_EXCLUSIVE ) {if (!oplock_break_set_ack_wait (brk_opinfo)) { -wake_up_oplock_break (brk_opinfo); +clear_and_wake_up_bit (0 , &brk_opinfo->pending_break);return -ENOENT ; } }
@@ -1342,7 +1334,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci,ksmbd_debug (OPLOCK ,"oplock granted = %d\n" , brk_opinfo->level);if (brk_opinfo->op_state ==OPLOCK_CLOSING ) err = -EAGAIN ; -wake_up_oplock_break (brk_opinfo); +clear_and_wake_up_bit (0 , &brk_opinfo->pending_break);return err; }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/4 ksmbd: track delivery of queued TCP writes
From: Namjae Jeon <[email protected]>
[ Upstream commit c0632d2010ffc006eed9becc8d0d10d54f294bbd ]
A nonblocking break send must distinguish a PDU accepted by TCP from one still queued in ksmbd. Retrying an accepted PDU can send duplicates, while abandoning a partial send can corrupt the stream.
Queue owned PDU bytes when TCP cannot accept the full message. Track the stream position through socket delivery and TCP ACK, and expose it to break retries. Bound retained data and stop queued writes during teardown.
Stable-dep-of: f728cfee6929 ("ksmbd: fix app-instance non-durable supersede session UAF")
--- fs/smb/server/connection.c | 52 +++++- fs/smb/server/connection.h | 24 ++- fs/smb/server/transport_tcp.c | 299 +++++++++++++++++++++++++++++++++- 3 files changed, 356 insertions(+), 19 deletions(-)
fs/smb/server/connection.c +44 -8
@@ -458,17 +458,13 @@ void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status)void ksmbd_conn_abort (struct ksmbd_conn *conn) { -bool shutdown = false; -spin_lock (&conn->request_lock); -if (!ksmbd_conn_exiting (conn) && !ksmbd_conn_releasing (conn)) { +if (!ksmbd_conn_exiting (conn) && !ksmbd_conn_releasing (conn))ksmbd_conn_set_exiting (conn); - shutdown = true; - }spin_unlock (&conn->request_lock);wake_up_all (&conn->req_running_q); -if (shutdown && conn->transport->ops->shutdown) +if (conn->transport->ops->shutdown) conn->transport->ops->shutdown (conn->transport); }
@@ -535,12 +531,19 @@ static int __ksmbd_conn_write(struct ksmbd_work *work, tx->need_invalidate_rkey = work->need_invalidate_rkey; tx->remote_key = work->remote_key; -ksmbd_conn_lock (conn); +if (tx->tracking) { +/* A busy writer must not consume the break's retry budget. */ +if (!mutex_trylock (&conn->srv_mutex)) +return -EAGAIN ; + }else { +ksmbd_conn_lock (conn); + } sent = conn->transport->ops->writev (conn->transport, tx);ksmbd_conn_unlock (conn);if (sent <0 ) { -pr_err ("Failed to send message: %d\n" , sent); +if (!tx->tracking || sent != -EAGAIN ) +pr_err ("Failed to send message: %d\n" , sent);return sent; }
@@ -554,6 +557,20 @@ int ksmbd_conn_write(struct ksmbd_work *work)return __ksmbd_conn_write (work, &tx); } +int ksmbd_conn_write_tracked (struct ksmbd_work *work, +struct ksmbd_tx_tracking *tracking) +{ +struct ksmbd_transport_write tx = {}; + + tracking->valid = false; +/* Transports without delivery tracking keep their synchronous send path. */ +if (work->conn->transport->ops->is_acked) { + tx.msg_flags =MSG_DONTWAIT ; + tx.tracking = tracking; + } +return __ksmbd_conn_write (work, &tx); +} +int ksmbd_conn_write_eor (struct ksmbd_work *work) {struct ksmbd_transport_write tx = {
@@ -807,7 +824,12 @@ int ksmbd_conn_handler_loop(void *p) } } +/* Exclude new break senders before draining their work references. */ +spin_lock (&conn->request_lock);ksmbd_conn_set_releasing (conn); +spin_unlock (&conn->request_lock); +/* TCP writers waiting for queue space must release their work references. */ +wake_up_all (&conn->req_running_q);ksmbd_conn_cancel_async_requests (conn);/* Wait till all reference dropped to the Server object*/ ksmbd_debug (CONN ,"Wait for all pending requests(%d)\n" ,atomic_read (&conn->r_count));
@@ -834,6 +856,20 @@ void ksmbd_conn_r_count_inc(struct ksmbd_conn *conn)atomic_inc (&conn->r_count); } +/* The caller already owns a connection reference. */ +bool ksmbd_conn_r_count_inc_if_good (struct ksmbd_conn *conn) +{ +bool ret = false; + +spin_lock (&conn->request_lock); +if (ksmbd_conn_good (conn)) { +atomic_inc (&conn->r_count); + ret = true; + } +spin_unlock (&conn->request_lock); +return ret; +} +void ksmbd_conn_r_count_dec (struct ksmbd_conn *conn) {/*
fs/smb/server/connection.h +22 -2
@@ -102,6 +102,8 @@ struct ksmbd_conn {wait_queue_head_t r_count_q;/* Lock to protect requests list*/ spinlock_t request_lock; +/* Set under request_lock at teardown start and kept set thereafter. */ +bool releasing;struct list_head requests;struct list_head async_requests;int connection_type;
@@ -161,6 +163,13 @@ struct ksmbd_conn_ops {int (*terminate_fn)(struct ksmbd_conn *conn); }; +struct ksmbd_tx_tracking { +/* Required TCP byte count, including transport and socket queues. */ +u64 end; +unsigned long retry_after; +bool valid; +}; +struct ksmbd_transport_write {struct kvec *iov;int iov_cnt;
@@ -168,6 +177,7 @@ struct ksmbd_transport_write {bool need_invalidate_rkey;unsigned int remote_key;int msg_flags; +struct ksmbd_tx_tracking *tracking; };struct ksmbd_transport_ops {
@@ -175,8 +185,12 @@ struct ksmbd_transport_ops {void (*shutdown)(struct ksmbd_transport *t);int (*read)(struct ksmbd_transport *t,char *buf,unsigned int size,int max_retries); +/* Retained bytes must be privately owned before a full result is returned. */ int (*writev)(struct ksmbd_transport *t,const struct ksmbd_transport_write *tx); +/* Optional delivery tracking for nonblocking break sends. */ +bool (*is_acked)(struct ksmbd_transport *t, +const struct ksmbd_tx_tracking *tracking); int (*rdma_read)(struct ksmbd_transport *t, void *buf, unsigned int len, struct smbdirect_buffer_descriptor_v1 *desc,
@@ -216,6 +230,8 @@ int ksmbd_conn_wq_init(void);void ksmbd_conn_wq_destroy (void );bool ksmbd_conn_lookup_dialect (struct ksmbd_conn *c);int ksmbd_conn_write (struct ksmbd_work *work); +int ksmbd_conn_write_tracked (struct ksmbd_work *work, +struct ksmbd_tx_tracking *tracking);int ksmbd_conn_write_eor (struct ksmbd_work *work); int ksmbd_conn_rdma_read(struct ksmbd_conn *conn, void *buf, unsigned int buflen,
@@ -234,6 +250,7 @@ void ksmbd_conn_transport_destroy(void);void ksmbd_conn_lock (struct ksmbd_conn *conn);void ksmbd_conn_unlock (struct ksmbd_conn *conn);void ksmbd_conn_r_count_inc (struct ksmbd_conn *conn); +bool ksmbd_conn_r_count_inc_if_good (struct ksmbd_conn *conn);void ksmbd_conn_r_count_dec (struct ksmbd_conn *conn);/*
@@ -249,7 +266,8 @@ static inline bool ksmbd_conn_new(struct ksmbd_conn *conn)static inline bool ksmbd_conn_good (struct ksmbd_conn *conn) { -return READ_ONCE (conn->status) ==KSMBD_SESS_GOOD ; +return !READ_ONCE (conn->releasing) && +READ_ONCE (conn->status) ==KSMBD_SESS_GOOD ; } static inline unsigned int
@@ -283,7 +301,8 @@ static inline bool ksmbd_conn_exiting(struct ksmbd_conn *conn)static inline bool ksmbd_conn_releasing (struct ksmbd_conn *conn) { -return READ_ONCE (conn->status) ==KSMBD_SESS_RELEASING ; +return READ_ONCE (conn->releasing) || +READ_ONCE (conn->status) ==KSMBD_SESS_RELEASING ; }static inline void ksmbd_conn_set_new (struct ksmbd_conn *conn)
@@ -318,6 +337,7 @@ static inline void ksmbd_conn_set_exiting(struct ksmbd_conn *conn)static inline void ksmbd_conn_set_releasing (struct ksmbd_conn *conn) { +WRITE_ONCE (conn->releasing, true);WRITE_ONCE (conn->status,KSMBD_SESS_RELEASING ); }
fs/smb/server/transport_tcp.c +290 -9
373 lines not on this page · raw message
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/4 ksmbd: retry break notifications on another connection
In reply to Sasha LevinFrom: Namjae Jeon <[email protected]>
[ Upstream commit 14ad1f21fbf05179116dd6ef4ab3b6556478fa50 ]
An oplock or lease break belongs to an open that can outlive its original channel. A draining channel or stalled TCP writer can otherwise leave the break pending until timeout or invalidate a durable open.
Snapshot the owner's live channels and try eligible connections with the same notification. Track TCP delivery separately from the SMB ACK, retry when a channel stops progressing, and share one break deadline. Run ordinary break waits on a dedicated workqueue so blocked request responses do not stall breaks.
Publish NONE before sending a level II break, which requires no ACK, so an unsolicited ACK observes the completed level while TCP delivery is pending.
Keep the first rename lease notification in the request context and enqueue the interim writer only after the notification is accepted. Join that work before returning so a blocked interim response does not stop notification retries or release the request early.
Stable-dep-of: f728cfee6929 ("ksmbd: fix app-instance non-durable supersede session UAF")
--- fs/smb/server/ksmbd_work.c | 19 + fs/smb/server/ksmbd_work.h | 2 + fs/smb/server/mgmt/user_session.c | 31 ++ fs/smb/server/mgmt/user_session.h | 3 + fs/smb/server/oplock.c | 855 +++++++++++++++++------------- fs/smb/server/oplock.h | 14 +- fs/smb/server/vfs_cache.c | 2 + 7 files changed, 539 insertions(+), 387 deletions(-)
fs/smb/server/ksmbd_work.c +19 -0
@@ -16,6 +16,7 @@static struct kmem_cache *work_cache;static struct workqueue_struct *ksmbd_wq; +static struct workqueue_struct *ksmbd_break_wq;static int ksmbd_reserve_iov (struct ksmbd_work *work,int need_iov_cnt) {
@@ -113,6 +114,12 @@ int ksmbd_workqueue_init(void) ksmbd_wq =alloc_workqueue ("ksmbd-io" ,WQ_PERCPU ,0 );if (!ksmbd_wq)return -ENOMEM ; + ksmbd_break_wq =alloc_workqueue ("ksmbd-break" ,WQ_UNBOUND ,0 ); +if (!ksmbd_break_wq) { +destroy_workqueue (ksmbd_wq); + ksmbd_wq =NULL ; +return -ENOMEM ; + }return 0 ; }
@@ -120,6 +127,8 @@ void ksmbd_workqueue_destroy(void) {destroy_workqueue (ksmbd_wq); ksmbd_wq =NULL ; +destroy_workqueue (ksmbd_break_wq); + ksmbd_break_wq =NULL ; }bool ksmbd_queue_work (struct ksmbd_work *work)
@@ -127,6 +136,16 @@ bool ksmbd_queue_work(struct ksmbd_work *work)return queue_work (ksmbd_wq, &work->work); } +void ksmbd_queue_io_work (struct work_struct *work) +{ +queue_work (ksmbd_wq, work); +} + +void ksmbd_queue_break_work (struct work_struct *work) +{ +queue_work (ksmbd_break_wq, work); +} +static inline void __ksmbd_iov_pin (struct ksmbd_work *work,void *ib,unsigned int ib_len) {
fs/smb/server/ksmbd_work.h +2 -0
@@ -156,6 +156,8 @@ int ksmbd_work_pool_init(void);int ksmbd_workqueue_init (void );void ksmbd_workqueue_destroy (void );bool ksmbd_queue_work (struct ksmbd_work *work); +void ksmbd_queue_io_work (struct work_struct *work); +void ksmbd_queue_break_work (struct work_struct *work);int ksmbd_iov_pin_rsp_read (struct ksmbd_work *work,void *ib,int len,void *aux_buf,unsigned int aux_size);int ksmbd_iov_pin_rsp (struct ksmbd_work *work,void *ib,int len);
fs/smb/server/mgmt/user_session.c +31 -0
@@ -767,6 +767,37 @@ struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id)return sess; } +/* The caller holds a session reference and must put each returned connection. */ +unsigned int ksmbd_session_get_channels (struct ksmbd_session *sess, +struct ksmbd_conn **conns, +unsigned int max_conns) +{ +struct channel *chann; +unsigned long index; +unsigned int nr_conns =0 ; + +/* +* Channel connections are borrowed. Unregister can leave the channel +* list in a pinned session after removing it from the registry, so +* chann_lock alone does not keep those connections alive. Only collect +* registered sessions, excluding both unregister and connection cleanup +* until the connection references have been taken. +*/ +down_read (&sessions_table_lock); +if (hlist_unhashed (&sess->hlist)) + goto out; +down_read (&sess->chann_lock); +xa_for_each (&sess->ksmbd_chann_list, index, chann) { +if (nr_conns == max_conns) +break ; + conns[nr_conns++] =ksmbd_conn_get (chann->conn); + } +up_read (&sess->chann_lock); +out: +up_read (&sessions_table_lock); +return nr_conns; +} +struct ksmbd_session *ksmbd_session_lookup_all_states (struct ksmbd_conn *conn,unsigned long long id) {
fs/smb/server/mgmt/user_session.h +3 -0
@@ -99,6 +99,9 @@ void ksmbd_session_release_slot(struct ksmbd_conn *conn);void ksmbd_session_destroy (struct ksmbd_session *sess);struct ksmbd_session *ksmbd_session_lookup_slowpath (unsigned long long id); +unsigned int ksmbd_session_get_channels (struct ksmbd_session *sess, +struct ksmbd_conn **conns, +unsigned int max_conns);struct ksmbd_session *ksmbd_session_lookup (struct ksmbd_conn *conn,unsigned long long id); bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn,
fs/smb/server/oplock.c +481 -374
1,057 lines not on this page · raw message
fs/smb/server/oplock.h +1 -13
@@ -62,6 +62,7 @@ struct lease {struct oplock_info {struct ksmbd_conn *conn;struct ksmbd_session *sess; +u64 session_id;struct ksmbd_work *work;struct ksmbd_file *o_fp;int level;
@@ -82,19 +83,6 @@ struct oplock_info {struct rcu_head rcu; }; -struct lease_break_info { -__le32 curr_state; -__le32 new_state; -__le16 epoch; -char lease_key[SMB2_LEASE_KEY_SIZE ]; -}; - -struct oplock_break_info { -int level; -int open_trunc; -int fid; -}; -int smb_grant_oplock (struct ksmbd_work *work,int req_op_level,u64 pid,struct ksmbd_file *fp,__u16 tid,struct lease_ctx_info *lctx,int share_ret,bool replay);
fs/smb/server/vfs_cache.c +2 -0
@@ -1815,6 +1815,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,ksmbd_conn_put (op->conn); op->conn =NULL ; op->sess =NULL ; + op->session_id =0 ; }up_write (&ci->m_lock);
@@ -1995,6 +1996,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)continue ; op->conn =ksmbd_conn_get (fp->conn); op->sess = work->sess; + op->session_id = work->sess->id; }up_write (&ci->m_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/4 ksmbd: fix app-instance non-durable supersede session UAF
In reply to Sasha LevinFrom: Ian Bridges <[email protected]>
[ Upstream commit f728cfee6929057087c303d07d7a6a7c61196b48 ]
ksmbd_close_fd_app_instance_id() supersedes a prior open that has the same AppInstanceId. It reaches that open's session through opinfo->sess. This pointer is not refcounted.
Session teardown detaches durable handles before it frees the session. It does not detach a non-durable open. That open still points at the session through opinfo->sess after the session is freed. Another session can supersede the open at the same time. The supersede then takes a write lock through the stale pointer, in the freed session's file table.
BUG: KASAN: slab-use-after-free in _raw_write_lock+0x71/0xd0 Call Trace: _raw_write_lock+0x71/0xd0 ksmbd_close_fd_app_instance_id+0xd9/0x670 [ksmbd] smb2_open+0x3e10/0xd910 [ksmbd] Freed by task 212: ksmbd_session_destroy+0x38d/0x480 [ksmbd]
Fix this by looking the session up by opinfo->session_id. The lookup returns a reference, as smb2_break_collect_channels() does. Recheck that opinfo->sess still points at it before using it.
Do the lookup without m_lock held. destroy_previous_session() takes m_lock under the session table lock, and taking them in the other order here would deadlock.
--- fs/smb/server/vfs_cache.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-)
fs/smb/server/vfs_cache.c +30 -2
@@ -1054,7 +1054,9 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work, struct ksmbd_file *fp, bool *closed) { struct ksmbd_file_table *ft; +struct ksmbd_session *sess;struct oplock_info *opinfo; +u64 session_id;int n_to_drop =0 ; *closed = false;
@@ -1067,10 +1069,35 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work,up_read (&fp->f_ci->m_lock); goto out; } +/* +* opinfo->sess is not refcounted, and m_lock does not keep it alive. +* The owner can be torn down while we hold only a reference to fp. +* Look the owner up by id to take a real reference instead. +*/ + session_id = opinfo->session_id; +up_read (&fp->f_ci->m_lock); - ft = &opinfo->sess->file_table; +/* destroy_previous_session() takes m_lock under sessions_table_lock. */ + sess =ksmbd_session_lookup_slowpath (session_id); +if (!sess) + goto out; + +down_read (&fp->f_ci->m_lock); +if (opinfo->sess != sess) { +up_read (&fp->f_ci->m_lock); +ksmbd_user_session_put (sess); + goto out; + } + ft = &sess->file_table;write_lock (&ft->lock); -if (fp->f_state ==FP_INITED &&has_file_id (fp->volatile_id) && +/* +* The table may already be detached by destroy_previous_session(). +* A new session could also reuse the old id and address and pass the +* check above. Only remove fp if this table still publishes it. +*/ +if (ft->idr && fp->f_state ==FP_INITED && +has_file_id (fp->volatile_id) && +idr_find (ft->idr, fp->volatile_id) == fp &&ksmbd_app_instance_id_matches (work, fp)) {idr_remove (ft->idr, fp->volatile_id); fp->volatile_id =KSMBD_NO_FID ;
@@ -1078,6 +1105,7 @@ int ksmbd_close_fd_app_instance_id(struct ksmbd_work *work, }write_unlock (&ft->lock);up_read (&fp->f_ci->m_lock); +ksmbd_user_session_put (sess);opinfo_put (opinfo); opinfo =NULL ;
Signature · 1 line
2.53.0