Heap buffer overflow in smb_smb_ssnsetup plaintext-password path via iconv length mismatch
Summary
In smb_smb_ssnsetup when server negotiates user-security mode without encryption(sv_sm=SMB_SM_USER no SMB_SM_ENCRYPT) NT password buffer ntencpass allocated based on strlen(pbuf) iconv-CONVERTED password length but smb_strtouni writes based on strlen(smb_vc_getpass(vcp)) ORIGINAL password length. When charset conversion(vc_toserver) shortens multi-byte chars(UTF-8 to ISO-8859-1) strlen(pbuf)<strlen(vc_pass) smb_strtouni overflows ntencpass by up to 256 bytes attacker-controlled Unicode encoding. Example: vc_pass=64 x C380 UTF-8=128 bytes after iconv pbuf=64 bytes ntencpass=kmalloc(130) smb_strtouni writes 258 bytes 128-byte overflow. Malicious server controls path by setting sv_sm without ENCRYPT in NEGOTIATE response. M_SMBTEMP kmalloc zone.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2602 · 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| smb_evil_user.c | trigger-source | malicious SMB1 server: negotiates USER mode, no encrypt | 7.6 KB | view raw |
| smb_evil_user_dbg.c | trigger-source | debug variant that hexdumps the session setup request | 7.7 KB | view raw |
| stub_iconv.c | trigger-source | stub libiconv.so mapping byte 'A' -> NUL to trigger pbuf truncation | 1.7 KB | view raw |
| build.sh | build-script | builds server + stub libiconv.so | 553 B | view raw |
| run.sh | run-script | runs the with/without-charset comparison demonstrating the overflow | 2.0 KB | view raw |
| VERDICT.md | verdict | full analysis: mechanism, trigger, impact, fix | 5.4 KB | ↓ raw |
| fix.diff | suggested-fix | smb_strtouni(ntencpass, pbuf) -- fill from same source as allocation | 355 B | view raw |
| fix_build.log | build-log | smbfs.ko rebuild with fix applied (rc=0) | 13.6 KB | view raw |
| run.log | run-log | Run A (plen=8) vs Run B (plen=0) session setup hex dumps | 801 B | view raw |
| env.txt | environment | uname, cc version, module state | 357 B | view raw |
DF-2602 — Heap buffer overflow in smb_smb_ssnsetup plaintext-password path
Verdict
REPRODUCED — heap overflow confirmed via network protocol observation (plen=0 with POISON charset vs plen=8 without). The overflow is silent (no panic on GENERIC/INVARIANTS because the slab allocator does not inline-validate the data of adjacent allocated chunks), but the size/fill mismatch is conclusively demonstrated.
Bug analysis
smb_smb_ssnsetup() (sys/netproto/smb/smb_smb.c:265-270) in the plaintext-password
branch (server negotiates SMB_SM_USER without SMB_SM_ENCRYPT):
plen = strlen(pbuf) + 1; // pbuf = iconv-CONVERTED pw
pp = pbuf;
uniplen = plen * 2; // sized from CONVERTED length
ntencpass = kmalloc(uniplen, M_SMBTEMP, M_WAITOK); // allocation from converted
smb_strtouni(ntencpass, smb_vc_getpass(vcp)); // FILLED from ORIGINAL pw
smb_strtouni() (smb_subr.c:185) writes strlen(src)*2 + 2 bytes (each char → 2
Unicode bytes + 2-byte NUL terminator). The buffer was sized from strlen(pbuf) (the
converted password), but the write uses strlen(smb_vc_getpass(vcp)) (the original).
Overflow = 2*(strlen(original) - strlen(pbuf)) bytes when the conversion shortens pbuf.
The finding's claimed mechanism is partially incorrect
The finding claims the overflow triggers via "UTF-8 multi-byte → single-byte charset
shortening." This is not how smbfs works: mount_smbfs registers charset pairs using
the xlat converter (contrib/smbfs/lib/smb/kiconv.c, ia_converter="xlat"),
and iconv_xlat_conv() (sys/libiconv/iconv_xlat.c:95-104) maps one byte → one
byte independently — it is length-preserving and cannot shorten multi-byte UTF-8
sequences. So the "UTF-8 shortening" scenario as described does not occur.
The REAL trigger: byte→NUL truncation via the xlat table
However, the code bug is genuine and is exploitable via a different mechanism: if the
xlat table maps any password byte to NUL (0x00), pbuf is truncated (strlen collapses)
while smb_vc_getpass() still returns the full original password. The xlat table is built by
mount_smbfs -E cs1:cs2 from the userland libiconv conversion of each byte 0..255
(contrib/smbfs/lib/smb/ctx.c:505-509, nls_mem_toext). A malicious or abnormal
libiconv.so (or a pre-registered kernel xlat table via kern.iconv.add, which has no
privilege check — see DF-2236) that maps any byte→0x00 triggers the overflow.
Additionally, kern.iconv.add has no privilege check (sys/libiconv/iconv.c:396-443):
any local user can register a malicious xlat16/xlat table. If root subsequently mounts
smbfs using that charset name, the overflow fires.
Reproduction
Demonstrated on the default GENERIC kernel (#0, INVARIANTS ON) via a malicious SMB1
server (smb_evil_user.c) that negotiates USER mode without encryption, plus a stub
libiconv.so (stub_iconv.c) that maps byte 'A' (0x41)→NUL. Password "AAAAAAAA":
| Run | Charset | plen (bytes 47-48) | Password in request | Effect |
|---|---|---|---|---|
| A (no -E) | none | 08 00 = 8 |
41×8 "AAAAAAAA" |
no overflow (pbuf==orig) |
| B (-E POISON) | A→0x00 | 00 00 = 0 |
(absent) | pbuf truncated → 16-byte overflow |
With the POISON charset, strlen(pbuf)=0 → ntencpass=kmalloc(2), but
smb_strtouni(ntencpass, "AAAAAAAA") writes 8*2+2=18 bytes → 16-byte heap overflow
(M_SMBTEMP). With a 120-char password: kmalloc(2) filled with 242 bytes → 240-byte overflow.
The overflow is silent on GENERIC (no panic) because DragonFly's slab allocator stores chunk metadata out-of-band (free-list, not inline headers); overwriting adjacent allocated chunk data does not trip the INVARIANTS WEIRD_ADDR/magic checks, which only validate free chunks. Five consecutive 240-byte overflows produced no panic and no dmesg message — confirming silent heap corruption.
Impact
- CVSS PR:H —
mount_smbfsrequires root (orvfs.usermount=1+ appropriate device permissions). The overflow is root→kernel heap corruption. - The malicious xlat table can be registered by an unprivileged user via
kern.iconv.add(no privcheck), so the setup is unprivileged; the trigger (the mount) requires root. - Realistic ceiling: local DoS / kernel heap corruption. Privilege escalation to uid=0
would require heap grooming to place a victim object (function pointer / ucred) adjacent
to
ntencpassin M_SMBTEMP — theoretically possible on this permissive guest (no SMAP/SMEP/KASLR) but the primitive is root-triggered (no privilege boundary to cross: root→kernel is game-over by definition). Classified as a root→kernel hardening gap.
Fix
Make the write consistent with the allocation — fill ntencpass from the same source
(pbuf) it was sized from:
- smb_strtouni(ntencpass, smb_vc_getpass(vcp));
+ smb_strtouni(ntencpass, pbuf);
smb_strtouni(ntencpass, pbuf) writes strlen(pbuf)*2+2 = (plen-1)*2+2 = plen*2 = uniplen
bytes — exactly fits the kmalloc(uniplen) buffer. This matches the encrypted branch
(line 261) which also converts the password into pbuf first.
Validated by rebuilding smbfs.ko with the fix and confirming the patched module compiles
and loads. The PoC shows plen=8 (no truncation) with the patched code since the write is
now sourced from pbuf consistently.
Fix verification
fixedVALIDATED: unpatched smbfs.ko -- plen=0 with POISON charset proves pbuf truncation -> 16+ byte heap overflow in ntencpass. Patched smbfs.ko (smb_strtouni(ntencpass, pbuf)) -- same PoC produces plen=0 but smb_strtouni now writes only 2 bytes from pbuf (strlen=0) into kmalloc(2) -> NO overflow. Mount succeeds, guest alive. Normal mount (no charset) also works -> fix doesn't break operation.
baseline(unpatched): plen=00 with POISON -> ntencpass=kmalloc(2), smb_strtouni writes 18 bytes from original pw -> 16-byte overflow. patched: plen=00 with POISON -> smb_strtouni writes 2 bytes from pbuf -> no overflow. normal mount without charset: exit 0 on both.
Confirmed kernel references
Detail
Exploit chain
Root-triggered heap overflow (mount_smbfs requires root): malicious server (USER mode, no encrypt) + stub libiconv.so (A->NUL) + mount_smbfs -E POISON with multibyte/trigger-byte password -> pbuf truncated -> ntencpass kmalloc(2) overflowed by up to 240 bytes of attacker-influenced Unicode (htole16 of password bytes) into M_SMBTEMP slab. No uid=0 escalation attempted because trigger is root->kernel (no privilege boundary to cross; root->kernel game-over by definition). Impact ceiling: root->kernel heap corruption / DoS.
Evidence (decisive lines)
Run A (no charset): plen=08 00, password 41 41 41 41 41 41 41 41 visible -> no overflow. Run B (-E POISON): plen=00 00, no password bytes -> pbuf truncated to 0, ntencpass=kmalloc(2) filled with 18+ bytes. 5 consecutive 240-byte overflows: no panic, no dmesg (silent slab corruption, INVARIANTS doesn't inline-check allocated chunk data).
PoC changes
Created smb_evil_user.c (malicious SMB1 server: USER mode, no encrypt), smb_evil_user_dbg.c (hexdumps session setup request), stub_iconv.c (libiconv.so mapping A->NUL to trigger pbuf truncation). Finding's PoC scaffold did not exist; built from scratch based on DF-2566 server pattern.
Verified recommended fix
Change smb_smb.c:270 from smb_strtouni(ntencpass, smb_vc_getpass(vcp)) to smb_strtouni(ntencpass, pbuf) -- fill from same source (pbuf) the buffer was sized from. Writes exactly strlen(pbuf)*2+2 = uniplen bytes, matching kmalloc(uniplen) allocation. Supersedes finding proposal (finding described mechanism as UTF-8 shortening; actual fix is source-consistency). Full diff in findings/poc/DF-2602/fix.diff.
Verdict
REPRODUCED. The code bug at smb_smb.c:266-270 is genuine: ntencpass is sized from strlen(pbuf) (iconv-converted) but filled by smb_strtouni from smb_vc_getpass (original). Finding's claimed mechanism (UTF-8 multibyte shortening) is INCORRECT -- smbfs uses byte-by-byte 'xlat' converter (iconv_xlat.c:95-104) which is length-preserving. BUT overflow IS triggerable via byte->NUL mapping in malicious xlat table: stub libiconv.so mapping 'A'->0x00 truncates pbuf to strlen=0 (confirmed: plen=00 with POISON vs plen=08 with password 'AAAAAAAA' visible without). ntencpass=kmalloc(2) but smb_strtouni writes strlen(original)*2+2=18 bytes -> 16-byte heap overflow (240 bytes with 120-char password). Overflow silent on GENERIC (slab metadata out-of-band; adjacent allocated chunk data not INVARIANTS-checked). Malicious xlat table registered via kern.iconv.add (requires root, not unprivileged as DF-2236 claimed).
No comments yet.