summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNamjae Jeon <linkinjeon@kernel.org>2026-07-17 12:30:00 +0900
committerNamjae Jeon <linkinjeon@kernel.org>2026-08-17 15:00:49 +0900
commitd6bf101da7dd5d2c2fd6e21de67d4ac43900110e (patch)
treeaaa33004c6e214e4bff188be77110c9cb848dd4e
parentbc2f3f3dd69424f76e2ed3dc53d29bfd6c9966d4 (diff)
downloadlinux-d6bf101da7dd5d2c2fd6e21de67d4ac43900110e.tar.gz
linux-d6bf101da7dd5d2c2fd6e21de67d4ac43900110e.zip
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 <linkinjeon@kernel.org>
-rw-r--r--fs/smb/server/oplock.c6
-rw-r--r--fs/smb/server/smb2pdu.c38
-rw-r--r--fs/smb/server/vfs_cache.c2
3 files changed, 36 insertions, 10 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))
+ 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
+ } 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;