# DF-0879 — Verdict

## Verdict: REPRODUCED (DoS) — FIX VALIDATED

**Finding:** `cd9660_rrip_loop()` in `sys/vfs/isofs/cd9660/cd9660_rrip.c`
follows the Rock Ridge SUSP CE (Continuation Entry) chain without bounding
the chain depth or detecting cycles.  A crafted ISO9660 image with a
self-referential CE chain causes the kernel to loop forever, hanging the
mount syscall and the guest — a local denial of service.

**Impact:** Denial of Service (kernel hang).  The mount process enters an
uninterruptible infinite loop in `cd9660_rrip_loop()` and cannot be killed
(`kill -9` has no effect — the process is spinning in kernel context with
no preemption point).  The guest becomes fully unresponsive to further SSH
connections.

---

## Mechanism (trigger → primitive → effect)

### Trigger path (line-by-line)

1. **`mount_cd9660` → `cd9660_mount`** reads the PVD and root directory
   extent from the ISO image
   (`sys/vfs/isofs/cd9660/cd9660_vfsops.c:326-430`).

2. At **`cd9660_vfsops.c:472`**, mount calls
   `cd9660_rrip_offset(rootp, isomp)` to detect Rock Ridge extensions.

3. **`cd9660_rrip_offset`** (`cd9660_rrip.c:702`) checks for the SP
   signature at `isodir->name + 1` (line 708-714), then calls
   `cd9660_rrip_loop(isodir, &analyze, rrip_table_extref)` at line 719.

4. **`cd9660_rrip_loop`** (`cd9660_rrip.c:469`) enters the outer
   `while (1)` loop at **line 503**.  Each iteration:
   - Resets `ana->iso_ce_len = 0` (line 504).
   - Processes SUSP entries in the current record's System Use area
     (inner loop, lines 509-539).  When a `CE` entry is found,
     `cd9660_rrip_cont` (`cd9660_rrip.c:429-436`) stores the continuation
     block/offset/length in `ana->iso_ce_blk/off/len`.
   - **Termination check (line 544):** `if (ana->fields == 0 ||
     ana->iso_ce_len == 0) break;` — exits only if all desired fields
     were found OR no CE was seen.
   - **Bounds check (lines 550-555):** validates `iso_ce_blk <
     volume_space_size` and `iso_ce_off + iso_ce_len <=
     logical_block_size` — but **NO depth limit and NO cycle detection**.
   - Reads the CE block (lines 560-572) and loops back.

### The bug: no cycle detection

The loop unconditionally follows every CE entry.  If the continuation
block's SUSP area again contains a CE pointing back to the same block (or
to another block in a cycle), the loop never terminates.  The kernel
re-reads the same block forever, spinning the CPU.

### PoC construction

The PoC (`make_iso.c`) builds a minimal 20-sector ISO9660 image:
- **Sector 16:** PVD with root extent at sector 18
- **Sector 17:** Volume Descriptor Set Terminator
- **Sector 18:** Root directory record with System Use area containing:
  - `SP` entry (7 bytes) — Rock Ridge signature, satisfies
    `cd9660_rrip_offset`'s SP check
  - `CE` entry (28 bytes) — continuation at sector 19, offset 0, len 28
- **Sector 19:** CE continuation block containing a `CE` entry pointing
  **back to sector 19, offset 0** — a self-cycle

When mounted, `cd9660_rrip_loop` reads sector 19, finds the CE → reads
sector 19, finds the CE → reads sector 19 ... forever.

---

## Exploit chain

This is a **DoS-only** finding (infinite loop / kernel hang), not a
memory-corruption primitive.  There is no escalation chain to `uid=0` —
the bug's impact ceiling is denial of service.

The trigger is realistic: an attacker who can cause a crafted ISO9660
image to be mounted (distributed as an installer image, auto-mounted USB
device, or provided to a service that inspects ISO images) can hang the
kernel indefinitely.

---

## Reproduction evidence

### Baseline (unpatched `#0` kernel — 6.5-DEVELOPMENT Thu Jul 2 06:02:54 UTC 2026)

```
VN=vn4
BG_PID=875
--- after 8s ---
MOUNT_STILL_RUNNING=yes (kernel hang confirmed)
--- ps check ---
(ssh session hung after kill -9 attempt — process unkillable, stuck in
uninterruptible kernel state inside cd9660_rrip_loop following cyclic CE chain)
```

Serial console shows the `vn4` device was configured, then silence — the
kernel entered the infinite loop and stopped producing output:
```
login: vn4: MBR magic not found; assume a COMPATIBILITY_SLICE (s0)
```

The guest became fully unresponsive to further SSH connections (connection
timeout during banner exchange).  **`kill -9` on the mount process had no
effect** — it is spinning in kernel context, not in an interruptible sleep
state.

### Fixed kernel (`#1` — 6.5-DEVELOPMENT Sat Jul 11 21:30:15 UTC 2026)

Three consecutive runs, all returning promptly:
```
--- mount attempt on FIXED kernel #1 ---
MOUNT_RC=0
 9:38PM  up 50 secs, 0 users, load averages: 0.11, 0.04, 0.01
```

The mount returns instantly, the guest stays responsive, and the load
average stays near zero.  The CE depth bound breaks the cycle after 100
iterations.

---

## Fix

**File:** `sys/vfs/isofs/cd9660/cd9660_rrip.c`

**Change:** Added a CE chain depth counter to `cd9660_rrip_loop()`.  Each
time the loop follows a CE continuation entry, the counter increments.
When it exceeds `ISO_RRIP_MAX_CE_DEPTH` (100), the loop breaks — preventing
both cyclic and excessively deep CE chains from hanging the kernel.

The fix is minimal (3 hunks: one `#define`, one local variable, one
bounds check) and does not change the behavior for well-formed images
(which need at most a handful of CE hops per directory record).

**Validation:** Built a single-fix kernel (`#1`), installed, rebooted,
re-ran the exact same PoC three times.  All three runs returned promptly
with the guest remaining fully responsive.  The fix is deterministic.

See `fix.diff` for the git-apply-able unified diff.

---

## PoC changes

The finding had no pre-existing PoC scaffolding (the markdown and PoC
directory did not exist when this runner was spawned).  All PoC components
were authored by this runner:
- `make_iso.c` — C program that constructs the crafted ISO9660 image with
  a self-referential CE chain
- `build.sh` — builds `make_iso` and generates `cyclic.iso`
- `run.sh` — mounts `cyclic.iso` via `vnconfig` + `mount_cd9660`
- `fix.diff` — the verified fix bounding CE chain depth
