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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4134 Β· 3 files
FileTypeDescriptionSize
README.md file 2.8 KB ↓ raw
manifest.json file 403 B view raw
verdict.json file 1.3 KB view raw
README.md file
↓ download 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 VOL heap 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->plexes becomes 9 and every later for (i = 0; i < vol->plexes; i++) walk (vinumconfig.c:210-211, 2040-2044) reads the out-of-bounds plex[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_testable
per-fix-DF-4134

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