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

launch_requests() keeps walking the request group after an inline completion freed it (trailing XFR_BAD_SUBDISK rqe) - kernel panic on the ORDINARY degraded RAID-5 write (new, waitlist-independent arm of the DF-4482 UAF cluster)

Summary

The designed degraded write layout puts the skipped BAD entry AFTER the last launched one; on synchronously-completing drives (md) the last launch's biodone runs inline through complete_rqe -> freerq while launch_requests is still looping - it then reads rqe->flags from freed memory and dereferences the freed group's kmalloc freelist pointer. REPRODUCED 2/2 with a single 32KB write; fix (snapshot nextrqg + bound loop to last launchable rqe) validated: write completes, readback matches, no panic. The third arm DF-4482's verifier predicted (their fixes still panicked). Filed under vinumraid5 batch; root lives in vinumrequest.c launch loop.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4721 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
trigger.c β€” 2.1 KB view raw
fix.diff β€” 1.7 KB view raw
panic.txt β€” 802 B view raw
run.patched.log β€” 1.2 KB view raw
verdict.json β€” 3.1 KB view raw
manifest.json β€” 767 B view raw

DF-4721 β€” vinum launch_requests() walks the freed request group when the last group element is a skipped XFR_BAD_SUBDISK rqe and the drive completes inline (md) β†’ kernel panic

What this proves

The DESIGNED degraded RAID-5 write (ONE down data subdisk β€” no reborn trickery, no revive, no waitlist) builds the request group as

rqe[0] = parity read                 (launched)
rqe[1] = good data block             (launched)
rqe[2] = down data block, XFR_BAD_SUBDISK   (skipped at launch)

The last LAUNCHED element is rqe[1]; rqe[2] is a TRAILING skipped element. With md (malloc) backing, biodone() runs inline inside dev_dstrategy(), so rqe[1]'s completion runs complete_rqe() -> complete_raid5_write() (XOR + phase-2 data write + parity write), whose own inline completions drive rq->active to 0 and freerq() the entire request while launch_requests() is still inside its launch loop (sys/dev/raid/vinum/vinumrequest.c:395-440). The loop then reads rqg->rqe[2].flags and rqg->next (:408-409) from the freed group; kmalloc's freelist pointer overwrites rqg->next and the loop dereferences garbage:

Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x0
Stopped at launch_requests+0xc2: movq (%rax),%rax

Reproduced 2/2 baseline runs (once via the plain designed-degraded write with rv.p0.s2 down, once via DF-4714's two-reborn trigger β€” identical signature). This is the "at least one further lifetime defect in the relaunch path" that DF-4482's verification predicted after its two waitlist fixes failed to stop that panic: this arm needs no revive and no waitlist at all β€” just a degraded RAID-5 write whose trailing rqe is a skipped BAD_SUBDISK, on any drive completing synchronously in strategy (md; also plausible with other stacking).

Trigger

Same array as DF-4714. sh ../DF-4714/setup_vinum.sh; vinum setstate -f down rv.p0.s2; ./trigger (single 32KB write at volume sectors [128,192)) β†’ panic within milliseconds.

Fix validation

fix.diff snapshots rqg->next before any firing and bounds the launch loop to the last non-BAD rqe (precomputed), so no group memory is read after an inline completion can free it. Applied + kernel rebuilt: write succeeds (correct degraded semantics), readback matches, no panic (run.patched.log step 3); DF-4714's panicking trigger now gets a clean EIO instead.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

patched kernel: the designed degraded write completes with correct readback and no panic; DF-4714's panicking trigger gets a clean EIO - run.patched.log steps 3,4

run.patched.log
↓ fix.diffper-fix-DF-4721

Confirmed kernel references

Detail

Exploit chain

root: RAID-5 vinum volume on md-backed drives with one data subdisk down; unprivileged: any single write() spanning both data columns of a stripe (e.g. filesystem I/O on the mounted volume) -> inline completion frees the request mid-launch -> launch_requests dereferences freed/garbage pointers -> kernel panic

Evidence (decisive lines)

panic.txt (Fatal trap 12, launch_requests+0xc2, designed-degraded run); ../DF-4714/panic.txt (same signature via DF-4714-B); run.patched.log step 3 (fixed: write OK, readback matches, no panic)

PoC changes

no seeded PoC; trigger.c written from scratch (single 32KB write, distinct per-block patterns)

Verified recommended fix

snapshot rqg->next before any I/O and bound the launch loop to the last non-BAD rqe so no group memory is read after an inline completion can free it (fix.diff, validated)

Verdict

launch_requests() (vinumrequest.c:395-440) continues iterating after an inline (md) completion has run complete_rqe()->complete_raid5_write()->freerq() and freed the whole request: trailing skipped XFR_BAD_SUBDISK entries make the loop read rqe->flags from freed memory and rqg->next from the freed group header (kmalloc freelist pointer) -> wild pointer dereference. Reached by the ORDINARY degraded RAID-5 write (single down data subdisk; trailing bad rqe is the standard layout); reproduced 2/2 with identical Fatal-trap-12 signature at launch_requests+0xc2. This is the previously-unidentified arm of the DF-4482 UAF cluster (their patched kernels still panicked; this arm needs no revive/waitlist). Patched kernel: degraded write completes correctly, no panic.