DF-2883 / verdict.json
{ "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" ] } |