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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3824 Β· 10 files
FileTypeDescriptionSize
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_message SGL loop + sense element), 2680-2682 (the 512-byte stack buffer struct Message { char M[MAX_INBOUND_SIZE]; }), 2459-2465 (ha_SgSize computed but never used)
  • Class: CWE-787 stack OOB write (kernel), 8 bytes
  • Reach: XPT_SCSI_IO with csio.dxfer_len spanning β‰₯ 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 (camcontrol on /dev/passN, root-only device) can submit a CCB with arbitrary dxfer_len and 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's InboundMFrameSize at asr.c:2459-2465, referenced nowhere else in the driver) and I2O_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.md
↓ download raw

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_len and data_ptr are 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.c compiled 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/asr0 open β†’ ENOENT (../DF-3825/run.log), zero vendor-0x1044 PCI devices (env.txt) β‡’ asr_action/ASR_init_message never execute β‡’ kernel write itself inconclusive.

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_testable
baseline no→ patch + rebuild →patched clean

Baseline 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
↓ fix.diffper-fix-DF-3824

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.