DragonFlyBSD Kernel Audit
DF-2779 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2779",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Race proven twice on the stock INVARIANTS kernel: (1) smuggled signo=64 (via SIGEV_NONE re-registration, unvalidated at sys_mqueue.c:973-975) was delivered by the kernel as signal 64, killing the racing process ('Unknown signal: 64', exit 192) within ~15 s; (2) smuggled signo=0x4000001 caused 'panic: lwpsignal: invalid signal 67108865' with stack lwpsignal <- mq_send1 <- syscall2 (exact attacker constant in the message; panic.txt). Root cause: mq_send1 checks sigev_notify under mq_mtx (:877) but re-reads sigev_signo after releasing it (:905). On production kernels without INVARIANTS the same ksignal() performs SIGADDSET_ATOMIC at word ((sig-1)>>5) - an OOB atomic bit-set write at attacker-chosen offset (8 MB for the tested constant, up to ~256 MB).",
  "exploit_chain": "armer thread registers SIGEV_SIGNAL{SIGUSR1}; stealer thread loops mq_notify(SIGEV_NONE{bad_signo})/mq_notify(NULL); closer thread races close(fd) into the send window so the sender's fdrop takes the slow last-ref path (stretching the release->read window); contender thread keeps mqlist_mtx contended; sender consumes the registration, releases mq_mtx, and reads the stealer's unvalidated signo -> ksignal(p, attacker int) -> INVARIANTS: KASSERT panic; production: OOB read (SIGISMEMBER p_sigignore) + OOB atomic bit-set write (SIGADDSET_ATOMIC p_siglist, kern_sig.c:1367).",
  "evidence": ["run.log: run1 'Unknown signal: 64' EXIT=192 in ~15 s", "panic.txt: panic: lwpsignal: invalid signal 67108865 / mq_send1+0x3ee in trace", "fix_validation.log: 600 s x 2 patched runs, 7.0M+337K sends vs 700M arms/384M steals, no panic"],
  "kernel_refs": ["sys/kern/sys_mqueue.c:877", "sys/kern/sys_mqueue.c:905", "sys/kern/sys_mqueue.c:973-975", "sys/kern/sys_mqueue.c:985-988", "sys/kern/kern_sig.c:1139", "sys/kern/kern_sig.c:1183", "sys/kern/kern_sig.c:1367", "sys/sys/signal.h:64-68"],
  "poc_changes": "raw syscalls; BAD_SIGNO raised from 64 to 0x4000001 after discovering _SIG_MAXSIG=128 (64 is sigset-representable and 'only' kills the process; >=129 gives OOB word index); closer+fdrop-slowpath+lock-contender threads added to widen the ~100 ns window.",
  "attempts": 4,
  "guest_uname": "DragonFly dfbsd 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": 200,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -pthread -o race_signo race_signo.c",
  "run_cmd": "./race_signo 20          # signo=64 build: process killed by sig 64\n./race_signo 180         # signo=0x4000001 build: kernel panic",
  "code_hash": "3d06d4ccbc5958ce419ea225449e2ece79693dbcdae979a7c8c2334c5ad57053",
  "notes": "Race win rate is high thanks to the closer-induced slow-path fdrop; the panic reproduces within one 3-minute run.",
  "recommended_fix": "Snapshot notify_signo (and the whole sigevent if desired) into a local while mq_mtx is held (fix.diff).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Tue Sep  1 04:57:57 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Patched kernel survived two 600-s races (7.0M and 337K sends against 537M/675M arm-registrations and 321M/384M steal-registrations) with no panic and no invalid-signal death; stock kernel panicked in under 180 s.",
  "fix_evidence": "fix_validation.log, fix_validation_b2.log"
}