All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 01/29] ksmbd: handle missing create contexts for lease opens
@ 2026-06-21 12:48 Namjae Jeon
  2026-06-21 12:48 ` [PATCH 02/29] ksmbd: supersede disconnected delete-on-close durable handle Namjae Jeon
                   ` (27 more replies)
  0 siblings, 28 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2_find_context_vals() assumes that callers only search create
contexts when the SMB2 CREATE request contains a non-empty create context
area. That is not always true. a client can send RequestedOplockLevel set
to SMB2_OPLOCK_LEVEL_LEASE without a lease create context.

In that case parse_lease_state() searches for a lease context and
smb2_find_context_vals() starts parsing from offset 0 with length 0,
returning -EINVAL. This makes the open fail with STATUS_INVALID_PARAMETER.
The smbtorture smb2.lease.duplicate_open test hits this while creating
a second file without a lease request.

Return NULL when the request has no create context area so the missing
context is treated the same as any other absent create context.  The open
then continues without granting a lease.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/oplock.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 400a3f2f8489..0a97d86cabf5 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1758,6 +1758,9 @@ struct create_context *smb2_find_context_vals(void *open_req, const char *tag, i
 	 * CreateContextsOffset and CreateContextsLength are guaranteed to
 	 * be valid because of ksmbd_smb2_check_message().
 	 */
+	if (!req->CreateContextsOffset || !req->CreateContextsLength)
+		return NULL;
+
 	cc = (struct create_context *)((char *)req +
 				       le32_to_cpu(req->CreateContextsOffset));
 	remain_len = le32_to_cpu(req->CreateContextsLength);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 02/29] ksmbd: supersede disconnected delete-on-close durable handle
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 03/29] ksmbd: invalidate durable handles on oplock break Namjae Jeon
                   ` (26 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

A durable handle opened with FILE_DELETE_ON_CLOSE is preserved across a
disconnect so it can be reclaimed by a durable reconnect.
smb2.durable-open.delete_on_close2 disconnects such a handle and then
reconnects it, expecting the reconnect to succeed.

When the client does not reconnect but instead opens the same name with a
new delete-on-close create, the preserved handle keeps the file present
with delete-on-close set. ksmbd then rejects the new open with
STATUS_ACCESS_DENIED on the file_present + FILE_DELETE_ON_CLOSE +
OPEN_IF/OVERWRITE_IF path. smb2.durable-open.delete_on_close1 expects this
open to create a fresh, empty file instead, i.e. the disconnected handle's
delete-on-close must take effect first.

Add ksmbd_close_disconnected_durable_delete_on_close(), which closes
disconnected (conn == NULL) durable handles that keep a delete-on-close
file present. The final close promotes S_DEL_ON_CLS to S_DEL_PENDING and
unlinks the file, so a re-resolved path is absent and the new open creates
it fresh. Call it from smb2_open() before the delete-on-close conflict
check, only for the conflicting open shapes. A live (connected) handle
still keeps the file and blocks the open as before.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c   | 17 ++++++++++++
 fs/smb/server/vfs_cache.c | 57 +++++++++++++++++++++++++++++++++++++++
 fs/smb/server/vfs_cache.h |  1 +
 3 files changed, 75 insertions(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 2bc275ed450a..37a20c5740cd 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3272,6 +3272,23 @@ int smb2_open(struct ksmbd_work *work)
 
 	rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS,
 				 &path, 1);
+
+	/*
+	 * A durable handle opened with delete-on-close is preserved across a
+	 * disconnect so it can be reclaimed by a durable reconnect.  When a new
+	 * delete-on-close open for the same name arrives instead, the
+	 * disconnected handle must give way: close it so its delete-on-close
+	 * removes the file, then re-resolve so this open can create a fresh one.
+	 */
+	if (!rc && (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) &&
+	    (req->CreateDisposition == FILE_OVERWRITE_IF_LE ||
+	     req->CreateDisposition == FILE_OPEN_IF_LE) &&
+	    ksmbd_close_disconnected_durable_delete_on_close(path.dentry)) {
+		path_put(&path);
+		rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS,
+					 &path, 1);
+	}
+
 	if (!rc) {
 		file_present = true;
 
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 39c56942ae44..96fa3f160d5b 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -517,6 +517,63 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
 	kmem_cache_free(filp_cache, fp);
 }
 
+/**
+ * ksmbd_close_disconnected_durable_delete_on_close() - drop a delete-on-close
+ *	file kept present only by disconnected durable handles
+ * @dentry:	dentry of the file being opened
+ *
+ * A durable handle opened with delete-on-close is preserved across a
+ * disconnect so it can be reclaimed by a durable reconnect.  When a new
+ * (non-reconnect) open arrives for the same name instead, the disconnected
+ * handle has to give way.  Close such handles so their delete-on-close is
+ * applied and the file is removed once the last handle is gone, letting the
+ * new open create a fresh file.
+ *
+ * The caller's inode reference is dropped before closing so that the final
+ * close can promote S_DEL_ON_CLS to S_DEL_PENDING and unlink the file.
+ *
+ * Return:	true if a disconnected durable handle was closed.
+ */
+bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
+{
+	struct ksmbd_inode *ci;
+	struct ksmbd_file *fp, *tmp;
+	LIST_HEAD(dispose);
+	bool closed = false;
+
+	ci = ksmbd_inode_lookup_lock(dentry);
+	if (!ci)
+		return false;
+
+	down_write(&ci->m_lock);
+	if (ci->m_flags & (S_DEL_ON_CLS | S_DEL_ON_CLS_STREAM | S_DEL_PENDING)) {
+		list_for_each_entry_safe(fp, tmp, &ci->m_fp_list, node) {
+			if (fp->conn || !fp->is_durable ||
+			    fp->f_state != FP_INITED)
+				continue;
+			list_move_tail(&fp->node, &dispose);
+		}
+	}
+	up_write(&ci->m_lock);
+
+	/*
+	 * Drop our lookup reference before closing so the last __ksmbd_close_fd()
+	 * can drop m_count to zero and unlink the delete-on-close file.  The
+	 * collected handles still hold references, so ci stays valid until they
+	 * are closed below.
+	 */
+	ksmbd_inode_put(ci);
+
+	while (!list_empty(&dispose)) {
+		fp = list_first_entry(&dispose, struct ksmbd_file, node);
+		list_del_init(&fp->node);
+		__ksmbd_close_fd(NULL, fp);
+		closed = true;
+	}
+
+	return closed;
+}
+
 static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp)
 {
 	if (fp->f_state != FP_INITED)
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index a3a9fda6de91..21c24956c7c2 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -160,6 +160,7 @@ struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id,
 void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp);
 struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d);
 void ksmbd_inode_put(struct ksmbd_inode *ci);
+bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry);
 struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id);
 struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
 void ksmbd_put_durable_fd(struct ksmbd_file *fp);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 03/29] ksmbd: invalidate durable handles on oplock break
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
  2026-06-21 12:48 ` [PATCH 02/29] ksmbd: supersede disconnected delete-on-close durable handle Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 04/29] ksmbd: fix durable reconnect context parsing Namjae Jeon
                   ` (25 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

When a durable handle is preserved after a disconnect, its oplock state
can still block later opens. If another client opens the same file and
the preserved oplock or lease has to be broken, the old durable handle
must no longer be reconnectable after the break cannot be acknowledged.

ksmbd was treating a missing connection, or an oplock break timeout, as a
successful break only by downgrading the oplock state.  The old durable
handle remained reconnectable, so a later durable reconnect for that
stale handle could succeed.

The open path can also see a detached durable handle before the break
notification helpers fully dispose of it. Invalidate such a preserved
durable handle directly when a competing open has to break its batch or
exclusive oplock, while leaving ordinary durable reconnects without a
competing open untouched.

If the old handle still reaches the reconnect path, reject it when the
same inode already has another active open. This matches the
smb2.durable-open.open2-lease/open2-oplock sequence where a later open
replaces the disconnected durable owner and the stale first handle must
not be reclaimed.

Also, reconnect lookup used only the persistent id. A new durable open
can get a persistent id that matches the stale reconnect request after
the old durable state is invalidated. Preserve the disconnected
handle's old volatile id and require durable reconnect contexts to match
it, so a stale reconnect cannot attach to a different durable open.

Windows allows the later open to proceed and rejects the old reconnect
with STATUS_OBJECT_NAME_NOT_FOUND. The smbtorture
smb2.durable-open.oplock test covers this case.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/oplock.c    | 40 +++++++++++++++++++++++++------
 fs/smb/server/smb2pdu.c   | 14 +++++++++++
 fs/smb/server/vfs_cache.c | 50 ++++++++++++++++++++++++++++++++++++++-
 fs/smb/server/vfs_cache.h |  4 ++++
 4 files changed, 100 insertions(+), 8 deletions(-)

diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 0a97d86cabf5..50cc067696d3 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -676,7 +676,7 @@ static struct oplock_info *same_client_has_lease(struct ksmbd_inode *ci,
 	return m_opinfo;
 }
 
-static void wait_for_break_ack(struct oplock_info *opinfo)
+static bool wait_for_break_ack(struct oplock_info *opinfo)
 {
 	int rc = 0;
 
@@ -693,7 +693,10 @@ static void wait_for_break_ack(struct oplock_info *opinfo)
 		}
 		opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
 		opinfo->op_state = OPLOCK_STATE_NONE;
+		return true;
 	}
+
+	return false;
 }
 
 static void wake_up_oplock_break(struct oplock_info *opinfo)
@@ -843,7 +846,7 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
 
 	conn = READ_ONCE(opinfo->conn);
 	if (!conn)
-		return 0;
+		return ksmbd_invalidate_durable_fd(opinfo->fid);
 
 	work = ksmbd_alloc_work_struct();
 	if (!work)
@@ -868,7 +871,8 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo)
 		INIT_WORK(&work->work, __smb2_oplock_break_noti);
 		ksmbd_queue_work(work);
 
-		wait_for_break_ack(opinfo);
+		if (wait_for_break_ack(opinfo))
+			ret = ksmbd_invalidate_durable_fd(opinfo->fid);
 	} else {
 		__smb2_oplock_break_noti(&work->work);
 		if (opinfo->level == SMB2_OPLOCK_LEVEL_II)
@@ -950,13 +954,14 @@ static int smb2_lease_break_noti(struct oplock_info *opinfo, bool wait_ack,
 	struct ksmbd_work *work;
 	struct lease_break_info *br_info;
 	struct lease *lease = opinfo->o_lease;
+	int ret = 0;
 
 	conn = READ_ONCE(opinfo->conn);
 	if (lease->version == 2 && lease->l_lb && lease->l_lb->conn &&
 	    !ksmbd_conn_releasing(lease->l_lb->conn))
 		conn = lease->l_lb->conn;
 	if (!conn)
-		return 0;
+		return ksmbd_invalidate_durable_fd(opinfo->fid);
 
 	work = ksmbd_alloc_work_struct();
 	if (!work)
@@ -987,8 +992,10 @@ static int smb2_lease_break_noti(struct oplock_info *opinfo, bool wait_ack,
 	if (opinfo->op_state == OPLOCK_ACK_WAIT) {
 		INIT_WORK(&work->work, __smb2_lease_break_noti);
 		ksmbd_queue_work(work);
-		if (wait_ack)
-			wait_for_break_ack(opinfo);
+		if (wait_ack) {
+			if (wait_for_break_ack(opinfo))
+				ret = ksmbd_invalidate_durable_fd(opinfo->fid);
+		}
 	} else {
 		__smb2_lease_break_noti(&work->work);
 		if (opinfo->o_lease->new_state == SMB2_LEASE_NONE_LE) {
@@ -996,7 +1003,7 @@ static int smb2_lease_break_noti(struct oplock_info *opinfo, bool wait_ack,
 			lease_update_oplock_levels(opinfo->o_lease);
 		}
 	}
-	return 0;
+	return ret;
 }
 
 static void wait_lease_breaking(struct oplock_info *opinfo)
@@ -1335,6 +1342,9 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 	struct ksmbd_inode *ci = fp->f_ci;
 	struct lease_table *new_lb = NULL;
 	bool prev_op_has_lease;
+	bool prev_durable_open = false;
+	bool prev_durable_detached = false;
+	unsigned long long prev_fid = KSMBD_NO_FID;
 	bool new_lease = false;
 	__le32 prev_op_state = 0;
 
@@ -1412,7 +1422,17 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 		goto op_break_not_needed;
 	}
 
+	if (prev_opinfo->o_fp && prev_opinfo->o_fp != fp &&
+	    prev_opinfo->o_fp->is_durable) {
+		prev_durable_open = true;
+		prev_durable_detached = !prev_opinfo->o_fp->conn ||
+					!prev_opinfo->o_fp->tcon;
+		prev_fid = prev_opinfo->fid;
+	}
+
 	err = oplock_break(prev_opinfo, break_level, work);
+	if (prev_durable_detached || (prev_durable_open && err == -ENOENT))
+		ksmbd_invalidate_durable_fd(prev_fid);
 	opinfo_put(prev_opinfo);
 	if (err == -ENOENT)
 		goto set_lev;
@@ -2027,6 +2047,12 @@ int smb2_check_durable_oplock(struct ksmbd_conn *conn,
 	if (!opinfo)
 		return 0;
 
+	if (ksmbd_has_other_active_fd(fp)) {
+		ksmbd_debug(SMB, "Durable handle reconnect failed: competing open\n");
+		ret = -EBADF;
+		goto out;
+	}
+
 	if (ksmbd_vfs_compare_durable_owner(fp, user) == false) {
 		ksmbd_debug(SMB, "Durable handle reconnect failed: owner mismatch\n");
 		ret = -EBADF;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 37a20c5740cd..f700f2f94ff2 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2861,6 +2861,13 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 				goto out;
 			}
 
+			if (dh_info->fp->durable_volatile_id !=
+			    recon_v2->dcontext.Fid.VolatileFileId) {
+				err = -EBADF;
+				ksmbd_put_durable_fd(dh_info->fp);
+				goto out;
+			}
+
 			if (memcmp(dh_info->fp->create_guid, recon_v2->dcontext.CreateGuid,
 				   SMB2_CREATE_GUID_SIZE)) {
 				err = -EBADF;
@@ -2901,6 +2908,13 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 				goto out;
 			}
 
+			if (dh_info->fp->durable_volatile_id !=
+			    recon->Data.Fid.VolatileFileId) {
+				err = -EBADF;
+				ksmbd_put_durable_fd(dh_info->fp);
+				goto out;
+			}
+
 			dh_info->type = dh_idx;
 			dh_info->reconnected = true;
 			ksmbd_debug(SMB, "reconnect Persistent-id from reconnect = %llu\n",
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 96fa3f160d5b..3546d95df76f 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -731,7 +731,8 @@ struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id)
 	struct ksmbd_file *fp;
 
 	fp = __ksmbd_lookup_fd(&global_ft, id);
-	if (fp && (fp->conn ||
+	if (fp && (fp->durable_reconnect_disabled ||
+		   fp->conn ||
 		   (fp->durable_scavenger_timeout &&
 		    (fp->durable_scavenger_timeout <
 		     jiffies_to_msecs(jiffies))))) {
@@ -750,6 +751,52 @@ void ksmbd_put_durable_fd(struct ksmbd_file *fp)
 	__ksmbd_close_fd(NULL, fp);
 }
 
+bool ksmbd_has_other_active_fd(struct ksmbd_file *fp)
+{
+	struct ksmbd_file *lfp;
+	struct ksmbd_inode *ci = fp->f_ci;
+	bool ret = false;
+
+	down_read(&ci->m_lock);
+	list_for_each_entry(lfp, &ci->m_fp_list, node) {
+		if (lfp == fp)
+			continue;
+
+		if (lfp->f_state == FP_INITED &&
+		    (READ_ONCE(lfp->conn) || READ_ONCE(lfp->tcon))) {
+			ret = true;
+			break;
+		}
+	}
+	up_read(&ci->m_lock);
+
+	return ret;
+}
+
+int ksmbd_invalidate_durable_fd(unsigned long long id)
+{
+	struct ksmbd_file *fp;
+
+	fp = ksmbd_lookup_global_fd(id);
+	if (!fp)
+		return -ENOENT;
+
+	fp->durable_reconnect_disabled = true;
+
+	if (fp->conn) {
+		ksmbd_put_durable_fd(fp);
+		return -ENOENT;
+	}
+
+	fp->durable_timeout = 1;
+	fp->durable_scavenger_timeout = jiffies_to_msecs(jiffies);
+	ksmbd_put_durable_fd(fp);
+	if (waitqueue_active(&dh_wq))
+		wake_up(&dh_wq);
+
+	return -ENOENT;
+}
+
 struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid)
 {
 	struct ksmbd_file	*fp = NULL;
@@ -990,6 +1037,7 @@ __close_file_table_ids(struct ksmbd_session *sess,
 			 * global_ft.
 			 */
 			idr_remove(ft->idr, id);
+			fp->durable_volatile_id = fp->volatile_id;
 			fp->volatile_id = KSMBD_NO_FID;
 			write_unlock(&ft->lock);
 
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 21c24956c7c2..4803f41a91ef 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -81,6 +81,7 @@ struct ksmbd_file {
 	struct file			*filp;
 	u64				persistent_id;
 	u64				volatile_id;
+	u64				durable_volatile_id;
 
 	spinlock_t			f_lock;
 
@@ -122,6 +123,7 @@ struct ksmbd_file {
 	bool				is_durable;
 	bool				is_persistent;
 	bool				is_resilient;
+	bool				durable_reconnect_disabled;
 
 	bool                            is_posix_ctxt;
 	struct durable_owner		owner;
@@ -164,6 +166,8 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry);
 struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id);
 struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
 void ksmbd_put_durable_fd(struct ksmbd_file *fp);
+int ksmbd_invalidate_durable_fd(unsigned long long id);
+bool ksmbd_has_other_active_fd(struct ksmbd_file *fp);
 struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
 struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
 unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 04/29] ksmbd: fix durable reconnect context parsing
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
  2026-06-21 12:48 ` [PATCH 02/29] ksmbd: supersede disconnected delete-on-close durable handle Namjae Jeon
  2026-06-21 12:48 ` [PATCH 03/29] ksmbd: invalidate durable handles on oplock break Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 05/29] ksmbd: handle durable v2 app instance id Namjae Jeon
                   ` (24 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

SMB2 create context DataLength describes only the create context data
payload. It does not include the create context header, name field, or
any local padding that exists in ksmbd's helper structures.

ksmbd validated durable reconnect contexts by comparing
DataOffset + DataLength against sizeof the whole helper structure. This
rejects a valid durable v2 reconnect context because the wire DH2C data
is 36 bytes while struct create_durable_handle_reconnect_v2 contains an
extra four byte pad.

Validate the durable context payload length against the corresponding
payload member instead. Also keep the reconnect context authoritative
when a later durable request context is present, matching the existing
durable v1 reconnect behavior.

This fixes smbtorture smb2.durable-v2-open.durable-v2-setinfo, where
the durable v2 reconnect after SET_INFO was rejected with
STATUS_INVALID_PARAMETER.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index f700f2f94ff2..35db86da79d3 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2845,9 +2845,8 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 				goto out;
 			}
 
-			if (le16_to_cpu(context->DataOffset) +
-				le32_to_cpu(context->DataLength) <
-			    sizeof(struct create_durable_handle_reconnect_v2)) {
+			if (le32_to_cpu(context->DataLength) <
+			    sizeof(recon_v2->dcontext)) {
 				err = -EINVAL;
 				goto out;
 			}
@@ -2892,9 +2891,8 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 				goto out;
 			}
 
-			if (le16_to_cpu(context->DataOffset) +
-				le32_to_cpu(context->DataLength) <
-			    sizeof(create_durable_reconn_t)) {
+			if (le32_to_cpu(context->DataLength) <
+			    sizeof(recon->Data)) {
 				err = -EINVAL;
 				goto out;
 			}
@@ -2931,9 +2929,8 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 				goto out;
 			}
 
-			if (le16_to_cpu(context->DataOffset) +
-				le32_to_cpu(context->DataLength) <
-			    sizeof(struct create_durable_req_v2)) {
+			if (le32_to_cpu(context->DataLength) <
+			    sizeof(durable_v2_blob->dcontext)) {
 				err = -EINVAL;
 				goto out;
 			}
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 05/29] ksmbd: handle durable v2 app instance id
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (2 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 04/29] ksmbd: fix durable reconnect context parsing Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 06/29] ksmbd: preserve open change time across rename Namjae Jeon
                   ` (23 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

The SMB2_CREATE_APP_INSTANCE_ID create context is used with durable v2
opens to identify another open from the same application instance. When
a new durable v2 open arrives with the same AppInstanceId as an existing
open, the server should close the previous open without sending an
oplock break notification.

ksmbd ignored this create context. A second durable v2 batch oplock open
with the same AppInstanceId therefore went through the normal competing
open path and sent an oplock break to the first opener. smbtorture
smb2.durable-v2-open.app-instance expects no oplock break and then
expects the old handle to be closed.

Parse and store AppInstanceId for durable v2 opens. Before creating the
new open, find an existing file with the same AppInstanceId and close it
through the normal close teardown path without issuing an oplock break.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c   | 37 ++++++++++++++++++++
 fs/smb/server/vfs_cache.c | 72 +++++++++++++++++++++++++++++++++++++++
 fs/smb/server/vfs_cache.h |  1 +
 3 files changed, 110 insertions(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 35db86da79d3..a21760394637 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2807,8 +2807,10 @@ struct durable_info {
 	unsigned short int type;
 	bool persistent;
 	bool reconnected;
+	bool app_instance_id;
 	unsigned int timeout;
 	char *CreateGuid;
+	char AppInstanceId[SMB2_CREATE_GUID_SIZE];
 };
 
 static int parse_durable_handle_context(struct ksmbd_work *work,
@@ -2993,6 +2995,31 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
 	return err;
 }
 
+static int parse_app_instance_id(struct smb2_create_req *req,
+				 struct durable_info *dh_info)
+{
+	struct create_context *context;
+	char *data;
+
+	context = smb2_find_context_vals(req, SMB2_CREATE_APP_INSTANCE_ID,
+					 SMB2_CREATE_GUID_SIZE);
+	if (IS_ERR(context))
+		return PTR_ERR(context);
+	if (!context)
+		return 0;
+
+	if (le32_to_cpu(context->DataLength) < 20)
+		return -EINVAL;
+
+	data = (char *)context + le16_to_cpu(context->DataOffset);
+	if (data[0] != 20 || data[1])
+		return -EINVAL;
+
+	memcpy(dh_info->AppInstanceId, data + 4, SMB2_CREATE_GUID_SIZE);
+	dh_info->app_instance_id = true;
+	return 0;
+}
+
 /**
  * smb2_open() - handler for smb file open request
  * @work:	smb work containing request buffer
@@ -3135,6 +3162,9 @@ int smb2_open(struct ksmbd_work *work)
 			ksmbd_debug(SMB, "error parsing durable handle context\n");
 			goto err_out2;
 		}
+		rc = parse_app_instance_id(req, &dh_info);
+		if (rc)
+			goto err_out2;
 
 		if (dh_info.reconnected == true) {
 			rc = smb2_check_durable_oplock(conn, share, dh_info.fp,
@@ -3161,6 +3191,9 @@ int smb2_open(struct ksmbd_work *work)
 
 			goto reconnected_fp;
 		}
+
+		if (dh_info.type == DURABLE_REQ_V2 && dh_info.app_instance_id)
+			ksmbd_close_fd_app_instance_id(dh_info.AppInstanceId);
 	} else if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) {
 		lc = parse_lease_state(req);
 		if (IS_ERR(lc)) {
@@ -3775,6 +3808,10 @@ int smb2_open(struct ksmbd_work *work)
 		if (dh_info.type == DURABLE_REQ_V2) {
 			memcpy(fp->create_guid, dh_info.CreateGuid,
 					SMB2_CREATE_GUID_SIZE);
+			if (dh_info.app_instance_id)
+				memcpy(fp->app_instance_id,
+				       dh_info.AppInstanceId,
+				       SMB2_CREATE_GUID_SIZE);
 			if (dh_info.timeout)
 				fp->durable_timeout =
 					min_t(unsigned int, dh_info.timeout,
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 3546d95df76f..5a2fddadcddf 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -37,6 +37,8 @@ static struct ksmbd_file_table global_ft;
 static atomic_long_t fd_limit;
 static struct kmem_cache *filp_cache;
 
+static int ksmbd_mark_fp_closed(struct ksmbd_file *fp);
+
 #define OPLOCK_NONE      0
 #define OPLOCK_EXCLUSIVE 1
 #define OPLOCK_BATCH     2
@@ -773,6 +775,76 @@ bool ksmbd_has_other_active_fd(struct ksmbd_file *fp)
 	return ret;
 }
 
+static struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id)
+{
+	struct ksmbd_file *fp = NULL;
+	unsigned int id;
+
+	if (!memchr_inv(app_instance_id, 0, SMB2_CREATE_GUID_SIZE))
+		return NULL;
+
+	read_lock(&global_ft.lock);
+	idr_for_each_entry(global_ft.idr, fp, id) {
+		if (!memcmp(fp->app_instance_id, app_instance_id,
+			    SMB2_CREATE_GUID_SIZE)) {
+			fp = ksmbd_fp_get(fp);
+			break;
+		}
+	}
+	read_unlock(&global_ft.lock);
+
+	return fp;
+}
+
+int ksmbd_close_fd_app_instance_id(char *app_instance_id)
+{
+	struct ksmbd_file_table *ft;
+	struct ksmbd_file *fp;
+	struct oplock_info *opinfo;
+	int n_to_drop = 0;
+
+	fp = ksmbd_lookup_fd_app_instance_id(app_instance_id);
+	if (!fp)
+		return 0;
+
+	opinfo = opinfo_get(fp);
+	if (!opinfo || !opinfo->sess)
+		goto out;
+
+	ft = &opinfo->sess->file_table;
+	write_lock(&ft->lock);
+	if (fp->f_state == FP_INITED) {
+		if (has_file_id(fp->volatile_id)) {
+			idr_remove(ft->idr, fp->volatile_id);
+			fp->volatile_id = KSMBD_NO_FID;
+		}
+		n_to_drop = ksmbd_mark_fp_closed(fp);
+	}
+	write_unlock(&ft->lock);
+	opinfo_put(opinfo);
+	opinfo = NULL;
+
+	if (!n_to_drop)
+		goto out;
+
+	down_write(&fp->f_ci->m_lock);
+	list_del_init(&fp->node);
+	up_write(&fp->f_ci->m_lock);
+
+	if (atomic_sub_and_test(n_to_drop, &fp->refcount)) {
+		if (fp->conn)
+			atomic_dec(&fp->conn->stats.open_files_count);
+		__ksmbd_close_fd(NULL, fp);
+	}
+	return 0;
+
+out:
+	if (opinfo)
+		opinfo_put(opinfo);
+	ksmbd_put_durable_fd(fp);
+	return 0;
+}
+
 int ksmbd_invalidate_durable_fd(unsigned long long id)
 {
 	struct ksmbd_file *fp;
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 4803f41a91ef..ca391d597e2e 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -168,6 +168,7 @@ struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
 void ksmbd_put_durable_fd(struct ksmbd_file *fp);
 int ksmbd_invalidate_durable_fd(unsigned long long id);
 bool ksmbd_has_other_active_fd(struct ksmbd_file *fp);
+int ksmbd_close_fd_app_instance_id(char *app_instance_id);
 struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
 struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
 unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 06/29] ksmbd: preserve open change time across rename
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (3 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 05/29] ksmbd: handle durable v2 app instance id Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 07/29] ksmbd: check parent directory sharing conflicts on rename Namjae Jeon
                   ` (22 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

inode ctime is updated when a file is renamed. ksmbd returned that
ctime directly as SMB2 ChangeTime for handle-based query information.
This makes ChangeTime change after a rename through an already-open
handle, while Windows keeps the handle's ChangeTime stable for this
case.

Store the SMB ChangeTime in struct ksmbd_file when the handle is opened
and use that value for create, close, and handle-based query information
responses.  If a client explicitly sets FILE_BASIC_INFORMATION
ChangeTime, update the stored value as well.

This fixes smbtorture smb2.rename.simple_modtime, which expects
ChangeTime and LastWriteTime to remain unchanged after renaming an
already-open file.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c   | 23 ++++++++++-------------
 fs/smb/server/vfs_cache.h |  1 +
 2 files changed, 11 insertions(+), 13 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index a21760394637..f2ba52c7e325 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3783,6 +3783,7 @@ int smb2_open(struct ksmbd_work *work)
 		fp->create_time = ksmbd_UnixTimeToNT(stat.btime);
 	else
 		fp->create_time = ksmbd_UnixTimeToNT(stat.ctime);
+	fp->change_time = ksmbd_UnixTimeToNT(stat.ctime);
 	if (req->FileAttributes || fp->f_ci->m_fattr == 0)
 		fp->f_ci->m_fattr =
 			cpu_to_le32(smb2_get_dos_mode(&stat, le32_to_cpu(req->FileAttributes)));
@@ -3832,8 +3833,7 @@ int smb2_open(struct ksmbd_work *work)
 	rsp->LastAccessTime = cpu_to_le64(time);
 	time = ksmbd_UnixTimeToNT(stat.mtime);
 	rsp->LastWriteTime = cpu_to_le64(time);
-	time = ksmbd_UnixTimeToNT(stat.ctime);
-	rsp->ChangeTime = cpu_to_le64(time);
+	rsp->ChangeTime = cpu_to_le64(fp->change_time);
 	rsp->AllocationSize = S_ISDIR(stat.mode) ? 0 :
 		cpu_to_le64(stat.blocks << 9);
 	rsp->EndofFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
@@ -5102,8 +5102,7 @@ static int get_file_basic_info(struct smb2_query_info_rsp *rsp,
 	basic_info->LastAccessTime = cpu_to_le64(time);
 	time = ksmbd_UnixTimeToNT(stat.mtime);
 	basic_info->LastWriteTime = cpu_to_le64(time);
-	time = ksmbd_UnixTimeToNT(stat.ctime);
-	basic_info->ChangeTime = cpu_to_le64(time);
+	basic_info->ChangeTime = cpu_to_le64(fp->change_time);
 	basic_info->Attributes = fp->f_ci->m_fattr;
 	basic_info->Pad = 0;
 	rsp->OutputBufferLength =
@@ -5205,8 +5204,7 @@ static int get_file_all_info(struct ksmbd_work *work,
 	file_info->LastAccessTime = cpu_to_le64(time);
 	time = ksmbd_UnixTimeToNT(stat.mtime);
 	file_info->LastWriteTime = cpu_to_le64(time);
-	time = ksmbd_UnixTimeToNT(stat.ctime);
-	file_info->ChangeTime = cpu_to_le64(time);
+	file_info->ChangeTime = cpu_to_le64(fp->change_time);
 	file_info->Attributes = fp->f_ci->m_fattr;
 	file_info->Pad1 = 0;
 	if (ksmbd_stream_fd(fp) == false) {
@@ -5415,8 +5413,7 @@ static int get_file_network_open_info(struct smb2_query_info_rsp *rsp,
 	file_info->LastAccessTime = cpu_to_le64(time);
 	time = ksmbd_UnixTimeToNT(stat.mtime);
 	file_info->LastWriteTime = cpu_to_le64(time);
-	time = ksmbd_UnixTimeToNT(stat.ctime);
-	file_info->ChangeTime = cpu_to_le64(time);
+	file_info->ChangeTime = cpu_to_le64(fp->change_time);
 	file_info->Attributes = fp->f_ci->m_fattr;
 	if (ksmbd_stream_fd(fp) == false) {
 		file_info->AllocationSize = cpu_to_le64(stat.blocks << 9);
@@ -5547,8 +5544,7 @@ static int find_file_posix_info(struct smb2_query_info_rsp *rsp,
 	file_info->LastAccessTime = cpu_to_le64(time);
 	time = ksmbd_UnixTimeToNT(stat.mtime);
 	file_info->LastWriteTime = cpu_to_le64(time);
-	time = ksmbd_UnixTimeToNT(stat.ctime);
-	file_info->ChangeTime = cpu_to_le64(time);
+	file_info->ChangeTime = cpu_to_le64(fp->change_time);
 	file_info->DosAttributes = fp->f_ci->m_fattr;
 	file_info->Inode = cpu_to_le64(stat.ino);
 	if (ksmbd_stream_fd(fp) == false) {
@@ -6271,8 +6267,7 @@ int smb2_close(struct ksmbd_work *work)
 		rsp->LastAccessTime = cpu_to_le64(time);
 		time = ksmbd_UnixTimeToNT(stat.mtime);
 		rsp->LastWriteTime = cpu_to_le64(time);
-		time = ksmbd_UnixTimeToNT(stat.ctime);
-		rsp->ChangeTime = cpu_to_le64(time);
+		rsp->ChangeTime = cpu_to_le64(fp->change_time);
 		ksmbd_fd_put(work, fp);
 	} else {
 		rsp->Flags = 0;
@@ -6485,9 +6480,11 @@ static int set_file_basic_info(struct ksmbd_file *fp,
 		attrs.ia_valid |= (ATTR_ATIME | ATTR_ATIME_SET);
 	}
 
-	if (file_info->ChangeTime)
+	if (file_info->ChangeTime) {
+		fp->change_time = le64_to_cpu(file_info->ChangeTime);
 		inode_set_ctime_to_ts(inode,
 				ksmbd_NTtimeToUnix(file_info->ChangeTime));
+	}
 
 	if (file_info->LastWriteTime) {
 		attrs.ia_mtime = ksmbd_NTtimeToUnix(file_info->LastWriteTime);
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index ca391d597e2e..8c456484cab2 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -97,6 +97,7 @@ struct ksmbd_file {
 	__le32				coption;
 	__le32				cdoption;
 	__u64				create_time;
+	__u64				change_time;
 	__u64				itime;
 
 	bool				is_nt_open;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 07/29] ksmbd: check parent directory sharing conflicts on rename
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (4 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 06/29] ksmbd: preserve open change time across rename Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 08/29] ksmbd: deny renaming directory with open children Namjae Jeon
                   ` (21 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

When renaming a file, some existing opens on the parent directory must
block the rename with STATUS_SHARING_VIOLATION. This includes parent
directory handles opened with DELETE access and handles opened without
FILE_SHARE_DELETE.

ksmbd checked only the parent's desired access for FILE_DELETE.  That
handled smb2.rename.share_delete_and_delete_access, but missed the case
where the parent directory was opened without delete access and without
delete sharing, so smb2.rename.no_share_delete_no_delete_access incorrectly
succeeded.

Attribute-only parent opens, however, must not block the rename.
smb2.rename.msword opens the parent directory with only SYNCHRONIZE and
FILE_READ_ATTRIBUTES, no share access, and then renames an already-open
child file.  Windows allows this pattern.

Reject parent directory handles that request DELETE access, and reject
non-attribute-only parent opens that deny FILE_SHARE_DELETE, while allowing
attribute-only parent opens to coexist with child rename.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/vfs.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index 74b0307cb100..80fd27752b2c 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -702,8 +702,10 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
 
 	parent_fp = ksmbd_lookup_fd_inode(old_child->d_parent);
 	if (parent_fp) {
-		if (parent_fp->daccess & FILE_DELETE_LE) {
-			pr_err("parent dir is opened with delete access\n");
+		if ((parent_fp->daccess & FILE_DELETE_LE) ||
+		    (!parent_fp->attrib_only &&
+		     !(parent_fp->saccess & FILE_SHARE_DELETE_LE))) {
+			pr_err("parent dir blocks delete sharing\n");
 			err = -ESHARE;
 			ksmbd_fd_put(work, parent_fp);
 			goto out3;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 08/29] ksmbd: deny renaming directory with open children
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (5 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 07/29] ksmbd: check parent directory sharing conflicts on rename Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 09/29] ksmbd: propagate failed command status in related compounds Namjae Jeon
                   ` (20 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

Windows denies renaming a directory while a file below that directory is
still open. smb2.rename.rename_dir_openfile checks this by keeping a file
handle open under the directory and then attempting to rename the directory
handle.  ksmbd did not check open children before calling vfs_rename(), so
the rename incorrectly succeeded.

For non-POSIX clients, scan the global open file table for active handles
whose dentries are below the directory being renamed.  If any child is
open, fail the rename with -EACCES so the client receives
STATUS_ACCESS_DENIED.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/vfs.c       |  6 ++++++
 fs/smb/server/vfs_cache.c | 25 +++++++++++++++++++++++++
 fs/smb/server/vfs_cache.h |  1 +
 3 files changed, 32 insertions(+)

diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index 80fd27752b2c..f5fa22d87603 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -700,6 +700,12 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
 	if (err)
 		goto out_drop_write;
 
+	if (!work->tcon->posix_extensions && d_is_dir(old_child) &&
+	    ksmbd_has_open_files(old_child)) {
+		err = -EACCES;
+		goto out3;
+	}
+
 	parent_fp = ksmbd_lookup_fd_inode(old_child->d_parent);
 	if (parent_fp) {
 		if ((parent_fp->daccess & FILE_DELETE_LE) ||
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 5a2fddadcddf..98c5bac93d63 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -10,6 +10,7 @@
 #include <linux/vmalloc.h>
 #include <linux/kthread.h>
 #include <linux/freezer.h>
+#include <linux/dcache.h>
 
 #include "glob.h"
 #include "vfs_cache.h"
@@ -914,6 +915,30 @@ struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry)
 	return NULL;
 }
 
+bool ksmbd_has_open_files(struct dentry *dentry)
+{
+	struct ksmbd_file *fp;
+	unsigned int id;
+	bool ret = false;
+
+	read_lock(&global_ft.lock);
+	idr_for_each_entry(global_ft.idr, fp, id) {
+		struct dentry *fp_dentry = fp->filp->f_path.dentry;
+
+		if (fp->f_state != FP_INITED)
+			continue;
+		if (fp_dentry == dentry)
+			continue;
+		if (is_subdir(fp_dentry, dentry)) {
+			ret = true;
+			break;
+		}
+	}
+	read_unlock(&global_ft.lock);
+
+	return ret;
+}
+
 #define OPEN_ID_TYPE_VOLATILE_ID	(0)
 #define OPEN_ID_TYPE_PERSISTENT_ID	(1)
 
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 8c456484cab2..52a0e8b1f79f 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -172,6 +172,7 @@ bool ksmbd_has_other_active_fd(struct ksmbd_file *fp);
 int ksmbd_close_fd_app_instance_id(char *app_instance_id);
 struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
 struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
+bool ksmbd_has_open_files(struct dentry *dentry);
 unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp);
 struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp);
 void ksmbd_launch_ksmbd_durable_scavenger(void);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 09/29] ksmbd: propagate failed command status in related compounds
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (6 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 08/29] ksmbd: deny renaming directory with open children Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 10/29] ksmbd: validate handle for create or get object id Namjae Jeon
                   ` (19 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

In a related compound request, later commands can refer to the file handle
from an earlier command using the related FID value. If the earlier
command fails without producing a valid compound FID, the later related
commands must fail with the same status instead of operating on an invalid
or stale handle.

smb2.compound.related4 sends CREATE followed by IOCTL, CLOSE and SET_INFO.
The CREATE is expected to fail with STATUS_ACCESS_DENIED, and the remaining
related commands are expected to return STATUS_ACCESS_DENIED as well. ksmbd
only stored the compound FID on successful CREATE and did not remember
failed compound statuses.

Store the failed status in the work item and make related handle-based
requests fail immediately with that status only when the compound FID is
invalid. Also preserve and consume the related FID across successful
FLUSH, READ and WRITE requests whose responses do not carry a file id. Keep
a valid compound FID across non-close failures so later related commands
can continue to use the handle.

When extracting the FID from a successful READ, WRITE or FLUSH request, use
the request structure matching the SMB2 command: READ and WRITE place
PersistentFileId and VolatileFileId at a different offset than FLUSH, so a
single smb2_flush_req cast can save the wrong value as compound_fid and
make the following related request fail with STATUS_FILE_CLOSED
(smb2.compound_async.write_write after smb2.compound_async.flush_flush).
Only update the saved compound FID when the request carries a valid
volatile FID. otherwise an all-ones related FID would overwrite the CREATE
FID and break smb2.compound.related6.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/ksmbd_work.h |   1 +
 fs/smb/server/smb2pdu.c    | 159 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 156 insertions(+), 4 deletions(-)

diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index 5368430561fb..df0554a2c50d 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -60,6 +60,7 @@ struct ksmbd_work {
 	u64				compound_fid;
 	u64				compound_pfid;
 	u64				compound_sid;
+	__le32				compound_status;
 
 	const struct cred		*saved_cred;
 
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index f2ba52c7e325..df79533dc0a2 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -405,6 +405,59 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
 		work->compound_fid = ((struct smb2_create_rsp *)rsp)->VolatileFileId;
 		work->compound_pfid = ((struct smb2_create_rsp *)rsp)->PersistentFileId;
 		work->compound_sid = le64_to_cpu(rsp->SessionId);
+		work->compound_status = STATUS_SUCCESS;
+	} else if ((req->Command == SMB2_FLUSH ||
+		    req->Command == SMB2_READ ||
+		    req->Command == SMB2_WRITE) &&
+		   rsp->Status == STATUS_SUCCESS) {
+		u64 volatile_id = KSMBD_NO_FID;
+		u64 persistent_id = KSMBD_NO_FID;
+
+		if (req->Command == SMB2_FLUSH) {
+			struct smb2_flush_req *flush_req =
+				(struct smb2_flush_req *)req;
+
+			volatile_id = flush_req->VolatileFileId;
+			persistent_id = flush_req->PersistentFileId;
+		} else if (req->Command == SMB2_READ) {
+			struct smb2_read_req *read_req =
+				(struct smb2_read_req *)req;
+
+			volatile_id = read_req->VolatileFileId;
+			persistent_id = read_req->PersistentFileId;
+		} else {
+			struct smb2_write_req *write_req =
+				(struct smb2_write_req *)req;
+
+			volatile_id = write_req->VolatileFileId;
+			persistent_id = write_req->PersistentFileId;
+		}
+
+		if (has_file_id(volatile_id)) {
+			work->compound_fid = volatile_id;
+			work->compound_pfid = persistent_id;
+			work->compound_sid = le64_to_cpu(rsp->SessionId);
+			work->compound_status = STATUS_SUCCESS;
+		}
+	} else if (req->Command == SMB2_CREATE) {
+		work->compound_fid = KSMBD_NO_FID;
+		work->compound_pfid = KSMBD_NO_FID;
+		work->compound_sid = le64_to_cpu(rsp->SessionId);
+		work->compound_status = rsp->Status;
+	} else if (rsp->Status != STATUS_SUCCESS) {
+		work->compound_sid = le64_to_cpu(rsp->SessionId);
+		/*
+		 * Only carry the failed status forward when the failing command
+		 * was itself part of the related chain. An unrelated command
+		 * that fails (e.g. a standalone request with a bad session id)
+		 * must not seed the status for a following related command,
+		 * which has to be evaluated on its own (and may legitimately
+		 * fail with a different status such as INVALID_PARAMETER). The
+		 * compound session id is still tracked so a following related
+		 * command can validate it.
+		 */
+		if (req->Flags & SMB2_FLAGS_RELATED_OPERATIONS)
+			work->compound_status = rsp->Status;
 	}
 
 	len = get_rfc1002_len(work->response_buf) - work->next_smb2_rsp_hdr_off;
@@ -430,6 +483,7 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
 		ksmbd_debug(SMB, "related flag should be set\n");
 		work->compound_fid = KSMBD_NO_FID;
 		work->compound_pfid = KSMBD_NO_FID;
+		work->compound_status = STATUS_SUCCESS;
 	}
 	memset((char *)rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
 	rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
@@ -449,6 +503,19 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
 	memcpy(rsp_hdr->Signature, rcv_hdr->Signature, 16);
 }
 
+static bool smb2_compound_has_failed(struct ksmbd_work *work,
+				     struct smb2_hdr *rsp)
+{
+	if (!work->next_smb2_rcv_hdr_off ||
+	    has_file_id(work->compound_fid) ||
+	    work->compound_status == STATUS_SUCCESS)
+		return false;
+
+	rsp->Status = work->compound_status;
+	smb2_set_err_rsp(work);
+	return true;
+}
+
 /**
  * is_chained_smb2_message() - check for chained command
  * @work:	smb work containing smb request buffer
@@ -4608,11 +4675,28 @@ int smb2_query_dir(struct ksmbd_work *work)
 	unsigned char srch_flag;
 	int buffer_sz;
 	struct smb2_query_dir_private query_dir_private = {NULL, };
+	unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
 
 	ksmbd_debug(SMB, "Received smb2 query directory request\n");
 
 	WORK_BUFFERS(work, req, rsp);
 
+	if (smb2_compound_has_failed(work, &rsp->hdr))
+		return -EACCES;
+
+	if (work->next_smb2_rcv_hdr_off &&
+	    !has_file_id(req->VolatileFileId)) {
+		ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+			    work->compound_fid);
+		id = work->compound_fid;
+		pid = work->compound_pfid;
+	}
+
+	if (!has_file_id(id)) {
+		id = req->VolatileFileId;
+		pid = req->PersistentFileId;
+	}
+
 	if (ksmbd_override_fsids(work)) {
 		rsp->hdr.Status = STATUS_NO_MEMORY;
 		smb2_set_err_rsp(work);
@@ -4625,7 +4709,7 @@ int smb2_query_dir(struct ksmbd_work *work)
 		goto err_out2;
 	}
 
-	dir_fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
+	dir_fp = ksmbd_lookup_fd_slow(work, id, pid);
 	if (!dir_fp) {
 		rc = -EBADF;
 		goto err_out2;
@@ -6088,6 +6172,9 @@ int smb2_query_info(struct ksmbd_work *work)
 
 	WORK_BUFFERS(work, req, rsp);
 
+	if (smb2_compound_has_failed(work, &rsp->hdr))
+		return -EACCES;
+
 	if (ksmbd_override_fsids(work)) {
 		rc = -ENOMEM;
 		goto err_out;
@@ -6192,6 +6279,9 @@ int smb2_close(struct ksmbd_work *work)
 
 	WORK_BUFFERS(work, req, rsp);
 
+	if (smb2_compound_has_failed(work, &rsp->hdr))
+		return -EACCES;
+
 	if (test_share_config_flag(work->tcon->share_conf,
 				   KSMBD_SHARE_FLAG_PIPE)) {
 		ksmbd_debug(SMB, "IPC pipe close request\n");
@@ -6862,6 +6952,8 @@ int smb2_set_info(struct ksmbd_work *work)
 	if (work->next_smb2_rcv_hdr_off) {
 		req = ksmbd_req_buf_next(work);
 		rsp = ksmbd_resp_buf_next(work);
+		if (smb2_compound_has_failed(work, &rsp->hdr))
+			return -EACCES;
 		if (!has_file_id(req->VolatileFileId)) {
 			ksmbd_debug(SMB, "Compound request set FID = %llu\n",
 				    work->compound_fid);
@@ -7093,6 +7185,8 @@ int smb2_read(struct ksmbd_work *work)
 	if (work->next_smb2_rcv_hdr_off) {
 		req = ksmbd_req_buf_next(work);
 		rsp = ksmbd_resp_buf_next(work);
+		if (smb2_compound_has_failed(work, &rsp->hdr))
+			return -EACCES;
 		if (!has_file_id(req->VolatileFileId)) {
 			ksmbd_debug(SMB, "Compound request set FID = %llu\n",
 					work->compound_fid);
@@ -7366,11 +7460,28 @@ int smb2_write(struct ksmbd_work *work)
 	bool writethrough = false, is_rdma_channel = false;
 	int err = 0;
 	unsigned int max_write_size = work->conn->vals->max_write_size;
+	unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
 
 	ksmbd_debug(SMB, "Received smb2 write request\n");
 
 	WORK_BUFFERS(work, req, rsp);
 
+	if (smb2_compound_has_failed(work, &rsp->hdr))
+		return -EACCES;
+
+	if (work->next_smb2_rcv_hdr_off &&
+	    !has_file_id(req->VolatileFileId)) {
+		ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+			    work->compound_fid);
+		id = work->compound_fid;
+		pid = work->compound_pfid;
+	}
+
+	if (!has_file_id(id)) {
+		id = req->VolatileFileId;
+		pid = req->PersistentFileId;
+	}
+
 	if (test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_PIPE)) {
 		ksmbd_debug(SMB, "IPC pipe write request\n");
 		return smb2_write_pipe(work);
@@ -7415,7 +7526,7 @@ int smb2_write(struct ksmbd_work *work)
 		goto out;
 	}
 
-	fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
+	fp = ksmbd_lookup_fd_slow(work, id, pid);
 	if (!fp) {
 		err = -ENOENT;
 		goto out;
@@ -7509,13 +7620,30 @@ int smb2_flush(struct ksmbd_work *work)
 {
 	struct smb2_flush_req *req;
 	struct smb2_flush_rsp *rsp;
+	u64 id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
 	int err;
 
 	WORK_BUFFERS(work, req, rsp);
 
 	ksmbd_debug(SMB, "Received smb2 flush request(fid : %llu)\n", req->VolatileFileId);
 
-	err = ksmbd_vfs_fsync(work, req->VolatileFileId, req->PersistentFileId);
+	if (smb2_compound_has_failed(work, &rsp->hdr))
+		return -EACCES;
+
+	if (work->next_smb2_rcv_hdr_off &&
+	    !has_file_id(req->VolatileFileId)) {
+		ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+			    work->compound_fid);
+		id = work->compound_fid;
+		pid = work->compound_pfid;
+	}
+
+	if (!has_file_id(id)) {
+		id = req->VolatileFileId;
+		pid = req->PersistentFileId;
+	}
+
+	err = ksmbd_vfs_fsync(work, id, pid);
 	if (err)
 		goto out;
 
@@ -7734,11 +7862,29 @@ int smb2_lock(struct ksmbd_work *work)
 	LIST_HEAD(lock_list);
 	LIST_HEAD(rollback_list);
 	int prior_lock = 0, bkt;
+	unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
 
 	WORK_BUFFERS(work, req, rsp);
 
 	ksmbd_debug(SMB, "Received smb2 lock request\n");
-	fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
+
+	if (smb2_compound_has_failed(work, &rsp->hdr))
+		return -EACCES;
+
+	if (work->next_smb2_rcv_hdr_off &&
+	    !has_file_id(req->VolatileFileId)) {
+		ksmbd_debug(SMB, "Compound request set FID = %llu\n",
+			    work->compound_fid);
+		id = work->compound_fid;
+		pid = work->compound_pfid;
+	}
+
+	if (!has_file_id(id)) {
+		id = req->VolatileFileId;
+		pid = req->PersistentFileId;
+	}
+
+	fp = ksmbd_lookup_fd_slow(work, id, pid);
 	if (!fp) {
 		ksmbd_debug(SMB, "Invalid file id for lock : %llu\n", req->VolatileFileId);
 		err = -ENOENT;
@@ -8544,6 +8690,8 @@ int smb2_ioctl(struct ksmbd_work *work)
 	if (work->next_smb2_rcv_hdr_off) {
 		req = ksmbd_req_buf_next(work);
 		rsp = ksmbd_resp_buf_next(work);
+		if (smb2_compound_has_failed(work, &rsp->hdr))
+			return -EACCES;
 		if (!has_file_id(req->VolatileFileId)) {
 			ksmbd_debug(SMB, "Compound request set FID = %llu\n",
 				    work->compound_fid);
@@ -9208,6 +9356,9 @@ int smb2_notify(struct ksmbd_work *work)
 
 	WORK_BUFFERS(work, req, rsp);
 
+	if (smb2_compound_has_failed(work, &rsp->hdr))
+		return -EACCES;
+
 	if (work->next_smb2_rcv_hdr_off && req->hdr.NextCommand) {
 		rsp->hdr.Status = STATUS_INTERNAL_ERROR;
 		smb2_set_err_rsp(work);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 10/29] ksmbd: validate handle for create or get object id
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (7 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 09/29] ksmbd: propagate failed command status in related compounds Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 11/29] ksmbd: preserve compound responses for chained errors Namjae Jeon
                   ` (18 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

FSCTL_CREATE_OR_GET_OBJECT_ID returned a dummy successful response without
checking whether the request handle was valid. That let an invalid related
compound handle succeed in smb2.compound.related5, although the client
expected STATUS_FILE_CLOSED.

Look up the file handle before building the object id response and fail
with STATUS_FILE_CLOSED when the handle is invalid or already closed.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index df79533dc0a2..d3bd198ec938 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -8789,6 +8789,15 @@ int smb2_ioctl(struct ksmbd_work *work)
 	case FSCTL_CREATE_OR_GET_OBJECT_ID:
 	{
 		struct file_object_buf_type1_ioctl_rsp *obj_buf;
+		struct ksmbd_file *fp;
+
+		fp = ksmbd_lookup_fd_fast(work, id);
+		if (!fp) {
+			ret = -EBADF;
+			rsp->hdr.Status = STATUS_FILE_CLOSED;
+			goto out2;
+		}
+		ksmbd_fd_put(work, fp);
 
 		nbytes = sizeof(struct file_object_buf_type1_ioctl_rsp);
 		obj_buf = (struct file_object_buf_type1_ioctl_rsp *)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 11/29] ksmbd: preserve compound responses for chained errors
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (8 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 10/29] ksmbd: validate handle for create or get object id Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 12/29] ksmbd: return success for deferred final close Namjae Jeon
                   ` (17 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

set_smb2_rsp_status() resets the response iov and compound offsets before
building an error response. That is fine for a single request, but it
corrupts a compound response when an error is detected after an earlier
compound element has already been completed.

smb2.compound.invalid4 sends a READ as the first compound element and a
bogus command as the second one. The READ response must remain in
the compound response with STATUS_END_OF_FILE, followed by the bogus
command response with STATUS_INVALID_PARAMETER. Resetting the response
state for the second command breaks the compound framing and the client
reports NT_STATUS_INVALID_NETWORK_RESPONSE.

When setting an error for a chained command, update and pin only
the current compound response slot instead of resetting the whole response.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index d3bd198ec938..35f23b427bd1 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -246,6 +246,13 @@ void set_smb2_rsp_status(struct ksmbd_work *work, __le32 err)
 {
 	struct smb2_hdr *rsp_hdr;
 
+	if (work->next_smb2_rcv_hdr_off) {
+		rsp_hdr = ksmbd_resp_buf_next(work);
+		rsp_hdr->Status = err;
+		smb2_set_err_rsp(work);
+		return;
+	}
+
 	rsp_hdr = smb_get_msg(work->response_buf);
 	rsp_hdr->Status = err;
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 12/29] ksmbd: return success for deferred final close
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (9 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 11/29] ksmbd: preserve compound responses for chained errors Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 13/29] ksmbd: send pending interim for last compound I/O Namjae Jeon
                   ` (16 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

ksmbd_close_fd() marks an open file as FP_CLOSED and drops the file table
reference. If another in-flight request still holds a reference, the final
close is deferred until that request drops its reference.

The function currently returns -EINVAL in that deferred-final-close case
because fp is cleared when the reference count does not reach zero.  That
turns a valid close into STATUS_FILE_CLOSED.

smb2.compound_find.compound_find_close sends QUERY_DIRECTORY and then
closes the same directory handle before receiving the find response.
The query holds a reference while it builds the response, so close must
mark the handle closed and return success even though final teardown is
delayed. Track whether the handle was successfully transitioned to
FP_CLOSED and return success when only the final close is deferred.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/vfs_cache.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 98c5bac93d63..b617edef950a 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -640,6 +640,7 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
 {
 	struct ksmbd_file	*fp;
 	struct ksmbd_file_table	*ft;
+	bool closed = false;
 
 	if (!has_file_id(id))
 		return 0;
@@ -654,6 +655,7 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
 			fp = NULL;
 		else {
 			fp->f_state = FP_CLOSED;
+			closed = true;
 			if (!atomic_dec_and_test(&fp->refcount))
 				fp = NULL;
 		}
@@ -661,7 +663,7 @@ int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
 	write_unlock(&ft->lock);
 
 	if (!fp)
-		return -EINVAL;
+		return closed ? 0 : -EINVAL;
 
 	__put_fd_final(work, fp);
 	return 0;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 13/29] ksmbd: send pending interim for last compound I/O
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (10 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 12/29] ksmbd: return success for deferred final close Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 14/29] ksmbd: honor stream delete sharing for base file Namjae Jeon
                   ` (15 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.compound_async.write_write and smb2.compound_async.read_read expect
the last I/O request in a compound request to become cancellable before
its final response is received. smb clients mark a request cancellable
after receiving an interim STATUS_PENDING response.

ksmbd handled the last READ/WRITE synchronously and returned the final
response directly, so the client never observed STATUS_PENDING and
req->cancel.can_cancel remained false.

For the last READ or WRITE in a compound request, register the work briefly
as async and send a STATUS_PENDING interim response before continuing with
the normal synchronous completion. The final READ/WRITE response remains
unchanged.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 35f23b427bd1..cc9cd9297557 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -7176,7 +7176,7 @@ int smb2_read(struct ksmbd_work *work)
 	size_t length, mincount;
 	ssize_t nbytes = 0, remain_bytes = 0;
 	int err = 0;
-	bool is_rdma_channel = false;
+	bool is_rdma_channel = false, async_interim = false;
 	unsigned int max_read_size = conn->vals->max_read_size;
 	unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
 	void *aux_payload_buf;
@@ -7248,6 +7248,14 @@ int smb2_read(struct ksmbd_work *work)
 		goto out;
 	}
 
+	if (work->next_smb2_rcv_hdr_off && !req->hdr.NextCommand) {
+		err = setup_async_work(work, NULL, NULL);
+		if (err)
+			goto out;
+		smb2_send_interim_resp(work, STATUS_PENDING);
+		async_interim = true;
+	}
+
 	offset = le64_to_cpu(req->Offset);
 	if (offset < 0) {
 		err = -EINVAL;
@@ -7283,6 +7291,8 @@ int smb2_read(struct ksmbd_work *work)
 		kvfree(aux_payload_buf);
 		rsp->hdr.Status = STATUS_END_OF_FILE;
 		smb2_set_err_rsp(work);
+		if (async_interim)
+			release_async_work(work);
 		ksmbd_fd_put(work, fp);
 		return -ENODATA;
 	}
@@ -7317,6 +7327,8 @@ int smb2_read(struct ksmbd_work *work)
 		kvfree(aux_payload_buf);
 		goto out;
 	}
+	if (async_interim)
+		release_async_work(work);
 	/*
 	 * RDMA responses are transferred through channel buffers and encrypted
 	 * responses use the encryption transform, so only normal SMB transport
@@ -7330,6 +7342,8 @@ int smb2_read(struct ksmbd_work *work)
 	return 0;
 
 out:
+	if (async_interim)
+		release_async_work(work);
 	if (err) {
 		if (err == -EISDIR)
 			rsp->hdr.Status = STATUS_INVALID_DEVICE_REQUEST;
@@ -7465,6 +7479,7 @@ int smb2_write(struct ksmbd_work *work)
 	ssize_t nbytes;
 	char *data_buf;
 	bool writethrough = false, is_rdma_channel = false;
+	bool async_interim = false;
 	int err = 0;
 	unsigned int max_write_size = work->conn->vals->max_write_size;
 	unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
@@ -7545,6 +7560,14 @@ int smb2_write(struct ksmbd_work *work)
 		goto out;
 	}
 
+	if (work->next_smb2_rcv_hdr_off && !req->hdr.NextCommand) {
+		err = setup_async_work(work, NULL, NULL);
+		if (err)
+			goto out;
+		smb2_send_interim_resp(work, STATUS_PENDING);
+		async_interim = true;
+	}
+
 	if (length > max_write_size) {
 		ksmbd_debug(SMB, "limiting write size to max size(%u)\n",
 			    max_write_size);
@@ -7593,10 +7616,15 @@ int smb2_write(struct ksmbd_work *work)
 	err = ksmbd_iov_pin_rsp(work, rsp, offsetof(struct smb2_write_rsp, Buffer));
 	if (err)
 		goto out;
+	if (async_interim)
+		release_async_work(work);
 	ksmbd_fd_put(work, fp);
 	return 0;
 
 out:
+	if (async_interim)
+		release_async_work(work);
+
 	if (err == -EAGAIN)
 		rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
 	else if (err == -ENOSPC || err == -EFBIG)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 14/29] ksmbd: honor stream delete sharing for base file
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (11 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 13/29] ksmbd: send pending interim for last compound I/O Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 15/29] ksmbd: reject empty-attribute synchronize-only create Namjae Jeon
                   ` (14 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.streams.delete opens an alternate data stream without
FILE_SHARE_DELETE and then tries to delete the base file.  Windows rejects
the base-file delete with STATUS_SHARING_VIOLATION while the stream handle
is open. ksmbd tracks stream opens on the same ksmbd_inode as the base
file, but the delete-on-close path only checked delete access on the base
handle before marking the inode delete-pending.  As a result, deleting
the base file succeeded even though an open stream handle denied delete
sharing. Add a helper to detect open stream handles on the same inode that
do not allow FILE_SHARE_DELETE, and reject base-file delete pending and
DELETE opens with a sharing violation in that case.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c   |  9 +++++++++
 fs/smb/server/vfs_cache.c | 27 +++++++++++++++++++++++++++
 fs/smb/server/vfs_cache.h |  1 +
 3 files changed, 37 insertions(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index cc9cd9297557..a3ae37e8b24d 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3737,6 +3737,12 @@ int smb2_open(struct ksmbd_work *work)
 		goto err_out;
 	}
 
+	if (!stream_name && daccess & FILE_DELETE_LE &&
+	    ksmbd_has_stream_without_delete_share(fp)) {
+		rc = -EPERM;
+		goto err_out;
+	}
+
 	if (file_present || created)
 		path_put(&path);
 
@@ -6758,6 +6764,9 @@ static int set_file_disposition_info(struct ksmbd_work *work,
 
 	inode = file_inode(fp->filp);
 	if (file_info->DeletePending) {
+		if (ksmbd_has_stream_without_delete_share(fp))
+			return -ESHARE;
+
 		if (S_ISDIR(inode->i_mode) &&
 		    ksmbd_vfs_empty_dir(fp) == -ENOTEMPTY)
 			return -EBUSY;
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index b617edef950a..11b51320b96e 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -254,6 +254,33 @@ void ksmbd_clear_inode_pending_delete(struct ksmbd_file *fp)
 	up_write(&ci->m_lock);
 }
 
+bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp)
+{
+	struct ksmbd_file *prev_fp;
+	struct ksmbd_inode *ci = fp->f_ci;
+	bool ret = false;
+
+	if (ksmbd_stream_fd(fp))
+		return false;
+
+	down_read(&ci->m_lock);
+	list_for_each_entry(prev_fp, &ci->m_fp_list, node) {
+		if (prev_fp == fp || !ksmbd_stream_fd(prev_fp))
+			continue;
+
+		if (file_inode(fp->filp) != file_inode(prev_fp->filp))
+			continue;
+
+		if (!(prev_fp->saccess & FILE_SHARE_DELETE_LE)) {
+			ret = true;
+			break;
+		}
+	}
+	up_read(&ci->m_lock);
+
+	return ret;
+}
+
 void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp,
 				  int file_info)
 {
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 52a0e8b1f79f..8aa87843025f 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -169,6 +169,7 @@ struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
 void ksmbd_put_durable_fd(struct ksmbd_file *fp);
 int ksmbd_invalidate_durable_fd(unsigned long long id);
 bool ksmbd_has_other_active_fd(struct ksmbd_file *fp);
+bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp);
 int ksmbd_close_fd_app_instance_id(char *app_instance_id);
 struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
 struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 15/29] ksmbd: reject empty-attribute synchronize-only create
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (12 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 14/29] ksmbd: honor stream delete sharing for base file Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 16/29] ksmbd: tighten create file attribute validation Namjae Jeon
                   ` (13 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.create.gentest checks each desired access bit independently and
expects an open that requests only SYNCHRONIZE with CreateDisposition
OPEN_IF and FileAttributes 0 to fail with STATUS_ACCESS_DENIED.

Rejecting all SYNCHRONIZE-only opens is too broad: SYNCHRONIZE does not
imply read, write, or delete data access, and
smb2.sharemode.sharemode-access expects a SYNCHRONIZE-only open to succeed
when it does not conflict with the existing share mode.

Limit the rejection to the gentest create shape: SYNCHRONIZE-only access,
OPEN_IF disposition, and no file attributes. Other synchronize-only opens
are handled by the normal permission and share-mode checks.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index a3ae37e8b24d..9a1308f32f45 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3332,6 +3332,13 @@ int smb2_open(struct ksmbd_work *work)
 		goto err_out2;
 	}
 
+	if (req->DesiredAccess == FILE_SYNCHRONIZE_LE &&
+	    req->CreateDisposition == FILE_OPEN_IF_LE &&
+	    !req->FileAttributes) {
+		rc = -EACCES;
+		goto err_out2;
+	}
+
 	if (req->FileAttributes && !(req->FileAttributes & FILE_ATTRIBUTE_MASK_LE)) {
 		pr_err("Invalid file attribute : 0x%x\n",
 		       le32_to_cpu(req->FileAttributes));
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 16/29] ksmbd: tighten create file attribute validation
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (13 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 15/29] ksmbd: reject empty-attribute synchronize-only create Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 17/29] ksmbd: return requested create allocation size Namjae Jeon
                   ` (12 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.create.gentest checks each create FileAttributes bit independently and
expects FILE_ATTRIBUTE_INTEGRITY_STREAM and FILE_ATTRIBUTE_NO_SCRUB_DATA to
be rejected with STATUS_INVALID_PARAMETER.

ksmbd validates create FileAttributes against FILE_ATTRIBUTE_MASK, which
includes those bits. It also rejects only requests that have no known
attribute bit at all, so a request containing both known and unknown bits
can pass validation.

Use a create-specific attribute mask that excludes INTEGRITY_STREAM and
NO_SCRUB_DATA, and reject any bit outside that mask.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 9a1308f32f45..d6001cdce085 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -57,6 +57,10 @@ static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
 
 #define WORK_BUFFERS(w, rq, rs)	__wbuf((w), (void **)&(rq), (void **)&(rs))
 
+#define SMB2_CREATE_FILE_ATTRIBUTE_MASK \
+	(FILE_ATTRIBUTE_MASK & ~(FILE_ATTRIBUTE_INTEGRITY_STREAM | \
+				 FILE_ATTRIBUTE_NO_SCRUB_DATA))
+
 /**
  * check_session_id() - check for valid session id in smb header
  * @conn:	connection instance
@@ -3339,7 +3343,8 @@ int smb2_open(struct ksmbd_work *work)
 		goto err_out2;
 	}
 
-	if (req->FileAttributes && !(req->FileAttributes & FILE_ATTRIBUTE_MASK_LE)) {
+	if (req->FileAttributes &&
+	    (req->FileAttributes & ~cpu_to_le32(SMB2_CREATE_FILE_ATTRIBUTE_MASK))) {
 		pr_err("Invalid file attribute : 0x%x\n",
 		       le32_to_cpu(req->FileAttributes));
 		rc = -EINVAL;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 17/29] ksmbd: return requested create allocation size
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (14 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 16/29] ksmbd: tighten create file attribute validation Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-22 23:01   ` Nathan Chancellor
  2026-06-21 12:48 ` [PATCH 18/29] ksmbd: apply create security descriptor first Namjae Jeon
                   ` (11 subsequent siblings)
  27 siblings, 1 reply; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.create.blob sends an SMB2_CREATE_ALLOCATION_SIZE create context with
a 1MiB allocation size and expects the create response AllocationSize field
to match the requested size.  smb2.create.open additionally compares the
AllocationSize returned in the CREATE response with the AllocationSize
returned by FILE_ALL_INFORMATION on the same handle.

ksmbd applies the allocation with fallocate(), but then fills both the
create response and handle-based information from stat.blocks << 9.  On
filesystems such as ext4 this can include filesystem allocation rounding
and metadata effects, causing a response larger than the SMB2 allocation
size context and a disagreement between the two queries.

Remember the requested allocation size while processing the create context,
store the reported allocation size in struct ksmbd_file, and use it for
both the create response and handle-based allocation size responses. Update
the stored value when FILE_ALLOCATION_INFORMATION changes it, and fall back
to stat.blocks << 9 when no allocation size context was provided.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c   | 34 +++++++++++++++++++++++-----------
 fs/smb/server/vfs_cache.h |  1 +
 2 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index d6001cdce085..cca04bf84b81 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3133,7 +3133,7 @@ int smb2_open(struct ksmbd_work *work)
 	char *stream_name = NULL;
 	bool file_present = false, created = false, already_permitted = false;
 	int share_ret, need_truncate = 0;
-	u64 time;
+	u64 time, alloc_size = 0;
 	umode_t posix_mode = 0;
 	__le32 daccess, maximal_access = 0;
 	int iov_len = 0;
@@ -3826,7 +3826,6 @@ int smb2_open(struct ksmbd_work *work)
 			rc = PTR_ERR(az_req);
 			goto err_out1;
 		} else if (az_req) {
-			loff_t alloc_size;
 			int err;
 
 			if (le16_to_cpu(az_req->ccontext.DataOffset) +
@@ -3876,6 +3875,8 @@ int smb2_open(struct ksmbd_work *work)
 	else
 		fp->create_time = ksmbd_UnixTimeToNT(stat.ctime);
 	fp->change_time = ksmbd_UnixTimeToNT(stat.ctime);
+	fp->allocation_size = S_ISDIR(stat.mode) ? 0 :
+		(alloc_size ?: stat.blocks << 9);
 	if (req->FileAttributes || fp->f_ci->m_fattr == 0)
 		fp->f_ci->m_fattr =
 			cpu_to_le32(smb2_get_dos_mode(&stat, le32_to_cpu(req->FileAttributes)));
@@ -3926,8 +3927,19 @@ int smb2_open(struct ksmbd_work *work)
 	time = ksmbd_UnixTimeToNT(stat.mtime);
 	rsp->LastWriteTime = cpu_to_le64(time);
 	rsp->ChangeTime = cpu_to_le64(fp->change_time);
-	rsp->AllocationSize = S_ISDIR(stat.mode) ? 0 :
-		cpu_to_le64(stat.blocks << 9);
+	/*
+	 * The cached allocation size hides filesystem rounding for the
+	 * requested allocation, but it can go stale when the file grows past
+	 * it via writes (e.g. across a durable reconnect). Refresh it once the
+	 * file exceeds the cached value, rounding the end of file up to the
+	 * volume allocation unit (the filesystem block size, matching the
+	 * SectorsPerAllocationUnit/BytesPerSector ksmbd advertises) rather than
+	 * using the raw on-disk block count, which can include filesystem
+	 * preallocation and metadata rounding.
+	 */
+	if (!S_ISDIR(stat.mode) && stat.size > fp->allocation_size)
+		fp->allocation_size = roundup(stat.size, stat.blksize);
+	rsp->AllocationSize = cpu_to_le64(fp->allocation_size);
 	rsp->EndofFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
 	rsp->FileAttributes = fp->f_ci->m_fattr;
 
@@ -5236,7 +5248,7 @@ static int get_file_standard_info(struct smb2_query_info_rsp *rsp,
 	delete_pending = ksmbd_inode_pending_delete(fp);
 
 	if (ksmbd_stream_fd(fp) == false) {
-		sinfo->AllocationSize = cpu_to_le64(stat.blocks << 9);
+		sinfo->AllocationSize = cpu_to_le64(fp->allocation_size);
 		sinfo->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
 	} else {
 		sinfo->AllocationSize = cpu_to_le64(fp->stream.size);
@@ -5317,8 +5329,7 @@ static int get_file_all_info(struct ksmbd_work *work,
 	file_info->Attributes = fp->f_ci->m_fattr;
 	file_info->Pad1 = 0;
 	if (ksmbd_stream_fd(fp) == false) {
-		file_info->AllocationSize =
-			cpu_to_le64(stat.blocks << 9);
+		file_info->AllocationSize = cpu_to_le64(fp->allocation_size);
 		file_info->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
 	} else {
 		file_info->AllocationSize = cpu_to_le64(fp->stream.size);
@@ -5525,7 +5536,7 @@ static int get_file_network_open_info(struct smb2_query_info_rsp *rsp,
 	file_info->ChangeTime = cpu_to_le64(fp->change_time);
 	file_info->Attributes = fp->f_ci->m_fattr;
 	if (ksmbd_stream_fd(fp) == false) {
-		file_info->AllocationSize = cpu_to_le64(stat.blocks << 9);
+		file_info->AllocationSize = cpu_to_le64(fp->allocation_size);
 		file_info->EndOfFile = S_ISDIR(stat.mode) ? 0 : cpu_to_le64(stat.size);
 	} else {
 		file_info->AllocationSize = cpu_to_le64(fp->stream.size);
@@ -5658,7 +5669,7 @@ static int find_file_posix_info(struct smb2_query_info_rsp *rsp,
 	file_info->Inode = cpu_to_le64(stat.ino);
 	if (ksmbd_stream_fd(fp) == false) {
 		file_info->EndOfFile = cpu_to_le64(stat.size);
-		file_info->AllocationSize = cpu_to_le64(stat.blocks << 9);
+		file_info->AllocationSize = cpu_to_le64(fp->allocation_size);
 	} else {
 		file_info->EndOfFile = cpu_to_le64(fp->stream.size);
 		file_info->AllocationSize = cpu_to_le64(fp->stream.size);
@@ -6373,8 +6384,7 @@ int smb2_close(struct ksmbd_work *work)
 		}
 
 		rsp->Flags = SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB;
-		rsp->AllocationSize = S_ISDIR(stat.mode) ? 0 :
-			cpu_to_le64(stat.blocks << 9);
+		rsp->AllocationSize = cpu_to_le64(fp->allocation_size);
 		rsp->EndOfFile = cpu_to_le64(stat.size);
 		rsp->Attributes = fp->f_ci->m_fattr;
 		rsp->CreationTime = cpu_to_le64(fp->create_time);
@@ -6707,6 +6717,8 @@ static int set_file_allocation_info(struct ksmbd_work *work,
 		if (size < alloc_blks * 512)
 			i_size_write(inode, size);
 	}
+
+	fp->allocation_size = le64_to_cpu(file_alloc_info->AllocationSize);
 	return 0;
 }
 
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 8aa87843025f..f85021c11d6e 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -98,6 +98,7 @@ struct ksmbd_file {
 	__le32				cdoption;
 	__u64				create_time;
 	__u64				change_time;
+	__u64				allocation_size;
 	__u64				itime;
 
 	bool				is_nt_open;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 18/29] ksmbd: apply create security descriptor first
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (15 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 17/29] ksmbd: return requested create allocation size Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 19/29] ksmbd: downgrade oplock after break timeout Namjae Jeon
                   ` (10 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.create.aclfile creates files with an SMB2_CREATE_SD_BUFFER create
context and expects the resulting security descriptor to match
the descriptor supplied by the client.

ksmbd currently tries to inherit the parent DACL first and only parses
the SMB2_CREATE_SD_BUFFER context when DACL inheritance fails.
If inheritance succeeds, the explicit security descriptor supplied on
create is ignored. This breaks create requests that include owner/group
information in the security descriptor.

Apply the create security descriptor first when the context is present.
Fall back to the existing inherited/default ACL path only when no create
security descriptor was supplied.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index cca04bf84b81..526d5be97364 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3651,14 +3651,16 @@ int smb2_open(struct ksmbd_work *work)
 		if (posix_acl_rc)
 			ksmbd_debug(SMB, "inherit posix acl failed : %d\n", posix_acl_rc);
 
-		if (test_share_config_flag(work->tcon->share_conf,
-					   KSMBD_SHARE_FLAG_ACL_XATTR)) {
-			rc = smb_inherit_dacl(conn, &path, sess->user->uid,
-					      sess->user->gid);
-		}
+		rc = smb2_create_sd_buffer(work, req, &path);
+		if (rc && rc != -ENOENT)
+			goto err_out;
 
-		if (rc) {
-			rc = smb2_create_sd_buffer(work, req, &path);
+		if (rc == -ENOENT) {
+			if (test_share_config_flag(work->tcon->share_conf,
+						   KSMBD_SHARE_FLAG_ACL_XATTR)) {
+				rc = smb_inherit_dacl(conn, &path, sess->user->uid,
+						      sess->user->gid);
+			}
 			if (rc) {
 				if (posix_acl_rc)
 					ksmbd_vfs_set_init_posix_acl(idmap,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 19/29] ksmbd: downgrade oplock after break timeout
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (16 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 18/29] ksmbd: apply create security descriptor first Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 20/29] ksmbd: avoid level II oplock break notification on unlink Namjae Jeon
                   ` (9 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.oplock.batch22a opens a file with a batch oplock and then issues a
second open that waits for the oplock break timeout.  After the timeout the
second open should succeed, but the granted oplock level must be level II.

When the break times out, oplock_break() returns -ENOENT after invalidating
the previous opener.  smb_grant_oplock() went straight to set_lev with the
original requested oplock level, so the second open could be granted a new
batch oplock.  Downgrade the requested oplock to level II on the -ENOENT
break-timeout path before granting the oplock to the new open.

A break that completes because the previous owner closed its handle from
the oplock break handler must be distinguished from a real timeout.
smb2.oplock.batch7 closes the first handle during the break wait, and the
second open is then expected to be granted the originally requested batch
oplock.  Return -EAGAIN from the non-lease break path when the previous
opener closed during the break wait, recheck sharing in smb_grant_oplock(),
and grant the requested oplock if the close removed the conflict. Real
break timeouts still return -ENOENT and keep the downgrade to level II.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/oplock.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 50cc067696d3..a8e1a08d457e 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1130,7 +1130,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
 
 	ksmbd_debug(OPLOCK, "oplock granted = %d\n", brk_opinfo->level);
 	if (brk_opinfo->op_state == OPLOCK_CLOSING)
-		err = -ENOENT;
+		err = -EAGAIN;
 	wake_up_oplock_break(brk_opinfo);
 
 	return err;
@@ -1434,8 +1434,19 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 	if (prev_durable_detached || (prev_durable_open && err == -ENOENT))
 		ksmbd_invalidate_durable_fd(prev_fid);
 	opinfo_put(prev_opinfo);
-	if (err == -ENOENT)
+	if (err == -EAGAIN) {
+		share_ret = ksmbd_smb_check_shared_mode(fp->filp, fp);
+		if (share_ret < 0) {
+			err = share_ret;
+			goto err_out;
+		}
 		goto set_lev;
+	}
+	if (err == -ENOENT) {
+		if (req_op_level != SMB2_OPLOCK_LEVEL_NONE)
+			req_op_level = SMB2_OPLOCK_LEVEL_II;
+		goto set_lev;
+	}
 	/* Check all oplock was freed by close */
 	else if (err < 0)
 		goto err_out;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 20/29] ksmbd: avoid level II oplock break notification on unlink
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (17 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 19/29] ksmbd: downgrade oplock after break timeout Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 21/29] ksmbd: return oplock protocol error for level II ack Namjae Jeon
                   ` (8 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2_util_unlink() opens the target with FILE_DELETE_ON_CLOSE and then
closes that handle.  Other clients can also mark a file for delete with
SMB2 SET_INFO FileDispositionInformation.

When these unlink paths break existing SMB2 level II oplocks, ksmbd sends
an unsolicited SMB2_OPLOCK_BREAK notification to none.  This races with the
synchronous CREATE or SET_INFO response expected by the client, and
smbtorture reports NT_STATUS_INVALID_NETWORK_RESPONSE while running
smb2.oplock.exclusive2.

SMB2 level II oplock breaks do not require an acknowledgment in the delete
path.  Keep lease handling unchanged, but drop plain SMB2 level II oplocks
locally for unlink requests without sending a break notification.  Normal
write/truncate paths still send the level II to none notification,
preserving the behavior covered by smb2.oplock.levelII500.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/oplock.c  | 31 ++++++++++++++++++++++++-------
 fs/smb/server/oplock.h  |  4 ++++
 fs/smb/server/smb2pdu.c |  4 ++--
 3 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index a8e1a08d457e..23e97bddf28d 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1544,7 +1544,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
  */
 static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
 					 struct ksmbd_file *fp, int is_trunc,
-					 bool send_interim)
+					 bool send_interim, bool send_oplock_break)
 {
 	struct oplock_info *op, *brk_op;
 	struct ksmbd_inode *ci;
@@ -1591,10 +1591,15 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
 			    SMB2_LEASE_KEY_SIZE))
 			goto next;
 		brk_op->open_trunc = is_trunc;
-		oplock_break(brk_op,
-			     brk_op->is_lease && !is_trunc ?
-			     SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE,
-			     send_interim && !sent_interim ? work : NULL);
+		if (!brk_op->is_lease && !send_oplock_break) {
+			brk_op->level = SMB2_OPLOCK_LEVEL_NONE;
+			brk_op->op_state = OPLOCK_STATE_NONE;
+		} else {
+			oplock_break(brk_op,
+				     brk_op->is_lease && !is_trunc ?
+				     SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE,
+				     send_interim && !sent_interim ? work : NULL);
+		}
 		sent_interim = true;
 next:
 		opinfo_put(brk_op);
@@ -1608,7 +1613,19 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
 void smb_break_all_levII_oplock(struct ksmbd_work *work, struct ksmbd_file *fp,
 				int is_trunc)
 {
-	__smb_break_all_levII_oplock(work, fp, is_trunc, true);
+	__smb_break_all_levII_oplock(work, fp, is_trunc, true, true);
+}
+
+void smb_break_all_levII_oplock_no_interim(struct ksmbd_work *work,
+					   struct ksmbd_file *fp, int is_trunc)
+{
+	__smb_break_all_levII_oplock(work, fp, is_trunc, false, true);
+}
+
+void smb_break_all_levII_oplock_for_delete(struct ksmbd_work *work,
+					   struct ksmbd_file *fp)
+{
+	__smb_break_all_levII_oplock(work, fp, 0, false, false);
 }
 
 /**
@@ -1625,7 +1642,7 @@ void smb_break_all_oplock(struct ksmbd_work *work, struct ksmbd_file *fp)
 		return;
 
 	sent_break = smb_break_all_write_oplock(work, fp, 1);
-	__smb_break_all_levII_oplock(work, fp, 1, !sent_break);
+	__smb_break_all_levII_oplock(work, fp, 1, !sent_break, true);
 }
 
 /**
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index 8d1943586246..3f581d22bb67 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -99,6 +99,10 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
 		     struct lease_ctx_info *lctx, int share_ret);
 void smb_break_all_levII_oplock(struct ksmbd_work *work,
 				struct ksmbd_file *fp, int is_trunc);
+void smb_break_all_levII_oplock_no_interim(struct ksmbd_work *work,
+					   struct ksmbd_file *fp, int is_trunc);
+void smb_break_all_levII_oplock_for_delete(struct ksmbd_work *work,
+					   struct ksmbd_file *fp);
 int opinfo_write_to_read(struct oplock_info *opinfo);
 int opinfo_read_handle_to_read(struct oplock_info *opinfo);
 int opinfo_write_to_none(struct oplock_info *opinfo);
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 526d5be97364..97dc24c5c44b 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3809,7 +3809,7 @@ int smb2_open(struct ksmbd_work *work)
 	}
 
 	if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
-		smb_break_all_levII_oplock(work, fp, 0);
+		smb_break_all_levII_oplock_for_delete(work, fp);
 		ksmbd_fd_set_delete_on_close(fp, file_info);
 	}
 
@@ -6796,7 +6796,7 @@ static int set_file_disposition_info(struct ksmbd_work *work,
 		if (S_ISDIR(inode->i_mode) &&
 		    ksmbd_vfs_empty_dir(fp) == -ENOTEMPTY)
 			return -EBUSY;
-		smb_break_all_levII_oplock(work, fp, 0);
+		smb_break_all_levII_oplock_for_delete(work, fp);
 		ksmbd_set_inode_pending_delete(fp);
 	} else {
 		ksmbd_clear_inode_pending_delete(fp);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 21/29] ksmbd: return oplock protocol error for level II ack
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (18 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 20/29] ksmbd: avoid level II oplock break notification on unlink Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 22/29] ksmbd: normalize ungrantable lease states Namjae Jeon
                   ` (7 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

SMB2 level II to none oplock breaks do not require an acknowledgment from
the client.  smb2.oplock.levelii500 intentionally acknowledges such a break
and expects the server to reject it with STATUS_INVALID_OPLOCK_PROTOCOL.

ksmbd drops the local level II oplock to none immediately after sending the
break notification because it does not wait for an ACK.  When the client
then sends the invalid ACK, smb20_oplock_break_ack() sees that the oplock
is not in OPLOCK_ACK_WAIT state and returns STATUS_INVALID_DEVICE_STATE
before checking the current oplock level.

If the oplock is already none when an unexpected SMB2 oplock break ACK
arrives, report STATUS_INVALID_OPLOCK_PROTOCOL.  Keep the existing
STATUS_INVALID_DEVICE_STATE response for other unexpected non-wait states.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 97dc24c5c44b..67a1850d4368 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -9225,7 +9225,10 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
 	if (opinfo->op_state != OPLOCK_ACK_WAIT) {
 		ksmbd_debug(SMB, "unexpected oplock state 0x%x\n",
 			    opinfo->op_state);
-		status = STATUS_INVALID_DEVICE_STATE;
+		if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE)
+			status = STATUS_INVALID_OPLOCK_PROTOCOL;
+		else
+			status = STATUS_INVALID_DEVICE_STATE;
 		goto err_out;
 	}
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 22/29] ksmbd: normalize ungrantable lease states
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (19 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 21/29] ksmbd: return oplock protocol error for level II ack Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 23/29] ksmbd: break handle caching for share conflicts Namjae Jeon
                   ` (6 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.lease.request verifies which SMB2 lease state combinations are granted
by the server.  Requests for H-only, W-only, and HW leases are valid lease
state bitmasks, but they are not grantable combinations and should be
returned as lease state none.

ksmbd only checked that the requested bits were inside the SMB2 lease state
mask.  As a result it could grant H-only, W-only, or HW requests and return
non-zero lease states where the client expects no lease.

Keep the bitmask validation, but normalize ungrantable combinations to zero
before allocating or looking up the lease.  The grantable combinations
remain unchanged: R, RH, RW, and RHW.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/oplock.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 23e97bddf28d..ff9a72236b4d 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -29,6 +29,17 @@ static bool lease_state_valid(__le32 state)
 	return !(state & ~SMB2_LEASE_STATE_MASK_LE);
 }
 
+static __le32 lease_state_grantable(__le32 state)
+{
+	if (state == SMB2_LEASE_READ_CACHING_LE ||
+	    state == (SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE) ||
+	    state == (SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_WRITE_CACHING_LE) ||
+	    state == SMB2_LEASE_STATE_MASK_LE)
+		return state;
+
+	return 0;
+}
+
 static bool lease_v2_flags_valid(__le32 flags)
 {
 	return !(flags & ~SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE);
@@ -1758,6 +1769,7 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
 		if (!lease_state_valid(lreq->req_state) ||
 		    !lease_v2_flags_valid(lreq->flags))
 			goto err_out;
+		lreq->req_state = lease_state_grantable(lreq->req_state);
 		if (lreq->flags == SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE)
 			memcpy(lreq->parent_lease_key, lc->lcontext.ParentLeaseKey,
 			       SMB2_LEASE_KEY_SIZE);
@@ -1775,6 +1787,7 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
 		lreq->duration = lc->lcontext.LeaseDuration;
 		if (!lease_state_valid(lreq->req_state))
 			goto err_out;
+		lreq->req_state = lease_state_grantable(lreq->req_state);
 		lreq->version = 1;
 	} else
 		goto err_out;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 23/29] ksmbd: break handle caching for share conflicts
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (20 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 22/29] ksmbd: normalize ungrantable lease states Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 24/29] ksmbd: break conflicting-open leases only as far as needed Namjae Jeon
                   ` (5 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.lease.break_twice first opens a file with an RHW lease and then tries
a second open with restrictive sharing.  That open must fail with a sharing
violation, but the existing lease should be broken from RHW to RW because
only handle caching conflicts with the requested sharing.

ksmbd used the normal write-cache break calculation for this path, so RHW
was broken to RH.  The following successful open then did not generate the
expected second break from RW to R.

Pass share-conflict context into the lease break helper and, for lease
breaks caused by sharing, drop only SMB2_LEASE_HANDLE_CACHING from the
current lease state.  Other break paths keep the existing write/truncate
break behavior.

A share-conflict break must also remain a single break.  The triggering
open fails with a sharing violation and is never granted, so there is no
target oplock level to converge on.  The lease break retry loop, however,
keeps breaking while the lease level is still above req_op_level, which
broke RHW all the way down to R in one open (lease_break_info.count became
2 instead of 1).  Skip the again loop for share-conflict breaks so the
sharing open produces exactly one RHW->RW break and the later successful
open produces the separate RW->R break the test expects.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/oplock.c | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index ff9a72236b4d..b15c0fb0c0c7 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1035,7 +1035,7 @@ static void wait_lease_breaking(struct oplock_info *opinfo)
 }
 
 static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
-			struct ksmbd_work *in_work)
+			struct ksmbd_work *in_work, bool share_break)
 {
 	int err = 0;
 	bool sent_interim = false;
@@ -1073,6 +1073,10 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
 			 * none.
 			 */
 			lease->new_state = SMB2_LEASE_NONE_LE;
+		} else if (share_break &&
+			   lease->state & SMB2_LEASE_HANDLE_CACHING_LE) {
+			lease->new_state =
+				lease->state & ~SMB2_LEASE_HANDLE_CACHING_LE;
 		} else {
 			if (lease->state & SMB2_LEASE_WRITE_CACHING_LE) {
 				if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
@@ -1121,7 +1125,13 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
 
 		if (wait_ack)
 			wait_lease_breaking(brk_opinfo);
-		if (wait_ack && !err &&
+		/*
+		 * A break caused by a share-mode conflict only drops the
+		 * conflicting caching bit and the triggering open still fails
+		 * with a sharing violation, so it must stay a single break.
+		 * Do not cascade down to req_op_level through the again loop.
+		 */
+		if (wait_ack && !err && !share_break &&
 		    lease_break_needed(brk_opinfo, req_op_level, open_trunc))
 			goto again;
 
@@ -1281,7 +1291,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
 				continue;
 			}
 
-			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL);
+			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
 			opinfo_put(opinfo);
 		}
 	}
@@ -1322,7 +1332,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
 				continue;
 			}
 
-			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL);
+			oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
 			opinfo_put(opinfo);
 		}
 	}
@@ -1441,7 +1451,8 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 		prev_fid = prev_opinfo->fid;
 	}
 
-	err = oplock_break(prev_opinfo, break_level, work);
+	err = oplock_break(prev_opinfo, break_level, work,
+			   share_ret < 0 && prev_opinfo->is_lease);
 	if (prev_durable_detached || (prev_durable_open && err == -ENOENT))
 		ksmbd_invalidate_durable_fd(prev_fid);
 	opinfo_put(prev_opinfo);
@@ -1539,7 +1550,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
 	}
 
 	brk_opinfo->open_trunc = is_trunc;
-	oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work);
+	oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work, false);
 	sent_break = true;
 	opinfo_put(brk_opinfo);
 
@@ -1609,7 +1620,8 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
 			oplock_break(brk_op,
 				     brk_op->is_lease && !is_trunc ?
 				     SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE,
-				     send_interim && !sent_interim ? work : NULL);
+				     send_interim && !sent_interim ? work : NULL,
+				     false);
 		}
 		sent_interim = true;
 next:
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 24/29] ksmbd: break conflicting-open leases only as far as needed
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (21 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 23/29] ksmbd: break handle caching for share conflicts Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 25/29] ksmbd: validate :: stream type against directory create Namjae Jeon
                   ` (4 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.lease.oplock and smb2.lease.breaking1 hold a lease and then issue a
single conflicting open on the same file.  The held lease must break one
step to drop write caching (RWH->RH, RW->R) and then stop, so
lease_break_info.count is 1 and the lease keeps its read/handle caching.

ksmbd instead cascaded the break all the way down to none
(e.g. RWH->RH->R->none), so the break count was 2 or 3 and the reported
lease state ended at 0.  Commit "chain pending lease breaks before waking
waiters" forces break_level to SMB2_OPLOCK_LEVEL_NONE for any non-lease
open against a handle-caching lease, which drives oplock_break()'s retry
loop down to none even when only one open is contending.

Drop that break_level override so a conflicting open breaks a lease only
to its own compatible level (level II, i.e. RH/R).

A deeper break is still required when a truncating open is also waiting
behind the same lease break.  smb2.lease.breaking3 keeps a normal open
pending through RWH->RH and an overwrite open pending behind it, and
expects the lease to continue RH->R->none before either open completes.
The overwrite waiter sets open_trunc on the lease while it blocks on the
pending break, so extend the retry loop to chain another break while that
truncating waiter still needs the lease at none.  The per-break open_trunc
snapshot stays cleared, so the cascade steps down (RH->R->none) instead of
collapsing straight to none, and the normal open stays pending until the
lease is fully broken.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/oplock.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index b15c0fb0c0c7..fa7efd45b8e5 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1126,13 +1126,22 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
 		if (wait_ack)
 			wait_lease_breaking(brk_opinfo);
 		/*
-		 * A break caused by a share-mode conflict only drops the
-		 * conflicting caching bit and the triggering open still fails
-		 * with a sharing violation, so it must stay a single break.
-		 * Do not cascade down to req_op_level through the again loop.
+		 * A share-mode conflict break only drops the conflicting
+		 * caching bit; the triggering open fails with a sharing
+		 * violation, so keep it to a single break.
+		 *
+		 * Otherwise chain another break while the lease is still
+		 * incompatible with this open (req_op_level), or while a
+		 * truncating waiter that arrived during the break still needs
+		 * the lease dropped to none.  open_trunc snapshotted for this
+		 * break stays cleared, so the next state is computed from the
+		 * lease state and the cascade steps down (e.g. RH->R->none)
+		 * instead of collapsing straight to none.
 		 */
 		if (wait_ack && !err && !share_break &&
-		    lease_break_needed(brk_opinfo, req_op_level, open_trunc))
+		    (lease_break_needed(brk_opinfo, req_op_level, open_trunc) ||
+		     (brk_opinfo->open_trunc &&
+		      lease->state != SMB2_LEASE_NONE_LE)))
 			goto again;
 
 		wake_up_oplock_break(brk_opinfo);
@@ -1426,10 +1435,6 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 	prev_op_has_lease = prev_opinfo->is_lease;
 	if (prev_op_has_lease)
 		prev_op_state = prev_opinfo->o_lease->state;
-	if (prev_op_has_lease && !lctx &&
-	    prev_op_state & SMB2_LEASE_HANDLE_CACHING_LE)
-		break_level = SMB2_OPLOCK_LEVEL_NONE;
-
 	if (share_ret < 0 &&
 	    prev_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
 		err = share_ret;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 25/29] ksmbd: validate :: stream type against directory create
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (22 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 24/29] ksmbd: break conflicting-open leases only as far as needed Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 26/29] ksmbd: treat read-control opens as stat opens only for leases Namjae Jeon
                   ` (3 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

smb2.streams.dir opens <dir>::$DATA with FILE_DIRECTORY_FILE and expects
STATUS_NOT_A_DIRECTORY, then opens <dir>::$DATA without it and expects
STATUS_FILE_IS_A_DIRECTORY.

Commit "treat unnamed DATA stream as base file" canonicalizes the ::$DATA
suffix to a NULL stream name so the open continues through the base-file
path. That skipped the stream/directory type validation, which was
guarded by "if (stream_name)", so opening a directory's ::$DATA stream
with FILE_DIRECTORY_FILE incorrectly returned STATUS_OK and a plain open
of it no longer reported STATUS_FILE_IS_A_DIRECTORY.

parse_stream_name() still records the explicit $DATA type in s_type even
when it clears stream_name.  Run the data-stream vs directory validation
whenever s_type is DATA_STREAM, not only when stream_name is set, so the
canonicalized ::$DATA open is rejected with the correct status.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 67a1850d4368..778677ec3361 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3453,7 +3453,14 @@ int smb2_open(struct ksmbd_work *work)
 		rc = 0;
 	}
 
-	if (stream_name) {
+	/*
+	 * An explicit ::$DATA suffix names the unnamed data stream and is
+	 * canonicalized to a NULL stream name (base file), but the request
+	 * still has to be validated against the data-stream type, e.g. opening
+	 * <dir>::$DATA with FILE_DIRECTORY_FILE must fail with
+	 * STATUS_NOT_A_DIRECTORY.
+	 */
+	if (stream_name || s_type == DATA_STREAM) {
 		if (req->CreateOptions & FILE_DIRECTORY_FILE_LE) {
 			if (s_type == DATA_STREAM) {
 				rc = -EIO;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 26/29] ksmbd: treat read-control opens as stat opens only for leases
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (23 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 25/29] ksmbd: validate :: stream type against directory create Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 27/29] ksmbd: start file id allocation at 1 Namjae Jeon
                   ` (2 subsequent siblings)
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

A second open that requests only metadata-level access must not break
the existing caching state. ksmbd already skips the break for such opens
via fp->attrib_only (FILE_READ_ATTRIBUTES,
FILE_WRITE_ATTRIBUTES and FILE_SYNCHRONIZE).

An open requesting only READ_CONTROL (reading the security descriptor)
must be treated differently depending on the existing caching state.
smbtorture smb2.lease.statopen4 expects a read-control open NOT to break
a caching lease, while smb2.oplock.statopen1 expects the same open to
break a batch oplock. So READ_CONTROL is a stat open for leases but not
for oplocks.

Extend the stat-open break-skip in smb_grant_oplock() to also cover a
read-control-only open, but only when the existing holder is a lease.
The global fp->attrib_only flag (used for share-mode, rename and truncate
decisions) is left unchanged so oplock behaviour is preserved.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/oplock.c | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index fa7efd45b8e5..5b5783d75389 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -312,6 +312,18 @@ void opinfo_put(struct oplock_info *opinfo)
 	free_opinfo(opinfo);
 }
 
+static bool ksmbd_inode_has_lease(struct ksmbd_inode *ci)
+{
+	struct oplock_info *opinfo = opinfo_get_list(ci);
+	bool is_lease;
+
+	if (!opinfo)
+		return false;
+	is_lease = opinfo->is_lease;
+	opinfo_put(opinfo);
+	return is_lease;
+}
+
 static void opinfo_add(struct oplock_info *opinfo, struct ksmbd_file *fp)
 {
 	struct ksmbd_inode *ci = fp->f_ci;
@@ -1402,10 +1414,22 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
 	if (!opinfo_count(fp))
 		goto set_lev;
 
-	/* grant none-oplock if second open is trunc */
-	if (fp->attrib_only && fp->cdoption != FILE_OVERWRITE_IF_LE &&
+	/*
+	 * A stat open that only requests metadata access must not break the
+	 * existing caching state. READ_CONTROL (reading the security
+	 * descriptor) does not conflict with a lease, but it does conflict
+	 * with an oplock, so only treat a read-control-only open as a stat
+	 * open when the existing holder is a lease.
+	 */
+	if (fp->cdoption != FILE_OVERWRITE_IF_LE &&
 	    fp->cdoption != FILE_OVERWRITE_LE &&
-	    fp->cdoption != FILE_SUPERSEDE_LE) {
+	    fp->cdoption != FILE_SUPERSEDE_LE &&
+	    (fp->attrib_only ||
+	     (!(fp->daccess & ~(FILE_READ_ATTRIBUTES_LE |
+				FILE_WRITE_ATTRIBUTES_LE |
+				FILE_SYNCHRONIZE_LE |
+				FILE_READ_CONTROL_LE)) &&
+	      ksmbd_inode_has_lease(ci)))) {
 		req_op_level = SMB2_OPLOCK_LEVEL_NONE;
 		goto set_lev;
 	}
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 27/29] ksmbd: start file id allocation at 1
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (24 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 26/29] ksmbd: treat read-control opens as stat opens only for leases Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 28/29] ksmbd: sleep interruptibly in the durable handle scavenger Namjae Jeon
  2026-06-21 12:48 ` [PATCH 29/29] ksmbd: fix UBSAN array-index-out-of-bounds in decode_compress_ctxt() Namjae Jeon
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

ksmbd allocates both the volatile id (per-session file table) and the
persistent id (global file table) with idr_alloc_cyclic() starting at 0.
The first open after the module loads therefore gets volatile id 0 and
persistent id 0, and ksmbd returns an SMB2 FileId of {0, 0} in the create
response.

Clients treat an all-zero FileId as a null handle. smbtorture's
smb2_util_handle_empty() considers {0, 0} empty, so tests that guard the
close with it (e.g. smb2.oplock.statopen1, smb2.lease.statopen*) never
close that first handle. The leaked open keeps the inode's oplock count
non-zero, so a later batch oplock request on the same file is downgraded
to level II and the test fails.

Start the id allocation at 1 (KSMBD_START_FID) so no handle is ever
assigned a {0, 0} FileId, matching the behaviour of other SMB servers.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/vfs_cache.c | 3 ++-
 fs/smb/server/vfs_cache.h | 7 ++++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 11b51320b96e..7495166b0262 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -992,7 +992,8 @@ static int __open_id(struct ksmbd_file_table *ft, struct ksmbd_file *fp,
 
 	idr_preload(KSMBD_DEFAULT_GFP);
 	write_lock(&ft->lock);
-	ret = idr_alloc_cyclic(ft->idr, fp, 0, INT_MAX - 1, GFP_NOWAIT);
+	ret = idr_alloc_cyclic(ft->idr, fp, KSMBD_START_FID, INT_MAX - 1,
+			       GFP_NOWAIT);
 	if (ret >= 0) {
 		id = ret;
 		ret = 0;
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index f85021c11d6e..287f3e675cd3 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -23,7 +23,12 @@
 #define	FILE_GENERIC_WRITE	0x120116
 #define	FILE_GENERIC_EXECUTE	0X1200a0
 
-#define KSMBD_START_FID		0
+/*
+ * Start volatile/persistent file id allocation at 1. A file id of 0 yields an
+ * SMB2 FileId of {0, 0}, which clients (e.g. Windows, Samba) treat as a null
+ * handle and never close, leaking the open on the server.
+ */
+#define KSMBD_START_FID		1
 #define KSMBD_NO_FID		(INT_MAX)
 #define SMB2_NO_FID		(0xFFFFFFFFFFFFFFFFULL)
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 28/29] ksmbd: sleep interruptibly in the durable handle scavenger
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (25 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 27/29] ksmbd: start file id allocation at 1 Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  2026-06-21 12:48 ` [PATCH 29/29] ksmbd: fix UBSAN array-index-out-of-bounds in decode_compress_ctxt() Namjae Jeon
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

The durable handle scavenger kthread waits up to DURABLE_HANDLE_MAX_TIMEOUT
(300 seconds) between scans using wait_event_timeout(), which sleeps in
TASK_UNINTERRUPTIBLE. When there are no durable handles pending expiry the
task stays in D state far longer than 120 seconds, so the hung task
detector prints a bogus "task ksmbd-durable-s blocked for more than 120
seconds" warning with a backtrace, even though the thread is only idle.

Use wait_event_interruptible_timeout() so the thread sleeps in
TASK_INTERRUPTIBLE, which the hung task detector ignores. This also suits
the already-freezable kthread. Treat a negative return (e.g. -ERESTARTSYS)
like a timeout when recomputing the next wake interval.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/vfs_cache.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 7495166b0262..fde22742d193 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -1344,10 +1344,10 @@ static int ksmbd_durable_scavenger(void *dummy)
 		if (try_to_freeze())
 			continue;
 
-		remaining_jiffies = wait_event_timeout(dh_wq,
+		remaining_jiffies = wait_event_interruptible_timeout(dh_wq,
 				   ksmbd_durable_scavenger_alive() == false,
 				   __msecs_to_jiffies(min_timeout));
-		if (remaining_jiffies)
+		if ((long)remaining_jiffies > 0)
 			min_timeout = jiffies_to_msecs(remaining_jiffies);
 		else
 			min_timeout = DURABLE_HANDLE_MAX_TIMEOUT;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH 29/29] ksmbd: fix UBSAN array-index-out-of-bounds in decode_compress_ctxt()
  2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
                   ` (26 preceding siblings ...)
  2026-06-21 12:48 ` [PATCH 28/29] ksmbd: sleep interruptibly in the durable handle scavenger Namjae Jeon
@ 2026-06-21 12:48 ` Namjae Jeon
  27 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-21 12:48 UTC (permalink / raw)
  To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon

decode_compress_ctxt() walks CompressionAlgorithms[] using the client
supplied CompressionAlgorithmCount. That field is declared in
struct smb2_compression_capabilities_context as a fixed 4-element array,
but the number of algorithms is actually variable and clients such as
Windows advertise more than four (e.g. LZ77, LZ77+Huffman, LZNT1,
Pattern_V1 and LZ4).

The on-wire context length is already validated, so the access is within
the received buffer, but indexing the statically sized [4] array makes
UBSAN report an out-of-bounds access:

  UBSAN: array-index-out-of-bounds in smb2pdu.c:1122:48
  index 4 is out of range for type '__le16 [4]'
  Call Trace:
   smb2_handle_negotiate+0xda7/0xde0 [ksmbd]
   ksmbd_smb_negotiate_common+0x27b/0x3e0 [ksmbd]
   smb2_negotiate_request+0x14/0x20 [ksmbd]
   handle_ksmbd_work+0x181/0x500 [ksmbd]

Walk the algorithms through a pointer so the fixed-array bounds check is
not applied, while keeping the existing length validation that bounds the
loop to the data actually received.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 fs/smb/server/smb2pdu.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 778677ec3361..8198590c9345 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1094,6 +1094,7 @@ static __le32 decode_compress_ctxt(struct ksmbd_conn *conn,
 				   int ctxt_len)
 {
 	int alg_cnt, algs_size, i;
+	__le16 *algs;
 
 	if (sizeof(struct smb2_neg_context) + 10 > ctxt_len) {
 		pr_err("Invalid SMB2_COMPRESSION_CAPABILITIES context length\n");
@@ -1118,8 +1119,15 @@ static __le32 decode_compress_ctxt(struct ksmbd_conn *conn,
 		return STATUS_INVALID_PARAMETER;
 	}
 
+	/*
+	 * CompressionAlgorithms[] is declared as a fixed 4-element array, but
+	 * the actual element count is variable (clients such as Windows may
+	 * advertise more). The on-wire length was validated above, so walk the
+	 * algorithms through a pointer to avoid a fixed-array bounds check.
+	 */
+	algs = pneg_ctxt->CompressionAlgorithms;
 	for (i = 0; i < alg_cnt; i++) {
-		__le16 alg = pneg_ctxt->CompressionAlgorithms[i];
+		__le16 alg = algs[i];
 
 		/*
 		 * LZ77 is the required general-purpose codec. Pattern_V1 is an
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [PATCH 17/29] ksmbd: return requested create allocation size
  2026-06-21 12:48 ` [PATCH 17/29] ksmbd: return requested create allocation size Namjae Jeon
@ 2026-06-22 23:01   ` Nathan Chancellor
  2026-06-23  1:28     ` Namjae Jeon
  0 siblings, 1 reply; 31+ messages in thread
From: Nathan Chancellor @ 2026-06-22 23:01 UTC (permalink / raw)
  To: Namjae Jeon; +Cc: linux-cifs, smfrench, senozhatsky, tom, atteh.mailbox

Hi,

On Sun, Jun 21, 2026 at 09:48:32PM +0900, Namjae Jeon wrote:
...
> diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
> index d6001cdce085..cca04bf84b81 100644
> --- a/fs/smb/server/smb2pdu.c
> +++ b/fs/smb/server/smb2pdu.c
...
> @@ -3926,8 +3927,19 @@ int smb2_open(struct ksmbd_work *work)
>  	time = ksmbd_UnixTimeToNT(stat.mtime);
>  	rsp->LastWriteTime = cpu_to_le64(time);
>  	rsp->ChangeTime = cpu_to_le64(fp->change_time);
> -	rsp->AllocationSize = S_ISDIR(stat.mode) ? 0 :
> -		cpu_to_le64(stat.blocks << 9);
> +	/*
> +	 * The cached allocation size hides filesystem rounding for the
> +	 * requested allocation, but it can go stale when the file grows past
> +	 * it via writes (e.g. across a durable reconnect). Refresh it once the
> +	 * file exceeds the cached value, rounding the end of file up to the
> +	 * volume allocation unit (the filesystem block size, matching the
> +	 * SectorsPerAllocationUnit/BytesPerSector ksmbd advertises) rather than
> +	 * using the raw on-disk block count, which can include filesystem
> +	 * preallocation and metadata rounding.
> +	 */
> +	if (!S_ISDIR(stat.mode) && stat.size > fp->allocation_size)
> +		fp->allocation_size = roundup(stat.size, stat.blksize);

This patch is in -next as commit 64ab25180b27 ("ksmbd: return requested
create allocation size"), where it breaks the build for 32-bit
platforms, as stat.size is a loff_t and roundup() performs a plain
division:

  $ cat allno.config
  CONFIG_INET=y
  CONFIG_NET=y
  CONFIG_NETWORK_FILESYSTEMS=y
  CONFIG_SMB_SERVER=y

  $ make -skj"$(nproc)" ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- KCONFIG_ALLCONFIG=1 mrproper allnoconfig vmlinux
  arm-linux-gnueabi-ld: fs/smb/server/smb2pdu.o: in function `smb2_open':
  smb2pdu.c:(.text+0x765a): undefined reference to `__aeabi_ldivmod'
  arm-linux-gnueabi-ld: (__aeabi_ldivmod): Unknown destination type (ARM/Thumb) in fs/smb/server/smb2pdu.o
  smb2pdu.c:(.text+0x765a): dangerous relocation: unsupported relocation
  ...

Is this series intended for 7.2 or 7.3? If it is 7.3, it really
shouldn't be in -next during the merge window...

-- 
Cheers,
Nathan

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH 17/29] ksmbd: return requested create allocation size
  2026-06-22 23:01   ` Nathan Chancellor
@ 2026-06-23  1:28     ` Namjae Jeon
  0 siblings, 0 replies; 31+ messages in thread
From: Namjae Jeon @ 2026-06-23  1:28 UTC (permalink / raw)
  To: Nathan Chancellor; +Cc: linux-cifs, smfrench, senozhatsky, tom, atteh.mailbox

On Tue, Jun 23, 2026 at 8:01 AM Nathan Chancellor <nathan@kernel.org> wrote:
>
> Hi,
>
> On Sun, Jun 21, 2026 at 09:48:32PM +0900, Namjae Jeon wrote:
> ...
> > diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
> > index d6001cdce085..cca04bf84b81 100644
> > --- a/fs/smb/server/smb2pdu.c
> > +++ b/fs/smb/server/smb2pdu.c
> ...
> > @@ -3926,8 +3927,19 @@ int smb2_open(struct ksmbd_work *work)
> >       time = ksmbd_UnixTimeToNT(stat.mtime);
> >       rsp->LastWriteTime = cpu_to_le64(time);
> >       rsp->ChangeTime = cpu_to_le64(fp->change_time);
> > -     rsp->AllocationSize = S_ISDIR(stat.mode) ? 0 :
> > -             cpu_to_le64(stat.blocks << 9);
> > +     /*
> > +      * The cached allocation size hides filesystem rounding for the
> > +      * requested allocation, but it can go stale when the file grows past
> > +      * it via writes (e.g. across a durable reconnect). Refresh it once the
> > +      * file exceeds the cached value, rounding the end of file up to the
> > +      * volume allocation unit (the filesystem block size, matching the
> > +      * SectorsPerAllocationUnit/BytesPerSector ksmbd advertises) rather than
> > +      * using the raw on-disk block count, which can include filesystem
> > +      * preallocation and metadata rounding.
> > +      */
> > +     if (!S_ISDIR(stat.mode) && stat.size > fp->allocation_size)
> > +             fp->allocation_size = roundup(stat.size, stat.blksize);
>
> This patch is in -next as commit 64ab25180b27 ("ksmbd: return requested
> create allocation size"), where it breaks the build for 32-bit
> platforms, as stat.size is a loff_t and roundup() performs a plain
> division:
>
>   $ cat allno.config
>   CONFIG_INET=y
>   CONFIG_NET=y
>   CONFIG_NETWORK_FILESYSTEMS=y
>   CONFIG_SMB_SERVER=y
>
>   $ make -skj"$(nproc)" ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- KCONFIG_ALLCONFIG=1 mrproper allnoconfig vmlinux
>   arm-linux-gnueabi-ld: fs/smb/server/smb2pdu.o: in function `smb2_open':
>   smb2pdu.c:(.text+0x765a): undefined reference to `__aeabi_ldivmod'
>   arm-linux-gnueabi-ld: (__aeabi_ldivmod): Unknown destination type (ARM/Thumb) in fs/smb/server/smb2pdu.o
>   smb2pdu.c:(.text+0x765a): dangerous relocation: unsupported relocation
>   ...
I have fixed it. Thanks for the report:)
>
> Is this series intended for 7.2 or 7.3? If it is 7.3, it really
> shouldn't be in -next during the merge window...
This series is intended for 7.2. Since we just updated our tree, we
expect it to be merged in linux-next today.
Thanks.
>
> --
> Cheers,
> Nathan

^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2026-06-23  1:28 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-21 12:48 [PATCH 01/29] ksmbd: handle missing create contexts for lease opens Namjae Jeon
2026-06-21 12:48 ` [PATCH 02/29] ksmbd: supersede disconnected delete-on-close durable handle Namjae Jeon
2026-06-21 12:48 ` [PATCH 03/29] ksmbd: invalidate durable handles on oplock break Namjae Jeon
2026-06-21 12:48 ` [PATCH 04/29] ksmbd: fix durable reconnect context parsing Namjae Jeon
2026-06-21 12:48 ` [PATCH 05/29] ksmbd: handle durable v2 app instance id Namjae Jeon
2026-06-21 12:48 ` [PATCH 06/29] ksmbd: preserve open change time across rename Namjae Jeon
2026-06-21 12:48 ` [PATCH 07/29] ksmbd: check parent directory sharing conflicts on rename Namjae Jeon
2026-06-21 12:48 ` [PATCH 08/29] ksmbd: deny renaming directory with open children Namjae Jeon
2026-06-21 12:48 ` [PATCH 09/29] ksmbd: propagate failed command status in related compounds Namjae Jeon
2026-06-21 12:48 ` [PATCH 10/29] ksmbd: validate handle for create or get object id Namjae Jeon
2026-06-21 12:48 ` [PATCH 11/29] ksmbd: preserve compound responses for chained errors Namjae Jeon
2026-06-21 12:48 ` [PATCH 12/29] ksmbd: return success for deferred final close Namjae Jeon
2026-06-21 12:48 ` [PATCH 13/29] ksmbd: send pending interim for last compound I/O Namjae Jeon
2026-06-21 12:48 ` [PATCH 14/29] ksmbd: honor stream delete sharing for base file Namjae Jeon
2026-06-21 12:48 ` [PATCH 15/29] ksmbd: reject empty-attribute synchronize-only create Namjae Jeon
2026-06-21 12:48 ` [PATCH 16/29] ksmbd: tighten create file attribute validation Namjae Jeon
2026-06-21 12:48 ` [PATCH 17/29] ksmbd: return requested create allocation size Namjae Jeon
2026-06-22 23:01   ` Nathan Chancellor
2026-06-23  1:28     ` Namjae Jeon
2026-06-21 12:48 ` [PATCH 18/29] ksmbd: apply create security descriptor first Namjae Jeon
2026-06-21 12:48 ` [PATCH 19/29] ksmbd: downgrade oplock after break timeout Namjae Jeon
2026-06-21 12:48 ` [PATCH 20/29] ksmbd: avoid level II oplock break notification on unlink Namjae Jeon
2026-06-21 12:48 ` [PATCH 21/29] ksmbd: return oplock protocol error for level II ack Namjae Jeon
2026-06-21 12:48 ` [PATCH 22/29] ksmbd: normalize ungrantable lease states Namjae Jeon
2026-06-21 12:48 ` [PATCH 23/29] ksmbd: break handle caching for share conflicts Namjae Jeon
2026-06-21 12:48 ` [PATCH 24/29] ksmbd: break conflicting-open leases only as far as needed Namjae Jeon
2026-06-21 12:48 ` [PATCH 25/29] ksmbd: validate :: stream type against directory create Namjae Jeon
2026-06-21 12:48 ` [PATCH 26/29] ksmbd: treat read-control opens as stat opens only for leases Namjae Jeon
2026-06-21 12:48 ` [PATCH 27/29] ksmbd: start file id allocation at 1 Namjae Jeon
2026-06-21 12:48 ` [PATCH 28/29] ksmbd: sleep interruptibly in the durable handle scavenger Namjae Jeon
2026-06-21 12:48 ` [PATCH 29/29] ksmbd: fix UBSAN array-index-out-of-bounds in decode_compress_ctxt() Namjae Jeon

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.