โฌข DragonFlyBSD Kernel Audit
DF-3960 / trigger.c
โ† back to finding โ†“ download raw
/* DF-3960 โ€” ar5416GetGainBoundariesAndPdadcs() out-of-bounds READS on
 * degenerate EEPROM calibration data.
 *
 * sys/dev/netif/ath/ath_hal/ar5416/ar5416_reset.c
 *
 * (a) xpdGain mask == 0 (crafted/corrupt EEPROM):
 *
 *     ar5416SetPowerCalTable() (ar5416_reset.c:2337-2405) computes
 *     numXpdGain = ar5416GetXpdGainValues(ah, xpdMask, ...) which returns
 *     0 for an empty mask with no validation (:2247-2265).  It then calls
 *     ar5416GetGainBoundariesAndPdadcs(..., numXpdGains=0) (:2387-2391):
 *       - both `for (i = 0; i < numXpdGains; i++)' loops are skipped,
 *         leaving i == 0 and k == 0;
 *       - `while (i < AR5416_PD_GAINS_IN_MASK)
 *              pPdGainBoundaries[i] = pPdGainBoundaries[i-1];' (:2566-2568)
 *         reads pPdGainBoundaries[-1] โ€” the caller's STACK array
 *         gainBoundaries[4] (ar5416_reset.c:2345);
 *       - `while (k < AR5416_NUM_PDADC_VALUES)
 *              pPDADCValues[k] = pPDADCValues[k-1];' (:2571-2573)
 *         reads pPDADCValues[-1] โ€” 1 byte before the driver-static
 *         pdadcValues[128] (ar5416_reset.c:2344).
 *     The garbage propagates into PHY registers TPCRG5/PDADC.
 *
 * (b) degenerate pier power range (maxPwrT4[i] == minPwrT4[i]):
 *
 *     sizeCurrVpdTable = (max - min)/2 + 1 == 1 (:2541), then
 *     `vpdStep = vpdTableI[i][sizeCurrVpdTable - 1]
 *             - vpdTableI[i][sizeCurrVpdTable - 2];' (:2549)
 *     reads vpdTableI[i][-1] and vpdTableI[i][-2] โ€” static .bss OOB reads.
 *
 * Triggered on every channel change once the EEPROM holds the degenerate
 * values (attacker model: malicious/corrupted NIC EEPROM, same model as
 * DF-1308/DF-1309).
 *
 * This harness replicates both index computations exactly.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define	AR5416_NUM_PD_GAINS		4
#define	AR5416_PD_GAINS_IN_MASK		4
#define	AR5416_NUM_PDADC_VALUES		128

/* instrumented arrays: record every index touched */
static int16_t  pdadc_shadow[AR5416_NUM_PDADC_VALUES + 2]; /* [-1]..[128] */
static int16_t *pPDADCValues = &pdadc_shadow[1];	/* index 0 == real[0] */
static int16_t  gainb_shadow[AR5416_PD_GAINS_IN_MASK + 1];
static int16_t *pPdGainBoundaries = &gainb_shadow[1];
static int	oob_read_pdadc, oob_read_gainb, oob_read_vpd;

/* vpdTableI is [4][64]; we only need the [-2]/[-1] probe of row i */
static uint8_t vpd_shadow[4][64 + 2];

static void
replica_backfill(int numXpdGains)
{
	int i = 0, k = 0;

	/* `for (i = 0; i < numXpdGains; i++)' โ€” both loops skipped for 0.
	 * The main fill loop writes at least one PDADC entry per gain
	 * (the extrapolate loop at :2555-2561 always runs >= 1 iteration
	 * for the last gain), so k >= 1 whenever numXpdGains >= 1. */
	for (; i < numXpdGains; i++)
		;
	k = (numXpdGains > 0) ? 1 : 0;

	/* ar5416_reset.c:2565-2569 */
	while (i < AR5416_PD_GAINS_IN_MASK) {
		if (i - 1 < 0)
			oob_read_gainb++;	/* pPdGainBoundaries[-1] */
		pPdGainBoundaries[i] = (i - 1 >= 0) ?
		    pPdGainBoundaries[i - 1] : 0x4242/*the OOB load*/;
		i++;
	}

	/* ar5416_reset.c:2571-2574 */
	while (k < AR5416_NUM_PDADC_VALUES) {
		if (k - 1 < 0)
			oob_read_pdadc++;	/* pPDADCValues[-1] */
		pPDADCValues[k] = (k - 1 >= 0) ?
		    pPDADCValues[k - 1] : 0x4242;
		k++;
	}
}

static int
replica_vpdstep(int maxPwrT4, int minPwrT4, int row)
{
	uint16_t sizeCurrVpdTable;

	/* ar5416_reset.c:2541 */
	sizeCurrVpdTable = (uint8_t)((maxPwrT4 - minPwrT4) / 2 + 1);

	/* ar5416_reset.c:2549 โ€” reads [size-1] and [size-2] */
	if ((int)sizeCurrVpdTable - 2 < 0)
		oob_read_vpd++;			/* vpdTableI[row][-1] or [-2] */
	/* copy loop bound: while (ss < maxIndex <= sizeCurrVpdTable) reads
	 * vpdTableI[row][ss] โ€” OOB past the 64-entry row when size > 64 */
	if (sizeCurrVpdTable > 64)
		oob_read_vpd++;			/* reads [64..size-1] of row */
	return ((int)sizeCurrVpdTable < 2) ? -1 : 0;
}

int
main(void)
{
	printf("== DF-3960 ar5416GetGainBoundariesAndPdadcs degenerate-EEPROM "
	    "OOB reads ==\n");

	/* (a) xpdGain mask == 0 โ€” ar5416_reset.c:2371,2387,2566-2574 */
	replica_backfill(0);
	printf("(a) xpdGain mask 0: pPdGainBoundaries[-1] stack OOB read "
	    "(caller gainBoundaries[4], ar5416_reset.c:2345): %s\n",
	    oob_read_gainb ? "YES (ar5416_reset.c:2567)" : "no");
	printf("    pPDADCValues[-1] static OOB read "
	    "(pdadcValues[128], ar5416_reset.c:2344): %s\n",
	    oob_read_pdadc ? "YES (ar5416_reset.c:2572)" : "no");

	/* (b) zero-range pier: maxPwrT4 == minPwrT4 โ€” ar5416_reset.c:2541,2549 */
	replica_vpdstep(100, 100, 0);
	printf("(b) maxPwrT4==minPwrT4: sizeCurrVpdTable==1 -> "
	    "vpdTableI[i][-1]/[-2] reads (ar5416_reset.c:2549): %s\n",
	    oob_read_vpd ? "YES" : "no");

	/* (c) inverted range: maxPwrT4 < minPwrT4 โ€” (uint8_t)((0-255)/2+1)
	 *     truncates to 130 > 64 -> copy loop reads past the row */
	oob_read_vpd = 0;
	replica_vpdstep(0, 255, 0);
	printf("(c) inverted range (0,255): sizeCurrVpdTable==130 -> "
	    "vpdTableI[i][64..129] row OOB reads (:2545-2547): %s\n",
	    oob_read_vpd ? "YES" : "no");

	/* control: sane EEPROM values touch no OOB index */
	oob_read_gainb = oob_read_pdadc = oob_read_vpd = 0;
	replica_backfill(2);
	replica_vpdstep(120, 40, 0);
	printf("control (2 xpd gains, range 80): OOB reads = %d "
	    "(expect 0)\n",
	    oob_read_gainb + oob_read_pdadc + oob_read_vpd);

	return (0);
}