DF-2775 / verdict.json
{ "finding_id": "DF-2775", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "Reproduced on the stock INVARIANTS guest two ways. (1) Deterministic primitive: an exact copy of the sysctl_intrnames walk (kern_intr.c:1106-1110) stalled between loading a rec pointer and using rec->name, then resumed after the rec was unlinked, freed and immediately reclaimed (kfree->kmalloc LIFO, 100 percent over 3 runs) with a chosen rec->name, printed the chosen string into the walk output: harness token DF2775-SECRET-LEAK-TOKE and the kernel version string from 0xffffffff811473a0 (arbitrary-address NUL-terminated kernel read, no KASLR). (2) Organic: an unprivileged uid 1001 reader hammering the real sysctl hw.intrnames (pinned cpu0) while kernel-side churn on cpu1 performed the interposed unregister at ~200k unlinks/s panicked the kernel in 4 of 4 runs within seconds with the identical signature Fatal trap 9 / trap 12 stopped at strlen cmpb 0,(rdi) on the reader cpu, i.e. ksnprintf percent s of rec->name (kern_intr.c:1107) on a rec freed by the concurrent unregister (kern_intr.c:486-487). Organic token capture in reader output: 0 hits, mechanically explained: the stock loop separates rec load from rec->name dereference by ~5ns so a content-controlled read needs the reader preempted exactly inside that window (the rig proves that case); organic outcomes are benign or crash, matching the 4 panics. Privilege ceiling: unpriv-readable heap disclosure plus local DoS panic; the churn side needs kernel-context (root-driven driver) activity, an unprivileged user cannot trigger alone, and there is no write primitive, so this is an info leak not a privesc. fix.diff (per-cpu migrate+crit snapshot, then a single SYSCTL_OUT) validated on a rebuilt kernel: same organic PoC gave reads=113091 hits=0 with 17.4M deeds and no panic (plus a fully-real mode-0 run reads=5829 hits=0 over 9.2M register/unregister iterations) and byte-identical sysctl output (1536 entries, correct slot contents).", "exploit_chain": "", "evidence": [ "run.log section 1-2: DF2775-RIG walker entry dfh0/..../dfh7/DF2775-SECRET-LEAK-TOKE and dfh7/DragonFly 6.5-DEVELOPME (chosen-address reads via freed rec->name)", "panic.txt and logs/boot.after4thpanic.log: Fatal trap 9 stopped at strlen cmpb 0,(rdi) on cpuid 0 from the unprivileged reader, 4 of 4 organic runs", "logs/hits.log: t=1s reads=1927 hits=0 pre-churn baseline; reader rate evidence", "run.log section 7-9: fixed kernel A/B SUMMARY reads=113091 hits=0 secs=90 at 17,408,000 deeds (100 percent reclaim), mode-0 run reads=5829 hits=0, and chk.c output rc=0 bytes=10218 entries=1536 e508=[dfp0/dfp1/dfp2/dfp3]", "fix.diff: sysctl_intrnames migrate+crit snapshot rewrite" ], "kernel_refs": [ "sys/kern/kern_intr.c:1100", "sys/kern/kern_intr.c:1106", "sys/kern/kern_intr.c:1107", "sys/kern/kern_intr.c:438", "sys/kern/kern_intr.c:448", "sys/kern/kern_intr.c:486", "sys/kern/kern_intr.c:487" ], "poc_changes": "Replaced the stale seeded pack (it contained an unrelated refuted kern_time.c nanosleep candidate) with a purpose-built in-kernel harness: df_harness.c (exact stock-walk copy with a load-to-use stall point, cpu1 churn thread with interposed unregister doing kfree plus same-cpu LIFO reclaim groom, fixed-walk snapshot variant), an unprivileged O_SYNC-logging reader (df2775_reader.c) and a pin-then-setuid launcher (pin_drop.c) because USCHED_SET_CPU needs privilege. Fixed include paths (machine/atomic.h), volatile sysctl node casts, buffer bounds in the walk copy, a persist-mode chain-growth bug, and switched reader logging to O_SYNC so hits survive panic reboots.", "attempts": 4, "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": 5400, "guest_dirty": 0, "build_cmd": "cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC (df_harness.c compiled in; kernel.stripped installed manually to /boot/kernel/kernel)", "run_cmd": "sysctl dfh.mode2775=2 dfh.target2775=0 dfh.run2775=2 ; nohup /root/pin_drop 0 1001 ./df2775_reader.bin 90 0 /home/maxx/logs/hits.log & sysctl dfh.mode2775=2 dfh.persist2775=1 dfh.secs2775=85 dfh.run2775=1", "code_hash": "15da5b1aa9b39d592f02f878101e81f28d056fc27f28823a6ec9b392054e2cbe", "notes": "Honest rates: deterministic groom 3/3; organic panic 4/4 (seconds to ~1 min at 200k deeds/s); organic token-in-output 0 with a documented 5ns-window mechanism. Impact recorded as leak (arbitrary-address NUL-terminated read through freed rec->name, proven deterministically; unpriv readable) with panic as additional demonstrated effect. Privesc assessment: none possible from this primitive alone (no write, churn needs kernel context). Guest reset with-src after evidence collection.", "recommended_fix": "Snapshot hw.intrnames per CPU under int_moveto_destcpu plus crit_enter (the protocol unregister_int uses for the unlink), copy the rendered strings into a kmalloc buffer, and SYSCTL_OUT once afterwards (see fix.diff hunk sysctl_intrnames)." } |