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