[PATCH] smb: client: bound the NTLMv2 blob to the SMB1 session-setup byte area
Bryam Vargas via B4 Relay <[email protected]> Mon, 27 Jul 2026 11:50:34 -0500
| Newsgroups | gmane.linux.kernel,gmane.network.samba.internals,gmane.linux.kernel.cifs |
|---|---|
| Message-ID | <[email protected]> |
From: Bryam Vargas <[email protected]> sess_auth_ntlmv2() copies the NTLMv2 response blob into the fixed 2000-byte byte area that sess_alloc_buffer() allocates, without checking that it fits. Since commit 33cfdd726381 ("smb: client: fix session setup against servers that require SPN") ses->auth_key.len carries the SPN, so it grows with strlen(server->hostname), and extract_hostname() does not cap that. A hostname of a few hundred bytes overflows the byte area, and server->hostname can come from a DFS referral target by way of __reconnect_target_locked(), i.e. from the server. Reject the session setup when the blob does not fit. The SMB2/3 consumer of auth_key.len is unaffected: size_of_ntlmssp_blob() sizes its allocation from it. A hostname at the 253-byte DNS limit needs 590 of the 775 available. Fixes: 33cfdd726381 ("smb: client: fix session setup against servers that require SPN") Cc: [email protected] Signed-off-by: Bryam Vargas <[email protected]> --- Reproduced on v7.2-rc1 with KASAN and CONFIG_CIFS_ALLOW_INSECURE_LEGACY=y, against a fake SMB1 server whose only job is to answer negprot with EncryptionKeyLength == 8 and no CAP_EXTENDED_SECURITY. That picks CIFS_NEGFLAVOR_UNENCAP, which routes the session setup through sess_auth_ntlmv2(). The server never has to answer the session setup itself: the copy happens while the client builds the request, before sess_sendreceive(). Reproducer. A UNC whose hostname is long enough that the SPN pushes auth_key.len past the byte area: mount(2)("//<2000 x 'h'>/share", "/mnt", "cifs", 0, "vers=1.0,sec=ntlmv2,user=u,password=p,ip=127.0.0.1,port=4445") mount(8) will not deliver that. It prefers the new mount API, where fsconfig(FSCONFIG_SET_STRING) truncates the source at 255 bytes (strndup_user(_value, 256)). copy_mount_string() takes PATH_MAX, so the reproducer calls mount(2) directly. A/B on v7.2-rc1, all three arms in one boot with kasan.fault=report, the clean ones first since KASAN only reports once per boot: 3-char hostname, unpatched: memcpy of 90 bytes, clean 2000-char hostname, patched: "NTLMv2 response blob too long (4084)", mount fails -EIO, clean 2000-char hostname, unpatched: BUG: KASAN: slab-out-of-bounds in sess_auth_ntlmv2+0x3d1/0xee0 [cifs] Write of size 4084 at addr ffff88810d921000 by task smb1_mount/1121 __asan_memcpy+0x3c/0x60 sess_auth_ntlmv2+0x3d1/0xee0 [cifs] CIFS_SessSetup+0x1bf/0x350 [cifs] cifs_setup_session+0x248/0xaf0 [cifs] cifs_get_smb_ses+0xf46/0x17b0 [cifs] dfs_mount_share+0x1e5/0x21f0 [cifs] Allocated by task 1121: __kmalloc_cache_noprof+0x153/0x360 sess_alloc_buffer+0x176/0x2e0 [cifs] sess_auth_ntlmv2+0xc1/0xee0 [cifs] The buggy address is located 0 bytes inside of allocated 2000-byte region [ffff88810d921000, ffff88810d9217d0) auth_key.len there is CIFS_SESS_KEY_SIZE + sizeof(struct ntlmv2_resp) + the AV-pair blob + 2 * (5 + strlen(hostname)) + the two trailing AV pairs, so 4100 for that hostname and the default WORKGROUP domain, and a copy of 4084: 2084 past the end. It does not stop at the blob either. The strings that unicode_ssetup_strings() appends afterwards land on the next object, and the following _raw_spin_lock() takes a GP fault on 0x2000530046005630, which is UTF-16 from that tail. The other two writers of that byte area stay inside it. sess_auth_kerberos() and _sess_auth_rawntlmssp_assemble_req() put only the OS and domain strings there -- the SPNEGO blob travels in iov[1] by pointer, uncopied -- which caps them near 711 bytes. Every other consumer of auth_key.len sizes its allocation from it: size_of_ntlmssp_blob() on the SMB2/3 side, and the kmemdup() in sess_establish_session(). Nothing that works today stops working. The other way to reach the limit is a 256-byte domain next to a 253-byte hostname, needing 1084 bytes; but that pair already writes 2309 into the 2000-byte area on current mainline, so it is broken before this patch rather than by it. The DFS route is by inspection, not reproduced. __reconnect_target_locked() replaces server->hostname with extract_hostname(<referral target>), and the reconn_set_ipaddr_from_hostname() return value is logged and then overwritten by generic_ip_connect(), so a hostname that does not resolve still reaches session setup. 33cfdd726381 first appeared in v6.17-rc1. Growing the byte area instead would also work, but put_bcc() puts that count on the wire as a __u16, so it only moves the ceiling. Happy to switch if you'd rather not fail the mount. --- fs/smb/client/smb1session.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index 83bfbf0c068e..d610a32cc4f0 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -16,6 +16,22 @@ #include "cifs_unicode.h" #include "cifs_debug.h" +/* byte area sess_alloc_buffer() hands to a session setup request */ +#define SESS_SETUP_BYTE_AREA 2000 + +/* what unicode_ssetup_strings() appends after the blob: alignment pad, + * user, domain, and three cifs_strtoUTF16() calls capped at 32 characters + */ +#define SESS_SETUP_STRINGS_MAX \ + (1 \ + + 2 * CIFS_MAX_USERNAME_LEN + 2 \ + + 2 * CIFS_MAX_DOMAINNAME_LEN + 2 \ + + 2 * 32 + 2 * 32 + 2 \ + + 2 * 32 + 2) + +#define SESS_SETUP_BLOB_MAX \ + (SESS_SETUP_BYTE_AREA - SESS_SETUP_STRINGS_MAX) + struct sess_data { unsigned int xid; struct cifs_ses *ses; @@ -330,8 +346,7 @@ sess_alloc_buffer(struct sess_data *sess_data, int wct) */ sess_data->buf0_type = CIFS_SMALL_BUFFER; - /* 2000 big enough to fit max user, domain, NOS name etc. */ - sess_data->iov[2].iov_base = kmalloc(2000, GFP_KERNEL); + sess_data->iov[2].iov_base = kmalloc(SESS_SETUP_BYTE_AREA, GFP_KERNEL); if (!sess_data->iov[2].iov_base) { rc = -ENOMEM; goto out_free_smb_buf; @@ -449,6 +464,14 @@ sess_auth_ntlmv2(struct sess_data *sess_data) goto out; } + if (ses->auth_key.len - CIFS_SESS_KEY_SIZE > + SESS_SETUP_BLOB_MAX) { + cifs_dbg(VFS, "NTLMv2 response blob too long (%u)\n", + ses->auth_key.len - CIFS_SESS_KEY_SIZE); + rc = -EIO; + goto out; + } + memcpy(bcc_ptr, ses->auth_key.response + CIFS_SESS_KEY_SIZE, ses->auth_key.len - CIFS_SESS_KEY_SIZE); bcc_ptr += ses->auth_key.len - CIFS_SESS_KEY_SIZE; --- base-commit: 4235cb24ec1e8e96843f3671ba4da2a6ccca2c7b change-id: 20260727-b4-disp-f5a64b1f-7c3c49d601c4 Best regards, -- Bryam Vargas <[email protected]>