XPT_ABORT handler dereferences the ccb hcb back-pointer without a NULL check β aborting a completed/never-issued CCB panics
Summary
XPT_ABORT reads abort_hcb->ccb where the back-pointer is NULLed on every completion and NULL for CCBs never issued. Any abort of a not-in-flight CCB faults at a small fixed offset from NULL. Reachable via targ(4) abort paths racing a concurrent completion (abort_all_pending under crit only while completions clear the pointer under sc->lock), and targwrite's user abort translation. The sibling virtio-scsi never dereferences a back-pointer for abort. NULL-check + back-pointer-hygiene fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4222 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | summary | 2.8 KB | β raw |
| VERDICT.md | verdict | line-by-line trace + reachability + fix validation | 4.7 KB | β raw |
| fix.diff | suggested-fix | one-line NULL check | 459 B | view raw |
| patch_apply.log | apply-log | clean patch -p2 | 319 B | view raw |
| build.log | build-log | module rebuild with fix | 876 B | view raw |
| env.txt | environment | guest uname, source md5, no-VMware-PCI note | 478 B | view raw |
| verdict.json | verdict | machine verdict | 3.3 KB | view raw |
DF-4222 β pvscsi XPT_ABORT handler dereferences a NULL hcb back-pointer (panic on abort of a completed CCB)
File: sys/dev/virtual/vmware/pvscsi/pvscsi.c (pass 2; DF-1454/DF-1455 excluded)
What
sys/dev/virtual/vmware/pvscsi/pvscsi.c:1394-1397
abort_ccb = ccb->cab.abort_ccb;
abort_hcb = abort_ccb->ccb_h.ccb_pvscsi_hcb;
if (abort_hcb->ccb != NULL && abort_hcb->ccb == abort_ccb) { /* <-- deref */
ccb_pvscsi_hcb is the driver's per-CCB back-pointer. It is cleared to
NULL on every normal completion:
sys/dev/virtual/vmware/pvscsi/pvscsi.c:1018-1019
ccb->ccb_h.ccb_pvscsi_hcb = NULL;
ccb->ccb_h.ccb_pvscsi_sc = NULL;
and is NULL for any CCB that was never issued through this SIM. The XPT_ABORT
handler dereferences it unconditionally (abort_hcb->ccb on the first line
of the condition) β NULL->ccb read at a small fixed offset β kernel page
fault / panic whenever CAM asks pvscsi to abort a CCB that is not currently
in flight on this SIM.
The correct pattern is the standard one used by every other checked SIM in this tree β NULL-check the back-pointer before dereferencing:
if (abort_hcb != NULL && abort_hcb->ccb == abort_ccb) {
(the original first clause abort_hcb->ccb != NULL short-circuits the same
way once the pointer itself is checked; the semantics for a live hcb are
identical).
Reachability
XPT_ABORT reaches pvscsi_action() from:
- targ(4) kernel paths β
abort_all_pending()(sys/bus/cam/scsi/scsi_target.c:1074-1116) walks the pending queue undercrit_enter()only (scsi_target.c:498-500) and issuesxpt_action(XPT_ABORT)per CCB; a completion landing on another CPU between queue inspection and the abort running clearsccb_pvscsi_hcb(pvscsi.c:1018) β NULL deref. Same forscsi_targ_bh.c:351,359. - targ(4) user paths β
targwriteaccepts user XPT_ABORT CCBs (scsi_target.c:719-745 translates the user abort_ccb pointer to a kernel CCB found on the pending queue; the same completion race applies). - Any kernel or userland (
/dev/xpt0CAMIOCOMMAND, root) issuer that aborts a CCB after it completed.
targ device nodes are privileged (root/operator), and the completion race
window is narrow, so this is a privileged/racy local DoS β but it is a plain
missing NULL check, trivially fixed. Contrast: the sibling virtio-scsi driver
never dereferences a back-pointer for abort (vtscsi_cam_abort,
virtio_scsi.c:941-974 uses its own request queue).
Reproduce / validation
No pvscsi device on the audit guest (QEMU; no 15ad PCI ids) β no live
trigger. Source trace in VERDICT.md; fix.diff (one-line NULL check)
applies cleanly to the pristine guest kernel source and the module rebuilds
(build.log).
Files
VERDICT.md, fix.diff, patch_apply.log, build.log, env.txt,
manifest.json, verdict.json
DF-4222 VERDICT β source-certain NULL back-pointer deref in XPT_ABORT; HW(vmm)-gated guest, not live-reproducible
Status: inconclusive (no pvscsi device on audit guest); defect certain at source level
Root cause
pvscsi_action() case XPT_ABORT:
sys/dev/virtual/vmware/pvscsi/pvscsi.c:1394 abort_ccb = ccb->cab.abort_ccb; sys/dev/virtual/vmware/pvscsi/pvscsi.c:1395 abort_hcb = abort_ccb->ccb_h.ccb_pvscsi_hcb; sys/dev/virtual/vmware/pvscsi/pvscsi.c:1397 if (abort_hcb->ccb != NULL && abort_hcb->ccb == abort_ccb) { ^^^^^^^^^^^^^^^ NULL deref when abort_hcb == NULL
ccb_pvscsi_hcb lifecycle in this driver:
- set when a SCSI I/O starts: pvscsi.c:1326 (
ccb_h->ccb_pvscsi_hcb = hcb); - cleared on every completion: pvscsi.c:1018
(
ccb->ccb_h.ccb_pvscsi_hcb = NULL;) insidepvscsi_process_completion, which runs undersc->lockfrom the interrupt / cmp-ring processing; - never set for CCBs that never passed through
pvscsi_action(XPT_SCSI_IO).
Therefore any XPT_ABORT whose abort_ccb (a) already completed normally or
(b) was never issued through this SIM presents abort_hcb == NULL and the
first operand read abort_hcb->ccb faults at a small fixed offset from NULL
(union ccb's ccb_h is at offset 0; spriv_ptr0 sits a few hundred bytes
in) β page fault β panic.
The driver's own error paths make the dangling-pointer situation worse in a
benign direction but underline the inconsistency: the bus_dmamap_load_ccb
failure path (pvscsi.c:1200-1212) and the two finish_ccb paths
(pvscsi.c:1329-1341) xpt_done() the CCB without clearing
ccb_pvscsi_hcb (unlike pvscsi.c:1018) β leaving a stale non-NULL pointer
that happens not to crash the abort handler only because the hcb memory is
still valid.
Race that reaches it in-tree
targ(4):
sys/bus/cam/scsi/scsi_target.c:498-500 crit_enter(); abort_all_pending(softc); crit_exit(); sys/bus/cam/scsi/scsi_target.c:1099-1103 TAILQ_FOREACH(ccb_h, &softc->pending_ccb_queue, ...) { cab->abort_ccb = (union ccb *)ccb_h; xpt_action((union ccb *)cab);
crit_enter() blocks interrupts only on the current CPU. A pvscsi
completion interrupt on another CPU completes the very CCB being iterated
(pvscsi_process_completion clears ccb_pvscsi_hcb at pvscsi.c:1018 and
targdone removes it from the pending queue); the subsequently dispatched
XPT_ABORT then dereferences the now-NULL back-pointer β panic. The
user-triggered abort path (targwrite β targgetccb translation at
scsi_target.c:719-745) has the same window against the pending queue.
Impact ceiling: local denial of service (panic). Requires pvscsi hardware (VMware host) plus a privileged or racy CAM-level abort β hence severity Low.
Why not a duplicate of DF-1454/DF-1455
Those cover the completion ring's device-fed sense_len bound and
context/log2 validation. This is the CAM action path (XPT_ABORT) and a
host-side NULL check β different function, different input, different fix.
(A comparison against the FreeBSD original was attempted but the FreeBSD
pvscsi driver never shipped in a release branch and is no longer fetchable;
the defect is established from DragonFly's own source, which NULL-checks the
identical pointer in pvscsi_timeout at pvscsi.c:850-853 but not here.)
Fix validation
fix.diff= one-line change to the standardabort_hcb != NULL && abort_hcb->ccb == abort_ccbpattern.- Applied with
patch -p2to the pristine guest kernel source (md5-identical to hostsys/, seeenv.txt):patch_apply.logβ clean. - Module rebuild:
build.logβ OK. - Runtime HW-gated on this guest (no pvscsi device;
pciconf -lshows no 15ad vendor) βfix_status: inconclusive(compile-validated).
Kernel references
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1389-1411 (XPT_ABORT case)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1018-1019 (pointer cleared on completion)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1326-1327 (pointer set at start)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:850-853 (timeout path DOES NULL-check)
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1200-1212, 1329-1341 (error paths leave stale pointer)
- sys/bus/cam/scsi/scsi_target.c:498-500, 1099-1103, 719-745 (racy issuers)
- sys/dev/virtual/virtio/scsi/virtio_scsi.c:941-974 (safe sibling pattern)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
inconclusiveOne-line NULL-check fix applies cleanly to the pristine guest kernel source (patch_apply.log) and the pvscsi module compiles (build.log). Runtime validation HW-gated on this guest.
patch_apply.log (clean apply), build.log (module rebuild OK)
Confirmed kernel references
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1389-1411
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1395-1397
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1018-1019
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:850-853
- sys/dev/virtual/vmware/pvscsi/pvscsi.c:1200-1212
- sys/bus/cam/scsi/scsi_target.c:498-500
- sys/bus/cam/scsi/scsi_target.c:1099-1103
- sys/dev/virtual/virtio/scsi/virtio_scsi.c:941-974
Detail
Evidence (decisive lines)
VERDICT.md: pvscsi.c:1397 first-operand deref vs pvscsi.c:1018 NULL-clear vs pvscsi.c:850-853 guarded timeout path; scsi_target.c:498-500 crit_enter-only abort loop racing pvscsi.c:1018. patch_apply.log + build.log: one-line fix applies and compiles on the pristine guest kernel source.
PoC changes
no PoC source β device-gated; evidence is the source trace plus on-guest fix build validation
Verified recommended fix
Change the XPT_ABORT guard to 'if (abort_hcb != NULL && abort_hcb->ccb == abort_ccb)' and clear ccb_pvscsi_hcb in the execute_ccb/finish_ccb error paths
Verdict
Source-certain NULL dereference: pvscsi_action(XPT_ABORT) at pvscsi.c:1395-1397 reads abort_hcb->ccb where abort_hcb = abort_ccb->ccb_h.ccb_pvscsi_hcb, a back-pointer the driver itself sets to NULL on every completion (pvscsi.c:1018) and that is NULL for CCBs never issued through this SIM. Aborting a completed/never-issued CCB β reachable in-tree through targ(4) abort paths that race a concurrent completion (scsi_target.c:498-500,1099-1103 run under crit_enter() only, completion clears the pointer on another CPU under sc->lock) β faults at a small fixed offset from NULL => panic. The driver already NULL-checks the identical pointer in its timeout handler (pvscsi.c:850-853), proving the intended protocol; every other checked SIM in the tree validates before dereference. Not live-reproducible on the audit guest (QEMU exposes no pvscsi device) so impact recorded as none; realistic ceiling is a privileged/racy local DoS on VMware guests.
No comments yet.