xref: /netbsd-src/external/bsd/libfido2/dist/src/hid_netbsd.c (revision d95e80469e30db18c45f7e823e0cbb29a751594f)
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