# DF-0844 — Heap OOB read in UFS dirhash (missing d_reclen >= DIRSIZ check)

## Summary

`ufsdirhash_build()` in `sys/vfs/ufs/ufs_dirhash.c` validates that each directory
entry's `d_reclen` fits within its 512-byte `DIRBLKSIZ` chunk (line 201-202), but
does **not** validate that `d_reclen` is large enough to hold the entry's own name
(`d_reclen >= DIRSIZ(NEWDIRFMT, ep)`). A crafted UFS image with a truncated final
entry (`d_reclen=8`, `d_namlen=255`) passes the existing check but causes
`ufsdirhash_hash()` to read 255 bytes from `ep->d_name` — extending 247 bytes past
the entry boundary into adjacent kernel heap (CWE-125).

The check already exists in `ufs_dirbadentry()` (`ufs_lookup.c:636`) but is gated
behind `dirchk=0` (default off) and never called by dirhash.

## Build

```sh
./build.sh
```

Produces `dirhash_oob` (userspace harness) and `dirhash_corrupt` (image corruptor).

## Run

### Userspace harness (deterministic OOB proof)

```sh
./dirhash_oob          # Buggy mode: SIGSEGV at guard page = OOB confirmed
./dirhash_oob --fixed  # Fixed mode: entry rejected, no OOB
```

### Image-based trigger (live kernel)

Requires root on the guest to mount. See `image_trigger.sh` for the full workflow,
or use `dirhash_patch.c` to create a malformed UFS image:

```sh
# Create a UFS image with 25 files in testdir (dir size = 6656 bytes)
# (see image_trigger.sh for vnconfig/newfs/mount commands)

# Patch the last directory entry to be malformed
./dirhash_patch /tmp/df844.img $((1033 * 512))

# Mount and trigger dirhash
mount -o ro /dev/vn0 /mnt
ls -f /mnt/testdir/
sysctl -n vfs.ufs.dirhash_mem  # increases on unpatched, unchanged on patched
```

## Expected results

| Kernel | dirhash_mem | Harness |
|--------|-------------|---------|
| Unpatched (#0) | 9685 → 14177 (dirhash built with malformed entry) | OOB READ CONFIRMED |
| Patched (#1) | 9685 → 9685 (entry rejected, no dirhash) | Entry REJECTED |

## Fix

See `fix.diff`: adds `d_reclen >= DIRSIZ(NEWDIRFMT, ep)` check before hashing.
