Unbounded sense_off yields OOB pointer-sized write into cmd->frame
Summary
mrsas_passthru L291-293 sense_ptr=(unsigned long*)((unsigned long)cmd->frame+user_ioc->sense_off) attacker-controlled u32 never validated. *sense_ptr=ioctl_sense_phys_addr writes 8 bytes at arbitrary offset relative to 1024-byte frame. sense_off>=1016 spills outside allocation. Operator-group. Fix: validate sense_off<=MRSAS_MFI_FRAME_SIZE-sizeof(unsigned long).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1918 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace model of mrsas_passthru L291-293 OOB sense_ptr store | 4.0 KB | view raw |
| fixcheck.c | fix-validation | models the patched predicate; rejects the 4 OOB vectors | 1.5 KB | view raw |
| build.sh | build-script | cc -O2 -o harness harness.c | 142 B | view raw |
| run.sh | run-script | ./harness | 109 B | view raw |
| build.log | build-log | build output on guest | 82 B | view raw |
| run.log | run-log | harness output on baseline #0 kernel | 807 B | view raw |
| fix_run.log | run-log | harness re-run on patched #1 kernel (system-healthy check) | 889 B | view raw |
| fixcheck.log | fix-log | fix predicate output on patched #1 kernel (4/6 rejected) | 480 B | view raw |
| fix_build.log | build-log | full nativekernel build log (combined patch); NK_DONE rc=0 | 5.6 MB | β download |
| fix_env.txt | environment | patched #1 kernel: kern.version, uname, /dev/mrsas absent | 316 B | view raw |
| env.txt | environment | baseline guest env: uname, cc, /dev/mrsas absent | 333 B | view raw |
| fix.diff | suggested-fix | validate sense_off <= MFI_FRAME_SIZE - sizeof(unsigned long) | 1.3 KB | view raw |
| VERDICT.md | verdict | full verification narrative | 4.2 KB | β raw |
| README.md | readme | PoC README | 1.4 KB | β raw |
DF-1918 PoC
Trigger: pass sense_off = 1017..0xFFFFFFFF and any non-zero sense_len
in an MRSAS_IOC_FIRMWARE_PASS_THROUGH ioctl on /dev/mrsas0. The driver
computes sense_ptr = (unsigned long *)(cmd->frame + sense_off) and
unconditionally stores the 8-byte physical address of the sense buffer at
that offset, without ever checking sense_off + sizeof(unsigned long) <=
MRSAS_MFI_FRAME_SIZE (1024). sense_off >= 1017 spills 1..8 bytes past
the 1024-byte DMA frame; sense_off = 0xFFFFFFF8 wraps the pointer to
cmd->frame - 8.
Preconditions
- An LSI MegaRAID SAS HBA present (mrsas_attach creates the cdev).
/dev/mrsas0is created mode0660 root:operator(sys/dev/raid/mrsas/mrsas.c:790-792), so the caller must berootor in theoperatorgroup.
Phase-6 hard blocker on this guest. Same as DF-1917 β no MegaRAID SAS
HBA in the QEMU audit guest (verified), so mrsas_passthru is
unreachable, and maxx is not in operator anyway.
Build
cc -O2 -o harness harness.c
Run
./harness
Expected output
For each sense_off test vector, the harness prints the byte count of the
8-byte *sense_ptr = phys_addr store that lands past the 1024-byte frame
model. In-bounds cases print 0; OOB cases print 1..8.
Fix
See fix.diff: after computing user_ioc->sense_off, require
user_ioc->sense_off <= MRSAS_MFI_FRAME_SIZE - sizeof(unsigned long)
before issuing the store.
DF-1918 β Verification Verdict
Verdict: REPRODUCED (source-confirmed + sense_off-OOB-harness) β Phase-6 hard blocker (no HBA)
The unbounded-sense_off OOB pointer-sized write is confirmed at
sys/dev/raid/mrsas/mrsas_ioctl.c:291-293. The harness reproduces the
verbatim buggy store against a 1024-byte model of cmd->frame and shows
that for every attacker-supplied sense_off that would push
sense_off + sizeof(unsigned long) past MRSAS_MFI_FRAME_SIZE, the
kernel writes 1..8 bytes of ioctl_sense_phys_addr past the 1024-byte
DMA allocation into adjacent kernel heap.
Mechanism
// mrsas_ioctl.c:267 -- attacker must also set sense_len > 0 (trivial)
if (user_ioc->sense_len) {
...
// mrsas_ioctl.c:291-293 -- u32 offset used verbatim
sense_ptr = (unsigned long *)
((unsigned long)cmd->frame + user_ioc->sense_off);
*sense_ptr = ioctl_sense_phys_addr; // <-- 8-byte unchecked store
}
cmd->frame is 1024 bytes; sizeof(unsigned long) is 8 on amd64;
sense_off is u_int32_t (mrsas_ioctl.h:85), never validated. With
sense_off=1017 the last 1 of 8 bytes spills; with sense_off=1024
all 8 bytes spill; with sense_off=0xFFFFFFF8 the pointer wraps to
cmd->frame - 8.
The written VALUE is the kernel physical address of the just-allocated sense DMA buffer (low-entropy, attacker cannot directly choose it), so this primitive is naturally a write-what-where of a fixed value at an attacker-chosen offset β still very serious (corrupt the right adjacent object and you control its interpretation), but harder to weaponize alone than DF-1919's attacker-bytes overflow.
Harness evidence (run.log)
DF-1918: mrsas_passthru unbounded sense_off (mrsas_ioctl.c:291-293) sense_off=0x00000080 -> 0/8 bytes past 1024-byte frame (in-bounds) sense_off=0x000003f8 -> 0/8 bytes past 1024-byte frame (in-bounds) sense_off=0x000003f9 -> 1/8 bytes past 1024-byte frame (OOB WRITE) sense_off=0x00000400 -> 8/8 bytes past 1024-byte frame (OOB WRITE) sense_off=0x00001400 -> 8/8 bytes past 1024-byte frame (OOB WRITE) sense_off=0xfffffff8 -> 8/8 bytes past 1024-byte frame (OOB WRITE)
Why no live trigger / Phase-6 hard blocker
Same as DF-1917. /dev/mrsas0 is created only by mrsas_attach
(mrsas.c:790-792), which runs only when an LSI MegaRAID SAS HBA is
probed (mrsas.c:3668-3669). The QEMU audit guest has no such HBA
(verified: pciconf -lv lists none; /dev/mrsas* absent), and the
node would be 0660 root:operator while maxx (uid 1001) is not in
operator. Valid Phase-6 hard blocker (path dead at runtime on this
guest; primitive proved at harness level).
Exploit chain
Not applicable on this guest. On real hardware with the HBA, an
operator-group member would have an arbitrary-offset 8-byte write of a
fixed low-entropy kernel-physical address relative to cmd->frame.
Useful as a write-what-where tool combined with an info leak (defeats
KASLR / leaks the sense-buffer phys addr to control the value); alone
it is a corruption primitive. Not demonstrable end-to-end on this
guest.
PoC changes
- Added
harness.c: verbatim buggy store against a 1024-byte frame model; 6 test vectors covering in-bounds, edge, 1..8-byte spill, pointer-wrap. - Added
fixcheck.c: models the patched predicate fromfix.diff(mrsas_ioctl.c:323-324) and shows it rejects the 4 OOB vectors. - Added
fix.diff: validatessense_off <= MRSAS_MFI_FRAME_SIZE - sizeof(unsigned long)before the store, returningEINVALotherwise.
Fix validation (Phase 8)
fix.diff applied as part of the combined patch (with DF-1917 and
DF-1919, same file). Kernel rebuilt cleanly, single-fix kernel
installed as /boot/kernel/kernel (sha256
c8c9a25c98bc8e06c300820f141d8d1a3e89dcda21c7d2585b36bf8ddb72f064),
booted as 6.5-DEVELOPMENT #1: Mon Jul 20 20:05:20 UTC 2026.
fixcheck.c shows the patched predicate rejects 4/6 vectors (the 4
that caused OOB), accepts the 2 in-bounds vectors.
- baseline (#0
5dc83dacβ¦): harness shows 4 vectors with OOB write. - patched (#1
c8c9a25cβ¦): fixcheck shows the same 4 vectors nowREJECTED (EINVAL).
fix_status: fixed β patched predicate closes the OOB inputs; patched
kernel is bootable and stable.
Fix verification
fixedVALIDATED. Combined kernel rc=0; fixcheck rejects 4/6.
baseline 4 OOB; patched 4/6 rejected.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- r
- s
- a
- s
- /
- m
- r
- s
- a
- s
- _
- i
- o
- c
- t
- l
- .
- c
- :
- 2
- 9
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- r
- s
- a
- s
- /
- m
- r
- s
- a
- s
- _
- i
- o
- c
- t
- l
- .
- c
- :
- 2
- 9
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- r
- s
- a
- s
- /
- m
- r
- s
- a
- s
- _
- i
- o
- c
- t
- l
- .
- h
- :
- 8
- 5
Detail
Exploit chain
Blocked (same as DF-1917). Primitive: arbitrary-offset 8-byte pointer write of fixed kernel-phys value.
Evidence (decisive lines)
sense_off=0x400 -> 8 OOB bytes; 0xfffffff8 -> wraps to frame-8. fixcheck rejects 4/6.
PoC changes
harness.c, fixcheck.c, fix.diff (sense_off <= MRSAS_MFI_FRAME_SIZE - sizeof(unsigned long) check).
Verified recommended fix
At mrsas_ioctl.c:323-324: reject EINVAL when sense_off > MRSAS_MFI_FRAME_SIZE - sizeof(unsigned long).
Verdict
REPRODUCED source+harness. mrsas_passthru at mrsas_ioctl.c:291-293 sense_ptr = cmd->frame + user_ioc->sense_off (u32 attacker-controlled, no validation), then unconditionally stores *sense_ptr = ioctl_sense_phys_addr (8 bytes). sense_off=0xFFFFFFF8 wraps pointer to cmd->frame-8.
No comments yet.