1 /* 2 * Copyright (c) 2020 Yubico AB. All rights reserved. 3 * Use of this source code is governed by a BSD-style 4 * license that can be found in the LICENSE file. 5 */ 6 7 #include <sys/types.h> 8 #include <sys/ioctl.h> 9 10 #include <dev/usb/usb.h> 11 #include <dev/usb/usbhid.h> 12 13 #include <errno.h> 14 #include <fcntl.h> 15 #include <poll.h> 16 #include <stdbool.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 #include <unistd.h> 21 #include <usbhid.h> 22 23 #include "fido.h" 24 25 #define MAX_UHID 64 26 27 struct hid_netbsd { 28 int fd; 29 size_t report_in_len; 30 size_t report_out_len; 31 sigset_t sigmask; 32 const sigset_t *sigmaskp; 33 }; 34 35 /* Hack to make this work with newer kernels even if /usr/include is old. */ 36 #if __NetBSD_Version__ < 901000000 /* 9.1 */ 37 #define USB_HID_GET_RAW _IOR('h', 1, int) 38 #define USB_HID_SET_RAW _IOW('h', 2, int) 39 #endif 40 41 static bool 42 is_fido(int fd) 43 { 44 report_desc_t rdesc; 45 hid_data_t hdata; 46 hid_item_t hitem; 47 bool isfido; 48 int raw = 1; 49 50 if ((rdesc = hid_get_report_desc(fd)) == NULL) { 51 fido_log_debug("%s: failed to get report descriptor", 52 __func__); 53 return (false); 54 } 55 if ((hdata = hid_start_parse(rdesc, 1 << hid_collection, -1)) 56 == NULL) { 57 fido_log_debug("%s: failed to parse report descriptor", 58 __func__); 59 hid_dispose_report_desc(rdesc); 60 return (false); 61 } 62 isfido = false; 63 while ((hid_get_item(hdata, &hitem)) > 0) { 64 if (HID_PAGE(hitem.usage) == 0xf1d0) { 65 isfido = true; 66 break; 67 } 68 } 69 hid_end_parse(hdata); 70 hid_dispose_report_desc(rdesc); 71 if (!isfido) 72 return (false); 73 74 /* 75 * This step is not strictly necessary -- NetBSD puts fido 76 * devices into raw mode automatically by default, but in 77 * principle that might change, and this serves as a test to 78 * verify that we're running on a kernel with support for raw 79 * mode at all so we don't get confused issuing writes that try 80 * to set the report descriptor rather than transfer data on 81 * the output interrupt pipe as we need. 82 */ 83 if (ioctl(fd, USB_HID_SET_RAW, &raw) == -1) { 84 fido_log_debug("%s: unable to set raw", __func__); 85 return (false); 86 } 87 88 return (true); 89 } 90 91 static int 92 copy_info(fido_dev_info_t *di, const char *path) 93 { 94 int fd = -1; 95 int ok = -1; 96 struct usb_device_info udi; 97 98 memset(di, 0, sizeof(*di)); 99 memset(&udi, 0, sizeof(udi)); 100 101 if ((fd = open(path, O_RDWR)) == -1) { 102 if (errno != EBUSY && errno != ENOENT) 103 fido_log_debug("%s: open %s: %s", __func__, path, 104 strerror(errno)); 105 goto fail; 106 } 107 if (!is_fido(fd)) 108 goto fail; 109 110 if (ioctl(fd, USB_GET_DEVICEINFO, &udi) == -1) 111 goto fail; 112 113 if ((di->path = strdup(path)) == NULL || 114 (di->manufacturer = strdup(udi.udi_vendor)) == NULL || 115 (di->product = strdup(udi.udi_product)) == NULL) 116 goto fail; 117 118 di->vendor_id = (int16_t)udi.udi_vendorNo; 119 di->product_id = (int16_t)udi.udi_productNo; 120 121 ok = 0; 122 fail: 123 if (fd != -1) 124 close(fd); 125 126 if (ok < 0) { 127 free(di->path); 128 free(di->manufacturer); 129 free(di->product); 130 explicit_bzero(di, sizeof(*di)); 131 } 132 133 return (ok); 134 } 135 136 int 137 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) 138 { 139 char path[64]; 140 size_t i; 141 142 *olen = 0; 143 144 if (ilen == 0) 145 return (FIDO_OK); /* nothing to do */ 146 147 if (devlist == NULL || olen == NULL) 148 return (FIDO_ERR_INVALID_ARGUMENT); 149 150 for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) { 151 snprintf(path, sizeof(path), "/dev/uhid%zu", i); 152 if (copy_info(&devlist[*olen], path) == 0) { 153 devlist[*olen].io = (fido_dev_io_t) { 154 fido_hid_open, 155 fido_hid_close, 156 fido_hid_read, 157 fido_hid_write, 158 }; 159 ++(*olen); 160 } 161 } 162 163 return (FIDO_OK); 164 } 165 166 /* 167 * Workaround for NetBSD (as of 201910) bug that loses 168 * sync of DATA0/DATA1 sequence bit across uhid open/close. 169 * Send pings until we get a response - early pings with incorrect 170 * sequence bits will be ignored as duplicate packets by the device. 171 */ 172 static int 173 terrible_ping_kludge(struct hid_netbsd *ctx) 174 { 175 u_char data[256]; 176 int i, n; 177 struct pollfd pfd; 178 179 if (sizeof(data) < ctx->report_out_len + 1) 180 return -1; 181 for (i = 0; i < 4; i++) { 182 memset(data, 0, sizeof(data)); 183 /* broadcast channel ID */ 184 data[1] = 0xff; 185 data[2] = 0xff; 186 data[3] = 0xff; 187 data[4] = 0xff; 188 /* Ping command */ 189 data[5] = 0x81; 190 /* One byte ping only, Vasili */ 191 data[6] = 0; 192 data[7] = 1; 193 fido_log_debug("%s: send ping %d", __func__, i); 194 if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1) 195 return -1; 196 fido_log_debug("%s: wait reply", __func__); 197 memset(&pfd, 0, sizeof(pfd)); 198 pfd.fd = ctx->fd; 199 pfd.events = POLLIN; 200 if ((n = poll(&pfd, 1, 100)) == -1) { 201 fido_log_debug("%s: poll: %d", __func__, errno); 202 return -1; 203 } else if (n == 0) { 204 fido_log_debug("%s: timed out", __func__); 205 continue; 206 } 207 if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1) 208 return -1; 209 /* 210 * Ping isn't always supported on the broadcast channel, 211 * so we might get an error, but we don't care - we're 212 * synched now. 213 */ 214 fido_log_xxd(data, ctx->report_out_len, "%s: got reply", 215 __func__); 216 return 0; 217 } 218 fido_log_debug("%s: no response", __func__); 219 return -1; 220 } 221 222 void * 223 fido_hid_open(const char *path) 224 { 225 struct hid_netbsd *ctx; 226 report_desc_t rdesc = NULL; 227 hid_data_t hdata; 228 int len, report_id = 0; 229 230 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) 231 goto fail0; 232 if ((ctx->fd = open(path, O_RDWR)) == -1) 233 goto fail1; 234 if (ioctl(ctx->fd, USB_GET_REPORT_ID, &report_id) == -1) { 235 fido_log_debug("%s: failed to get report ID: %s", __func__, 236 strerror(errno)); 237 goto fail2; 238 } 239 if ((rdesc = hid_get_report_desc(ctx->fd)) == NULL) { 240 fido_log_debug("%s: failed to get report descriptor", 241 __func__); 242 goto fail2; 243 } 244 if ((hdata = hid_start_parse(rdesc, 1 << hid_collection, -1)) 245 == NULL) { 246 fido_log_debug("%s: failed to parse report descriptor", 247 __func__); 248 goto fail3; 249 } 250 if ((len = hid_report_size(rdesc, hid_input, report_id)) <= 0 || 251 (size_t)len > CTAP_MAX_REPORT_LEN) { 252 fido_log_debug("%s: bad input report size %d", __func__, len); 253 goto fail3; 254 } 255 ctx->report_in_len = (size_t)len; 256 if ((len = hid_report_size(rdesc, hid_output, report_id)) <= 0 || 257 (size_t)len > CTAP_MAX_REPORT_LEN) { 258 fido_log_debug("%s: bad output report size %d", __func__, len); 259 goto fail3; 260 } 261 ctx->report_out_len = (size_t)len; 262 hid_dispose_report_desc(rdesc); 263 264 /* 265 * NetBSD has a bug that causes it to lose 266 * track of the DATA0/DATA1 sequence toggle across uhid device 267 * open and close. This is a terrible hack to work around it. 268 */ 269 if (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) 270 goto fail2; 271 272 return (ctx); 273 274 fail3: hid_dispose_report_desc(rdesc); 275 fail2: close(ctx->fd); 276 fail1: free(ctx); 277 fail0: return (NULL); 278 } 279 280 void 281 fido_hid_close(void *handle) 282 { 283 struct hid_netbsd *ctx = handle; 284 285 close(ctx->fd); 286 free(ctx); 287 } 288 289 static void 290 xstrerror(int errnum, char *buf, size_t len) 291 { 292 if (len < 1) 293 return; 294 295 memset(buf, 0, len); 296 297 if (strerror_r(errnum, buf, len - 1) != 0) 298 snprintf(buf, len - 1, "error %d", errnum); 299 } 300 301 static int 302 timespec_to_ms(const struct timespec *ts, int upper_bound) 303 { 304 int64_t x; 305 int64_t y; 306 307 if (ts->tv_sec < 0 || (uint64_t)ts->tv_sec > INT64_MAX / 1000LL || 308 ts->tv_nsec < 0 || (uint64_t)ts->tv_nsec / 1000000LL > INT64_MAX) 309 return (upper_bound); 310 311 x = ts->tv_sec * 1000LL; 312 y = ts->tv_nsec / 1000000LL; 313 314 if (INT64_MAX - x < y || x + y > upper_bound) 315 return (upper_bound); 316 317 return (int)(x + y); 318 } 319 320 int 321 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) 322 { 323 struct hid_netbsd *ctx = handle; 324 325 ctx->sigmask = *sigmask; 326 ctx->sigmaskp = &ctx->sigmask; 327 328 return (FIDO_OK); 329 } 330 331 int 332 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 333 { 334 struct hid_netbsd *ctx = handle; 335 ssize_t r; 336 337 if (len != ctx->report_in_len) { 338 fido_log_debug("%s: len %zu", __func__, len); 339 return (-1); 340 } 341 342 if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { 343 fido_log_debug("%s: fd not ready", __func__); 344 return (-1); 345 } 346 347 if ((r = read(ctx->fd, buf, len)) == -1 || (size_t)r != len) { 348 fido_log_debug("%s: read", __func__); 349 return (-1); 350 } 351 352 return ((int)r); 353 } 354 355 int 356 fido_hid_write(void *handle, const unsigned char *buf, size_t len) 357 { 358 struct hid_netbsd *ctx = handle; 359 ssize_t r; 360 361 if (len != ctx->report_out_len + 1) { 362 fido_log_debug("%s: len %zu", __func__, len); 363 return (-1); 364 } 365 366 if ((r = write(ctx->fd, buf + 1, len - 1)) == -1 || 367 (size_t)r != len - 1) { 368 fido_log_debug("%s: write", __func__); 369 return (-1); 370 } 371 372 return ((int)len); 373 } 374 375 size_t 376 fido_hid_report_in_len(void *handle) 377 { 378 struct hid_netbsd *ctx = handle; 379 380 return (ctx->report_in_len); 381 } 382 383 size_t 384 fido_hid_report_out_len(void *handle) 385 { 386 struct hid_netbsd *ctx = handle; 387 388 return (ctx->report_out_len); 389 } 390