Coverage Report

Created: 2020-12-02 17:02

/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 <libudev.h>
14
#include <string.h>
15
#include <unistd.h>
16
17
#include "fido.h"
18
19
struct hid_linux {
20
        int     fd;
21
        size_t  report_in_len;
22
        size_t  report_out_len;
23
};
24
25
static int
26
get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
27
0
{
28
0
        int s = -1;
29
0
30
0
        if (ioctl(fd, HIDIOCGRDESCSIZE, &s) < 0 || s < 0 ||
31
0
            (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
32
0
                fido_log_debug("%s: ioctl HIDIOCGRDESCSIZE", __func__);
33
0
                return (-1);
34
0
        }
35
0
36
0
        hrd->size = (unsigned)s;
37
0
38
0
        if (ioctl(fd, HIDIOCGRDESC, hrd) < 0) {
39
0
                fido_log_debug("%s: ioctl HIDIOCGRDESC", __func__);
40
0
                return (-1);
41
0
        }
42
0
43
0
        return (0);
44
0
}
45
46
static bool
47
is_fido(const char *path)
48
0
{
49
0
        int                             fd;
50
0
        uint32_t                        usage_page = 0;
51
0
        struct hidraw_report_descriptor hrd;
52
0
53
0
        memset(&hrd, 0, sizeof(hrd));
54
0
55
0
        if ((fd = fido_hid_unix_open(path)) == -1)
56
0
                return (false);
57
0
58
0
        if (get_report_descriptor(fd, &hrd) < 0 ||
59
0
            fido_hid_get_usage(hrd.value, hrd.size, &usage_page) < 0)
60
0
                usage_page = 0;
61
0
62
0
        close(fd);
63
0
64
0
        return (usage_page == 0xf1d0);
65
0
}
66
67
static int
68
parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
69
    int16_t *product_id)
70
0
{
71
0
        char                    *cp;
72
0
        char                    *p;
73
0
        char                    *s;
74
0
        int                      ok = -1;
75
0
        short unsigned int       x;
76
0
        short unsigned int       y;
77
0
        short unsigned int       z;
78
0
79
0
        if ((s = cp = strdup(uevent)) == NULL)
80
0
                return (-1);
81
0
82
0
        while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
83
0
                if (strncmp(p, "HID_ID=", 7) == 0) {
84
0
                        if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
85
0
                                *bus = (int)x;
86
0
                                *vendor_id = (int16_t)y;
87
0
                                *product_id = (int16_t)z;
88
0
                                ok = 0;
89
0
                                break;
90
0
                        }
91
0
                }
92
0
        }
93
0
94
0
        free(s);
95
0
96
0
        return (ok);
97
0
}
98
99
static char *
100
get_parent_attr(struct udev_device *dev, const char *subsystem,
101
    const char *devtype, const char *attr)
102
0
{
103
0
        struct udev_device      *parent;
104
0
        const char              *value;
105
0
106
0
        if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
107
0
            subsystem, devtype)) == NULL || (value =
108
0
            udev_device_get_sysattr_value(parent, attr)) == NULL)
109
0
                return (NULL);
110
0
111
0
        return (strdup(value));
112
0
}
113
114
static char *
115
get_usb_attr(struct udev_device *dev, const char *attr)
116
0
{
117
0
        return (get_parent_attr(dev, "usb", "usb_device", attr));
118
0
}
119
120
static int
121
copy_info(fido_dev_info_t *di, struct udev *udev,
122
    struct udev_list_entry *udev_entry)
123
0
{
124
0
        const char              *name;
125
0
        const char              *path;
126
0
        char                    *uevent = NULL;
127
0
        struct udev_device      *dev = NULL;
128
0
        int                      bus = 0;
129
0
        int                      ok = -1;
130
0
131
0
        memset(di, 0, sizeof(*di));
132
0
133
0
        if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
134
0
            (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
135
0
            (path = udev_device_get_devnode(dev)) == NULL ||
136
0
            is_fido(path) == 0)
137
0
                goto fail;
138
0
139
0
        if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
140
0
            parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) {
141
0
                fido_log_debug("%s: uevent", __func__);
142
0
                goto fail;
143
0
        }
144
0
145
0
#ifndef FIDO_HID_ANY
146
0
        if (bus != BUS_USB) {
147
0
                fido_log_debug("%s: bus", __func__);
148
0
                goto fail;
149
0
        }
150
0
#endif
151
0
152
0
        di->path = strdup(path);
153
0
        di->manufacturer = get_usb_attr(dev, "manufacturer");
154
0
        di->product = get_usb_attr(dev, "product");
155
0
156
0
        if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
157
0
                goto fail;
158
0
159
0
        ok = 0;
160
0
fail:
161
0
        if (dev != NULL)
162
0
                udev_device_unref(dev);
163
0
164
0
        free(uevent);
165
0
166
0
        if (ok < 0) {
167
0
                free(di->path);
168
0
                free(di->manufacturer);
169
0
                free(di->product);
170
0
                explicit_bzero(di, sizeof(*di));
171
0
        }
172
0
173
0
        return (ok);
174
0
}
175
176
int
177
fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
178
0
{
179
0
        struct udev             *udev = NULL;
180
0
        struct udev_enumerate   *udev_enum = NULL;
181
0
        struct udev_list_entry  *udev_list;
182
0
        struct udev_list_entry  *udev_entry;
183
0
        int                      r = FIDO_ERR_INTERNAL;
184
0
185
0
        *olen = 0;
186
0
187
0
        if (ilen == 0)
188
0
                return (FIDO_OK); /* nothing to do */
189
0
190
0
        if (devlist == NULL)
191
0
                return (FIDO_ERR_INVALID_ARGUMENT);
192
0
193
0
        if ((udev = udev_new()) == NULL ||
194
0
            (udev_enum = udev_enumerate_new(udev)) == NULL)
195
0
                goto fail;
196
0
197
0
        if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
198
0
            udev_enumerate_scan_devices(udev_enum) < 0)
199
0
                goto fail;
200
0
201
0
        if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
202
0
                r = FIDO_OK; /* zero hidraw devices */
203
0
                goto fail;
204
0
        }
205
0
206
0
        udev_list_entry_foreach(udev_entry, udev_list) {
207
0
                if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
208
0
                        devlist[*olen].io = (fido_dev_io_t) {
209
0
                                fido_hid_open,
210
0
                                fido_hid_close,
211
0
                                fido_hid_read,
212
0
                                fido_hid_write,
213
0
                        };
214
0
                        if (++(*olen) == ilen)
215
0
                                break;
216
0
                }
217
0
        }
218
0
219
0
        r = FIDO_OK;
220
0
fail:
221
0
        if (udev_enum != NULL)
222
0
                udev_enumerate_unref(udev_enum);
223
0
        if (udev != NULL)
224
0
                udev_unref(udev);
225
0
226
0
        return (r);
227
0
}
228
229
void *
230
fido_hid_open(const char *path)
231
0
{
232
0
        struct hid_linux                *ctx;
233
0
        struct hidraw_report_descriptor  hrd;
234
0
235
0
        if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
236
0
                return (NULL);
237
0
238
0
        if ((ctx->fd = fido_hid_unix_open(path)) == -1) {
239
0
                free(ctx);
240
0
                return (NULL);
241
0
        }
242
0
243
0
        if (get_report_descriptor(ctx->fd, &hrd) < 0 ||
244
0
            fido_hid_get_report_len(hrd.value, hrd.size, &ctx->report_in_len,
245
0
            &ctx->report_out_len) < 0 || ctx->report_in_len == 0 ||
246
0
            ctx->report_out_len == 0) {
247
0
                fido_log_debug("%s: using default report sizes", __func__);
248
0
                ctx->report_in_len = CTAP_MAX_REPORT_LEN;
249
0
                ctx->report_out_len = CTAP_MAX_REPORT_LEN;
250
0
        }
251
0
252
0
        return (ctx);
253
0
}
254
255
void
256
fido_hid_close(void *handle)
257
0
{
258
0
        struct hid_linux *ctx = handle;
259
0
260
0
        close(ctx->fd);
261
0
        free(ctx);
262
0
}
263
264
int
265
fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
266
0
{
267
0
        struct hid_linux        *ctx = handle;
268
0
        ssize_t                  r;
269
0
270
0
        if (len != ctx->report_in_len) {
271
0
                fido_log_debug("%s: len %zu", __func__, len);
272
0
                return (-1);
273
0
        }
274
0
275
0
        if (fido_hid_unix_wait(ctx->fd, ms) < 0) {
276
0
                fido_log_debug("%s: fd not ready", __func__);
277
0
                return (-1);
278
0
        }
279
0
280
0
        if ((r = read(ctx->fd, buf, len)) < 0 || (size_t)r != len) {
281
0
                fido_log_debug("%s: read", __func__);
282
0
                return (-1);
283
0
        }
284
0
285
0
        return ((int)r);
286
0
}
287
288
int
289
fido_hid_write(void *handle, const unsigned char *buf, size_t len)
290
0
{
291
0
        struct hid_linux        *ctx = handle;
292
0
        ssize_t                  r;
293
0
294
0
        if (len != ctx->report_out_len + 1) {
295
0
                fido_log_debug("%s: len %zu", __func__, len);
296
0
                return (-1);
297
0
        }
298
0
299
0
        if ((r = write(ctx->fd, buf, len)) < 0 || (size_t)r != len) {
300
0
                fido_log_debug("%s: write", __func__);
301
0
                return (-1);
302
0
        }
303
0
304
0
        return ((int)r);
305
0
}
306
307
size_t
308
fido_hid_report_in_len(void *handle)
309
0
{
310
0
        struct hid_linux *ctx = handle;
311
0
312
0
        return (ctx->report_in_len);
313
0
}
314
315
size_t
316
fido_hid_report_out_len(void *handle)
317
0
{
318
0
        struct hid_linux *ctx = handle;
319
0
320
0
        return (ctx->report_out_len);
321
0
}