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

remove_plex_entry compacts vol->plex[] with a byte count instead of element count and decrements vol->plexes even when the plex was not found

Summary

The compaction bcopy length 'vol->plexes - 1 - myplexno' lacks *sizeof(int) (the correct twin at :1780 has it): from plex number 256 upward the volume's plex list silently points at wrong plexes - I/O routed to a foreign plex; and vol->plexes-- runs even after 'not found', so repeatable removals can drive plexes negative, making give_plex_to_volume's vol->plex[plexes] write BEFORE the array. sizeof(int)+conditional-decrement fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4137 Β· 3 files
FileTypeDescriptionSize
README.md file 2.6 KB ↓ raw
manifest.json file 369 B view raw
verdict.json file 1.2 KB view raw
README.md file
↓ download raw

DF-4137 β€” remove_plex_entry() compacts vol->plex[] with a byte

count instead of an element count, and decrements vol->plexes

unconditionally

Root cause

sys/dev/raid/vinum/vinumconfig.c:1841-1853:

        for (myplexno = 0; myplexno < vol->plexes; myplexno++)
        if (vol->plex[myplexno] == plexno)  /* found it */
            break;
        if (myplexno == vol->plexes)        /* didn't find it.  Huh? */
        log(LOG_ERR, ...);          /* ... but execution continues! */
        if (myplexno < (vol->plexes - 1))       /* not the last plex */
        bcopy(&vol->plex[myplexno + 1],
            &vol->plex[myplexno],
            vol->plexes - 1 - myplexno);    /* <-- count, not bytes */
        vol->plexes--;              /* <-- even when not found */

Two defects:

  1. The bcopy length is vol->plexes - 1 - myplexno ints expressed in bytes β€” it copies only a quarter of the required data (compare the correct form in remove_sd_entry, vinumconfig.c:1780-1782, which multiplies by sizeof(int)). For plex indices < 256 the corrupted entry accidentally retains the right value (little-endian LSB copy); from plex number 256 upward (plexes_allocated grows by 8 per EXPAND, reachable with ~33 plex lines) the volume's plex list silently points at the wrong plex β€” I/O is routed to a foreign plex (data corruption).
  2. When the plex is not found in its own volume's list (reachable via the index/plexno confusion written by the kw_prefer bug DF-1399, or dangling references after partial config), the error is only logged and vol->plexes is decremented anyway β€” repeatable removals drive vol->plexes negative, and vol->plex[vol->plexes] = plexno in give_plex_to_volume (vinumconfig.c:205) then writes before the array.

Fix

--- a/sys/dev/raid/vinum/vinumconfig.c
+++ b/sys/dev/raid/vinum/vinumconfig.c
@@ -1848,11 +1848,12 @@ remove_plex_entry(int plexno, int force, int recurse)
        if (myplexno == vol->plexes)        /* didn't find it.  Huh? */
        log(LOG_ERR,
            "Error removing plex %s: not found in volume %s\n",
            plex->name,
            vol->name);
+       else {
        if (myplexno < (vol->plexes - 1))   /* not the last plex in the list */
        bcopy(&vol->plex[myplexno + 1],
            &vol->plex[myplexno],
-           vol->plexes - 1 - myplexno);
+           (vol->plexes - 1 - myplexno) * sizeof(int));
        vol->plexes--;
+       }

Verification

Analysis-only (skipped live): Low severity; needs >=256 allocated plexes to corrupt an index in an observable way (bookkeeping-heavy setup), and the negative-plexes route piggybacks on DF-1399's state. The missing-sizeof(int) is directly visible against the correct twin in remove_sd_entry:1780.

Fix verification

not_testable
per-fix-DF-4137

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md analysis

PoC changes

n/a - analysis only

Verified recommended fix

Multiply the bcopy length by sizeof(int) and only decrement vol->plexes when the plex was found.

Verdict

remove_plex_entry compacts vol->plex[] with a byte count instead of sizeof(int) elements (vinumconfig.c:1850-1852 vs the correct twin at :1780-1782) and decrements vol->plexes even when the plex was not found (:1844-1853). For plex indices >=256 the volume's plex list silently points at wrong plexes (I/O routed to a foreign plex); repeated not-found removals can drive vol->plexes negative leading to writes before vol->plex[] in give_plex_to_volume (:205). Live test skipped: needs >=256 allocated plexes or DF-1399-state to matter.