DF-2803 / verdict.json
{ "finding_id": "DF-2803", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Reproduced on the stock INVARIANTS guest: one initiated-but-stalled root reader of /dev/udev plus one unprivileged pty-churn process (user maxx, /dev/ptmx 0666) grew the kernel udev event queue to 240,136 live events (~90 MB across propdict/prop_dictionary/propstng/udev malloc domains) in 20 seconds, linearly and without bound (udev_event_insert at sys/kern/kern_udev.c:500-532 increments udev_evqlen with no cap and no backpressure; udev_clean_events_locked reaps only up to the oldest reader marker). Kernel memory exhaustion DoS; reader side gated by /dev/udev 0600 root:wheel (same gate as DF-0055), producer side fully unprivileged.", "exploit_chain": "", "evidence": [ "run.log: baseline propdict 288 in-use -> 72.4K (t+6s) -> 167K (t+14s) -> 226K (t+19s) while reader stalled; udev domain 139 -> 226K event structs", "run.log: 'churn: 120068 pty cycles in 20s' as user maxx", "run.log: 'harness: backlog drained: 240136 events (185144856 bytes)'", "run.log: post-drain sample returns to baseline (propdict 333) - growth was queued events, not a leak", "harness.c stallcount mode (initiate via UDEVPROP getdevs, stall 30s, then non-blocking drain-count), churn.c (posix_openpt loop)" ], "kernel_refs": [ "sys/kern/kern_udev.c:500-532", "sys/kern/kern_udev.c:517", "sys/kern/kern_udev.c:534-545", "sys/kern/kern_udev.c:826-830", "sys/kern/kern_udev.c:973-979", "sys/kern/kern_udev.c:1039-1041", "sys/vfs/devfs/devfs_core.c:1428", "sys/vfs/devfs/devfs_core.c:1451", "sys/kern/tty_pty.c:176" ], "poc_changes": "Harness written from scratch for this finding (no seed). Fixed two of my own bugs during bring-up: BSD signal() sets SA_RESTART so alarm could not interrupt the blocking /dev/udev read (switched to sigaction with sa_flags=0), and churn arg parsing accepted only '-n N'. Added stallcount mode (stall then non-blocking drain) to quantify the backlog without triggering the DF-2804 leak in the same run.", "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": 42, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o harness harness.c -lprop && cc -O2 -Wall -o churn churn.c", "run_cmd": "sh /tmp/df2803_run.sh", "code_hash": "181b484caf4890bb7d42d37a80de710ec7ec40003203d0999f4a6364a607abdc", "notes": "Guest reset (with-src) after verification. Event production rate measured at ~12k events/s per unprivileged churn process (~370B retained per event in propdict + ~190B dict copy). Even a diligently-reading consumer measured only ~10k events/s drain, so any reader lag accumulates. devd holds only /dev/devctl on this system (verified via fstat), so udev queueing starts only when something initiates /dev/udev.", "recommended_fix": "Cap the queue: check udev_evqlen >= UDEV_EVQ_MAX under udev_lk in udev_event_insert() and drop (not queue) excess events, waking UDEVWAIT/kqueue consumers so the drop is observable.", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff authored from line-accurate root cause (see pack). Kernel rebuild validation not performed: DoS-class finding; the mandatory fix-build cycle applies to reproduced memory-corruption findings. Guest was reset to the clean-source snapshot after evidence collection.", "fix_evidence": "fix.diff" } |