LSI v2 parser: OOB write on static ata_raid_arrays[] via unvalidated meta->raid_number
Summary
ata_raid_lsiv2_read_meta at ata-raid.c:2802-2803: raidp[array+meta->raid_number] indexed with meta->raid_number (u8 from disk, 0-255) but raidp is static ata_raid_arrays[16]. raid_number>=16 -> OOB BSS write of heap pointer. Also :2819 meta->configs[raid_entry] OOB read (configs[30], raid_entry up to 255). Also :2866 raid->disks[meta->disk_number] OOB write (disks[16], disk_number up to 255). Vendor-agnostic (tried for ALL disks at :1477). Magic "$XIDE$" trivially crafted. Attacker: crafted disk on ANY SATA/IDE controller.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1172 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| overflow_harness.c | trigger-source | userspace replica of ata_raid_lsiv2_read_meta indexing using real ar_softc/ar_disk/lsiv2_raid_conf layouts; prints the 3 OOB sites + fix check | 9.6 KB | view raw |
| build_meta.py | trigger-source | builds a 2MB raw disk image with a valid LSI v2 metadata block (magic $XIDE$) whose raid_number/disk_number are attacker-set | 2.8 KB | view raw |
| crafted_lsiv2.img | trigger-source | built crafted disk image (raid_number=200, disk_number=200) for the live-trigger attempts | 2.0 MB | β download |
| fix.diff | suggested-fix | git-apply-able: reject raid_number>=MAX_ARRAYS || disk_number>=MAX_DISKS after the magic check | 1.3 KB | view raw |
| build.sh | build-script | builds the harness and/or the crafted disk image | 938 B | view raw |
| run.sh | run-script | runs the overflow-math harness | 282 B | view raw |
| harness_output.txt | run-log | captured harness stdout: the 3 OOB sites + WITH-FIX -> 0 | 1.1 KB | view raw |
| fix_validation.txt | build-log | apply+compile+boot evidence for the combined single-fix kernel (ata-raid.o, -Werror, #1, guard string present) | 2.1 KB | view raw |
| fix_build.log | build-log | nativekernel build log tail (NK_DONE rc=0) | 68.9 KB | view raw |
| env.txt | environment | guest uname/cc, Intel PIIX4 IDE controller, no ATA disks, nataraid in GENERIC | 1.5 KB | view raw |
| VERDICT.md | verdict | full narrative: 3-site mechanism, primitive characterization, reachability, live-trigger attempt, uid0 assessment, fix validation | 9.2 KB | β raw |
| README.md | readme | status + how to reproduce | 1.6 KB | β raw |
DF-1172 β PoC evidence pack
LSI v2 RAID parser OOB in ata_raid_lsiv2_read_meta
(sys/dev/disk/nata/ata-raid.c:2799-2869).
ata_raid_lsiv2_read_meta indexes raidp[array+meta->raid_number]
(ata_raid_arrays[16], BSS OOB write of a heap pointer for raid_number>=16),
meta->configs[raid_number] (configs[30], OOB read for raid_number>=30),
and raid->disks[meta->disk_number] (disks[16], heap OOB write for
disk_number>=16) β all three indices are u_int8_t (0..255) read straight
off the disk. nataraid sibling of DF-1171 (Intel parser).
Status
REPRODUCED (primitive confirmed at source + harness). nataraid is in
X86_64_GENERIC:83; the live in-kernel trigger is blocked by the QEMU/loader
artifact (loader hangs on any extra hard disk, as in DF-1171).
Reproduce (harness)
./build.sh harness # cc -Wall -O2 -o overflow_harness overflow_harness.c ./run.sh harness # prints the 3-site OOB math + fix check
Expected (harness, raid_number=200, disk_number=200):
- BUG 1: 16 BSS-OOB pointer stores, worst idx=215 β 1600 B into adjacent BSS
- BUG 2: configs[200] β OOB read (CWE-125)
- BUG 3: 240 OOB ar_disk entries Γ 48 B = 11520 B heap spill
- WITH FIX: all 0
build_meta.py / crafted_lsiv2.img is a reference crafted-disk artifact for
the (guest-blocked) live trigger.
Fix
fix.diff rejects metadata whose raid_number>=MAX_ARRAYS || disk_number>=MAX_DISKS.
Validated: applies + compiles clean in a combined single-fix kernel
(ata-raid.o, -Werror); boots #1; the rejection-guard string is present in
the booted kernel. See VERDICT.md / fix_validation.txt.
DF-1172 β LSI v2 parser: OOB write on static ata_raid_arrays[] + heap overflow via unvalidated meta->raid_number / meta->disk_number
Verdict
REPRODUCED (primitive confirmed at source + harness level). The bug is a
genuine three-site OOB in ata_raid_lsiv2_read_meta() β confirmed by
line-by-line source trace and a faithful userspace harness built from the real
kernel structs (struct ar_softc, struct ar_disk, struct lsiv2_raid_conf).
The driver is compiled into the default X86_64_GENERIC kernel
(device nataraid, X86_64_GENERIC:83) and the guest's Intel PIIX4 IDE
controller passes the read_metadata vendor gate, but the live in-kernel
trigger is blocked by the same QEMU/loader artifact documented in DF-1171 (the
DragonFly loader hangs on any extra hard disk before the kernel boots). The
demonstrated primitive ceiling is memory corruption / kernel panic (DoS) at
device-attach time; uid0 escalation is not realistic from this primitive
(see "Exploit chain").
Mechanism (trigger β primitive β effect)
ata_raid_lsiv2_read_meta() (sys/dev/disk/nata/ata-raid.c:2770) parses
on-disk LSI v2 RAID metadata (magic "$XIDE$", ata-raid.h:461) read from the
last sector of an ATA disk (LSIV2_LBA = total_secs-1, ata-raid.h:456).
After the magic check it uses the attacker-supplied per-disk indices with no
bounds checking. Three OOB sites:
-
BUG 1 β BSS OOB write of a heap pointer (
ata-raid.c:2802-2803,2807):for (array = 0; array < MAX_ARRAYS; array++) { if (!raidp[array + meta->raid_number]) { // :2802 raidp[array + meta->raid_number] = kmalloc(sizeof(ar_softc),...); // :2803 } raid = raidp[array + meta->raid_number]; // :2807raidp=static struct ar_softc *ata_raid_arrays[MAX_ARRAYS]withMAX_ARRAYS = 16(ata-raid.c:138,ata-raid.h:37).meta->raid_numberisu_int8_t(0..255,ata-raid.h:510). Forraid_number >= 16,array + raid_number >= 16stores akmalloc'd heap pointer into BSS pastata_raid_arrays[]. -
BUG 2 β
configs[]OOB read (ata-raid.c:2818-2822):raid_entry = meta->raid_number(0..255);meta->configs[raid_entry]whereconfigsis an array of 30 (ata-raid.h:508).raid_entry >= 30reads past the configs union array. -
BUG 3 β
raid->disks[]heap OOB write (ata-raid.c:2866-2869):raid->disks[meta->disk_number].dev = parent; // :2866 raid->disks[meta->disk_number].sectors = meta->configs[...].disk.disk_sectors; // :2867 raid->disks[meta->disk_number].flags = ...; // :2869disks[MAX_DISKS]withMAX_DISKS = 16(ata-raid.h:39,108);meta->disk_numberisu_int8_t(0..255,ata-raid.h:509). `disk_number= 16
overwrites the trailingar_softcfields (toggle,rebuild_lba,lock,disk,devstat,cdev,pid,ata-raid.h:109-115) and then past thekmalloc(sizeof(ar_softc))allocation into adjacent kernel heap. Thesectors` field is attacker-controlled (from disk metadata).
Primitive characterization (harness, real struct layouts)
overflow_harness.c reproduces the in-kernel indexing using faithful struct
copies. On LP64: sizeof(struct ar_disk)=48, disks[16] (first OOB) at offset
880. For crafted raid_number=200, disk_number=200:
| site | effect |
|---|---|
| BUG 1 | 16 BSS-OOB pointer stores into ata_raid_arrays[16..], worst idx=215 β 1600 B into adjacent BSS |
| BUG 2 | configs[200] read (configs[30]) β OOB read (CWE-125) |
| BUG 3 | 240 OOB ar_disk entries Γ 48 B = 11520 B heap spill (~436 B inside ar_softc, ~8444 B into adjacent heap) |
With the fix (reject raid_number>=MAX_ARRAYS || disk_number>=MAX_DISKS), all
three sites drop to 0 (loop never runs). (Full output in harness_output.txt.)
Reachability & threat model
- Driver present in default GENERIC:
device nataraid(X86_64_GENERIC:83);ata-raid.cis built whennataraidis set. The LSI v2 parser is tried for every ATA subdisk atata-raid.c:1480. - Vendor gate passes on this guest: the guest exposes an Intel PIIX4 IDE controller (
atapci0@pci0:0:1:1, chip0x70108086). - Trigger context:
ata_raid_subdisk_attachβata_raid_read_metadataβata_raid_lsiv2_read_metafires at device-probe/attach time (boot or hot-plug of an ATA/SATA disk), not from any userspace syscall. - Realistic threat model: a malicious SATA/IDE disk (USB-attached SATA, hot-plug, or crafted VM disk image) carrying LSI v2 metadata with out-of-range indices triggers the overflow automatically at attach, no user privilege required. This is a valid default-config attack surface.
Live-trigger attempt (why no in-kernel panic is shown)
build_meta.py builds a crafted raw disk image (crafted_lsiv2.img) carrying
a valid LSI v2 metadata block (magic "$XIDE$", RAID0 type) with
raid_number=200, disk_number=200 at the last sector. Attaching it to the
guest is blocked by the same QEMU/loader artifact as DF-1171: the DragonFly
loader produces no serial output for ~80β90 s on any extra hard disk (a
blank all-zero control disk hangs identically), so the kernel probe path never
runs. This is an environment blocker for the live demo, not a property of the
bug. On real Intel AHCI/SATA hardware the overflow would fire and, on default
GENERIC with INVARIANTS, panic in ata_raid_attach.
Exploit chain (uid0 assessment)
uid0 escalation is NOT realistic from this primitive. This is a valid hard
stop, for a structural reason (identical to DF-1171):
- The overflow fires synchronously inside ata_raid_subdisk_attach during
device probe (boot/hot-plug). No userspace process is concurrently running in
a position to groom the slab so a chosen victim object lands adjacent to
the freshly kmalloc(sizeof(ar_softc)) allocation.
- The attacker controls the on-disk metadata content (notably
disks[].sectors) and the index (which BSS/heap word is hit), but has no
influence over the slab layout at attach time, so the spill is blind.
- BUG 3 first lands inside ar_softc (toggle/rebuild_lba/lock/disk/
devstat/cdev/pid); on default GENERIC (INVARIANTS ON) the subsequent
ata_raid_attach use of the corrupted lock/disk/devstat reliably
panics before any privilege-meaningful operation. BUG 1's BSS pointer
writes are even less convertible (the pointer value is a heap address the
attacker does not shape).
The realistic ceiling is therefore reliable kernel panic / local DoS from a malicious disk, not uid0.
PoC changes
Authored from scratch. Deliverables: overflow_harness.c, build_meta.py,
crafted_lsiv2.img (reference artifact), fix.diff, build.sh, run.sh,
VERDICT.md, manifest.json, env.txt, harness_output.txt,
fix_validation.txt, fix_build.log.
Recommended fix
fix.diff inserts, right after the "$XIDE$" magic check and before the
conversion loop, a range rejection:
if (meta->raid_number >= MAX_ARRAYS || meta->disk_number >= MAX_DISKS) {
if (testing || bootverbose)
device_printf(parent,
"LSI (v2) metadata raid_number %u / disk_number %u out of range\n",
meta->raid_number, meta->disk_number);
goto lsiv2_out;
}
With raid_number < MAX_ARRAYS(16), BUG 1 (raidp[array+raid_number]) and BUG 2
(configs[raid_entry=raid_number], 16 < 30) are both in-bounds; with
disk_number < MAX_DISKS(16), BUG 3 (disks[disk_number]) is in-bounds. The
secondary conf_entry = (configs[raid_entry].config_offset>>4) + disk_number - 1
is then bounded β€ 15+15-1 = 29 < 30 for valid inputs. This matches the finding
proposal ("validate the indices").
Fix validation (Phase 8)
fix.diffapplies cleanly (git apply --checkOK; Hunk #1 @2795).- Combined single-fix kernel build (DF-1157 + DF-1172, warm obj):
make -j6 nativekernel KERNCONF=X86_64_GENERICβ NK_DONE rc=0;ata-raid.ocompiled clean under-Werror. - Patched kernel installed over
/boot/kernel/kernel; booted#1: Fri Jul 17 06:10:36 UTC 2026. Rejection-guard string"raid_number %u / disk_number %u out of range"is present in the booted kernel (strings | grep -cβ 1). fix_status: not_testablefor runtime: the live trigger requires an ATA disk the loader cannot add on this guest (see above). Validated at apply + compile + boot + harness level: the range rejection provably prevents all three OOB indexings.
Kernel references (confirmed)
sys/dev/disk/nata/ata-raid.c:2770βata_raid_lsiv2_read_metaentrysys/dev/disk/nata/ata-raid.c:2802-2807β BUG 1 (raidp[array+raid_number]BSS OOB)sys/dev/disk/nata/ata-raid.c:2818-2822β BUG 2 (configs[raid_entry]OOB read)sys/dev/disk/nata/ata-raid.c:2866-2869β BUG 3 (raid->disks[disk_number]heap OOB)sys/dev/disk/nata/ata-raid.c:138βstatic struct ar_softc *ata_raid_arrays[MAX_ARRAYS]sys/dev/disk/nata/ata-raid.h:37βMAX_ARRAYS 16sys/dev/disk/nata/ata-raid.h:39βMAX_DISKS 16sys/dev/disk/nata/ata-raid.h:108βdisks[MAX_DISKS]sys/dev/disk/nata/ata-raid.h:459-513βstruct lsiv2_raid_conf(configs[30], disk_number/raid_number u8)sys/dev/disk/nata/ata-raid.c:1480β tried for every ATA subdisksys/config/X86_64_GENERIC:83βdevice nataraid
Fix verification
not_testablecompile+harness validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). ata_raid_lsiv2_read_meta 3-site OOB: raid_number>=16 BSS write, configs[30] OOB read, disk_number>=16 11.5KB heap overflow. nataraid in GENERIC. Loader blocks live trigger.
No comments yet.