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 <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>
21
22 #include "fido.h"
23
24 #define MAX_UHID 64
25
26 struct hid_netbsd {
27 int fd;
28 size_t report_in_len;
29 size_t report_out_len;
30 sigset_t sigmask;
31 const sigset_t *sigmaskp;
32 };
33
34 /* 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 #endif
39
40 static bool
is_fido(int fd)41 is_fido(int fd)
42 {
43 struct usb_ctl_report_desc ucrd;
44 uint32_t usage_page = 0;
45 int raw = 1;
46
47 memset(&ucrd, 0, sizeof(ucrd));
48
49 if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd) == -1) {
50 fido_log_error(errno, "%s: ioctl", __func__);
51 return (false);
52 }
53
54 if (ucrd.ucrd_size < 0 ||
55 (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
56 fido_hid_get_usage(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
57 &usage_page) < 0) {
58 fido_log_debug("%s: fido_hid_get_usage", __func__);
59 return (false);
60 }
61
62 if (usage_page != 0xf1d0)
63 return (false);
64
65 /*
66 * This step is not strictly necessary -- NetBSD puts fido
67 * devices into raw mode automatically by default, but in
68 * principle that might change, and this serves as a test to
69 * verify that we're running on a kernel with support for raw
70 * mode at all so we don't get confused issuing writes that try
71 * to set the report descriptor rather than transfer data on
72 * the output interrupt pipe as we need.
73 */
74 if (ioctl(fd, IOCTL_REQ(USB_HID_SET_RAW), &raw) == -1) {
75 fido_log_error(errno, "%s: unable to set raw", __func__);
76 return (false);
77 }
78
79 return (true);
80 }
81
82 static int
copy_info(fido_dev_info_t * di,const char * path)83 copy_info(fido_dev_info_t *di, const char *path)
84 {
85 int fd = -1;
86 int ok = -1;
87 struct usb_device_info udi;
88
89 memset(di, 0, sizeof(*di));
90 memset(&udi, 0, sizeof(udi));
91
92 if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
93 goto fail;
94
95 if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
96 fido_log_error(errno, "%s: ioctl", __func__);
97 goto fail;
98 }
99
100 if ((di->path = strdup(path)) == NULL ||
101 (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
102 (di->product = strdup(udi.udi_product)) == NULL)
103 goto fail;
104
105 di->vendor_id = (int16_t)udi.udi_vendorNo;
106 di->product_id = (int16_t)udi.udi_productNo;
107
108 ok = 0;
109 fail:
110 if (fd != -1 && close(fd) == -1)
111 fido_log_error(errno, "%s: close", __func__);
112
113 if (ok < 0) {
114 free(di->path);
115 free(di->manufacturer);
116 free(di->product);
117 explicit_bzero(di, sizeof(*di));
118 }
119
120 return (ok);
121 }
122
123 int
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)124 fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
125 {
126 char path[64];
127 size_t i;
128
129 *olen = 0;
130
131 if (ilen == 0)
132 return (FIDO_OK); /* nothing to do */
133
134 if (devlist == NULL || olen == NULL)
135 return (FIDO_ERR_INVALID_ARGUMENT);
136
137 for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
138 snprintf(path, sizeof(path), "/dev/uhid%zu", i);
139 if (copy_info(&devlist[*olen], path) == 0) {
140 devlist[*olen].io = (fido_dev_io_t) {
141 fido_hid_open,
142 fido_hid_close,
143 fido_hid_read,
144 fido_hid_write,
145 };
146 ++(*olen);
147 }
148 }
149
150 return (FIDO_OK);
151 }
152
153 /*
154 * Workaround for NetBSD (as of 201910) bug that loses
155 * sync of DATA0/DATA1 sequence bit across uhid open/close.
156 * Send pings until we get a response - early pings with incorrect
157 * sequence bits will be ignored as duplicate packets by the device.
158 */
159 static int
terrible_ping_kludge(struct hid_netbsd * ctx)160 terrible_ping_kludge(struct hid_netbsd *ctx)
161 {
162 u_char data[256];
163 int i, n;
164 struct pollfd pfd;
165
166 if (sizeof(data) < ctx->report_out_len + 1)
167 return -1;
168 for (i = 0; i < 4; i++) {
169 memset(data, 0, sizeof(data));
170 /* broadcast channel ID */
171 data[1] = 0xff;
172 data[2] = 0xff;
173 data[3] = 0xff;
174 data[4] = 0xff;
175 /* Ping command */
176 data[5] = 0x81;
177 /* One byte ping only, Vasili */
178 data[6] = 0;
179 data[7] = 1;
180 fido_log_debug("%s: send ping %d", __func__, i);
181 if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1)
182 return -1;
183 fido_log_debug("%s: wait reply", __func__);
184 memset(&pfd, 0, sizeof(pfd));
185 pfd.fd = ctx->fd;
186 pfd.events = POLLIN;
187 if ((n = poll(&pfd, 1, 100)) == -1) {
188 fido_log_error(errno, "%s: poll", __func__);
189 return -1;
190 } else if (n == 0) {
191 fido_log_debug("%s: timed out", __func__);
192 continue;
193 }
194 if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1)
195 return -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're
199 * synched now.
200 */
201 fido_log_xxd(data, ctx->report_out_len, "%s: got reply",
202 __func__);
203 return 0;
204 }
205 fido_log_debug("%s: no response", __func__);
206 return -1;
207 }
208
209 void *
fido_hid_open(const char * path)210 fido_hid_open(const char *path)
211 {
212 struct hid_netbsd *ctx;
213 struct usb_ctl_report_desc ucrd;
214 int r;
215
216 memset(&ucrd, 0, sizeof(ucrd));
217
218 if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
219 (ctx->fd = fido_hid_unix_open(path)) == -1) {
220 free(ctx);
221 return (NULL);
222 }
223
224 if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd)) == -1 ||
225 ucrd.ucrd_size < 0 ||
226 (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
227 fido_hid_get_report_len(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
228 &ctx->report_in_len, &ctx->report_out_len) < 0) {
229 if (r == -1)
230 fido_log_error(errno, "%s: ioctl", __func__);
231 fido_log_debug("%s: using default report sizes", __func__);
232 ctx->report_in_len = CTAP_MAX_REPORT_LEN;
233 ctx->report_out_len = CTAP_MAX_REPORT_LEN;
234 }
235
236 /*
237 * NetBSD has a bug that causes it to lose
238 * track of the DATA0/DATA1 sequence toggle across uhid device
239 * open and close. This is a terrible hack to work around it.
240 */
241 if (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) {
242 fido_hid_close(ctx);
243 return NULL;
244 }
245
246 return (ctx);
247 }
248
249 void
fido_hid_close(void * handle)250 fido_hid_close(void *handle)
251 {
252 struct hid_netbsd *ctx = handle;
253
254 if (close(ctx->fd) == -1)
255 fido_log_error(errno, "%s: close", __func__);
256
257 free(ctx);
258 }
259
260 int
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)261 fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
262 {
263 struct hid_netbsd *ctx = handle;
264
265 ctx->sigmask = *sigmask;
266 ctx->sigmaskp = &ctx->sigmask;
267
268 return (FIDO_OK);
269 }
270
271 int
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)272 fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
273 {
274 struct hid_netbsd *ctx = handle;
275 ssize_t r;
276
277 if (len != ctx->report_in_len) {
278 fido_log_debug("%s: len %zu", __func__, len);
279 return (-1);
280 }
281
282 if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
283 fido_log_debug("%s: fd not ready", __func__);
284 return (-1);
285 }
286
287 if ((r = read(ctx->fd, buf, len)) == -1) {
288 fido_log_error(errno, "%s: read", __func__);
289 return (-1);
290 }
291
292 if (r < 0 || (size_t)r != len) {
293 fido_log_error(errno, "%s: %zd != %zu", __func__, r, len);
294 return (-1);
295 }
296
297 return ((int)r);
298 }
299
300 int
fido_hid_write(void * handle,const unsigned char * buf,size_t len)301 fido_hid_write(void *handle, const unsigned char *buf, size_t len)
302 {
303 struct hid_netbsd *ctx = handle;
304 ssize_t r;
305
306 if (len != ctx->report_out_len + 1) {
307 fido_log_debug("%s: len %zu", __func__, len);
308 return (-1);
309 }
310
311 if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
312 fido_log_error(errno, "%s: write", __func__);
313 return (-1);
314 }
315
316 if (r < 0 || (size_t)r != len - 1) {
317 fido_log_error(errno, "%s: %zd != %zu", __func__, r, len - 1);
318 return (-1);
319 }
320
321 return ((int)len);
322 }
323
324 size_t
fido_hid_report_in_len(void * handle)325 fido_hid_report_in_len(void *handle)
326 {
327 struct hid_netbsd *ctx = handle;
328
329 return (ctx->report_in_len);
330 }
331
332 size_t
fido_hid_report_out_len(void * handle)333 fido_hid_report_out_len(void *handle)
334 {
335 struct hid_netbsd *ctx = handle;
336
337 return (ctx->report_out_len);
338 }
339