DragonFlyBSD Kernel Audit
DF-1397 / vinum_overflow.c
← back to finding ↓ download raw
/*
 * 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;
}