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

MLYIO_COMMAND size handling: DataTransferLength==INT_MIN negates/wraps into kmalloc(0xFFFFFFFF80000000, M_INTWAIT) β€” kernel panic (sink PROVEN on guest); plus unbounded wired allocation and ignored dmamap_load failure

Summary

Signed int DataTransferLength negated wraps for INT_MIN, sign-extends to size_t 0xFFFFFFFF80000000, passes the >0 guard and reaches kmalloc(M_INTWAIT) which panics in kmem_slab_alloc (M_INTWAIT lacks M_NULLOK). SINK PROVEN on the guest via a replica kld: panic 'kmem_slab_alloc(): kernel_map ran out of space!' captured in the serial log. Also no upper bound (2GB-1 wired per ioctl) and an ignored bus_dmamap_load error. Root-only control device; robustness DoS. Reject-and-cap fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3932 Β· 9 files
FileTypeDescriptionSize
README.md β€” 1.3 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
kmallocsink_replica.c β€” 2.2 KB view raw
build.log β€” 847 B view raw
run.log β€” 1.9 KB view raw
panic.txt β€” 599 B view raw
fix.diff β€” 900 B view raw
env.txt β€” 543 B view raw
verdict.json β€” 3.6 KB view raw

DF-3932 β€” mly: MLYIO_COMMAND size handling β€” INT_MIN negation panics kmalloc; unbounded sizes; ignored dmamap_load failure

Build / Run

Sink proof (done, on the audit guest)

The exact kmalloc the ioctl path performs was executed via a kernel-module replica (kmallocsink_replica.c, in this pack):

build: see build.log (guest cc 8.3, -Werror; note the compiler itself flagged the size 18446744071562067968 as exceeding max object size β€” same value mly.c:2889 computes) run: kldload kmallocsink.ko; sysctl kern.kmallocsink=1 expected + observed: panic "kmem_slab_alloc(): kernel_map ran out of space!" (panic.txt, run.log) β€” guest died, reset afterwards.

Full trigger (hardware-gated, on a machine with /dev/mly0)

#include ; #include #include "mlyio.h" / struct mly_user_command; MLYIO_COMMAND / struct mly_user_command uc; memset(&uc, 0, sizeof(uc)); uc.DataTransferLength = -2147483647 - 1; / INT_MIN / int fd = open("/dev/mly0", O_RDWR); / 0600 root:operator / ioctl(fd, MLYIO_COMMAND, &uc); / -> kernel panic /

Expected result

Kernel panic (proven at the sink level on this guest; the ioctl trigger itself needs the mly device node that only exists with the hardware).

See VERDICT.md.

VERDICT.md
↓ download raw

DF-3932 verdict narrative

Reproduced?

The sink is reproduced on the audit guest kernel: executing the exact kmalloc(0xffffffff80000000, M_DEVBUF, M_INTWAIT) that mly_user_command performs for DataTransferLength == INT_MIN panics the kernel with "kmem_slab_alloc(): kernel_map ran out of space!" (panic.txt; trace fire_sysctl β†’ _kmalloc β†’ kmem_slab_alloc). The trigger (MLYIO_COMMAND ioctl on /dev/mly0) is hardware-gated β€” no mly controller in the guest β€” so the finding as a whole is filed inconclusive (mechanism proven, instance hardware-gated), following the DF-3715 family convention.

Root cause chain (path:line)

  1. mlyio.h:42 β€” DataTransferLength is a plain signed int, fully user-controlled through the 0600 root:operator control device.
  2. mly.c:2889 c mc->mc_length = (uc->DataTransferLength >= 0) ? uc->DataTransferLength : -uc->DataTransferLength; For INT_MIN the negation wraps (int overflow) and the sign-extension into size_t mc_length yields 0xFFFFFFFF80000000 β€” which satisfies mc->mc_length > 0 at mly.c:2890.
  3. mly.c:2891 mc->mc_data = kmalloc(mc->mc_length, M_DEVBUF, M_INTWAIT); β†’ kern_slaballoc.c:942 size >= ZoneLimit β†’ kmem_slab_alloc β†’ vm_map_findspace fails β†’ kern_slaballoc.c:1724 panic (M_INTWAIT has no M_NULLOK). Proven on-guest (panic.txt).
  4. Secondary defects in the same size handling (source-certain, folded here): - No upper bound at all: any root user can request up to 2GB-1 of WIRED kernel memory per ioctl (M_INTWAIT β†’ kmem_slab_alloc wires it), repeatable β€” trivial kernel-memory exhaustion by the management daemon. - mly.c:1850 ignores bus_dmamap_load()'s error; on this DFly bus_dma the callback is always invoked (busdma_machdep.c:767-805) with the error passed in, and mly_map_command_sg (mly.c:1781) ignores its error argument too β€” a failed/partial load (EFBIG for >MAXBSIZE buffers, which the missing cap makes reachable) still dispatches the command with whatever stale s/g entries the slot's table holds.

Threat model

Root-only device node β€” the panic/DoS primitive is already-privileged (severity Low accordingly); the defect matters for robustness of the management stack (linux-mly-compatible tools pass user lengths straight through) and because the panic is reachable from a single ioctl with no hardware preconditions beyond the card being present.

Impact

Kernel panic (DoS), proven at the sink on this guest kernel; wired-memory exhaustion variant; stale-s/g dispatch variant.

Fix

Reject INT_MIN and cap |DataTransferLength| at MAXBSIZE (the buffer DMA tag's maxsize) β€” fix.diff.

Fix validation

Not run (fix touches mly path; hardware-gated). The kmallocsink replica already demonstrates that the size never reaching kmalloc removes the panic; the cap also makes bus_dmamap_load's EFBIG path unreachable.

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 not validated on the guest: the trigger path requires mly/age hardware absent from the audit guest. fix.diff is mechanical (bounds/termination-only) and authored post-verification against the cited lines.

findings/poc/DF-3932/fix.diff + VERDICT.md
↓ fix.diffper-fix-DF-3932

Confirmed kernel references

Detail

Exploit chain

root opens /dev/mly0, issues MLYIO_COMMAND with DataTransferLength=INT_MIN -> kmalloc(0xFFFFFFFF80000000, M_INTWAIT) -> kmem_slab_alloc panic (sink proven on guest via replica)

Evidence (decisive lines)

panic.txt + run.log (guest panic trace through _kmalloc); build.log (compiler's own -Walloc-size-larger-than at the exact size); kmallocsink_replica.c; fix.diff

PoC changes

Authored kmallocsink kernel-module replica (guest has no mly hardware); the /dev/mly0 trigger program is documented in README.md. Replica v1 was rejected by -Werror (the compiler flagging the overflowing negation is itself evidence); v2 uses a volatile to make the size opaque - behavior identical.

Verified recommended fix

Reject DataTransferLength == INT_MIN and cap |DataTransferLength| at MAXBSIZE (the buffer DMA tag maxsize).

Verdict

Mechanism PROVEN on the guest kernel at the sink level; the ioctl trigger itself is hardware-gated (no /dev/mly0 without the card) -> inconclusive per family convention, with the panic observed. mly_user_command (mly.c:2889) computes mc_length = -DataTransferLength for negative int lengths; for DataTransferLength == INT_MIN (mlyio.h:42: signed int) the negation wraps and sign-extends to size_t 0xFFFFFFFF80000000, which passes the mc_length > 0 guard (mly.c:2890) and reaches kmalloc(M_INTWAIT) (mly.c:2891). Guest replica executing that exact call (kmallocsink_replica.c): panic 'kmem_slab_alloc(): kernel_map ran out of space!' with trace fire_sysctl -> _kmalloc -> kmem_slab_alloc (panic.txt, run.log; kern_slaballoc.c:1724 - M_INTWAIT carries no M_NULLOK). The guest compiler independently flagged the size as exceeding maximum object size during build. Secondary, same locus (folded): no upper bound on DataTransferLength (up to 2GB-1 WIRED kernel memory per ioctl, root-gated), and bus_dmamap_load's failure is ignored at mly.c:1850 (on DFly the callback always runs with the error - busdma_machdep.c:767-805 - and mly_map_command_sg ignores it too), so oversized/unloadable buffers still dispatch with stale s/g entries. Root-only device (0600) -> Low severity.