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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3695 Β· 4 files
FileTypeDescriptionSize
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.

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.

VERDICT.md
↓ download raw

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

no fix authored (needs lifetime redesign, unvalidated band-aid would be worse)

n/a
per-fix-DF-3695

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).