bre5() second-bad-subdisk guard requires checksdstate() to fail: badsdno silently overwritten for reviving/reborn data subdisks -> parityless-write conversion defeated and two-bad-column writes accepted (silent data loss, parity scribbled onto down subdisk)
Summary
The refusal requires checksdstate to fail, but it returns OK for reborn data subdisks on writes and for reviving in a parity plex - badsdno is then overwritten. With the parity subdisk down plus one reborn data subdisk, the degraded path launches I/O against the DOWN parity subdisk, never writes the data block, and reports SUCCESS; with two reborn data subdisks, writes spanning both are accepted with neither written. GUEST-VERIFIED: 16KB write returned success while data was never written and parity was physically rewritten on the down subdisk; 32KB two-reborn write panics via DF-4721. Fixed kernel: EIO on both, correct degraded writes unaffected. Refuse-on-badsdno-alone fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4714 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| setup_vinum.sh | β | 2.1 KB | view raw | |
| run.sh | β | 111 B | view raw | |
| trigger_a.c | β | 2.2 KB | view raw | |
| trigger.c | β | 2.4 KB | view raw | |
| run.log | β | 2.4 KB | view raw | |
| run.patched.log | β | 1.2 KB | view raw | |
| panic.txt | β | 802 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| env.txt | β | 211 B | view raw |
DF-4714 β VERDICT
status: reproduced (4/4 decisive runs across 2 baseline boots + patched control boot) impact: silent data loss on RAID-5 writes (data corruption) + kernel panic via the DF-4721 arm fix: validated β bad behaviors eliminated on patched kernel
Root cause (code trace)
sys/dev/raid/vinum/vinumraid5.c, write path of bre5():
:363-364β if the parity subdisk's state <sd_reborn(down/crashed),m.badsdno = m.psdno.:403-404β for each participating data subdisk withstate != sd_up,s = checksdstate(...); the refusal for a second bad subdisk isif (s && (m.badsdno >= 0))β it requireschecksdstate()to have FAILED.sys/dev/raid/vinum/vinumstate.c:676-678βchecksdstate()returnsREQUEST_OK(0) forsd_reborndata subdisks on writes (and forsd_revivingin a parity plex withsddowncount <= 1,:651-661).:420β thereforem.badsdno = mysdnooverwrites the earlier (parity) marker, or the first bad data subdisk's marker.:443-453β thebadsdno == psdnoβXFR_PARITYLESS_WRITEconversion consequently never runs for the parity-down+reborn-data case.
Reachability of the states: a drive flap (downβup) moves every subdisk on
the drive sd_upβsd_crashedβsd_reborn (vinumstate.c:427-438,
set_drive_state calls update_sd_state per subdisk). Two subdisks of a
RAID-5 plex on one drive is a legal vinum configuration (used here; also
arises with reborn+reviving pairs because sd_reborn is not counted in
sddowncount, vinumstate.c:744-745).
What was run (guest DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC)
Setup: 2 md (malloc) disks, GPT vinum partitions, RAID-5 plex
stripesize 32s, sds: s0βd1, s1βd2@0, s2βd2@4361s. Volume sectors
[128,192) are stripe group 2 β parity ordinal 0 (s0), data ordinals 1 (s1)
and 2 (s2). All triggers use SINGLE write()/read() calls (a 512-byte dd
issues per-sector bios that only ever touch one data column and hide the
bug β see run.log history for that trap).
Baseline boot 1 (run.log)
- Scenario A β s0 forced down, s1 forced reborn, 16KB single write of
0xBB at [128,160):
- write returned SUCCESS;
- readback returned the OLD contents (0x75) β
*** SILENT DATA LOSS ***; - physical evidence: s0's (DOWN subdisk) drive region changed to 0xCE = 0xBB^0x75 β the user's new data was folded into parity and written to the down subdisk while the data block itself was discarded; s1's region never changed. - Scenario B first attempt (pre-reset boot,
panic.txt): 32KB single write with s1+s2 reborn β Fatal trap 12,launch_requests+0xc2: movq (%rax),%rax, fault VA 0x0 (the DF-4721 launch_requests UAF arm β reachable here because the overwrite leaves a trailing XFR_BAD_SUBDISK rqe).
Baseline boot 2
Re-ran scenario B cleanly (plex/volume lifted): identical panic, 2/2.
(panic.txt.)
Patched boot (run.patched.log)
Kernel rebuilt with fix.diff (this finding + DF-4715 correction):
- Scenario A: write β EIO (
write: Input/output error) β correct refusal instead of silent loss. - Scenario B: write β EIO, no panic.
- Degraded-but-correct write (single sd down, DF-4721 case): still SUCCEEDS, readback matches β the fix does not break legitimate degraded writes.
- Control roundtrip: OK.
Non-goals / limits
- No uid=0 chain: the bug corrupts storage data / panics; it does not grant privilege. The trigger needs a vinum array (root-created); after that, any user I/O to a mounted filesystem on the volume reaches the path.
- Scenario B's panic is attributed to the DF-4721 UAF (also reachable without this bug); scenario A is the pure, panic-free demonstration of THIS bug's data-loss impact.
Fix
fix.diff β guard on m.badsdno >= 0 alone (refuse regardless of
checksdstate() result), returning REQUEST_DOWN when s == REQUEST_OK;
co-located with the DF-4715 ordinal-loop correction. Validated above.
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.
Fix verification
fixedpatched kernel (fix.diff incl. DF-4715 correction): scenario A write EIO (was success+loss), scenario B write EIO no panic (was Fatal trap 12), designed degraded write still succeeds with correct readback, control roundtrip OK - run.patched.log
run.patched.log steps 1,3,4,5
Confirmed kernel references
Detail
Exploit chain
root: 3-sd RAID-5 vinum volume on md disks (s1+s2 share one drive); states placed via raw setstate ioctls exactly as a drive flap leaves them (down/reborn); unprivileged: single 16KB/32KB write() on the volume (e.g. via filesystem) -> silent data loss or kernel panic
Evidence (decisive lines)
run.log (scenario A loss + physical drive dumps), panic.txt (scenario B panic), run.patched.log (EIO on fixed kernel, degraded write still OK), VERDICT.md full matrix
PoC changes
no seeded PoC; built from scratch. Critical detail: triggers must use a SINGLE write() spanning both data blocks (dd bs=32768 or C write()); 512-byte dd hides the bug (per-sector bios touch one column each). vinum config syntax: 'plex org raid5 16k' (positional stripesize), explicit driveoffset must account for the 265-sector vinum label reserve; 'vinum setstate -f' uses the raw force ioctl (no state propagation) which is what makes deterministic state placement possible
Verified recommended fix
in bre5() refuse on m.badsdno >= 0 regardless of checksdstate() result, returning REQUEST_DOWN when s==REQUEST_OK (fix.diff, validated)
Verdict
bre5()'s second-bad-subdisk guard (vinumraid5.c:404) requires checksdstate() to fail, but sd_reborn/sd_reviving data subdisks return REQUEST_OK for writes, so m.badsdno is silently overwritten (:420). With parity down + a reborn data sd the XFR_PARITYLESS_WRITE conversion (:443) never runs: guest-verified silent data loss (16KB write SUCCESS, data block never written, parity recomputed and written onto the DOWN parity subdisk, readback stale). With two reborn data sds (one shared drive flap, vinumstate.c:427-438) writes spanning both columns are accepted with neither written, and the resulting trailing XFR_BAD_SUBDISK layout trips the DF-4721 launch_requests UAF -> kernel panic (Fatal trap 12, launch_requests+0xc2, 2/2 runs). Fixed kernel: both writes EIO, degraded-correct writes unaffected, no panic.
No comments yet.