# DF-0851 — VERDICT

## Verdict
**NOT REPRODUCED at runtime** — the bug is a **real code-level defect** (stale pointer dereference confirmed by source trace) but the race window is too narrow to trigger deterministically. The fix eliminates the stale read by construction and is validated (compiles, boots, PoC runs cleanly).

## Finding
**File:** `sys/vfs/isofs/cd9660/cd9660_vfsops.c`, function `iso_mountfs()`, lines 407–466  
**Severity:** Low  
**Title:** Stale dereference of rootp after pribp released — use-after-free style read on buffer cache recycle

## Mechanism (code-level trace)

The bug exists in the source code, confirmed line-by-line:

1. **Line 329–347:** `bread(devvp, ...)` reads the ISO Primary Volume Descriptor (PVD) into buffer cache buffer `pribp`. `pri = (struct iso_primary_descriptor *)vdp` where `vdp = (struct iso_volume_descriptor *)pribp->b_data` (line 333). So `pri` points into `pribp->b_data`.

2. **Line 407–410:** `rootp = (struct iso_directory_record *)pri->root_directory_record` — `rootp` is derived from `pri`, which points into `pribp->b_data`. `rootp` is a **pointer into the buffer cache data of `pribp`**.

3. **Line 428–430:** Valid reads of `rootp` while `pribp` is still held:
   - `bcopy(rootp, isomp->root, sizeof isomp->root)` — copies the root dir record
   - `isomp->root_extent = isonum_733(rootp->extent)` — reads extent field
   - `isomp->root_size = isonum_733(rootp->size)` — reads size field

4. **Line 435–437:** **The buffer is released:**
   ```c
   pribp->b_flags |= B_AGE;   /* mark for preferential eviction */
   brelse(pribp);              /* release to buffer cache free list */
   pribp = NULL;
   ```
   After this, `rootp` is a **stale pointer** — it still points into the released buffer's data area, which is now eligible for eviction and reuse.

5. **Line 466:** **Stale dereference:**
   ```c
   if ((error = bread(isomp->im_devvp,
                     lblktooff(isomp, isomp->root_extent + isonum_711(rootp->ext_attr_length)),
                     isomp->logical_block_size, &bp)) != 0)
   ```
   `isonum_711(rootp->ext_attr_length)` reads from `rootp`, which points into the released buffer. If the buffer has been evicted and reused by a concurrent `bread()` for a different block, this reads stale/wrong data.

## Why the race does not trigger at runtime

The race window is between `brelse(pribp)` (line 436) and `isonum_711(rootp->ext_attr_length)` (line 466). Examining lines 437–465: the code only performs **field assignments** (`isomp->*`, `mp->*`, `dev->*`, `argp->*`) — **no I/O operations**. Only a concurrent `bread()`/`geteblk()` from another CPU could evict the `B_AGE`-marked buffer in that window. The window is microseconds wide.

Testing with 3000 mount/unmount cycles and 8 concurrent I/O-pressure processes (heavy random reads from `/boot/kernel/kernel`, `/var/log/messages`, etc.) produced **0 failures** on both unpatched and patched kernels. The buffer is virtually always still in cache when `rootp->ext_attr_length` is read.

## Impact ceiling

- **Worst case:** If the race triggered, `rootp->ext_attr_length` would return a wrong byte → `isomp->root_extent + wrong_byte` → `bread()` reads the wrong block → either EIO (mount fails — DoS) or wrong RRIP detection (mount succeeds with wrong Rock Ridge behavior — correctness bug).
- **NOT memory corruption:** The read is from valid buffer-cache memory (the buffer is on the free list, not freed to the slab allocator). It's a stale-data read, not a wild pointer dereference. No UAF primitive, no escalation chain possible.
- **Reachability:** `cd9660` mount requires `SYSCAP_RESTRICTEDROOT` (`sys/kern/vfs_syscalls.c:5397`), so only root can reach `iso_mountfs()`. The threat model is root mounting an attacker-provided ISO image under heavy concurrent I/O. No privilege boundary is crossed.

## Reachability analysis

```
sys/kern/vfs_syscalls.c:152  priv = get_fscap(fstypename)
sys/kern/vfs_syscalls.c:5397 get_fscap() returns SYSCAP_RESTRICTEDROOT for "cd9660"
sys/kern/vfs_syscalls.c:154-158  caps_priv_check_td() — only root passes RESTRICTEDROOT
```

Unprivileged users cannot mount `cd9660` even with `vfs.usermount=1` (confirmed on guest: `mount -t cd9660` as maxx → "Operation not permitted"). The bug is **root-only reachable**.

## Fix

The fix caches `ext_attr_length` in a local variable **before** `brelse(pribp)`, then uses the local at line 466:

```diff
+	int root_ext_attr_length;     // new local variable

 	isomp->root_extent = isonum_733 (rootp->extent);
 	isomp->root_size = isonum_733 (rootp->size);
+	root_ext_attr_length = isonum_711(rootp->ext_attr_length);  // cache BEFORE brelse

 	pribp->b_flags |= B_AGE;
 	brelse(pribp);                // rootp is now stale, but root_ext_attr_length is safe
 	pribp = NULL;

 	...
-	isonum_711(rootp->ext_attr_length)   // STALE READ (eliminated)
+	root_ext_attr_length                  // uses cached value (safe)
```

This eliminates the stale read by construction. The full git-apply-able diff is in `fix.diff`.

## Fix validation (Phase 8)

| Kernel | kern.version | PoC result |
|--------|-------------|------------|
| Unpatched baseline | 6.5-DEVELOPMENT #0 (Thu Jul 2 06:02:54 UTC 2026) | 3000 mounts ok, 0 failures |
| Single-fix kernel | 6.5-DEVELOPMENT #1 (Tue Jul 14 02:42:17 UTC 2026) | 3000 mounts ok, 0 failures |

The runtime behavior is identical because the race does not trigger on either kernel. The fix is validated **by construction**: the stale read at the former line 466 is eliminated — the code now reads from a stack local that was populated before `brelse()`. The patched kernel compiles cleanly (rc=0), boots, and the PoC runs without panics or new failures.

**Fix status: fixed** — the code-level defect is eliminated, the patched kernel works correctly.
