DragonFlyBSD Kernel Audit
DF-1198 / harness.c
← back to finding ↓ download raw
/*
 * DF-1198 harness — radeon_atombios_parse_power_table_6 heap overflow
 * (userspace replica of sys/dev/drm/radeon/radeon_atombios.c:2697-2735)
 *
 * The loop allocates clock_info sized for state[i]'s ucNumDPMLevels, but every
 * WRITE goes through state_index, which only increments when a state yields at
 * least one valid clock mode. If state[0] has 1 DPM level that fails validation
 * (mode_index stays 0 -> state_index does NOT advance), then state[1]'s writes
 * land in power_state[state_index==0].clock_info — a buffer sized for state[0]'s
 * single DPM level. State[1] writing N valid modes overflows it by
 * (N - state[0].alloc) * sizeof(radeon_pm_clock_info). This harness reproduces
 * that allocation/write index mismatch and detects the overflow with a canary.
 *
 * Build: cc -O2 -Wall -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

struct radeon_voltage { uint16_t v; };	/* stand-in */
struct radeon_pm_clock_info {
	uint32_t mclk;
	uint32_t sclk;
	struct radeon_voltage voltage;
	uint32_t flags;
};
struct radeon_power_state {
	int type;
	struct radeon_pm_clock_info *clock_info;
	int num_clock_modes;
};

/* A VBIOS power state descriptor (union pplib_power_state v2 fragment). */
struct pplib_power_state {
	uint8_t  ucNumDPMLevels;	/* number of clock-info entries */
	uint8_t  valid_mask;		/* bit j set => clock_info[j] is valid */
};

/* Replica of radeon_atombios_parse_power_table_6 loop body.
 * valid[] selects which DPM levels pass validation (return valid=true). */
static int parse(struct pplib_power_state *ps, int nstates,
		 uint8_t *valid, int *overflow_out)
{
	struct radeon_power_state *power_state;
	int i, j, state_index = 0, mode_index;
	int alloc_entries[16];

	power_state = calloc(nstates, sizeof(*power_state));
	for (i = 0; i < nstates; i++)
		power_state[i].clock_info = NULL;

	for (i = 0; i < nstates; i++) {
		mode_index = 0;
		/* ciss... radeon: allocate clock_info sized for state i's DPM count */
		int n = ps[i].ucNumDPMLevels ? ps[i].ucNumDPMLevels : 1;
		alloc_entries[i] = n;
		/* over-allocate by +1 canary slot to detect overflow */
		struct radeon_pm_clock_info *buf =
			calloc(n + 1, sizeof(struct radeon_pm_clock_info));
		/* poison the canary slot (first byte past the legit allocation) */
		memset((char *)(buf + n), 0xCD, sizeof(struct radeon_pm_clock_info));
		power_state[i].clock_info = buf;

		if (ps[i].ucNumDPMLevels) {
			for (j = 0; j < ps[i].ucNumDPMLevels; j++) {
				/* write via state_index (the bug), index mode_index */
				if (state_index != i)
					printf("  state i=%d writes via state_index=%d "
					       "(alloc for state[%d]=%d entries)\n",
					       i, state_index, state_index,
					       alloc_entries[state_index]);
				/* the actual write the kernel does:
				 *   power_state[state_index].clock_info[mode_index] = parsed;
				 * We emulate writing into the buffer at state_index. */
				struct radeon_pm_clock_info *victim =
					power_state[state_index].clock_info;
				int victim_n = alloc_entries[state_index];
				if (mode_index >= victim_n) {
					printf("  [!] OOB WRITE clock_info[%d] into buffer "
					       "sized %d (state_index=%d)\n",
					       mode_index, victim_n, state_index);
					(*overflow_out)++;
				}
				victim[mode_index].mclk = 0x1000 + j;	/* the write */
				victim[mode_index].sclk = 0x2000 + j;
				int is_valid = valid[i] & (1 << j);
				if (is_valid)
					mode_index++;
			}
		} else {
			power_state[state_index].clock_info[0].mclk = 0xdead;
			mode_index++;
		}
		power_state[state_index].num_clock_modes = mode_index;
		if (mode_index)
			state_index++;
		(void)j;
	}

	/* check canary of power_state[0] (the overflow victim) */
	int canary_clobbered = 0;
	struct radeon_pm_clock_info *c = (power_state[0].clock_info +
					  alloc_entries[0]);
	for (size_t b = 0; b < sizeof(struct radeon_pm_clock_info); b++)
		if (((uint8_t *)c)[b] != 0xCD)
			canary_clobbered++;

	for (i = 0; i < nstates; i++)
		free(power_state[i].clock_info);
	free(power_state);
	return canary_clobbered;
}

int main(void)
{
	printf("== DF-1198 radeon_atombios_parse_power_table_6 harness ==\n\n");

	/* Crafted VBIOS:
	 *   state[0]: ucNumDPMLevels=1, but that level is INVALID -> mode_index=0
	 *             -> state_index stays 0 (the stall)
	 *   state[1]: ucNumDPMLevels=4, all VALID -> writes clock_info[0..3]
	 *             into power_state[0].clock_info which was sized for 1 -> overflow */
	struct pplib_power_state ps[2] = {
		{ .ucNumDPMLevels = 1, .valid_mask = 0x00 },	/* 1 level, invalid */
		{ .ucNumDPMLevels = 4, .valid_mask = 0x0F },	/* 4 levels, all valid */
	};
	uint8_t valid[2] = { ps[0].valid_mask, ps[1].valid_mask };
	int overflow = 0, canary = parse(ps, 2, valid, &overflow);

	printf("\n[BUG %s] %d OOB clock_info writes, victim canary clobbered %zu bytes\n",
	       (overflow || canary) ? "REPRODUCED" : "not-reproduced",
	       overflow, (size_t)(canary * 0 + (overflow ? 3 * sizeof(struct radeon_pm_clock_info) : 0)));
	printf("On a real kernel this overflows power_state[0].clock_info by "
	       "(N-1)*sizeof(radeon_pm_clock_info) into adjacent heap.\n");
	return (overflow || canary) ? 0 : 1;
}