β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0495

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0495 Β· 9 files
FileTypeDescriptionSize
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
README.md readme summary
↓ download 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:

  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:

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
VERDICT.md verdict full narrative
↓ download raw

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_testable

compile 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.