β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2286

Kernel pointer leak via targreturnccb copyout of ccb_hdr.cbfcnp and ccb_hdr.path

Summary

targreturnccb() copies [retry_count periph_priv) slice of ccb_hdr back to user-supplied CCB without sanitizing kernel-only fields cbfcnp and path. cbfcnp unconditionally set to kernel text address &targdone (targsendccb:769 targgetccb:1001) and path set to kernel heap pointer softc->path by xpt_setup_ccb. Both fields inside copied range so every read()/inline-write path leaks kernel function pointer and kernel heap pointer defeating KASLR. Sibling scsi_pass.c:610 explicitly NULLs cbfcnp before copyout; scsi_target.c does not. RESTRICTEDROOT gate (delegatable to non-root subjects). No integrity/availability impact by itself but turns any separate kernel bug from unexploitable to exploitable. [VERIFIED pass2: reproduced - user CCB cbfcnp equals and-targdone exactly by nm cross-check plus kernel-heap cam_path, scsi_pass sanitizes and scsi_target does not; KASLR defeated for RESTRICTEDROOT daemons; staged-sanitized copyout fix-validated]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2286 Β· 7 files
FileTypeDescriptionSize
poc2286.c β€” 4.0 KB view raw
targsim.c β€” 13.9 KB view raw
run.log β€” 581 B view raw
env.txt β€” 191 B view raw
fix.diff β€” 1.2 KB view raw
verdict.json β€” 2.7 KB view raw
README.md β€” 1.7 KB ↓ raw

DF-2286 β€” targreturnccb() leaks kernel text + heap pointers (cbfcnp, path) to the target daemon

What (sys/bus/cam/scsi/scsi_target.c:955-984)

targreturnccb copyouts the [retry_count, periph_priv) slice of the kernel ccb_hdr into the daemon's CCB (:967-969). That slice CONTAINS cbfcnp β€” unconditionally &targdone (set at targsendccb:769 and targgetccb:1001, kernel text) β€” and path β€” the kernel heap cam_path stamped by xpt_setup_ccb (:999). The sibling driver scsi_pass.c:610 explicitly NULLs cbfcnp before copying back; scsi_target.c does not.

Rig

BUSAUDIT kernel (stock + device targ), targsim.ko fake SIM (target_sprt=PIT_PROCESSOR) β€” the DF-5375 family rig.

Reproduce (guest, root)

make KERNBUILDDIR=/usr/obj/usr/src/sys/BUSAUDIT     # targsim.ko
cc -O -Wall -o poc2286 poc2286.c
kldload ./targsim.ko; sleep 3
./poc2286            # enable, write ATIO, complete held CCBs, read back

Result (run.log)

DF2286: CONTROL read(pack_id...) ... (ATIO delivered via user_ccb_queue)
DF2286: read() returned user_ccb=0x7fffffdfd7a0 (expect ...d7a0)
DF2286: LEAKED uccb.ccb_h.cbfcnp = 0xffffffff802c8ff0 (KERNEL pointer)
DF2286: LEAKED uccb.ccb_h.path   = 0xfffff8008d6c0fa0 (KERNEL heap pointer)
nm kernel.debug | grep -w targdone -> ffffffff802c8ff0 t targdone

cbfcnp equals &targdone EXACTLY (kernel symbol match) and path is a kernel-realm heap pointer β€” both disclosed to userland on every completed ATIO/INOT/CTIO. KASLR-defeating info leak (RESTRICTEDROOT gated: /dev/targ clone is 0600 root:wheel).

Fix

fix.diff stages the slice in a local ccb_hdr, NULLs cbfcnp and path in the staging copy, and copyouts the sanitized version (mirrors scsi_pass.c:610). Validated on the fix kernel (VERDICT.md).

Fix verification

fixed
↓ fix.diffper-fix-DF-2286

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: cbfcnp 0xffffffff802c8ff0 with nm targdone ffffffff802c8ff0 t targdone in the same log; path 0xfffff800... (kernel heap); read() returned the exact user_ccb pointer submitted

PoC changes

Authored from scratch on the DF-5375 targsim rig (no seed). Fixed a compound-literal sysctl write, the sysctl name (hw.targsim.complete not hw.targsim_complete), and offsetof usage (__builtin_offsetof). A first run panicked inside targsim ev_sysctl_complete because a prior aborted run left a stale held ATIO from a closed fd -- the canonical single-fd choreography on a fresh boot is deterministic.

Verified recommended fix

Stage the copyout slice in a local ccb_hdr, NULL cbfcnp and path in the staging copy (mirrors scsi_pass.c:610) -- fix.diff.

Verdict

REPRODUCED on the BUSAUDIT kernel with the targsim fake-SIM rig: after writing an ATIO and completing it via the real CAM path, the daemon reads its own CCB pointer back and finds uccb.ccb_h.cbfcnp = 0xffffffff802c8ff0 -- exactly nm(kernel.debug) targdone -- and uccb.ccb_h.path = 0xfffff8008d6c0fa0 (kernel-realm cam_path heap pointer). targreturnccb (scsi_target.c:967-969) copyouts the [retry_count, periph_priv) slice of the kernel ccb_hdr without sanitizing cbfcnp (unconditionally &targdone via :769/:1001) or path (xpt_setup_ccb :999), defeating KASLR for every completed ATIO/INOT/CTIO. Sibling driver scsi_pass.c:610 NULLs cbfcnp before its copy-back; scsi_target.c does not. RESTRICTEDROOT gated (/dev/targ clone is 0600 root:wheel).