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)
PoC verification
Evidence pack
findings/poc/DF-4137 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.6 KB | β raw | |
| manifest.json | file | 369 B | view raw | |
| verdict.json | file | 1.2 KB | view 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:
- The bcopy length is
vol->plexes - 1 - myplexnoints 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 bysizeof(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). - When the plex is not found in its own volume's list (reachable via
the index/plexno confusion written by the
kw_preferbug DF-1399, or dangling references after partial config), the error is only logged andvol->plexesis decremented anyway β repeatable removals drivevol->plexesnegative, andvol->plex[vol->plexes] = plexnoingive_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_testableConfirmed 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.
No comments yet.