]> www.infradead.org Git - users/willy/xarray.git/commitdiff
ksmbd: fix race condition between destroy_previous_session() and smb2 operations()
authorNamjae Jeon <linkinjeon@kernel.org>
Sat, 17 Aug 2024 05:03:49 +0000 (14:03 +0900)
committerSteve French <stfrench@microsoft.com>
Sun, 18 Aug 2024 22:02:36 +0000 (17:02 -0500)
If there is ->PreviousSessionId field in the session setup request,
The session of the previous connection should be destroyed.
During this, if the smb2 operation requests in the previous session are
being processed, a racy issue could happen with ksmbd_destroy_file_table().
This patch sets conn->status to KSMBD_SESS_NEED_RECONNECT to block
incoming  operations and waits until on-going operations are complete
(i.e. idle) before desctorying the previous session.

Fixes: c8efcc786146 ("ksmbd: add support for durable handles v1/v2")
Cc: stable@vger.kernel.org # v6.6+
Reported-by: zdi-disclosures@trendmicro.com # ZDI-CAN-25040
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/connection.c
fs/smb/server/connection.h
fs/smb/server/mgmt/user_session.c
fs/smb/server/smb2pdu.c

index 09e1e7771592f522e44e309505078e04b6853cad..7889df8112b4eecf6b0ee91d2c04ec84e3a54326 100644 (file)
@@ -165,11 +165,43 @@ void ksmbd_all_conn_set_status(u64 sess_id, u32 status)
        up_read(&conn_list_lock);
 }
 
-void ksmbd_conn_wait_idle(struct ksmbd_conn *conn, u64 sess_id)
+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)
+{
+       struct ksmbd_conn *conn;
+       int rc, retry_count = 0, max_timeout = 120;
+       int rcount = 1;
+
+retry_idle:
+       if (retry_count >= max_timeout)
+               return -EIO;
+
+       down_read(&conn_list_lock);
+       list_for_each_entry(conn, &conn_list, conns_list) {
+               if (conn->binding || xa_load(&conn->sessions, sess_id)) {
+                       if (conn == curr_conn)
+                               rcount = 2;
+                       if (atomic_read(&conn->req_running) >= rcount) {
+                               rc = wait_event_timeout(conn->req_running_q,
+                                       atomic_read(&conn->req_running) < rcount,
+                                       HZ);
+                               if (!rc) {
+                                       up_read(&conn_list_lock);
+                                       retry_count++;
+                                       goto retry_idle;
+                               }
+                       }
+               }
+       }
+       up_read(&conn_list_lock);
+
+       return 0;
+}
+
 int ksmbd_conn_write(struct ksmbd_work *work)
 {
        struct ksmbd_conn *conn = work->conn;
index 5c2845e47cf2df5f33bba86bfb3e88ae1917635f..5b947175c048eb77597dfab486f60dd183d0d329 100644 (file)
@@ -145,7 +145,8 @@ extern struct list_head conn_list;
 extern struct rw_semaphore conn_list_lock;
 
 bool ksmbd_conn_alive(struct ksmbd_conn *conn);
-void ksmbd_conn_wait_idle(struct ksmbd_conn *conn, u64 sess_id);
+void ksmbd_conn_wait_idle(struct ksmbd_conn *conn);
+int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id);
 struct ksmbd_conn *ksmbd_conn_alloc(void);
 void ksmbd_conn_free(struct ksmbd_conn *conn);
 bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c);
index 162a12685d2c956222eae5438c8887afe3711c20..99416ce9f50183dec9bb1da9fc495b0255396c0c 100644 (file)
@@ -311,6 +311,7 @@ void destroy_previous_session(struct ksmbd_conn *conn,
 {
        struct ksmbd_session *prev_sess;
        struct ksmbd_user *prev_user;
+       int err;
 
        down_write(&sessions_table_lock);
        down_write(&conn->session_lock);
@@ -325,8 +326,16 @@ 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);
+       if (err) {
+               ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_NEGOTIATE);
+               goto out;
+       }
+
        ksmbd_destroy_file_table(&prev_sess->file_table);
        prev_sess->state = SMB2_SESSION_EXPIRED;
+       ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_NEGOTIATE);
        ksmbd_launch_ksmbd_durable_scavenger();
 out:
        up_write(&conn->session_lock);
index 3f4c56a10a86f8d16744cee823c9338532a7ce28..cb7f487c96af80147352344bf527a1505f5928ee 100644 (file)
@@ -2213,7 +2213,7 @@ int smb2_session_logoff(struct ksmbd_work *work)
        ksmbd_conn_unlock(conn);
 
        ksmbd_close_session_fds(work);
-       ksmbd_conn_wait_idle(conn, sess_id);
+       ksmbd_conn_wait_idle(conn);
 
        /*
         * Re-lookup session to validate if session is deleted