⬢ DragonFlyBSD Kernel Audit
DF-3017 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3017",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "uid0_privesc",
  "confidence": "certain",
  "verdict": "CONVERTED: the DF-3017 x DF-2694 composed chain escalates unprivileged uid 1001 to uid=0(root) euid=0 on the stock INVARIANTS kernel (2-vCPU instantiation), verified twice from fresh boots, winning in 0.9s/1.4s of the ticket phase with the guest surviving. The v3 decoupled-corner choreography (park all event ptys, arm the objcache magazine+depot corner with a >=220-put close burst, then a close-only ticket phase so every event cdev/node free kfrees directly to the slab zone head where the writev hammers re-image it) eliminated the magazine-recycled SRF_PUTAWAY KKASSERT draws entirely; the corrected uniform ops-spray V=0x000000022F000100 (high dword = D_DISK) with stub pages at 0x22F000100/0x22F00010000 and a low RWX NOP-sled converts junk-si_ops draws of any likely byte alignment into execution of the ring0 cred-zeroing stub (gs:0 -> gd_curthread -> td_proc -> p_ucred -> cr_uid/cr_ruid/cr_svuid=0), reached through the demonstrated dev_dioctl `call *si_ops->d_ioctl` dispatch. Success criterion: /dev/console marker DF3017-CHAIN-WON uid=0, root-owned /tmp/df3017.root, and `uid=0(root) gid=1001(maxx) egid=0(wheel) groups=0(wheel)` from an execd root shell.",
  "exploit_chain": "1) usched_set(USCHED_DEL_CPU) pins conductor+hammers to cpu0 (no privilege, kern_usched.c:303). 2) Park 850 /dev/ptmx clones (all cdev+node objcache GETs on cpu0). 3) Arm: open+close-burst 150 more ptys -> >=220 puts per cache fill both per-cpu magazines and the 6 depot magazines -> every further objcache_put takes the kern_objcache.c:706 corner and kfree()s directly into the cpu0 slab zone head. 4) Gate on park visibility (async devfs creates), start 320B/240B EPIPE-writev image hammers. 5) Ticket phase (close-only, corner stays armed): announce batches of 4 fresh units to 8 lstat victims; close unit i at announce+10us+i*25us+jitter -> destroy workers unlink/freep races the victims in-flight devfs_allocv (window A: devfs_lock drop across getnewvnode) and cached-vp devfs_vop_getattr (window B). 6) Raced victim reads the freed/reclaimed 240B node chunk: junk-reclaimed d_dev points into the 256MB+32MB uniform-V spray -> si_ops->head.flags@+12 = D_DISK, si_ops->d_ioctl@+72 = V (or V<<8 / V>>8k per byte alignment, all stub-covered); imaged chunks dispatch through FAKEOPS (0x40000000) equally. 7) reference_dev passes (sysref 0x21, PUTAWAY clear), v_associate_rdev inserts into a NULL si_hlist, cache_setvp caches the poisoned vnode. 8) The victims own lstat runs getattrs D_DISK branch -> dev_dioctl -> call *si_ops->d_ioctl -> ring0 stub zeroes p_ucred uids -> getuid()==0 -> execve(/bin/sh) -> uid=0(root).",
  "evidence": "win3.dx.log + win3b.dx.log (full winning runs: WIN: getuid()=0 geteuid()=0, uid=0(root) gid=1001(maxx) egid=0(wheel)), win3.serial.log + win3b.serial.log (DF3017-CHAIN-WON uid=0 euid=0 tickets=386/497), run_chain3.final.log (driver transcript of both wins incl. root-owned /tmp/df3017.root listing), dispatch.v3.trace.txt (pre-fix live proof of the kernel RIP dispatch: Fatal user address access from kernel mode from dx, RIP=0x2f00010000=V<<8), README.chain.md (full mechanics), VERDICT.md (chain-conversion section)",
  "kernel_refs": [
    "sys/vfs/devfs/devfs_core.c:303-390",
    "sys/vfs/devfs/devfs_core.c:424-535",
    "sys/vfs/devfs/devfs_core.c:1280-1305",
    "sys/vfs/devfs/devfs_core.c:1440-1460",
    "sys/vfs/devfs/devfs_core.c:2705-2720",
    "sys/vfs/devfs/devfs_vnops.c:474-521",
    "sys/vfs/devfs/devfs_vnops.c:548-623",
    "sys/vfs/devfs/devfs_vnops.c:1165-1185",
    "sys/kern/kern_objcache.c:262-348",
    "sys/kern/kern_objcache.c:619-718",
    "sys/kern/kern_slaballoc.c:1470-1560",
    "sys/kern/kern_conf.c:346-360",
    "sys/kern/kern_usched.c:303",
    "sys/kern/tty_pty.c:270-295",
    "sys/kern/vfs_subr.c:1211-1223",
    "sys/sys/sysref2.h:63-70",
    "sys/kern/kern_sysref.c:298-340"
  ],
  "poc_changes": "v3 weapon df3017d.c (from df3017c): (1) decoupled-corner choreography replaces storm/window cycles \u2014 park-all + arm-burst + close-only ticket phase keeps the objcache corner permanently armed (kills the ~1/220 magazine coin); (2) corrected uniform ops-spray V=0x000000022F000100 (the missing high-dword D_DISK digit was found from a live misaligned dispatch that trapped at V<<8) plus stub pages at 0x22F000100/0x22F00010000 and a low RWX NOP-sled for V>>8k misalignments; (3) announce-then-close timing measured against live telemetry (first resolves +87..+330us; instant DEVFS_INVISIBLE at close); (4) park-before-arm (zero-pty transient tears /dev/pts down), park-visibility gate + late hammer start (early hammers starve the devfs core and back up the create queue ~8ms), dead-slot skipping in the victim sweep.",
  "attempts": 5,
  "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 (stock kernel, 2-vCPU QEMU/KVM instantiation)",
  "runtime_sec": 5400,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -pthread -o dx df3017d.c   (on the DragonFly guest)",
  "run_cmd": "./tightloop3.sh 8 60 850 150 4   (host driver: fresh 2-vCPU boots, nodefree 700 + walkzones 22 template harvest, /tmp/dx 60 850 150 4 as uid 1001, serial watch for DF3017-CHAIN-WON)",
  "code_hash": "f7176d6d140c28a7b171e288b173d4dfac9a132f2014a4063d05e27eb8f3a380",
  "notes": "Two independent wins (tickets=386 @0.9s; tickets=497 @1.4s); winning draw class = junk-reclaimed node -> uniform-V spray (marks=0 accordingly); no KKASSERT in the winning runs (corner held); non-winning boots end benignly. Node template per boot harvested via the read-only gdbstub walk (same offsets the DF-2694 leak engine chases; the leak remains the production-kernel source for these values). Residuals: r=4/5 byte-misaligned junk si_ops yield non-canonical targets (#GP) and the one-pool-per-boot cadence (~996 tickets) bounds per-boot odds at ~40%.",
  "recommended_fix": "Same as the verified DF-3017 fix.diff (devfs_allocv/devfs_freep interlock); additionally serialize si_ops dereferences behind a reference held across the destroy (devfs_vop_getattr) and make destroy_dev teardown reject sysref resurrection."
}