* [PATCH] ksmbd: implement SMB2 AppInstanceVersion takeover
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: notify parent directory leases on child create Namjae Jeon
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
BVT_AppInstanceVersion_SMB311_GreaterVersion,
BVT_AppInstanceVersion_SMB311_SameVersion,
BVT_AppInstanceVersion_SMB311_LowerAppInstanceVersionHigh, and
BVT_AppInstanceVersion_SMB311_LowerAppInstanceVersionLow exercise
ordered opens using the same AppInstanceId. ksmbd tracked the
AppInstanceId, but did not parse the version context or enforce the
version ordering, so versioned opens returned incorrect sharing
violations.
Parse and retain the 24-byte AppInstanceVersion context with each open.
Reject a version that is lower than or equal to the active version with
STATUS_FILE_FORCED_CLOSED, reject an unversioned open against a versioned
handle, and close the previous handle for a newer takeover. Do not apply
the takeover check to durable reconnect or replay requests.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/smb2pdu.c | 98 +++++++++++++++++++++++++++++++++++++--
fs/smb/server/vfs_cache.c | 7 ++-
fs/smb/server/vfs_cache.h | 5 ++
3 files changed, 101 insertions(+), 9 deletions(-)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index bd806dca9dcd..da461535229a 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -16,6 +16,7 @@
#include <linux/filelock.h>
#include <linux/fileattr.h>
#include <linux/timekeeping.h>
+#include <linux/unaligned.h>
#include "glob.h"
#include "../common/smbfsctl.h"
@@ -3451,9 +3452,12 @@ struct durable_info {
bool replay;
bool replay_consumed;
bool app_instance_id;
+ bool app_instance_version_valid;
unsigned int timeout;
char *CreateGuid;
char AppInstanceId[SMB2_CREATE_GUID_SIZE];
+ u64 app_instance_version_high;
+ u64 app_instance_version_low;
};
static int smb2_check_durable_replay(struct ksmbd_work *work,
@@ -3819,6 +3823,68 @@ static int parse_app_instance_id(struct smb2_create_req *req,
return 0;
}
+static int parse_app_instance_version(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_VERSION,
+ SMB2_CREATE_GUID_SIZE);
+ if (IS_ERR(context))
+ return PTR_ERR(context);
+ if (!context)
+ return 0;
+
+ if (le32_to_cpu(context->DataLength) < 24)
+ return -EINVAL;
+
+ data = (char *)context + le16_to_cpu(context->DataOffset);
+ if (get_unaligned_le16(data) != 24 ||
+ get_unaligned_le16(data + 2) != 0)
+ return -EINVAL;
+
+ dh_info->app_instance_version_high = get_unaligned_le64(data + 8);
+ dh_info->app_instance_version_low = get_unaligned_le64(data + 16);
+ dh_info->app_instance_version_valid = true;
+ return 0;
+}
+
+static int smb2_handle_app_instance_id(struct smb2_create_rsp *rsp,
+ struct durable_info *dh_info)
+{
+ struct ksmbd_file *old_fp;
+ bool reject = false;
+
+ if (!dh_info->app_instance_id)
+ return 0;
+
+ old_fp = ksmbd_lookup_fd_app_instance_id(dh_info->AppInstanceId);
+ if (!old_fp)
+ return 0;
+
+ if (dh_info->app_instance_version_valid) {
+ if (old_fp->app_instance_version_valid &&
+ (dh_info->app_instance_version_high <
+ old_fp->app_instance_version_high ||
+ (dh_info->app_instance_version_high ==
+ old_fp->app_instance_version_high &&
+ dh_info->app_instance_version_low <=
+ old_fp->app_instance_version_low)))
+ reject = true;
+ } else if (old_fp->app_instance_version_valid) {
+ reject = true;
+ }
+
+ ksmbd_put_durable_fd(old_fp);
+ if (reject) {
+ rsp->hdr.Status = STATUS_FILE_FORCED_CLOSED;
+ return -EIO;
+ }
+
+ return ksmbd_close_fd_app_instance_id(dh_info->AppInstanceId);
+}
+
/**
* smb2_open() - handler for smb file open request
* @work: smb work containing request buffer
@@ -3946,6 +4012,15 @@ int smb2_open(struct ksmbd_work *work)
req_op_level = req->RequestedOplockLevel;
+ if (req->CreateContextsOffset) {
+ rc = parse_app_instance_id(req, &dh_info);
+ if (rc)
+ goto err_out2;
+ rc = parse_app_instance_version(req, &dh_info);
+ if (rc)
+ goto err_out2;
+ }
+
if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE &&
req->CreateContextsOffset) {
lc = parse_lease_state(req);
@@ -3960,9 +4035,6 @@ 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");
@@ -4010,8 +4082,6 @@ 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)) {
@@ -4026,6 +4096,13 @@ int smb2_open(struct ksmbd_work *work)
}
}
+ if (dh_info.app_instance_id && !dh_info.reconnected &&
+ !dh_info.replay) {
+ rc = smb2_handle_app_instance_id(rsp, &dh_info);
+ if (rc)
+ goto err_out2;
+ }
+
if (le32_to_cpu(req->ImpersonationLevel) > le32_to_cpu(IL_DELEGATE)) {
pr_err("Invalid impersonationlevel : 0x%x\n",
le32_to_cpu(req->ImpersonationLevel));
@@ -4426,6 +4503,17 @@ int smb2_open(struct ksmbd_work *work)
* waiting on the same break again.
*/
memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+ if (dh_info.app_instance_id) {
+ memcpy(fp->app_instance_id, dh_info.AppInstanceId,
+ SMB2_CREATE_GUID_SIZE);
+ fp->has_app_instance_id = true;
+ }
+ if (dh_info.app_instance_version_valid) {
+ fp->app_instance_version_high =
+ dh_info.app_instance_version_high;
+ fp->app_instance_version_low = dh_info.app_instance_version_low;
+ fp->app_instance_version_valid = true;
+ }
if (dh_info.CreateGuid) {
memcpy(fp->create_guid, dh_info.CreateGuid, SMB2_CREATE_GUID_SIZE);
fp->durable_replay_consumed = dh_info.replay_consumed;
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 972e8985a503..413997f393f0 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -995,16 +995,15 @@ 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 *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 (!fp->has_app_instance_id)
+ continue;
if (!memcmp(fp->app_instance_id, app_instance_id,
SMB2_CREATE_GUID_SIZE)) {
fp = ksmbd_fp_get(fp);
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 5cac022b540b..502efb16f05f 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -149,6 +149,10 @@ struct ksmbd_file {
bool is_durable;
bool is_persistent;
bool is_resilient;
+ bool has_app_instance_id;
+ bool app_instance_version_valid;
+ u64 app_instance_version_high;
+ u64 app_instance_version_low;
bool durable_reconnect_disabled;
bool durable_replay_consumed;
@@ -209,6 +213,7 @@ 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);
+struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id);
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] 12+ messages in thread* [PATCH] ksmbd: notify parent directory leases on child create
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: implement SMB2 AppInstanceVersion takeover Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: add per-share SMB3 encryption enforcement Namjae Jeon
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
BVT_DirectoryLeasing_ReadWriteHandleCaching requires a parent directory
lease break notification when another client creates a child in the
leased directory. A child CREATE without a lease context did not notify
the parent lease holders because the notification path expected a
non-NULL lease context.
Allow the parent lease notification helper to handle a NULL child lease
context and notify matching parent leases. Invoke it after a child is
created without a lease context while preserving the existing lease-key
filtering for requests that provide one.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/oplock.c | 9 +++++----
fs/smb/server/smb2pdu.c | 3 +++
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 5fc2c79881d1..58af0fddf39f 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1450,7 +1450,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
struct ksmbd_inode *p_ci = NULL;
LIST_HEAD(brk_list);
- if (lctx->version != 2)
+ if (lctx && lctx->version != 2)
return;
p_ci = ksmbd_inode_lookup_lock(fp->filp->f_path.dentry->d_parent);
@@ -1463,9 +1463,10 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
continue;
if (opinfo->o_lease->state != SMB2_OPLOCK_LEVEL_NONE &&
- (!(lctx->flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE) ||
- !compare_guid_key(opinfo, fp->conn->ClientGUID,
- lctx->parent_lease_key))) {
+ (!lctx ||
+ (!(lctx->flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE) ||
+ !compare_guid_key(opinfo, fp->conn->ClientGUID,
+ lctx->parent_lease_key)))) {
if (!atomic_inc_not_zero(&opinfo->refcount))
continue;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index da461535229a..bcf4e8e1ca22 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -4663,6 +4663,9 @@ int smb2_open(struct ksmbd_work *work)
goto err_out1;
}
} else {
+ if (created && !lc)
+ smb_send_parent_lease_break_noti(fp, NULL);
+
if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE && lc) {
if (S_ISDIR(file_inode(filp)->i_mode)) {
lc->req_state &= ~SMB2_LEASE_WRITE_CACHING_LE;
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: add per-share SMB3 encryption enforcement
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: implement SMB2 AppInstanceVersion takeover Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: notify parent directory leases on child create Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: fix encrypted request lookup on bound channels Namjae Jeon
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
Add a share flag for requiring SMB3 encryption on an individual share.
Advertise SMB2_SHAREFLAG_ENCRYPT_DATA in TREE_CONNECT responses and
reject both unencrypted TREE_CONNECT attempts and plaintext requests for
shares carrying the flag.
Keep BIT(19) reserved for the existing ksmbd-tools WIDE_LINKS flag and
use BIT(20) for the new netlink ABI flag.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/common/smb2pdu.h | 3 ++-
fs/smb/server/ksmbd_netlink.h | 2 ++
fs/smb/server/mgmt/share_config.c | 1 +
fs/smb/server/server.c | 11 +++++++++++
fs/smb/server/smb2pdu.c | 22 ++++++++++++++++++----
5 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h
index c1414a1ffe30..d9650aff0d3c 100644
--- a/fs/smb/common/smb2pdu.h
+++ b/fs/smb/common/smb2pdu.h
@@ -370,7 +370,8 @@ struct smb2_tree_connect_req {
#define SMB2_SHAREFLAG_FORCE_LEVELII_OPLOCK 0x00001000
#define SMB2_SHAREFLAG_ENABLE_HASH_V1 0x00002000
#define SMB2_SHAREFLAG_ENABLE_HASH_V2 0x00004000
-#define SHI1005_FLAGS_ENCRYPT_DATA 0x00008000
+#define SMB2_SHAREFLAG_ENCRYPT_DATA 0x00008000
+#define SHI1005_FLAGS_ENCRYPT_DATA SMB2_SHAREFLAG_ENCRYPT_DATA
#define SMB2_SHAREFLAG_IDENTITY_REMOTING 0x00040000 /* 3.1.1 */
#define SMB2_SHAREFLAG_COMPRESS_DATA 0x00100000 /* 3.1.1 */
#define SMB2_SHAREFLAG_ISOLATED_TRANSPORT 0x00200000
diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index af1e760453d9..2673522c76bc 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -381,6 +381,8 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY BIT(16)
#define KSMBD_SHARE_FLAG_HIDE_UNREADABLE BIT(17)
#define KSMBD_SHARE_FLAG_TIME_MACHINE BIT(18)
+/* Keep BIT(19) reserved for the existing ksmbd-tools WIDE_LINKS flag. */
+#define KSMBD_SHARE_FLAG_ENCRYPT_DATA BIT(20)
/*
* Tree connect request flags.
diff --git a/fs/smb/server/mgmt/share_config.c b/fs/smb/server/mgmt/share_config.c
index 53d6f71dd871..9edb2fe08812 100644
--- a/fs/smb/server/mgmt/share_config.c
+++ b/fs/smb/server/mgmt/share_config.c
@@ -47,6 +47,7 @@ static const struct ksmbd_const_name ksmbd_share_flag_names[] = {
{KSMBD_SHARE_FLAG_UPDATE, "update"},
{KSMBD_SHARE_FLAG_CROSSMNT, "crossmnt"},
{KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY, "continuous-availability"},
+ {KSMBD_SHARE_FLAG_ENCRYPT_DATA, "encrypt-data"},
};
static int proc_show_shares(struct seq_file *m, void *v)
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index d619d1f22601..7881fc7bb8cd 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -24,6 +24,8 @@
#include "auth.h"
#include "stats.h"
#include "compress.h"
+#include "mgmt/share_config.h"
+#include "mgmt/tree_connect.h"
int ksmbd_debug_types;
@@ -236,6 +238,15 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
STATUS_NETWORK_NAME_DELETED);
goto send;
}
+
+ if (work->tcon &&
+ test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_ENCRYPT_DATA) &&
+ !work->encrypted) {
+ conn->ops->set_rsp_status(work,
+ STATUS_ACCESS_DENIED);
+ goto send;
+ }
}
}
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index bcf4e8e1ca22..aa662adaf63d 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2636,12 +2636,22 @@ int smb2_tree_connect(struct ksmbd_work *work)
name, treename);
status = ksmbd_tree_conn_connect(work, name);
- if (status.ret == KSMBD_TREE_CONN_STATUS_OK)
+ if (status.ret == KSMBD_TREE_CONN_STATUS_OK) {
rsp->hdr.Id.SyncId.TreeId = cpu_to_le32(status.tree_conn->id);
- else
+ share = status.tree_conn->share_conf;
+
+ /* A share that requires encryption needs a negotiated SMB3 cipher. */
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_ENCRYPT_DATA) &&
+ !smb3_encryption_negotiated(conn)) {
+ ksmbd_tree_conn_disconnect(sess, status.tree_conn);
+ status.tree_conn = NULL;
+ share = NULL;
+ status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
+ goto out_err1;
+ }
+ } else
goto out_err1;
- share = status.tree_conn->share_conf;
if (test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
ksmbd_debug(SMB, "IPC share path request\n");
rsp->ShareType = SMB2_SHARE_TYPE_PIPE;
@@ -2687,9 +2697,13 @@ int smb2_tree_connect(struct ksmbd_work *work)
conn->compress_algorithm != SMB3_COMPRESS_NONE)
rsp->ShareFlags |= cpu_to_le32(SMB2_SHAREFLAG_COMPRESS_DATA);
if (share && test_share_config_flag(share,
- KSMBD_SHARE_FLAG_HIDE_UNREADABLE))
+ KSMBD_SHARE_FLAG_HIDE_UNREADABLE))
rsp->ShareFlags |=
cpu_to_le32(SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM);
+ if (share && test_share_config_flag(share,
+ KSMBD_SHARE_FLAG_ENCRYPT_DATA))
+ rsp->ShareFlags |=
+ cpu_to_le32(SMB2_SHAREFLAG_ENCRYPT_DATA);
rc = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_tree_connect_rsp));
if (rc) {
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: fix encrypted request lookup on bound channels
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
` (2 preceding siblings ...)
2026-08-17 12:15 ` [PATCH] ksmbd: add per-share SMB3 encryption enforcement Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: scope session state changes to bound connections Namjae Jeon
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
An SMB3 multichannel binding registers the secondary connection in the
session channel list, but does not insert the session into the secondary
connection's session xarray.
The decryption path only searches the connection-local xarray. As a
result, every encrypted request received on a bound channel fails with
"Could not get decryption key".
Use the channel-aware session lookup for decryption. Also stop using the
temporary conn->binding flag to decide whether the global lookup is
allowed. Validate the permanent channel association under chann_lock
instead.
Fixes: f5a544e3bab7 ("ksmbd: add support for SMB3 multichannel")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/auth.c | 2 +-
fs/smb/server/mgmt/user_session.c | 11 +++++++++--
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 2f89af029247..bcd371f5550d 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -729,7 +729,7 @@ static int ksmbd_get_encryption_key(struct ksmbd_work *work, __u64 ses_id,
* that the command can reach the session setup handler. Other
* commands are rejected there with STATUS_NETWORK_SESSION_EXPIRED.
*/
- sess = ksmbd_session_lookup(work->conn, ses_id);
+ sess = ksmbd_session_lookup_all_states(work->conn, ses_id);
if (sess && sess->state != SMB2_SESSION_VALID &&
(sess->state != SMB2_SESSION_EXPIRED ||
!sess->kerberos_expiry)) {
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index f4675c457714..31eccad5d732 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -551,11 +551,18 @@ struct ksmbd_session *ksmbd_session_lookup_all_states(struct ksmbd_conn *conn,
unsigned long long id)
{
struct ksmbd_session *sess;
+ bool channel_found;
sess = ksmbd_session_lookup(conn, id);
- if (!sess && conn->binding) {
+ if (!sess) {
sess = ksmbd_session_lookup_slowpath(id);
- if (sess && !xa_load(&sess->ksmbd_chann_list, (long)conn)) {
+ if (!sess)
+ return NULL;
+
+ down_read(&sess->chann_lock);
+ channel_found = xa_load(&sess->ksmbd_chann_list, (long)conn);
+ up_read(&sess->chann_lock);
+ if (!channel_found) {
ksmbd_user_session_put(sess);
sess = NULL;
}
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: scope session state changes to bound connections
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
` (3 preceding siblings ...)
2026-08-17 12:15 ` [PATCH] ksmbd: fix encrypted request lookup on bound channels Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: encrypt interim responses to encrypted requests Namjae Jeon
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
ksmbd_all_conn_set_status() treats every connection whose transient
binding flag is set as belonging to the target SessionId. A logoff or
session replacement can consequently move an unrelated connection to
NEED_RECONNECT or NEED_SETUP.
Pass the target session itself and select connections using either the
connection-local session xarray or the session's permanent channel list.
Use the same association test while waiting for requests to drain.
Serialize session-wide status changes under request_lock and do not
overwrite EXITING or RELEASING. Protect the shutdown transition with the
same lock so a concurrent session update cannot revive a closing
connection.
Fixes: f5a544e3bab7 ("ksmbd: add support for SMB3 multichannel")
Fixes: abcc506a9a71 ("ksmbd: fix racy issue from smb2 close and logoff with multichannel")
Fixes: c444139cb747 ("ksmbd: rewrite stop_sessions() with restartable iteration")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/connection.c | 37 ++++++++++++++++++++++++++-----
fs/smb/server/connection.h | 6 +++--
fs/smb/server/mgmt/user_session.c | 8 +++----
fs/smb/server/smb2pdu.c | 6 ++---
4 files changed, 41 insertions(+), 16 deletions(-)
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index e225aca67686..71d55d903f6f 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -404,15 +404,37 @@ void ksmbd_conn_unlock(struct ksmbd_conn *conn)
mutex_unlock(&conn->srv_mutex);
}
-void ksmbd_all_conn_set_status(u64 sess_id, u32 status)
+static bool ksmbd_session_is_bound_to_conn(struct ksmbd_session *sess,
+ struct ksmbd_conn *conn)
+{
+ bool found;
+
+ rcu_read_lock();
+ found = xa_load(&conn->sessions, sess->id) == sess;
+ rcu_read_unlock();
+ if (found)
+ return true;
+
+ down_read(&sess->chann_lock);
+ found = xa_load(&sess->ksmbd_chann_list, (long)conn);
+ up_read(&sess->chann_lock);
+ return found;
+}
+
+void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status)
{
struct ksmbd_conn *conn;
int bkt;
down_read(&conn_list_lock);
hash_for_each(conn_list, bkt, conn, hlist) {
- if (conn->binding || xa_load(&conn->sessions, sess_id))
- WRITE_ONCE(conn->status, status);
+ if (ksmbd_session_is_bound_to_conn(sess, conn)) {
+ spin_lock(&conn->request_lock);
+ if (!ksmbd_conn_exiting(conn) &&
+ !ksmbd_conn_releasing(conn))
+ WRITE_ONCE(conn->status, status);
+ spin_unlock(&conn->request_lock);
+ }
}
up_read(&conn_list_lock);
}
@@ -422,7 +444,8 @@ void ksmbd_conn_wait_idle(struct ksmbd_conn *conn)
wait_event(conn->req_running_q, atomic_read(&conn->req_running) < 2);
}
-int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id)
+int ksmbd_conn_wait_idle_sess(struct ksmbd_conn *curr_conn,
+ struct ksmbd_session *sess)
{
struct ksmbd_conn *conn;
int rc, retry_count = 0, max_timeout = 120;
@@ -434,7 +457,7 @@ int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id)
down_read(&conn_list_lock);
hash_for_each(conn_list, bkt, conn, hlist) {
- if (conn->binding || xa_load(&conn->sessions, sess_id)) {
+ if (ksmbd_session_is_bound_to_conn(sess, conn)) {
rcount = (conn == curr_conn) ? 2 : 1;
if (atomic_read(&conn->req_running) >= rcount) {
rc = wait_event_timeout(conn->req_running_q,
@@ -780,8 +803,10 @@ static void stop_sessions(void)
* handler exited its receive loop for an unrelated
* reason).
*/
- if (READ_ONCE(conn->status) != KSMBD_SESS_RELEASING)
+ spin_lock(&conn->request_lock);
+ if (!ksmbd_conn_releasing(conn))
ksmbd_conn_set_exiting(conn);
+ spin_unlock(&conn->request_lock);
target = conn;
break;
}
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index 9ca03f9774d3..c01ccbe8b97c 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -23,6 +23,7 @@
#include "ksmbd_work.h"
struct smbdirect_buffer_descriptor_v1;
+struct ksmbd_session;
#define KSMBD_SOCKET_BACKLOG 16
@@ -196,7 +197,8 @@ extern struct rw_semaphore conn_list_lock;
bool ksmbd_conn_alive(struct ksmbd_conn *conn);
void ksmbd_conn_wait_idle(struct ksmbd_conn *conn);
-int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id);
+int ksmbd_conn_wait_idle_sess(struct ksmbd_conn *curr_conn,
+ struct ksmbd_session *sess);
struct ksmbd_conn *ksmbd_conn_alloc(void);
void ksmbd_conn_free(struct ksmbd_conn *conn);
struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn);
@@ -310,5 +312,5 @@ static inline void ksmbd_conn_set_releasing(struct ksmbd_conn *conn)
WRITE_ONCE(conn->status, KSMBD_SESS_RELEASING);
}
-void ksmbd_all_conn_set_status(u64 sess_id, u32 status);
+void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status);
#endif /* __CONNECTION_H__ */
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index 31eccad5d732..7e187d20828b 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -647,17 +647,17 @@ void destroy_previous_session(struct ksmbd_conn *conn,
memcmp(user->passkey, prev_user->passkey, user->passkey_sz))
goto out;
- ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_RECONNECT);
- err = ksmbd_conn_wait_idle_sess_id(conn, id);
+ ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_RECONNECT);
+ err = ksmbd_conn_wait_idle_sess(conn, prev_sess);
if (err) {
- ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP);
+ ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP);
goto out;
}
ksmbd_destroy_file_table(prev_sess);
prev_sess->kerberos_expiry = 0;
prev_sess->state = SMB2_SESSION_EXPIRED;
- ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP);
+ ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP);
ksmbd_launch_ksmbd_durable_scavenger();
out:
up_write(&conn->session_lock);
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index aa662adaf63d..6581c79635fa 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2899,7 +2899,6 @@ int smb2_session_logoff(struct ksmbd_work *work)
struct ksmbd_session *sess = work->sess;
struct smb2_logoff_req *req;
struct smb2_logoff_rsp *rsp;
- u64 sess_id;
int err;
WORK_BUFFERS(work, req, rsp);
@@ -2913,8 +2912,7 @@ int smb2_session_logoff(struct ksmbd_work *work)
smb2_set_err_rsp(work);
return -ENOENT;
}
- sess_id = le64_to_cpu(req->hdr.SessionId);
- ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_RECONNECT);
+ ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_RECONNECT);
ksmbd_conn_unlock(conn);
ksmbd_close_session_fds(work);
@@ -2932,7 +2930,7 @@ int smb2_session_logoff(struct ksmbd_work *work)
sess->state = SMB2_SESSION_EXPIRED;
up_write(&conn->session_lock);
- ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_SETUP);
+ ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_SETUP);
rsp->StructureSize = cpu_to_le16(4);
err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_logoff_rsp));
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: encrypt interim responses to encrypted requests
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
` (4 preceding siblings ...)
2026-08-17 12:15 ` [PATCH] ksmbd: scope session state changes to bound connections Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: disconnect on SMB3 decryption failure Namjae Jeon
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
The normal response path applies an SMB3 transform when the request was
encrypted. Async interim responses, completed compound prefixes and two
CHANGE_NOTIFY cleanup paths write their synthetic response work directly,
bypassing that encryption step.
A packet capture shows FE SMB2 STATUS_PENDING, CREATE and CHANGE_NOTIFY
responses following FD SMB3 requests. The client resets the connection
immediately after receiving those plaintext responses.
Send synthetic interim work through a common helper that applies the
session encryption transform first. A compound prefix shares the original
work's response iov, which encryption would replace in place, so flatten it
into an independently owned work before encrypting and sending it.
Fixes: 64bfa9d49026 ("smb/server: use MSG_EOR for async interim response")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/smb2pdu.c | 71 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 67 insertions(+), 4 deletions(-)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 6581c79635fa..ade16532a8c1 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1124,6 +1124,66 @@ void release_async_work(struct ksmbd_work *work)
}
}
+static int smb2_send_interim_work(struct ksmbd_work *in_work,
+ struct ksmbd_work *work, bool eor)
+{
+ int err = 0;
+
+ in_work->encrypted = work->encrypted;
+ if (work->encrypted && work->sess && work->sess->enc &&
+ work->conn->ops->encrypt_resp) {
+ in_work->sess = work->sess;
+ err = work->conn->ops->encrypt_resp(in_work);
+ in_work->sess = NULL;
+ }
+ if (err)
+ return err;
+
+ return eor ? ksmbd_conn_write_eor(in_work) :
+ ksmbd_conn_write(in_work);
+}
+
+static int smb2_send_interim_prefix_work(struct ksmbd_work *work)
+{
+ struct ksmbd_work *in_work;
+ unsigned int len, copied = 0;
+ char *dst;
+ int err = -ENOMEM;
+ int i;
+
+ len = get_rfc1002_len(work->iov[0].iov_base);
+ in_work = ksmbd_alloc_work_struct();
+ if (!in_work)
+ return err;
+
+ in_work->response_buf = kvzalloc(len + 4, KSMBD_DEFAULT_GFP);
+ if (!in_work->response_buf)
+ goto out;
+ in_work->response_sz = len + 4;
+ in_work->conn = work->conn;
+ dst = in_work->response_buf + 4;
+ for (i = 1; i <= work->iov_idx; i++) {
+ if (work->iov[i].iov_len > len - copied) {
+ err = -EINVAL;
+ goto out;
+ }
+ memcpy(dst + copied, work->iov[i].iov_base,
+ work->iov[i].iov_len);
+ copied += work->iov[i].iov_len;
+ }
+ if (copied != len) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ err = ksmbd_iov_pin_rsp(in_work, dst, len);
+ if (!err)
+ err = smb2_send_interim_work(in_work, work, true);
+out:
+ ksmbd_free_work_struct(in_work);
+ return err;
+}
+
static void smb2_send_interim_compound_prefix(struct ksmbd_work *work)
{
struct smb2_hdr *req_hdr;
@@ -1152,7 +1212,7 @@ static void smb2_send_interim_compound_prefix(struct ksmbd_work *work)
work->conn->ops->set_sign_rsp)
work->conn->ops->set_sign_rsp(work);
- err = ksmbd_conn_write_eor(work);
+ err = smb2_send_interim_prefix_work(work);
if (err)
ksmbd_debug(SMB, "failed to send compound interim prefix: %d\n",
err);
@@ -1193,7 +1253,8 @@ void smb2_send_interim_resp(struct ksmbd_work *work, __le32 status)
smb2_set_err_rsp(in_work);
rsp_hdr->Status = status;
- ksmbd_conn_write_eor(in_work);
+ if (smb2_send_interim_work(in_work, work, true))
+ ksmbd_debug(SMB, "failed to send interim response\n");
ksmbd_free_work_struct(in_work);
}
@@ -11310,7 +11371,8 @@ int smb2_notify(struct ksmbd_work *work)
in_work->async_id = work->async_id;
work->async_id = 0;
release_async_work(work);
- ksmbd_conn_write(in_work);
+ if (smb2_send_interim_work(in_work, work, false))
+ ksmbd_debug(SMB, "failed to send notify cleanup\n");
ksmbd_free_work_struct(in_work);
work->send_no_response = 1;
return 0;
@@ -11415,7 +11477,8 @@ int smb2_notify(struct ksmbd_work *work)
in_work->cancel_fn = NULL;
in_work->asynchronous = false;
ksmbd_fd_put(work, fp);
- ksmbd_conn_write(in_work);
+ if (smb2_send_interim_work(in_work, work, false))
+ ksmbd_debug(SMB, "failed to send notify cleanup\n");
ksmbd_free_work_struct(in_work);
work->send_no_response = 1;
return 0;
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: disconnect on SMB3 decryption failure
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
` (5 preceding siblings ...)
2026-08-17 12:15 ` [PATCH] ksmbd: encrypt interim responses to encrypted requests Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: decrypt requests from expired encrypted sessions Namjae Jeon
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
MS-SMB2 requires the server to disconnect a connection when an
encrypted transform cannot be associated with a session or fails
authenticated decryption. This includes an encrypted request that
still carries a SessionId invalidated through PreviousSessionId.
Move the connection to EXITING and shut down its transport when
decrypt_req() fails. Add the missing TCP shutdown callback so a receive
blocked in kernel_recvmsg() is released; SMB Direct already provides
the corresponding callback.
Plaintext requests using an invalidated SessionId do not take this
path and continue to receive STATUS_USER_SESSION_DELETED.
Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/connection.c | 16 ++++++++++++++++
fs/smb/server/connection.h | 1 +
fs/smb/server/server.c | 4 +++-
fs/smb/server/transport_tcp.c | 6 ++++++
4 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 71d55d903f6f..5d729473dd18 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -439,6 +439,22 @@ void ksmbd_all_conn_set_status(struct ksmbd_session *sess, u32 status)
up_read(&conn_list_lock);
}
+void ksmbd_conn_abort(struct ksmbd_conn *conn)
+{
+ bool shutdown = false;
+
+ spin_lock(&conn->request_lock);
+ if (!ksmbd_conn_exiting(conn) && !ksmbd_conn_releasing(conn)) {
+ ksmbd_conn_set_exiting(conn);
+ shutdown = true;
+ }
+ spin_unlock(&conn->request_lock);
+ wake_up_all(&conn->req_running_q);
+
+ if (shutdown && conn->transport->ops->shutdown)
+ conn->transport->ops->shutdown(conn->transport);
+}
+
void ksmbd_conn_wait_idle(struct ksmbd_conn *conn)
{
wait_event(conn->req_running_q, atomic_read(&conn->req_running) < 2);
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index c01ccbe8b97c..421907aed473 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -203,6 +203,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void);
void ksmbd_conn_free(struct ksmbd_conn *conn);
struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn);
void ksmbd_conn_put(struct ksmbd_conn *conn);
+void ksmbd_conn_abort(struct ksmbd_conn *conn);
int ksmbd_conn_wq_init(void);
void ksmbd_conn_wq_destroy(void);
bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 7881fc7bb8cd..ba44bea9ddc3 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -188,8 +188,10 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (conn->ops->is_transform_hdr &&
conn->ops->is_transform_hdr(work->request_buf)) {
rc = conn->ops->decrypt_req(work);
- if (rc < 0)
+ if (rc < 0) {
+ ksmbd_conn_abort(conn);
return;
+ }
work->encrypted = true;
}
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 1045eca581c3..0ae5f145a332 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -435,6 +435,11 @@ static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
atomic_dec(&active_num_conn);
}
+static void ksmbd_tcp_shutdown(struct ksmbd_transport *t)
+{
+ kernel_sock_shutdown(TCP_TRANS(t)->sock, SHUT_RDWR);
+}
+
static void tcp_destroy_socket(struct socket *ksmbd_socket)
{
int ret;
@@ -681,5 +686,6 @@ static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops = {
.read = ksmbd_tcp_read,
.writev = ksmbd_tcp_writev,
.disconnect = ksmbd_tcp_disconnect,
+ .shutdown = ksmbd_tcp_shutdown,
.free_transport = ksmbd_tcp_free_transport,
};
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: decrypt requests from expired encrypted sessions
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
` (6 preceding siblings ...)
2026-08-17 12:15 ` [PATCH] ksmbd: disconnect on SMB3 decryption failure Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: handle encrypted compressed requests Namjae Jeon
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
Previous-session replacement marks the old session expired but retains its
SMB3 encryption key. An in-flight encrypted request can still arrive on
that connection. Rejecting the expired session before decryption made ksmbd
treat the request as a key failure and abort the transport, causing
reconnect failures.
Allow key lookup for expired sessions that have encryption enabled. Keep
the session reference during validation so the normal
STATUS_USER_SESSION_DELETED response is encrypted with the old key. The
session remains expired and no command is executed.
Fixes: fa9415d4024f ("ksmbd: mark SMB2_SESSION_EXPIRED to session when destroying previous session")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/auth.c | 12 ++++++------
fs/smb/server/smb2pdu.c | 8 ++++++++
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index bcd371f5550d..78491b20897e 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -724,15 +724,15 @@ static int ksmbd_get_encryption_key(struct ksmbd_work *work, __u64 ses_id,
sess = work->sess;
else {
/*
- * An encrypted SESSION_SETUP request may reauthenticate an expired
- * Kerberos session. Keep using the established decryption key so
- * that the command can reach the session setup handler. Other
- * commands are rejected there with STATUS_NETWORK_SESSION_EXPIRED.
+ * A previous-session replacement leaves the old encryption key in
+ * place. Use it to authenticate an encrypted request, then let
+ * session validation reject the expired session. This preserves the
+ * encrypted STATUS_USER_SESSION_DELETED response without reviving
+ * the session.
*/
sess = ksmbd_session_lookup_all_states(work->conn, ses_id);
if (sess && sess->state != SMB2_SESSION_VALID &&
- (sess->state != SMB2_SESSION_EXPIRED ||
- !sess->kerberos_expiry)) {
+ (sess->state != SMB2_SESSION_EXPIRED || !sess->enc)) {
ksmbd_user_session_put(sess);
sess = NULL;
}
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index ade16532a8c1..8d06c934f24f 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1012,6 +1012,14 @@ int smb2_check_user_session(struct ksmbd_work *work)
1 : -EKEYEXPIRED;
}
if (work->sess->state != SMB2_SESSION_VALID) {
+ /*
+ * Keep the reference for an encrypted request so the caller can
+ * return STATUS_USER_SESSION_DELETED encrypted with the old key.
+ */
+ if (work->encrypted &&
+ work->sess->state == SMB2_SESSION_EXPIRED &&
+ work->sess->enc)
+ return -ENOENT;
ksmbd_user_session_put(work->sess);
work->sess = NULL;
return -ENOENT;
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: handle encrypted compressed requests
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
` (7 preceding siblings ...)
2026-08-17 12:15 ` [PATCH] ksmbd: decrypt requests from expired encrypted sessions Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: add SMB Direct RDMA encryption transform Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: make RDMA encryption diagnostics conditional Namjae Jeon
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
SMB3 permits a message to be compressed before it is encrypted. After
decrypting such a request, ksmbd must trim the AEAD tag using
OriginalMessageSize, decompress the nested compression transform, and
validate the resulting SMB2 PDU.
Share the decompression helper between the connection receive path and
the post-decryption work path so unencrypted and encrypted compressed
requests follow the same validation.
Fixes: a08de24c2b85 ("ksmbd: negotiate and decode SMB2 compression")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/compress.c | 76 ++++++++++++++++++++++++++++++----------
fs/smb/server/compress.h | 1 +
fs/smb/server/server.c | 22 ++++++++++++
fs/smb/server/smb2pdu.c | 22 ++++++++----
4 files changed, 97 insertions(+), 24 deletions(-)
diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c
index 01d1771ff663..5162fb84c755 100644
--- a/fs/smb/server/compress.c
+++ b/fs/smb/server/compress.c
@@ -14,24 +14,14 @@
#define SMB_COMPRESS_MIN_LEN PAGE_SIZE
-/**
- * ksmbd_decompress_request() - replace a compressed request with its SMB2 PDU
- * @conn: connection which owns the current RFC1002 request buffer
- *
- * Derive the uncompressed size from the transform variant, enforce ksmbd's
- * normal message limits, and ask the common decoder to validate every payload.
- * On success, replace conn->request_buf with a regular RFC1002-framed SMB2
- * message so the rest of the request path needs no compression awareness.
- *
- * Return: 0 on success, otherwise a negative errno.
- */
-int ksmbd_decompress_request(struct ksmbd_conn *conn)
+static int __ksmbd_decompress_request(struct ksmbd_conn *conn,
+ void *request_buf, void **out_buf)
{
struct smb2_compression_hdr *hdr;
- unsigned int pdu_size = get_rfc1002_len(conn->request_buf);
+ unsigned int pdu_size = get_rfc1002_len(request_buf);
u32 orig_size, offset, out_size;
u32 max_allowed_pdu_size;
- char *buf, *out;
+ char *out;
int rc;
if (pdu_size < sizeof(struct smb2_compression_hdr))
@@ -41,7 +31,7 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn)
conn->compress_algorithm == SMB3_COMPRESS_NONE)
return -EINVAL;
- hdr = smb_get_msg(conn->request_buf);
+ hdr = smb_get_msg(request_buf);
if (hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID)
return -EINVAL;
@@ -74,19 +64,69 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn)
if (!out)
return -ENOMEM;
- buf = (char *)hdr;
*(__be32 *)out = cpu_to_be32(out_size);
rc = smb_compression_decompress(conn->compress_algorithm,
conn->compress_chained,
conn->compress_pattern,
- buf, pdu_size, out + 4, out_size);
+ (char *)hdr, pdu_size, out + 4, out_size);
if (rc) {
kvfree(out);
return rc;
}
+ *out_buf = out;
+ return 0;
+}
+
+/**
+ * ksmbd_decompress_request() - replace a compressed request with its SMB2 PDU
+ * @conn: connection which owns the current RFC1002 request buffer
+ *
+ * Derive the uncompressed size from the transform variant, enforce ksmbd's
+ * normal message limits, and ask the common decoder to validate every payload.
+ * On success, replace conn->request_buf with a regular RFC1002-framed SMB2
+ * message so the rest of the request path needs no compression awareness.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int ksmbd_decompress_request(struct ksmbd_conn *conn)
+{
+ void *out_buf;
+ int rc;
+
+ rc = __ksmbd_decompress_request(conn, conn->request_buf, &out_buf);
+ if (rc)
+ return rc;
+
kvfree(conn->request_buf);
- conn->request_buf = out;
+ conn->request_buf = out_buf;
+ return 0;
+}
+
+/**
+ * ksmbd_decompress_work_request() - decompress an encrypted work request
+ * @work: work item whose request buffer contains a compression transform
+ *
+ * SMB3 encrypts a compressed message by applying compression first and
+ * encryption second. The receive loop can therefore only decode the
+ * compression transform before work allocation for an unencrypted request;
+ * an encrypted request must be decompressed after its encryption layer has
+ * been removed.
+ *
+ * Return: 0 on success, otherwise a negative errno.
+ */
+int ksmbd_decompress_work_request(struct ksmbd_work *work)
+{
+ void *out_buf;
+ int rc;
+
+ rc = __ksmbd_decompress_request(work->conn, work->request_buf,
+ &out_buf);
+ if (rc)
+ return rc;
+
+ kvfree(work->request_buf);
+ work->request_buf = out_buf;
return 0;
}
diff --git a/fs/smb/server/compress.h b/fs/smb/server/compress.h
index 663c6f44f09b..13df2eb221e8 100644
--- a/fs/smb/server/compress.h
+++ b/fs/smb/server/compress.h
@@ -11,6 +11,7 @@
#include "../common/compress/compress.h"
int ksmbd_decompress_request(struct ksmbd_conn *conn);
+int ksmbd_decompress_work_request(struct ksmbd_work *work);
int ksmbd_compress_response(struct ksmbd_work *work);
#endif /* __KSMBD_COMPRESS_H__ */
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index ba44bea9ddc3..6cfe8148da85 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -193,6 +193,28 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
return;
}
work->encrypted = true;
+
+ /*
+ * SMB3 applies compression before encryption. The receive loop
+ * handles a plain compression transform before allocating work, but
+ * an encrypted request exposes that transform only after decryption.
+ */
+ if (((struct smb2_hdr *)smb_get_msg(work->request_buf))->ProtocolId ==
+ SMB2_COMPRESSION_TRANSFORM_ID) {
+ rc = ksmbd_decompress_work_request(work);
+ if (rc < 0) {
+ ksmbd_conn_abort(conn);
+ return;
+ }
+ }
+
+ /* The decrypted payload must now be a complete SMB2 request. */
+ if (((struct smb2_hdr *)smb_get_msg(work->request_buf))->ProtocolId !=
+ SMB2_PROTO_NUMBER ||
+ get_rfc1002_len(work->request_buf) < sizeof(struct smb2_pdu)) {
+ ksmbd_conn_abort(conn);
+ return;
+ }
}
if (conn->ops->allocate_rsp_buf(work))
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 8d06c934f24f..a564535132e5 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -11817,18 +11817,27 @@ int smb3_decrypt_req(struct ksmbd_work *work)
char *buf = work->request_buf;
unsigned int pdu_length = get_rfc1002_len(buf);
struct kvec iov[2];
- int buf_data_size = pdu_length - sizeof(struct smb2_transform_hdr);
+ unsigned int buf_data_size;
struct smb2_transform_hdr *tr_hdr = smb_get_msg(buf);
+ unsigned int original_msg_size;
int rc = 0;
- if (pdu_length < sizeof(struct smb2_transform_hdr) ||
- buf_data_size < sizeof(struct smb2_hdr)) {
+ if (pdu_length < sizeof(struct smb2_transform_hdr)) {
pr_err("Transform message is too small (%u)\n",
pdu_length);
return -ECONNABORTED;
}
- if (buf_data_size < le32_to_cpu(tr_hdr->OriginalMessageSize)) {
+ buf_data_size = pdu_length - sizeof(struct smb2_transform_hdr);
+ original_msg_size = le32_to_cpu(tr_hdr->OriginalMessageSize);
+ if (buf_data_size < sizeof(struct smb2_compression_hdr) ||
+ original_msg_size < sizeof(struct smb2_compression_hdr)) {
+ pr_err("Transform message is too small (%u)\n",
+ pdu_length);
+ return -ECONNABORTED;
+ }
+
+ if (buf_data_size < original_msg_size) {
pr_err("Transform message is broken\n");
return -ECONNABORTED;
}
@@ -11841,8 +11850,9 @@ int smb3_decrypt_req(struct ksmbd_work *work)
if (rc)
return rc;
- memmove(buf + 4, iov[1].iov_base, buf_data_size);
- *(__be32 *)buf = cpu_to_be32(buf_data_size);
+ /* Drop the AEAD authentication tag from the inner RFC1002 frame. */
+ memmove(buf + 4, iov[1].iov_base, original_msg_size);
+ *(__be32 *)buf = cpu_to_be32(original_msg_size);
return rc;
}
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: add SMB Direct RDMA encryption transform
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
` (8 preceding siblings ...)
2026-08-17 12:15 ` [PATCH] ksmbd: handle encrypted compressed requests Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
2026-08-17 12:15 ` [PATCH] ksmbd: make RDMA encryption diagnostics conditional Namjae Jeon
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
Port SMB Direct RDMA payload encryption support to the current ksmbd tree.
The current tree already supports all-state lookup for encrypted expired
sessions, so the overlapping lookup hunk from the original patch is
intentionally omitted.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/client/smb2pdu.h | 24 --
fs/smb/common/smb2pdu.h | 22 ++
fs/smb/common/smb2status.h | 1 +
fs/smb/server/auth.c | 158 +++++++++++
fs/smb/server/auth.h | 4 +
fs/smb/server/connection.c | 2 +
fs/smb/server/connection.h | 3 +
fs/smb/server/smb2pdu.c | 500 ++++++++++++++++++++++++++++++---
fs/smb/server/transport_rdma.c | 10 +
fs/smb/server/transport_rdma.h | 2 +
10 files changed, 663 insertions(+), 63 deletions(-)
diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h
index b9bf2fa989d5..ab6c667bebc0 100644
--- a/fs/smb/client/smb2pdu.h
+++ b/fs/smb/client/smb2pdu.h
@@ -21,30 +21,6 @@
/* The total header size for SMB2 read and write */
#define SMB2_READWRITE_PDU_HEADER_SIZE (48 + sizeof(struct smb2_hdr))
-/* See MS-SMB2 2.2.43 */
-struct smb2_rdma_transform {
- __le16 RdmaDescriptorOffset;
- __le16 RdmaDescriptorLength;
- __le32 Channel; /* for values see channel description in smb2 read above */
- __le16 TransformCount;
- __le16 Reserved1;
- __le32 Reserved2;
-} __packed;
-
-/* TransformType */
-#define SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION 0x0001
-#define SMB2_RDMA_TRANSFORM_TYPE_SIGNING 0x0002
-
-struct smb2_rdma_crypto_transform {
- __le16 TransformType;
- __le16 SignatureLength;
- __le16 NonceLength;
- __u16 Reserved;
- __u8 Signature[]; /* variable length */
- /* u8 Nonce[] */
- /* followed by padding */
-} __packed;
-
/*
* Definitions for SMB2 Protocol Data Units (network frames)
*
diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h
index d9650aff0d3c..f9a8862cb3d4 100644
--- a/fs/smb/common/smb2pdu.h
+++ b/fs/smb/common/smb2pdu.h
@@ -743,6 +743,28 @@ struct smb2_close_rsp {
#define SMB2_CHANNEL_RDMA_V1_INVALIDATE cpu_to_le32(0x00000002)
#define SMB2_CHANNEL_RDMA_TRANSFORM cpu_to_le32(0x00000003)
+/* See MS-SMB2 2.2.43. */
+struct smb2_rdma_transform {
+ __le16 RdmaDescriptorOffset;
+ __le16 RdmaDescriptorLength;
+ __le32 Channel;
+ __le16 TransformCount;
+ __le16 Reserved1;
+ __le32 Reserved2;
+} __packed;
+
+#define SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION 0x0001
+#define SMB2_RDMA_TRANSFORM_TYPE_SIGNING 0x0002
+
+struct smb2_rdma_crypto_transform {
+ __le16 TransformType;
+ __le16 SignatureLength;
+ __le16 NonceLength;
+ __le16 Reserved;
+ __u8 Signature[];
+ /* Followed by Nonce[] and optional alignment padding. */
+} __packed;
+
/* SMB2 read request without RFC1001 length at the beginning */
struct smb2_read_req {
struct smb2_hdr hdr;
diff --git a/fs/smb/common/smb2status.h b/fs/smb/common/smb2status.h
index b6421bc5113c..2989c3a5cb67 100644
--- a/fs/smb/common/smb2status.h
+++ b/fs/smb/common/smb2status.h
@@ -1049,6 +1049,7 @@ struct ntstatus {
#define STATUS_WOW_ASSERTION cpu_to_le32(0xC0009898) // -EIO
#define STATUS_INVALID_SIGNATURE cpu_to_le32(0xC000A000) // -EIO
#define STATUS_HMAC_NOT_SUPPORTED cpu_to_le32(0xC000A001) // -EIO
+#define STATUS_AUTH_TAG_MISMATCH cpu_to_le32(0xC000A002) // -EBADMSG
#define STATUS_IPSEC_QUEUE_OVERFLOW cpu_to_le32(0xC000A010) // -EIO
#define STATUS_ND_QUEUE_OVERFLOW cpu_to_le32(0xC000A011) // -EIO
#define STATUS_HOPLIMIT_EXCEEDED cpu_to_le32(0xC000A012) // -EIO
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 78491b20897e..db362c64af8d 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -833,6 +833,164 @@ static struct scatterlist *ksmbd_init_sg(struct kvec *iov, unsigned int nvec,
return sg;
}
+/**
+ * ksmbd_init_rdma_sg() - build an AEAD scatterlist for an RDMA payload
+ * @buf: payload buffer
+ * @buflen: payload length
+ * @tag: authentication tag buffer
+ * @taglen: authentication tag length
+ *
+ * Split vmalloc-backed payloads at page boundaries and append the detached
+ * authentication tag as the final scatterlist entry.
+ *
+ * Return: allocated scatterlist, or NULL on allocation failure
+ */
+static struct scatterlist *ksmbd_init_rdma_sg(void *buf,
+ unsigned int buflen,
+ u8 *tag,
+ unsigned int taglen)
+{
+ struct scatterlist *sg;
+ unsigned int nr_data = 1, nr_entries, i = 0;
+ void *data = buf;
+ int len = buflen;
+
+ if (is_vmalloc_addr(buf))
+ nr_data = DIV_ROUND_UP(offset_in_page(buf) + buflen, PAGE_SIZE);
+ nr_entries = nr_data + 1;
+
+ sg = kmalloc_objs(struct scatterlist, nr_entries, KSMBD_DEFAULT_GFP);
+ if (!sg)
+ return NULL;
+
+ sg_init_table(sg, nr_entries);
+ if (!is_vmalloc_addr(buf)) {
+ smb2_sg_set_buf(&sg[i++], buf, buflen);
+ } else {
+ while (len) {
+ unsigned int bytes = min_t(unsigned int,
+ PAGE_SIZE - offset_in_page(data), len);
+
+ sg_set_page(&sg[i++], vmalloc_to_page(data), bytes,
+ offset_in_page(data));
+ data += bytes;
+ len -= bytes;
+ }
+ }
+ smb2_sg_set_buf(&sg[i], tag, taglen);
+ return sg;
+}
+
+/**
+ * ksmbd_crypt_rdma() - encrypt or decrypt an SMB Direct data buffer
+ * @conn: connection containing the negotiated cipher
+ * @key: session encryption or decryption key
+ * @buf: RDMA payload, transformed in place
+ * @buflen: payload length (the authentication tag is carried out of band)
+ * @nonce: transform nonce
+ * @nonce_len: nonce length
+ * @tag: authentication tag output for encryption, input for decryption
+ * @tag_len: authentication tag length
+ * @enc: true to encrypt, false to decrypt
+ *
+ * SMB2_RDMA_CRYPTO_TRANSFORM carries the nonce and authentication tag in the
+ * SMB2 message while only the payload is transferred through RDMA. Therefore
+ * this uses AEAD without the normal SMB3 transform header as associated data.
+ *
+ * Return: 0 on success, otherwise a negative errno
+ */
+int ksmbd_crypt_rdma(struct ksmbd_conn *conn, const u8 *key,
+ void *buf, unsigned int buflen, const u8 *nonce,
+ unsigned int nonce_len, u8 *tag, unsigned int tag_len,
+ bool enc)
+{
+ struct ksmbd_crypto_ctx *ctx;
+ struct crypto_aead *tfm;
+ struct aead_request *req = NULL;
+ struct scatterlist *sg = NULL;
+ unsigned int iv_len, crypt_len;
+ u8 auth_tag[SMB2_SIGNATURE_SIZE] = {};
+ u8 *iv = NULL;
+ int rc;
+ DECLARE_CRYPTO_WAIT(wait);
+
+ if (!buflen || !tag_len || tag_len > SMB2_SIGNATURE_SIZE)
+ return -EINVAL;
+ if (!enc)
+ memcpy(auth_tag, tag, tag_len);
+
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
+ if (nonce_len != SMB3_AES_GCM_NONCE)
+ return -EINVAL;
+ ctx = ksmbd_crypto_ctx_find_gcm();
+ } else {
+ if (nonce_len != SMB3_AES_CCM_NONCE)
+ return -EINVAL;
+ ctx = ksmbd_crypto_ctx_find_ccm();
+ }
+ if (!ctx)
+ return -ENOMEM;
+
+ tfm = (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
+ CRYPTO_GCM(ctx) : CRYPTO_CCM(ctx);
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
+ rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE);
+ else
+ rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE);
+ if (rc)
+ goto out;
+
+ rc = crypto_aead_setauthsize(tfm, tag_len);
+ if (rc)
+ goto out;
+
+ req = aead_request_alloc(tfm, KSMBD_DEFAULT_GFP);
+ if (!req) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ sg = ksmbd_init_rdma_sg(buf, buflen, auth_tag, tag_len);
+ if (!sg) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ iv_len = crypto_aead_ivsize(tfm);
+ iv = kzalloc(iv_len, KSMBD_DEFAULT_GFP);
+ if (!iv) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
+ memcpy(iv, nonce, nonce_len);
+ } else {
+ iv[0] = 3;
+ memcpy(iv + 1, nonce, nonce_len);
+ }
+
+ crypt_len = buflen + (enc ? 0 : tag_len);
+ aead_request_set_crypt(req, sg, sg, crypt_len, iv);
+ aead_request_set_ad(req, 0);
+ aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+ CRYPTO_TFM_REQ_MAY_SLEEP,
+ crypto_req_done, &wait);
+ rc = crypto_wait_req(enc ? crypto_aead_encrypt(req) :
+ crypto_aead_decrypt(req), &wait);
+ if (!rc && enc)
+ memcpy(tag, auth_tag, tag_len);
+out:
+ kfree(iv);
+ kfree(sg);
+ aead_request_free(req);
+ ksmbd_release_crypto_ctx(ctx);
+ return rc;
+}
+
int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
unsigned int nvec, int enc)
{
diff --git a/fs/smb/server/auth.h b/fs/smb/server/auth.h
index f14b7c033264..7ce9c42d58f1 100644
--- a/fs/smb/server/auth.h
+++ b/fs/smb/server/auth.h
@@ -38,6 +38,10 @@ struct kvec;
int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
unsigned int nvec, int enc);
+int ksmbd_crypt_rdma(struct ksmbd_conn *conn, const u8 *key,
+ void *buf, unsigned int buflen, const u8 *nonce,
+ unsigned int nonce_len, u8 *tag, unsigned int tag_len,
+ bool enc);
void ksmbd_copy_gss_neg_header(void *buf);
int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
struct ntlmv2_resp *ntlmv2, int blen, char *domain_name,
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 5d729473dd18..d32f4f3cef93 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -77,6 +77,8 @@ static void proc_show_conn_features(struct seq_file *m,
proc_show_conn_feature(m, &separator,
conn->compress_algorithm != SMB3_COMPRESS_NONE,
"compress");
+ proc_show_conn_feature(m, &separator, conn->rdma_transform_ids,
+ "rdma-transform");
proc_show_conn_feature(m, &separator, conn->posix_ext_supported, "posix");
if (!separator)
seq_puts(m, "none");
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index 421907aed473..63484c8efbbd 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -139,6 +139,9 @@ struct ksmbd_conn {
/* Negotiated SMB 3.1.1 compression capabilities. */
bool compress_chained;
bool compress_pattern;
+ /* Bitmap indexed by SMB2_RDMA_TRANSFORM_* IDs. */
+ unsigned long rdma_transform_ids;
+ bool rdma_transform_negotiated;
bool posix_ext_supported;
bool signing_negotiated;
__le16 signing_algorithm;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index a564535132e5..b48eff02dbf8 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1356,6 +1356,37 @@ static void build_compress_ctxt(struct smb2_compression_capabilities_context *pn
pneg_ctxt->CompressionAlgorithms[3] = 0;
}
+/**
+ * build_rdma_ctx() - build an RDMA transform negotiate response context
+ * @ctxt: response context header to populate
+ * @transform_ids: bitmap of transforms common to the client and server
+ *
+ * Return: encoded negotiate context length
+ */
+static int build_rdma_ctx(struct smb2_neg_context *ctxt,
+ unsigned long transform_ids)
+{
+ struct smb2_rdma_transform_capabilities_context *pneg_ctxt;
+ int count = 0;
+
+ pneg_ctxt = (void *)ctxt;
+ pneg_ctxt->ContextType = SMB2_RDMA_TRANSFORM_CAPABILITIES;
+ pneg_ctxt->Reserved = 0;
+ pneg_ctxt->Reserved1 = 0;
+ pneg_ctxt->Reserved2 = 0;
+ if (transform_ids & BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION))
+ pneg_ctxt->RDMATransformIds[count++] =
+ cpu_to_le16(SMB2_RDMA_TRANSFORM_ENCRYPTION);
+ if (!count)
+ pneg_ctxt->RDMATransformIds[count++] =
+ cpu_to_le16(SMB2_RDMA_TRANSFORM_NONE);
+
+ pneg_ctxt->TransformCount = cpu_to_le16(count);
+ pneg_ctxt->DataLength = cpu_to_le16(8 + count * sizeof(__le16));
+ return sizeof(struct smb2_neg_context) +
+ le16_to_cpu(pneg_ctxt->DataLength);
+}
+
static void build_sign_cap_ctxt(struct smb2_signing_capabilities *pneg_ctxt,
__le16 sign_algo)
{
@@ -1431,6 +1462,18 @@ static unsigned int assemble_neg_contexts(struct ksmbd_conn *conn,
(conn->compress_pattern ? 12 : 10);
}
+ if (conn->rdma_transform_negotiated) {
+ struct smb2_neg_context *rdma_ctxt;
+
+ ctxt_size = round_up(ctxt_size, 8);
+ ksmbd_debug(SMB,
+ "assemble SMB2_RDMA_TRANSFORM_CAPABILITIES context\n");
+ rdma_ctxt = (void *)(pneg_ctxt + ctxt_size);
+ ctxt_size += build_rdma_ctx(rdma_ctxt,
+ conn->rdma_transform_ids);
+ neg_ctxt_cnt++;
+ }
+
if (conn->posix_ext_supported) {
ctxt_size = round_up(ctxt_size, 8);
ksmbd_debug(SMB,
@@ -1631,6 +1674,46 @@ static void decode_sign_cap_ctxt(struct ksmbd_conn *conn,
}
}
+/**
+ * decode_rdma_ctx() - decode an RDMA transform negotiate request context
+ * @conn: connection being negotiated
+ * @ctxt: request context header to decode
+ * @ctxt_len: total context length, including the negotiate context header
+ *
+ * Record transforms supported by both peers only for SMB Direct connections.
+ *
+ * Return: NT status describing the decode result
+ */
+static __le32 decode_rdma_ctx(struct ksmbd_conn *conn,
+ struct smb2_neg_context *ctxt, int ctxt_len)
+{
+ struct smb2_rdma_transform_capabilities_context *pneg_ctxt;
+ unsigned int count, i;
+
+ pneg_ctxt = (void *)ctxt;
+ /* RDMA transforms are a node capability, not just a transport capability. */
+ if (!ksmbd_rdma_enabled())
+ return STATUS_SUCCESS;
+
+ if (ctxt_len < sizeof(*pneg_ctxt))
+ return STATUS_INVALID_PARAMETER;
+
+ count = le16_to_cpu(pneg_ctxt->TransformCount);
+ if (!count || count >
+ (ctxt_len - sizeof(*pneg_ctxt)) / sizeof(__le16))
+ return STATUS_INVALID_PARAMETER;
+
+ conn->rdma_transform_negotiated = true;
+ conn->rdma_transform_ids = 0;
+ for (i = 0; i < count; i++) {
+ u16 id = le16_to_cpu(pneg_ctxt->RDMATransformIds[i]);
+
+ if (id == SMB2_RDMA_TRANSFORM_ENCRYPTION)
+ conn->rdma_transform_ids |= BIT(id);
+ }
+ return STATUS_SUCCESS;
+}
+
static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
struct smb2_negotiate_req *req,
unsigned int len_of_smb)
@@ -1641,7 +1724,7 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
unsigned int offset = le32_to_cpu(req->NegotiateContextOffset);
unsigned int neg_ctxt_cnt = le16_to_cpu(req->NegotiateContextCount);
__le32 status = STATUS_INVALID_PARAMETER;
- int compress_ctxt_cnt = 0;
+ int compress_ctxt_cnt = 0, rdma_transform_ctxt_cnt = 0;
ksmbd_debug(SMB, "decoding %d negotiate contexts\n", neg_ctxt_cnt);
if (len_of_smb <= offset) {
@@ -1700,6 +1783,17 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
} else if (pctx->ContextType == SMB2_NETNAME_NEGOTIATE_CONTEXT_ID) {
ksmbd_debug(SMB,
"deassemble SMB2_NETNAME_NEGOTIATE_CONTEXT_ID context\n");
+ } else if (pctx->ContextType == SMB2_RDMA_TRANSFORM_CAPABILITIES) {
+ ksmbd_debug(SMB,
+ "deassemble SMB2_RDMA_TRANSFORM_CAPABILITIES context\n");
+ if (ksmbd_rdma_enabled() &&
+ rdma_transform_ctxt_cnt++) {
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ status = decode_rdma_ctx(conn, pctx, ctxt_len);
+ if (status != STATUS_SUCCESS)
+ break;
} else if (pctx->ContextType == SMB2_POSIX_EXTENSIONS_AVAILABLE) {
ksmbd_debug(SMB,
"deassemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n");
@@ -1807,6 +1901,9 @@ int smb2_handle_negotiate(struct ksmbd_work *work)
conn->preauth_info = NULL;
goto err_out;
}
+ if (!conn->cipher_type)
+ conn->rdma_transform_ids &=
+ ~BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION);
rc = init_smb3_11_server(conn);
if (rc < 0) {
@@ -8553,18 +8650,31 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work)
return err;
}
-static int smb2_set_remote_key_for_rdma(struct ksmbd_work *work,
- struct smbdirect_buffer_descriptor_v1 *desc,
- __le32 Channel,
- __le16 ChannelInfoLength)
+/**
+ * smb2_set_rdma_key() - validate descriptors and save invalidation state
+ * @work: request work item
+ * @desc: first RDMA buffer descriptor
+ * @Channel: nested RDMA channel type
+ * @channel_info_len: descriptor array length
+ *
+ * Return: 0 on success, otherwise -EINVAL
+ */
+static int smb2_set_rdma_key(struct ksmbd_work *work,
+ struct smbdirect_buffer_descriptor_v1 *desc,
+ __le32 Channel, __le16 channel_info_len)
{
unsigned int i, ch_count;
+ if (Channel != SMB2_CHANNEL_RDMA_V1 &&
+ Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE)
+ return -EINVAL;
if (work->conn->dialect == SMB30_PROT_ID &&
Channel != SMB2_CHANNEL_RDMA_V1)
return -EINVAL;
+ if (le16_to_cpu(channel_info_len) % sizeof(*desc))
+ return -EINVAL;
- ch_count = le16_to_cpu(ChannelInfoLength) / sizeof(*desc);
+ ch_count = le16_to_cpu(channel_info_len) / sizeof(*desc);
if (ksmbd_debug_types & KSMBD_DEBUG_RDMA) {
for (i = 0; i < ch_count; i++) {
pr_info("RDMA r/w request %#x: token %#x, length %#x\n",
@@ -8583,9 +8693,223 @@ static int smb2_set_remote_key_for_rdma(struct ksmbd_work *work,
return 0;
}
-static ssize_t smb2_read_rdma_channel(struct ksmbd_work *work,
- struct smb2_read_req *req, void *data_buf,
- size_t length)
+/**
+ * smb2_prep_rdma_read() - transform an RDMA READ payload
+ * @work: request work item
+ * @req: READ request controlling encryption or signing
+ * @rsp: READ response receiving transform metadata
+ * @data: data that will be transferred through RDMA
+ * @datalen: data length
+ *
+ * Encrypt the payload in place and encode the detached crypto metadata in
+ * the response buffer.
+ *
+ * Return: metadata length, zero when no transform applies, or negative errno
+ */
+static int smb2_prep_rdma_read(struct ksmbd_work *work,
+ struct smb2_read_req *req,
+ struct smb2_read_rsp *rsp,
+ void *data, unsigned int datalen)
+{
+ struct ksmbd_conn *conn = work->conn;
+ struct smb2_rdma_transform *transform;
+ struct smb2_rdma_crypto_transform *crypto;
+ u8 *nonce;
+ unsigned int nonce_len = 0, transform_len;
+ u16 transform_type;
+ int err;
+
+ if (work->encrypted &&
+ (conn->rdma_transform_ids & BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION))) {
+ transform_type = SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION;
+ nonce_len = (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
+ SMB3_AES_GCM_NONCE : SMB3_AES_CCM_NONCE;
+ } else {
+ return 0;
+ }
+
+ transform = (struct smb2_rdma_transform *)rsp->Buffer;
+ crypto = (struct smb2_rdma_crypto_transform *)(transform + 1);
+ memset(transform, 0, sizeof(*transform) + sizeof(*crypto) +
+ SMB2_SIGNATURE_SIZE + nonce_len);
+ transform->Channel = SMB2_CHANNEL_NONE;
+ transform->TransformCount = cpu_to_le16(1);
+
+ crypto->TransformType = cpu_to_le16(transform_type);
+ crypto->SignatureLength = cpu_to_le16(SMB2_SIGNATURE_SIZE);
+ crypto->NonceLength = cpu_to_le16(nonce_len);
+ nonce = crypto->Signature + SMB2_SIGNATURE_SIZE;
+
+ get_random_bytes(nonce, nonce_len);
+ err = ksmbd_crypt_rdma(conn,
+ work->sess->smb3encryptionkey,
+ data, datalen, nonce, nonce_len,
+ crypto->Signature,
+ SMB2_SIGNATURE_SIZE, true);
+ if (err)
+ return err;
+
+ transform_len = sizeof(*transform) + sizeof(*crypto) +
+ SMB2_SIGNATURE_SIZE + nonce_len;
+ rsp->Flags = SMB2_READFLAG_RESPONSE_RDMA_TRANSFORM;
+ rsp->DataLength = cpu_to_le32(transform_len);
+ return transform_len;
+}
+
+struct smb2_rdma_write_transform {
+ struct smbdirect_buffer_descriptor_v1 *desc;
+ struct smb2_rdma_crypto_transform *crypto;
+ u8 *nonce;
+ unsigned int desc_len;
+ unsigned int nonce_len;
+ unsigned int signature_len;
+ u16 type;
+ __le32 channel;
+};
+
+/**
+ * smb2_current_req_len() - return the current compound request element size
+ * @work: request work item
+ * @hdr: current SMB2 header
+ *
+ * Return: current request element length measured from the SMB2 header
+ */
+static unsigned int smb2_current_req_len(struct ksmbd_work *work,
+ struct smb2_hdr *hdr)
+{
+ if (hdr->NextCommand)
+ return le32_to_cpu(hdr->NextCommand);
+ return get_rfc1002_len(work->request_buf) -
+ work->next_smb2_rcv_hdr_off;
+}
+
+/**
+ * check_rdma_desc() - validate an RDMA descriptor array
+ * @desc: descriptor array
+ * @desc_len: descriptor array length
+ * @required_len: minimum aggregate buffer length
+ *
+ * Return: 0 when the descriptors cover the transfer, otherwise -EINVAL
+ */
+static int check_rdma_desc(struct smbdirect_buffer_descriptor_v1 *desc,
+ unsigned int desc_len,
+ unsigned int required_len)
+{
+ unsigned int i, count;
+ u64 described_len = 0;
+
+ if (!desc_len || desc_len % sizeof(*desc))
+ return -EINVAL;
+ count = desc_len / sizeof(*desc);
+ if (!le32_to_cpu(desc[0].length))
+ return -EINVAL;
+ for (i = 0; i < count; i++)
+ described_len += le32_to_cpu(desc[i].length);
+ return described_len < required_len ? -EINVAL : 0;
+}
+
+/**
+ * smb2_parse_rdma_write_transform() - validate RDMA WRITE transform metadata
+ * @work: request work item
+ * @req: WRITE request containing the transform
+ * @info: parsed transform information
+ *
+ * Validate transform counts, crypto fields, descriptor alignment and bounds,
+ * negotiated algorithms, and the nested RDMA channel.
+ *
+ * Return: 0 on success, otherwise a negative errno
+ */
+static int smb2_parse_rdma_write_transform(struct ksmbd_work *work,
+ struct smb2_write_req *req,
+ struct smb2_rdma_write_transform *info)
+{
+ struct smb2_rdma_transform *transform;
+ struct smb2_rdma_crypto_transform *crypto;
+ unsigned int req_len = smb2_current_req_len(work, &req->hdr);
+ unsigned int offset = le16_to_cpu(req->WriteChannelInfoOffset);
+ unsigned int length = le16_to_cpu(req->WriteChannelInfoLength);
+ unsigned int desc_offset, desc_len, crypto_len, expected_desc_offset;
+
+ if (!work->conn->rdma_transform_ids ||
+ offset < offsetof(struct smb2_write_req, Buffer) ||
+ length < sizeof(*transform) || offset > req_len ||
+ length > req_len - offset)
+ return -EINVAL;
+
+ transform = (struct smb2_rdma_transform *)((char *)req + offset);
+ if (le16_to_cpu(transform->TransformCount) != 1 ||
+ (transform->Channel != SMB2_CHANNEL_RDMA_V1 &&
+ transform->Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE))
+ return -EINVAL;
+
+ desc_offset = le16_to_cpu(transform->RdmaDescriptorOffset);
+ desc_len = le16_to_cpu(transform->RdmaDescriptorLength);
+ if (!desc_len || desc_len % sizeof(*info->desc) ||
+ desc_offset < sizeof(*transform) || desc_offset > length ||
+ desc_len > length - desc_offset)
+ return -EINVAL;
+
+ crypto = (struct smb2_rdma_crypto_transform *)(transform + 1);
+ if (length - sizeof(*transform) < sizeof(*crypto))
+ return -EINVAL;
+ info->type = le16_to_cpu(crypto->TransformType);
+ info->signature_len = le16_to_cpu(crypto->SignatureLength);
+ info->nonce_len = le16_to_cpu(crypto->NonceLength);
+ if (!info->signature_len)
+ return info->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION ?
+ -EBADMSG : -EINVAL;
+ if (info->signature_len > SMB2_SIGNATURE_SIZE)
+ return info->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION ?
+ -EBADMSG : -EINVAL;
+ if (info->signature_len > length - sizeof(*transform) - sizeof(*crypto) ||
+ info->nonce_len > length - sizeof(*transform) - sizeof(*crypto) -
+ info->signature_len)
+ return -EINVAL;
+
+ crypto_len = sizeof(*crypto) + info->signature_len + info->nonce_len;
+ expected_desc_offset = ALIGN(sizeof(*transform) + crypto_len, 8);
+ if (desc_offset != expected_desc_offset)
+ return -EINVAL;
+
+ if (info->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION) {
+ unsigned int expected_nonce_len;
+
+ if (!(work->conn->rdma_transform_ids &
+ BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION)) || !work->encrypted)
+ return -EINVAL;
+ expected_nonce_len =
+ (work->conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ work->conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
+ SMB3_AES_GCM_NONCE : SMB3_AES_CCM_NONCE;
+ if (info->nonce_len != expected_nonce_len)
+ return -EBADMSG;
+ } else {
+ return -EINVAL;
+ }
+
+ info->desc = (struct smbdirect_buffer_descriptor_v1 *)
+ ((char *)transform + desc_offset);
+ info->desc_len = desc_len;
+ info->crypto = crypto;
+ info->nonce = crypto->Signature + info->signature_len;
+ info->channel = transform->Channel;
+ return check_rdma_desc(info->desc, info->desc_len,
+ le32_to_cpu(req->RemainingBytes));
+}
+
+/**
+ * smb2_read_rdma() - transfer READ data to client RDMA buffers
+ * @work: request work item
+ * @req: READ request containing client descriptors
+ * @data_buf: data to transfer
+ * @length: data length
+ *
+ * Return: transferred length on success, otherwise a negative errno
+ */
+static ssize_t smb2_read_rdma(struct ksmbd_work *work,
+ struct smb2_read_req *req, void *data_buf,
+ size_t length)
{
int err;
@@ -8615,6 +8939,7 @@ int smb2_read(struct ksmbd_work *work)
size_t length, mincount;
ssize_t nbytes = 0, remain_bytes = 0;
int err = 0;
+ int rdma_transform_len = 0;
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;
@@ -8649,6 +8974,12 @@ int smb2_read(struct ksmbd_work *work)
pid = req->PersistentFileId;
}
+ if (req->Channel != SMB2_CHANNEL_NONE &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1 &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE) {
+ err = -EINVAL;
+ goto out;
+ }
if (req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE ||
req->Channel == SMB2_CHANNEL_RDMA_V1) {
is_rdma_channel = true;
@@ -8661,16 +8992,24 @@ int smb2_read(struct ksmbd_work *work)
if (is_rdma_channel == true) {
unsigned int ch_offset = le16_to_cpu(req->ReadChannelInfoOffset);
+ unsigned int ch_len = le16_to_cpu(req->ReadChannelInfoLength);
+ unsigned int req_len = smb2_current_req_len(work, &req->hdr);
+ struct smbdirect_buffer_descriptor_v1 *desc;
- if (ch_offset < offsetof(struct smb2_read_req, Buffer)) {
+ if (!le32_to_cpu(req->Length) ||
+ ch_offset < offsetof(struct smb2_read_req, Buffer) ||
+ ch_offset > req_len || ch_len > req_len - ch_offset) {
err = -EINVAL;
goto out;
}
- err = smb2_set_remote_key_for_rdma(work,
- (struct smbdirect_buffer_descriptor_v1 *)
- ((char *)req + ch_offset),
- req->Channel,
- req->ReadChannelInfoLength);
+ desc = (struct smbdirect_buffer_descriptor_v1 *)
+ ((char *)req + ch_offset);
+ err = check_rdma_desc(desc, ch_len, le32_to_cpu(req->Length));
+ if (err)
+ goto out;
+ err = smb2_set_rdma_key(work, desc,
+ req->Channel,
+ req->ReadChannelInfoLength);
if (err)
goto out;
}
@@ -8753,10 +9092,19 @@ int smb2_read(struct ksmbd_work *work)
nbytes, offset, mincount);
if (is_rdma_channel == true) {
+ rdma_transform_len = smb2_prep_rdma_read(work, req,
+ rsp,
+ aux_payload_buf,
+ nbytes);
+ if (rdma_transform_len < 0) {
+ kvfree(aux_payload_buf);
+ err = rdma_transform_len;
+ goto out;
+ }
/* write data to the client using rdma channel */
- remain_bytes = smb2_read_rdma_channel(work, req,
- aux_payload_buf,
- nbytes);
+ remain_bytes = smb2_read_rdma(work, req,
+ aux_payload_buf,
+ nbytes);
kvfree(aux_payload_buf);
aux_payload_buf = NULL;
nbytes = 0;
@@ -8769,11 +9117,13 @@ int smb2_read(struct ksmbd_work *work)
rsp->StructureSize = cpu_to_le16(17);
rsp->DataOffset = 80;
rsp->Reserved = 0;
- rsp->DataLength = cpu_to_le32(nbytes);
+ rsp->DataLength = cpu_to_le32(rdma_transform_len ?: nbytes);
rsp->DataRemaining = cpu_to_le32(remain_bytes);
- rsp->Flags = 0;
+ rsp->Flags = rdma_transform_len ?
+ SMB2_READFLAG_RESPONSE_RDMA_TRANSFORM : 0;
err = ksmbd_iov_pin_rsp_read(work, (void *)rsp,
- offsetof(struct smb2_read_rsp, Buffer),
+ offsetof(struct smb2_read_rsp, Buffer) +
+ rdma_transform_len,
aux_payload_buf, nbytes);
if (err) {
kvfree(aux_payload_buf);
@@ -8885,10 +9235,28 @@ static noinline int smb2_write_pipe(struct ksmbd_work *work)
return err;
}
-static ssize_t smb2_write_rdma_channel(struct ksmbd_work *work,
- struct smb2_write_req *req,
- struct ksmbd_file *fp,
- loff_t offset, size_t length, bool sync)
+/**
+ * smb2_write_rdma() - receive and store an RDMA WRITE payload
+ * @work: request work item
+ * @desc: client RDMA buffer descriptors
+ * @desc_len: descriptor array length
+ * @transform: parsed transform, or NULL for an untransformed transfer
+ * @fp: target open file
+ * @offset: target file offset
+ * @length: transfer length
+ * @sync: request synchronous storage completion
+ *
+ * Receive the payload, authenticate or decrypt it when required, and write it
+ * to the target file.
+ *
+ * Return: written byte count on success, otherwise a negative errno
+ */
+static ssize_t smb2_write_rdma(struct ksmbd_work *work,
+ struct smbdirect_buffer_descriptor_v1 *desc,
+ unsigned int desc_len,
+ struct smb2_rdma_write_transform *transform,
+ struct ksmbd_file *fp, loff_t offset,
+ size_t length, bool sync)
{
char *data_buf;
int ret;
@@ -8898,15 +9266,27 @@ static ssize_t smb2_write_rdma_channel(struct ksmbd_work *work,
if (!data_buf)
return -ENOMEM;
- ret = ksmbd_conn_rdma_read(work->conn, data_buf, length,
- (struct smbdirect_buffer_descriptor_v1 *)
- ((char *)req + le16_to_cpu(req->WriteChannelInfoOffset)),
- le16_to_cpu(req->WriteChannelInfoLength));
+ ret = ksmbd_conn_rdma_read(work->conn, data_buf, length, desc,
+ desc_len);
if (ret < 0) {
kvfree(data_buf);
return ret;
}
+ if (transform &&
+ transform->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION) {
+ ret = ksmbd_crypt_rdma(work->conn,
+ work->sess->smb3decryptionkey,
+ data_buf, length, transform->nonce,
+ transform->nonce_len,
+ transform->crypto->Signature,
+ transform->signature_len, false);
+ if (ret) {
+ kvfree(data_buf);
+ return ret == -ENOMEM ? ret : -EBADMSG;
+ }
+ }
+
ret = ksmbd_vfs_write(work, fp, data_buf, length, &offset, sync, &nbytes);
kvfree(data_buf);
if (ret < 0)
@@ -8925,6 +9305,10 @@ int smb2_write(struct ksmbd_work *work)
{
struct smb2_write_req *req;
struct smb2_write_rsp *rsp;
+ struct smb2_rdma_write_transform rdma_transform = {};
+ struct smb2_rdma_write_transform *rdma_info = NULL;
+ struct smbdirect_buffer_descriptor_v1 *rdma_desc = NULL;
+ unsigned int rdma_desc_len = 0;
struct ksmbd_file *fp = NULL;
loff_t offset;
size_t length;
@@ -8969,8 +9353,21 @@ int smb2_write(struct ksmbd_work *work)
}
length = le32_to_cpu(req->Length);
+ if (req->Channel != SMB2_CHANNEL_NONE &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1 &&
+ req->Channel != SMB2_CHANNEL_RDMA_V1_INVALIDATE &&
+ req->Channel != SMB2_CHANNEL_RDMA_TRANSFORM) {
+ err = -EINVAL;
+ goto out;
+ }
+ if (req->Channel == SMB2_CHANNEL_RDMA_TRANSFORM &&
+ work->conn->dialect != SMB311_PROT_ID) {
+ err = -EINVAL;
+ goto out;
+ }
if (req->Channel == SMB2_CHANNEL_RDMA_V1 ||
- req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE) {
+ req->Channel == SMB2_CHANNEL_RDMA_V1_INVALIDATE ||
+ req->Channel == SMB2_CHANNEL_RDMA_TRANSFORM) {
is_rdma_channel = true;
max_write_size = get_smbd_max_read_write_size(work->conn->transport);
if (max_write_size == 0) {
@@ -8995,17 +9392,37 @@ int smb2_write(struct ksmbd_work *work)
if (is_rdma_channel == true) {
unsigned int ch_offset = le16_to_cpu(req->WriteChannelInfoOffset);
+ unsigned int ch_len = le16_to_cpu(req->WriteChannelInfoLength);
+ unsigned int req_len = smb2_current_req_len(work, &req->hdr);
- if (req->Length != 0 || req->DataOffset != 0 ||
- ch_offset < offsetof(struct smb2_write_req, Buffer)) {
+ if (!length || req->Length != 0 || req->DataOffset != 0 ||
+ ch_offset < offsetof(struct smb2_write_req, Buffer) ||
+ ch_offset > req_len || ch_len > req_len - ch_offset) {
err = -EINVAL;
goto out;
}
- err = smb2_set_remote_key_for_rdma(work,
- (struct smbdirect_buffer_descriptor_v1 *)
- ((char *)req + ch_offset),
- req->Channel,
- req->WriteChannelInfoLength);
+ if (req->Channel == SMB2_CHANNEL_RDMA_TRANSFORM) {
+ err = smb2_parse_rdma_write_transform(work, req,
+ &rdma_transform);
+ if (err)
+ goto out;
+ rdma_desc = rdma_transform.desc;
+ rdma_desc_len = rdma_transform.desc_len;
+ rdma_info = &rdma_transform;
+ err = smb2_set_rdma_key(work, rdma_desc,
+ rdma_transform.channel,
+ cpu_to_le16(rdma_desc_len));
+ } else {
+ rdma_desc = (struct smbdirect_buffer_descriptor_v1 *)
+ ((char *)req + ch_offset);
+ rdma_desc_len = ch_len;
+ err = check_rdma_desc(rdma_desc, rdma_desc_len, length);
+ if (err)
+ goto out;
+ err = smb2_set_rdma_key(work, rdma_desc,
+ req->Channel,
+ req->WriteChannelInfoLength);
+ }
if (err)
goto out;
}
@@ -9074,8 +9491,9 @@ int smb2_write(struct ksmbd_work *work)
/* read data from the client using rdma channel, and
* write the data.
*/
- nbytes = smb2_write_rdma_channel(work, req, fp, offset, length,
- writethrough);
+ nbytes = smb2_write_rdma(work, rdma_desc, rdma_desc_len,
+ rdma_info, fp, offset, length,
+ writethrough);
if (nbytes < 0) {
err = (int)nbytes;
goto out;
@@ -9112,6 +9530,10 @@ int smb2_write(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_SHARING_VIOLATION;
else if (err == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ else if (err == -EBADMSG)
+ rsp->hdr.Status = STATUS_AUTH_TAG_MISMATCH;
+ else if (err == -EKEYREJECTED)
+ rsp->hdr.Status = STATUS_INVALID_SIGNATURE;
else if (rsp->hdr.Status == 0)
rsp->hdr.Status = STATUS_INVALID_HANDLE;
diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c
index 85d12c4c354c..ee28a4d1cc86 100644
--- a/fs/smb/server/transport_rdma.c
+++ b/fs/smb/server/transport_rdma.c
@@ -76,6 +76,8 @@ static int smb_direct_max_receive_size = 1364;
static int smb_direct_max_read_write_size = SMBD_DEFAULT_IOSIZE;
+static bool smb_direct_enabled;
+
static struct smb_direct_listener {
int port;
@@ -512,18 +514,26 @@ int ksmbd_rdma_init(void)
ksmbd_debug(RDMA, "iWarp RDMA listener. socket=%p\n",
smb_direct_iw_listener.socket);
+ WRITE_ONCE(smb_direct_enabled, true);
return 0;
err:
+ WRITE_ONCE(smb_direct_enabled, false);
ksmbd_rdma_stop_listening();
return ret;
}
void ksmbd_rdma_stop_listening(void)
{
+ WRITE_ONCE(smb_direct_enabled, false);
smb_direct_listener_destroy(&smb_direct_ib_listener);
smb_direct_listener_destroy(&smb_direct_iw_listener);
}
+bool ksmbd_rdma_enabled(void)
+{
+ return READ_ONCE(smb_direct_enabled);
+}
+
bool ksmbd_rdma_capable_netdev(struct net_device *netdev)
{
u8 node_type = smbdirect_netdev_rdma_capable_node_type(netdev);
diff --git a/fs/smb/server/transport_rdma.h b/fs/smb/server/transport_rdma.h
index 8b78917a1795..23247713b5c3 100644
--- a/fs/smb/server/transport_rdma.h
+++ b/fs/smb/server/transport_rdma.h
@@ -14,12 +14,14 @@
#ifdef CONFIG_SMB_SERVER_SMBDIRECT
int ksmbd_rdma_init(void);
void ksmbd_rdma_stop_listening(void);
+bool ksmbd_rdma_enabled(void);
bool ksmbd_rdma_capable_netdev(struct net_device *netdev);
void init_smbd_max_io_size(unsigned int sz);
unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt);
#else
static inline int ksmbd_rdma_init(void) { return 0; }
static inline void ksmbd_rdma_stop_listening(void) { }
+static inline bool ksmbd_rdma_enabled(void) { return false; }
static inline bool ksmbd_rdma_capable_netdev(struct net_device *netdev) { return false; }
static inline void init_smbd_max_io_size(unsigned int sz) { }
static inline unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt) { return 0; }
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH] ksmbd: make RDMA encryption diagnostics conditional
2026-08-17 12:15 [PATCH] ksmbd: accept unspecified volatile ID on durable reconnect Namjae Jeon
` (9 preceding siblings ...)
2026-08-17 12:15 ` [PATCH] ksmbd: add SMB Direct RDMA encryption transform Namjae Jeon
@ 2026-08-17 12:15 ` Namjae Jeon
10 siblings, 0 replies; 12+ messages in thread
From: Namjae Jeon @ 2026-08-17 12:15 UTC (permalink / raw)
To: linux-cifs; +Cc: smfrench, senozhatsky, tom, atteh.mailbox, Namjae Jeon
The temporary RDMA encryption diagnostics logged every SMB3 request
and successful payload operation with pr_err(), which made normal
traffic too noisy.
Keep only negotiation, RDMA READ preparation, RDMA WRITE transform
metadata, crypto completion, and final transfer completion messages as
KSMBD_DEBUG_RDMA diagnostics. Keep error reports for malformed metadata,
crypto, RDMA transfer, and file write failures at error level.
This preserves the diagnostics needed to verify RDMA transform
operation without flooding the kernel error log during normal I/O.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/smb/server/auth.c | 33 +++++++++++++++---
fs/smb/server/smb2pdu.c | 75 +++++++++++++++++++++++++++++++++--------
2 files changed, 90 insertions(+), 18 deletions(-)
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index db362c64af8d..9f3151a9f379 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -911,26 +911,42 @@ int ksmbd_crypt_rdma(struct ksmbd_conn *conn, const u8 *key,
unsigned int iv_len, crypt_len;
u8 auth_tag[SMB2_SIGNATURE_SIZE] = {};
u8 *iv = NULL;
+ u16 cipher = le16_to_cpu(conn->cipher_type);
int rc;
DECLARE_CRYPTO_WAIT(wait);
- if (!buflen || !tag_len || tag_len > SMB2_SIGNATURE_SIZE)
+ if (!buflen || !tag_len || tag_len > SMB2_SIGNATURE_SIZE) {
+ pr_err("RDMA %s rejected: cipher=0x%04x payload=%u nonce=%u tag=%u\n",
+ enc ? "encryption" : "decryption", cipher, buflen,
+ nonce_len, tag_len);
return -EINVAL;
+ }
if (!enc)
memcpy(auth_tag, tag, tag_len);
if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
- if (nonce_len != SMB3_AES_GCM_NONCE)
+ if (nonce_len != SMB3_AES_GCM_NONCE) {
+ pr_err("RDMA %s rejected: cipher=0x%04x invalid nonce=%u expected=%u\n",
+ enc ? "encryption" : "decryption", cipher,
+ nonce_len, SMB3_AES_GCM_NONCE);
return -EINVAL;
+ }
ctx = ksmbd_crypto_ctx_find_gcm();
} else {
- if (nonce_len != SMB3_AES_CCM_NONCE)
+ if (nonce_len != SMB3_AES_CCM_NONCE) {
+ pr_err("RDMA %s rejected: cipher=0x%04x invalid nonce=%u expected=%u\n",
+ enc ? "encryption" : "decryption", cipher,
+ nonce_len, SMB3_AES_CCM_NONCE);
return -EINVAL;
+ }
ctx = ksmbd_crypto_ctx_find_ccm();
}
- if (!ctx)
+ if (!ctx) {
+ pr_err("RDMA %s failed: cipher=0x%04x crypto context unavailable\n",
+ enc ? "encryption" : "decryption", cipher);
return -ENOMEM;
+ }
tfm = (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
@@ -988,6 +1004,15 @@ int ksmbd_crypt_rdma(struct ksmbd_conn *conn, const u8 *key,
kfree(sg);
aead_request_free(req);
ksmbd_release_crypto_ctx(ctx);
+ if (rc)
+ pr_err("RDMA %s failed: cipher=0x%04x payload=%u nonce=%u tag=%u rc=%d\n",
+ enc ? "encryption" : "decryption", cipher, buflen,
+ nonce_len, tag_len, rc);
+ else
+ ksmbd_debug(RDMA,
+ "RDMA %s completed: cipher=0x%04x payload=%u nonce=%u tag=%u\n",
+ enc ? "encryption" : "decryption", cipher, buflen,
+ nonce_len, tag_len);
return rc;
}
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index b48eff02dbf8..bd74b45ce0e7 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1904,6 +1904,13 @@ int smb2_handle_negotiate(struct ksmbd_work *work)
if (!conn->cipher_type)
conn->rdma_transform_ids &=
~BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION);
+ ksmbd_debug(RDMA,
+ "RDMA transform negotiation: transport=%s context=%s encryption=%s cipher=0x%04x\n",
+ conn->transport->ops->rdma_read ? "rdma" : "tcp",
+ conn->rdma_transform_negotiated ? "present" : "absent",
+ conn->rdma_transform_ids &
+ BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION) ? "enabled" : "disabled",
+ le16_to_cpu(conn->cipher_type));
rc = init_smb3_11_server(conn);
if (rc < 0) {
@@ -8719,15 +8726,14 @@ static int smb2_prep_rdma_read(struct ksmbd_work *work,
u16 transform_type;
int err;
- if (work->encrypted &&
- (conn->rdma_transform_ids & BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION))) {
- transform_type = SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION;
- nonce_len = (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
- conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
- SMB3_AES_GCM_NONCE : SMB3_AES_CCM_NONCE;
- } else {
+ if (!work->encrypted ||
+ !(conn->rdma_transform_ids & BIT(SMB2_RDMA_TRANSFORM_ENCRYPTION)))
return 0;
- }
+
+ transform_type = SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION;
+ nonce_len = (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
+ conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) ?
+ SMB3_AES_GCM_NONCE : SMB3_AES_CCM_NONCE;
transform = (struct smb2_rdma_transform *)rsp->Buffer;
crypto = (struct smb2_rdma_crypto_transform *)(transform + 1);
@@ -8747,13 +8753,20 @@ static int smb2_prep_rdma_read(struct ksmbd_work *work,
data, datalen, nonce, nonce_len,
crypto->Signature,
SMB2_SIGNATURE_SIZE, true);
- if (err)
+ if (err) {
+ pr_err("RDMA READ encryption failed: session=%llu payload=%u rc=%d\n",
+ work->sess->id, datalen, err);
return err;
+ }
transform_len = sizeof(*transform) + sizeof(*crypto) +
SMB2_SIGNATURE_SIZE + nonce_len;
rsp->Flags = SMB2_READFLAG_RESPONSE_RDMA_TRANSFORM;
rsp->DataLength = cpu_to_le32(transform_len);
+ ksmbd_debug(RDMA,
+ "RDMA READ encryption prepared: session=%llu cipher=0x%04x payload=%u transform=%u nonce=%u tag=%u\n",
+ work->sess->id, le16_to_cpu(conn->cipher_type), datalen,
+ transform_len, nonce_len, SMB2_SIGNATURE_SIZE);
return transform_len;
}
@@ -8830,6 +8843,7 @@ static int smb2_parse_rdma_write_transform(struct ksmbd_work *work,
unsigned int offset = le16_to_cpu(req->WriteChannelInfoOffset);
unsigned int length = le16_to_cpu(req->WriteChannelInfoLength);
unsigned int desc_offset, desc_len, crypto_len, expected_desc_offset;
+ int err;
if (!work->conn->rdma_transform_ids ||
offset < offsetof(struct smb2_write_req, Buffer) ||
@@ -8894,8 +8908,18 @@ static int smb2_parse_rdma_write_transform(struct ksmbd_work *work,
info->crypto = crypto;
info->nonce = crypto->Signature + info->signature_len;
info->channel = transform->Channel;
- return check_rdma_desc(info->desc, info->desc_len,
- le32_to_cpu(req->RemainingBytes));
+ err = check_rdma_desc(info->desc, info->desc_len,
+ le32_to_cpu(req->RemainingBytes));
+ if (err)
+ return err;
+
+ ksmbd_debug(RDMA,
+ "RDMA WRITE encryption metadata: session=%llu cipher=0x%04x payload=%u channel=0x%x descriptors=%zu nonce=%u tag=%u\n",
+ work->sess->id, le16_to_cpu(work->conn->cipher_type),
+ le32_to_cpu(req->RemainingBytes), le32_to_cpu(info->channel),
+ info->desc_len / sizeof(*info->desc), info->nonce_len,
+ info->signature_len);
+ return 0;
}
/**
@@ -9105,6 +9129,15 @@ int smb2_read(struct ksmbd_work *work)
remain_bytes = smb2_read_rdma(work, req,
aux_payload_buf,
nbytes);
+ if (remain_bytes < 0)
+ pr_err("RDMA READ transfer failed: session=%llu payload=%zu transform=%d rc=%zd\n",
+ work->sess ? work->sess->id : 0, nbytes,
+ rdma_transform_len, remain_bytes);
+ else
+ ksmbd_debug(RDMA,
+ "RDMA READ transfer completed: session=%llu payload=%zu transform=%d\n",
+ work->sess ? work->sess->id : 0, nbytes,
+ rdma_transform_len);
kvfree(aux_payload_buf);
aux_payload_buf = NULL;
nbytes = 0;
@@ -9269,10 +9302,12 @@ static ssize_t smb2_write_rdma(struct ksmbd_work *work,
ret = ksmbd_conn_rdma_read(work->conn, data_buf, length, desc,
desc_len);
if (ret < 0) {
+ if (transform)
+ pr_err("RDMA WRITE encrypted transfer failed: session=%llu payload=%zu rdma_read_rc=%d\n",
+ work->sess->id, length, ret);
kvfree(data_buf);
return ret;
}
-
if (transform &&
transform->type == SMB2_RDMA_TRANSFORM_TYPE_ENCRYPTION) {
ret = ksmbd_crypt_rdma(work->conn,
@@ -9282,6 +9317,8 @@ static ssize_t smb2_write_rdma(struct ksmbd_work *work,
transform->crypto->Signature,
transform->signature_len, false);
if (ret) {
+ pr_err("RDMA WRITE decryption failed: session=%llu payload=%zu rc=%d\n",
+ work->sess->id, length, ret);
kvfree(data_buf);
return ret == -ENOMEM ? ret : -EBADMSG;
}
@@ -9289,8 +9326,15 @@ static ssize_t smb2_write_rdma(struct ksmbd_work *work,
ret = ksmbd_vfs_write(work, fp, data_buf, length, &offset, sync, &nbytes);
kvfree(data_buf);
- if (ret < 0)
+ if (ret < 0) {
+ if (transform)
+ pr_err("RDMA WRITE encrypted file write failed: session=%llu payload=%zu rc=%d\n",
+ work->sess->id, length, ret);
return ret;
+ }
+ ksmbd_debug(RDMA,
+ "RDMA WRITE transfer completed: session=%llu payload=%zu transformed=%u written=%zd\n",
+ work->sess ? work->sess->id : 0, length, !!transform, nbytes);
return nbytes;
}
@@ -9404,8 +9448,11 @@ int smb2_write(struct ksmbd_work *work)
if (req->Channel == SMB2_CHANNEL_RDMA_TRANSFORM) {
err = smb2_parse_rdma_write_transform(work, req,
&rdma_transform);
- if (err)
+ if (err) {
+ pr_err("RDMA WRITE encryption metadata rejected: session=%llu rc=%d\n",
+ work->sess ? work->sess->id : 0, err);
goto out;
+ }
rdma_desc = rdma_transform.desc;
rdma_desc_len = rdma_transform.desc_len;
rdma_info = &rdma_transform;
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread