rn_delete integer underflow: klen-head_off wraps to huge size_t when key sa_len < tree offset -> kernel panic
Summary
rn_delete(:884-889): klen=clen(key)=sa_len, head_off=head->rnh_treetop->rn_offset(e.g. 4 for IPv4). bcmp(:888) uses klen-head_off as length. If caller passes key with sa_len<head_off(e.g. sa_len=2 for AF_INET tree head_off=4): result is -2, implicitly converts to size_t~2^64-2. bcmp reads ~16 EiB -> hits unmapped page -> panic. radix API has NO validation clen(key)>=head_off. Routing socket rt_xaddrs only rejects sa_len==0 not too-short. Root sends RTM_DELETE with truncated dest sockaddr -> panic. No info leak (bcmp crashes before returning). Fix: if(klen<head_off) return NULL.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0495 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| route_radix_underflow.c | trigger-source | RTM_DELETE short-sa_len probe | 1.7 KB | view raw |
| build.sh | build-script | cc -O2 compile | 56 B | view raw |
| run.sh | run-script | run probe | 216 B | view raw |
| run.log | run-log | ESRCH for sin_len 1/2/3, kernel alive | 189 B | view raw |
| fix_build.log | build-log | combined-fix kernel build rc=0 + kern.version #1 | 4.7 KB | view raw |
| env.txt | environment | uname + cc | 188 B | view raw |
| fix.diff | suggested-fix | if(klen<head_off) return NULL guard in rn_delete | 372 B | view raw |
| VERDICT.md | verdict | full narrative | 2.4 KB | β raw |
| README.md | readme | summary | 3.9 KB | β raw |
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):
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:
key+head_off(the user's truncated sockaddr) almost never byte-equals an existing route'srn_key+head_offfor a long run, and- on default GENERIC the slab around
key/rn_keyis 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:
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 scriptsrun.logβ probe output (ESRCH, kernel alive)fix_build.logβ combined-fix kernel build (rc=0) + kern.version #1fix.diffβif (klen < head_off)guard
DF-0495 detailed verdict
Verdict: NOT REPRODUCED (claimed panic does not manifest); real latent underflow
Claim vs reality
The finding (CWE-191, A:H) claims a kernel panic from bcmp reading a
~2βΆβ΄-byte region after (klen - head_off) underflows. Source confirms the
underflow is real (sys/net/radix.c:884-889, no klen >= head_off check;
reachable via RTM_DELETE because rt_xaddrs at sys/net/rtsock.c:1034 only
rejects sa_len==0). But the panic does not occur on default GENERIC:
DragonFly's bcmp (MEMCMP macro, asm_mjgmacros.h) short-circuits on the first
16-byte mismatch, and INVARIANTS slab poisoning guarantees a mismatch within a
chunk or two, so bcmp returns non-zero long before reaching an unmapped page.
rn_delete returns NULL β userspace ESRCH. No crash.
Empirically confirmed: sin_len β {1,2,3} (< rn_offset=4 for AF_INET) via
RTM_DELETE all return ESRCH, guest stays up, no panic in boot.log.
Classification
status = not_reproduced for the claimed impact (panic): the bug path is
exercised but the claimed effect does not manifest on this kernel. The
integer-underflow defect itself is genuine and worth fixing (defense-in-depth);
the realistic impact ceiling is a benign ESRCH plus a theoretical
layout-dependent OOB read that is non-exploitable on GENERIC. The Medium/A:H
severity is overstated β this is closer to a Low/Info hardening defect.
Privilege boundary
socket(PF_ROUTE, SOCK_RAW, ...) requires SYSCAP_RESTRICTEDROOT
(sys/net/raw_usrreq.c:195). Root-only β rootβkernel; no unprivβroot chain.
Fix (defense-in-depth)
fix.diff:
if (klen < head_off || tt == NULL || bcmp(...) != 0) return (NULL);
Validated: applied + compiled + booted as part of the combined single-fix
kernel (#1, kern.version "...#1: Sat Jul 18 17:19:50 UTC 2026"). On the
patched kernel the probe still returns ESRCH (no regression) and the
underflow path is closed deterministically rather than relying on bcmp's
short-circuit. Because the claimed panic never reproduced, there is no runtime
before/after crash contrast β fix_status = not_testable (fix validated as
applies+compiles+boots and traced to close the path).
Why no chain
The primitive would be an OOB read of underflowed length, but it short-circuits before reading anything useful, and the path is root-only. No write, no corruption, no leak β there is no escalation chain to develop.
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed real but bcmp short-circuits before OOB read. Root-only PF_ROUTE. Returns ESRCH, no panic. Severity overstated.
No comments yet.