โฌข DragonFlyBSD Kernel Audit
DF-3591 / fwsize_bypass_si.c
โ† back to finding โ†“ download raw
/*
 * DF-3591 โ€” legacy-firmware size-check bypass in si_init_microcode()
 *           -> heap OOB reads in constant-bounded legacy ucode loaders.
 *
 * Statement-faithful logic harness of sys/dev/drm/radeon/si.c:
 *   - si_init_microcode()   si.c:1660-1954  (CHIP_TAHITI, all-legacy fw set,
 *                                               truncated legacy ME firmware)
 *   - si_cp_load_microcode() legacy ME loop  si.c:3515-3538 (SI_PM4_UCODE_SIZE)
 *
 * The kernel path requires an SI (Tahiti/Pitcairn/Verde/Oland/Hainan) GPU to
 * attach; this guest is virtio-only, so this harness reproduces the *control
 * flow and the memory-safety violation* with identical statements and data.
 *
 * The truncated ME firmware buffer is placed at the end of an mmap'd page
 * followed by a PROT_NONE guard page, so the exact first OOB dword read
 * faults observably (in the kernel the kmalloc'd fw buffer is silently
 * overread instead).
 *
 * Build:  cc -O -Wall -o fwsize_bypass_si fwsize_bypass_si.c
 * Fixed:  cc -O -Wall -DFIXED -o fwsize_bypass_si_fixed fwsize_bypass_si.c
 * Run:    ./fwsize_bypass_si            (expects SIGSEGV in ME loader, exit 42)
 *         ./fwsize_bypass_si_fixed      (expects err=-EINVAL, exit 0)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>

/* --- constants from sys/dev/drm/radeon/radeon_ucode.h (SI, TAHITI) --- */
#define SI_PFP_UCODE_SIZE	2144	/* radeon_ucode.h:35 */
#define SI_PM4_UCODE_SIZE	2144	/* radeon_ucode.h:36 */
#define SI_CE_UCODE_SIZE	2144	/* radeon_ucode.h:37 */
#define SI_RLC_UCODE_SIZE	2048	/* radeon_ucode.h:51 */
#define SI_MC_UCODE_SIZE	7769	/* radeon_ucode.h:60 */
#define TAHITI_MC_UCODE_SIZE	7808	/* radeon_ucode.h:61 */
#define TAHITI_SMC_UCODE_SIZE	0xf458	/* radeon_ucode.h:136 */
#define ALIGN4(x)		(((x) + 3) & ~3)

static size_t pfp_req_size, me_req_size, ce_req_size, rlc_req_size,
	      mc_req_size, smc_req_size, mc2_req_size;

/* --- firmware stub (kernel: request_firmware from radeonkmsfw_* modules) --- */
struct fw {
	unsigned char	*data;		/* == kernel fw->data */
	size_t		datasize;	/* == kernel fw->datasize */
};

static struct fw pfp_fw, me_fw, ce_fw, rlc_fw, mc_fw, smc_fw;

/* files under /tmp/sifw/<name>; absence == request_firmware failure */
static int request_firmware(struct fw *f, const char *name)
{
	char path[256];
	FILE *fp;

	snprintf(path, sizeof(path), "/tmp/sifw/%s", name);
	fp = fopen(path, "rb");
	if (fp == NULL)
		return -1;			/* err != 0 */
	fseek(fp, 0, SEEK_END);
	f->datasize = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	f->data = malloc(f->datasize ? f->datasize : 1);
	if (fread(f->data, 1, f->datasize, fp) != f->datasize) {
		fclose(fp);
		return -1;
	}
	fclose(fp);
	return 0;
}

static void release_firmware(struct fw *f)
{
	free(f->data);
	memset(f, 0, sizeof(*f));
}

/* kernel radeon_ucode_validate(): only reached for new-format fw; the
 * all-legacy scenario never calls it with a present new-format file. */
static int radeon_ucode_validate(struct fw *f) { (void)f; return -1; }

static int new_fw;

/* ================= si_init_microcode, si.c:1660-1954 (TAHITI) ============ */
static int si_init_microcode(void)
{
	const char *chip_name = "TAHITI";
	const char *new_chip_name = "tahiti";
	char fw_name[30];
	int err;

	pfp_req_size = SI_PFP_UCODE_SIZE * 4;		/* si.c:1679 */
	me_req_size = SI_PM4_UCODE_SIZE * 4;		/* si.c:1680 */
	ce_req_size = SI_CE_UCODE_SIZE * 4;		/* si.c:1681 */
	rlc_req_size = SI_RLC_UCODE_SIZE * 4;		/* si.c:1682 */
	mc_req_size = SI_MC_UCODE_SIZE * 4;		/* si.c:1683 */
	mc2_req_size = TAHITI_MC_UCODE_SIZE * 4;	/* si.c:1684 */
	smc_req_size = ALIGN4(TAHITI_SMC_UCODE_SIZE);	/* si.c:1685 */

	/* ---- pfp block, si.c:1771-1793 ---- */
	snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_pfp", new_chip_name);
	err = request_firmware(&pfp_fw, fw_name);
	if (err) {
		snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_pfp", chip_name);
		err = request_firmware(&pfp_fw, fw_name);
		if (err)
			goto out;
		if (pfp_fw.datasize != pfp_req_size) {
			printf("si_cp: Bogus length %zu in firmware \"%s\"\n",
			       pfp_fw.datasize, fw_name);
			err = -EINVAL;
			goto out;			/* si.c:1782 โ€” pfp HAS goto out */
		}
	} else {
		err = radeon_ucode_validate(&pfp_fw);
		if (err)
			goto out;
		new_fw++;
	}

	/* ---- me block, si.c:1795-1816 โ€” TRUNCATED legacy fw goes here ---- */
	snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_me", new_chip_name);
	err = request_firmware(&me_fw, fw_name);
	if (err) {
		snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_me", chip_name);
		err = request_firmware(&me_fw, fw_name);
		if (err)
			goto out;
		if (me_fw.datasize != me_req_size) {
			printf("si_cp: Bogus length %zu in firmware \"%s\"\n",
			       me_fw.datasize, fw_name);
			err = -EINVAL;			/* si.c:1806 โ€” NO goto out */
#ifdef FIXED
			goto out;			/* fix.diff adds this */
#endif
		}
	} else {
		err = radeon_ucode_validate(&me_fw);
		if (err)
			goto out;
		new_fw++;
	}

	/* ---- ce block, si.c:1818-1839: err gets CLOBBERED here ---- */
	snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_ce", new_chip_name);
	err = request_firmware(&ce_fw, fw_name);
	if (err) {
		snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_ce", chip_name);
		err = request_firmware(&ce_fw, fw_name);
		if (err)
			goto out;
		if (ce_fw.datasize != ce_req_size) {
			printf("si_cp: Bogus length %zu in firmware \"%s\"\n",
			       ce_fw.datasize, fw_name);
			err = -EINVAL;			/* si.c:1829 โ€” NO goto out */
#ifdef FIXED
			goto out;
#endif
		}
	} else {
		err = radeon_ucode_validate(&ce_fw);
		if (err)
			goto out;
		new_fw++;
	}
	if (err == 0)
		printf("  [err clobbered: -EINVAL -> 0 by ce request_firmware success]\n");

	/* ---- rlc block, si.c:1841-1862 ---- */
	snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_rlc", new_chip_name);
	err = request_firmware(&rlc_fw, fw_name);
	if (err) {
		snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_rlc", chip_name);
		err = request_firmware(&rlc_fw, fw_name);
		if (err)
			goto out;
		if (rlc_fw.datasize != rlc_req_size) {
			printf("si_rlc: Bogus length %zu in firmware \"%s\"\n",
			       rlc_fw.datasize, fw_name);
			err = -EINVAL;			/* si.c:1852 โ€” NO goto out */
#ifdef FIXED
			goto out;
#endif
		}
	} else {
		err = radeon_ucode_validate(&rlc_fw);
		if (err)
			goto out;
		new_fw++;
	}

	/* ---- mc block, si.c:1864-1894 ---- */
	snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_mc", new_chip_name);
	err = request_firmware(&mc_fw, fw_name);
	if (err) {
		snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_mc2", chip_name);
		err = request_firmware(&mc_fw, fw_name);
		if (err) {
			snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_mc", chip_name);
			err = request_firmware(&mc_fw, fw_name);
			if (err)
				goto out;
		}
		if ((mc_fw.datasize != mc_req_size) &&
		    (mc_fw.datasize != mc2_req_size)) {
			printf("si_mc: Bogus length %zu in firmware \"%s\"\n",
			       mc_fw.datasize, fw_name);
			err = -EINVAL;			/* si.c:1883 โ€” NO goto out */
#ifdef FIXED
			goto out;
#endif
		}
	} else {
		err = radeon_ucode_validate(&mc_fw);
		if (err)
			goto out;
		new_fw++;
	}

	/* ---- smc block, si.c:1896-1925 ---- */
	snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_smc", new_chip_name);
	err = request_firmware(&smc_fw, fw_name);
	if (err) {
		snprintf(fw_name, sizeof(fw_name), "radeonkmsfw_%s_smc", chip_name);
		err = request_firmware(&smc_fw, fw_name);
		if (err) {
			printf("smc: error loading firmware \"%s\"\n", fw_name);
			release_firmware(&smc_fw);
			err = 0;
		} else if (smc_fw.datasize != smc_req_size) {
			printf("si_smc: Bogus length %zu in firmware \"%s\"\n",
			       smc_fw.datasize, fw_name);
			err = -EINVAL;			/* si.c:1915 โ€” NO goto out */
#ifdef FIXED
			goto out;
#endif
		}
	} else {
		err = radeon_ucode_validate(&smc_fw);
		if (err)
			goto out;
		new_fw++;
	}

	/* ---- final gate, si.c:1927-1934 ---- */
	if (new_fw == 0) {
		/* rdev->new_fw = false */
	} else if (new_fw < 6) {
		printf("si_fw: mixing new and old firmware!\n");
		err = -EINVAL;
	}
out:
	if (err) {
		release_firmware(&pfp_fw);		/* si.c:1940-1951 */
		release_firmware(&me_fw);
		release_firmware(&ce_fw);
		release_firmware(&rlc_fw);
		release_firmware(&mc_fw);
		release_firmware(&smc_fw);
	}
	return err;
}

/* ============ si_cp_load_microcode legacy ME loop, si.c:3515-3538 ======= */
static volatile unsigned long oob_dword_index;
static size_t me_buf_size;
static unsigned char *me_buf;

static void segv_handler(int sig, siginfo_t *si, void *ctx)
{
	(void)sig; (void)ctx;
	printf("  SIGSEGV at %p on ME loader iteration i=%lu: read crossed "
	       "datasize=%zu into the guard page\n",
	       si->si_addr, oob_dword_index, me_buf_size);
	printf("  OOB READ PROVEN: SI_PM4_UCODE_SIZE=%d dwords required, "
	       "buffer holds %zu dwords -> %ld bytes past allocation\n",
	       SI_PM4_UCODE_SIZE, me_buf_size / 4,
	       (long)(SI_PM4_UCODE_SIZE * 4) - (long)me_buf_size);
	_exit(42);
}

static void si_cp_load_microcode_legacy_me(void)
{
	volatile uint32_t *fw_data;
	long i;

	/* kernel: fw_data = (const __be32 *)rdev->me_fw->data;  si.c:3533 */
	fw_data = (volatile uint32_t *)me_buf;

	/* kernel: for (i = 0; i < SI_PM4_UCODE_SIZE; i++)
	 *           WREG32(CP_ME_RAM_DATA, be32_to_cpup(fw_data++));  si.c:3535 */
	for (i = 0; i < SI_PM4_UCODE_SIZE; i++) {
		oob_dword_index = i;
		(void)fw_data[i];		/* the OOB read */
	}
	printf("  ME loop completed without OOB (unexpected)\n");
}

int main(void)
{
	struct sigaction sa;
	long pagesz = sysconf(_SC_PAGESIZE);
	int r;

	/* truncated legacy ME firmware: 1024 bytes instead of 8576 */
	setvbuf(stdout, NULL, _IONBF, 0);
	me_buf_size = 1024;

	printf("== DF-3591 si_init_microcode() err-clobber (CHIP_TAHITI, "
	       "all-legacy, truncated ME fw) ==\n");

	r = si_init_microcode();
	printf("si_init_microcode -> err=%d (%s), me_fw=%zu B "
	       "(required %zu B), new_fw=%d\n",
	       r, r == 0 ? "ACCEPTED" : "rejected",
	       me_fw.datasize, me_req_size, new_fw);

#ifdef FIXED
	if (r != 0) {
		printf("FIXED build: bogus-length ME rejected with err=%d, "
		       "all firmware released at out: โ€” no loader runs.\n", r);
		return 0;
	}
	printf("FIXED build unexpectedly accepted; failing\n");
	return 1;
#else
	if (r != 0) {
		printf("baseline unexpectedly rejected; failing\n");
		return 1;
	}
	/* place the accepted truncated ME firmware at a page end w/ guard */
	me_buf = mmap(NULL, pagesz * 2, PROT_READ | PROT_WRITE,
		      MAP_PRIVATE | MAP_ANON, -1, 0);
	if (me_buf == MAP_FAILED) { perror("mmap"); return 1; }
	if (mprotect(me_buf + pagesz, pagesz, PROT_NONE) != 0) {
		perror("mprotect"); return 1;
	}
	memcpy(me_buf + pagesz - me_buf_size, me_fw.data, me_buf_size);
	me_buf += pagesz - me_buf_size;		/* buffer now ends at the guard */

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = segv_handler;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	printf("running si_cp_load_microcode legacy ME loop "
	       "(guard page after %zu-byte buffer)...\n", me_buf_size);
	si_cp_load_microcode_legacy_me();
	return 1;	/* not reached on successful demo */
#endif
}