Coverage Report

Created: 2020-09-01 07:05

/libfido2/src/hid_linux.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2019 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
9
#include <sys/ioctl.h>
10
#include <linux/hidraw.h>
11
#include <linux/input.h>
12
13
#include <errno.h>
14
#include <fcntl.h>
15
#include <libudev.h>
16
#include <poll.h>
17
#include <string.h>
18
#include <unistd.h>
19
20
#include "fido.h"
21
22
struct hid_linux {
23
        int     fd;
24
        size_t  report_in_len;
25
        size_t  report_out_len;
26
};
27
28
static int
29
get_key_len(uint8_t tag, uint8_t *key, size_t *key_len)
30
0
{
31
0
        *key = tag & 0xfc;
32
0
        if ((*key & 0xf0) == 0xf0) {
33
0
                fido_log_debug("%s: *key=0x%02x", __func__, *key);
34
0
                return (-1);
35
0
        }
36
0
37
0
        *key_len = tag & 0x3;
38
0
        if (*key_len == 3) {
39
0
                *key_len = 4;
40
0
        }
41
0
42
0
        return (0);
43
0
}
44
45
static int
46
get_key_val(const void *body, size_t key_len, uint32_t *val)
47
0
{
48
0
        const uint8_t *ptr = body;
49
0
50
0
        switch (key_len) {
51
0
        case 0:
52
0
                *val = 0;
53
0
                break;
54
0
        case 1:
55
0
                *val = ptr[0];
56
0
                break;
57
0
        case 2:
58
0
                *val = (uint32_t)((ptr[1] << 8) | ptr[0]);
59
0
                break;
60
0
        default:
61
0
                fido_log_debug("%s: key_len=%zu", __func__, key_len);
62
0
                return (-1);
63
0
        }
64
0
65
0
        return (0);
66
0
}
67
68
static int
69
get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page,
70
    uint32_t *usage)
71
0
{
72
0
        const uint8_t   *ptr = hrd->value;
73
0
        size_t           len = hrd->size;
74
0
75
0
        while (len > 0) {
76
0
                const uint8_t tag = ptr[0];
77
0
                ptr++;
78
0
                len--;
79
0
80
0
                uint8_t  key;
81
0
                size_t   key_len;
82
0
                uint32_t key_val;
83
0
84
0
                if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
85
0
                    get_key_val(ptr, key_len, &key_val) < 0) {
86
0
                        return (-1);
87
0
                }
88
0
89
0
                if (key == 0x4) {
90
0
                        *usage_page = key_val;
91
0
                } else if (key == 0x8) {
92
0
                        *usage = key_val;
93
0
                }
94
0
95
0
                ptr += key_len;
96
0
                len -= key_len;
97
0
        }
98
0
99
0
        return (0);
100
0
}
101
102
static int
103
get_report_sizes(const struct hidraw_report_descriptor *hrd,
104
    size_t *report_in_len, size_t *report_out_len)
105
0
{
106
0
        const uint8_t   *ptr = hrd->value;
107
0
        size_t           len = hrd->size;
108
0
        uint32_t         report_size = 0;
109
0
110
0
        while (len > 0) {
111
0
                const uint8_t tag = ptr[0];
112
0
                ptr++;
113
0
                len--;
114
0
115
0
                uint8_t  key;
116
0
                size_t   key_len;
117
0
                uint32_t key_val;
118
0
119
0
                if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
120
0
                    get_key_val(ptr, key_len, &key_val) < 0) {
121
0
                        return (-1);
122
0
                }
123
0
124
0
                if (key == 0x94) {
125
0
                        report_size = key_val;
126
0
                } else if (key == 0x80) {
127
0
                        *report_in_len = (size_t)report_size;
128
0
                } else if (key == 0x90) {
129
0
                        *report_out_len = (size_t)report_size;
130
0
                }
131
0
132
0
                ptr += key_len;
133
0
                len -= key_len;
134
0
        }
135
0
136
0
        return (0);
137
0
}
138
139
static int
140
get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
141
0
{
142
0
        int s = -1;
143
0
144
0
        if (ioctl(fd, HIDIOCGRDESCSIZE, &s) < 0 || s < 0 ||
145
0
            (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
146
0
                fido_log_debug("%s: ioctl HIDIOCGRDESCSIZE", __func__);
147
0
                return (-1);
148
0
        }
149
0
150
0
        hrd->size = (unsigned)s;
151
0
152
0
        if (ioctl(fd, HIDIOCGRDESC, hrd) < 0) {
153
0
                fido_log_debug("%s: ioctl HIDIOCGRDESC", __func__);
154
0
                return (-1);
155
0
        }
156
0
157
0
        return (0);
158
0
}
159
160
static bool
161
is_fido(const char *path)
162
0
{
163
0
        int                             fd;
164
0
        uint32_t                        usage = 0;
165
0
        uint32_t                        usage_page = 0;
166
0
        struct hidraw_report_descriptor hrd;
167
0
168
0
        memset(&hrd, 0, sizeof(hrd));
169
0
170
0
        if ((fd = open(path, O_RDONLY)) == -1) {
171
0
                fido_log_debug("%s: open", __func__);
172
0
                return (false);
173
0
        }
174
0
175
0
        if (get_report_descriptor(fd, &hrd) < 0 ||
176
0
            get_usage_info(&hrd, &usage_page, &usage) < 0) {
177
0
                close(fd);
178
0
                return (false);
179
0
        }
180
0
181
0
        close(fd);
182
0
183
0
        return (usage_page == 0xf1d0);
184
0
}
185
186
static int
187
parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
188
    int16_t *product_id)
189
0
{
190
0
        char                    *cp;
191
0
        char                    *p;
192
0
        char                    *s;
193
0
        int                      ok = -1;
194
0
        short unsigned int       x;
195
0
        short unsigned int       y;
196
0
        short unsigned int       z;
197
0
198
0
        if ((s = cp = strdup(uevent)) == NULL)
199
0
                return (-1);
200
0
201
0
        while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
202
0
                if (strncmp(p, "HID_ID=", 7) == 0) {
203
0
                        if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
204
0
                                *bus = (int)x;
205
0
                                *vendor_id = (int16_t)y;
206
0
                                *product_id = (int16_t)z;
207
0
                                ok = 0;
208
0
                                break;
209
0
                        }
210
0
                }
211
0
        }
212
0
213
0
        free(s);
214
0
215
0
        return (ok);
216
0
}
217
218
static char *
219
get_parent_attr(struct udev_device *dev, const char *subsystem,
220
    const char *devtype, const char *attr)
221
0
{
222
0
        struct udev_device      *parent;
223
0
        const char              *value;
224
0
225
0
        if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
226
0
            subsystem, devtype)) == NULL || (value =
227
0
            udev_device_get_sysattr_value(parent, attr)) == NULL)
228
0
                return (NULL);
229
0
230
0
        return (strdup(value));
231
0
}
232
233
static char *
234
get_usb_attr(struct udev_device *dev, const char *attr)
235
0
{
236
0
        return (get_parent_attr(dev, "usb", "usb_device", attr));
237
0
}
238
239
static int
240
copy_info(fido_dev_info_t *di, struct udev *udev,
241
    struct udev_list_entry *udev_entry)
242
0
{
243
0
        const char              *name;
244
0
        const char              *path;
245
0
        char                    *uevent = NULL;
246
0
        struct udev_device      *dev = NULL;
247
0
        int                      bus = 0;
248
0
        int                      ok = -1;
249
0
250
0
        memset(di, 0, sizeof(*di));
251
0
252
0
        if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
253
0
            (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
254
0
            (path = udev_device_get_devnode(dev)) == NULL ||
255
0
            is_fido(path) == 0)
256
0
                goto fail;
257
0
258
0
        if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
259
0
            parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) {
260
0
                fido_log_debug("%s: uevent", __func__);
261
0
                goto fail;
262
0
        }
263
0
264
0
#ifndef FIDO_HID_ANY
265
0
        if (bus != BUS_USB) {
266
0
                fido_log_debug("%s: bus", __func__);
267
0
                goto fail;
268
0
        }
269
0
#endif
270
0
271
0
        di->path = strdup(path);
272
0
        di->manufacturer = get_usb_attr(dev, "manufacturer");
273
0
        di->product = get_usb_attr(dev, "product");
274
0
275
0
        if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
276
0
                goto fail;
277
0
278
0
        ok = 0;
279
0
fail:
280
0
        if (dev != NULL)
281
0
                udev_device_unref(dev);
282
0
283
0
        free(uevent);
284
0
285
0
        if (ok < 0) {
286
0
                free(di->path);
287
0
                free(di->manufacturer);
288
0
                free(di->product);
289
0
                explicit_bzero(di, sizeof(*di));
290
0
        }
291
0
292
0
        return (ok);
293
0
}
294
295
int
296
fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
297
0
{
298
0
        struct udev             *udev = NULL;
299
0
        struct udev_enumerate   *udev_enum = NULL;
300
0
        struct udev_list_entry  *udev_list;
301
0
        struct udev_list_entry  *udev_entry;
302
0
        int                      r = FIDO_ERR_INTERNAL;
303
0
304
0
        *olen = 0;
305
0
306
0
        if (ilen == 0)
307
0
                return (FIDO_OK); /* nothing to do */
308
0
309
0
        if (devlist == NULL)
310
0
                return (FIDO_ERR_INVALID_ARGUMENT);
311
0
312
0
        if ((udev = udev_new()) == NULL ||
313
0
            (udev_enum = udev_enumerate_new(udev)) == NULL)
314
0
                goto fail;
315
0
316
0
        if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
317
0
            udev_enumerate_scan_devices(udev_enum) < 0)
318
0
                goto fail;
319
0
320
0
        if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
321
0
                r = FIDO_OK; /* zero hidraw devices */
322
0
                goto fail;
323
0
        }
324
0
325
0
        udev_list_entry_foreach(udev_entry, udev_list) {
326
0
                if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
327
0
                        devlist[*olen].io = (fido_dev_io_t) {
328
0
                                fido_hid_open,
329
0
                                fido_hid_close,
330
0
                                fido_hid_read,
331
0
                                fido_hid_write,
332
0
                        };
333
0
                        if (++(*olen) == ilen)
334
0
                                break;
335
0
                }
336
0
        }
337
0
338
0
        r = FIDO_OK;
339
0
fail:
340
0
        if (udev_enum != NULL)
341
0
                udev_enumerate_unref(udev_enum);
342
0
        if (udev != NULL)
343
0
                udev_unref(udev);
344
0
345
0
        return (r);
346
0
}
347
348
void *
349
fido_hid_open(const char *path)
350
0
{
351
0
        struct hid_linux                *ctx;
352
0
        struct hidraw_report_descriptor  hrd;
353
0
354
0
        if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
355
0
                return (NULL);
356
0
357
0
        if ((ctx->fd = open(path, O_RDWR)) < 0) {
358
0
                free(ctx);
359
0
                return (NULL);
360
0
        }
361
0
362
0
        if (get_report_descriptor(ctx->fd, &hrd) < 0 || get_report_sizes(&hrd,
363
0
            &ctx->report_in_len, &ctx->report_out_len) < 0 ||
364
0
            ctx->report_in_len == 0 || ctx->report_out_len == 0) {
365
0
                fido_log_debug("%s: using default report sizes", __func__);
366
0
                ctx->report_in_len = CTAP_MAX_REPORT_LEN;
367
0
                ctx->report_out_len = CTAP_MAX_REPORT_LEN;
368
0
        }
369
0
370
0
        return (ctx);
371
0
}
372
373
void
374
fido_hid_close(void *handle)
375
0
{
376
0
        struct hid_linux *ctx = handle;
377
0
378
0
        close(ctx->fd);
379
0
        free(ctx);
380
0
}
381
382
static int
383
timespec_to_ms(const struct timespec *ts, int upper_bound)
384
0
{
385
0
        int64_t x;
386
0
        int64_t y;
387
0
388
0
        if (ts->tv_sec < 0 || ts->tv_sec > INT64_MAX / 1000LL ||
389
0
            ts->tv_nsec < 0 || ts->tv_nsec / 1000000LL > INT64_MAX)
390
0
                return (upper_bound);
391
0
392
0
        x = ts->tv_sec * 1000LL;
393
0
        y = ts->tv_nsec / 1000000LL;
394
0
395
0
        if (INT64_MAX - x < y || x + y > upper_bound)
396
0
                return (upper_bound);
397
0
398
0
        return (int)(x + y);
399
0
}
400
401
static int
402
waitfd(int fd, int ms)
403
0
{
404
0
        struct timespec ts_start;
405
0
        struct timespec ts_now;
406
0
        struct timespec ts_delta;
407
0
        struct pollfd   pfd;
408
0
        int             ms_remain;
409
0
        int             r;
410
0
411
0
        if (ms < 0)
412
0
                return (0);
413
0
414
0
        memset(&pfd, 0, sizeof(pfd));
415
0
        pfd.events = POLLIN;
416
0
        pfd.fd = fd;
417
0
418
0
        if (clock_gettime(CLOCK_MONOTONIC, &ts_start) != 0) {
419
0
                fido_log_debug("%s: clock_gettime: %s", __func__,
420
0
                    strerror(errno));
421
0
                return (-1);
422
0
        }
423
0
424
0
        for (ms_remain = ms; ms_remain > 0;) {
425
0
                if ((r = poll(&pfd, 1, ms_remain)) > 0)
426
0
                        return (0);
427
0
                else if (r == 0)
428
0
                        break;
429
0
                else if (errno != EINTR) {
430
0
                        fido_log_debug("%s: poll: %s", __func__,
431
0
                            strerror(errno));
432
0
                        return (-1);
433
0
                }
434
0
                /* poll interrupted - subtract time already waited */
435
0
                if (clock_gettime(CLOCK_MONOTONIC, &ts_now) != 0) {
436
0
                        fido_log_debug("%s: clock_gettime: %s", __func__,
437
0
                            strerror(errno));
438
0
                        return (-1);
439
0
                }
440
0
                timespecsub(&ts_now, &ts_start, &ts_delta);
441
0
                ms_remain = ms - timespec_to_ms(&ts_delta, ms);
442
0
        }
443
0
444
0
        return (-1);
445
0
}
446
447
int
448
fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
449
0
{
450
0
        struct hid_linux        *ctx = handle;
451
0
        ssize_t                  r;
452
0
453
0
        if (len != ctx->report_in_len) {
454
0
                fido_log_debug("%s: len %zu", __func__, len);
455
0
                return (-1);
456
0
        }
457
0
458
0
        if (waitfd(ctx->fd, ms) < 0) {
459
0
                fido_log_debug("%s: fd not ready", __func__);
460
0
                return (-1);
461
0
        }
462
0
463
0
        if ((r = read(ctx->fd, buf, len)) < 0 || (size_t)r != len) {
464
0
                fido_log_debug("%s: read", __func__);
465
0
                return (-1);
466
0
        }
467
0
468
0
        return ((int)r);
469
0
}
470
471
int
472
fido_hid_write(void *handle, const unsigned char *buf, size_t len)
473
0
{
474
0
        struct hid_linux        *ctx = handle;
475
0
        ssize_t                  r;
476
0
477
0
        if (len != ctx->report_out_len + 1) {
478
0
                fido_log_debug("%s: len %zu", __func__, len);
479
0
                return (-1);
480
0
        }
481
0
482
0
        if ((r = write(ctx->fd, buf, len)) < 0 || (size_t)r != len) {
483
0
                fido_log_debug("%s: write", __func__);
484
0
                return (-1);
485
0
        }
486
0
487
0
        return ((int)r);
488
0
}
489
490
size_t
491
fido_hid_report_in_len(void *handle)
492
0
{
493
0
        struct hid_linux *ctx = handle;
494
0
495
0
        return (ctx->report_in_len);
496
0
}
497
498
size_t
499
fido_hid_report_out_len(void *handle)
500
0
{
501
0
        struct hid_linux *ctx = handle;
502
0
503
0
        return (ctx->report_out_len);
504
0
}