Skip to contentSkip to conversation

FAILED: patch "[PATCH] ksmbd: serialize directory emptiness checks with readdir" failed to apply to 6.12-stable tree

PATCH New [email protected] 2 participants 2 messages Nov 2

Subsystems (2)
lore
Share & tools
lore

[email protected]

author maintainer
#0 lore raw

The patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.

To reproduce the conflict and resubmit, you may use the following commands:

git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 7220980b2423d06a0604516129bfa4cec8a697ae
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026110241-arrest-granny-ae4b@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'

Possible dependencies:

thanks,

greg k-h

------------------ original commit in Linus's tree ------------------

From 7220980b2423d06a0604516129bfa4cec8a697ae Mon Sep 17 00:00:00 2001
From: Yongjun Gwon <[email protected]>
Date: Fri, 23 Oct 2026 02:38:32 +0900
Subject: [PATCH] ksmbd: serialize directory emptiness checks with readdir

SMB2 QUERY_DIRECTORY holds fp->readdir_lock while it calls iterate_dir() on fp->filp. An SMB2 SET_INFO request with FILE_DISPOSITION_INFORMATION can resolve the same FileId and call ksmbd_vfs_empty_dir() on the same filp without that lock.

iterate_dir()'s inode read lock permits concurrent readers and does not serialize per-file iterator state. ext4 stores its dx readdir cursor and fname RB tree in file->private_data. Concurrent reset or refill can therefore free a fname node while the other worker still traverses it or retains its RB-tree link, resulting in use-after-free accesses.

An authenticated SMB client with list and delete rights on a directory in a writable KSMBD share backed by ext4 can trigger this when the directory takes the dx readdir path. The client overlaps an SMB2 QUERY_DIRECTORY request with an SMB2 SET_INFO request carrying FILE_DISPOSITION_INFORMATION on one live FileId.

In one x86_64 SMP KASAN multi-shot guest using ksmbd-for-next at 171df27077fb, the unmodified kernel reported a cross-worker use-after-free before a panic. The first report's access and free stacks show the two request paths directly (trimmed):

  BUG: KASAN: slab-use-after-free in ext4_htree_store_dirent+0x488/0x550
  Read of size 8 by task kworker/2:0/29
  Workqueue: ksmbd-io handle_ksmbd_work
  Call Trace:
   ext4_htree_store_dirent+0x488/0x550
   htree_dirblock_to_tree+0x535/0x990
   ext4_htree_fill_tree+0x319/0xba0
   ext4_readdir+0x175a/0x2f40
   iterate_dir+0x1b6/0x630
   smb2_query_dir+0x1003/0x1cc0

  Freed by task 279:
   free_rb_tree_fname+0x67/0xc0
   ext4_readdir+0x13a9/0x2f40
   iterate_dir+0x1b6/0x630
   ksmbd_vfs_empty_dir+0xa2/0x100
   smb2_set_info+0x2ffa/0x45d0

Take fp->readdir_lock across the entire emptiness check. This places both iterators over that handle's filp in the same lock domain while preserving the disposition status handling. The exact code change completed a warning-free x86_64 KASAN bzImage build. Tracing three request pairs showed that the two iterate_dir() calls no longer overlapped and the expected statuses were preserved. A further 1,600 reproducer iterations completed with expected statuses and no KASAN report, Oops, or panic.

The reproducer and complete guest configuration are available privately to maintainers on request.

Fixes e2f34481b24d ("cifsd: add server-side procedures for SMB3")
Assisted-by LLM
fs/smb/server/smb2pdu.c +8 -3
index da82d80c222a..f5fe3cf8ba32 100644
@@ -8918,6 +8918,7 @@ static int set_file_disposition_info(struct ksmbd_work *work,
 				     struct smb2_file_disposition_info *file_info)
 {
 	struct inode *inode;
+	int err;
 
 	if (!(fp->daccess & FILE_DELETE_LE)) {
 		pr_err("no right to delete : 0x%x\n", fp->daccess);
@@ -8932,9 +8933,13 @@ static int set_file_disposition_info(struct ksmbd_work *work,
 		if (ksmbd_has_stream_without_delete_share(fp))
 			return -ESHARE;
 
-		if (S_ISDIR(inode->i_mode) && !ksmbd_stream_fd(fp) &&
-		    ksmbd_vfs_empty_dir(fp) == -ENOTEMPTY)
-			return -EBUSY;
+		if (S_ISDIR(inode->i_mode) && !ksmbd_stream_fd(fp)) {
+			mutex_lock(&fp->readdir_lock);
+			err = ksmbd_vfs_empty_dir(fp);
+			mutex_unlock(&fp->readdir_lock);
+			if (err == -ENOTEMPTY)
+				return -EBUSY;
+		}
 		smb_break_all_levII_oplock_for_delete(work, fp);
 		ksmbd_fd_set_delete_pending(fp);
 	} else {
maintainer
#1 lore raw

PATCH 1/1 ksmbd: serialize directory emptiness checks with readdir

From: Yongjun Gwon <[email protected]>

[ Upstream commit 7220980b2423d06a0604516129bfa4cec8a697ae ]

SMB2 QUERY_DIRECTORY holds fp->readdir_lock while it calls iterate_dir() on fp->filp. An SMB2 SET_INFO request with FILE_DISPOSITION_INFORMATION can resolve the same FileId and call ksmbd_vfs_empty_dir() on the same filp without that lock.

iterate_dir()'s inode read lock permits concurrent readers and does not serialize per-file iterator state. ext4 stores its dx readdir cursor and fname RB tree in file->private_data. Concurrent reset or refill can therefore free a fname node while the other worker still traverses it or retains its RB-tree link, resulting in use-after-free accesses.

An authenticated SMB client with list and delete rights on a directory in a writable KSMBD share backed by ext4 can trigger this when the directory takes the dx readdir path. The client overlaps an SMB2 QUERY_DIRECTORY request with an SMB2 SET_INFO request carrying FILE_DISPOSITION_INFORMATION on one live FileId.

In one x86_64 SMP KASAN multi-shot guest using ksmbd-for-next at 171df27077fb, the unmodified kernel reported a cross-worker use-after-free before a panic. The first report's access and free stacks show the two request paths directly (trimmed):

  BUG: KASAN: slab-use-after-free in ext4_htree_store_dirent+0x488/0x550
  Read of size 8 by task kworker/2:0/29
  Workqueue: ksmbd-io handle_ksmbd_work
  Call Trace:
   ext4_htree_store_dirent+0x488/0x550
   htree_dirblock_to_tree+0x535/0x990
   ext4_htree_fill_tree+0x319/0xba0
   ext4_readdir+0x175a/0x2f40
   iterate_dir+0x1b6/0x630
   smb2_query_dir+0x1003/0x1cc0

  Freed by task 279:
   free_rb_tree_fname+0x67/0xc0
   ext4_readdir+0x13a9/0x2f40
   iterate_dir+0x1b6/0x630
   ksmbd_vfs_empty_dir+0xa2/0x100
   smb2_set_info+0x2ffa/0x45d0

Take fp->readdir_lock across the entire emptiness check. This places both iterators over that handle's filp in the same lock domain while preserving the disposition status handling. The exact code change completed a warning-free x86_64 KASAN bzImage build. Tracing three request pairs showed that the two iterate_dir() calls no longer overlapped and the expected statuses were preserved. A further 1,600 reproducer iterations completed with expected statuses and no KASAN report, Oops, or panic.

The reproducer and complete guest configuration are available privately to maintainers on request.

Fixes e2f34481b24d ("cifsd: add server-side procedures for SMB3")
Assisted-by LLM
[ sashal: adapted to the existing directory check without the ksmbd_stream_fd() guard ]
---
 fs/smb/server/smb2pdu.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
fs/smb/server/smb2pdu.c +8 -3
index 51436cc50c790..7ff5b9b3f243a 100644
@@ -6574,6 +6574,7 @@ static int set_file_disposition_info(struct ksmbd_work *work,
 				     struct smb2_file_disposition_info *file_info)
 {
 	struct inode *inode;
+	int err;
 
 	if (!(fp->daccess & FILE_DELETE_LE)) {
 		pr_err("no right to delete : 0x%x\n", fp->daccess);
@@ -6582,9 +6583,13 @@ static int set_file_disposition_info(struct ksmbd_work *work,
 
 	inode = file_inode(fp->filp);
 	if (file_info->DeletePending) {
-		if (S_ISDIR(inode->i_mode) &&
-		    ksmbd_vfs_empty_dir(fp) == -ENOTEMPTY)
-			return -EBUSY;
+		if (S_ISDIR(inode->i_mode)) {
+			mutex_lock(&fp->readdir_lock);
+			err = ksmbd_vfs_empty_dir(fp);
+			mutex_unlock(&fp->readdir_lock);
+			if (err == -ENOTEMPTY)
+				return -EBUSY;
+		}
 		smb_break_all_levII_oplock(work, fp, 0);
 		ksmbd_set_inode_pending_delete(fp);
 	} else {
Signature · 1 line
2.53.0