NULL CDB pointer dereference in sym_action() INQUIRY peek (CAM_CDB_POINTER with cdb_ptr == NULL)
Summary
sym_action() peeks the first CDB byte with cdb_ptr[0] whenever CAM_CDB_POINTER is set, before any validation; sym_setup_cdb()'s bcopy shares the exposure for cdb_len > 0. A CCB with cdb_ptr == NULL - deliverable by root via /dev/passN CAMIOCOMMAND (xpt_merge_ccb copies ccb_h.flags verbatim; cam_periph_mapmem never touches cdb_ptr) - NULL-derefs in kernel context and panics. Privileged-only fixed-offset NULL read; no corruption primitive. Trigger not runnable on the virtio guest (no 53C8xx PCI function; sym.ko unattached) - source-verified certain, not_reproduced. Fix: guard the peek + reject NULL CDB pointers in sym_setup_cdb.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3575 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 2.6 KB | β raw | |
| fix.diff | β | 1.5 KB | view raw | |
| verdict.json | β | 2.6 KB | view raw | |
| env.txt | β | 975 B | view raw |
DF-3575 β NULL CDB pointer dereference in sym_action() INQUIRY peek
File: sys/dev/disk/sym/sym_hipd.c (pass 2, GLM 5.3 re-audit)
What
sym_hipd.c:7526-7528:
if (!(ccb_h->flags & CAM_CDB_PHYS) &&
(0x12 == ((ccb_h->flags & CAM_CDB_POINTER) ?
csio->cdb_io.cdb_ptr[0] : csio->cdb_io.cdb_bytes[0]))) {
When an XPT_SCSI_IO CCB arrives with CAM_CDB_POINTER set and
csio->cdb_io.cdb_ptr == NULL, the first operand dereferences NULL before
sym_setup_cdb() ever runs (its bcopy(csio->cdb_io.cdb_ptr, cp->cdb_buf,
cmd_len) at sym_hipd.c:7695 would also deref NULL for any cdb_len > 0).
passsendccb() (scsi_pass.c:526-528 onwards) merges the user-supplied CCB
flags verbatim (xpt_merge_ccb() copies ccb_h.flags) and never validates
cdb_ptr; CAM's cam_periph_mapmem() only maps the data pointer, not the
CDB pointer. So a privileged user (root via /dev/passN, mode 0600
root:operator per scsi_pass.c:279-280) sending CAMIOCOMMAND with
CAM_CDB_POINTER | cdb_ptr = NULL takes the kernel to a NULL deref β
panic. (Also reachable by any in-kernel peripheral that sets
CAM_CDB_POINTER without a pointer β none currently do.)
Impact
Privileged-only kernel NULL-deref panic (DoS). No memory-corruption primitive: the read is at a fixed NULL offset. Severity Low per the audit rubric ("requires ... already-privileged user").
Why not verified on the guest
The dereference lives in sym_action(), the SIM entry point β it only runs
once a sym(4) adapter has attached to an NCR/LSI 53C8xx PCI device
(vendor 0x1000, dev 0x000b-0x0021). The audit guest is QEMU/virtio and has
no such PCI device (pciconf -l shows only virtio/isa; sym.ko is present
in /boot/kernel but cannot attach), so the path is not executable there.
Source-verified only: the deref is unambiguous from the code.
Fix
fix.diff β (1) guard the INQUIRY peek, (2) reject a NULL CDB pointer in
sym_setup_cdb() with CAM_REQ_INVALID (covers the bcopy path as well).
DF-3575 β VERDICT (pass 2 audit of sys/dev/disk/sym/sym_hipd.c)
STATUS: not_reproduced (source-verified; hardware absent on guest) IMPACT: none (privileged-only kernel NULL-deref panic if hardware present) CONFIDENCE: certain (that the deref exists and is reachable by a privileged caller via CAMIOCOMMAND); exploit ceiling is a panic.
Narrative
sym_hipd.c:7526-7528, inside sym_action() (XPT_SCSI_IO):
if (!(ccb_h->flags & CAM_CDB_PHYS) &&
(0x12 == ((ccb_h->flags & CAM_CDB_POINTER) ?
csio->cdb_io.cdb_ptr[0] : csio->cdb_io.cdb_bytes[0]))) {
The INQUIRY-opcode "peek" dereferences csio->cdb_io.cdb_ptr[0] whenever CAM_CDB_POINTER is set, before sym_setup_cdb() runs. With cdb_ptr == NULL this is a NULL-page read -> panic. The later bcopy in sym_setup_cdb (sym_hipd.c:7695) has the same exposure for any cdb_len > 0.
Delivery path (verified in-tree): /dev/passN (0600 root:operator β scsi_pass.c:279-280) -> passioctl CAMIOCOMMAND (scsi_pass.c:464) -> passsendccb -> xpt_merge_ccb copies ccb_h.flags verbatim (cam_xpt.c:1818-1830; flags include CAM_CDB_POINTER) -> cam_periph_mapmem only maps the DATA pointer, never cdb_ptr (scsi_pass.c:564-575) -> xpt_action(XPT_SCSI_IO) -> sym_action -> NULL deref.
So: a privileged user (root) can panic the kernel with one ioctl on a system that has a sym-attached SCSI bus. Root-only trigger, fixed-NULL read, no corruption primitive => Low severity. Kernel-internal senders (da/cd/etc.) never set CAM_CDB_POINTER without a valid pointer, so no unprivileged or remote path exists.
Why not reproduced on the guest
sym_action() only executes after a sym(4) adapter attaches to PCI vendor 0x1000 (53C8xx). Guest is QEMU/virtio: pciconf -l => hostb/isab/atapci/none/vgapci/virtio_pci{0,1} only. /boot/kernel/sym.ko exists, 'pci/sym' in kldstat -v alias table, but no device to attach to => sym_action unreachable at runtime. The code path itself is unambiguous; source-level certainty stands.
Fix
findings/poc/DF-3575/fix.diff: (1) guard the INQUIRY peek against cdb_ptr == NULL; (2) reject NULL CDB pointers in sym_setup_cdb with CAM_REQ_INVALID before the bcopy (covers all later uses). Not build-validated (no sym hardware to attach a patched kernel); the diff is minimal and self-evident.
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
not_testablefix.diff authored against the read-only sys/ tree (never applied). Not build-validated: guest has no sym adapter so the patched path cannot be exercised; the change is a two-line NULL guard plus a validation return, no behavioral risk.
findings/poc/DF-3575/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-3575/VERDICT.md (delivery path traced through scsi_pass.c and cam_xpt.c with cites); findings/poc/DF-3575/fix.diff (peek guard + CAM_REQ_INVALID on NULL cdb pointer in sym_setup_cdb); findings/poc/DF-3575/env.txt (guest probe)
PoC changes
No PoC seed existed. A guest-side trigger is impossible (no sym adapter to attach; sym_action unreachable), so no PoC source was authored β env probe recorded instead.
Verified recommended fix
Guard the INQUIRY peek against cdb_ptr == NULL and reject NULL CDB pointers in sym_setup_cdb() with CAM_REQ_INVALID (findings/poc/DF-3575/fix.diff).
Verdict
sym_action() peeks at the first CDB byte with csio->cdb_io.cdb_ptr[0] whenever CAM_CDB_POINTER is set (sym_hipd.c:7526-7528) before any validation; a CCB with cdb_ptr == NULL (deliverable by root via /dev/passN CAMIOCOMMAND, since xpt_merge_ccb copies ccb_h.flags verbatim and cam_periph_mapmem never touches cdb_ptr) NULL-derefs in kernel context and panics. sym_setup_cdb's bcopy (sym_hipd.c:7695) shares the exposure. Privileged-only fixed-offset NULL read: panic/DoS, no corruption primitive -> Low. Not reproduced: the deref lives in the SIM entry path which requires an attached 53C8xx adapter; the guest is virtio-only (sym.ko present, no PCI device to attach), so the path cannot execute there. Source-level certainty is unambiguous.
No comments yet.