# DF-0053 — Fix notes (post-verification, authored by df-bsd-pocrunner)

`fix.diff` is a standalone, `git apply`-able unified diff against the read-only
`sys/` tree (never applied here). Verified to apply cleanly via both
`git apply --check` (rc=0) and `patch --dry-run -p1` against
`sys/kern/kern_jail.c` at repo HEAD.

## Root cause (confirmed by verification)

`sysctl_jail_list()` (`sys/kern/kern_jail.c:661`) sizes its buffer as
`jlssize = count * 1024` (`:688`) but advances the write cursor `jlsused`
(`:671`, `unsigned int`) by the **would-be** length that `ksnprintf` returns
(`:710` after the per-jail `ksnprintf` at `:704`, and `:741` after each IP
`ksnprintf` at `:737`). That length can exceed the per-jail budget, so
`jlsused` can exceed `jlssize`. Once it does, every unsigned
`(jlssize - jlsused)` **underflows** to ~`UINT_MAX`: the IP-loop bounds check
at `:733` (`huge < strlen(oip)+1` ⇒ FALSE) stops firing, `ksnprintf` at `:737`
gets a ~`UINT_MAX` size and writes past the allocation end (OOB **write**), and
the final `SYSCTL_OUT(req, jls, jlsused)` at `:749` copies `jlsused > jlssize`
bytes out (OOB **read** / info leak).

## What the fix does

Restores and enforces the invariant `jlsused <= jlssize` at **every** cursor
advance, and makes the IP-loop bounds check underflow-proof:

1. **`:710` (after the per-jail `ksnprintf`)** — clamp: if the would-be length
   is `>=` the remaining space, set `jlsused = jlssize` instead of advancing.
2. **`:733` (IP-loop bounds check)** — add an explicit `jlsused >= jlssize ||`
   short-circuit so the unsigned subtraction can never underflow and bypass
   the check (defense-in-depth).
3. **`:741` (after each IP `ksnprintf`)** — same clamp as `:710`.

With the invariant held, `(jlssize - jlsused)` is always a small, valid
non-negative value, so the `ksnprintf` size args (`:704`, `:737`) and the
`SYSCTL_OUT` length (`:749`) can never overflow. The terminator still lands in
the `kmalloc(jlssize + 1, ...)` slack byte. When the buffer fills, the IP-loop
guard trips `ERANGE` and `goto end` returns cleanly (no OOB write, no OOB read).

## Why this supersedes the finding markdown's `## Recommended fix`

The finding proposed only two hunks: the clamp at `:710` and the guard at
`:733`. That pair is **incomplete for the multi-prison case**: if prison N's
*last* IP `ksnprintf` returns a would-be length that over-advances `jlsused`
past `jlssize` at `:741`, the IP-loop guard at `:733` never re-trips (there is
no next IP), `LIST_FOREACH` advances to prison N+1, and the per-jail
`ksnprintf` at `:704` receives `(jlssize - jlsused)` which **underflows** and
overflows the buffer — re-opening the exact defect. Adding the **third clamp
at `:741`** (hunk 3) is what actually restores the invariant for all iteration
orders and closes the bug fully. The clamp expression, guard form, and
`:710`/`:733` hunks otherwise match the finding's proposal.

## Verification of the diff itself

```
$ git apply --check findings/poc/DF-0053/fix.diff     # rc=0
$ patch --dry-run -p1 < findings/poc/DF-0053/fix.diff  # "checking file sys/kern/kern_jail.c", no rejects
```

(Not applied to `sys/` — the audit tree is read-only. Behavioral confirmation
that clamping `jlsused` to `jlssize` eliminates the overflow is implied by the
trigger PoC: a fixed kernel would return at most `jlssize` bytes and the
`[+] BUG DF-0053 CONFIRMED` markers would not fire.)
