[PATCH 3/4] ksmbd: add fencing for disconnected persistent handles

From: Yunseong Kim

Date: Sun Aug 23 2026 - 20:57:12 EST


Per MS-SMB2 3.3.5.9, when a non-reconnect CREATE request arrives for a
file that has a disconnected persistent handle from a different client,
the server SHOULD fail the request with STATUS_FILE_NOT_AVAILABLE.

Add ksmbd_has_disconnected_persistent_handle() which checks the inode's
open list for any persistent handle that is disconnected (fp->conn is
NULL) and belongs to a different client (different ClientGUID). The same
client is allowed through so it can reconnect its own handle via DH2C.

This fencing prevents data corruption by blocking conflicting opens
while the original client's persistent handle is preserved for
reconnection.

Signed-off-by: Yunseong Kim <yunseong.kim@xxxxxxxx>
---
fs/smb/server/smb2pdu.c | 13 +++++++++++++
fs/smb/server/vfs_cache.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
fs/smb/server/vfs_cache.h | 2 ++
3 files changed, 60 insertions(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index b4bf36fb7183..36efc3f0bf15 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -4427,6 +4427,19 @@ int smb2_open(struct ksmbd_work *work)
if (!rc) {
file_present = true;

+ /*
+ * MS-SMB2 3.3.5.9: If a non-reconnect CREATE arrives for a
+ * file with a disconnected persistent handle from a different
+ * client, fail with STATUS_FILE_NOT_AVAILABLE.
+ */
+ if (!dh_info.reconnected && !dh_info.replay &&
+ ksmbd_has_disconnected_persistent_handle(path.dentry,
+ conn->ClientGUID)) {
+ rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
+ rc = -EAGAIN;
+ goto err_out;
+ }
+
if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
struct xattr_dos_attrib da;

diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 81626d204249..48762f743789 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -788,6 +788,51 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
return closed;
}

+/**
+ * ksmbd_has_disconnected_persistent_handle() - check if a file has a
+ * disconnected persistent handle that should block new opens (fencing)
+ * @dentry: dentry of the file being opened
+ * @client_guid: ClientGUID of the requesting client (same client may reconnect)
+ *
+ * Per MS-SMB2 3.3.5.9: when a non-reconnect CREATE arrives for a file with
+ * a disconnected persistent handle, the server SHOULD fail with
+ * STATUS_FILE_NOT_AVAILABLE. Exception: same ClientGuid with handle-caching
+ * lease or batch oplock may attempt to break the lease.
+ *
+ * Return: true if a disconnected persistent handle exists from a
+ * different client.
+ */
+bool ksmbd_has_disconnected_persistent_handle(struct dentry *dentry,
+ const char *client_guid)
+{
+ struct ksmbd_inode *ci;
+ struct ksmbd_file *fp;
+ bool found = false;
+
+ ci = ksmbd_inode_lookup_lock(dentry);
+ if (!ci)
+ return false;
+
+ down_read(&ci->m_lock);
+ list_for_each_entry(fp, &ci->m_fp_list, node) {
+ if (!fp->is_persistent)
+ continue;
+ if (fp->conn)
+ continue;
+ if (fp->f_state != FP_INITED)
+ continue;
+ /* Same client can reconnect — don't fence it */
+ if (!memcmp(fp->client_guid, client_guid,
+ SMB2_CLIENT_GUID_SIZE))
+ continue;
+ found = true;
+ break;
+ }
+ up_read(&ci->m_lock);
+ ksmbd_inode_put(ci);
+ return found;
+}
+
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 502efb16f05f..c562ed1c9ee8 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -207,6 +207,8 @@ 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);
+bool ksmbd_has_disconnected_persistent_handle(struct dentry *dentry,
+ const char *client_guid);
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.47.3