Path: blob/main/contrib/libfido2/src/hid_netbsd.c
106125 views
/*1* Copyright (c) 2020 Yubico AB. All rights reserved.2* Use of this source code is governed by a BSD-style3* license that can be found in the LICENSE file.4* SPDX-License-Identifier: BSD-2-Clause5*/67#include <sys/types.h>8#include <sys/ioctl.h>910#include <dev/usb/usb.h>11#include <dev/usb/usbhid.h>1213#include <errno.h>14#include <poll.h>15#include <signal.h>16#include <stdbool.h>17#include <stdio.h>18#include <stdlib.h>19#include <string.h>20#include <unistd.h>2122#include "fido.h"2324#define MAX_UHID 642526struct hid_netbsd {27int fd;28size_t report_in_len;29size_t report_out_len;30sigset_t sigmask;31const sigset_t *sigmaskp;32};3334/* Hack to make this work with newer kernels even if /usr/include is old. */35#if __NetBSD_Version__ < 901000000 /* 9.1 */36#define USB_HID_GET_RAW _IOR('h', 1, int)37#define USB_HID_SET_RAW _IOW('h', 2, int)38#endif3940static bool41is_fido(int fd)42{43struct usb_ctl_report_desc ucrd;44uint32_t usage_page = 0;45int raw = 1;4647memset(&ucrd, 0, sizeof(ucrd));4849if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd) == -1) {50fido_log_error(errno, "%s: ioctl", __func__);51return (false);52}5354if (ucrd.ucrd_size < 0 ||55(size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||56fido_hid_get_usage(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,57&usage_page) < 0) {58fido_log_debug("%s: fido_hid_get_usage", __func__);59return (false);60}6162if (usage_page != 0xf1d0)63return (false);6465/*66* This step is not strictly necessary -- NetBSD puts fido67* devices into raw mode automatically by default, but in68* principle that might change, and this serves as a test to69* verify that we're running on a kernel with support for raw70* mode at all so we don't get confused issuing writes that try71* to set the report descriptor rather than transfer data on72* the output interrupt pipe as we need.73*/74if (ioctl(fd, IOCTL_REQ(USB_HID_SET_RAW), &raw) == -1) {75fido_log_error(errno, "%s: unable to set raw", __func__);76return (false);77}7879return (true);80}8182static int83copy_info(fido_dev_info_t *di, const char *path)84{85int fd = -1;86int ok = -1;87struct usb_device_info udi;8889memset(di, 0, sizeof(*di));90memset(&udi, 0, sizeof(udi));9192if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)93goto fail;9495if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {96fido_log_error(errno, "%s: ioctl", __func__);97goto fail;98}99100if ((di->path = strdup(path)) == NULL ||101(di->manufacturer = strdup(udi.udi_vendor)) == NULL ||102(di->product = strdup(udi.udi_product)) == NULL)103goto fail;104105di->vendor_id = (int16_t)udi.udi_vendorNo;106di->product_id = (int16_t)udi.udi_productNo;107108ok = 0;109fail:110if (fd != -1 && close(fd) == -1)111fido_log_error(errno, "%s: close", __func__);112113if (ok < 0) {114free(di->path);115free(di->manufacturer);116free(di->product);117explicit_bzero(di, sizeof(*di));118}119120return (ok);121}122123int124fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)125{126char path[64];127size_t i;128129*olen = 0;130131if (ilen == 0)132return (FIDO_OK); /* nothing to do */133134if (devlist == NULL || olen == NULL)135return (FIDO_ERR_INVALID_ARGUMENT);136137for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {138snprintf(path, sizeof(path), "/dev/uhid%zu", i);139if (copy_info(&devlist[*olen], path) == 0) {140devlist[*olen].io = (fido_dev_io_t) {141fido_hid_open,142fido_hid_close,143fido_hid_read,144fido_hid_write,145};146++(*olen);147}148}149150return (FIDO_OK);151}152153/*154* Workaround for NetBSD (as of 201910) bug that loses155* sync of DATA0/DATA1 sequence bit across uhid open/close.156* Send pings until we get a response - early pings with incorrect157* sequence bits will be ignored as duplicate packets by the device.158*/159static int160terrible_ping_kludge(struct hid_netbsd *ctx)161{162u_char data[256];163int i, n;164struct pollfd pfd;165166if (sizeof(data) < ctx->report_out_len + 1)167return -1;168for (i = 0; i < 4; i++) {169memset(data, 0, sizeof(data));170/* broadcast channel ID */171data[1] = 0xff;172data[2] = 0xff;173data[3] = 0xff;174data[4] = 0xff;175/* Ping command */176data[5] = 0x81;177/* One byte ping only, Vasili */178data[6] = 0;179data[7] = 1;180fido_log_debug("%s: send ping %d", __func__, i);181if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1)182return -1;183fido_log_debug("%s: wait reply", __func__);184memset(&pfd, 0, sizeof(pfd));185pfd.fd = ctx->fd;186pfd.events = POLLIN;187if ((n = poll(&pfd, 1, 100)) == -1) {188fido_log_error(errno, "%s: poll", __func__);189return -1;190} else if (n == 0) {191fido_log_debug("%s: timed out", __func__);192continue;193}194if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1)195return -1;196/*197* Ping isn't always supported on the broadcast channel,198* so we might get an error, but we don't care - we're199* synched now.200*/201fido_log_xxd(data, ctx->report_out_len, "%s: got reply",202__func__);203return 0;204}205fido_log_debug("%s: no response", __func__);206return -1;207}208209void *210fido_hid_open(const char *path)211{212struct hid_netbsd *ctx;213struct usb_ctl_report_desc ucrd;214int r;215216memset(&ucrd, 0, sizeof(ucrd));217218if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||219(ctx->fd = fido_hid_unix_open(path)) == -1) {220free(ctx);221return (NULL);222}223224if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd)) == -1 ||225ucrd.ucrd_size < 0 ||226(size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||227fido_hid_get_report_len(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,228&ctx->report_in_len, &ctx->report_out_len) < 0) {229if (r == -1)230fido_log_error(errno, "%s: ioctl", __func__);231fido_log_debug("%s: using default report sizes", __func__);232ctx->report_in_len = CTAP_MAX_REPORT_LEN;233ctx->report_out_len = CTAP_MAX_REPORT_LEN;234}235236/*237* NetBSD has a bug that causes it to lose238* track of the DATA0/DATA1 sequence toggle across uhid device239* open and close. This is a terrible hack to work around it.240*/241if (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) {242fido_hid_close(ctx);243return NULL;244}245246return (ctx);247}248249void250fido_hid_close(void *handle)251{252struct hid_netbsd *ctx = handle;253254if (close(ctx->fd) == -1)255fido_log_error(errno, "%s: close", __func__);256257free(ctx);258}259260int261fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)262{263struct hid_netbsd *ctx = handle;264265ctx->sigmask = *sigmask;266ctx->sigmaskp = &ctx->sigmask;267268return (FIDO_OK);269}270271int272fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)273{274struct hid_netbsd *ctx = handle;275ssize_t r;276277if (len != ctx->report_in_len) {278fido_log_debug("%s: len %zu", __func__, len);279return (-1);280}281282if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {283fido_log_debug("%s: fd not ready", __func__);284return (-1);285}286287if ((r = read(ctx->fd, buf, len)) == -1) {288fido_log_error(errno, "%s: read", __func__);289return (-1);290}291292if (r < 0 || (size_t)r != len) {293fido_log_error(errno, "%s: %zd != %zu", __func__, r, len);294return (-1);295}296297return ((int)r);298}299300int301fido_hid_write(void *handle, const unsigned char *buf, size_t len)302{303struct hid_netbsd *ctx = handle;304ssize_t r;305306if (len != ctx->report_out_len + 1) {307fido_log_debug("%s: len %zu", __func__, len);308return (-1);309}310311if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {312fido_log_error(errno, "%s: write", __func__);313return (-1);314}315316if (r < 0 || (size_t)r != len - 1) {317fido_log_error(errno, "%s: %zd != %zu", __func__, r, len - 1);318return (-1);319}320321return ((int)len);322}323324size_t325fido_hid_report_in_len(void *handle)326{327struct hid_netbsd *ctx = handle;328329return (ctx->report_in_len);330}331332size_t333fido_hid_report_out_len(void *handle)334{335struct hid_netbsd *ctx = handle;336337return (ctx->report_out_len);338}339340341