DF-0291 / overflow_proof.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 | /* * overflow_proof.c -- code-level proof for DF-0291 * * STATUS: This is a USERSPACE MODEL of the vulnerable kernel code path in * sys/netproto/802_11/wlan/ieee80211_ioctl.c, NOT an in-kernel trigger. * * WHY A MODEL: The vulnerable SIOCS80211 / SIOCG80211 ioctls are only wired to * *wlan vap* interfaces (ieee80211.c:567 ifp->if_ioctl = ieee80211_ioctl). * A wlan vap can only be created through wlan_clone_create() * (ieee80211_dragonfly.c:80), which at :92 does: * ic = ieee80211_find_com(cp.icp_parent); * if (ic == NULL) return ENXIO; // <-- needs a RADIO parent * The KVM audit guest has NO wifi radio hardware and no attached radio * driver (only vtnet0/lo0 exist), so ieee80211_find_com() always returns NULL * and no vap can ever exist -> the ioctls are unreachable from userspace on * THIS guest. (Verified: see env.txt -- ifconfig wlan create wlandev <any> * -> "SIOCIFCREATE2: Device not configured" for every radio name.) * * The overflow is, however, REAL in the shipped kernel source. This program * reproduces the exact vulnerable logic (the missing bounds check) against * buffers laid out exactly like the kernel structs, so the byte counts and * the corruption are concrete and undeniable. Compile & run on the guest. * * References (audited master DEV tree): * setmlme_assoc_adhoc ieee80211_ioctl.c:1568-1608 * missing >IEEE80211_NWID_LEN check at :1580 (only checks ssid_len == 0) * heap overflow #1 into iv_des_ssid[0].ssid[32] at :1595 * heap overflow #2 into sr->sr_ssid[0].ssid[32] at :1600 * sibling IOC_SSID SET handler :2671-2683 (correctly bounds ireq->i_len at :2673) * GET IOC_SSID handler :805-818 * stack overflow into char tmpssid[32] at :810 (uses corrupted iv_des_ssid[0].len) * struct ieee80211req_mlme ieee80211_ioctl.h:301-313 (im_ssid_len is uint8_t -> max 255) * iv_des_ssid ieee80211_var.h:400 (struct ieee80211_scan_ssid iv_des_ssid[1]) * struct ieee80211_scan_req ieee80211_ioctl.h:768-791 (sr_ssid[3], each ssid[32]) * * Build: cc -O2 -Wall -o overflow_proof overflow_proof.c */ #include <stdio.h> #include <string.h> #include <stdlib.h> #define IEEE80211_NWID_LEN 32 /* sys/netproto/802_11/_ieee80211.h */ #define MAX_ATTACKER_LEN 255 /* im_ssid_len is uint8_t */ /* ---- struct models that matter (field SIZES, the kernel's real layout) ---- */ struct ieee80211_scan_ssid { /* ieee80211_scan.h:76 */ int len; unsigned char ssid[IEEE80211_NWID_LEN]; }; /* model of struct ieee80211vap region around iv_des_ssid[0] */ struct vap_model { char pad_before[64]; struct ieee80211_scan_ssid iv_des_ssid[1]; /* ieee80211_var.h:400 (array of 1) */ unsigned char heap_guard[256]; /* adjacent vap fields / next slab object */ }; /* model of struct ieee80211_scan_req */ struct scan_req_model { int sr_flags; struct { int len; unsigned char ssid[IEEE80211_NWID_LEN]; } sr_ssid[3]; /* ieee80211_ioctl.h:790 sr_ssid[3] */ unsigned char heap_guard[256]; /* past end of kmalloc'd scan_req */ }; #define CANARY 0xCD static void fill_canary(unsigned char *p, size_t n) { memset(p, CANARY, n); } static int first_clobbered(const unsigned char *p, size_t n) { for (size_t i = 0; i < n; i++) if (p[i] != CANARY) return (int)i; return -1; } /* * Mirror of setmlme_assoc_adhoc (ieee80211_ioctl.c:1568). The ONLY guard is * the zero-length check at :1580; there is NO ssid_len > IEEE80211_NWID_LEN * check (the sibling IOC_SSID SET handler HAS one at :2673). */ static void setmlme_assoc_adhoc_model(struct vap_model *vap, struct scan_req_model *sr, int ssid_len, const unsigned char *ssid) { if (ssid_len == 0) { /* :1580 */ printf(" setmlme_assoc_adhoc: ssid_len==0 -> EINVAL (return)\n"); return; } /* :1593-1595 */ memset(vap->iv_des_ssid[0].ssid, 0, IEEE80211_NWID_LEN); vap->iv_des_ssid[0].len = ssid_len; memcpy(vap->iv_des_ssid[0].ssid, ssid, ssid_len); /* HEAP OVERFLOW #1 (:1595) */ /* :1600-1601 */ memcpy(sr->sr_ssid[0].ssid, ssid, ssid_len); /* HEAP OVERFLOW #2 (:1600) */ sr->sr_ssid[0].len = ssid_len; } /* * Mirror of the GET IOC_SSID INIT/SCAN branch (ieee80211_ioctl.c:805-817). * Models the kernel stack frame: char tmpssid[32] (:801) followed by whatever * lives above it on the stack (return addr, saved regs, locals). We over-size * the allocation so the demo does not crash, but the LOGICAL 32-byte boundary * and the byte count past it are exactly what the kernel experiences. */ static void get_ssid_model(int corrupted_len, const unsigned char *src) { unsigned char *frame = calloc(1, 512); /* oversized so the demo won't crash */ char *tmpssid = (char *)frame; /* :801 char tmpssid[IEEE80211_NWID_LEN]; */ unsigned char *stack_above = frame + IEEE80211_NWID_LEN; fill_canary(stack_above, 512 - IEEE80211_NWID_LEN); /* :809-810 */ /* (ireq->i_len = vap->iv_des_ssid[0].len;) */ memcpy(tmpssid, src, corrupted_len); /* STACK OVERFLOW (:810) */ int past = corrupted_len - IEEE80211_NWID_LEN; int at = first_clobbered(stack_above, 512 - IEEE80211_NWID_LEN); printf(" GET IOC_SSID: memcpy %d bytes into 32-byte stack tmpssid -> " "%d bytes written past end (first clobber @ stack+%d)\n", corrupted_len, past > 0 ? past : 0, at); free(frame); } int main(void) { struct vap_model *vap = calloc(1, sizeof(*vap)); struct scan_req_model *sr = calloc(1, sizeof(*sr)); unsigned char attacker[MAX_ATTACKER_LEN]; printf("=== DF-0291 code-level overflow proof (userspace model) ===\n"); printf("IEEE80211_NWID_LEN = %d ; attacker im_ssid_len (uint8_t max) = %d\n", IEEE80211_NWID_LEN, MAX_ATTACKER_LEN); printf("\n"); /* attacker-controlled payload (in the kernel the first 32 bytes are the * user-supplied im_ssid, bytes 33..255 are kernel-stack residue because * mlme.im_ssid is the LAST field of struct ieee80211req_mlme and the * source pointer is &mlme.im_ssid). */ memset(attacker, 0x41, sizeof(attacker)); fill_canary(vap->heap_guard, sizeof(vap->heap_guard)); fill_canary(sr->heap_guard, sizeof(sr->heap_guard)); printf("[1] SET-path: SIOCS80211 -> setmlme_assoc_adhoc, im_ssid_len = %d\n", MAX_ATTACKER_LEN); setmlme_assoc_adhoc_model(vap, sr, MAX_ATTACKER_LEN, attacker); int g1 = first_clobbered(vap->heap_guard, sizeof(vap->heap_guard)); int g2 = first_clobbered(sr->heap_guard, sizeof(sr->heap_guard)); int o1 = MAX_ATTACKER_LEN - IEEE80211_NWID_LEN; printf(" HEAP OVERFLOW #1 (iv_des_ssid[0].ssid, :1595): %d bytes past the " "32-byte field into ieee80211vap heap (guard first clobbered @+%d)\n", o1, g1); printf(" HEAP OVERFLOW #2 (sr->sr_ssid[0].ssid, :1600): %d bytes past the " "32-byte field; spilled into sr_ssid[1]/sr_ssid[2] and beyond " "(post-struct guard first clobbered @+%d)\n", o1, g2); printf("\n"); printf("[2] GET-path amplification: SIOCG80211 IOC_SSID reads the corrupted " "iv_des_ssid[0].len = %d\n", vap->iv_des_ssid[0].len); get_ssid_model(vap->iv_des_ssid[0].len, vap->iv_des_ssid[0].ssid); printf("\n"); printf("[3] Contrast -- sibling IOC_SSID SET handler (:2671) DOES guard:\n"); printf(" if (ireq->i_val != 0 || ireq->i_len > IEEE80211_NWID_LEN) return EINVAL;\n"); printf(" setmlme_assoc_adhoc (:1580) has ONLY: if (ssid_len == 0) return EINVAL;\n"); printf(" -> the adhoc path is the ONLY unguarded sink.\n"); printf("\n"); printf("CONCLUSION: the overflow logic is real (up to %d bytes past a 32-byte\n" "buffer on BOTH the heap set-path and the stack get-path). On the KVM\n" "audit guest it is UNREACHABLE from userspace because no wlan vap can\n" "be created without a wifi radio parent (see env.txt / VERDICT.md).\n", o1); free(vap); free(sr); return 0; } |