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