From c50e628122aed077695669e25b842e778511a43d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 13 Aug 2026 09:01:00 +0900 Subject: [PATCH] ksmbd: scope session state changes to bound connections ksmbd_all_conn_set_status() treats every connection whose transient binding flag is set as belonging to the target SessionId. A logoff or session replacement can consequently move an unrelated connection to NEED_RECONNECT or NEED_SETUP. Pass the target session itself and select connections using either the connection-local session xarray or the session's permanent channel list. Use the same association test while waiting for requests to drain. Serialize session-wide status changes under request_lock and do not overwrite EXITING or RELEASING. Protect the shutdown transition with the same lock so a concurrent session update cannot revive a closing connection. Fixes: f5a544e3bab7 ("ksmbd: add support for SMB3 multichannel") Fixes: abcc506a9a71 ("ksmbd: fix racy issue from smb2 close and logoff with multichannel") Fixes: c444139cb747 ("ksmbd: rewrite stop_sessions() with restartable iteration") Signed-off-by: Namjae Jeon --- fs/smb/server/connection.c | 37 ++++++++++++++++++++++++++----- fs/smb/server/connection.h | 6 +++-- fs/smb/server/mgmt/user_session.c | 8 +++---- fs/smb/server/smb2pdu.c | 6 ++--- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index e225aca67686..71d55d903f6f 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -404,15 +404,37 @@ void ksmbd_conn_unlock(struct ksmbd_conn *conn) mutex_unlock(&conn->srv_mutex); } -void ksmbd_all_conn_set_status(u64 sess_id, u32 status) +static bool ksmbd_session_is_bound_to_conn(struct ksmbd_session *sess, + struct ksmbd_conn *conn) +{ + bool found; + + rcu_read_lock(); + found = xa_load(&conn->sessions, sess->id) == sess; + rcu_read_unlock(); + if (found) + return true; + + down_read(&sess->chann_lock); + found = xa_load(&sess->ksmbd_chann_list, (long)conn); + up_read(&sess->chann_lock); + return found; +} + +void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status) { struct ksmbd_conn *conn; int bkt; down_read(&conn_list_lock); hash_for_each(conn_list, bkt, conn, hlist) { - if (conn->binding || xa_load(&conn->sessions, sess_id)) - WRITE_ONCE(conn->status, status); + if (ksmbd_session_is_bound_to_conn(sess, conn)) { + spin_lock(&conn->request_lock); + if (!ksmbd_conn_exiting(conn) && + !ksmbd_conn_releasing(conn)) + WRITE_ONCE(conn->status, status); + spin_unlock(&conn->request_lock); + } } up_read(&conn_list_lock); } @@ -422,7 +444,8 @@ void ksmbd_conn_wait_idle(struct ksmbd_conn *conn) wait_event(conn->req_running_q, atomic_read(&conn->req_running) < 2); } -int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id) +int ksmbd_conn_wait_idle_sess(struct ksmbd_conn *curr_conn, + struct ksmbd_session *sess) { struct ksmbd_conn *conn; int rc, retry_count = 0, max_timeout = 120; @@ -434,7 +457,7 @@ int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id) down_read(&conn_list_lock); hash_for_each(conn_list, bkt, conn, hlist) { - if (conn->binding || xa_load(&conn->sessions, sess_id)) { + if (ksmbd_session_is_bound_to_conn(sess, conn)) { rcount = (conn == curr_conn) ? 2 : 1; if (atomic_read(&conn->req_running) >= rcount) { rc = wait_event_timeout(conn->req_running_q, @@ -780,8 +803,10 @@ static void stop_sessions(void) * handler exited its receive loop for an unrelated * reason). */ - if (READ_ONCE(conn->status) != KSMBD_SESS_RELEASING) + spin_lock(&conn->request_lock); + if (!ksmbd_conn_releasing(conn)) ksmbd_conn_set_exiting(conn); + spin_unlock(&conn->request_lock); target = conn; break; } diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h index 9ca03f9774d3..c01ccbe8b97c 100644 --- a/fs/smb/server/connection.h +++ b/fs/smb/server/connection.h @@ -23,6 +23,7 @@ #include "ksmbd_work.h" struct smbdirect_buffer_descriptor_v1; +struct ksmbd_session; #define KSMBD_SOCKET_BACKLOG 16 @@ -196,7 +197,8 @@ extern struct rw_semaphore conn_list_lock; bool ksmbd_conn_alive(struct ksmbd_conn *conn); void ksmbd_conn_wait_idle(struct ksmbd_conn *conn); -int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id); +int ksmbd_conn_wait_idle_sess(struct ksmbd_conn *curr_conn, + struct ksmbd_session *sess); struct ksmbd_conn *ksmbd_conn_alloc(void); void ksmbd_conn_free(struct ksmbd_conn *conn); struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn); @@ -310,5 +312,5 @@ static inline void ksmbd_conn_set_releasing(struct ksmbd_conn *conn) WRITE_ONCE(conn->status, KSMBD_SESS_RELEASING); } -void ksmbd_all_conn_set_status(u64 sess_id, u32 status); +void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status); #endif /* __CONNECTION_H__ */ diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 31eccad5d732..7e187d20828b 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -647,17 +647,17 @@ void destroy_previous_session(struct ksmbd_conn *conn, memcmp(user->passkey, prev_user->passkey, user->passkey_sz)) goto out; - ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_RECONNECT); - err = ksmbd_conn_wait_idle_sess_id(conn, id); + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_RECONNECT); + err = ksmbd_conn_wait_idle_sess(conn, prev_sess); if (err) { - ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP); + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP); goto out; } ksmbd_destroy_file_table(prev_sess); prev_sess->kerberos_expiry = 0; prev_sess->state = SMB2_SESSION_EXPIRED; - ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP); + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP); ksmbd_launch_ksmbd_durable_scavenger(); out: up_write(&conn->session_lock); diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index aa662adaf63d..6581c79635fa 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -2899,7 +2899,6 @@ int smb2_session_logoff(struct ksmbd_work *work) struct ksmbd_session *sess = work->sess; struct smb2_logoff_req *req; struct smb2_logoff_rsp *rsp; - u64 sess_id; int err; WORK_BUFFERS(work, req, rsp); @@ -2913,8 +2912,7 @@ int smb2_session_logoff(struct ksmbd_work *work) smb2_set_err_rsp(work); return -ENOENT; } - sess_id = le64_to_cpu(req->hdr.SessionId); - ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_RECONNECT); + ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_RECONNECT); ksmbd_conn_unlock(conn); ksmbd_close_session_fds(work); @@ -2932,7 +2930,7 @@ int smb2_session_logoff(struct ksmbd_work *work) sess->state = SMB2_SESSION_EXPIRED; up_write(&conn->session_lock); - ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_SETUP); + ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_SETUP); rsp->StructureSize = cpu_to_le16(4); err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_logoff_rsp));