]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ksmbd: fix use-after-free of a deferred file_lock on double SMB2_CANCEL
authorGil Portnoy <dddhkts1@gmail.com>
Sun, 31 May 2026 23:27:56 +0000 (08:27 +0900)
committerSteve French <stfrench@microsoft.com>
Mon, 1 Jun 2026 00:13:48 +0000 (19:13 -0500)
A deferred byte-range lock (an SMB2_LOCK that blocks) registers an async work on
conn->async_requests via setup_async_work(), with cancel_fn =
smb2_remove_blocked_lock and cancel_argv[0] pointing at the struct file_lock.

When the request is cancelled, the worker frees the file_lock with
locks_free_lock() and takes the cancelled early-exit, which "goto out"s and never
reaches release_async_work() -- the only site that unlinks the work from
conn->async_requests and clears cancel_fn/cancel_argv. The work therefore stays
matchable on async_requests with a live cancel_fn pointing at the freed file_lock,
until connection teardown finally runs release_async_work().

smb2_cancel() fires cancel_fn unconditionally with no state guard, so a second
SMB2_CANCEL for the same AsyncId, arriving in that window, re-runs
smb2_remove_blocked_lock() on the freed file_lock -- a slab use-after-free:

  BUG: KASAN: slab-use-after-free in __locks_delete_block
    __locks_delete_block
    locks_delete_block
    ksmbd_vfs_posix_lock_unblock
    smb2_remove_blocked_lock
    smb2_cancel                 <- 2nd SMB2_CANCEL fires cancel_fn
    handle_ksmbd_work
  Allocated by ...: locks_alloc_lock <- smb2_lock
  Freed by ...:     locks_free_lock  <- smb2_lock (cancelled branch)
  ... cache file_lock_cache of size 192

Reproduced on mainline with KASAN by an authenticated SMB client.

Skip a work whose state is already KSMBD_WORK_CANCELLED so its cancel callback
cannot be fired a second time.

Cc: stable@vger.kernel.org
Signed-off-by: Gil Portnoy <dddhkts1@gmail.com>
Acked-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/smb2pdu.c

index 620bcfbbfd92e76e32a94ffe57071ac8190accff..3eb3b1711acb36b8cbddea1e85d420a5dd049f7e 100644 (file)
@@ -7322,6 +7322,17 @@ int smb2_cancel(struct ksmbd_work *work)
                            le64_to_cpu(hdr->Id.AsyncId))
                                continue;
 
+                       /*
+                        * A cancelled deferred byte-range lock frees its
+                        * file_lock and takes the smb2_lock() early-exit that
+                        * skips release_async_work(), so the work stays on
+                        * conn->async_requests with a live cancel_fn pointing
+                        * at the freed file_lock.  Re-firing it on a second
+                        * SMB2_CANCEL is a use-after-free.
+                        */
+                       if (iter->state == KSMBD_WORK_CANCELLED)
+                               break;
+
                        ksmbd_debug(SMB,
                                    "smb2 with AsyncId %llu cancelled command = 0x%x\n",
                                    le64_to_cpu(hdr->Id.AsyncId),