DragonFlyBSD Kernel Audit
DF-2883 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2883",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "none",
  "confidence": "certain",
  "verdict": "kvsscanf's CT_INT accept path (sys/kern/subr_scanf.c:487-490) does not advance inp when --inr hits 0, while the x/X pushback (:505-510) unconditionally does inp--/inr++; for an input ending exactly in '0x' under %i/%x/%p the pushback therefore lands inp one byte stale (on the already-consumed '0') with inr=1 and zero characters truly remaining, and execution continues: the next directive re-reads the consumed byte and nread double-counts it. Proven in-kernel on the stock kernel (E4: ksscanf(\"0x\",\"%i%c%n\") yields ch='0' where guest libc yields ch='x'; same for '7 0x'), stable across two module loads. All reads stay within [str, NUL] so the defect is memory-safe -- pure parse divergence from userland scanf semantics, hit by base-system %i/%x parsers (kern_uuid.c:394, if_ethersubr.c:1690, autoconf.c:263, pci.c:3657) on inputs truncated at '0x'. Low severity.",
  "exploit_chain": "none (no memory-unsafety): wrong values delivered to callers, wrong match/failure decisions, wrong %n counts",
  "evidence": [
    "run.log: \"dfpoc: E4 '0x' %i%c%n: ret=2 v=0 ch='0' nread=2 (libc: ch='x')\" + RESULT DIVERGENCE REPRODUCED",
    "run.2.log: identical on second load",
    "run_patched.log: kernel #1 with fix.diff hunk 3 -- \"E4 ... ch='x' (libc: ch='x')\"",
    "VERDICT.md: root-cause trace at :487-490 / :505-510 and invariant re-audit"
  ],
  "kernel_refs": [
    "sys/kern/subr_scanf.c:487",
    "sys/kern/subr_scanf.c:490",
    "sys/kern/subr_scanf.c:498",
    "sys/kern/subr_scanf.c:505",
    "sys/kern/subr_scanf.c:508",
    "sys/kern/kern_uuid.c:394"
  ],
  "poc_changes": "authored fresh (no seed): experiment E4 of the shared dfpoc.ko KLD harness (same module as DF-2882's pack)",
  "attempts": 2,
  "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": 2400,
  "guest_dirty": 0,
  "build_cmd": "cd /root/dfscanf && make   (fix validation: cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC)",
  "run_cmd": "kldload /root/dfscanf/dfpoc.ko && sleep 1 && dmesg | tail -8",
  "code_hash": "0c3b123fc9ff001261befd22c3cdba2bce64a21495883ef913e4ab55413b5b3e",
  "notes": "distinct from DF-2882 (different branch, different root cause; this stale state is in-bounds). The NDIGITS sign pushback at :498-502 has the same under-advance in principle but immediately returns, making it unobservable.",
  "recommended_fix": "always advance inp on accept so inr==0 consistently means 'inp at the NUL' and both pushbacks are exact inverses: replace :487-490 with 'inp++; if (--inr <= 0) break;' (fix.diff hunk 3)",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep  2 21:17:17 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff hunk 3 (always advance inp in the CT_INT ok: path) validated on kernel #1: E4 returns ch='x', identical to libc, and the accept-path invariant was re-audited against every inp consumer (all inr>0-guarded) with no regressions in the other experiments.",
  "fix_evidence": [
    "run_patched.log: \"dfpoc: E4 '0x' %i%c%n: ret=2 v=0 ch='x' nread=2 (libc: ch='x')\"",
    "run.log (baseline kernel #0): ch='0'",
    "fix.diff hunk 3"
  ]
}