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_ptrisNULL, or - a 1-byte kernel-heap OOB read followed by an OOB write of the masked byte if
data_ptrpoints to a zero-sizemallocslab 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).
Recommended fix
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
sys/dev/raid/mfi/mfi_cam.c:339-343β unguardeddata_ptr[0]derefsys/dev/raid/mfi/mfi_cam.c:220-244β validator acceptsCAM_DIR_NONEsys/dev/raid/mfi/mfi_cam.c:306-311βmfip_startsetscm_data=NULLfor NONEsys/dev/raid/mfi/mfi.c:2159βcm_data != NULLguard in DMA map
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2016 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
DF-2016 PoC β Unguarded data_ptr deref in INQUIRY rewrite of mfip_done
Preconditions
- LSI MegaRAID SAS controller with
mfi(4)attached. - At least one physical drive target exposed via pass-through (
/dev/passN). - User has R/W access to
/dev/passN(typicallyoperatorgroup 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 insidemfip_donereadingcsio->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 -- 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_testableVALIDATED build.
VALIDATED build.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- f
- i
- /
- m
- f
- i
- _
- c
- a
- m
- .
- c
- :
- 3
- 3
- 9
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- f
- i
- /
- m
- f
- i
- _
- c
- a
- m
- .
- c
- :
- 3
- 4
- 0
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.
No comments yet.