[RFC PATCH 3/6] ksmbd: pass the domain of an NTLM login to user space

From: Jan Koester

Date: Wed Oct 28 2026 - 02:23:55 EST


The login request carries only the user name, though NTLMSSP's
AUTHENTICATE message names the domain too. A user space serving several
domains (or local accounts next to a domain's) can't tell DOMAIN\user
apart from OTHER\user, picks the first account of that name, and a
session that the domain controller then authenticates as one domain's
user runs with another account's ids.

Put the domain into the formerly reserved room of struct
ksmbd_login_request, flagged with KSMBD_LOGIN_REQ_DOMAIN, so the
structure keeps its size and older user space keeps working. Ask for
the login response extension by the account name user space answered
with: that one is unambiguous, the client's may not be.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@xxxxxxxxx>
---
fs/smb/server/ksmbd_netlink.h | 12 +++++++++++-
fs/smb/server/mgmt/user_config.c | 16 ++++++++++++----
fs/smb/server/mgmt/user_config.h | 2 +-
fs/smb/server/smb2pdu.c | 20 ++++++++++++++++----
fs/smb/server/transport_ipc.c | 12 +++++++++++-
fs/smb/server/transport_ipc.h | 2 +-
6 files changed, 52 insertions(+), 12 deletions(-)

diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index 3fa1c7f801..3df5264ad7 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -66,6 +66,7 @@
#define KSMBD_GENL_VERSION 0x01

#define KSMBD_REQ_MAX_ACCOUNT_NAME_SZ 48
+#define KSMBD_REQ_MAX_DOMAIN_NAME_SZ 48
#define KSMBD_REQ_MAX_HASH_SZ 18
#define KSMBD_REQ_MAX_SHARE_NAME 64

@@ -140,9 +141,18 @@ struct ksmbd_shutdown_request {
struct ksmbd_login_request {
__u32 handle;
__s8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ]; /* user account name */
- __u32 reserved[16]; /* Reserved room */
+ __u32 flags; /* KSMBD_LOGIN_REQ_* */
+ __s8 domain[KSMBD_REQ_MAX_DOMAIN_NAME_SZ]; /* with _DOMAIN */
+ __u32 reserved[3]; /* Reserved room */
};

+/*
+ * The domain the client named with the account (NTLMSSP DomainName): user
+ * space serving several domains can tell their accounts of the same name
+ * apart. Formerly reserved, so zero from older ksmbd.
+ */
+#define KSMBD_LOGIN_REQ_DOMAIN BIT(0)
+
/*
* IPC user login response.
*/
diff --git a/fs/smb/server/mgmt/user_config.c b/fs/smb/server/mgmt/user_config.c
index cb175a4c78..a25eb0647c 100644
--- a/fs/smb/server/mgmt/user_config.c
+++ b/fs/smb/server/mgmt/user_config.c
@@ -9,21 +9,29 @@
#include "user_config.h"
#include "../transport_ipc.h"

-struct ksmbd_user *ksmbd_login_user(const char *account)
+struct ksmbd_user *ksmbd_login_user(const char *account, const char *domain)
{
struct ksmbd_login_response *resp;
struct ksmbd_login_response_ext *resp_ext = NULL;
struct ksmbd_user *user = NULL;

- resp = ksmbd_ipc_login_request(account);
+ resp = ksmbd_ipc_login_request(account, domain);
if (!resp)
return NULL;

if (!(resp->status & KSMBD_USER_FLAG_OK))
goto out;

- if (resp->status & KSMBD_USER_FLAG_EXTENSION)
- resp_ext = ksmbd_ipc_login_request_ext(account);
+ /*
+ * The extension of the account user space found: the name it
+ * answered with is unambiguous, the one the client gave may not be.
+ */
+ if (resp->status & KSMBD_USER_FLAG_EXTENSION) {
+ resp->account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ - 1] = '\0';
+ resp_ext = ksmbd_ipc_login_request_ext(resp->account[0] ?
+ (const char *)resp->account :
+ account);
+ }

user = ksmbd_alloc_user(resp, resp_ext);
kvfree(resp_ext);
diff --git a/fs/smb/server/mgmt/user_config.h b/fs/smb/server/mgmt/user_config.h
index e55470e008..0035675e03 100644
--- a/fs/smb/server/mgmt/user_config.h
+++ b/fs/smb/server/mgmt/user_config.h
@@ -63,7 +63,7 @@ static inline unsigned int user_gid(struct ksmbd_user *user)
return user->gid;
}

-struct ksmbd_user *ksmbd_login_user(const char *account);
+struct ksmbd_user *ksmbd_login_user(const char *account, const char *domain);
struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
struct ksmbd_login_response_ext *resp_ext);
void ksmbd_free_user(struct ksmbd_user *user);
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 8e47d725c1..db7f677b07 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2169,8 +2169,8 @@ static struct ksmbd_user *session_user(struct ksmbd_conn *conn,
{
struct authenticate_message *authblob;
struct ksmbd_user *user;
- char *name;
- unsigned int name_off, name_len, secbuf_len;
+ char *name, *domain = NULL;
+ unsigned int name_off, name_len, dom_off, dom_len, secbuf_len;

if (conn->use_spnego && conn->mechToken)
secbuf_len = conn->mechTokenLen;
@@ -2196,8 +2196,20 @@ static struct ksmbd_user *session_user(struct ksmbd_conn *conn,
return NULL;
}

- ksmbd_debug(SMB, "session setup request for user %s\n", name);
- user = ksmbd_login_user(name);
+ /* DOMAIN\user: which of several domains' user of that name */
+ dom_off = le32_to_cpu(authblob->DomainName.BufferOffset);
+ dom_len = le16_to_cpu(authblob->DomainName.Length);
+ if (dom_len && secbuf_len >= (u64)dom_off + dom_len) {
+ domain = smb_strndup_from_utf16((const char *)authblob + dom_off,
+ dom_len, true, conn->local_nls);
+ if (IS_ERR(domain))
+ domain = NULL;
+ }
+
+ ksmbd_debug(SMB, "session setup request for user %s domain %s\n",
+ name, domain ? domain : "");
+ user = ksmbd_login_user(name, domain);
+ kfree(domain);
kfree(name);
return user;
}
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 7bbe1b3607..4d2f16b96e 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -648,7 +648,12 @@ static int ksmbd_ipc_heartbeat_request(void)
return ret;
}

-struct ksmbd_login_response *ksmbd_ipc_login_request(const char *account)
+/*
+ * @domain: the domain the client named (NULL or "": none); one too long to
+ * pass is left out, user space then decides by the account alone
+ */
+struct ksmbd_login_response *ksmbd_ipc_login_request(const char *account,
+ const char *domain)
{
struct ksmbd_ipc_msg *msg;
struct ksmbd_login_request *req;
@@ -665,6 +670,11 @@ struct ksmbd_login_response *ksmbd_ipc_login_request(const char *account)
req = (struct ksmbd_login_request *)msg->payload;
req->handle = ksmbd_acquire_id(&ipc_ida);
strscpy(req->account, account, KSMBD_REQ_MAX_ACCOUNT_NAME_SZ);
+ if (domain && domain[0] &&
+ strlen(domain) < KSMBD_REQ_MAX_DOMAIN_NAME_SZ) {
+ req->flags |= KSMBD_LOGIN_REQ_DOMAIN;
+ strscpy(req->domain, domain, KSMBD_REQ_MAX_DOMAIN_NAME_SZ);
+ }

resp = ipc_msg_send_request(msg, req->handle);
ipc_msg_handle_free(req->handle);
diff --git a/fs/smb/server/transport_ipc.h b/fs/smb/server/transport_ipc.h
index f957a33ae3..1aefa23264 100644
--- a/fs/smb/server/transport_ipc.h
+++ b/fs/smb/server/transport_ipc.h
@@ -11,7 +11,7 @@
#define KSMBD_IPC_MAX_PAYLOAD 4096

struct ksmbd_login_response *
-ksmbd_ipc_login_request(const char *account);
+ksmbd_ipc_login_request(const char *account, const char *domain);
struct ksmbd_login_response_ext *
ksmbd_ipc_login_request_ext(const char *account);

--
2.56.0