# DF-0600 — Verdict

## Verdict: REPRODUCED (defense-in-depth / hardening gap; root-only reachability)

The bug is **real**: `dup_sockaddr()` (sys/kern/uipc_socket2.c:808-816) trusts
`sa->sa_len` unconditionally (`kmalloc(sa->sa_len); bcopy(sa, sa2, sa->sa_len)`),
and `smb_memdupin()` (sys/netproto/smb/smb_subr.c:137-148) allocates a buffer of
size `ioc_svlen`/`ioc_lolen` (the user-supplied length) with **no cross-check**
against the `sa_len` field inside the copied-in `sockaddr`. A mismatch
(`sa_len > ioc_svlen`) drives a heap OOB read of up to ~251 bytes (sa_len is
`u_char`, max 255; allocation 4 bytes → 251 bytes OOB). The finding is correct.

However, the impact is **capped at a root-only hardening gap** — not an
unprivileged→root escalation — for a definitive reason:

## Threat model / reachability — root-only (valid Phase 6 hard blocker)

The vulnerable code path (`SMBIOC_OPENSESSION` ioctl → `smb_usr_opensession` →
`smb_sm_lookup` → `smb_vc_create` → `dup_sockaddr`) is reachable **only by root**:

1. **Module not loaded by default.** `netsmb`/`smbfs` is `optional netsmb` in
   `sys/conf/files` and is **NOT** in `X86_64_GENERIC` (sys/config/X86_64_GENERIC
   lists only `smbus`/`smbacpi` — the SMBus hardware bus, unrelated). The guest
   boots with no `/dev/nsmb*`. Loading requires `kldload smbfs` → **root only**.
2. **Device is 0700 root:root.** `make_autoclone_dev(... 0700, NSMB_NAME)` at
   sys/netproto/smb/smb_dev.c:355-356. Opening `/dev/nsmb` requires root.
3. **No setuid helper.** `mount_smbfs` is not installed on the guest; no
   setuid-root SMB helper exists (`find / -perm -4000 -name '*smb*'` → empty).

An unprivileged user **cannot** load the module, open the device, or issue the
ioctl. Root→kernel is game-over by definition; there is no privilege boundary
to cross. This is one of the explicitly-listed valid Phase 6 hard blockers.
Per the bright-line rule, this finding is **not** a `uid0` escalation — it is a
root→kernel hardening gap (the finding correctly rates it **Low**).

## Mechanism (confirmed, every hop cited)

1. `nsmb_dev_ioctl` `SMBIOC_OPENSESSION` (smb_dev.c:187) → `smb_usr_opensession`
   (smb_usr.c:164-178).
2. `smb_usr_vc2spec` (smb_usr.c:60): `spec->sap = smb_memdupin(dp->ioc_server,
   dp->ioc_svlen)` — allocates exactly `ioc_svlen` bytes (smb_subr.c:137-148),
   no check that `sap->sa_len <= ioc_svlen`.
3. `smb_usr_opensession` sets `SMBV_CREATE` (smb_usr.c:175) → `smb_sm_lookup`
   (smb_conn.c:183): empty VC list → `smb_sm_lookupint` returns ENOENT →
   `smb_vc_create` (smb_conn.c:202).
4. `smb_vc_create` (smb_conn.c:462): `vcp->vc_paddr = dup_sockaddr(vcspec->sap)`.
5. `dup_sockaddr` (uipc_socket2.c:809-815): `kmalloc(sa->sa_len); bcopy(sa, sa2,
   sa->sa_len)` — if `sa_len > ioc_svlen`, the `bcopy` reads `(sa_len -
   ioc_svlen)` bytes past the `smb_memdupin` allocation → **heap OOB read**.
6. sa_len is `u_char` (max 255); with `ioc_svlen=4` the OOB is 251 bytes, all
   within the same slab page → **silent read** (no panic from the OOB itself).
   The leaked bytes land in `vcp->vc_paddr`; reachable indirectly via the
   `CONNADDREQ` 1-bit comparison oracle (smb_conn.h:178, smb_sm_lookupint:143).

## Reproduction on the unpatched kernel (6.5-DEVELOPMENT #0)

The PoC (`oob_read.c`) opens `/dev/nsmb`, issues `SMBIOC_OPENSESSION` with
`ioc_svlen=4` and `sa_len=255` (+`SMBVOPT_CREATE`), and `ioc_local=NULL`.

**Result: kernel PANIC.** The first `dup_sockaddr(vcspec->sap)` (smb_conn.c:462)
executes the 251-byte OOB read silently (in-slab-page), then the second
`dup_sockaddr(vcspec->lap=NULL)` (smb_conn.c:466) dereferences NULL → page fault.
Serial-console panic signature (boot.log):
```
Fatal user address access from kernel mode from oob_read at ffffffff806c9c98
Fatal trap 12: page fault while in kernel mode
fault virtual address    = 0x0
Stopped at dup_sockaddr+0x18:  movzbl (%rdi),%edi
```
The OOB read at line 462 provably executed before the incidental NULL-lap deref
at line 466 (the `do{...}while` body is sequential; `vc_paddr` was assigned
non-NULL). Deterministic across 2 runs (PIDs 952, 874).

## Escalation assessment — blocked (valid hard blocker)

Primitive is an **OOB read** (not a write): `bcopy` reads past the source
allocation into a fresh destination. No corruption of kernel objects occurs
(the destination is freshly `kmalloc`'d). The leaked bytes are only reachable
via the indirect `CONNADDREQ` 1-bit oracle. Combined with the **root-only
reachability**, there is no unprivileged escalation path:
- The primitive is read-only (no write/corruption) → no slab grooming, no
  pointer overwrite, no refcount attack.
- Root→kernel is game-over; no privilege boundary to cross.
- No setuid helper / world-readable device / auto-load path exists.

This is a **valid hard blocker** (read-only primitive + root-only reachability).

## Fix (VALIDATED)

The validated fix lives in `smb_usr.c` (`smb_usr_vc2spec`), validating `sa_len`
against the actual allocation **before** the sockaddr reaches `dup_sockaddr`.
This supersedes the finding's smb_conn.c proposal (which triggered a pre-existing
NULL-deref in `smb_vc_disconnect` when the EINVAL cleanup freed a partially-
initialized VC whose `vc_iod` was NULL — see PoC-changes note below).

**Before (unpatched, #0):** PoC → PANIC at `dup_sockaddr+0x18` (after the 251-byte
OOB read). Guest down.
**After (fixed smbfs.ko):** PoC → `ioctl rc=-1 errno=22 (EINVAL)`, clean exit,
guest up. Deterministic over 2 runs.

The fix checks `sa_len < 2 || sa_len > ioc_svlen` (and likewise for `lap`) right
after each `smb_memdupin` call, returning `EINVAL` before `smb_sm_lookup` /
`smb_vc_create` is ever entered — so no VC is allocated and no cleanup path runs.

## PoC changes

- Wrote `oob_read.c` (the finding shipped only a README): opens `/dev/nsmb`,
  issues `SMBIOC_OPENSESSION` with `ioc_svlen=4, sa_len=255, SMBVOPT_CREATE`.
  Added `setvbuf(stdout, NULL, _IONBF, 0)` so output survives a kernel crash.
- Wrote `valid_sa.c` (diagnostic): confirmed the `smb_iod_request` crash is a
  **pre-existing** module issue (fires on any VC creation with a valid sa_len),
  independent of DF-0600. This is why the finding's smb_conn.c fix location
  doesn't validate cleanly: the EINVAL cleanup path (`smb_vc_put → smb_vc_gone →
  smb_vc_disconnect:681 → smb_iod_request(vc_iod=NULL)`) NULL-derefs because
  `vc_iod` was never created. The smb_usr.c fix avoids this entirely by
  rejecting the bad sa_len before VC allocation.
- Authored `fix.diff` against `sys/netproto/smb/smb_usr.c` (supersedes the
  finding's smb_conn.c proposal).

## Impact ceiling

Low. Root-only OOB heap read of ≤251 bytes, observable only via a 1-bit indirect
comparison oracle. No write primitive, no unprivileged reachability. The finding's
CVSS (3.0, Low) and CWE-125 classification are accurate.
