DragonFlyBSD Kernel Audit
← triage · dashboard
DF-2602

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2602 · 10 files
FileTypeDescriptionSize
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
VERDICT.md verdict full analysis: mechanism, trigger, impact, fix
↓ download 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)=0ntencpass=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:Hmount_smbfs requires root (or vfs.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 ntencpass in 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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: 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.
↓ fix.diffDragonFly 6.5-DEVELOPMENT (smbfs.ko rebuilt with fix, loaded as module)

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).