⬢ DragonFlyBSD Kernel Audit
DF-5692 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5692",
  "status": "reproduced",
  "reproduced": true,
  "impact": "none",
  "confidence": "certain",
  "verdict": "sys/libkern/fnmatch.c is memory-safe and unreachable by unprivileged input (only consumers are ipfw/ipfw3/ip6fw iface_match() with root-installed rule globs matched against kernel-set if_xname, flags=0, raw-socket creation priv-gated at raw_ip.c:473/raw_ip6.c:530). A differential rig compiling the kernel source verbatim (md5-verified against /usr/src) against DragonFly libc fnmatch on the guest found 67,195 divergences in 32,303,668 cases across four classes: (A) trailing backslash treated as literal where libc returns FNM_NOMATCH (66,505; fnmatch.c:140-146); (B) signed-char bracket ranges wrong for bytes >=0x80 (651; fnmatch.c:170,215); (C) unterminated [ + / under FNM_PATHNAME hard-fails where libc falls back to literal (~39; fnmatch.c:198-199); (D) MAXNEST=20 recursion cap makes >=20-star-glob patterns silently FNM_NOMATCH (fnmatch.c:51,62-63,112). None is reachable through current consumers: flags are always 0 (kills C), ifname globs are root-authored ASCII (kills A/B), and if_xname is capped at 15 chars (IFNAMSIZ=16, if_var.h:326) while a successful depth-20 match needs >=19 string chars (kills D). Guard-page harness: 0 overreads in 32,445,104 cases (no write exists in the file; no leak surface). Worst-case runtime at the 16-byte ifname bound is 0.45 ms, so no ReDoS either. Impact ceiling: silent misbehavior of root-authored firewall globs only; filed as Info hardening. fix.diff (unsigned-char ranges) validated in-rig: class B eliminated (690->39 other-class divergences, targeted probes 4/4 kernel==libc after fix).",
  "exploit_chain": "",
  "evidence": "difftest2.log: TOTAL divergences 67195/32303668 (trailing-backslash 66505, other 690) and [maxnest] first divergence at 20 stars and [signed] kernel=1 libc=0 for [a-\\xff] vs 0x80/0xfe; guardfuzz.log: 32445104 cases, 0 overreads; difftest_fixed.log: TOTAL 66544 (other 39) after fix.diff; [time-ifname-worst] stars=10 n=16 kernel: 0.000449 s; VERDICT.md for the full per-class trace with kernel citations.",
  "kernel_refs": [
    "sys/libkern/fnmatch.c:51",
    "sys/libkern/fnmatch.c:62-63",
    "sys/libkern/fnmatch.c:112",
    "sys/libkern/fnmatch.c:140-146",
    "sys/libkern/fnmatch.c:170",
    "sys/libkern/fnmatch.c:198-199",
    "sys/libkern/fnmatch.c:215",
    "sys/net/ipfw/ip_fw2.c:1355-1360",
    "sys/net/ipfw3_basic/ip_fw3_basic.c:134-140",
    "sys/net/ip6fw/ip6_fw.c:323-330",
    "sys/netinet/raw_ip.c:473",
    "sys/netinet6/raw_ip6.c:530",
    "sys/net/if_var.h:326",
    "sys/net/if.h:54-56"
  ],
  "poc_changes": "No runnable seed existed; built the full rig: shim.h (kernel headers swapped for userland equivalents, flag values verified identical to sys/sys/libkern.h:177-186), fnmatch_k.c (verbatim kernel source, include-swap only, diff-verified), difftest.c (32.2M-case differential + bracket sweep + MAXNEST probe + signed-range probes + worst-case timing), guardfuzz.c (guard-page overread detector), fnmatch_k_fixed.c + fix.diff (signedness fix A/B). First calibration run hit the exponential path on a 40-char/26-star probe and was killed; probes rescaled to string length k+2.",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 480,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -Wall -o difftest difftest.c fnmatch_k.c && cc -O2 -Wall -o guardfuzz guardfuzz.c fnmatch_k.c (plus *_fixed variants)",
  "run_cmd": "./difftest > difftest2.log; ./guardfuzz > guardfuzz.log; ./difftest_fixed > difftest_fixed.log; ./guardfuzz_fixed",
  "code_hash": "sha256:3d28d1a449b03812dd506e8332c3d79adcb0408f3b74e54b55386a22b4580401",
  "notes": "Memory-safety negative result is machine-checked (guard pages, 32.4M cases, 0 overreads, baseline and fixed); the file contains zero stores, so memory-corruption classes are structurally absent. Orchestrator hint that devfs rules / jail params consume fnmatch was checked and refuted (rg over sys/: only ipfw/ipfw3/ip6fw). ReDoS angle killed by measurement: worst case at the if_xname 15-char bound is 0.45 ms per call; exponential growth (~x5.5 per +4 chars) only matters for string lengths no kernel consumer supplies. IDs DF-5693..DF-5704 reserved but unused (single merged Info finding). Guest left up and healthy; only ~/fnm rig files remain on it.",
  "recommended_fix": "Apply the 3-line unsigned-char signedness fix to rangematch() (fix.diff); for full semantic parity with userland fnmatch(3) (trailing-backslash, PATHNAME-unterminated-bracket, MAXNEST>=20 silent NOMATCH), replace the recursive kernel matcher with the iterative algorithm from lib/libc/gen/fnmatch.c (byte-oriented variant).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (unsigned char c,c2 + two (unsigned char)test casts in rangematch) eliminates the signed-range divergence class in the rig: baseline 690 other-class divergences -> 39 after fix (all remaining are the separate unterminated-bracket/PATHNAME class), targeted [signed] probes agree with libc 4/4 afterwards, guard-page harness still 0 overreads on the patched source. Classes A/C/D are algorithm-level and require adopting the libc algorithm; documented in VERDICT.md, not addressed by this diff.",
  "fix_evidence": "difftest_fixed.log: TOTAL divergences: 66544 / 32303668 cases (trailing-backslash class: 66505, other: 39) and [signed] lines showing kernel=0 libc=0 for [a-\\xff] cases; fix_guard_run.log: 0 overreads; compare difftest2.log baseline other: 690.",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64 (rig-level validation: verbatim kernel source compiled userland; kernel rebuild not required for an Info/no-impact finding)"
}