⬢ DragonFlyBSD Kernel Audit
DF-4147 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4147",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "uid0_privesc",
  "confidence": "certain",
  "verdict": "cuse_proc2proc_copy/proc_rwmem_dfly (cuse.c:881-893) passes the server-controlled peer_ptr of CUSE_IOCTL_{READ,WRITE}_DATA as the raw kernel-side pointer of copyin()/copyout(); DFlys std_copyin/std_copyout validate only the user side (support.s:228-328), so any process holding /dev/cuse open with one pending client command gets arbitrary kernel read AND write. Demonstrated 4/4 runs on DragonFly 6.5-DEVELOPMENT: read of kernel `version symbol, write to the same (readback verified), and a full chain that turned a setuid(nobody) child process into uid=0 by walking kern.proc kp_paddr -> struct proc+0x10 p_ucred and zeroing its uid/gid fields. Threat model: /dev/cuse is 0600 root and CREATE_DEV needs SYSCAP_NODRIVER, so the attacker is a (possibly caps-jailed) userspace driver daemon - this is a sandbox escape to full kernel compromise; it is not reachable by an unprivileged user without such a daemon. The bug also functionally breaks all legitimate >4KiB cuse transfers (they EFAULT or land in the servers own address space). fix.diff (procfs_rwmem-style vm_fault_page walk + VM_MAX_USER_ADDRESS rejection of the peer address) was built into cuse.ko and validated: kernel read AND write now return EFAULT, and the legitimate cross-process fallback copy works for the first time.",
  "exploit_chain": "root/capped daemon opens /dev/cuse; CREATE_DEV makes a world-accessible /dev node; an unprivileged process opens it and blocks in open(); server GET_COMMAND takes the pending CUSE_CMD_OPEN; CUSE_IOCTL_READ_DATA{peer_ptr=KERNEL_VA} copies kernel memory to the server (arbitrary read); CUSE_IOCTL_WRITE_DATA{peer_ptr=KERNEL_VA} writes server data to kernel memory (arbitrary write, no length cap); to escalate any process to root: sysctl kern.proc.pid.<pid> -> kp_paddr (struct proc), kread proc+0x10 -> p_ucred, kwrite zeros over cr_uid/cr_ruid/cr_svuid/cr_gid/... -> target process is uid 0 (demonstrated: nobody child -> uid=0(root) shell reading /etc/spwd.db).",
  "evidence": "run.log/2/3: [+] ARBITRARY KERNEL READ confirmed, [+] ARBITRARY KERNEL WRITE confirmed (readback CUSE-PWNED...), [+] *** CHILD IS uid 0 - privilege escalation complete *** + uid=0(root) gid=0(wheel) + can read shadow db; fix_run.2.log + fix_run_wprobe.log: EFAULT both directions on patched kernel; fixtest_baseline.log vs fix_run_fixtest.log: legit >4KB copy broken before, works after.",
  "kernel_refs": [
    "sys/dev/misc/cuse/cuse.c:881",
    "sys/dev/misc/cuse/cuse.c:895",
    "sys/dev/misc/cuse/cuse.c:949",
    "sys/dev/misc/cuse/cuse.c:1352",
    "sys/platform/pc64/x86_64/support.s:228",
    "sys/platform/pc64/x86_64/support.s:290",
    "sys/vfs/procfs/procfs_mem.c:69"
  ],
  "poc_changes": "PoC authored from scratch (raw ioctl protocol per cuse_ioctl.h inlined; no libcuse on guest). Kernel VA of `version taken from nm /boot/kernel/kernel (0xffffffff81144960, no KASLR). uid/gid fields located robustly by pattern-matching 65534 in a 256-byte ucred dump instead of trusting layout offsets. Child bails out of its open-retry loop on non-ENOENT errors so the patched-kernel run exits cleanly.",
  "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": 1450,
  "guest_dirty": 0,
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0 + fixed cuse.ko (sha256 d921b024de4556055fcedec9b3c0350b7bc52f1b14f4b16defea23a722d03315)",
  "fix_verdict": "fix.diff replaces proc_rwmem_dfly with a procfs_rwmem()-style page walk of the TARGET processs vm map (vm_fault_page + pmap_kenter_quick + uiomove) and rejects peer addresses outside the user range. Incremental cuse.ko rebuild (fix_build.log), installed to /boot/kernel, rebooted: CUSE_IOCTL_READ_DATA and WRITE_DATA against a kernel VA both return EFAULT (fix_run.2.log, fix_run_wprobe.log), the uid0 chain is gone (PoC RC=1, child released with ENXIO), and the positive control shows the legitimate server->client >4KiB fallback copy now works (fix_run_fixtest.log) whereas it EFAULTed on the stock kernel (fixtest_baseline.log).",
  "fix_evidence": "fix_run.2.log: [-] kernel read failed/invalid (error=-1 errno=14) + PATCHED_POC_RC=1; fix_run_wprobe.log: WRITE_DATA ... rc=-1 errno=14 (Bad address); fix_run_fixtest.log: [+] cross-process fallback copy WORKS; fix_build.log: clean -Werror module rebuild.",
  "build_cmd": "cc -O1 -Wall -o /root/cuse_krw /root/cuse_krw.c",
  "run_cmd": "kldload cuse; /root/cuse_krw",
  "notes": "Also observed: several harness-side fd-inheritance wedges (client inheriting /dev/cuse keeps the dead servers file alive; blocked clients become unkillable D-state until that fd closes) - operational amplifier for DF-4149/DF-4148 class issues, noted in those packs.",
  "recommended_fix": "Replace proc_rwmem_dflys raw copyin/copyout with a vm_fault_page-based walk of the target processs map and reject peer addresses >= VM_MAX_USER_ADDRESS (see fix.diff).",
  "code_hash": "776e1887724add56cd9874295c164439812ccd762f65a65d68705f8aadfca06d"
}