DF-2334 / uaudio_rig.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 | /* * uaudio_rig.c -- driver for the VERBATIM uaudio20_mixer_verify_desc() * (uaudio.c:3935-4085) verification rig (DF-2334). * * build.sh machine-extracts the production function byte-identical from * /usr/src (sha256-gated) into uaudio_verify.inc, plus the descriptor * structs it walks (uaudioreg.h) into uaudio_structs.inc. * * DF-2334 claim under test: for a UAC2 MIXER unit descriptor with * bLength == 5 + bNrInPins (header + source IDs, no mixer_unit_1), the * read of d1->bNrChannels (uaudio.c:3988) lands at offset bLength -- * one byte past the declared descriptor body. * * Scenarios: * guard - the descriptor's last byte is the last byte of a mapped * page; the next page is PROT_NONE -> the 1-byte OOB read * faults with SIGSEGV at si_addr == page boundary. * canary - the byte at offset bLength (physically present, e.g. the * next descriptor in the walk buffer) is 0x5A. The function * reads it into `len` (visible in the rejection DPRINTF). * control - well-formed mixer (bNrChannels present in-body) -> accepted, * returns non-NULL. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <signal.h> #include <sys/mman.h> /* ---------------- kernel type shims ----------------------------------- */ typedef uint8_t uByte; typedef uint16_t uWord; typedef uint32_t uDWord; #define __packed __attribute__((__packed__)) #define kprintf printf static int uaudio_debug = 1; #define DPRINTF(fmt, ...) do { if (uaudio_debug) \ printf("UAUDIO: " fmt, ##__VA_ARGS__); } while (0) /* usb.h:36-ish (generic descriptor header) */ struct usb_descriptor { uByte bLength; uByte bDescriptorType; uByte bDescriptorSubtype; } __packed; #define UDESC_CS_INTERFACE 0x24 /* ---------------- VERBATIM descriptor structs (uaudioreg.h) ----------- */ #include "uaudio_structs.inc" /* ---------------- VERBATIM production function (static, no clash) ----- */ #include "uaudio_verify.inc" /* ---------------- scenarios ------------------------------------------- */ static void segv_handler(int sig, siginfo_t *si, void *uc) { (void)uc; printf("SCENARIO guard: SIGSEGV si_addr=%p (1-byte OOB read of " "d1->bNrChannels past bLength hit the guard page)\n", si->si_addr); fflush(stdout); _exit(91); } static long pagesz; int main(int argc, char **argv) { const char *scen = argc == 2 ? argv[1] : ""; uint8_t *page, *desc; int npins = 3; /* bLength will be 5+3 = 8 */ struct sigaction sa; pagesz = sysconf(_SC_PAGESIZE); if (strcmp(scen, "guard") == 0 || strcmp(scen, "canary") == 0 || strcmp(scen, "control") == 0) { page = mmap(NULL, 2 * pagesz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (page == MAP_FAILED) { perror("mmap"); return 2; } if (mprotect(page + pagesz, pagesz, PROT_NONE) != 0) { perror("mprotect"); return 2; } memset(page, 0x00, pagesz); if (strcmp(scen, "control") == 0) { /* well-formed: bLength = len(1) + 5 + npins + 6 + 2ch */ desc = page + pagesz - (5 + npins + 12); desc[0] = 5 + npins + 12; /* == 20 */ } else if (strcmp(scen, "canary") == 0) { /* crafted bLength == 5 + npins, with the OOB byte * physically present IN-Page (last mapped byte) */ desc = page + pagesz - (5 + npins + 1); desc[0] = 5 + npins; } else { /* crafted: bLength == 5 + npins exactly */ desc = page + pagesz - (5 + npins); desc[0] = 5 + npins; } desc[1] = UDESC_CS_INTERFACE; desc[2] = 0x04; /* UDESCSUB_AC_MIXER */ desc[3] = 0x10; /* bUnitId */ desc[4] = npins; /* bNrInPins */ desc[5] = 0x01; /* baSourceId[0] */ desc[6] = 0x02; desc[7] = 0x03; if (strcmp(scen, "control") == 0) { desc[8] = 2; /* bNrChannels (in body) */ /* bmChannelConfig + iChannelNames + bmControls */ memset(desc + 9, 0, 7); } if (strcmp(scen, "canary") == 0) { /* byte physically present just past the declared * body (the next descriptor's bLength in the walk * buffer): a marked value */ desc[5 + npins] = 0x5A; } printf("SCENARIO %s: desc at page+%zd, bLength=%u, " "bNrInPins=%u, d1 lands at offset %d (== bLength)\n", scen, (ssize_t)(desc - page), desc[0], desc[4], 5 + npins); if (strcmp(scen, "guard") == 0) { memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = segv_handler; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); printf("SCENARIO guard: byte at offset %d is the " "first byte of the PROT_NONE page -> expect " "SIGSEGV now\n", 5 + npins); fflush(stdout); } { int vlen = strcmp(scen, "control") == 0 ? 1 : 0; const void *r = uaudio20_mixer_verify_desc(desc, vlen); printf("SCENARIO %s: returned %p (%s)\n", scen, r, r != NULL ? "ACCEPTED" : "rejected"); if (strcmp(scen, "canary") == 0 && r == NULL) printf("SCENARIO canary: rejection DPRINTF " "above shows the OOB-read 0x5A inside " "'len=%d of %u bytes' -- the byte past " "the declared body was consumed\n", 5 + npins + 6 + 0x5A, desc[0]); } return 0; } fprintf(stderr, "usage: %s guard|canary|control\n", argv[0]); return 2; } |