⬢ DragonFlyBSD Kernel Audit
DF-3741 / harness_hang.c
← back to finding ↓ download raw
/*
 * DF-3741 harness: zero-stride record walks -> infinite kernel loop (BIOS-gated hang)
 *
 * Two verbatim replicas from sys/dev/drm/radeon/radeon_atombios.c:
 *
 * (1) radeon_atom_init_mc_reg_table() :4023-4045
 *        while ((le32_to_cpu(*(u32 *)reg_data) != END_OF_REG_DATA_BLOCK) &&
 *               (num_ranges < VBIOS_MAX_AC_TIMING_ENTRIES)) {
 *            t_mem_id = ...; if (module_index == t_mem_id) { ... num_ranges++; }
 *            reg_data = (void *)((u8 *)reg_data + le16_to_cpu(reg_block->usRegDataBlkSize));
 *        }
 *     If usRegDataBlkSize == 0 and the (BIOS-chosen) t_mem_id never equals module_index,
 *     the pointer never advances, num_ranges never increments -> infinite loop.
 *
 * (2) atom_lookup_voltage_object_v3() :3423-3429
 *        while (offset < size) {
 *            vo = start + offset;
 *            if (match) return vo;
 *            offset += le16_to_cpu(vo->asGpioVoltageObj.sHeader.usSize);  (may be 0)
 *        }
 *     If the first object's usSize == 0 and type/mode do not match -> infinite loop.
 *
 * A userland process cannot loop forever in a proof, so both replicas run with an
 * iteration cap that DOES NOT EXIST in the kernel code; hitting the cap == proof of
 * non-termination.
 */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32;

#define END_OF_REG_DATA_BLOCK 0xFFFFFFFFUL
#define VBIOS_MAX_AC_TIMING_ENTRIES 20

struct mc_reg_table_model { u32 x; };
struct atom_mc_reg_table { u8 last, num_entries; u32 tbl[VBIOS_MAX_AC_TIMING_ENTRIES]; };

static int walk_mc_reg(u16 usRegDataBlkSize, u8 module_index, const u8 *bios, size_t bios_len)
{
	struct atom_mc_reg_table reg_table; memset(&reg_table, 0, sizeof(reg_table));
	/* model: usMemClkPatchTblOffset points at reg_block; data begins after index table */
	const u8 *reg_block = bios + 0x40;
	u32 *reg_data = (u32 *)(bios + 0x80);
	*reg_data = 0x02000000UL;			/* t_mem_id = 2 (mismatch w/ module_index 0) */
	const unsigned long CAP = 1UL << 24;		/* NOT present in kernel */
	unsigned long it = 0;
	int num_ranges = 0, module_index_model = module_index;

	while ((reg_data[0] != END_OF_REG_DATA_BLOCK) &&
	       (num_ranges < VBIOS_MAX_AC_TIMING_ENTRIES)) {	/* :4023 */
		u8 t_mem_id = (u8)((reg_data[0] & 0xFF000000UL) >> 24);
		if (module_index_model == t_mem_id)
			num_ranges++;				/* never taken: 0 != 2 */
		reg_data = (u32 *)((u8 *)reg_data + usRegDataBlkSize);	/* :4043-4044 stride 0 */
		if (++it >= CAP) return -1;			/* cap only in harness */
	}
	(void)reg_block; (void)bios_len;
	return (int)it;
}

struct ATOM_GPIO_VOLTAGE_OBJECT_V3_hdr { u16 usSize; u8 ucVoltageType, ucVoltageMode; /*...*/ };

static int walk_volt_v3(u16 first_usSize, u8 want_type, const u8 *bios)
{
	u32 size = 0xFFFF;				/* self-declared table size (BIOS) */
	u32 offset = 8;
	const u8 *start = bios;
	const unsigned long CAP = 1UL << 24;
	unsigned long it = 0;

	while (offset < size) {
		struct ATOM_GPIO_VOLTAGE_OBJECT_V3_hdr *vo =
			(struct ATOM_GPIO_VOLTAGE_OBJECT_V3_hdr *)(start + offset);
		if (vo->ucVoltageType == want_type) return (int)it;
		offset += first_usSize;			/* :3428 — usSize == 0 => stuck */
		if (++it >= CAP) return -1;
	}
	return (int)it;
}

int main(void)
{
	static u8 bios[1024];
	memset(bios, 0x11, sizeof(bios));

	printf("[1] radeon_atom_init_mc_reg_table usRegDataBlkSize=0, t_mem_id != module_index...\n");
	int r1 = walk_mc_reg(0, 0, bios, sizeof(bios));
	if (r1 == -1)
		printf("    loop did NOT terminate after %lu iterations (kernel has no cap: HANG)\n", 1UL << 24);
	else { printf("    terminated after %d iterations\n", r1); return 1; }

	printf("[2] atom_lookup_voltage_object_v3 usSize=0, type mismatch...\n");
	int r2 = walk_volt_v3(0, 0x01, bios);
	if (r2 == -1)
		printf("    loop did NOT terminate after %lu iterations (kernel has no cap: HANG)\n", 1UL << 24);
	else { printf("    terminated after %d iterations\n", r2); return 1; }

	/* contrast: nonzero stride terminates (END marker 3 blocks ahead) */
	*(u32 *)(bios + 0x8C) = END_OF_REG_DATA_BLOCK;
	int rc = walk_mc_reg(4, 0, bios, sizeof(bios));
	printf("[control] usRegDataBlkSize=4 with END marker at +3 blocks: terminates (%d iters)\n", rc);

	printf("REPRODUCED (harness): zero-stride BIOS record walks never terminate\n");
	return 0;
}