DF-5792 / verdict.json
{ "finding_id": "DF-5792", "status": "reproduced", "verdict": "Unprivileged local UAF upgraded from fatal-trap to a USER-STEERABLE KERNEL WRITE MACHINE on the stock kernel, uid 1001: the closef() fdtol-chain walk (kern_descrip.c:3071-3088) chases into entries freed by concurrent fdfree() (2675-2688); reclaiming the freed 40-byte M_FILEDESC_TO_LEADER chunk with a forged 40-byte AF_UNIX soname (sun_len=40 -- M_SONAME shares the exact 40-byte slab zone; unp_bind parses only the NUL-terminated path while dup_sockaddr bcopy()s all 40 attacker bytes) gives the walker attacker-controlled fdl_leader/fdl_next. fdl_leader = user page (no SMAP) with byte[0x58]=1 passes the p_advlock_flag gate; fdl_next = user SPIN page self-loop parks the walker INSIDE closef() forever -- a kernel-resident walk interpreter over user-editable memory. Steering SPIN+0x20 aims it at any address A: visit does ++/--*(int*)(A+4), VOP_ADVLOCK(vp,*(A+0x10),F_UNLCK), and if *(int*)(A+4)==0 && *(int*)(A+8)!=0 ZEROES *(int*)(A+8) (verified twice via pcblist soname readback, second time RACE-FREE with the storm stopped). uid0 NOT reached: exhaustive layout analysis shows the primitive set (gated zero-write, net-zero ++/-- pair, wakeup/unlock keyed by forged pointers) composes to no privilege on this kernel -- every content-controlled 33..40B zone occupant fails the fdfree-write-pair requirements (refcount==1@+0, holdcount==0@+4, forged ptrs@+0x18/+0x20) or caps forged pointers below 2^48, and no function pointer is ever dispatched through attacker data on this path. Impact ceiling honestly reported: persistent gated arbitrary-address 4-byte kernel zero-write + 1-bit kernel read oracle + cross-identity POSIX-lock strip + panic at will. fix.diff blocks the machine (walk serialized under fd_spin, fix_ab run). NOTE: fix.diff does NOT cover the same-family refcount lost-update (kern_fork.c:570 addl vs fdfrees split mov/sub/mov dec at 0x8063395b/0x8063344a) -- flagged for the maintainers.", "reproduced": true, "impact": "panic", "confidence": "certain", "exploit_chain": "unpriv uid 1001: RFMEM family; walkers open/close a VMAYHAVELOCKS vnode (each close walks the fdtol chain under only p_token); paced churners fork usec-lived RFMEM babies (each exit unlinks+kfrees its 40B fdtol entry under fd_spin); binder spinners continuously bind 40-byte AF_UNIX sonames containing a forged fdtol (leader=user page w/ flag byte 1, next=user SPIN page) -> a mid-walk walker chases a freed entry reclaimed by a marker -> ACT branch runs, chase lands on the user SPIN self-loop -> walker PARKED inside closef() -> flip SPIN+0x20 from userspace to aim the kernel walk at ANY address A -> gated zero-write *(int*)(A+8)=0 (plus ++/-- pair and wakeup(A)) -> chase *(A+0x20) (victim soname leads back to re-park) -> verified twice via net.local.dgram.pcblist soname readback (victim +8 dword 0x41414141 -> 0x00000000). uid0 blocked: zero-write cannot set pointers; chase-survival constraint confines foreign writes; no 33..40B occupant provides refcount@+0==1 + forged next/prev (sockaddr sa_len/family at +0, pargs kernel-owned +0/+4 with ar_length>0 sleeping fdfree, pollfd/sembuf 16-bit fields capping pointers <2^48); no fn-ptr dispatch through attacker data. Original walk-race still panics ~40-60% of attempts (closef+0x80) when the forks M_ZERO recycle beats the marker.", "evidence": "run_machine_success.log (full untrimmed decisive run: stall detected -> [verify 0] victim 0xfffff8004f145260 +8..+11 = 00000000 KERNEL WRITE VERIFIED -> SUCCESS -> [sess2] race-free second write at 0xfffff8004f145288+8 -> uid=1001 system alive); exp5792.c (exploit source); EXPLOIT.md (mechanics); serial_panic_run1/2.txt (original 2/2 baseline traps); fix_ab/ (A/B on fix.diff kernel).", "kernel_refs": [ "sys/kern/kern_descrip.c:3063", "sys/kern/kern_descrip.c:3071", "sys/kern/kern_descrip.c:3074", "sys/kern/kern_descrip.c:3076", "sys/kern/kern_descrip.c:2675", "sys/kern/kern_descrip.c:2678", "sys/kern/kern_descrip.c:2686", "sys/kern/kern_fork.c:557", "sys/kern/kern_descrip.c:3346", "sys/sys/filedesc.h:109" ], "poc_changes": "exploit-development run: exp5792.c written fresh -- marker-soname reclaim of the freed fdtol chunk (unp_bind parses only to the path NUL; dup_sockaddr copies all 40 attacker bytes), user-page flag gates + SPIN self-loop park, pcblist (DF-5816) as readback channel, victim sonames with +4==0 gate, paced churn (1 fork/~2.5ms) so binders win the LIFO recycle against the churners own next fork, sticky p_advlock_flag fillers + helper lock ranges to stretch walk windows. Several dead ends mapped first: RFTHREAD refcount lost-update race is REAL in the shipped kernel (fork1+0x3f8 addl vs fdfree split dec) but kern_exit.c:384 p_peers chain walk wedges the guest under churn, and its write potential dies on the occupant analysis.", "attempts": 12, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026\troot@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 21000, "guest_dirty": 0, "build_cmd": "cc -O2 -o exp5792 exp5792.c", "run_cmd": "./exp5792 120 (uid 1001, stock kernel; decisive run in run_machine_success.log; ~40-60% of attempts end in the original fatal trap closef+0x80 instead -- retry on fresh boot)", "code_hash": "8996458a0de6814b3639a6c9f1e73a8314a19515b0cde06c432796301629a3c9", "notes": "Impact ceiling honestly reported: persistent user-steerable gated arbitrary-address 4-byte kernel zero-write (memcorrupt bucket), 1-bit kernel read oracle (gate pass/fail observable via victim zeroing), spurious wakeup primitive, cross-identity POSIX lock strip via forged fdl_leader. uid0 NOT composed -- blockers documented in VERDICT.md exploit addendum (occupant matrix for the 40-byte zone; 2^48 pointer cap on all forgeable occupants; zero-write cannot set pointers; no fn-ptr dispatch). Guest run-to-run: the storm itself still triggers the original fatal trap ~40-60% (evidence the underlying bug is live); successes leave one parked walker burning a CPU by design. NEW for maintainers: kern_exit.c:330-388 p_peers walk is unsynchronized vs concurrent RFTHREAD fork prepends (kern_fork.c:484-486) -- hard guest freeze under RFTHREAD churn, independent bug observed during development; and kern_fork.c:570s fdl_refcount++ remains unsynchronized vs fdfrees split decrement even WITH fix.diff applied.", "recommended_fix": "Serialize ALL filedesc_to_leader mutation under fdp->fd_spin as in fix.diff (validated: walk+insert), and additionally: (a) make kern_fork.c:570s fdtol->fdl_refcount++ atomic or fd_spin-protected to close the lost-update race vs fdfrees decrement; (b) reconsider the p_peers chain handling in kern_exit.c:384 for RFTHREAD processes.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline: 2/2 fatal traps at closef+0x80 (<=60s and <4min). Patched: 2x240s storms, 20,791,326 closef walks and 1,533,261 unlink+kfree events, zero panics, guest healthy. fix.diff applied cleanly to the guest /usr/src and booted.", "fix_evidence": "fixrun1.stats (opens=10673660 forks=764229 rc=0), fixrun2.stats (opens=10117666 forks=769032 rc=0), dmesg grep fatal|panic => 0 on patched kernel #1/#2; baseline serial logs in serial_panic_run1/2.txt." } |