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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3575 Β· 5 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-3575

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.