DF-3576 / iwn_timing_div0.c
/* * DF-3576 PoC harness: division by zero in iwn_set_timing() * sys/dev/netif/iwn/if_iwn.c:5583-5604 (DragonFlyBSD) * * The arithmetic below is copied verbatim from iwn_set_timing() * (if_iwn.c:5590-5598): * * memset(&cmd, 0, sizeof cmd); * memcpy(&cmd.tstamp, ni->ni_tstamp.data, sizeof (uint64_t)); * cmd.bintval = htole16(ni->ni_intval); * cmd.lintval = htole16(10); * val = (uint64_t)ni->ni_intval * IEEE80211_DUR_TU; * mod = le64toh(cmd.tstamp) % val; <-- divisor == 0 * * ni_intval == 0 happens when the station associates to an AP whose * beacon/probe-response carries bintval == 0: * - ieee80211_input.c:545 parses bintval from the frame * - ieee80211_input.c:700-707 marks IEEE80211_BPARSE_BINTVAL_INVALID * - BUT the STA scan consumer never checks that bit: * ieee80211_scan_sta.c:314 ise->se_intval = sp->bintval; * - ieee80211_node.c:818 (ieee80211_sta_join) ni->ni_intval = se->se_intval; * - on IEEE80211_S_RUN, iwn_newstate -> iwn_run (if_iwn.c:7237) * -> iwn_set_timing -> "% 0" -> #DE -> kernel panic. * * This userland model executes the exact expression with ni_intval == 0. * Success criterion: process dies with SIGFPE (Floating point exception), * exit status 136 (128+8), proving the kernel would take a #DE on this path. * * Build: cc -O2 -o iwn_timing_div0 iwn_timing_div0.c * Run: ./iwn_timing_div0 */ #include <stdint.h> #include <stdio.h> #include <string.h> #include <stdlib.h> /* if_iwnreg.h: struct iwn_cmd_timing (relevant prefix) */ struct iwn_cmd_timing { uint64_t tstamp; uint16_t bintval; uint16_t lintval; uint32_t binitval; } __attribute__((__packed__)); #define le16toh(x) (x) #define le64toh(x) (x) #define htole16(x) (x) #define IEEE80211_DUR_TU 1024 /* verbatim arithmetic of iwn_set_timing(), if_iwn.c:5590-5598 */ static void iwn_set_timing_model(uint16_t ni_intval, const uint8_t ni_tstamp[8]) { struct iwn_cmd_timing cmd; uint64_t val, mod; memset(&cmd, 0, sizeof cmd); memcpy(&cmd.tstamp, ni_tstamp, sizeof (uint64_t)); cmd.bintval = htole16(ni_intval); cmd.lintval = htole16(10); /* Compute remaining time until next beacon. */ val = (uint64_t)ni_intval * IEEE80211_DUR_TU; printf("ni_intval=%u -> val=%llu ; taking modulo\n", ni_intval, (unsigned long long)val); fflush(stdout); mod = le64toh(cmd.tstamp) % val; /* <-- divide by zero */ cmd.binitval = (uint32_t)(val - mod); printf("survived: binitval=%u (BUG NOT REPRODUCED)\n", cmd.binitval); } int main(int argc, char *argv[]) { uint8_t tstamp[8]; memset(tstamp, 0xAB, sizeof tstamp); /* control: a normal AP (bintval=100) must not fault */ iwn_set_timing_model(100, tstamp); printf("control (ni_intval=100) OK\n"); /* malicious AP: beacon carried bintval == 0 */ iwn_set_timing_model(0, tstamp); printf("UNEXPECTED: survived ni_intval=0\n"); exit(1); } |