ata_raid_delete frees the ar_softc without draining in-flight I/O β use-after-free in ata_raid_done/strategy
Summary
IOCATARAIDDELETE tears down the array (disk_destroy, devstat_remove_entry, wipe, kfree) with no quiesce; every in-flight ata_request holds request->driver=rdp and ata_raid_done dereferences it after hardware completion, and strategy touches rdp unlocked concurrently - a delete racing array I/O is a UAF on the freed M_AR chunk. Mechanism source-certain; no successful runtime race within the run's time box (needs a stallable member disk), filed untested/likely. Fix: reference-count ar_softc per in-flight request + deferred free (interim: refuse delete while open).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3695 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| verdict.json | β | 1.7 KB | view raw | |
| manifest.json | β | 2.0 KB | view raw |
DF-3695 β ata_raid_delete frees a live ar_softc without draining in-flight I/O (use-after-free)
Root cause (source)
ata_raid_delete (sys/dev/disk/nata/ata-raid.c:1280-1314) tears the array
down with no quiesce step:
1290: rdp->status &= ~AR_S_READY;
1291: disk_destroy(&rdp->disk);
1292: devstat_remove_entry(&rdp->devstat);
...
1310: ata_raid_wipe_metadata(rdp);
1311: ata_raid_arrays[array] = NULL;
1312: kfree(rdp, M_AR);
Nothing waits for in-flight bios. Every struct ata_request built by
ata_raid_init_request carries request->driver = rdp (:4030) and
ata_raid_done dereferences it after the hardware completes
(:643 request->driver, :662 rdp->type, :686 rdp->disks[...],
:847 devstat_end_transaction_buf(&rdp->devstat, ...)) β i.e. after the
kfree if a request was in flight when the delete ioctl ran. Similarly
ata_raid_strategy itself reads rdp->status/rdp->disks[] while another
thread executes the delete (no lock is held across the strategy path; the
softc lock only guards config_changed's status computation :916-996).
Result: use-after-free read/write on a freed M_AR chunk driven by I/O
completion timing β memory corruption (typically flags/last_lba writes and
devstat touching) or panic, triggered from the privileged
IOCATARAIDDELETE path while the array is under I/O load.
Why only "likely" / not verified here
Reproducing needs delete racing outstanding I/O on an arX device; the window is per-request milliseconds. On this guest the array I/O path is testable (see DF-3691/DF-3696 packs) but the race needs instrumentation (e.g. fault injection or stalling the member disk) that the legacy nata setup here doesn't provide in reasonable time. Filed from source analysis; classification: speculative-to-likely, untested.
Recommended fix (not in fix.diff β needs lifetime design, not a band-aid)
Reference-count the softc: take a reference per in-flight request
(ata_raid_init_request/ata_raid_done) and per strategy entry, and make
ata_raid_delete mark the array !READY, drain (tsleep on an in-flight
counter reaching zero), then kfree. Interim hardening: refuse delete while
any arX minor is open (diskdestroy with open check) and leak the softc
(delayed free via taskqueue) instead of kfree under I/O.
DF-3695 VERDICT
Status: untested (no runtime attempt succeeded within this run's scope). impact: none demonstrated; source-indicated memory corruption (UAF).
Analysis
ata_raid_delete (ata-raid.c:1280-1314) kfree's the ar_softc with no
drain of in-flight bios/requests. Every in-flight ata_request holds
request->driver = rdp (:4030) and ata_raid_done dereferences rdp
(:643/:662/:686/:800/:847) after hardware completion; ata_raid_strategy
also touches rdp unlocked while delete runs concurrently. A delete ioctl
issued while arX carries I/O therefore produces a use-after-free on the
M_AR chunk (read of type/status/disks, write of flags/last_lba, devstat
touch) β corruption or panic, timing-dependent.
Why not verified
Requires a controlled race between IOCATARAIDDELETE and outstanding array I/O. Setting that up on this guest needs a stallable member disk (the legacy-IDE nata path has no latency-injection knob exposed here; QEMU IDE has no throttling on this setup) and repeated hammering with instrumentation to observe the free-then-complete ordering. Time-boxed out; the mechanism is a straightforward code-read (no alternative interpretation exists: there is no refcount, no lock held across the request lifetime, and the completion callback is asynchronous by design).
Classification
speculative-to-likely UAF; privileged trigger (root/operator via /dev/ata*). Recommended fix requires a lifetime rework (per-request references + drain before kfree) β see README; deliberately NOT included in fix.diff to avoid shipping an unvalidated redesign.
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableno fix authored (needs lifetime redesign, unvalidated band-aid would be worse)
n/a
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md source trace (:1280-1314 teardown vs :4030 driver pointer vs :643/:847 completion derefs)
PoC changes
n/a
Verified recommended fix
reference-count ar_softc per in-flight request/strategy entry; delete marks !READY, drains, then frees
Verdict
ata_raid_delete kfree's the ar_softc with no drain of in-flight bios/requests; every in-flight ata_request keeps request->driver=rdp and ata_raid_done dereferences it after completion (type/status/disks/devstat), so an IOCATARAIDDELETE racing array I/O is a use-after-free on the M_AR chunk. Source-certain mechanism; no runtime attempt succeeded within this run's time box (needs a stallable member disk to widen the race window). Privileged trigger (root/operator).
No comments yet.