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)
PoC verification
Evidence pack
findings/poc/DF-1359 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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:
- Bounds-check
RequestSizeBEFORE the copyin β rejects requests outside[sizeof(tmphdr), IOCRequestFrameSize*4]. - Copy only
sizeof(tmphdr)bytes for the routing peek. - Replace both
bcopy(&tmphdr, ..., RequestSize)sites with a freshcopyin(PtrRequest, ..., RequestSize)into the properly-sized HW command buffer.
Phase 8 β fix validation (not_testable)
fix.diffapplies cleanly viapatch -p1 --forward(all 3 hunks succeed).make -j6 nativekernel KERNCONF=X86_64_GENERICbuilds the patched source withcc 8.3 -Werror, rc=0, no warnings onmps_user.c.- Patched kernel installs and boots cleanly (
kern.versionβ#3). nm /boot/kernel/kernelshowst mps_user_pass_thrulinked in.- Live PoC cannot be re-run (no
/dev/mpsNon this guest).fix_status: not_testableβ diff applies, compiles, boots, and source read confirms the previously-unchecked copyin is now range-checked and limited tosizeof(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_testablenot_testable live (HW absent); fix.diff applies cleanly + kernel builds rc=0.
fix.diff applies, kernel build rc=0, symbol present in patched kernel.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- s
- /
- m
- p
- s
- _
- u
- s
- e
- r
- .
- c
- :
- 7
- 4
- 7
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- s
- /
- m
- p
- s
- _
- u
- s
- e
- r
- .
- c
- :
- 8
- 0
- 2
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- s
- /
- m
- p
- s
- _
- u
- s
- e
- r
- .
- c
- :
- 8
- 0
- 6
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- s
- /
- m
- p
- s
- _
- u
- s
- e
- r
- .
- c
- :
- 8
- 3
- 0
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.
No comments yet.