DF-4526 / ani_null_deref.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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | /* * 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 *)¶ms->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; } |