xref: /netbsd-src/external/bsd/libfido2/dist/src/hid_linux.c (revision 2d40c4512a84c0d064ec30a492c5e2a14d230bc3)
1ba9bdd8bSchristos /*
2*2d40c451Schristos  * Copyright (c) 2019-2022 Yubico AB. All rights reserved.
3ba9bdd8bSchristos  * Use of this source code is governed by a BSD-style
4ba9bdd8bSchristos  * license that can be found in the LICENSE file.
5*2d40c451Schristos  * SPDX-License-Identifier: BSD-2-Clause
6ba9bdd8bSchristos  */
7ba9bdd8bSchristos 
8ba9bdd8bSchristos #include <sys/types.h>
9ede6d7f8Schristos #include <sys/file.h>
10ba9bdd8bSchristos #include <sys/ioctl.h>
11ede6d7f8Schristos 
12ba9bdd8bSchristos #include <linux/hidraw.h>
131fc1e710Schristos #include <linux/input.h>
14ba9bdd8bSchristos 
151fc1e710Schristos #include <errno.h>
16ba9bdd8bSchristos #include <libudev.h>
17ede6d7f8Schristos #include <time.h>
18ba9bdd8bSchristos #include <unistd.h>
19ba9bdd8bSchristos 
20ba9bdd8bSchristos #include "fido.h"
21ba9bdd8bSchristos 
221fc1e710Schristos struct hid_linux {
231fc1e710Schristos 	int             fd;
241fc1e710Schristos 	size_t          report_in_len;
251fc1e710Schristos 	size_t          report_out_len;
2695dbdf32Schristos 	sigset_t        sigmask;
2795dbdf32Schristos 	const sigset_t *sigmaskp;
281fc1e710Schristos };
29ba9bdd8bSchristos 
30ba9bdd8bSchristos static int
get_report_descriptor(int fd,struct hidraw_report_descriptor * hrd)311fc1e710Schristos get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
321fc1e710Schristos {
331fc1e710Schristos 	int s = -1;
341fc1e710Schristos 
3595dbdf32Schristos 	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) == -1) {
3695dbdf32Schristos 		fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
3795dbdf32Schristos 		return (-1);
3895dbdf32Schristos 	}
3995dbdf32Schristos 
4095dbdf32Schristos 	if (s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
4195dbdf32Schristos 		fido_log_debug("%s: HIDIOCGRDESCSIZE %d", __func__, s);
421fc1e710Schristos 		return (-1);
43ba9bdd8bSchristos 	}
44ba9bdd8bSchristos 
451fc1e710Schristos 	hrd->size = (unsigned)s;
46ba9bdd8bSchristos 
4795dbdf32Schristos 	if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) == -1) {
4895dbdf32Schristos 		fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
491fc1e710Schristos 		return (-1);
50ba9bdd8bSchristos 	}
51ba9bdd8bSchristos 
521fc1e710Schristos 	return (0);
53ba9bdd8bSchristos }
54ba9bdd8bSchristos 
55ba9bdd8bSchristos static bool
is_fido(const char * path)56ba9bdd8bSchristos is_fido(const char *path)
57ba9bdd8bSchristos {
58*2d40c451Schristos 	int				 fd = -1;
59ba9bdd8bSchristos 	uint32_t			 usage_page = 0;
60*2d40c451Schristos 	struct hidraw_report_descriptor	*hrd = NULL;
61ba9bdd8bSchristos 
62*2d40c451Schristos 	if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
63*2d40c451Schristos 	    (fd = fido_hid_unix_open(path)) == -1)
64*2d40c451Schristos 		goto out;
65*2d40c451Schristos 	if (get_report_descriptor(fd, hrd) < 0 ||
66*2d40c451Schristos 	    fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
6795dbdf32Schristos 		usage_page = 0;
681fc1e710Schristos 
69*2d40c451Schristos out:
70*2d40c451Schristos 	free(hrd);
71*2d40c451Schristos 
72*2d40c451Schristos 	if (fd != -1 && close(fd) == -1)
7395dbdf32Schristos 		fido_log_error(errno, "%s: close", __func__);
741fc1e710Schristos 
75ba9bdd8bSchristos 	return (usage_page == 0xf1d0);
76ba9bdd8bSchristos }
77ba9bdd8bSchristos 
78ba9bdd8bSchristos static int
parse_uevent(const char * uevent,int * bus,int16_t * vendor_id,int16_t * product_id)791fc1e710Schristos parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
801fc1e710Schristos     int16_t *product_id)
81ba9bdd8bSchristos {
82ba9bdd8bSchristos 	char			*cp;
83ba9bdd8bSchristos 	char			*p;
84ba9bdd8bSchristos 	char			*s;
85ba9bdd8bSchristos 	int			 ok = -1;
86ba9bdd8bSchristos 	short unsigned int	 x;
87ba9bdd8bSchristos 	short unsigned int	 y;
881fc1e710Schristos 	short unsigned int	 z;
89ba9bdd8bSchristos 
90ba9bdd8bSchristos 	if ((s = cp = strdup(uevent)) == NULL)
91ba9bdd8bSchristos 		return (-1);
92ba9bdd8bSchristos 
931fc1e710Schristos 	while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
94ba9bdd8bSchristos 		if (strncmp(p, "HID_ID=", 7) == 0) {
951fc1e710Schristos 			if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
961fc1e710Schristos 				*bus = (int)x;
971fc1e710Schristos 				*vendor_id = (int16_t)y;
981fc1e710Schristos 				*product_id = (int16_t)z;
99ba9bdd8bSchristos 				ok = 0;
100ba9bdd8bSchristos 				break;
101ba9bdd8bSchristos 			}
102ba9bdd8bSchristos 		}
1031fc1e710Schristos 	}
104ba9bdd8bSchristos 
105ba9bdd8bSchristos 	free(s);
106ba9bdd8bSchristos 
107ba9bdd8bSchristos 	return (ok);
108ba9bdd8bSchristos }
109ba9bdd8bSchristos 
1101fc1e710Schristos static char *
get_parent_attr(struct udev_device * dev,const char * subsystem,const char * devtype,const char * attr)1111fc1e710Schristos get_parent_attr(struct udev_device *dev, const char *subsystem,
1121fc1e710Schristos     const char *devtype, const char *attr)
1131fc1e710Schristos {
1141fc1e710Schristos 	struct udev_device	*parent;
1151fc1e710Schristos 	const char		*value;
1161fc1e710Schristos 
1171fc1e710Schristos 	if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
1181fc1e710Schristos 	    subsystem, devtype)) == NULL || (value =
1191fc1e710Schristos 	    udev_device_get_sysattr_value(parent, attr)) == NULL)
1201fc1e710Schristos 		return (NULL);
1211fc1e710Schristos 
1221fc1e710Schristos 	return (strdup(value));
1231fc1e710Schristos }
1241fc1e710Schristos 
1251fc1e710Schristos static char *
get_usb_attr(struct udev_device * dev,const char * attr)1261fc1e710Schristos get_usb_attr(struct udev_device *dev, const char *attr)
1271fc1e710Schristos {
1281fc1e710Schristos 	return (get_parent_attr(dev, "usb", "usb_device", attr));
1291fc1e710Schristos }
1301fc1e710Schristos 
131ba9bdd8bSchristos static int
copy_info(fido_dev_info_t * di,struct udev * udev,struct udev_list_entry * udev_entry)132ba9bdd8bSchristos copy_info(fido_dev_info_t *di, struct udev *udev,
133ba9bdd8bSchristos     struct udev_list_entry *udev_entry)
134ba9bdd8bSchristos {
135ba9bdd8bSchristos 	const char		*name;
136ba9bdd8bSchristos 	const char		*path;
1371fc1e710Schristos 	char			*uevent = NULL;
138ba9bdd8bSchristos 	struct udev_device	*dev = NULL;
1391fc1e710Schristos 	int			 bus = 0;
140ba9bdd8bSchristos 	int			 ok = -1;
141ba9bdd8bSchristos 
142ba9bdd8bSchristos 	memset(di, 0, sizeof(*di));
143ba9bdd8bSchristos 
144ba9bdd8bSchristos 	if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
145ba9bdd8bSchristos 	    (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
146ba9bdd8bSchristos 	    (path = udev_device_get_devnode(dev)) == NULL ||
147ba9bdd8bSchristos 	    is_fido(path) == 0)
148ba9bdd8bSchristos 		goto fail;
149ba9bdd8bSchristos 
1501fc1e710Schristos 	if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
1511fc1e710Schristos 	    parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) {
1521fc1e710Schristos 		fido_log_debug("%s: uevent", __func__);
153ba9bdd8bSchristos 		goto fail;
1541fc1e710Schristos 	}
155ba9bdd8bSchristos 
1561fc1e710Schristos #ifndef FIDO_HID_ANY
1571fc1e710Schristos 	if (bus != BUS_USB) {
1581fc1e710Schristos 		fido_log_debug("%s: bus", __func__);
159ba9bdd8bSchristos 		goto fail;
1601fc1e710Schristos 	}
1611fc1e710Schristos #endif
162ba9bdd8bSchristos 
163ba9bdd8bSchristos 	di->path = strdup(path);
164ede6d7f8Schristos 	if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL)
165*2d40c451Schristos 		di->manufacturer = strdup("");
166ede6d7f8Schristos 	if ((di->product = get_usb_attr(dev, "product")) == NULL)
167*2d40c451Schristos 		di->product = strdup("");
1681fc1e710Schristos 	if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
169ba9bdd8bSchristos 		goto fail;
170ba9bdd8bSchristos 
171ba9bdd8bSchristos 	ok = 0;
172ba9bdd8bSchristos fail:
173ba9bdd8bSchristos 	if (dev != NULL)
174ba9bdd8bSchristos 		udev_device_unref(dev);
175ba9bdd8bSchristos 
1761fc1e710Schristos 	free(uevent);
1771fc1e710Schristos 
178ba9bdd8bSchristos 	if (ok < 0) {
179ba9bdd8bSchristos 		free(di->path);
180ba9bdd8bSchristos 		free(di->manufacturer);
181ba9bdd8bSchristos 		free(di->product);
182ba9bdd8bSchristos 		explicit_bzero(di, sizeof(*di));
183ba9bdd8bSchristos 	}
184ba9bdd8bSchristos 
185ba9bdd8bSchristos 	return (ok);
186ba9bdd8bSchristos }
187ba9bdd8bSchristos 
188ba9bdd8bSchristos int
fido_hid_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)189ba9bdd8bSchristos fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
190ba9bdd8bSchristos {
191ba9bdd8bSchristos 	struct udev		*udev = NULL;
192ba9bdd8bSchristos 	struct udev_enumerate	*udev_enum = NULL;
193ba9bdd8bSchristos 	struct udev_list_entry	*udev_list;
194ba9bdd8bSchristos 	struct udev_list_entry	*udev_entry;
195ba9bdd8bSchristos 	int			 r = FIDO_ERR_INTERNAL;
196ba9bdd8bSchristos 
197ba9bdd8bSchristos 	*olen = 0;
198ba9bdd8bSchristos 
199ba9bdd8bSchristos 	if (ilen == 0)
200ba9bdd8bSchristos 		return (FIDO_OK); /* nothing to do */
201ba9bdd8bSchristos 
202ba9bdd8bSchristos 	if (devlist == NULL)
203ba9bdd8bSchristos 		return (FIDO_ERR_INVALID_ARGUMENT);
204ba9bdd8bSchristos 
205ba9bdd8bSchristos 	if ((udev = udev_new()) == NULL ||
206ba9bdd8bSchristos 	    (udev_enum = udev_enumerate_new(udev)) == NULL)
207ba9bdd8bSchristos 		goto fail;
208ba9bdd8bSchristos 
209ba9bdd8bSchristos 	if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
2101fc1e710Schristos 	    udev_enumerate_scan_devices(udev_enum) < 0)
211ba9bdd8bSchristos 		goto fail;
212ba9bdd8bSchristos 
2131fc1e710Schristos 	if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
2141fc1e710Schristos 		r = FIDO_OK; /* zero hidraw devices */
2151fc1e710Schristos 		goto fail;
2161fc1e710Schristos 	}
2171fc1e710Schristos 
218ba9bdd8bSchristos 	udev_list_entry_foreach(udev_entry, udev_list) {
219ba9bdd8bSchristos 		if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
220ba9bdd8bSchristos 			devlist[*olen].io = (fido_dev_io_t) {
221ba9bdd8bSchristos 				fido_hid_open,
222ba9bdd8bSchristos 				fido_hid_close,
223ba9bdd8bSchristos 				fido_hid_read,
224ba9bdd8bSchristos 				fido_hid_write,
225ba9bdd8bSchristos 			};
226ba9bdd8bSchristos 			if (++(*olen) == ilen)
227ba9bdd8bSchristos 				break;
228ba9bdd8bSchristos 		}
229ba9bdd8bSchristos 	}
230ba9bdd8bSchristos 
231ba9bdd8bSchristos 	r = FIDO_OK;
232ba9bdd8bSchristos fail:
233ba9bdd8bSchristos 	if (udev_enum != NULL)
234ba9bdd8bSchristos 		udev_enumerate_unref(udev_enum);
235ba9bdd8bSchristos 	if (udev != NULL)
236ba9bdd8bSchristos 		udev_unref(udev);
237ba9bdd8bSchristos 
238ba9bdd8bSchristos 	return (r);
239ba9bdd8bSchristos }
240ba9bdd8bSchristos 
241ba9bdd8bSchristos void *
fido_hid_open(const char * path)242ba9bdd8bSchristos fido_hid_open(const char *path)
243ba9bdd8bSchristos {
2441fc1e710Schristos 	struct hid_linux *ctx;
245*2d40c451Schristos 	struct hidraw_report_descriptor *hrd;
246ede6d7f8Schristos 	struct timespec tv_pause;
247ede6d7f8Schristos 	long interval_ms, retries = 0;
248*2d40c451Schristos 	bool looped;
249*2d40c451Schristos 
250*2d40c451Schristos retry:
251*2d40c451Schristos 	looped = false;
252ba9bdd8bSchristos 
253ede6d7f8Schristos 	if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
254ede6d7f8Schristos 	    (ctx->fd = fido_hid_unix_open(path)) == -1) {
2551fc1e710Schristos 		free(ctx);
256ba9bdd8bSchristos 		return (NULL);
257ba9bdd8bSchristos 	}
258ba9bdd8bSchristos 
259ede6d7f8Schristos 	while (flock(ctx->fd, LOCK_EX|LOCK_NB) == -1) {
260ede6d7f8Schristos 		if (errno != EWOULDBLOCK) {
261ede6d7f8Schristos 			fido_log_error(errno, "%s: flock", __func__);
262ede6d7f8Schristos 			fido_hid_close(ctx);
263ede6d7f8Schristos 			return (NULL);
264ede6d7f8Schristos 		}
265*2d40c451Schristos 		looped = true;
266*2d40c451Schristos 		if (retries++ >= 20) {
267ede6d7f8Schristos 			fido_log_debug("%s: flock timeout", __func__);
268ede6d7f8Schristos 			fido_hid_close(ctx);
269ede6d7f8Schristos 			return (NULL);
270ede6d7f8Schristos 		}
271ede6d7f8Schristos 		interval_ms = retries * 100000000L;
272ede6d7f8Schristos 		tv_pause.tv_sec = interval_ms / 1000000000L;
273ede6d7f8Schristos 		tv_pause.tv_nsec = interval_ms % 1000000000L;
274ede6d7f8Schristos 		if (nanosleep(&tv_pause, NULL) == -1) {
275ede6d7f8Schristos 			fido_log_error(errno, "%s: nanosleep", __func__);
276ede6d7f8Schristos 			fido_hid_close(ctx);
277ede6d7f8Schristos 			return (NULL);
278ede6d7f8Schristos 		}
279ede6d7f8Schristos 	}
280ede6d7f8Schristos 
281*2d40c451Schristos 	if (looped) {
282*2d40c451Schristos 		fido_log_debug("%s: retrying", __func__);
283*2d40c451Schristos 		fido_hid_close(ctx);
284*2d40c451Schristos 		goto retry;
285*2d40c451Schristos 	}
286*2d40c451Schristos 
287*2d40c451Schristos 	if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
288*2d40c451Schristos 	    get_report_descriptor(ctx->fd, hrd) < 0 ||
289*2d40c451Schristos 	    fido_hid_get_report_len(hrd->value, hrd->size, &ctx->report_in_len,
29095dbdf32Schristos 	    &ctx->report_out_len) < 0 || ctx->report_in_len == 0 ||
29195dbdf32Schristos 	    ctx->report_out_len == 0) {
2921fc1e710Schristos 		fido_log_debug("%s: using default report sizes", __func__);
2931fc1e710Schristos 		ctx->report_in_len = CTAP_MAX_REPORT_LEN;
2941fc1e710Schristos 		ctx->report_out_len = CTAP_MAX_REPORT_LEN;
2951fc1e710Schristos 	}
2961fc1e710Schristos 
297*2d40c451Schristos 	free(hrd);
298*2d40c451Schristos 
2991fc1e710Schristos 	return (ctx);
300ba9bdd8bSchristos }
301ba9bdd8bSchristos 
302ba9bdd8bSchristos void
fido_hid_close(void * handle)303ba9bdd8bSchristos fido_hid_close(void *handle)
304ba9bdd8bSchristos {
3051fc1e710Schristos 	struct hid_linux *ctx = handle;
306ba9bdd8bSchristos 
30795dbdf32Schristos 	if (close(ctx->fd) == -1)
30895dbdf32Schristos 		fido_log_error(errno, "%s: close", __func__);
30995dbdf32Schristos 
3101fc1e710Schristos 	free(ctx);
3111fc1e710Schristos }
3121fc1e710Schristos 
31395dbdf32Schristos int
fido_hid_set_sigmask(void * handle,const fido_sigset_t * sigmask)31495dbdf32Schristos fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
3151fc1e710Schristos {
31695dbdf32Schristos 	struct hid_linux *ctx = handle;
3171fc1e710Schristos 
31895dbdf32Schristos 	ctx->sigmask = *sigmask;
31995dbdf32Schristos 	ctx->sigmaskp = &ctx->sigmask;
3201fc1e710Schristos 
32195dbdf32Schristos 	return (FIDO_OK);
322ba9bdd8bSchristos }
323ba9bdd8bSchristos 
324ba9bdd8bSchristos int
fido_hid_read(void * handle,unsigned char * buf,size_t len,int ms)325ba9bdd8bSchristos fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
326ba9bdd8bSchristos {
3271fc1e710Schristos 	struct hid_linux	*ctx = handle;
328ba9bdd8bSchristos 	ssize_t			 r;
329ba9bdd8bSchristos 
3301fc1e710Schristos 	if (len != ctx->report_in_len) {
3311fc1e710Schristos 		fido_log_debug("%s: len %zu", __func__, len);
332ba9bdd8bSchristos 		return (-1);
333ba9bdd8bSchristos 	}
334ba9bdd8bSchristos 
33595dbdf32Schristos 	if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
3361fc1e710Schristos 		fido_log_debug("%s: fd not ready", __func__);
337ba9bdd8bSchristos 		return (-1);
3381fc1e710Schristos 	}
339ba9bdd8bSchristos 
34095dbdf32Schristos 	if ((r = read(ctx->fd, buf, len)) == -1) {
34195dbdf32Schristos 		fido_log_error(errno, "%s: read", __func__);
34295dbdf32Schristos 		return (-1);
34395dbdf32Schristos 	}
34495dbdf32Schristos 
34595dbdf32Schristos 	if (r < 0 || (size_t)r != len) {
34695dbdf32Schristos 		fido_log_debug("%s: %zd != %zu", __func__, r, len);
3471fc1e710Schristos 		return (-1);
3481fc1e710Schristos 	}
3491fc1e710Schristos 
3501fc1e710Schristos 	return ((int)r);
351ba9bdd8bSchristos }
352ba9bdd8bSchristos 
353ba9bdd8bSchristos int
fido_hid_write(void * handle,const unsigned char * buf,size_t len)354ba9bdd8bSchristos fido_hid_write(void *handle, const unsigned char *buf, size_t len)
355ba9bdd8bSchristos {
3561fc1e710Schristos 	struct hid_linux	*ctx = handle;
357ba9bdd8bSchristos 	ssize_t			 r;
358ba9bdd8bSchristos 
3591fc1e710Schristos 	if (len != ctx->report_out_len + 1) {
3601fc1e710Schristos 		fido_log_debug("%s: len %zu", __func__, len);
361ba9bdd8bSchristos 		return (-1);
362ba9bdd8bSchristos 	}
363ba9bdd8bSchristos 
36495dbdf32Schristos 	if ((r = write(ctx->fd, buf, len)) == -1) {
36595dbdf32Schristos 		fido_log_error(errno, "%s: write", __func__);
36695dbdf32Schristos 		return (-1);
36795dbdf32Schristos 	}
36895dbdf32Schristos 
36995dbdf32Schristos 	if (r < 0 || (size_t)r != len) {
37095dbdf32Schristos 		fido_log_debug("%s: %zd != %zu", __func__, r, len);
371ba9bdd8bSchristos 		return (-1);
372ba9bdd8bSchristos 	}
373ba9bdd8bSchristos 
3741fc1e710Schristos 	return ((int)r);
3751fc1e710Schristos }
3761fc1e710Schristos 
3771fc1e710Schristos size_t
fido_hid_report_in_len(void * handle)3781fc1e710Schristos fido_hid_report_in_len(void *handle)
3791fc1e710Schristos {
3801fc1e710Schristos 	struct hid_linux *ctx = handle;
3811fc1e710Schristos 
3821fc1e710Schristos 	return (ctx->report_in_len);
3831fc1e710Schristos }
3841fc1e710Schristos 
3851fc1e710Schristos size_t
fido_hid_report_out_len(void * handle)3861fc1e710Schristos fido_hid_report_out_len(void *handle)
3871fc1e710Schristos {
3881fc1e710Schristos 	struct hid_linux *ctx = handle;
3891fc1e710Schristos 
3901fc1e710Schristos 	return (ctx->report_out_len);
391ba9bdd8bSchristos }
392