Skip to content

Commit 2f029c7

Browse files
committed
ksmbd: fix use-after-free in __ksmbd_close_fd() via durable scavenger
When a durable file handle survives session disconnect (TCP close without SMB2_LOGOFF), session_fd_check() sets fp->conn = NULL to preserve the handle for later reconnection. However, it did not clean up the byte-range locks on fp->lock_list. Later, when the durable scavenger thread times out and calls __ksmbd_close_fd(NULL, fp), the lock cleanup loop did: spin_lock(&fp->conn->llist_lock); This caused a slab use-after-free because fp->conn was NULL and the original connection object had already been freed by ksmbd_tcp_disconnect(). The root cause is asymmetric cleanup: lock entries (smb_lock->clist) were left dangling on the freed conn->lock_list while fp->conn was nulled out. To fix this issue properly, we need to handle the lifetime of smb_lock->clist across three paths: - Safely skip clist deletion when list is empty and fp->conn is NULL. - Remove the lock from the old connection's lock_list in session_fd_check() - Re-add the lock to the new connection's lock_list in ksmbd_reopen_durable_fd(). Fixes: c8efcc786146 ("ksmbd: add support for durable handles v1/v2") Co-developed-by: munan Huang <munanevil@gmail.com> Signed-off-by: munan Huang <munanevil@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
1 parent e9688f7 commit 2f029c7

1 file changed

Lines changed: 21 additions & 4 deletions

File tree

vfs_cache.c

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -482,9 +482,11 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
482482
* there are not accesses to fp->lock_list.
483483
*/
484484
list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
485-
spin_lock(&fp->conn->llist_lock);
486-
list_del(&smb_lock->clist);
487-
spin_unlock(&fp->conn->llist_lock);
485+
if (!list_empty(&smb_lock->clist) && fp->conn) {
486+
spin_lock(&fp->conn->llist_lock);
487+
list_del(&smb_lock->clist);
488+
spin_unlock(&fp->conn->llist_lock);
489+
}
488490

489491
list_del(&smb_lock->flist);
490492
locks_free_lock(smb_lock->fl);
@@ -1052,6 +1054,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
10521054
struct ksmbd_inode *ci;
10531055
struct oplock_info *op;
10541056
struct ksmbd_conn *conn;
1057+
struct ksmbd_lock *smb_lock, *tmp_lock;
10551058

10561059
if (!is_reconnectable(fp))
10571060
return false;
@@ -1068,6 +1071,12 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
10681071
}
10691072
up_write(&ci->m_lock);
10701073

1074+
list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
1075+
spin_lock(&fp->conn->llist_lock);
1076+
list_del_init(&smb_lock->clist);
1077+
spin_unlock(&fp->conn->llist_lock);
1078+
}
1079+
10711080
fp->conn = NULL;
10721081
fp->tcon = NULL;
10731082
fp->volatile_id = KSMBD_NO_FID;
@@ -1163,6 +1172,8 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
11631172
{
11641173
struct ksmbd_inode *ci;
11651174
struct oplock_info *op;
1175+
struct ksmbd_conn *conn = work->conn;
1176+
struct ksmbd_lock *smb_lock;
11661177

11671178
if (!fp->is_durable || fp->conn || fp->tcon) {
11681179
pr_err("Invalid durable fd [%p:%p]\n", fp->conn, fp->tcon);
@@ -1174,9 +1185,15 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
11741185
return -EBADF;
11751186
}
11761187

1177-
fp->conn = work->conn;
1188+
fp->conn = conn;
11781189
fp->tcon = work->tcon;
11791190

1191+
list_for_each_entry(smb_lock, &fp->lock_list, flist) {
1192+
spin_lock(&conn->llist_lock);
1193+
list_add_tail(&smb_lock->clist, &conn->lock_list);
1194+
spin_unlock(&conn->llist_lock);
1195+
}
1196+
11801197
ci = fp->f_ci;
11811198
down_write(&ci->m_lock);
11821199
list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {

0 commit comments

Comments
 (0)