# DF-0852 — Unvalidated user `ssector` → signed-integer overflow in cd9660

**File:** `sys/vfs/isofs/cd9660/cd9660_vfsops.c`
**Severity:** Low (auditor) — **confirmed**: real signed-integer overflow, but root-only reachability and no write/corruption primitive (int size field), so impact ceiling = DoS/info-confusion (poisoned `statfs`).

## Claim
`iso_args.ssector` (a signed `int`, copied in from userspace in `cd9660_mount` at `cd9660_vfsops.c:197`) is never range-checked. It then drives:
- the volume-descriptor scan loop  `for (iso_blknum = 16 + ssector; iso_blknum < 100 + ssector; ...)`  (`:326-327`) — `16+ssector` / `100+ssector` wrap when `ssector` is near `INT_MAX`;
- the multi-session adjustment  `isomp->volume_space_size += argp->ssector;`  (`:427`) — overflows when `volume_space_size` (read from the attacker-controlled ISO Primary Volume Descriptor) plus `ssector` exceeds `INT_MAX`.

The poisoned `volume_space_size` is published via `cd9660_statfs` (`:633` `sbp->f_blocks = isomp->volume_space_size;`) and used as a bound in `cd9660_vget_internal` (`:775`).

## Reproduction
A crafted ISO 9660 image (`craft_iso.py`) places a valid Primary Volume Descriptor at sector `16 + SSECTOR` with `volume_space_size = 0x7FFFFFF0`. Mounting it with `ssector = 16` makes line 427 compute `0x7FFFFFF0 + 16 = 0x80000000` → `INT_MIN`, and `statfs` reports the wrapped value.

```
$ ./poc /dev/vn0 /tmp/df0852_mnt 16
[poc] mount() rc=0 errno=2 (No such file or directory)
[poc] statfs f_blocks = -2147483648  (0xffffffff80000000)  f_bsize=2048
[poc] !!! BOGUS f_blocks: signed-integer overflow confirmed !!!
```

## Reachability / threat model (why Low, not higher)
`get_fscap("cd9660")` returns `SYSCAP_RESTRICTEDROOT` (`sys/kern/vfs_syscalls.c` get_fscap): cd9660 is **not** in the user-mountable set (only null/devfs/procfs/tmpfs/fuse are). The `mount(2)` syscall gates non-root callers with `caps_priv_check_td(td, priv | __SYSCAP_NOROOTTEST)` even when `vfs.usermount=1`. Verified empirically: an unprivileged user (`maxx`, uid 1001) gets `EPERM` even with `vfs.usermount=1` + a group-readable `vn` device + a self-owned mountpoint. **Only root (or a RESTRICTEDROOT-capable process) can reach the `ssector` input.** Root→kernel is game-over by definition; there is no privilege boundary to cross and no escalation chain.

The primitive is an `int` size/bound field, not a pointer/refcount/function-pointer — there is no write that yields code execution. The realistic impact is a poisoned `statfs` (DoS/info-confusion for `df`/quota/NFS-export stats). No `uid=0` chain exists (valid hard blocker: root-only reachability + non-write primitive).

## Build / run
```
./build.sh                 # cc -o poc poc.c
python3 craft_iso.py 16 0x7FFFFFF0 iso_overflow.iso   # host: build the crafted ISO
python3 craft_iso.py 0  100          iso_normal.iso    # host: build a normal baseline ISO
# on guest (root):
vnconfig -c -s labels vn0 /root/iso_overflow.iso
./run.sh 16                 # overflow case  -> bogus f_blocks
./run.sh 2147483647         # loop-overflow  -> ssector=INT_MAX wraps loop bounds
```

## Fix
`fix.diff` (git-apply-able) adds two guards in `iso_mountfs()`:
1. validate `ssector` before the loop (`ssector < 0 || ssector > INT_MAX - 100` → `EINVAL`), closing the loop-bound overflow;
2. guard the multi-session sum (`volume_space_size < 0 || volume_space_size > INT_MAX - ssector` → `EINVAL`), closing the line-427 overflow for crafted images.

It **supersedes** the finding markdown's proposal (which only bounded `ssector`) by also guarding the image-controlled `volume_space_size` addition. Validated on a built-and-booted single-fix kernel (`#1`): the overflow is rejected with `EINVAL`, normal mounts still report correct `f_blocks`.
