# DF-3147 — VERDICT

**reproduced** — unprivileged disclosure of uninitialized kernel heap
memory (8 bytes) through the directory file offset, on a stock cd9660
mount, no crafted image required beyond two ordinary ISO filesystems.

## Mechanism (line-accurate)

* `cd9660_vnops.c:447` — `idp = kmalloc(sizeof(*idp), M_TEMP, M_WAITOK)`
  (no `M_ZERO`); `struct isoreaddir.uio_off` (:332) is not initialized
  anywhere in `cd9660_readdir`.
* `:345-348` — `iso_uiodir()` returns -1 without touching `uio_off` when
  `uio_resid < _DIRENT_DIRSIZ(dp)` (an 8-byte getdents buffer is smaller
  than the 24-byte first dirent).
* `:573-584` — the -1 becomes `error = 0`; the function returns success
  having shipped nothing.
* `:601` — `uio->uio_offset = idp->uio_off;` ← uninitialized heap word.
* `sys/kern/vfs_syscalls.c:4644-4646` — `kern_getdirentries` copies it into
  `fp->f_offset`; `lseek(fd, 0, SEEK_CUR)` hands it to userspace.

## Observation (decisive run, 2026-09-07, guest 6.5-DEVELOPMENT #0)

1. As nobody: full `getdents` walk of `/mnt/b` (cd9660 image B; its
   directory walk ends at byte offset **1670** — 20 crafted entries). The
   freed `isoreaddir` chunk retains `uio_off = 1670`.
2. As nobody, **fresh process**: one 8-byte `getdents` on `/mnt/a`
   (cd9660 image A on a *different* vn device; its own walk would end at
   **338**), then `lseek(fd, 0, SEEK_CUR)`:

```
getdents(8) rc=0 ; lseek(SEEK_CUR) = 0x686 (1670) errno=0
```

The value 1670 cannot come from `/mnt/a` — it is the stale heap content of
the previous, unrelated `isoreaddir` allocation (cross-object disclosure).
Repeated calls return the same value (same recycled chunk); a follow-up
full getdents then continues from the garbage offset (observed benign here
because 1670 < i_size, but the offset is attacker-unknowable kernel state
in general).

## Why Low

8 stale bytes per call; the recycled 912-byte M_TEMP chunk is in practice
usually a previous `isoreaddir`, so typical content is kernel-internal
directory offsets rather than pointers. (With other tenants of the same
size class, or NFS-cookie readdirs on exported cd9660, pointer-class data
can surface.) No memory corruption; deterministic fix. Rated Low /
kernleak, consistent with DF-0866 (same allocation, different field+sink).

## Fix validation

`fix.diff` adds `idp->uio_off = uio->uio_offset;` at the `curroff`
initialization (:473 area). Built together with the DF-3146 fix
(`make -j8 nativekernel && make installkernel`, reboot). On the patched
kernel the same tiny-getdents probe reports offset `0` (the uio's own
offset) instead of stale heap — leak eliminated (fix_run.log).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
