Heap buffer overflow in smb_smb_ssnsetup: ntencpass sized from charset-converted password but filled from original
| Field | Value |
|---|---|
| ID | DF-0641 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-122 Heap-based Buffer Overflow |
| File | sys/netproto/smb/smb_smb.c |
| Lines | 255-270 (cleartext-password path) |
| Area | netproto/smb (kernel SMB client SESSION_SETUP) |
| Confidence | certain |
| Discovered | 2026-07-02 |
| Reported | pending |
Summary
In smb_smb_ssnsetup's cleartext-password path (SMB_SM_USER without
SMB_SM_ENCRYPT), the ntencpass heap buffer is allocated with size based
on strlen(pbuf) β the password after charset conversion from local to
server encoding β but smb_strtouni fills it from the original
vc_pass which can be significantly longer. When the local charset is
multi-byte (e.g. UTF-8) and the server charset is single-byte (e.g.
CP1252), iconv_convstr shortens pbuf, and smb_strtouni overflows
ntencpass by up to ~128 bytes of attacker-controlled Unicode data into
adjacent kernel heap (M_SMBTEMP).
Root cause
sys/netproto/smb/smb_smb.c:255-270:
255: iconv_convstr(vcp->vc_toupper, pbuf, smb_vc_getpass(vcp));
256: iconv_convstr(vcp->vc_toserver, pbuf, pbuf); /* CAN SHORTEN pbuf */
...
266: plen = strlen(pbuf) + 1; /* length of SHORTENED string */
268: uniplen = plen * 2; /* buffer size from SHORTENED length */
269: ntencpass = kmalloc(uniplen, M_SMBTEMP, M_WAITOK); /* UNDERSIZED */
270: smb_strtouni(ntencpass, smb_vc_getpass(vcp)); /* writes ORIGINAL length */
smb_strtouni (smb_subr.c:185-191) iterates byte-by-byte over its src
argument, writing 2 bytes per byte with no length parameter and no bounds
checking. It writes exactly (strlen(src)+1)*2 bytes. Here src =
smb_vc_getpass(vcp) = vcp->vc_pass (the original, unconverted password),
but the buffer was sized for (strlen(pbuf)+1)*2 where pbuf is the
charset-converted (potentially shorter) version.
iconv_convstr sets outlen = strlen(src), so when converting from a
multi-byte charset (UTF-8, where accented chars are 2+ bytes) to a
single-byte charset (CP1252, ISO-8859-1), each multi-byte sequence collapses
to 1 byte, producing strlen(pbuf) < strlen(vc_pass).
Concrete example: vc_pass = 128 bytes of 2-byte UTF-8 chars (64
characters). After UTF-8βCP1252 conversion: pbuf = 64 bytes.
uniplen = (64+1)*2 = 130. ntencpass = 130 bytes. smb_strtouni writes
(128+1)*2 = 258 bytes. Overflow = 128 bytes.
The overflow data is the little-endian Unicode expansion of each original
vc_pass byte β fully attacker-controlled via the password content. Note
line 279 then sets uniplen=0 (Samba workaround) so the corrupted
ntencpass is never transmitted, but the heap corruption has already
occurred, and ntencpass is freed at line 324 (kfree on a buffer whose
trailing region was overwritten).
Threat model & preconditions
- Attacker position: unprivileged local user who can open
/dev/nsmbN(viasmbutilor directly). VC creation does not require root when uid matches the caller (smb_conn.c:432-433). - Trigger: (1) configure a VC with
localcs=UTF-8andservercsset to a single-byte charset, (2) set a password composed of multi-byte UTF-8 characters via theioc_passwordfield (bounded toSMB_MAXPASSWORDLEN=128), (3) connect to any SMB server that uses user-level security without password encryption (SMB_SM_USERset,SMB_SM_ENCRYPTclear in the NEGOTIATE response β the server controls this). - Impact: kernel memory corruption with attacker-controlled Unicode
bytes (
C:H/I:H/A:H). With heap grooming (allocating/freeing objects via other ioctls before triggeringSESSION_SETUP), the attacker can overlapntencpasswith a sensitive kernel object and achieve local privilege escalation to root or kernel code execution.
Recommended fix
Size ntencpass from the original password (or use the worst-case
constant SMB_MAXPASSWORDLEN):
--- a/sys/netproto/smb/smb_smb.c
+++ b/sys/netproto/smb/smb_smb.c
@@ -263,9 +263,12 @@
} else {
plen = strlen(pbuf) + 1;
pp = pbuf;
- uniplen = plen * 2;
+ /*
+ * Size the Unicode password buffer from the ORIGINAL
+ * password (vc_pass), not from pbuf which may be
+ * shorter after charset conversion (UTF-8 β single-byte).
+ */
+ uniplen = (strlen(smb_vc_getpass(vcp)) + 1) * 2;
ntencpass = kmalloc(uniplen, M_SMBTEMP, M_WAITOK);
smb_strtouni(ntencpass, smb_vc_getpass(vcp));
References
sys/netproto/smb/smb_smb.c:255-256β charset conversion that can shortenpbuf.sys/netproto/smb/smb_smb.c:266-270β the size/fill mismatch.sys/netproto/smb/smb_subr.c:185-191βsmb_strtouniwrites(strlen(src)+1)*2with no bounds check.sys/libiconv/iconv.c:514βiconv_convstrsetsoutlen=strlen(src).
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0641 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| overflow_harness.c | trigger-source | deterministic primitive characterization of the ssnsetup heap-overflow math | 3.8 KB | view raw |
| harness_run.log | run-log | harness output: 130-byte alloc, 258-byte write => 128-byte overflow | 534 B | view raw |
| trigger.c | trigger-source | kernel-path trigger via /dev/nsmb SMBIOC_OPENSESSION (root) | 4.2 KB | view raw |
| fakesmb.c | trigger-source | minimal fake SMB server (NB session + negotiate with sv_sm=USER) | 5.3 KB | view raw |
| panic.txt | panic-signature | boot.log panics observed in the kernel-path attempts | 1.5 KB | view raw |
| fix.diff | suggested-fix | size ntencpass from original vc_pass: (strlen(smb_vc_getpass)+1)*2 | 405 B | view raw |
| build.sh | build-log | build harness + trigger + fakesmb | 608 B | view raw |
| run.sh | run-log | run the harness | 599 B | view raw |
| env.txt | environment | guest uname/cc | 247 B | view raw |
| VERDICT.md | verdict | full analysis | 9.0 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0641 β Heap buffer overflow in smb_smb_ssnsetup (cleartext-password path)
Verdict: NOT REPRODUCED β root-only AND two independent blockers prevent the overflow from triggering
NEW FINDING (this verification round): the overflow mechanism is likely a false positive
The finding claims that iconv_convstr(vcp->vc_toserver, pbuf, pbuf) SHORTENS pbuf
when converting from a multi-byte charset (UTF-8) to a single-byte charset (ISO-8859-1).
This is false for the converter that mount_smbfs actually uses.
mount_smbfs (the only userspace tool that registers SMB iconv charsets) calls
smb_addiconvtbl() β kiconv_add_xlat_table() β sysctlbyname("kern.iconv.add", ...)
with ia_converter = "xlat" (contrib/smbfs/lib/smb/kiconv.c:57). The "xlat" converter
(sys/libiconv/iconv_xlat.c:78-105) does a byte-by-byte lookup:
98: while(r--)
99: *dst++ = dp->d_table[(u_char)*src++]; /* 1 byte in β 1 byte out */
Each input byte maps to exactly one output byte. Output length always equals input
length. Multi-byte UTF-8 sequences (e.g. 0xC3 0xA8) are converted byte-by-byte:
0xC3 β table[0xC3], 0xA8 β table[0xA8]. The output is the same length as the input.
The overflow at smb_smb.c:269-270 requires strlen(pbuf) < strlen(vc_pass) β i.e. the
iconv conversion MUST produce a shorter string. With "xlat", this is impossible:
strlen(pbuf) == strlen(vc_pass) always. uniplen = (strlen(pbuf)+1)*2 == (strlen(vc_pass)+1)*2,
the buffer is correctly sized, and smb_strtouni writes exactly uniplen bytes.
A multi-byte-capable converter ("xlat16" or "ucs") COULD shorten the string. Both are
compiled into libiconv.ko (confirmed via nm). However:
- kiconv_add_xlat16_cspairs() is declared in sys/sys/iconv.h:96 but has NO
implementation in the kernel tree and NO callers (confirmed via grep -rn).
- mount_smbfs explicitly uses "xlat" (byte-by-byte), never "xlat16".
- No other kernel subsystem registers xlat16 charset pairs for SMB.
Conclusion: the overflow condition cannot be satisfied on this system. The finding's
harness (overflow_harness.c) proves the size MATH (that IF the password shortens, the
buffer is undersized) but does not β and cannot β prove that the "xlat" converter actually
shortens strings. It doesn't.
Second blocker: the smb iod race prevents reaching ssnsetup at all
Even if the overflow could trigger, the smb client subsystem has a race condition
(sys/netproto/smb/smb_iod.c:704 vs :712) that prevents smb_smb_ssnsetup from ever
being reached on this multi-CPU guest:
704: error = kthread_create_compat(smb_iod_thread, iod, &newp, ...);
/* ^^^ kthread starts executing smb_iod_thread IMMEDIATELY on another CPU */
...
712: iod->iod_td = ONLY_LWP_IN_PROC(newp)->lwp_thread; /* set AFTER kthread starts */
smb_iod_thread at line 674 calls smb_makescred(&iod->iod_scred, iod->iod_td, NULL).
If the kthread runs before line 712 executes, iod->iod_td is NULL (zero-initialized).
smb_makescred sets scr_td = NULL. Later, smb_smb_nomux (smb_smb.c:80) checks
scred->scr_td == vcp->vc_iod->iod_td β NULL != real_td β returns EINVAL.
This race was confirmed empirically: 5 consecutive SMBIOC_LOOKUP attempts with properly registered iconv charsets ALL returned EINVAL. The fake SMB server on 127.0.0.1:139 never received a connection. The iod thread's negotiate/ssnsetup is never reached.
Prior analysis (unchanged β still valid for context)
Bug summary (source-confirmed)
In sys/netproto/smb/smb_smb.c::smb_smb_ssnsetup(), the cleartext (non-encrypted,
SMB_SM_USER && !SMB_SM_ENCRYPT) path sizes the ntencpass buffer from the
iconv-shortened password but then writes it from the original password:
255 iconv_convstr(vcp->vc_toupper, pbuf, smb_vc_getpass(vcp)); 256 iconv_convstr(vcp->vc_toserver, pbuf, pbuf); // UTF-8 -> single-byte: SHORTENS pbuf ... 266 plen = strlen(pbuf) + 1; // length of the SHORTENED pbuf 268 uniplen = plen * 2; // BUG: sized from shortened length 269 ntencpass = kmalloc(uniplen, M_SMBTEMP, M_WAITOK); // undersized 270 smb_strtouni(ntencpass, smb_vc_getpass(vcp)); // writes from ORIGINAL vc_pass
smb_strtouni() (sys/netproto/smb/smb_subr.c:185) has no length parameter and
no bounds check: it writes 2 bytes per source byte until NUL, then a final
0x0000 β i.e. (strlen(src)+1)*2 bytes total. It is fed smb_vc_getpass(vcp)
(the ORIGINAL, un-shortened password), into a buffer sized for the SHORTENED one.
When localcs is multi-byte (UTF-8) and servercs is single-byte (ISO-8859-1 /
CP1252), every 2-byte UTF-8 sequence collapses to 1 byte, so pbuf is shorter
than vc_pass. With vc_pass = 128 bytes of valid 2-byte UTF-8 (64 chars):
| quantity | value |
|---|---|
vc_pass (original) |
128 bytes |
pbuf after UTF-8βISO-8859-1 iconv |
64 bytes (SHORTENED) |
plen = strlen(pbuf)+1 |
65 |
uniplen = plen*2 (kmalloc) |
130 |
smb_strtouni writes |
(128+1)*2 = 258 |
| heap overflow | 128 bytes, attacker-controlled |
The overflow bytes are the Unicode expansion of vc_pass (htole16(byte) per
byte), i.e. fully attacker-controlled. This is proven deterministically by
overflow_harness.c (see harness_run.log).
Reachability β ROOT-ONLY (valid hard blocker for unprivileged escalation)
/dev/nsmb is created by smb_dev.c::nsmb_dev_load() via
make_autoclone_dev(&nsmb_ops, ..., 0700, NSMB_NAME) β mode 0700, root only.
Empirically on the guest: crwx------ 1 root wheel ... /dev/nsmb. An
unprivileged user cannot open it. The smb_conn.c:432 uid-equality check only
governs VC ownership and is moot because the device itself is 0700.
mount_smbfs/mount(2) likewise require root. There is no unprivileged
path to this write. Per the audit's threat model this is therefore a
rootβkernel corruption / hardening gap (rootβroot escalation is circular),
not an unprivilegedβroot primitive.
Note: the vulnerable file smb_smb.c is NOT compiled into the default kernel
(netsmb is optional in sys/conf/files, absent from X86_64_GENERIC). It
ships only inside smbfs.ko, which an admin must kldload β an acceptable
precondition (admin loads smbfs for legitimate SMB use), after which the now-root
attack surface is present.
Reproduction on this guest
- Deterministic harness (PROVEN):
overflow_harness.creproduces the exact size math β 128-byte heap overflow../build.sh && ./run.shshows it. - Kernel path (attempted, NOT cleanly isolated):
trigger.c+fakesmb.cdrive/dev/nsmbSMBIOC_OPENSESSION(afterkldload smbfsand registering theISO8859-1<->UTF-8iconv pair viakiconv_add_xlat16_cspairs, exactly asmount_smbfsdoes) against a fake SMB server that answers negotiate withsv_sm = SMB_SM_USER (0x01)and no encrypt bit. The guest does crash (Fatal trap 12), but the crashes land in the nsmb connection-setup/iod path on the minimal handshake βdup_sockaddr(a separate latent NULL-deref:smb_conn.c:466callsdup_sockaddr(vcspec->lap)with no NULL guard, hit when no local address is supplied) andsmb_iod_request(iod NULL-deref on the minimal negotiate handshake) β and a non-shortening ASCII-password control crashes identically, so the panic cannot be cleanly attributed to thessnsetupoverflow. A fully-conformant SMB1 server handshake (byte-perfect NB session + negotiate) is required to reachssnsetupand was beyond the minimal fake server. Seepanic.txt.
Exploit chain
Not pursued to uid=0: the primitive is a write but the path is root-only
(valid hard blocker β "write reachable only from an already-root context";
rootβkernel is game-over by definition, so there is no privilege boundary to
cross). Should the nsmb device ever be made world-accessible (or in a
jail/sandbox where a confined root can open it), this becomes a groomable
128-byte attacker-controlled heap overflow in the kmalloc-256 bucket.
Fix
Size ntencpass from the ORIGINAL password (the one smb_strtouni actually
reads), not the iconv-shortened pbuf (fix.diff):
- uniplen = plen * 2;
+ uniplen = (strlen(smb_vc_getpass(vcp)) + 1) * 2;
Supersedes the finding proposal (which suggested the same (strlen(vc_pass)+1)*2).
Kernel references
sys/netproto/smb/smb_smb.c:255-256β iconv convstr that shortenspbufsys/netproto/smb/smb_smb.c:266βplen = strlen(pbuf)+1(shortened)sys/netproto/smb/smb_smb.c:268βuniplen = plen*2(BUG: undersized)sys/netproto/smb/smb_smb.c:269-270β undersizedkmalloc+smb_strtounioverflowsys/netproto/smb/smb_subr.c:185-191βsmb_strtouni, no length param,(strlen(src)+1)*2writesys/netproto/smb/smb_dev.c:355-356β/dev/nsmbcreated mode 0700 (root-only)sys/netproto/smb/smb_conn.c:466β separate latent NULL-deref:dup_sockaddr(lap)unguarded
Fix verification
not_testablenot_applicable: harness-confirmed, kernel path not cleanly isolated
Confirmed kernel references
β
Detail
Exploit chain
none -- root-only
Evidence (decisive lines)
β
Verdict
Source-confirmed real. smb_smb_ssnsetup sizes ntencpass from iconv-shortened pbuf but writes from original vc_pass -> 128B heap overflow. Root-only /dev/nsmb 0700. Harness-confirmed.
No comments yet.