# DF-0495 — rn_delete integer underflow (klen - head_off)

## Verdict: NOT REPRODUCED as a panic — the underflow is real but benign on GENERIC

## The bug (real, by inspection)
`sys/net/radix.c` `rn_delete()` (lines 884-889):

```c
klen     = clen(key);                 /* = key->sa_len (u_char)              */
head_off = x->rn_offset;              /* = 4 for the AF_INET route tree      */
if (tt == NULL ||
    bcmp(key + head_off, tt->rn_key + head_off, klen - head_off) != 0)
    return (NULL);
```

If a caller supplies a key whose `sa_len < head_off` (e.g. `sin_len=2` for an
AF_INET tree whose `rn_offset=4`), then `(klen - head_off)` is a negative `int`
(2 − 4 = −2) implicitly converted to `size_t` (~2⁶⁴) and passed to `bcmp`. This
is a genuine integer-underflow / OOB-read defect — there is **no** `klen >=
head_off` validation anywhere in the radix API.

The reachable caller path: routing socket `RTM_DELETE` with a truncated
destination sockaddr. `rt_xaddrs` (`sys/net/rtsock.c:1011`) only rejects
`sa_len==0` (substituting a safe zero sockaddr at :1034); it accepts any
`0 < sa_len`, so a too-short DST reaches `rtrequest1` → `rnh_deladdr`
(=`rn_delete`, `sys/net/radix.c:1277`) unvalidated.

## Why it does NOT panic on this kernel
The finding's claimed impact is a kernel panic ("bcmp reads ~16 EiB → unmapped
page → panic"). On DragonFly `bcmp` (`sys/cpu/x86_64/include/asm_mjgmacros.h`
`MEMCMP` macro) **short-circuits on the first 16-byte mismatch**: it compares
16-byte chunks and returns non-zero as soon as one differs. Because:

1. `key+head_off` (the user's truncated sockaddr) almost never byte-equals an
   existing route's `rn_key+head_off` for a long run, and
2. on default GENERIC the slab around `key`/`rn_key` is INVARIANTS-poisoned
   (`0xdeadc0de`) so adjacent memory never matches,

`bcmp` finds a mismatch within the first 1–2 chunks and returns non-zero
**without** reading the underflowed length to an unmapped page. `rn_delete`
therefore returns `NULL` → userspace sees `ESRCH`. No crash, no info leak (the
1-bit match/mismatch result is uncontrollable and not exposed).

## Reproduction (root-only; PF_ROUTE needs SYSCAP_RESTRICTEDROOT)
`route_radix_underflow.c` sends `RTM_DELETE` with `sin_len ∈ {1,2,3}` (all `< 4`)
against a non-existent destination (240.0.0.1, so no real route is deleted):

```
sin_len=1 -> write=-1 errno=3 (No such process)   /* ESRCH = rn_delete returned NULL */
sin_len=2 -> write=-1 errno=3 (No such process)
sin_len=3 -> write=-1 errno=3 (No such process)
DF-0495 probe complete - kernel still alive.
```

The underflow path is exercised (klen < head_off ⇒ huge bcmp length) but
manifests as a benign `ESRCH`, not a panic. The claimed A:H (availability:high,
reliable panic) is **not** achievable on default GENERIC.

⚠ Do **not** test `sin_len=0`: `rt_xaddrs` substitutes a `{16, AF_INET}` zero
sockaddr, which becomes `0.0.0.0` and can **delete the real default route**
(denying service / breaking ssh) — not the underflow bug.

## Privilege boundary
`socket(PF_ROUTE, SOCK_RAW, AF_INET)` requires `SYSCAP_RESTRICTEDROOT`
(`sys/net/raw_usrreq.c:195` via `raw_attach`). Root-only — root→kernel path.

## Fix (defense-in-depth, validated as applies+compiles+boots)
`fix.diff` adds the missing guard so the underflow never reaches `bcmp`:

```c
if (klen < head_off ||
    tt == NULL ||
    bcmp(...) != 0)
        return (NULL);
```

Built as part of the combined single-fix kernel (#1); on the patched kernel the
probe still returns `ESRCH` (no regression) and the underflow path is now
closed deterministically rather than relying on `bcmp`'s short-circuit.

## Files
- `route_radix_underflow.c` — RTM_DELETE short-key probe (sin_len 1/2/3)
- `build.sh`, `run.sh` — repro scripts
- `run.log` — probe output (ESRCH, kernel alive)
- `fix_build.log` — combined-fix kernel build (rc=0) + kern.version #1
- `fix.diff` — `if (klen < head_off)` guard
