# DF-0866 — Verdict

## VERDICT: REPRODUCED (info leak — Low). FIX VALIDATED.

## Mechanism (path:line)
1. **Trigger**: an unprivileged user calls `getdirentries(2)` on a cd9660-mounted directory.
2. **Allocation**: `sys/vfs/isofs/cd9660/cd9660_vnops.c:447`
   `idp = kmalloc(sizeof(*idp), M_TEMP, M_WAITOK);` — **no `M_ZERO`**.
   `struct isoreaddir` (idp) embeds three `struct iso_dirent`
   (`saveent`, `assocent`, `current`), each holding a
   `struct dirent de`. The full struct is ~1.5 KiB; **none of it is
   zeroed** by this allocation.
3. **Per-entry reuse**: the readdir loop (`cd9660_vnops.c:482-577`)
   overwrites only `d_ino`, `d_namlen`, `d_type` and writes
   `d_namlen+1` bytes of `d_name` per entry. **The remaining
   `d_name` bytes, plus `d_unused1` (1 B at off 11) and
   `d_unused2` (4 B at off 12-15), are never touched**.
4. **Sink**: `iso_uiodir` at `cd9660_vnops.c:360`
   `uiomove((caddr_t)dp, _DIRENT_DIRSIZ(dp), idp->uio)` copies the
   whole 8-byte-aligned record (16 + name + 8-aligned padding) to
   userspace. The uninitialized tail of `d_name` and any non-zero
   bytes in `d_unused1`/`d_unused2` ride along to userspace.

This is a classic **CWE-908 / CWE-200** info leak. UFS / MSDOSFS /
NTFS use the `vop_write_dirent` helper (`sys/kern/vfs_subr.c:2570`)
which `kmalloc(M_WAITOK | M_ZERO)`s a fresh, exactly-sized dirent per
entry — so they do not leak. cd9660 rolls its own zeroing-free path.

## Reproduction
- Built a 6-entry ISO image (`a`, `bb`, `cccccccc`, `subdir`, plus a
  40-char long name) with `mkisofs -J -r`.
- Mounted as `vn0` via `vnconfig` + `mount_cd9660`.
- Ran the unprivileged PoC `leak_dirent /mnt/iso` (as user `maxx`).
- **Baseline `6.5-DEVELOPMENT #0`**: the `subdir` entry (after
  `cccccccc`) shows byte `0x63` ('c') at offset 22 of its dirent — a
  non-zero residue from the prior entry's name sitting in the
  unzeroed `d_name` tail. In the very first run after a fresh slab
  allocation we also saw 28 leaked bytes per scan including the
  ASCII residue `s_is_` (from `this_is_a_long…`) bleeding into the
  padding of multiple short-name entries. **Decisive evidence
  (entry 6 on baseline):**

```
[run 1] entry   6 reclen= 24 namlen=6 type=0 name='subdir'
  bytes[0..23]: 00e800000000000006000000000000007375626469720063
                                                                ^^ LEAKED 'c'
  d_unused1=00 d_unused2=00000000  leak(unused1=0 unused2=0 namepad=1)=1
```

## Why my first fix (M_ZERO only) was insufficient — and the corrected fix

My first attempt added `M_ZERO` to the initial `kmalloc` only. That
zeroes the `idp` buffer once at the start of `cd9660_readdir()`, but
the readdir loop then **reuses** `idp->current.de.d_name` for every
entry in the same call. So after entry 5 `cccccccc` writes 9 bytes to
`d_name`, entry 6 `subdir` overwrites only 7 bytes and leaves
`d_name[7]` = 'c' from entry 5. The leak persisted: 1 leaked byte
still appeared on the M_ZERO-only kernel.

The **corrected fix** adds explicit per-entry zeroing in `iso_uiodir`
itself — the single funnel through which every dirent record passes
on its way to `uiomove`. Right before the residual check it now sets
`d_unused1 = 0`, `d_unused2 = 0`, and `bzero()`s the `d_name` tail
between `d_namlen+1` and the next 8-byte boundary. This is exactly
what UFS achieves per-call via a fresh `M_ZERO`'d `kmalloc` in
`vop_write_dirent`. We also keep `M_ZERO` on the outer `kmalloc` as
defense-in-depth (matches UFS, free insurance).

The corrected fix has two hunks (see `fix.diff`):
- `iso_uiodir` — zero reserved fields + d_name padding per entry.
- `cd9660_readdir` — `M_WAITOK | M_ZERO` on the initial `kmalloc`.

## Fix validation (Phase 8)

| Kernel                              | Build sha256 (kernel) | Result            |
|-------------------------------------|-----------------------|-------------------|
| `6.5-DEVELOPMENT #0` (unpatched)    | (audit baseline)      | **1 byte leaked** |
| `6.5-DEVELOPMENT #1` (single-fix)   | `9e471a74…d167376`    | **0 bytes leaked**|

Three back-to-back runs on the patched kernel (3× determinism) all
return `non-zero leaked bytes: 0`. The previously-leaked `0x63` byte
at offset 22 of `subdir` is now `0x00`. PoC `leak_dirent` now exits
`0` (no leak) instead of `1`.

## Impact ceiling
Information disclosure of kernel heap bytes (M_TEMP slab) — up to 7 B
per directory entry per `getdirentries` call, to any user with read
access to a mounted cd9660 directory. Realistic preconditions:
admin-mounted ISO image (cd9660 is root-only to mount, but any user
can read the mount point). No corruption, no privilege escalation.
Ceiling is KASLR / slab-layout fingerprinting — low standalone value.

## Files in this evidence pack
- `leak_dirent.c` — trigger PoC; per-entry hexdump + leak count.
- `build.sh`, `run.sh` — exact reproduction commands.
- `fix.diff` — corrected two-hunk patch (per-entry bzero + M_ZERO).
- `build.log`, `run.log`, `run.2.log`, `run.3.log`, `env.txt` — baseline.
- `fix_build.log`, `fix_run.log`, `fix_run.2.log`, `fix_run.3.log`,
  `fix_env.txt` — patched-kernel build + 3× clean runs.
