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

Stack buffer overflow in mps_user_pass_thru via unbounded copyin into 12-byte tmphdr

Summary

mps_user_pass_thru at mps_user.c:802: copyin(PtrRequest,&tmphdr,RequestSize). tmphdr is MPI2_REQUEST_HEADER 12 bytes. RequestSize uint32 user-controlled. Bound check at :806 AFTER copyin. Identical twin of DF-1326 (mpr_user.c). Also bcopy(&tmphdr,task/hdr,RequestSize) at :830/:878 reads past tmphdr off stack. Operator group (/dev/mpsN 0640). Fix: check RequestSize BEFORE copyin, copyin only sizeof(tmphdr).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1359 Β· 12 files
FileTypeDescriptionSize
poc_mps_stackoverflow.c trigger-source minimal MPTIOCTL_PASS_THRU overflow trigger (mps twin) 1.6 KB view raw
harness_overflow.c primitive-proof userspace structural proof of the 12-byte stack overflow 3.6 KB view raw
harness_run.log run-log harness output proving 52/52 attacker-controlled bytes 2.7 KB view raw
harness_run_patched.log run-log harness output on patched kernel (unchanged - pure userspace) 2.7 KB view raw
fix.diff suggested-fix bounds-check before copyin + sizeof(tmphdr) limit + direct copyin at bcopy sites 2.5 KB view raw
build.sh build-script cc -O2 -Wall for both PoC and harness 460 B view raw
run.sh run-script runs trigger (ENOENT) and harness 672 B view raw
fix_build.log build-log full nativekernel build log with DF-1359 fix applied (rc=0, -Werror) 5.6 MB ↓ download
env.txt environment uname, pciconf (no LSI SAS), dev nodes, kldstat, kernel sha256 2.5 KB view raw
VERDICT.md verdict full narrative: source confirmation (twin of DF-1326), primitive char, fix validation 5.8 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict full narrative: source confirmation (twin of DF-1326), primitive char, fix validation
↓ download raw

DF-1359 β€” VERDICT

Verdict

INCONCLUSIVE (live) β€” bug CONFIRMED in source; trigger requires LSI SAS HBA PCI hardware absent from this QEMU guest. Identical twin of DF-1326 in the MPS driver. The primitive is a kernel-stack buffer overflow with full attacker-controlled bytes. Fix authored and built cleanly into a single-fix kernel; the patched kernel is boot-stable and the cited path is closed in source. A userspace structural harness proves the overflow primitive independently of the live device.

Bug class

Kernel stack buffer overflow via copyin of an attacker-controlled size into a fixed-size stack variable, with the bounds check ordered AFTER the overflow. Identical class to DF-1326 (different driver β€” MPS instead of MPR).

Source confirmation (path:line)

The bug is real and byte-for-byte identical to DF-1326. mps_user_pass_thru declares tmphdr as a 12-byte MPI2_REQUEST_HEADER on its stack:

/* sys/dev/raid/mps/mps_user.c:747 */
MPI2_REQUEST_HEADER    *hdr, tmphdr;

The overflow (the cited :802):

/* sys/dev/raid/mps/mps_user.c:802-809 */
err = copyin(PTRIN(data->PtrRequest), &tmphdr, data->RequestSize);
/*                                       ^^^^^^^   ^^^^^^^^^^^^^^^^^
**                                       12 bytes    attacker uint32   */
if (err != 0)
    goto RetFreeUnlocked;

if (data->RequestSize > (int)sc->facts->IOCRequestFrameSize * 4) {
    err = EINVAL;                                /* bounds check runs */
    goto RetFreeUnlocked;                        /* AFTER the overflow */
}

MPI2_REQUEST_HEADER is the same 12-byte struct as in MPR (sys/dev/raid/mps/mpi/mpi2.h:693 β€” identical typedef). data->RequestSize is a user-controlled uint32_t straight from the ioctl arguments. Any RequestSize > 12 overflows the stack frame.

Same companion read-overflow at lines 830 and 878:

/* sys/dev/raid/mps/mps_user.c:830 */
bcopy(&tmphdr, task, data->RequestSize);   /* reads past 12-byte tmphdr */
/* sys/dev/raid/mps/mps_user.c:878 */
bcopy(&tmphdr, hdr, data->RequestSize);    /* same */

Device-node creation (mode 0640 root:operator):

/* sys/dev/raid/mps/mps_user.c:188 */
sc->mps_cdev = make_dev(&mps_ops, unit, UID_ROOT, GID_OPERATOR, 0640,
                        "mps%d", unit);

Primitive characterization (structural harness)

harness_overflow.c (same file as DF-1326 β€” the primitive is identical) proves the overflow mathematically:

sizeof(MPI2_REQUEST_HEADER) = 12
BUG: copyin 64 bytes into 12-byte tmphdr overflows by 52 bytes
...
Primitive confirmed: 52 of 52 out-of-band bytes are attacker-controlled.
In the kernel these bytes overwrite saved RBP/RIP/locals on the function's
stack frame.

Live reproduction: NOT POSSIBLE on this guest

$ ls /dev/mps*
ls: /dev/mps*: No such file or directory

$ pciconf -lv | grep -iE 'mps|LSI|SAS'
(no match)

$ id maxx
uid=1001(maxx) gid=1001(maxx) groups=1001(maxx)

$ grep operator /etc/group
operator:*:5:root                  # maxx is NOT in operator

Same situation as DF-1326: no LSI SAS HBA PCI hardware on QEMU, no attach, no cdev, no /dev/mpsN. The mps driver is compiled into the kernel (visible in kldstat -v as pci/mps), so on real hardware the bug would auto-attach and be live-reachable.

This is case (d) in the procedure. Live-reachable on real hardware with an LSI SAS2008-class HBA.

Phase 6 β€” escalation analysis (not exercisable live)

Identical to DF-1326 (same overflow class, same byte control). On this guest (no SMAP/SMEP/KASLR/PTI; INVARIANTS ON in GENERIC), the realistic chain on real hardware is: overwrite saved RIP in mps_user_pass_thru's frame with a KASLR-known kernel-text gadget (or userspace shellcode address since SMEP is OFF) β†’ pivot to commit_creds(prepare_kernel_cred(0)) β†’ root. Not demonstrable here only because the trigger device is absent.

Fix (fix.diff)

Three logical changes, all in sys/dev/raid/mps/mps_user.c:

  1. Bounds-check RequestSize BEFORE the copyin β€” rejects requests outside [sizeof(tmphdr), IOCRequestFrameSize*4].
  2. Copy only sizeof(tmphdr) bytes for the routing peek.
  3. Replace both bcopy(&tmphdr, ..., RequestSize) sites with a fresh copyin(PtrRequest, ..., RequestSize) into the properly-sized HW command buffer.

Phase 8 β€” fix validation (not_testable)

  • fix.diff applies cleanly via patch -p1 --forward (all 3 hunks succeed).
  • make -j6 nativekernel KERNCONF=X86_64_GENERIC builds the patched source with cc 8.3 -Werror, rc=0, no warnings on mps_user.c.
  • Patched kernel installs and boots cleanly (kern.version β†’ #3).
  • nm /boot/kernel/kernel shows t mps_user_pass_thru linked in.
  • Live PoC cannot be re-run (no /dev/mpsN on this guest). fix_status: not_testable β€” diff applies, compiles, boots, and source read confirms the previously-unchecked copyin is now range-checked and limited to sizeof(tmphdr).

PoC changes

No original PoC shipped for DF-1359; created poc_mps_stackoverflow.c (adapted from the DF-1326 PoC with the mps ioctl/device names) plus the same harness_overflow.c userspace primitive proof.

Files

File Purpose
poc_mps_stackoverflow.c live-trigger PoC for mps (compiles, ENOENT on guest)
harness_overflow.c userspace structural primitive proof (52/52 attacker bytes)
harness_run.log harness output on baseline kernel
harness_run_patched.log harness output on patched kernel (unchanged β€” pure userspace)
fix.diff bounds-check before copyin + sizeof(tmphdr) limit + direct copyin at bcopy sites
build.sh / run.sh exact build and run commands
fix_build.log full untrimmed nativekernel log for the patched build (rc=0)
env.txt guest uname / pciconf / dev nodes / kldstat / kernel sha256

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable live (HW absent); fix.diff applies cleanly + kernel builds rc=0.

fix.diff applies, kernel build rc=0, symbol present in patched kernel.
↓ fix.diffkernel #1/#2/#3 builds rc=0

Confirmed kernel references

Detail

Exploit chain

Not exercisable (no LSI SAS HBA). Twin of DF-1326.

Evidence (decisive lines)

ls /dev/mps*: ENOENT. Harness: 52/52 OOB bytes for RequestSize=64. fix.diff applies (3 hunks), kernel #3 builds rc=0.

Verified recommended fix

Same as DF-1326: bounds-check before copyin, copyin sizeof(tmphdr), fresh copyin at bcopy sites.

Verdict

INCONCLUSIVE live. Identical twin of DF-1326 in mps_user.c:802. Bug REAL in source. No LSI SAS HBA in QEMU -> /dev/mpsN absent.