ksmbd: fix use-after-free in ksmbd_tree_connect_put under concurrency

Under high concurrency, A tree-connection object (tcon) is freed on
a disconnect path while another path still holds a reference and later
executes *_put()/write on it.

Reported-by: Qianchang Zhao <pioooooooooip@gmail.com>
Reported-by: Zhitong Liu <liuzhitong1993@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
Namjae Jeon 2025-11-18 09:05:46 +09:00 committed by Steve French
parent 3316a8fc84
commit b39a1833cc
3 changed files with 4 additions and 18 deletions

View File

@ -78,7 +78,6 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
tree_conn->t_state = TREE_NEW; tree_conn->t_state = TREE_NEW;
status.tree_conn = tree_conn; status.tree_conn = tree_conn;
atomic_set(&tree_conn->refcount, 1); atomic_set(&tree_conn->refcount, 1);
init_waitqueue_head(&tree_conn->refcount_q);
ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn, ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn,
KSMBD_DEFAULT_GFP)); KSMBD_DEFAULT_GFP));
@ -100,14 +99,8 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon) void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon)
{ {
/* if (atomic_dec_and_test(&tcon->refcount))
* Checking waitqueue to releasing tree connect on kfree(tcon);
* tree disconnect. waitqueue_active is safe because it
* uses atomic operation for condition.
*/
if (!atomic_dec_return(&tcon->refcount) &&
waitqueue_active(&tcon->refcount_q))
wake_up(&tcon->refcount_q);
} }
int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
@ -119,13 +112,10 @@ int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
xa_erase(&sess->tree_conns, tree_conn->id); xa_erase(&sess->tree_conns, tree_conn->id);
write_unlock(&sess->tree_conns_lock); write_unlock(&sess->tree_conns_lock);
if (!atomic_dec_and_test(&tree_conn->refcount))
wait_event(tree_conn->refcount_q,
atomic_read(&tree_conn->refcount) == 0);
ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id); ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id);
ksmbd_release_tree_conn_id(sess, tree_conn->id); ksmbd_release_tree_conn_id(sess, tree_conn->id);
ksmbd_share_config_put(tree_conn->share_conf); ksmbd_share_config_put(tree_conn->share_conf);
if (atomic_dec_and_test(&tree_conn->refcount))
kfree(tree_conn); kfree(tree_conn);
return ret; return ret;
} }

View File

@ -33,7 +33,6 @@ struct ksmbd_tree_connect {
int maximal_access; int maximal_access;
bool posix_extensions; bool posix_extensions;
atomic_t refcount; atomic_t refcount;
wait_queue_head_t refcount_q;
unsigned int t_state; unsigned int t_state;
}; };

View File

@ -2190,7 +2190,6 @@ int smb2_tree_disconnect(struct ksmbd_work *work)
goto err_out; goto err_out;
} }
WARN_ON_ONCE(atomic_dec_and_test(&tcon->refcount));
tcon->t_state = TREE_DISCONNECTED; tcon->t_state = TREE_DISCONNECTED;
write_unlock(&sess->tree_conns_lock); write_unlock(&sess->tree_conns_lock);
@ -2200,8 +2199,6 @@ int smb2_tree_disconnect(struct ksmbd_work *work)
goto err_out; goto err_out;
} }
work->tcon = NULL;
rsp->StructureSize = cpu_to_le16(4); rsp->StructureSize = cpu_to_le16(4);
err = ksmbd_iov_pin_rsp(work, rsp, err = ksmbd_iov_pin_rsp(work, rsp,
sizeof(struct smb2_tree_disconnect_rsp)); sizeof(struct smb2_tree_disconnect_rsp));