# DF-0715 — smb_memdupin signed-length validation accepts zero

## Verdict
**REPRODUCED** — root→kernel panic (DoS). The bug is real and confirmed by a
clean `Fatal trap 12` at `dup_sockaddr+0x18` reading address `0xfffffffffffffff8`
(= `ZERO_LENGTH_PTR`). The fix (`if (len <= 0 || len > 8*1024) return NULL`)
closes it: the patched kernel+module returns `ENOMEM` cleanly, no panic.

## Severity / impact
**Low** — root→kernel DoS. The only path to `smb_memdupin` is via
`/dev/nsmb` (the nsmb clone device), which is created `0700 root:wheel`
(`smb_dev.c:355-356`). An unprivileged user cannot open the device
(`vfs.usermount=0`, no devfs rules loosening nsmb). An admin who has loaded
`smbfs.ko` (normal SMB-client setup) presents the device to root, and root can
then panic the kernel. No memory-corruption primitive is derived — the
sentinel pointer `(void*)-8` is dereferenced as a `struct sockaddr *`, causing
a non-canonical-address GPF before any attacker-controlled write lands. This is
a hardening gap, not an escalation vector.

## Mechanism (trigger → primitive → effect)

### Trigger
Open `/dev/nsmb` (root only) and issue `SMBIOC_OPENSESSION` with an
`smbioc_ossn` struct where `ioc_svlen == 0` (and `ioc_server != NULL`,
`ioc_user[0] != 0`, `ioc_localcs[0] != 0`, `ioc_opt = SMBVOPT_CREATE`).

### Data flow (every hop cited)
1. `nsmb_dev_ioctl` (`smb_dev.c:171`) dispatches `SMBIOC_OPENSESSION` →
   `smb_usr_opensession` (`smb_usr.c:164`).
2. `smb_usr_opensession` → `smb_usr_vc2spec` (`smb_usr.c:171` → `smb_usr.c:60`).
3. `smb_usr_vc2spec` calls `smb_memdupin(dp->ioc_server, dp->ioc_svlen)` with
   `ioc_svlen = 0` (`smb_usr.c:74`).
4. **THE BUG** — `smb_memdupin` (`smb_subr.c:137-148`):
   ```c
   if (len > 8 * 1024)   // 0 > 8192 == false → passes
       return NULL;
   p = kmalloc(len, M_SMBSTR, M_WAITOK);   // kmalloc(0) → ZERO_LENGTH_PTR
   ```
   DragonFly `kmalloc(0)` returns `ZERO_LENGTH_PTR = ((void*)-8)`
   (`kern_slaballoc.c:193,889-890`), **not** NULL. `copyin(umem, p, 0)` returns 0
   (zero-length copy), so `smb_memdupin` returns `(void*)-8`.
5. Back in `smb_usr_vc2spec`: `spec->sap = (void*)-8`;
   `if (spec->sap == NULL) return ENOMEM` (`smb_usr.c:75-76`) — **misses the
   sentinel** (it is not NULL).
6. `smb_usr_opensession` → `smb_sm_lookup` (`smb_usr.c:176`) →
   `smb_sm_lookupint` (empty VC list → ENOENT) → since `SMBV_CREATE` is set,
   `smb_vc_create` (`smb_conn.c:202,417`).
7. **THE PANIC** — `smb_vc_create` calls `dup_sockaddr(vcspec->sap)`
   (`smb_conn.c:462`). `dup_sockaddr` (`uipc_socket2.c:809`) does:
   ```c
   sa2 = kmalloc(sa->sa_len, M_SONAME, M_INTWAIT);
   ```
   reading `((struct sockaddr *)-8)->sa_len` at address `0xFFFFFFFFFFFFFFF8` —
   a non-canonical x86-64 address → **GPF → Fatal trap 12**.

### Why the privilege check doesn't help
`smb_suser(cred)` (`smb_conn.c:428`) runs inside `smb_vc_create`, **after**
`smb_memdupin` has already returned the sentinel. The check only gates uid/gid
selection (`smb_conn.c:431-434`); it does not block the `dup_sockaddr` call at
line 462. The real privilege boundary is the `/dev/nsmb` device open permission
(`0700 root:wheel`).

### The `len < 0` variant
With `ioc_svlen < 0`, the signed check `len > 8*1024` is false (negative is
never > 8192), and `kmalloc((size_t)negative, ...)` wraps to a huge value,
panicking in the slab allocator. The fix (`len <= 0`) closes both variants with
one check.

## Exploit chain
**Not applicable** — this is a pure DoS / hardening gap, not a memory-corruption
primitive. The `ZERO_LENGTH_PTR` sentinel is dereferenced as a pointer, causing
a GPF before any attacker-controlled data reaches a write target. No heap
grooming, no victim object, no escalation chain is possible. The finding is
correctly classified as Low severity (root→kernel DoS).

## PoC changes
Authored from scratch (no prior PoC existed in `findings/poc/DF-0715/`):
- `smb_memdupin_zero.c` — opens `/dev/nsmb`, fills `smbioc_ossn` with
  `ioc_svlen=0` + `SMBVOPT_CREATE`, issues `SMBIOC_OPENSESSION`. Built with the
  guest's installed `<netsmb/smb_dev.h>`.
- `build.sh` / `run.sh` — exact build/run commands.
- `fix.diff` — git-apply-able unified diff: `if (len <= 0 || len > 8*1024)
  return NULL` in both `smb_memdupin` and `smb_memdup` (`smb_subr.c:140,157`).

## Fix validation (Phase 8)
- **Baseline (unpatched #0 kernel + unpatched smbfs.ko):** PoC →
  `Fatal trap 12: page fault while in kernel mode`, `fault virtual address =
  0xfffffffffffffff8`, `Stopped at dup_sockaddr+0x18: movzbl (%rdi),%edi`.
  Guest down. Reproduced twice from fresh `with-src` resets.
- **Patched (#1 kernel + rebuilt smbfs.ko):** applied `fix.diff` to
  `/usr/src`, rebuilt `smbfs.ko` module (`make` in `sys/vfs/smbfs/`), installed
  to `/boot/kernel/smbfs.ko`, `kldload smbfs`. PoC → `SMBIOC_OPENSESSION:
  Cannot allocate memory` (errno=12 = ENOMEM), exit 0, guest stays up.
  Reproduced twice (deterministic).
- **fix_status: fixed** — clean before/after contrast.

## Recommended fix
`fix.diff` changes `smb_memdupin` and `smb_memdup` (both in
`sys/netproto/smb/smb_subr.c`) from `if (len > 8 * 1024) return NULL` to
`if (len <= 0 || len > 8 * 1024) return NULL`. This rejects zero (which causes
`kmalloc(0)` → `ZERO_LENGTH_PTR` sentinel confusion) and negative (which wraps
to a huge `size_t` in `kmalloc`) with a single signed check. **Matches the
finding proposal** (`if(len<=0||len>8*1024) return NULL`).
