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)
PoC verification
Evidence pack
findings/poc/DF-2286 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
fixedConfirmed 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).
No comments yet.