DF-5376 / verdict.json
{ "finding_id": "DF-5376", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED as an arbitrary-address kernel WRITE plus kernel-global-queue corruption (memory-corruption class; impact field panic understates it -- mode B ends in a trap only because DF kernel threads run without the user pmap). targusermerge() returns ESRCH when the daemons XPT_ABORT abort_ccb cannot be translated to a pending kernel CCB (scsi_target.c:741-744), but the inline send path checks only != EFAULT (scsi_target.c:626) and forwards the CCB anyway with the raw user pointer still in cab.abort_ccb. xpt_action(XPT_ABORT) (cam_xpt.c:3075-3124) then walks that pointer as a kernel union ccb. Mode A (clean, non-fatal): with the fake CCB in an mmapd user page the kernel wrote CAM_REQ_ABORTED|CAM_DEV_QFRZN (0x42) into fake ccb_h.status and incremented fake path->device->qfrozen_cnt (0x11110000 -> 0x11110001) at daemon-chosen addresses, and write() returned success -- proving the arbitrary-address write primitive end-to-end in syscall context (guest has no SMAP). Mode B (index=1 branch): cam_ccbq heap surgery on the fake queue, then xpt_done(fake) linked the fake struct cam_sim onto the kernel-global cam_simq and setsoftcambio(); the SWI then faulted at swi_cambio+0x59 writing into fake_sim+0x48 -- the kernel CAM completion machinery operating on attacker-supplied memory. The final (*ccb_h->cbfcnp)(periph,ccb) indirect call (attacker RIP) is gated only by the fake tree living in userland while the SWI runs without the user pmap; relocating it into kernel memory (e.g. inside a kernel CCB payload -- targusermerge copyin()s daemon data verbatim into xpt_alloc_ccb() allocations -- with addresses from the DF-2286 cbfcnp/path leak class; no KASLR on this guest) yields full kernel RIP control. Unprivileged escalation is hard-blocked by the device open (0600 root:wheel + SYSCAP_RESTRICTEDROOT, verified live); the primitive is directly weaponizable by the privileged daemon. Fix validated: == 0 check stops the send on ESRCH; no kernel writes into the fake tree, no SWI chain, guest healthy.", "exploit_chain": "write(fd,&ccb,8) with func_code=XPT_ABORT and cab.abort_ccb = fake userland CCB (func_code=0x901, index=CAM_UNQUEUED_INDEX, fake path->bus->sim->lock = real sim lock to pass KKASSERT(lockowned), fake path->device) -> targusermerge returns ESRCH (no pending match) -> targwrite:626 != EFAULT forwards it -> xpt_action XPT_ABORT branch 2: status write 0x42 + xpt_freeze_devq qfrozen_cnt++ at chosen addresses [mode A proof]. Branch 1 (index=1): cam_ccbq_remove_ccb surgery + xpt_done(fake) -> fake sim TAILQ_INSERTed onto kernel cam_simq -> SWI camisr walks it -> (*cbfcnp) call = RIP control once the fake tree is kernel-resident (e.g. inside a targusermerge-copied CCB payload; addresses via DF-2286-style leak) [mode B proof: trap at swi_cambio+0x59 on fake_sim+0x48].", "evidence": "run.log (mode A: status 0x42 written, qfrozen_cnt 0x11110000->0x11110001, write() success, guest alive); panic.b.txt + run.b.log (mode B: Fatal trap 12 supervisor WRITE at 0x8004a1248 = fake_sim+0x48 in the daemons mmap page, Stopped at swi_cambio+0x59, current process Idle); targsim.c (offset dump sysctl hw.targsim.off); run.patched.log (fake untouched, no SWI chain).", "kernel_refs": [ "sys/bus/cam/scsi/scsi_target.c:626", "sys/bus/cam/scsi/scsi_target.c:720", "sys/bus/cam/scsi/scsi_target.c:741", "sys/bus/cam/cam_xpt.c:3075", "sys/bus/cam/cam_xpt.c:3086", "sys/bus/cam/cam_xpt.c:3094", "sys/bus/cam/cam_xpt.c:4739", "sys/bus/cam/cam_xpt.c:4891", "sys/bus/cam/cam_xpt.c:7506" ], "poc_changes": "No seed existed. Fake-CCB builder reads private cam_xpt.c struct offsets (cam_path/cam_eb/cam_sim/cam_ed/cam_ccbq/camq/cam_periph) from the rig modules sysctl dump (shadow structs copied verbatim from cam_xpt.c so offsetof matches the running kernel); fake sim->lock points at the REAL targsim lock (held by targwrite) to satisfy xpt_freeze_devqs KKASSERT(lockowned()).", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Sat Oct 3 17:18:54 UTC 2026 x86_64 (baseline) / #3: Sat Oct 3 18:32:37 UTC 2026 (patched)", "runtime_sec": 900, "guest_dirty": 0, "build_cmd": "cd /root/targpwn && make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC && cc -O0 -g -o poc5376 poc5376.c", "run_cmd": "cd /root/targpwn && kldload ./targsim.ko && sleep 2 && ./poc5376 a (clean write demo, guest lives); ./poc5376 b (RIP-control chain, guest dies)", "code_hash": "45da05159351bd1408ec1ace1bd4d379ec38cb347c7132e6a0a2e9e100f8f49d", "notes": "uid=0 not pursued past the SWI boundary: the device open is root-only on the stock guest (verified: unpriv open EACCES), so the unpriv->root chain is hard-blocked by design; for the root daemon the demonstrated primitive (arbitrary-address write + controlled fake-sim on the kernel completion queue) is already full kernel corruption. RIP-control completion route documented in VERDICT.md.", "recommended_fix": "In targwrite()s inline path send only when targusermerge() returned 0 (i.e. == 0 instead of != EFAULT), so a failed abort_ccb translation is never forwarded to xpt_action().", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline mode A: kernel wrote 0x42 into fake ccb_h.status and 0x11110001 into fake qfrozen_cnt (chosen addresses), write() returned success. Baseline mode B: fake sim linked onto kernel cam_simq, SWI faulted operating on it (swi_cambio+0x59). Patched: mode A fake values byte-identical after write (kernel writes not observed), daemon CCB comes back CAM_PATH_INVALID; mode B no SWI chain after 5s, guest healthy. Primitive eliminated.", "fix_evidence": "run.log + panic.b.txt (baseline); run.patched.log (untouched fakes, alive).", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #3: Sat Oct 3 18:32:37 UTC 2026 x86_64" } |