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

WarpDrive validation accepts NumPhysDisks == 0: guaranteed integer divide-by-zero panic in mpssas_direct_drive_io on the first qualifying READ/WRITE

Summary

mps_wd_config_pages rejects NumPhysDisks > 8 and explicitly rejects stripe-size/block-size 0 but never rejects count 0 - a 0-disk RAID0 page sets DD_num_phys_disks = 0 and WD_valid_config = TRUE. Every qualifying READ/WRITE then reaches stripe_unit = physLBA / sc->DD_num_phys_disks -> #DE -> kernel panic on the first qualifying IO by any unprivileged user of the device. Corrupt/malicious controller firmware or NVRAM (0-disk RAID0 pages are a real volume-teardown-race shape). One-clause rejection fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3897 Β· 8 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 2.2 KB ↓ raw
run.sh β€” 111 B view raw
run.log β€” 482 B view raw
env.txt β€” 274 B view raw
fix.diff β€” 710 B view raw
verdict.json β€” 2.3 KB view raw
manifest.json β€” 634 B view raw

DF-3897 β€” WarpDrive validation accepts NumPhysDisks == 0: divide-by-zero panic in mpssas_direct_drive_io

File: sys/dev/raid/mps/mps_sas.c (pass 2); the missing guard lives in sys/dev/raid/mps/mps_config.c, the fatal sinks are in this file.

What

WarpDrive (direct-drive) configuration is enabled by mps_wd_config_pages (sys/dev/raid/mps/mps_config.c), which validates the RAID volume page 0 as:

    if ((raid_vol_pg0->VolumeType != MPI2_RAID_VOL_TYPE_RAID0) ||
        (raid_vol_pg0->NumPhysDisks > 8)) {     /* mps_config.c:399-400 */
        ... "Direct Drive I/O will not be used" ... goto out;
    }
    ...
    sc->DD_num_phys_disks = raid_vol_pg0->NumPhysDisks;   /* mps_config.c:412 */
    ...
    sc->WD_valid_config = TRUE;                            /* mps_config.c:511 */

There is no NumPhysDisks == 0 rejection: a RAID0 page with zero physical disks passes the > 8 check, DD_num_phys_disks becomes 0, and WD_valid_config is set. Every subsequent READ/WRITE to the volume's DevHandle enters mpssas_direct_drive_io (mps_sas.c:1760-1762), which divides by it:

    stripe_unit = physLBA / sc->DD_num_phys_disks;   /* mps_sas.c:2190 (6-byte CDBs) */
    stripe_unit = physLBA / sc->DD_num_phys_disks;   /* mps_sas.c:2269 (10/16-byte, low LBA) */
    stripe_unit = physLBA / sc->DD_num_phys_disks;   /* mps_sas.c:2347 (16-byte, high LBA) */

An integer division by zero in kernel text on x86_64 raises #DE β†’ panic: integer divide by zero (or fatal trap) on the first qualifying I/O β€” an unprivileged process merely has to read() or write() a file on the wed volume-shaped device.

Contrast: the same function does reject stripe-size 0 and block-size 0 explicitally (mps_config.c:428-433, 447-452) β€” the disk-count zero case was simply missed. (Note stripe_size==0 β†’ the virtLBA & (DD_stripe_size - 1) masks at mps_sas.c:2185/2263 also degrade, but the div-by-zero fires first in the zero-disk case.)

Threat model

NumPhysDisks comes from the controller's own config-page DMA (firmware / NVRAM-backed volume table), not from disk-supplied data β€” so this is a malicious-or-corrupt-firmware / mis-flashed-WarpDrive scenario, or a legitimate LSI firmware bug class (0-disk RAID0 pages have been observed during volume teardown races). Hardware- and firmware-gated; hence Low / speculative, but the sink is a guaranteed kernel panic.

Build/run

run.sh records the hardware gate.

Fix

Reject NumPhysDisks == 0 in the WarpDrive validation (mps_config.c:399-400). See fix.diff.

VERDICT.md
↓ download raw

DF-3897 β€” VERDICT

Status: not_reproduced (hardware/firmware-gated) β€” sink certain from source, trigger speculative. Impact class: divide-by-zero kernel panic (DoS), Low.

Proof chain

  1. sys/dev/raid/mps/mps_config.c:399-405 β€” the only NumPhysDisks check is > 8; == 0 passes. (VolumeType must be RAID0, which does not imply disk-count β‰₯ 1 to the parser.)
  2. sys/dev/raid/mps/mps_config.c:412 β€” sc->DD_num_phys_disks = raid_vol_pg0->NumPhysDisks; (uint8_t, mpsvar.h:435); validation continues; sc->WD_valid_config = TRUE at mps_config.c:511 (the function bails to out: before this on every checked anomaly β€” stripe 0 at 428-433, block 0 at 447-452, multiple volumes at 481-486, bad PD pages at 495-507 β€” but not for count 0).
  3. sys/dev/raid/mps/mps_sas.c:1760-1762 β€” every XPT_SCSI_IO on the adapter then calls mpssas_direct_drive_io(sassc, cm, ccb).
  4. sys/dev/raid/mps/mps_sas.c:2157-2191, 2222-2277, 2306-2391 β€” for READ/WRITE 6/10/16 to sc->DD_dev_handle, once the LBA/stripe checks pass: stripe_unit = physLBA / sc->DD_num_phys_disks; at 2190, 2269,
  5. With DD_num_phys_disks == 0 β†’ #DE β†’ kernel panic on first qualifying I/O by any unprivileged user of the device.
  6. Column note: column = physLBA % DD_num_phys_disks (2191, 2271, 2349) indexes DD_column_map[column] β€” with the count check fixed to β‰₯ 1 the modulo bounds column < count ≀ 8 = MPS_MAX_DISKS_IN_VOL, so that side is safe once this finding is fixed.

Why not reproduced

Needs a real LSI WarpDrive/SAS2 HBA reporting a 0-disk RAID0 volume page; the QEMU guest has none (env.txt/run.log). Speculative trigger, certain sink.

Fix validation

Not testable here; fix.diff adds the == 0 rejection beside the existing > 8 check β€” one line, same style as the stripe/block zero guards.

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 adds the one-line rejection; cannot be exercised on this guest (no SAS2 HBA).

findings/poc/DF-3897/fix.diff
↓ fix.diffper-fix-DF-3897

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (path:line proof: unchecked count, three division sinks, guarded siblings), env.txt, run.sh + run.log (hardware gate), fix.diff (== 0 rejection in mps_config.c)

PoC changes

n/a β€” hardware/firmware-gated.

Verified recommended fix

Reject NumPhysDisks == 0 in mps_wd_config_pages beside the existing > 8 check.

Verdict

The WarpDrive validation in mps_wd_config_pages only rejects NumPhysDisks > 8 (sys/dev/raid/mps/mps_config.c:399-400) while explicitly rejecting stripe-size 0 and block-size 0 (mps_config.c:428-433, 447-452) β€” a RAID0 volume page with zero physical disks passes, sets DD_num_phys_disks = 0 (mps_config.c:412) and WD_valid_config = TRUE (mps_config.c:511). Every subsequent READ/WRITE to the volume DevHandle then reaches stripe_unit = physLBA / sc->DD_num_phys_disks in mpssas_direct_drive_io (mps_sas.c:2190, 2269, 2347) β€” integer divide by zero, kernel panic on the first qualifying I/O by any unprivileged user of the device. Trigger is firmware/NVRAM-gated (controller config page, observed-class 0-disk RAID0 during volume teardown), hence speculative; the sink is certain from source. Not reproduced: guest has no SAS2/WarpDrive HBA (env.txt).