DF-3741 / harness_hang.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | /* * 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(®_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; } |