From d6bf101da7dd5d2c2fd6e21de67d4ac43900110e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 17 Jul 2026 12:30:00 +0900 Subject: [PATCH] ksmbd: fix durable V2 persistent handle handling Correct the durable-handle V2 response context layout and use the V2 context size when chaining a following CREATE response context. Validate the only defined DH2Q/DH2C flag, require the reconnect request type to match the saved open type, and process the application instance identifier before durable V2 state. Persistent opens are durable opens as required by MS-SMB2. Permit the durable reconnect path to rebind either type of disconnected open. Signed-off-by: Namjae Jeon --- fs/smb/server/oplock.c | 6 +++--- fs/smb/server/smb2pdu.c | 40 ++++++++++++++++++++++++++++++++------- fs/smb/server/vfs_cache.c | 2 +- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index a534fe6c26b2..591b2fca1d4e 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -2068,12 +2068,12 @@ void create_durable_v2_rsp_buf(char *cc, struct ksmbd_file *fp) struct create_durable_rsp_v2 *buf; buf = (struct create_durable_rsp_v2 *)cc; - memset(buf, 0, sizeof(struct create_durable_rsp)); + memset(buf, 0, sizeof(*buf)); buf->ccontext.DataOffset = cpu_to_le16(offsetof - (struct create_durable_rsp, Data)); + (struct create_durable_rsp_v2, dcontext)); buf->ccontext.DataLength = cpu_to_le32(8); buf->ccontext.NameOffset = cpu_to_le16(offsetof - (struct create_durable_rsp, Name)); + (struct create_durable_rsp_v2, Name)); buf->ccontext.NameLength = cpu_to_le16(4); /* SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2 is "DH2Q" */ buf->Name[0] = 'D'; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index d1cc29ae95a5..2c1418201708 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -3430,6 +3430,7 @@ static int parse_durable_handle_context(struct ksmbd_work *work, case DURABLE_RECONN_V2: { struct create_durable_handle_reconnect_v2 *recon_v2; + u32 flags; if (dh_info->type == DURABLE_RECONN || dh_info->type == DURABLE_REQ_V2) { @@ -3444,6 +3445,12 @@ static int parse_durable_handle_context(struct ksmbd_work *work, } recon_v2 = (struct create_durable_handle_reconnect_v2 *)context; + flags = le32_to_cpu(recon_v2->dcontext.Flags); + if (flags & ~SMB2_DHANDLE_FLAG_PERSISTENT) { + err = -EINVAL; + goto out; + } + dh_info->persistent = flags & SMB2_DHANDLE_FLAG_PERSISTENT; persistent_id = recon_v2->dcontext.Fid.PersistentFileId; dh_info->fp = ksmbd_lookup_durable_fd(persistent_id); if (!dh_info->fp) { @@ -3466,6 +3473,13 @@ static int parse_durable_handle_context(struct ksmbd_work *work, goto out; } + /* A persistent reconnect must match the original open type. */ + if (dh_info->fp->is_persistent != dh_info->persistent) { + err = dh_info->persistent ? -EINVAL : -EBADF; + ksmbd_put_durable_fd(dh_info->fp); + goto out; + } + dh_info->type = dh_idx; dh_info->reconnected = true; ksmbd_debug(SMB, @@ -3529,6 +3543,11 @@ static int parse_durable_handle_context(struct ksmbd_work *work, durable_v2_blob = (struct create_durable_req_v2 *)context; + if (le32_to_cpu(durable_v2_blob->dcontext.Flags) & + ~SMB2_DHANDLE_FLAG_PERSISTENT) { + err = -EINVAL; + goto out; + } ksmbd_debug(SMB, "Request for durable v2 open\n"); dh_info->CreateGuid = durable_v2_blob->dcontext.CreateGuid; dh_info->persistent = @@ -3812,14 +3831,14 @@ int smb2_open(struct ksmbd_work *work) if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) req_op_level = SMB2_OPLOCK_LEVEL_NONE; } + rc = parse_app_instance_id(req, &dh_info); + if (rc) + goto err_out2; rc = parse_durable_handle_context(work, req, lc, &dh_info); if (rc) { 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.replay == true) { fp = dh_info.fp; @@ -4594,10 +4613,15 @@ int smb2_open(struct ksmbd_work *work) if (dh_info.type == DURABLE_REQ_V2 || dh_info.type == DURABLE_REQ) { if (dh_info.type == DURABLE_REQ_V2 && dh_info.persistent && test_share_config_flag(work->tcon->share_conf, - KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY)) - fp->is_persistent = true; - else + KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY) && + (conn->vals->req_capabilities & + SMB2_GLOBAL_CAP_PERSISTENT_HANDLES)) { + /* MS-SMB2 3.3.5.9.10: a persistent open is durable too. */ fp->is_durable = true; + fp->is_persistent = true; + } else { + fp->is_durable = true; + } if (dh_info.type == DURABLE_REQ_V2) { if (dh_info.app_instance_id) memcpy(fp->app_instance_id, @@ -4760,7 +4784,9 @@ int smb2_open(struct ksmbd_work *work) if (next_ptr) *next_ptr = cpu_to_le32(next_off); next_ptr = &durable_ccontext->Next; - next_off = conn->vals->create_durable_size; + next_off = dh_info.type == DURABLE_REQ ? + conn->vals->create_durable_size : + conn->vals->create_durable_v2_size; } if (posix_ctxt) { diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index 08b15f528d88..0a8c3c6e1c81 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -1845,7 +1845,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp) unsigned int old_f_state; write_lock(&global_ft.lock); - if (!fp->is_durable || fp->conn || fp->tcon) { + if ((!fp->is_durable && !fp->is_persistent) || fp->conn || fp->tcon) { write_unlock(&global_ft.lock); pr_err("Invalid durable fd [%p:%p]\n", fp->conn, fp->tcon); return -EBADF;