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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1172 Β· 12 files
FileTypeDescriptionSize
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
README.md readme status + how to reproduce
↓ download 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.

VERDICT.md verdict full narrative: 3-site mechanism, primitive characterization, reachability, live-trigger attempt, uid0 assessment, fix validation
↓ download raw

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]; // :2807 raidp = static struct ar_softc *ata_raid_arrays[MAX_ARRAYS] with MAX_ARRAYS = 16 (ata-raid.c:138, ata-raid.h:37). meta->raid_number is u_int8_t (0..255, ata-raid.h:510). For raid_number >= 16, array + raid_number >= 16 stores a kmalloc'd heap pointer into BSS past ata_raid_arrays[].

  • BUG 2 β€” configs[] OOB read (ata-raid.c:2818-2822): raid_entry = meta->raid_number (0..255); meta->configs[raid_entry] where configs is an array of 30 (ata-raid.h:508). raid_entry >= 30 reads 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 = ...; // :2869 disks[MAX_DISKS] with MAX_DISKS = 16 (ata-raid.h:39,108); meta->disk_number is u_int8_t (0..255, ata-raid.h:509). `disk_number

    = 16overwrites 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.c is built when nataraid is set. The LSI v2 parser is tried for every ATA subdisk at ata-raid.c:1480.
  • Vendor gate passes on this guest: the guest exposes an Intel PIIX4 IDE controller (atapci0@pci0:0:1:1, chip 0x70108086).
  • Trigger context: ata_raid_subdisk_attach β†’ ata_raid_read_metadata β†’ ata_raid_lsiv2_read_meta fires 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.

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.diff applies cleanly (git apply --check OK; 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.o compiled 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_testable for 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)

Fix verification

not_testable

compile+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.