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

MLX_NEXT_CHILD uses mlx_sysdrive[0] instead of the found [i] -> device_get_unit(NULL) panic after drive 0 is detached

Summary

MLX_NEXT_CHILD with *arg==-1 reads the unit from mlx_sysdrive[0].ms_disk inside the loop that verified mlx_sysdrive[i].ms_disk != NULL - a [0]-vs-[i] copy-paste bug. DETACH_DRIVE on system drive 0 leaves [0].ms_disk==NULL while later drives stay attached; the next NEXT_CHILD(-1) calls device_get_unit(NULL) -> NULL deref panic. Operator/root only; management tooling enumerating drives after a detach. One-token fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3949 Β· 9 files
FileTypeDescriptionSize
README.md doc bug + reachability + fix 1.8 KB ↓ raw
VERDICT.md verdict source proof 1.7 KB ↓ raw
mlx_ioctl_trigger.c trigger DETACH drive0 then NEXT_CHILD(-1) 4.6 KB view raw
build.sh script build 108 B view raw
run.sh script run 120 B view raw
build.log log guest compile + run 356 B view raw
run.log log guest run (ENOENT, no HBA) 217 B view raw
env.txt env guest uname 185 B view raw
fix.diff suggested-fix [0] -> [i] 446 B view raw
README.md doc bug + reachability + fix
↓ download raw

DF-3949 β€” MLX_NEXT_CHILD NULL dereference (mlx_sysdrive[0] vs [i])

Severity: Low Β· CWE-476 Β· operator/root gated (/dev/mlxN is 0600 root:operator, mlx.c:466-468)

Bug

sys/dev/raid/mlx/mlx.c:746-761 β€” MLX_NEXT_CHILD walks the attached system drives. When the caller asks for the first child (*arg == -1) the code takes the unit number from sc->mlx_sysdrive[0].ms_disk instead of the drive it just found (sc->mlx_sysdrive[i].ms_disk):

for (i = 0; i < MLX_MAXDRIVES; i++) {
    if (sc->mlx_sysdrive[i].ms_disk != NULL) {
        if (*arg == -1) {
            *arg = device_get_unit(sc->mlx_sysdrive[0].ms_disk);  /* [0]! */
            return(0);

If system drive 0 has no child while a later drive does, this calls device_get_unit(NULL) β†’ return(dev->unit) (sys/kern/subr_bus.c:1646-1649) β†’ NULL dereference β†’ kernel panic.

Reachability

mlx_startup() only creates children sequentially up to the sd_size == 0xffffffff sentinel, so on a plain boot [0] is attached whenever any later drive is. The state is produced by MLX_DETACH_DRIVE on system drive 0 (mlx.c:774-804): mlx_findunit() locates drive 0, the child is deleted and dr->ms_disk = NULL (mlx.c:796), while drive 1+ stays attached. The very next MLX_NEXT_CHILD(-1) β€” e.g. from mlxcontrol-style management tooling enumerating drives after a detach β€” panics the kernel. (RESCAN_DRIVES after config changes can also produce holes.)

Build / Run / Expected

cc -O2 -Wall -o mlx_ioctl_trigger mlx_ioctl_trigger.c
./mlx_ioctl_trigger nextchild /dev/mlx0     # detach drive0, then NEXT_CHILD(-1)

Expected on hardware: immediate kernel panic in device_get_unit (NULL+offset read). On this audit guest: ENOENT (no DAC960 HBA) β€” hardware-gated.

Fix

Use the drive actually found: device_get_unit(sc->mlx_sysdrive[i].ms_disk).

VERDICT.md verdict source proof
↓ download raw

DF-3949 β€” Verification Verdict

Status: inconclusive (hardware-gated; source-certain) Impact demonstrated: none (expected: kernel panic) Confidence: certain (source), runtime untestable (no Mylex DAC960 HBA in QEMU guest)

Source proof

  1. Copy-paste index bug: sys/dev/raid/mlx/mlx.c:753 *arg = device_get_unit(sc->mlx_sysdrive[0].ms_disk); inside the loop that just verified sc->mlx_sysdrive[i].ms_disk != NULL (mlx.c:750). Should be [i].
  2. State producer: MLX_DETACH_DRIVE (mlx.c:774-804) deletes the child of the requested drive and sets dr->ms_disk = NULL (mlx.c:796); drive 0 can be detached while later drives remain attached.
  3. device_get_unit is a direct deref: sys/kern/subr_bus.c:1646-1649 int device_get_unit(device_t dev) { return(dev->unit); } β†’ NULL+offset read β†’ kernel page fault.
  4. Privilege: /dev/mlxN created UID_ROOT, GID_OPERATOR, S_IRUSR|S_IWUSR (mlx.c:466-468) β€” operator/root only β‡’ Low severity.

Guest result

Trigger compile-tested on DragonFly 6.5-DEVELOPMENT guest (BUILD_RC=0); runtime exits ENOENT β€” no DAC960 controller present. Honest classification: inconclusive, not false-positive (the path is fully live source; every hop cited).

Fix validation

fix.diff: one-token change [0] β†’ [i]. Not runtime-testable on this guest (no HBA) β†’ fix_status: not_testable.

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

not_testable: no Mylex hardware in guest; one-token fix

fix.diff
↓ fix.diffper-fix-DF-3949

Confirmed kernel references

Detail

Exploit chain

operator/root: ioctl(MLX_DETACH_DRIVE, drive0_unit) then ioctl(MLX_NEXT_CHILD, -1) -> device_get_unit(NULL) -> panic

Evidence (decisive lines)

README/VERDICT cite mlx.c:750-758,774-796 + subr_bus.c:1646; mlx_ioctl_trigger.c (nextchild mode); build.log/run.log; fix.diff [0]->[i]

PoC changes

no seed; self-contained trigger with mlxio.h structs inlined so it builds on stock guest

Verified recommended fix

device_get_unit(sc->mlx_sysdrive[i].ms_disk) instead of [0].

Verdict

Source-certain NULL-deref: MLX_NEXT_CHILD handler indexes mlx_sysdrive[0] instead of the found [i] (mlx.c:753). After MLX_DETACH_DRIVE removes system drive 0's child (mlx.c:774-796 leaves ms_disk==NULL) while a later drive stays attached, MLX_NEXT_CHILD(-1) calls device_get_unit(NULL) -> return(dev->unit) (subr_bus.c:1646-1649) -> kernel panic. Root/operator gated (make_dev 0600 root:operator, mlx.c:466-468). Runtime inconclusive: no Mylex DAC960 HBA in the QEMU guest; trigger compile-tested on guest (BUILD_RC=0), run exits ENOENT.