* [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller
@ 2026-10-18 17:53 Jan Koester
2026-10-18 17:53 ` [PATCH 1/2] ksmbd: pass through NTLM authentication " Jan Koester
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Jan Koester @ 2026-10-18 17:53 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, linux-cifs,
samba-technical, linux-kernel
Hi,
a ksmbd server joined to an Active Directory domain can authenticate
domain users with Kerberos, but not with NTLM: it has no password hash
for them, so the NTLMv2 response can't be checked locally. Clients that
fall back to NTLM (access by IP address, machines outside the domain,
mount.cifs with user name and password) are refused.
Samba solves this by forwarding the response to a domain controller
over NETLOGON (NetrLogonSamLogonEx on a schannel connection). These two
patches give ksmbd the kernel half of that, and leave the NETLOGON client
to user space, like the rest of ksmbd's user handling:
1/2 adds a KSMBD_USER_FLAG_PASSTHROUGH login flag. For such a user,
ksmbd_decode_ntlmssp_auth_blob() doesn't compute the response from a
local hash. It sends the server challenge, the user, domain and
workstation names and the NT/LM responses to user space in a new
KSMBD_EVENT_NTLM_AUTHEN_REQUEST event, then takes the session base
key from KSMBD_EVENT_NTLM_AUTHEN_RESPONSE and goes on with the
existing key exchange. Both events have a strict netlink policy and
are checked in ipc_validate_msg().
2/2 puts the workgroup, not the netbios name, into MsvAvNbDomainName and
MsvAvDnsDomainName of the challenge's target info. A domain
controller checks these when it verifies the NTLMv2 response (Samba
in NTLMv2_RESPONSE_verify_workstation()), so without this patch
every pass-through login fails with NT_STATUS_LOGON_FAILURE. The
patch also checks that the challenge blob fits the response buffer,
since the workgroup can be longer than the netbios name.
Logins of users without the new flag are checked as before. The only
change they see is the domain names in the challenge's target info
(patch 2/2). ksmbd-tools doesn't set the flag yet. The user space side
we tested with is sharedd/adconnect from FenrirOS: it marks domain users
without a local hash as pass-through, and it answers the event through
a NETLOGON schannel client, tested against a Samba 4.22 AD DC.
I can send the matching ksmbd-tools change if the interface looks
acceptable.
The series is based on v7.3-rc7.
Jan Koester (2):
ksmbd: pass through NTLM authentication to a domain controller
ksmbd: put the workgroup into the NTLMSSP target info's domain names
fs/smb/server/auth.c | 109 +++++++++++++++++++++++++++++++---
fs/smb/server/ksmbd_netlink.h | 41 +++++++++++++
fs/smb/server/smb2pdu.c | 21 ++++++-
fs/smb/server/transport_ipc.c | 73 ++++++++++++++++++++++-
fs/smb/server/transport_ipc.h | 3 +
5 files changed, 236 insertions(+), 11 deletions(-)
base-commit: 32c52ec87d6245ee2d12ed5dd509e40062939b50
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/2] ksmbd: pass through NTLM authentication to a domain controller
2026-10-18 17:53 [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller Jan Koester
@ 2026-10-18 17:53 ` Jan Koester
2026-10-19 1:48 ` Namjae Jeon
2026-10-18 17:53 ` [PATCH 2/2] ksmbd: put the workgroup into the NTLMSSP target info's domain names Jan Koester
2026-10-19 1:31 ` [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller Namjae Jeon
2 siblings, 1 reply; 8+ messages in thread
From: Jan Koester @ 2026-10-18 17:53 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, linux-cifs,
samba-technical, linux-kernel
A ksmbd server that is a member of an Active Directory domain cannot
check the NTLM response of a domain user on its own: it has no password
hash for that user. Add a way to defer the check to user space, which
can ask a domain controller over NETLOGON (NetrLogonSamLogonEx).
When the login response for a user carries the new
KSMBD_USER_FLAG_PASSTHROUGH flag, ksmbd no longer computes the NTLMv2
response from a local hash. Instead ksmbd_decode_ntlmssp_auth_blob()
hands the server challenge and the client's user name, domain name,
workstation and NT/LM responses to user space through the new
KSMBD_EVENT_NTLM_AUTHEN_REQUEST event and takes the session base key
from the KSMBD_EVENT_NTLM_AUTHEN_RESPONSE, then continues with the
existing key-exchange path.
The two new events get a strict netlink policy and are validated in
ipc_validate_msg(); ksmbd_nl_init_fixup() keeps strict validation for
commands at or after resv_start_op so the new fixed-size response is
checked.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
fs/smb/server/auth.c | 68 ++++++++++++++++++++++++++++++++
fs/smb/server/ksmbd_netlink.h | 41 ++++++++++++++++++++
fs/smb/server/transport_ipc.c | 73 ++++++++++++++++++++++++++++++++++-
fs/smb/server/transport_ipc.h | 3 ++
4 files changed, 183 insertions(+), 2 deletions(-)
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 1b24eb3f39..9a2a3c6a37 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -79,6 +79,16 @@ static int calc_ntlmv2_hash(struct ksmbd_conn *conn, struct ksmbd_session *sess,
__le16 *uniname = NULL;
struct hmac_md5_ctx ctx;
+ /*
+ * A pass-through user (KSMBD_USER_FLAG_PASSTHROUGH) has no local
+ * password hash and must never reach the NTLMv2 code -- it is handled
+ * in ksmbd_decode_ntlmssp_auth_blob() instead. Guard against a NULL
+ * passkey anyway so a user space that sets KSMBD_USER_FLAG_OK without a
+ * hash can only fail the login, not crash the kernel.
+ */
+ if (!user_passkey(sess->user))
+ return -EINVAL;
+
hmac_md5_init_usingrawkey(&ctx, user_passkey(sess->user),
CIFS_ENCPWD_SIZE);
@@ -184,6 +194,55 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
return rc;
}
+/**
+ * ksmbd_auth_ntlm_passthrough() - NTLM authentication by user space
+ * @conn: connection
+ * @authblob: authenticate blob
+ * @blob_len: length of @authblob
+ * @sess_key: session base key output buffer
+ *
+ * For a user that user space has no password hash for (a domain user): it
+ * checks the response, e.g. with a domain controller, and returns the
+ * session base key.
+ *
+ * Return: 0 on success, error number on error
+ */
+static int ksmbd_auth_ntlm_passthrough(struct ksmbd_conn *conn,
+ struct authenticate_message *authblob,
+ int blob_len, char *sess_key)
+{
+ const struct security_buffer *bufs[5] = {
+ &authblob->UserName, &authblob->DomainName,
+ &authblob->WorkstationName, &authblob->NtChallengeResponse,
+ &authblob->LmChallengeResponse,
+ };
+ struct ksmbd_ntlm_authen_response *resp;
+ const char *parts[5];
+ unsigned short lens[5];
+ int i, rc;
+
+ for (i = 0; i < 5; i++) {
+ unsigned int off = le32_to_cpu(bufs[i]->BufferOffset);
+
+ lens[i] = le16_to_cpu(bufs[i]->Length);
+ if (blob_len < (u64)off + lens[i])
+ return -EINVAL;
+ parts[i] = (const char *)authblob + off;
+ }
+
+ resp = ksmbd_ipc_ntlm_authen_request(conn->ntlmssp.cryptkey, parts,
+ lens);
+ if (!resp)
+ return -EINVAL;
+
+ rc = resp->status & KSMBD_USER_FLAG_OK ? 0 : -EINVAL;
+ if (!rc)
+ memcpy(sess_key, resp->session_key, SMB2_NTLMV2_SESSKEY_SIZE);
+ memzero_explicit(resp->session_key, sizeof(resp->session_key));
+ kvfree(resp);
+ return rc;
+}
+
/**
* ksmbd_decode_ntlmssp_auth_blob() - helper function to construct
* authenticate blob
@@ -225,6 +284,14 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
nt_len < CIFS_ENCPWD_SIZE)
return -EINVAL;
+ if (test_user_flag(sess->user, KSMBD_USER_FLAG_PASSTHROUGH)) {
+ ret = ksmbd_auth_ntlm_passthrough(conn, authblob, blob_len,
+ sess_key);
+ if (ret)
+ return ret;
+ goto key_exchange;
+ }
+
/* TODO : use domain name that imported from configuration file */
domain_name = smb_strndup_from_utf16((const char *)authblob + dn_off,
dn_len, true, conn->local_nls);
@@ -242,6 +309,7 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
if (ret)
return ret;
+key_exchange:
/* The recovered secondary session key */
if (conn->ntlmssp.client_flags & NTLMSSP_NEGOTIATE_KEY_XCH) {
struct arc4_ctx *ctx_arc4;
diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index 2673522c76..7438e0b0ba 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -54,6 +54,11 @@
*
* - KSMBD_EVENT_LOGIN_REQUEST_EXT/RESPONSE_EXT(ksmbd_login_request_ext/response_ext)
* This event is to get user account extension info to user IPC daemon.
+ *
+ * - KSMBD_EVENT_NTLM_AUTHEN_REQUEST/RESPONSE(ksmbd_ntlm_authen_request/response)
+ * This event is to make NTLM authentication of a user the login response
+ * marked KSMBD_USER_FLAG_PASSTHROUGH to be processed in userspace, e.g.
+ * by a domain controller.
*/
#define KSMBD_GENL_NAME "SMBD_GENL"
@@ -161,6 +166,38 @@ struct ksmbd_login_response_ext {
__s8 ____payload[];
};
+/*
+ * IPC request to check the NTLM response of a user whose login response
+ * had KSMBD_USER_FLAG_PASSTHROUGH: user space has no password hash for it
+ * and asks someone who has, e.g. a domain controller (NETLOGON).
+ */
+struct ksmbd_ntlm_authen_request {
+ __u32 handle;
+ __u8 challenge[8]; /* the server challenge */
+ __u16 user_name_len; /* lengths of the payload parts */
+ __u16 domain_name_len;
+ __u16 workstation_len;
+ __u16 nt_response_len;
+ __u16 lm_response_len;
+ __u16 reserved16;
+ __u32 reserved[16]; /* Reserved room */
+ __u8 payload[]; /*
+ * user name, domain name, workstation
+ * (UTF-16LE, as the client sent them),
+ * NT response, LM response
+ */
+};
+
+/*
+ * IPC response to an NTLM authentication request
+ */
+struct ksmbd_ntlm_authen_response {
+ __u32 handle;
+ __u32 status; /* KSMBD_USER_FLAG_OK or _BAD_PASSWORD */
+ __u8 session_key[16]; /* the session base key */
+ __u32 reserved[16]; /* Reserved room */
+};
+
/*
* IPC request to fetch net share config.
*/
@@ -326,6 +363,9 @@ enum ksmbd_event {
KSMBD_EVENT_LOGIN_REQUEST_EXT,
KSMBD_EVENT_LOGIN_RESPONSE_EXT,
+ KSMBD_EVENT_NTLM_AUTHEN_REQUEST,
+ KSMBD_EVENT_NTLM_AUTHEN_RESPONSE,
+
__KSMBD_EVENT_MAX,
KSMBD_EVENT_MAX = __KSMBD_EVENT_MAX - 1
};
@@ -357,6 +397,7 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_USER_FLAG_GUEST_ACCOUNT BIT(4)
#define KSMBD_USER_FLAG_DELAY_SESSION BIT(5)
#define KSMBD_USER_FLAG_EXTENSION BIT(6)
+#define KSMBD_USER_FLAG_PASSTHROUGH BIT(7)
/*
* Share config flags.
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index e550aa41ad..578a22dbff 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -127,6 +127,10 @@ static const struct nla_policy ksmbd_nl_policy[KSMBD_EVENT_MAX + 1] = {
[KSMBD_EVENT_LOGIN_RESPONSE_EXT] = {
.len = sizeof(struct ksmbd_login_response_ext),
},
+ [KSMBD_EVENT_NTLM_AUTHEN_REQUEST] =
+ NLA_POLICY_MIN_LEN(sizeof(struct ksmbd_ntlm_authen_request)),
+ [KSMBD_EVENT_NTLM_AUTHEN_RESPONSE] =
+ NLA_POLICY_EXACT_LEN(sizeof(struct ksmbd_ntlm_authen_response)),
};
static struct genl_ops ksmbd_genl_ops[] = {
@@ -202,6 +206,14 @@ static struct genl_ops ksmbd_genl_ops[] = {
.cmd = KSMBD_EVENT_LOGIN_RESPONSE_EXT,
.doit = handle_generic_event,
},
+ {
+ .cmd = KSMBD_EVENT_NTLM_AUTHEN_REQUEST,
+ .doit = handle_unsupported_event,
+ },
+ {
+ .cmd = KSMBD_EVENT_NTLM_AUTHEN_RESPONSE,
+ .doit = handle_generic_event,
+ },
};
static struct genl_family ksmbd_genl_family = {
@@ -220,9 +232,11 @@ static void ksmbd_nl_init_fixup(void)
{
int i;
+ /* the commands from resv_start_op on are validated strictly */
for (i = 0; i < ARRAY_SIZE(ksmbd_genl_ops); i++)
- ksmbd_genl_ops[i].validate = GENL_DONT_VALIDATE_STRICT |
- GENL_DONT_VALIDATE_DUMP;
+ if (ksmbd_genl_ops[i].cmd < ksmbd_genl_family.resv_start_op)
+ ksmbd_genl_ops[i].validate = GENL_DONT_VALIDATE_STRICT |
+ GENL_DONT_VALIDATE_DUMP;
ksmbd_genl_family.policy = ksmbd_nl_policy;
}
@@ -549,6 +563,10 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
return -EINVAL;
break;
}
+ case KSMBD_EVENT_NTLM_AUTHEN_REQUEST:
+ if (entry->msg_sz != sizeof(struct ksmbd_ntlm_authen_response))
+ return -EINVAL;
+ break;
case KSMBD_EVENT_LOGIN_REQUEST_EXT:
{
struct ksmbd_login_response_ext *resp = entry->response;
@@ -702,6 +720,57 @@ ksmbd_ipc_spnego_authen_request(const char *spnego_blob, int blob_len)
return resp;
}
+/**
+ * ksmbd_ipc_ntlm_authen_request() - have user space check an NTLM response
+ * @challenge: the server challenge (CIFS_CRYPTO_KEY_SIZE bytes)
+ * @parts: user name, domain name, workstation (UTF-16LE),
+ * NT response, LM response
+ * @lens: their lengths
+ *
+ * For a user whose login response had KSMBD_USER_FLAG_PASSTHROUGH.
+ *
+ * Return: the response (kvfree() it), NULL on error
+ */
+struct ksmbd_ntlm_authen_response *
+ksmbd_ipc_ntlm_authen_request(const char *challenge, const char *parts[5],
+ const unsigned short lens[5])
+{
+ struct ksmbd_ipc_msg *msg;
+ struct ksmbd_ntlm_authen_request *req;
+ struct ksmbd_ntlm_authen_response *resp;
+ size_t payload_sz = 0, off = 0;
+ int i;
+
+ for (i = 0; i < 5; i++)
+ payload_sz += lens[i];
+ if (payload_sz > KSMBD_IPC_MAX_PAYLOAD)
+ return NULL;
+
+ msg = ipc_msg_alloc(sizeof(struct ksmbd_ntlm_authen_request) +
+ payload_sz);
+ if (!msg)
+ return NULL;
+
+ msg->type = KSMBD_EVENT_NTLM_AUTHEN_REQUEST;
+ req = (struct ksmbd_ntlm_authen_request *)msg->payload;
+ req->handle = ksmbd_acquire_id(&ipc_ida);
+ memcpy(req->challenge, challenge, sizeof(req->challenge));
+ req->user_name_len = lens[0];
+ req->domain_name_len = lens[1];
+ req->workstation_len = lens[2];
+ req->nt_response_len = lens[3];
+ req->lm_response_len = lens[4];
+ for (i = 0; i < 5; i++) {
+ memcpy(req->payload + off, parts[i], lens[i]);
+ off += lens[i];
+ }
+
+ resp = ipc_msg_send_request(msg, req->handle);
+ ipc_msg_handle_free(req->handle);
+ ipc_msg_free(msg);
+ return resp;
+}
+
struct ksmbd_tree_connect_response *
ksmbd_ipc_tree_connect_request(struct ksmbd_session *sess,
struct ksmbd_share_config *share,
diff --git a/fs/smb/server/transport_ipc.h b/fs/smb/server/transport_ipc.h
index e51850f142..f957a33ae3 100644
--- a/fs/smb/server/transport_ipc.h
+++ b/fs/smb/server/transport_ipc.h
@@ -32,6 +32,9 @@ struct ksmbd_share_config_response *
ksmbd_ipc_share_config_request(const char *name);
struct ksmbd_spnego_authen_response *
ksmbd_ipc_spnego_authen_request(const char *spnego_blob, int blob_len);
+struct ksmbd_ntlm_authen_response *
+ksmbd_ipc_ntlm_authen_request(const char *challenge, const char *parts[5],
+ const unsigned short lens[5]);
int ksmbd_ipc_id_alloc(void);
void ksmbd_rpc_id_free(int handle);
struct ksmbd_rpc_command *ksmbd_rpc_open(struct ksmbd_session *sess, int handle);
--
2.56.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 2/2] ksmbd: put the workgroup into the NTLMSSP target info's domain names
2026-10-18 17:53 [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller Jan Koester
2026-10-18 17:53 ` [PATCH 1/2] ksmbd: pass through NTLM authentication " Jan Koester
@ 2026-10-18 17:53 ` Jan Koester
2026-10-19 2:00 ` Namjae Jeon
2026-10-19 1:31 ` [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller Namjae Jeon
2 siblings, 1 reply; 8+ messages in thread
From: Jan Koester @ 2026-10-18 17:53 UTC (permalink / raw)
To: Namjae Jeon, Steve French
Cc: Sergey Senozhatsky, Tom Talpey, ChenXiaoSong, linux-cifs,
samba-technical, linux-kernel
ksmbd_build_ntlmssp_challenge_blob() writes the netbios name into all
four AV pairs of the challenge's target info, the domain names
(MsvAvNbDomainName, MsvAvDnsDomainName) included. The client copies
them into its NTLMv2 response.
With NTLM pass-through, a domain controller verifies that response over
NETLOGON, and Samba (NTLMv2_RESPONSE_verify_workstation()) refuses it
with NT_STATUS_LOGON_FAILURE unless MsvAvNbComputerName is the secure
channel's computer account and MsvAvNbDomainName its workgroup. Every
pass-through login of a domain user then fails.
Put the workgroup into the two domain names. Size the challenge blob
from both names in one helper, ntlmssp_challenge_size(), and check that
the blob fits into the response buffer when it is built in place
without SPNEGO: the workgroup may be longer than the netbios name.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
fs/smb/server/auth.c | 41 ++++++++++++++++++++++++++++++++++-------
fs/smb/server/smb2pdu.c | 21 +++++++++++++++++++--
2 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 9a2a3c6a37..8639997080 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -376,10 +376,10 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
struct ksmbd_conn *conn)
{
struct target_info *tinfo;
- wchar_t *name;
+ wchar_t *name, *domain;
__u8 *target_name;
unsigned int flags, blob_off, blob_len, type, target_info_len = 0;
- int len, uni_len, conv_len;
+ int len, uni_len, dom_len, conv_len;
int cflags = conn->ntlmssp.client_flags;
memcpy(chgblob->Signature, NTLMSSP_SIGNATURE, 8);
@@ -426,6 +426,23 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
uni_len = UNICODE_LEN(conv_len);
+ len = strlen(ksmbd_work_group());
+ domain = kmalloc(2 + UNICODE_LEN(len), KSMBD_DEFAULT_GFP);
+ if (!domain) {
+ kfree(name);
+ return -ENOMEM;
+ }
+
+ conv_len = smb_strtoUTF16((__le16 *)domain, ksmbd_work_group(), len,
+ conn->local_nls);
+ if (conv_len < 0 || conv_len > len) {
+ kfree(domain);
+ kfree(name);
+ return -EINVAL;
+ }
+
+ dom_len = UNICODE_LEN(conv_len);
+
blob_off = sizeof(struct challenge_message);
blob_len = blob_off + uni_len;
@@ -446,14 +463,23 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
tinfo = (struct target_info *)(target_name + uni_len);
chgblob->TargetInfoArray.Length = 0;
- /* Add target info list for NetBIOS/DNS settings */
+ /*
+ * Add target info list for NetBIOS/DNS settings: the computer names
+ * are the netbios name, the domain names the workgroup. A domain
+ * controller checks the latter when it is asked to verify the
+ * NTLMv2 response of a domain user (NETLOGON pass-through).
+ */
for (type = NTLMSSP_AV_NB_COMPUTER_NAME;
type <= NTLMSSP_AV_DNS_DOMAIN_NAME; type++) {
+ bool is_domain = type == NTLMSSP_AV_NB_DOMAIN_NAME ||
+ type == NTLMSSP_AV_DNS_DOMAIN_NAME;
+ int av_len = is_domain ? dom_len : uni_len;
+
tinfo->Type = cpu_to_le16(type);
- tinfo->Length = cpu_to_le16(uni_len);
- memcpy(tinfo->Content, name, uni_len);
- tinfo = (struct target_info *)((char *)tinfo + 4 + uni_len);
- target_info_len += 4 + uni_len;
+ tinfo->Length = cpu_to_le16(av_len);
+ memcpy(tinfo->Content, is_domain ? domain : name, av_len);
+ tinfo = (struct target_info *)((char *)tinfo + 4 + av_len);
+ target_info_len += 4 + av_len;
}
/* Add terminator subblock */
@@ -464,6 +490,7 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
chgblob->TargetInfoArray.Length = cpu_to_le16(target_info_len);
chgblob->TargetInfoArray.MaximumLength = cpu_to_le16(target_info_len);
blob_len += target_info_len;
+ kfree(domain);
kfree(name);
ksmbd_debug(AUTH, "NTLMSSP SecurityBufferLength %d\n", blob_len);
return blob_len;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 6b8809f67b..704c694fe7 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1822,6 +1822,20 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
return status;
}
+/*
+ * ntlmssp_challenge_size() - room for the challenge blob: the netbios name as
+ * target name and the two computer names, the workgroup as the two domain
+ * names (UTF-16, each with its 4-byte AV pair header), the terminator
+ */
+static size_t ntlmssp_challenge_size(void)
+{
+ size_t nb = strlen(ksmbd_netbios_name()) * 2;
+ size_t wg = strlen(ksmbd_work_group()) * 2;
+
+ return sizeof(struct challenge_message) + nb + 2 * (4 + nb) +
+ 2 * (4 + wg) + 4;
+}
+
/**
* smb2_handle_negotiate() - handler for smb2 negotiate command
* @work: smb work containing smb request buffer
@@ -2096,6 +2110,10 @@ static int ntlm_negotiate(struct ksmbd_work *work,
memset(chgblob, 0, sizeof(struct challenge_message));
if (!work->conn->use_spnego) {
+ if ((char *)rsp->Buffer + ntlmssp_challenge_size() >
+ (char *)work->response_buf + work->response_sz)
+ return -EINVAL;
+
sz = ksmbd_build_ntlmssp_challenge_blob(chgblob, work->conn);
if (sz < 0)
return -ENOMEM;
@@ -2104,8 +2122,7 @@ static int ntlm_negotiate(struct ksmbd_work *work,
return 0;
}
- sz = sizeof(struct challenge_message);
- sz += (strlen(ksmbd_netbios_name()) * 2 + 1 + 4) * 6;
+ sz = ntlmssp_challenge_size();
neg_blob = kzalloc(sz, KSMBD_DEFAULT_GFP);
if (!neg_blob)
--
2.56.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller
2026-10-18 17:53 [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller Jan Koester
2026-10-18 17:53 ` [PATCH 1/2] ksmbd: pass through NTLM authentication " Jan Koester
2026-10-18 17:53 ` [PATCH 2/2] ksmbd: put the workgroup into the NTLMSSP target info's domain names Jan Koester
@ 2026-10-19 1:31 ` Namjae Jeon
2026-10-19 10:43 ` Stefan Metzmacher
2 siblings, 1 reply; 8+ messages in thread
From: Namjae Jeon @ 2026-10-19 1:31 UTC (permalink / raw)
To: Jan Koester
Cc: Steve French, Sergey Senozhatsky, Tom Talpey, ChenXiaoSong,
linux-cifs, samba-technical, linux-kernel, wang zhaolong,
Wang Zhaolong
On Mon, Oct 19, 2026 at 2:53 AM Jan Koester <jan.koester@gmx.net> wrote:
>
> Hi,
>
> a ksmbd server joined to an Active Directory domain can authenticate
> domain users with Kerberos, but not with NTLM: it has no password hash
> for them, so the NTLMv2 response can't be checked locally. Clients that
> fall back to NTLM (access by IP address, machines outside the domain,
> mount.cifs with user name and password) are refused.
>
> Samba solves this by forwarding the response to a domain controller
> over NETLOGON (NetrLogonSamLogonEx on a schannel connection). These two
> patches give ksmbd the kernel half of that, and leave the NETLOGON client
> to user space, like the rest of ksmbd's user handling:
>
> 1/2 adds a KSMBD_USER_FLAG_PASSTHROUGH login flag. For such a user,
> ksmbd_decode_ntlmssp_auth_blob() doesn't compute the response from a
> local hash. It sends the server challenge, the user, domain and
> workstation names and the NT/LM responses to user space in a new
> KSMBD_EVENT_NTLM_AUTHEN_REQUEST event, then takes the session base
> key from KSMBD_EVENT_NTLM_AUTHEN_RESPONSE and goes on with the
> existing key exchange. Both events have a strict netlink policy and
> are checked in ipc_validate_msg().
>
> 2/2 puts the workgroup, not the netbios name, into MsvAvNbDomainName and
> MsvAvDnsDomainName of the challenge's target info. A domain
> controller checks these when it verifies the NTLMv2 response (Samba
> in NTLMv2_RESPONSE_verify_workstation()), so without this patch
> every pass-through login fails with NT_STATUS_LOGON_FAILURE. The
> patch also checks that the challenge blob fits the response buffer,
> since the workgroup can be longer than the netbios name.
>
> Logins of users without the new flag are checked as before. The only
> change they see is the domain names in the challenge's target info
> (patch 2/2). ksmbd-tools doesn't set the flag yet. The user space side
> we tested with is sharedd/adconnect from FenrirOS: it marks domain users
> without a local hash as pass-through, and it answers the event through
> a NETLOGON schannel client, tested against a Samba 4.22 AD DC.
>
> I can send the matching ksmbd-tools change if the interface looks
> acceptable.
Okay, Please send a PR to ksmbd-tools. Wang is also currently
implementing AD/DC features in ksmbd and might be interested, so I'm
adding him to CC.
Thanks for the patches!
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] ksmbd: pass through NTLM authentication to a domain controller
2026-10-18 17:53 ` [PATCH 1/2] ksmbd: pass through NTLM authentication " Jan Koester
@ 2026-10-19 1:48 ` Namjae Jeon
0 siblings, 0 replies; 8+ messages in thread
From: Namjae Jeon @ 2026-10-19 1:48 UTC (permalink / raw)
To: Jan Koester
Cc: Steve French, Sergey Senozhatsky, Tom Talpey, ChenXiaoSong,
linux-cifs, samba-technical, linux-kernel
> @@ -225,6 +284,14 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
> nt_len < CIFS_ENCPWD_SIZE)
> return -EINVAL;
>
> + if (test_user_flag(sess->user, KSMBD_USER_FLAG_PASSTHROUGH)) {
> + ret = ksmbd_auth_ntlm_passthrough(conn, authblob, blob_len,
> + sess_key);
> + if (ret)
> + return ret;
> + goto key_exchange;
ksmbd_auth_ntlmv2() returns -EOPNOTSUPP when fips_enabled is set, but
this branch bypasses that check and proceeds with authentication.
Delegating response verification to userspace and the DC does not
change the fact that this is an NTLMv2 flow, and KEY_XCH can still
reach the kernel’s ARC4 key-exchange path. Could we keep pass-through
authentication disabled when fips_enabled is set, before taking this
branch?
> + }
> +
> /* TODO : use domain name that imported from configuration file */
> domain_name = smb_strndup_from_utf16((const char *)authblob + dn_off,
> dn_len, true, conn->local_nls);
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] ksmbd: put the workgroup into the NTLMSSP target info's domain names
2026-10-18 17:53 ` [PATCH 2/2] ksmbd: put the workgroup into the NTLMSSP target info's domain names Jan Koester
@ 2026-10-19 2:00 ` Namjae Jeon
0 siblings, 0 replies; 8+ messages in thread
From: Namjae Jeon @ 2026-10-19 2:00 UTC (permalink / raw)
To: Jan Koester
Cc: Steve French, Sergey Senozhatsky, Tom Talpey, ChenXiaoSong,
linux-cifs, samba-technical, linux-kernel
---
fs/smb/server/auth.c | 41 ++++++++++++++++++++++++++++++++++-------
fs/smb/server/smb2pdu.c | 21 +++++++++++++++++++--
2 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 9a2a3c6a37..8639997080 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -376,10 +376,10 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_m=
essage *chgblob,
=09=09=09=09 struct ksmbd_conn *conn)
{
=09struct target_info *tinfo;
-=09wchar_t *name;
+=09wchar_t *name, *domain;
=09__u8 *target_name;
=09unsigned int flags, blob_off, blob_len, type, target_info_len =3D 0;
-=09int len, uni_len, conv_len;
+=09int len, uni_len, dom_len, conv_len;
=09int cflags =3D conn->ntlmssp.client_flags;
=20
=09memcpy(chgblob->Signature, NTLMSSP_SIGNATURE, 8);
@@ -426,6 +426,23 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_me=
ssage *chgblob,
=20
=09uni_len =3D UNICODE_LEN(conv_len);
=20
+=09len =3D strlen(ksmbd_work_group());
+=09domain =3D kmalloc(2 + UNICODE_LEN(len), KSMBD_DEFAULT_GFP);
+=09if (!domain) {
+=09=09kfree(name);
+=09=09return -ENOMEM;
+=09}
The patch is quoted-printable encoded. Could you send it as plain text
when you submit the next version?
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller
2026-10-19 1:31 ` [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller Namjae Jeon
@ 2026-10-19 10:43 ` Stefan Metzmacher
2026-10-19 13:02 ` Namjae Jeon
0 siblings, 1 reply; 8+ messages in thread
From: Stefan Metzmacher @ 2026-10-19 10:43 UTC (permalink / raw)
To: Namjae Jeon, Jan Koester
Cc: linux-cifs, Sergey Senozhatsky, samba-technical, linux-kernel,
Tom Talpey, Wang Zhaolong, ChenXiaoSong, wang zhaolong
Hi Jan,
>> a ksmbd server joined to an Active Directory domain can authenticate
>> domain users with Kerberos, but not with NTLM: it has no password hash
>> for them, so the NTLMv2 response can't be checked locally. Clients that
>> fall back to NTLM (access by IP address, machines outside the domain,
>> mount.cifs with user name and password) are refused.
>>
>> Samba solves this by forwarding the response to a domain controller
>> over NETLOGON (NetrLogonSamLogonEx on a schannel connection). These two
>> patches give ksmbd the kernel half of that, and leave the NETLOGON client
>> to user space, like the rest of ksmbd's user handling:
>>
>> 1/2 adds a KSMBD_USER_FLAG_PASSTHROUGH login flag. For such a user,
>> ksmbd_decode_ntlmssp_auth_blob() doesn't compute the response from a
>> local hash. It sends the server challenge, the user, domain and
>> workstation names and the NT/LM responses to user space in a new
>> KSMBD_EVENT_NTLM_AUTHEN_REQUEST event, then takes the session base
>> key from KSMBD_EVENT_NTLM_AUTHEN_RESPONSE and goes on with the
>> existing key exchange. Both events have a strict netlink policy and
>> are checked in ipc_validate_msg().
>>
>> 2/2 puts the workgroup, not the netbios name, into MsvAvNbDomainName and
>> MsvAvDnsDomainName of the challenge's target info. A domain
>> controller checks these when it verifies the NTLMv2 response (Samba
>> in NTLMv2_RESPONSE_verify_workstation()), so without this patch
>> every pass-through login fails with NT_STATUS_LOGON_FAILURE. The
>> patch also checks that the challenge blob fits the response buffer,
>> since the workgroup can be longer than the netbios name.
>>
>> Logins of users without the new flag are checked as before. The only
>> change they see is the domain names in the challenge's target info
>> (patch 2/2). ksmbd-tools doesn't set the flag yet. The user space side
>> we tested with is sharedd/adconnect from FenrirOS: it marks domain users
>> without a local hash as pass-through, and it answers the event through
>> a NETLOGON schannel client, tested against a Samba 4.22 AD DC.
>>
>> I can send the matching ksmbd-tools change if the interface looks
>> acceptable.
> Okay, Please send a PR to ksmbd-tools. Wang is also currently
> implementing AD/DC features in ksmbd and might be interested, so I'm
> adding him to CC.
why making this NTLM specific, I think it would be good to the whole session setup
blob exchange to userspace, so that authentication is not used in kernel at all?
And it will also help when IAKERB and other things will be implemented later.
It would also be good to have the infrastructure working with winbindd being
the bridge to the domain.
What exactly is "sharedd/adconnect from FenrirOS"?
And Wang, what are the AD/DC features?
metze
> Thanks for the patches!
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller
2026-10-19 10:43 ` Stefan Metzmacher
@ 2026-10-19 13:02 ` Namjae Jeon
0 siblings, 0 replies; 8+ messages in thread
From: Namjae Jeon @ 2026-10-19 13:02 UTC (permalink / raw)
To: Stefan Metzmacher
Cc: Jan Koester, linux-cifs, Sergey Senozhatsky, samba-technical,
linux-kernel, Tom Talpey, Wang Zhaolong, ChenXiaoSong,
wang zhaolong
On Mon, Oct 19, 2026 at 7:43 PM Stefan Metzmacher <metze@samba.org> wrote:
>
> Hi Jan,
>
> >> a ksmbd server joined to an Active Directory domain can authenticate
> >> domain users with Kerberos, but not with NTLM: it has no password hash
> >> for them, so the NTLMv2 response can't be checked locally. Clients that
> >> fall back to NTLM (access by IP address, machines outside the domain,
> >> mount.cifs with user name and password) are refused.
> >>
> >> Samba solves this by forwarding the response to a domain controller
> >> over NETLOGON (NetrLogonSamLogonEx on a schannel connection). These two
> >> patches give ksmbd the kernel half of that, and leave the NETLOGON client
> >> to user space, like the rest of ksmbd's user handling:
> >>
> >> 1/2 adds a KSMBD_USER_FLAG_PASSTHROUGH login flag. For such a user,
> >> ksmbd_decode_ntlmssp_auth_blob() doesn't compute the response from a
> >> local hash. It sends the server challenge, the user, domain and
> >> workstation names and the NT/LM responses to user space in a new
> >> KSMBD_EVENT_NTLM_AUTHEN_REQUEST event, then takes the session base
> >> key from KSMBD_EVENT_NTLM_AUTHEN_RESPONSE and goes on with the
> >> existing key exchange. Both events have a strict netlink policy and
> >> are checked in ipc_validate_msg().
> >>
> >> 2/2 puts the workgroup, not the netbios name, into MsvAvNbDomainName and
> >> MsvAvDnsDomainName of the challenge's target info. A domain
> >> controller checks these when it verifies the NTLMv2 response (Samba
> >> in NTLMv2_RESPONSE_verify_workstation()), so without this patch
> >> every pass-through login fails with NT_STATUS_LOGON_FAILURE. The
> >> patch also checks that the challenge blob fits the response buffer,
> >> since the workgroup can be longer than the netbios name.
> >>
> >> Logins of users without the new flag are checked as before. The only
> >> change they see is the domain names in the challenge's target info
> >> (patch 2/2). ksmbd-tools doesn't set the flag yet. The user space side
> >> we tested with is sharedd/adconnect from FenrirOS: it marks domain users
> >> without a local hash as pass-through, and it answers the event through
> >> a NETLOGON schannel client, tested against a Samba 4.22 AD DC.
> >>
> >> I can send the matching ksmbd-tools change if the interface looks
> >> acceptable.
> > Okay, Please send a PR to ksmbd-tools. Wang is also currently
> > implementing AD/DC features in ksmbd and might be interested, so I'm
> > adding him to CC.
>
> why making this NTLM specific, I think it would be good to the whole session setup
> blob exchange to userspace, so that authentication is not used in kernel at all?
> And it will also help when IAKERB and other things will be implemented later.
> It would also be good to have the infrastructure working with winbindd being
> the bridge to the domain.
I prefer keeping the current patch's approach for now. I'd like to
stick to minimal changes rather than making a drastic refactoring. As
for IAKERB, we can revisit and discuss any necessary structural
changes when we actually reach the point of implementing it.
>
> What exactly is "sharedd/adconnect from FenrirOS"?
>
> And Wang, what are the AD/DC features?
Active Directory Domain Controller Support.
>
> metze
> > Thanks for the patches!
>
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-19 13:03 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-18 17:53 [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller Jan Koester
2026-10-18 17:53 ` [PATCH 1/2] ksmbd: pass through NTLM authentication " Jan Koester
2026-10-19 1:48 ` Namjae Jeon
2026-10-18 17:53 ` [PATCH 2/2] ksmbd: put the workgroup into the NTLMSSP target info's domain names Jan Koester
2026-10-19 2:00 ` Namjae Jeon
2026-10-19 1:31 ` [PATCH 0/2] ksmbd: NTLM pass-through to a domain controller Namjae Jeon
2026-10-19 10:43 ` Stefan Metzmacher
2026-10-19 13:02 ` Namjae Jeon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox