# 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`):

```c
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_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:

```diff
-			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.
