# DF-0851 — Stale dereference of rootp after pribp released

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

## Bug mechanism
In `iso_mountfs()`:
1. **Line 329–347:** `bread(devvp, ...)` reads the ISO volume descriptor into buffer `pribp`. `pri = (struct iso_primary_descriptor *)vdp` where `vdp = (struct iso_volume_descriptor *)pribp->b_data`. So `pri` points into the buffer cache data of `pribp`.
2. **Line 407–410:** `rootp = (struct iso_directory_record *)pri->root_directory_record` — `rootp` points into `pribp->b_data`.
3. **Line 428–430:** `bcopy(rootp, isomp->root, ...)` and `isomp->root_extent = isonum_733(rootp->extent)` — valid reads before release.
4. **Line 435–437:** `pribp->b_flags |= B_AGE; brelse(pribp); pribp = NULL;` — the buffer is released back to the buffer cache free list, marked `B_AGE` for preferential eviction.
5. **Line 466:** `isonum_711(rootp->ext_attr_length)` — **STALE READ**. `rootp` still points into the released buffer's data area. If the buffer has been evicted and reused by a concurrent `bread()` for a different block, this reads stale/wrong data.

The `B_AGE` flag explicitly requests preferential eviction, making the race more likely than a plain `brelse`.

## Impact ceiling
- **Worst case:** If the race triggers, `rootp->ext_attr_length` returns a wrong byte → `isomp->root_extent + wrong_byte` is computed → `bread()` reads the wrong block from the device → either an EIO (mount fails — DoS) or wrong RRIP detection (mount succeeds but with wrong Rock Ridge behavior — correctness bug).
- **NOT memory corruption:** The read is from valid buffer-cache memory (not freed to slab), just potentially stale data. No UAF primitive, no escalation chain possible.
- **Race window:** Between `brelse(pribp)` (line 436) and `isonum_711(rootp->ext_attr_length)` (line 466), the code only does field assignments — **no I/O**. Only concurrent `bread()`/`geteblk()` from another thread/CPU could evict the buffer in that window. The window is extremely narrow.

## Build
```
./build.sh
```

## Run
```
./run.sh
```
This script creates a valid ISO image, enables `vfs.usermount`, chowns the image to the unprivileged user, then runs the PoC as `maxx` with concurrent I/O pressure to try to trigger the race.

## Expected behavior
- **Bug present (unpatched):** The race is extremely unlikely to trigger — the window has no I/O. The PoC reports "no mount failures observed" in the vast majority of runs. This is a **code-level defect**, not a reliably-triggerable runtime fault.
- **Bug fixed (patched):** The stale read is eliminated by caching `ext_attr_length` before `brelse`. Behavior is identical at runtime (the race didn't trigger anyway), but the code is now correct by construction.
