ASR_init_message writes the mandatory sense SGL element 8 bytes past asr_action's 512-byte stack buffer when one CCB spans >=58 physical runs; the adapter-derived ha_SgSize limit is computed and never used
Summary
The SGL loop bound &SGL.u.Simple[SG_SIZE=58] lets data elements consume bytes [48,512) of the stack buffer; the unconditional sense element SG(sg,0,...) afterwards then writes [512,520) - 8 bytes (flags dword + low-32-bit physical address) past the buffer. Geometry verified on the guest by compiling/executing a layout proof (offsetof(SGL)=48, element=8, sense at [512,520)). Trigger needs >=58 physically-discontiguous runs (>=~232KiB) in one csio: unreachable via buffer-cache I/O (MAXPHYS=128KiB -> <=33 runs), reachable via CAM pass-through (root-only). Additionally sc->ha_SgSize is assigned once and never read, so the posted SGL also ignores the adapter's inbound frame size. Root with asr hardware: kernel stack OOB write of 8 controlled-ish bytes per large scattered pass-through transfer. Bound the loop by SG_SIZE-1 + honor ha_SgSize.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3824 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| sg_layout_proof.c | β | 3.3 KB | view raw | |
| build.sh | β | 104 B | view raw | |
| run.sh | β | 98 B | view raw | |
| build.log | β | 228 B | view raw | |
| run.log | β | 621 B | view raw | |
| env.txt | β | 323 B | view raw | |
| fix.diff | β | 942 B | view raw | |
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-3824 β ASR_init_message() writes the mandatory sense SGL element 8 bytes past asr_action()'s 512-byte stack buffer when a single CCB spans β₯58 physical runs (and the adapter's SG limit is ignored entirely)
- File:
sys/dev/raid/asr/asr.c - Lines: 1812-1847 (
ASR_init_messageSGL loop + sense element), 2680-2682 (the 512-byte stack bufferstruct Message { char M[MAX_INBOUND_SIZE]; }), 2459-2465 (ha_SgSizecomputed but never used) - Class: CWE-787 stack OOB write (kernel), 8 bytes
- Reach:
XPT_SCSI_IOwithcsio.dxfer_lenspanning β₯ 58 physically-discontiguous runs (β₯ ~232 KiB at 4 KiB pages).
Layout (verified by executing sg_layout_proof.c on the audit guest)
offsetof(PRIVATE_SCSI_SCB_EXECUTE_MESSAGE, SGL) = 48
sizeof(I2O_SGE_SIMPLE_ELEMENT) = 8
SG_SIZE (asr.c:258) = 58
asr_action stack buffer = 512 (MAX_INBOUND_SIZE)
loop fills SGL elements [0..57] -> bytes [48, 512)
sense element (always written after the loop, asr.c:1844-1845)
at &SGL.u.Simple[58] -> bytes [512, 520) <-- 8 bytes past
The loop bound sg < &SGL.u.Simple[SG_SIZE] (asr.c:1812-1813) lets the
data elements consume the entire 512-byte buffer; the sense element
SG(sg, 0, β¦, &ccb->csio.sense_data, span) (asr.c:1841-1845) is then
written unconditionally at element 58 β offsets 512..519 β past
struct Message M[512] on asr_action()'s stack.
The overwritten 8 bytes are FlagsCount
(I2O_SGL_FLAGS_SIMPLE_ADDRESS_ELEMENT|LAST_ELEMENT|EOB|flags, plus
Count = span) and PhysicalAddress = vtophys(&ccb->csio.sense_data)
β a low-32-bit physical address β written over whatever local/saved
state the compiler placed after Message.
Reachability
- Buffer-cache disk I/O is capped at
MAXPHYS = 128 KiB(sys/cpu/x86_64/include/param.h:122) β at most ~33 physical runs β loop uses β€ 33 elements β sense lands in-bounds. Not reachable via normal disk I/O. - CAM pass-through (
camcontrolon/dev/passN, root-only device) can submit a CCB with arbitrarydxfer_lenand a user-malloc'd, hence physically-scattered, buffer: a 256 KiB+ transfer trivially produces β₯58 runs β the overflow fires. - Independently, the SGL bound also ignores
sc->ha_SgSize(computed from the adapter'sInboundMFrameSizeat asr.c:2459-2465, referenced nowhere else in the driver) andI2O_MESSAGE_FRAME_getMessageSize()grows past what the adapter's inbound frame can hold β device-side frame overflow on every adapter whose frame is smaller than 512+ bytes.
Verdict
inconclusive for the kernel write itself β the audit guest has no
asr(4) HBA (see ../DF-3825/run.log), so asr_action's XPT_SCSI_IO path
never executes; the arithmetic was verified by compiling and running
sg_layout_proof.c on the guest (run.log in this pack). Source-level
confidence: certain.
VERDICT β DF-3824
Status: inconclusive (kernel write unexercisable without an asr HBA
on the guest; the layout arithmetic was verified by a program compiled
and executed on the guest β see run.log).
Derivation
asr_action() XPT_SCSI_IO declares the message on the stack
(asr.c:2680-2682):
struct Message {
char M[MAX_INBOUND_SIZE]; /* 512 */
} Message;
and builds it with ASR_init_message() (asr.c:2714). Inside
ASR_init_message:
1804: sg = &((PPRIVATE_SCSI_SCB_EXECUTE_MESSAGE)Message_Ptr)->SGL.u.Simple[0];
...
1812: while ((len > 0) && (sg < &((PPRIVATE_SCSI_SCB_EXECUTE_MESSAGE)
1813: Message_Ptr)->SGL.u.Simple[SG_SIZE])) {
... /* one physically-contiguous run per element, 8 bytes each */
1837: ++sg;
...
1841: if ((span = ccb->csio.sense_len) == 0) {
1842: span = sizeof(ccb->csio.sense_data);
1843: }
1844: SG(sg, 0, I2O_SGL_FLAGS_LAST_ELEMENT | I2O_SGL_FLAGS_END_OF_BUFFER,
1845: &(ccb->csio.sense_data), span);
SGL sits at offset 48 (verified: I2O_MESSAGE_FRAME=12 with
I2O_64BIT_CONTEXT 0 (i2omsg.h:128), private frame +20, bitfield dword
+4, CDBLength/Reserved/SCBFlags +4, CDB[16], ByteCount +4 β 48;
I2O_SGE_SIMPLE_ELEMENT = 8). The loop may fill elements 0..57 =
bytes [48,512) β exactly the buffer. The unconditional sense
element then writes bytes [512,520) β 8 bytes past M[512].
Trigger condition: the loop must reach element 57, i.e. the CCB data must span β₯ 58 physical runs (inner walk breaks per page/discontinuity, asr.c:1819-1829). 4 KiB pages β β₯ ~232 KiB in one CCB.
Feasibility split:
- buffer-cache I/O:
MAXPHYS= 128 KiB (sys/cpu/x86_64/include/param.h:122) β β€ ~33 runs β in-bounds. Not reachable. - CAM pass-through (
/dev/passN, mode 0600, root):dxfer_lenanddata_ptrare caller-chosen; a β₯256 KiB transfer over a user-malloc'd (physically scattered) buffer gives 58+ runs on the first try. Root + hardware gated β severity Low, class genuine.
Secondary defect in the same lines: sc->ha_SgSize β derived from the
adapter-reported InboundMFrameSize (asr.c:2459-2465) β is assigned
once and never read (grep -n ha_SgSize β only :356 and :2464), so
the SGL the driver posts can exceed the adapter's inbound frame;
asr_set_frame() (asr.c:1350) then copies getMessageSize() dwords of
message into a smaller device frame.
Guest evidence
sg_layout_proof.ccompiled with guest cc 8.3 and executed (build.log,run.log): offsetof(SGL)=48, element=8, loop end=512, sense at [512,520), overflow = 8 bytes. Byte-for-byte struct replicas from i2omsg.h/i2odpt.h.- No asr HBA:
/dev/asr0open β ENOENT (../DF-3825/run.log), zero vendor-0x1044 PCI devices (env.txt) βasr_action/ASR_init_messagenever execute β kernel write itselfinconclusive.
Fix
fix.diff: bound the loop by SG_SIZE - 1 so the mandatory sense
element can never leave the buffer (with a comment tying it to the
512-byte stack buffer and to the unused ha_SgSize).
Fix validation
not_testable β same hardware precondition as the baseline.
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_testableBaseline overflow cannot be triggered without an asr controller; the layout proof validates the geometry only. fix.diff reserves the sense slot so the maximum write is [48,512) in-bounds.
fix.diff
Confirmed kernel references
Detail
Exploit chain
root on host with asr card: camcontrol pass-through with dxfer_len >= ~256KiB over fragmented buffer -> 58+ physical runs -> sense SGL element overwrites 8 bytes past struct Message on asr_action's stack (adjacent locals/saved state, compiler-dependent) -> corruption/panic; exploitability beyond DoS depends on stack layout.
Evidence (decisive lines)
run.log (sg_layout_proof output on guest: offsets 48/8/512, sense at [512,520)); build.log; VERDICT.md (full derivation); fix.diff (reserve one SGL slot for the sense element)
PoC changes
Layout prover written fresh from the i2omsg.h/i2odpt.h struct definitions; no seed PoC existed.
Verified recommended fix
Bound the SGL loop by SG_SIZE-1 (reserve the last slot for the sense element) and honor sc->ha_SgSize (asr.c:1812).
Verdict
ASR_init_message()'s SGL loop is bounded by compile-time SG_SIZE=58 (asr.c:1812) which fills bytes [48,512) of asr_action()'s 512-byte stack buffer exactly; the mandatory sense element written unconditionally after the loop (asr.c:1844-1845) then lands at bytes [512,520) - an 8-byte kernel stack OOB write (flags dword + low-32-bit physical address) whenever a single XPT_SCSI_IO spans >=58 physically-discontiguous runs (~>=232KiB). Normal disk I/O is capped by MAXPHYS=128KiB (~33 runs) and cannot trigger it; CAM pass-through (root-only /dev/passN) with a large scattered buffer can. The adapter-derived ha_SgSize (asr.c:2459-2465) is computed and never used, so the posted SGL also ignores the adapter's inbound frame size. Arithmetic verified on the guest by compiling and running sg_layout_proof.c (struct replicas; output: sense element at [512,520), overflow 8 bytes). The kernel write itself is unexercisable: guest has no asr HBA (no /dev/asr0, zero vendor-0x1044 PCI devices) so asr_action never runs.
No comments yet.