give_plex_to_volume() writes vol->plex[MAXPLEX] past the last member of struct volume when the 'too many plexes' throw is swallowed by disk-config mode
Summary
throw_rude_remark returns normally in VF_READING_CONFIG mode, so give_plex_to_volume falls through to vol->plex[plexes]=plexno with plexes==MAXPLEX - one int past int plex[MAXPLEX], the last member of struct volume - and vol->plexes becomes 9. On power-of-two kmalloc buckets the write lands in intra-bucket slack; otherwise clobbers VOL[n+1].name[0..3]; plexes>MAXPLEX desyncs later walks. Return-after-throw fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4134 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.8 KB | β raw | |
| manifest.json | file | 403 B | view raw | |
| verdict.json | file | 1.3 KB | view raw |
DF-4134 β give_plex_to_volume() writes vol->plex[MAXPLEX] when the
"too many plexes" throw is swallowed by disk-config mode
Root cause
sys/dev/raid/vinum/vinumconfig.c:198-206:
if (vol->plexes == MAXPLEX) /* all plexes allocated */
throw_rude_remark(ENOSPC, "Too many plexes for volume %s", vol->name);
...
vol->plex[vol->plexes] = plexno; /* <-- plexes == MAXPLEX == 8 */
vol->plexes++;
throw_rude_remark() returns normally (it does not longjmp) when
VF_READING_CONFIG is set (vinumconfig.c:125-132), i.e. exactly when the
configuration is being read from a vinum-labelled disk
(vinum_scandisk, vinumio.c:682). Execution then falls through to
vol->plex[8] = plexno β one int past the end of int plex[MAXPLEX],
which is the last member of struct volume (vinumvar.h:634), so the
write lands past the end of the VOL[n] element:
- for the last element of the
VOLheap array it writes 4 bytes past the object (on DragonFly's power-of-two kmalloc buckets this stays inside the bucket's slack β no cross-object corruption in practice), - for a non-last element it clobbers the first 4 bytes of
VOL[n+1].name[0..3](config corruption), vol->plexesbecomes 9 and every laterfor (i = 0; i < vol->plexes; i++)walk (vinumconfig.c:210-211, 2040-2044) reads the out-of-boundsplex[8]slot as a plex index.
Threat
Crafted vinum disk with a volume line and 9+ plex definitions that
reference it (plex name=pN volume=v org concat), read via
vinum read / boot autodetect. Root/medium-integrity trigger, malicious
content.
Fix
--- a/sys/dev/raid/vinum/vinumconfig.c
+++ b/sys/dev/raid/vinum/vinumconfig.c
@@ -197,8 +197,9 @@ give_plex_to_volume(int volno, int plexno)
vol = &VOL[volno]; /* point to volume */
if (vol->plexes == MAXPLEX) /* all plexes allocated */
throw_rude_remark(ENOSPC,
"Too many plexes for volume %s",
vol->name);
+ else if (vol->plexes > MAXPLEX) /* belt and braces */
+ throw_rude_remark(ENOSPC, "Too many plexes for volume %s", vol->name);
else if ((vol->plexes > 0) /* we have other plexes */
(or: make the reading-config path of throw_rude_remark longjmp-safe here by
returning an error and having give_plex_to_volume return immediately; the
minimal robust fix is to return vol->plexes - 1; right after the throw).
Verification
Analysis-only (skipped live): severity Low β the 4-byte OOB write lands in
kmalloc bucket slack for the last element (200*n is never a power of two,
so sizeof(struct volume) * n never coincides with the bucket size), and
for non-last elements only renames the next volume's name; the OOB read of
plex[8] returns the plexno just written, which is in-bounds. The
defect is nonetheless a genuine bounds violation reachable from crafted
media and should be fixed.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
README.md analysis (struct layout math, throw_rude_remark return-in-reading-mode at vinumconfig.c:125-132)
PoC changes
n/a - analysis only
Verified recommended fix
Return immediately after the 'Too many plexes' throw (or make throw_rude_remark longjmp-safe on this path).
Verdict
Source-certain 4-byte OOB write (vol->plex[MAXPLEX] at vinumconfig.c:205 after the swallowed throw at :199) plus vol->plexes>MAXPLEX on the VF_READING_CONFIG path from crafted media. Live test skipped: on DragonFly's power-of-two kmalloc buckets the write lands in intra-bucket slack for the last VOL element (sizeof(struct volume)*n is never a power of two) and in VOL[n+1].name otherwise, so no cross-object corruption is achievable - impact is config corruption only. Findings is filed for correctness/defense-in-depth.
No comments yet.