⬢ DragonFlyBSD Kernel Audit
DF-4526 / ani_null_deref.c
← back to finding ↓ download raw
/*
 * DF-4526 harness -- userspace replica of the ar5416AniControl() dispatch
 * decision flow in sys/dev/netif/ath/ath_hal/ar5416/ar5416_ani.c, driven
 * with the exact state the HAL is in between ar5416Attach() and the first
 * ar5416AniReset() (i.e. before any channel is set):
 *
 *   - ath_hal_5416 softc is kmalloc'd ZEROED (ar5416_attach.c:312
 *     "NB: memory is returned zero'd")  ->  AH5212(ah)->ah_curani == NULL
 *   - ar5416Attach() sets AH5416(ah)->ah_ani_function = 0xffffffff
 *     (ar5416_attach.c:244 "Enable all ANI functions to begin with")
 *   - the diagnostic interface HAL_DIAG_ANI_CMD (ar5212_misc.c:1098-1103)
 *     forwards user-supplied (cmd, param) verbatim:
 *         AH5212(ah)->ah_aniControl(ah, ((const uint32_t *)args)[0],
 *                                        ((const uint32_t *)args)[1]);
 *
 * The replica walks the same branches ar5416AniControl() walks for
 * cmd = HAL_ANI_NOISE_IMMUNITY_LEVEL (1):
 *
 *   aniState = ahp->ah_curani;              // NULL
 *   if (aniState != AH_NULL) params = ...;  // params stays NULL
 *   if (cmd == HAL_ANI_PRESENT) ...         // no
 *   if (cmd == HAL_ANI_MODE) ...            // no
 *   if (((1 << cmd) & ah_ani_function) == 0) // 0xffffffff -> gate PASSES
 *        return AH_FALSE;
 *   switch (cmd) { case HAL_ANI_NOISE_IMMUNITY_LEVEL:
 *        u_int level = param;
 *        if (level > params->maxNoiseImmunityLevel)   // <-- NULL DEREF
 *
 * In the kernel, params->maxNoiseImmunityLevel with params == NULL reads
 * virtual address 0x4-ish (offset of maxNoiseImmunityLevel in
 * struct ar5212AniParams) -> page fault -> kernel panic.
 * (For HAL_ANI_OFDM_WEAK_SIGNAL_DETECTION the analogous bug is a NULL
 * WRITE: aniState->ofdmWeakSigDetectOff = !on at offset 3.)
 *
 * Build:  cc -O2 -o ani_null_deref ani_null_deref.c
 * Run:    ./ani_null_deref           (default: replicates kernel flow,
 *                                     flags the deref, exits cleanly)
 *         ./ani_null_deref --deref   (actually performs the NULL read
 *                                     and dies with SIGSEGV, proving the
 *                                     fault is on the cited instruction)
 *         ./ani_null_deref --fixed   (simulates the one-line guard; shows
 *                                     the flow returns AH_FALSE instead)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

/* --- minimal faithful struct/enum excerpts ------------------------- */

struct ar5212AniParams {
	int maxNoiseImmunityLevel;	/* first member, offset 0 */
	int totalSizeDesired[5];
	/* ... */
};

struct ar5212AniState {
	uint8_t noiseImmunityLevel;
	uint8_t spurImmunityLevel;
	uint8_t firstepLevel;
	uint8_t ofdmWeakSigDetectOff;
	uint8_t cckWeakSigThreshold;
	/* ... */
};

struct state {
	struct ar5212AniState *ah_curani;	/* NULL after attach */
	unsigned int ah_ani_function;		/* 0xffffffff after attach */
};

typedef enum {
	HAL_ANI_PRESENT = 0,
	HAL_ANI_NOISE_IMMUNITY_LEVEL = 1,
	HAL_ANI_OFDM_WEAK_SIGNAL_DETECTION = 2,
	HAL_ANI_CCK_WEAK_SIGNAL_THR = 3,
	HAL_ANI_FIRSTEP_LEVEL = 4,
	HAL_ANI_SPUR_IMMUNITY_LEVEL = 5,
	HAL_ANI_MODE = 6,
	HAL_ANI_PHYERR_RESET = 7,
	HAL_ANI_MRC_CCK = 8,
} HAL_ANI_CMD;

#define AH_TRUE 1
#define AH_FALSE 0

static sigjmp_buf jmp;
static volatile sig_atomic_t faulted;

static void
fault(int sig)
{
	faulted = 1;
	siglongjmp(jmp, 1);
}

/*
 * Decision-flow replica of ar5416AniControl() (ar5416_ani.c:172-252,
 * truncated at the first dereference of the NOISE_IMMUNITY case).
 * 'do_deref': actually execute the dereferencing load (kernel: page
 * fault); otherwise emulate "kernel would have faulted here".
 */
static int
ani_control_replica(struct state *ah, int cmd, int param,
    int do_deref, int fixed)
{
	struct ar5212AniState *aniState = ah->ah_curani;
	const struct ar5212AniParams *params = NULL;
	unsigned int level = (unsigned int)param;

	/* ar5416_ani.c:184-185 */
	if (aniState != NULL)
		params = (const struct ar5212AniParams *)aniState; /* unreachable */

	/* ar5416_ani.c:190-191 */
	if (cmd == HAL_ANI_PRESENT)
		return AH_TRUE;
	/* ar5416_ani.c:193-214 */
	if (cmd == HAL_ANI_MODE)
		return AH_TRUE;

	/* ar5416_ani.c:217 -- the function gate */
	if (((1u << cmd) & ah->ah_ani_function) == 0) {
		printf("  gate: command %d disabled (mask 0x%x)\n",
		    cmd, ah->ah_ani_function);
		return AH_FALSE;
	}

	/* proposed fix: NULL guard before the switch */
	if (fixed && params == NULL) {
		printf("  FIXED guard: params == NULL -> return AH_FALSE\n");
		return AH_FALSE;
	}

	switch (cmd) {
	case HAL_ANI_NOISE_IMMUNITY_LEVEL:
		/* ar5416_ani.c:230 */
		printf("  case NOISE_IMMUNITY_LEVEL: about to load "
		    "params->maxNoiseImmunityLevel with params=%p\n",
		    (void *)params);
		if (do_deref) {
			volatile int v = params->maxNoiseImmunityLevel;
			printf("  (loaded %d -- mapped NULL?)\n", v);
		} else {
			printf("  KERNEL: read at %p -> page fault -> panic\n",
			    (void *)&params->maxNoiseImmunityLevel);
		}
		return AH_FALSE;	/* kernel never reaches here */
	default:
		return AH_FALSE;
	}
}

int
main(int argc, char **argv)
{
	struct state ah;
	int do_deref = 0, fixed = 0;

	if (argc > 1 && strcmp(argv[1], "--deref") == 0)
		do_deref = 1;
	if (argc > 1 && strcmp(argv[1], "--fixed") == 0)
		fixed = 1;

	/* attach-time state (ar5416_attach.c:244, softc zeroed :312) */
	ah.ah_curani = NULL;
	ah.ah_ani_function = 0xffffffff;

	printf("state: ah_curani=%p ah_ani_function=0x%x\n",
	    (void *)ah.ah_curani, ah.ah_ani_function);
	printf("SIOCGATHDIAG HAL_DIAG_ANI_CMD args{cmd=1(NOISE_IMMUNITY_LEVEL),"
	    " param=0}\n");

	if (do_deref) {
		signal(SIGSEGV, fault);
		signal(SIGBUS, fault);
		if (sigsetjmp(jmp, 1) == 0) {
			ani_control_replica(&ah, HAL_ANI_NOISE_IMMUNITY_LEVEL,
			    0, 1, 0);
			printf("RESULT: BUGGY?? NULL was mapped (unexpected)\n");
			return 1;
		}
		printf("RESULT: BUGGY -- SIGSEGV on the NULL params load "
		    "(kernel: Fatal trap 12 page fault -> panic)\n");
		return 0;
	}

	ani_control_replica(&ah, HAL_ANI_NOISE_IMMUNITY_LEVEL, 0, 0, fixed);
	printf("RESULT: %s\n", fixed ? "PATCHED" : "BUGGY");
	return 0;
}