# DF-0852 — VERDICT

**Verdict:** REPRODUCED (signed-integer overflow confirmed, observable) — impact DoS/info-confusion, **root-only reachability, no escalation**. Fix VALIDATED on a built single-fix kernel.

## Root cause
`iso_args.ssector` is a signed `int` copied in from userspace by `cd9660_mount` (`sys/vfs/isofs/cd9660/cd9660_vfsops.c:197`, `copyin(data, &args, sizeof(struct iso_args))`) with **no validation anywhere** (confirmed by grep: the only other `ssector` use is the kernel's own `iso_get_ssector` root-mount probe at `:162`, not user input). It flows directly into three signed-arithmetic sites in `iso_mountfs()`:

- `:326`  `for (iso_blknum = 16 + argp->ssector;`     — wraps when `ssector > INT_MAX - 16`
- `:327`  `     iso_blknum < 100 + argp->ssector;`     — wraps when `ssector > INT_MAX - 100`
- `:427`  `isomp->volume_space_size += argp->ssector;` — wraps when `volume_space_size + ssector > INT_MAX`

The kernel is built with `-fno-strict-overflow` (confirmed in `nk_fix.log` CFLAGS), so the signed overflow wraps silently rather than trapping — no INVARIANTS/KASSERT fires (none exists in this file; verified by grep).

## Mechanism (confirmed, `path:line` at each hop)
1. User issues `mount("cd9660", mnt, &args, MNT_RDONLY)` with `args.ssector` controlled. `:197` copyin → `:246` `iso_mountfs(devvp, mp, &args)`.
2. `:326-327` the volume-descriptor loop iterates `iso_blknum` over `[16+ssector, 100+ssector)`. With a modest `ssector` and a crafted ISO whose PVD sits at sector `16+ssector`, the loop finds the PVD and `pri` is set.
3. `:414-417` `isomp->volume_space_size = isonum_733(pri->volume_space_size)` reads the **image-controlled** value (e.g. `0x7FFFFFF0`).
4. `:427` `isomp->volume_space_size += argp->ssector` overflows: `0x7FFFFFF0 + 16 = 0x80000000 = INT_MIN`. The mount **succeeds** (`:542 return 0`).
5. `:633` `cd9660_statfs`: `sbp->f_blocks = isomp->volume_space_size` publishes `INT_MIN` (sign-extended to `long` → `-2147483648` / `0xffffffff80000000`).

The PoC observes this directly via `statfs(2)` → `f_blocks = -2147483648`. Reproduced deterministically 3/3.

The loop-bound variant (`ssector = INT_MAX`) makes `16+ssector` and `100+ssector` wrap to `-2147483633` / `-2147483549`; the kernel executes the overflowed arithmetic and then fails the mount (negative-offset `bread`), proving `ssector` is accepted unvalidated.

## Why no escalation (Phase 6 hard blocker — valid)
- **Root-only reachability.** `sys/kern/vfs_syscalls.c` `get_fscap()` returns `SYSCAP_RESTRICTEDROOT` for `cd9660` (only null/devfs/procfs/tmpfs/fuse are user-mountable). `sys_mount` gates non-root callers with `caps_priv_check_td(td, priv | __SYSCAP_NOROOTTEST)` even with `vfs.usermount=1`. Verified empirically: `maxx` (uid 1001) gets `EPERM` mounting cd9660 even with `vfs.usermount=1` + a group-readable `vn0` + a self-owned mountpoint. Only root (or RESTRICTEDROOT) reaches `ssector`. Root→kernel is game-over; there is no privilege boundary to cross.
- **Non-write primitive.** `volume_space_size` is an `int` size/bound field, not a pointer/refcount/function-pointer. The corruption poisons `statfs` and weakens/strengthens the `:775 lbn >= volume_space_size` check, but yields no code-exec primitive. There is no chain to `uid=0`.

Realistic impact ceiling = DoS/info-confusion (poisoned `statfs` for `df`/quota/NFS-export accounting), gated behind root. This matches the auditor's **Low** severity.

## PoC changes
The finding shipped with no PoC folder (only the DB row). I created the full evidence pack from scratch:
- `craft_iso.py` — host-side generator for a minimal valid ISO 9660 image with a controllable `ssector` layout and a crafted `volume_space_size` near `INT_MAX` so line 427 overflows; also generates a normal baseline image.
- `poc.c` — drives `mount(2)` directly with a controllable `ssector`, reports `errno`, and on success `statfs(2)` `f_blocks` (with a bogus-value detector).
- `build.sh` / `run.sh` — exact build & run.
- `fix.diff` — the validated fix (supersedes the finding proposal).

## Fix
`fix.diff` adds two minimal guards in `iso_mountfs()`:
1. after `joliet_level = 0;` (`:325`), before the loop: `if (argp->ssector < 0 || argp->ssector > INT_MAX - 100) { error = EINVAL; goto out; }` — closes the loop-bound overflow (`:326-327`);
2. before `:427`: `if (isomp->volume_space_size < 0 || isomp->volume_space_size > INT_MAX - argp->ssector) { error = EINVAL; goto out; }` — closes the image-controlled `volume_space_size + ssector` overflow.

Adds `#include <machine/limits.h>` for `INT_MAX` (the kernel's `<sys/param.h>` only includes `<machine/limits.h>` under `#ifndef _KERNEL`). This **supersedes** the finding's proposal (which bounded only `ssector`) by also guarding the image-controlled sum.

## Fix validation (Phase 8)
- Baseline `#0` (unpatched `with-src`): overflow case → `mount rc=0`, `f_blocks = -2147483648`. **Reproduced.**
- Single-fix kernel `#1` (built `make nativekernel`, installed via `make installkernel`, booted): overflow case → `mount rc=-1 errno=EINVAL` (2× deterministic); loop-overflow (`ssector=INT_MAX`) → `EINVAL`; normal ISO (`VSS=100, ssector=0`) → `mount rc=0`, `f_blocks=100` (no regression).
- **fix_status = fixed.** See `fix_before_after.txt` for the before/after contrast and `fix_run.log` for the full patched-kernel run.
