⬢ DragonFlyBSD Kernel Audit
DF-4903 / sim_tailread.c
← back to finding ↓ download raw
/*
 * DF-4903 logic harness — userland replica of the exact kernel flow in
 * sys/dev/powermng/powernow/powernow.c that leaks one uninitialised heap
 * word into the world-readable sysctl machdep.powernow.frequency.current.
 *
 * Replicates, with identical struct layout and (in)expressions:
 *   - powernow_init()  (powernow.c:366-397): kmalloc(M_DEVBUF, M_WAITOK)
 *     [NOT zeroed], n_states from PST, k8_powernow_setperf(k8_get_curfreq())
 *   - k8_get_curfreq() (powernow.c:204-224): returns -1 when the CPU's
 *     current fid/vid (from MSR 0xc0010042) is NOT in the decoded table
 *   - k8_powernow_setperf() (powernow.c:226-313): `unsigned int freq`
 *     receives -1 -> UINT_MAX; the `state_table[i].freq >= freq` match
 *     loop never fires; i exits == n_states; line 310-311 then indexes
 *     state_table[n_states] -- past the initialised prefix -> the
 *     uninitialised 4-byte `freq` lands in the global `cpuspeed`.
 *
 * This is a LOGIC SIMULATION only (no AMD K8 hardware / no kernel MSR).
 * The heap is poisoned before the "decode" so the uninit read is visible.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define POWERNOW_MAX_STATES 16

struct k8pnow_state {
	int     freq;
	uint8_t fid;
	uint8_t vid;
};

struct k8pnow_cpu_state {
	struct k8pnow_state state_table[POWERNOW_MAX_STATES];
	unsigned int n_states;
	unsigned int vst, mvs, pll, rvo, irt;
	int low;
};

/* globals, as in powernow.c:125-126 */
static struct k8pnow_cpu_state *k8pnow_current_state = NULL;
int cpuspeed;

/* fake MSR status: CPU sits at cfid=0x0a/cvid=0x12 -- NOT in the table */
static uint64_t boot_fidvid_status = ((uint64_t)0x12 << 32) | 0x0a;

/* k8_get_curfreq -- powernow.c:204-224 */
static int k8_get_curfreq(void)
{
	unsigned int i;
	uint64_t status;
	int cfid, cvid;
	struct k8pnow_cpu_state *cstate;
	status = boot_fidvid_status;
	if ((status >> 31) & 1) /* PN8_STA_PENDING */
		return 1;
	cfid = status & 0x3f;
	cvid = (status >> 32) & 0x1f;
	cstate = k8pnow_current_state;
	for (i = 0; i < cstate->n_states; i++)
		if (cstate->state_table[i].fid == cfid &&
		    cstate->state_table[i].vid == cvid)
			return (cstate->state_table[i].freq);
	/* Not reached */			/* <-- powernow.c:222 */
	return -1;				/* <-- powernow.c:223 */
}

/* k8_powernow_setperf -- powernow.c:226-313 (MSR writes stubbed out) */
static int k8_powernow_setperf(unsigned int freq)
{
	unsigned int i;				/* <-- powernow.c:229 */
	int cfid, cvid, fid = 0, vid = 0;
	struct k8pnow_cpu_state *cstate;
	uint64_t status = boot_fidvid_status;

	cfid = status & 0x3f;
	cvid = (status >> 32) & 0x1f;
	cstate = k8pnow_current_state;
	for (i = 0; i < cstate->n_states; i++) {	/* <-- 245 */
		if (cstate->state_table[i].freq >= freq) { /* int vs unsigned! */
			fid = cstate->state_table[i].fid;
			vid = cstate->state_table[i].vid;
			break;
		}
	}
	/* loop never matched: freq == 0xFFFFFFFF > every MHz value.
	 * i == n_states here, fid == vid == 0.  (MSR transition phases
	 * stubbed: assume HW settles to cfid==0/cvid==0 as requested.) */
	cfid = fid; cvid = vid;

	if (cfid == fid || cvid == vid)		/* <-- powernow.c:310 */
		cpuspeed = cstate->state_table[i].freq; /* <-- 311: i==n_states! */
	return 0;
}

int main(void)
{
	struct k8pnow_cpu_state *cstate;
	unsigned int i;

	/* powernow_init(): kmalloc(sizeof, M_DEVBUF, M_WAITOK) -- NOT zeroed
	 * (powernow.c:366); poison the allocation to emulate live heap data */
	cstate = malloc(sizeof(struct k8pnow_cpu_state));
	memset(cstate, 0xA5, sizeof(*cstate));

	/* k8pnow_decode_pst() fills only n_states entries (say 3 of 16);
	 * the remaining 13 stay at 0xA5A5A5A5 = live-heap garbage */
	for (i = 0; i < 3; i++) {
		cstate->state_table[i].freq = 800 + 100 * (i + 4);
		cstate->state_table[i].fid  = i + 4;
		cstate->state_table[i].vid  = 0x10 + i;
	}
	cstate->n_states = 3;
	k8pnow_current_state = cstate;

	/* powernow_init():392 -- k8_powernow_setperf(k8_get_curfreq()) */
	int cur = k8_get_curfreq();
	printf("k8_get_curfreq() = %d   (boot cfid/cvid not in table)\n", cur);
	k8_powernow_setperf(cur);		/* -1 -> UINT_MAX */

	printf("cpuspeed after init = 0x%08x (%d)\n", cpuspeed, cpuspeed);
	if (cpuspeed == 0xA5A5A5A5) {
		printf("LEAK CONFIRMED (logic): uninitialised heap word read at\n"
		       "state_table[n_states=3] landed in the world-readable\n"
		       "sysctl machdep.powernow.frequency.current\n");
		return 0;
	}
	printf("unexpected value -- logic not reproduced\n");
	return 1;
}