# DF-5697 VERDICT — reproduced (userland rig, host + DragonFly guest)

**Bottom line:** `kqsort`/`kqsort_r` accept `es == 0` without any guard. For
`n >= 7` the algorithm never terminates (all pointer deltas are multiples of
es == 0; the partition loop at sys/libkern/qsort.c:136-159 spins with `pb`
frozen), and the very first pivot swap performs an out-of-contract 8-byte
load/store at the (zero-length) array base via the `swapcode` do-while
executing once at `n == 0` (qsort.c:48-57 reached through qsort.c:132 with
`pm == a` because the `n < 7` insertion path at qsort.c:113 is skipped for
n == 7). Both symptoms demonstrated against the verbatim kernel source
compiled in userland; the two-line guard (`if (es == 0) return;`) eliminates
both. **No in-tree caller can pass es == 0** (all five call sites pass
`sizeof`-constants), so this is hardening for a core kernel primitive —
severity Info, confidence certain.

## Demonstrations

1. **Hang** (`es0_hang`): n=64, es=0, comparator counts calls and exits 7 at
   5,000,000. Guest (gcc 8.3) and host agree: `ES0 HANG CONFIRMED …
   (pb frozen at +0; a correct 64-element sort needs <3000)`, rc=7. In a
   kernel caller this is an unkillable spin inside the sort (no comparator
   progress is possible — `pb += es` is a no-op), i.e. local DoS *if* any
   caller ever computes es from untrusted input.
2. **8-byte access** (`es0_segv`): n=7, es=0, base = 16 bytes into a
   PROT_NONE page. The first memory access of the entire sort is the pivot
   swap's 8-byte load → child dies SIGSEGV before the first comparator call
   (comparator count 0), proving the access comes from `swap(a, pm)` at
   qsort.c:132 → `swapfunc(a, a, 0, 1)` → `swapcode(long, …, n=0)` whose
   do-while runs its body once (`long i = 0; do { … } while (--i > 0);`).
   Honest characterization: on a normal writable page this is a self-swap
   (same address source and destination — no value corruption) — the defect
   is the *access itself* against a zero-length array plus the C-level UB of
   `do {} while` with a zero iteration count. The hang is the practical
   impact.
3. **Boundary**: verbatim es=0 with n ≤ 6 returns cleanly with 0 comparator
   calls (insertion loop body never runs) — the trigger threshold is exactly
   the `n < 7` branch at qsort.c:113.
4. **Fix A/B**: guard-only variant `qsort_fixed7.c` (`if (es == 0) return;`
   before the `loop:` label): returns immediately for n=0..8 (0 comparator
   calls, no fault — `es0_segv_f7` child exits 0). The guard alone (without
   DF-5696's type fix) is sufficient, proving the causality.

## Why not a kernel build for fix validation

No in-tree caller passes es==0, so a patched kernel exhibits no observable
behavioral delta; the fix is validated A/B on the identical verbatim source
(bad behavior present without the guard, gone with it).

## Environment

Host: zig cc 18.1.6 (clang) x86_64. Guest: DragonFly 6.5-DEVELOPMENT, gcc
8.3 [DragonFly], clean-source snapshot (es0 runs from the DF-5696 session;
guest since reset). Both hosts agree on every symptom.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
