## Coverage Report

#### Created: 2025-03-01 02:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /libfido2/src/hid_linux.c

    Line
    Count
    Source
    1
    /*
    2
     * Copyright (c) 2019-2024 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
     * SPDX-License-Identifier: BSD-2-Clause
    6
     */
    7
    8
    #include <sys/types.h>
    9
    #include <sys/file.h>
    10
    #include <sys/ioctl.h>
    11
    12
    #include <linux/hidraw.h>
    13
    #include <linux/input.h>
    14
    15
    #include <errno.h>
    16
    #include <libudev.h>
    17
    #include <time.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
            sigset_t        sigmask;
    27
            const sigset_t *sigmaskp;
    28
    };
    29
    30
    static int
    31
    get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
    32
    2.48M
    {
    33
    2.48M
            int s = -1;
    34
    35
    2.48M
            if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) == -1) {
    36
    6.13k
                    fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
    37
    6.13k
                    return (-1);
    38
    6.13k
            }
    39
    40
    2.47M
            if (s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
    41
    0
                    fido_log_debug("%s: HIDIOCGRDESCSIZE %d", __func__, s);
    42
    0
                    return (-1);
    43
    0
            }
    44
    45
    2.47M
            hrd->size = (unsigned)s;
    46
    47
    2.47M
            if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) == -1) {
    48
    5.94k
                    fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
    49
    5.94k
                    return (-1);
    50
    5.94k
            }
    51
    52
    2.47M
            return (0);
    53
    2.47M
    }
    54
    55
    static bool
    56
    is_fido(const char *path)
    57
    2.49M
    {
    58
    2.49M
            int                              fd = -1;
    59
    2.49M
            uint32_t                         usage_page = 0;
    60
    2.49M
            struct hidraw_report_descriptor *hrd = NULL;
    61
    62
    2.49M
            if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
    63
    2.49M
                (fd = fido_hid_unix_open(path)) == -1)
    64
    6.15k
                    goto out;
    65
    2.48M
            if (get_report_descriptor(fd, hrd) < 0 ||
    66
    2.48M
                fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
    67
    1.27M
                    usage_page = 0;
    68
    69
    2.49M
    out:
    70
    2.49M
            free(hrd);
    71
    72
    2.49M
            if (fd != -1 && close(fd) == -1)
    73
    0
                    fido_log_error(errno, "%s: close", __func__);
    74
    75
    2.49M
            return (usage_page == 0xf1d0);
    76
    2.48M
    }
    77
    78
    static int
    79
    parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
    80
        int16_t *product_id, char **hid_name)
    81
    357k
    {
    82
    357k
            char                    *cp;
    83
    357k
            char                    *p;
    84
    357k
            char                    *s;
    85
    357k
            bool                     found_id = false;
    86
    357k
            bool                     found_name = false;
    87
    357k
            short unsigned int       x;
    88
    357k
            short unsigned int       y;
    89
    357k
            short unsigned int       z;
    90
    91
    357k
            if ((s = cp = strdup(uevent)) == NULL)
    92
    1.10k
                    return (-1);
    93
    94
    720k
            while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
    95
    364k
                    if (!found_id && strncmp(p, "HID_ID=", 7) == 0) {
    96
    50.0k
                            if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
    97
    36.6k
                                    *bus = (int)x;
    98
    36.6k
                                    *vendor_id = (int16_t)y;
    99
    36.6k
                                    *product_id = (int16_t)z;
    100
    36.6k
                                    found_id = true;
    101
    36.6k
                            }
    102
    313k
                    } else if (!found_name && strncmp(p, "HID_NAME=", 9) == 0) {
    103
    78.1k
                            if ((*hid_name = strdup(p + 9)) != NULL)
    104
    77.8k
                                    found_name = true;
    105
    78.1k
                    }
    106
    364k
            }
    107
    108
    356k
            free(s);
    109
    110
    356k
            if (!found_name || !found_id)
    111
    327k
                    return (-1);
    112
    113
    28.7k
            return (0);
    114
    356k
    }
    115
    116
    static char *
    117
    get_parent_attr(struct udev_device *dev, const char *subsystem,
    118
        const char *devtype, const char *attr)
    119
    377k
    {
    120
    377k
            struct udev_device      *parent;
    121
    377k
            const char              *value;
    122
    123
    377k
            if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
    124
    377k
                subsystem, devtype)) == NULL || (value =
    125
    376k
                udev_device_get_sysattr_value(parent, attr)) == NULL)
    126
    2.08k
                    return (NULL);
    127
    128
    375k
            return (strdup(value));
    129
    377k
    }
    130
    131
    static char *
    132
    get_usb_attr(struct udev_device *dev, const char *attr)
    133
    17.6k
    {
    134
    17.6k
            return (get_parent_attr(dev, "usb", "usb_device", attr));
    135
    17.6k
    }
    136
    137
    static int
    138
    copy_info(fido_dev_info_t *di, struct udev *udev,
    139
        struct udev_list_entry *udev_entry)
    140
    2.51M
    {
    141
    2.51M
            const char              *name;
    142
    2.51M
            const char              *path;
    143
    2.51M
            char                    *uevent = NULL;
    144
    2.51M
            struct udev_device      *dev = NULL;
    145
    2.51M
            int                      bus = 0;
    146
    2.51M
            char                    *hid_name = NULL;
    147
    2.51M
            int                      ok = -1;
    148
    149
    2.51M
            memset(di, 0, sizeof(*di));
    150
    151
    2.51M
            if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
    152
    2.51M
                (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
    153
    2.51M
                (path = udev_device_get_devnode(dev)) == NULL ||
    154
    2.51M
                is_fido(path) == 0)
    155
    2.15M
                    goto fail;
    156
    157
    360k
            if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
    158
    360k
                parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id,
    159
    357k
                &hid_name) < 0) {
    160
    331k
                    fido_log_debug("%s: uevent", __func__);
    161
    331k
                    goto fail;
    162
    331k
            }
    163
    164
    28.7k
    #ifndef FIDO_HID_ANY
    165
    28.7k
            if (bus != BUS_USB) {
    166
    19.8k
                    fido_log_debug("%s: bus", __func__);
    167
    19.8k
                    goto fail;
    168
    19.8k
            }
    169
    8.83k
    #endif
    170
    171
    8.83k
            di->path = strdup(path);
    172
    8.83k
            di->manufacturer = get_usb_attr(dev, "manufacturer");
    173
    8.83k
            di->product = get_usb_attr(dev, "product");
    174
    175
    8.83k
            if (di->manufacturer == NULL && di->product == NULL) {
    176
    13
                    di->product = hid_name;  /* fallback */
    177
    13
                    hid_name = NULL;
    178
    13
            }
    179
    8.83k
            if (di->manufacturer == NULL)
    180
    242
                    di->manufacturer = strdup("");
    181
    8.83k
            if (di->product == NULL)
    182
    116
                    di->product = strdup("");
    183
    8.83k
            if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
    184
    186
                    goto fail;
    185
    186
    8.64k
            ok = 0;
    187
    2.51M
    fail:
    188
    2.51M
            if (dev != NULL)
    189
    2.49M
                    udev_device_unref(dev);
    190
    191
    2.51M
            free(uevent);
    192
    2.51M
            free(hid_name);
    193
    194
    2.51M
            if (ok < 0) {
    195
    2.50M
                    free(di->path);
    196
    2.50M
                    free(di->manufacturer);
    197
    2.50M
                    free(di->product);
    198
    2.50M
                    explicit_bzero(di, sizeof(*di));
    199
    2.50M
            }
    200
    201
    2.51M
            return (ok);
    202
    8.64k
    }
    203
    204
    int
    205
    fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
    206
    5.96k
    {
    207
    5.96k
            struct udev             *udev = NULL;
    208
    5.96k
            struct udev_enumerate   *udev_enum = NULL;
    209
    5.96k
            struct udev_list_entry  *udev_list;
    210
    5.96k
            struct udev_list_entry  *udev_entry;
    211
    5.96k
            int                      r = FIDO_ERR_INTERNAL;
    212
    213
    5.96k
            *olen = 0;
    214
    215
    5.96k
            if (ilen == 0)
    216
    0
                    return (FIDO_OK); /* nothing to do */
    217
    218
    5.96k
            if (devlist == NULL)
    219
    0
                    return (FIDO_ERR_INVALID_ARGUMENT);
    220
    221
    5.96k
            if ((udev = udev_new()) == NULL ||
    222
    5.96k
                (udev_enum = udev_enumerate_new(udev)) == NULL)
    223
    64
                    goto fail;
    224
    225
    5.90k
            if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
    226
    5.90k
                udev_enumerate_scan_devices(udev_enum) < 0)
    227
    25
                    goto fail;
    228
    229
    5.87k
            if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
    230
    8
                    r = FIDO_OK; /* zero hidraw devices */
    231
    8
                    goto fail;
    232
    8
            }
    233
    234
    2.51M
            udev_list_entry_foreach(udev_entry, udev_list) {
    235
    2.51M
                    if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
    236
    8.64k
                            devlist[*olen].io = (fido_dev_io_t) {
    237
    8.64k
                                    fido_hid_open,
    238
    8.64k
                                    fido_hid_close,
    239
    8.64k
                                    fido_hid_read,
    240
    8.64k
                                    fido_hid_write,
    241
    8.64k
                            };
    242
    8.64k
                            if (++(*olen) == ilen)
    243
    160
                                    break;
    244
    8.64k
                    }
    245
    2.51M
            }
    246
    247
    5.86k
            r = FIDO_OK;
    248
    5.96k
    fail:
    249
    5.96k
            if (udev_enum != NULL)
    250
    5.90k
                    udev_enumerate_unref(udev_enum);
    251
    5.96k
            if (udev != NULL)
    252
    5.94k
                    udev_unref(udev);
    253
    254
    5.96k
            return (r);
    255
    5.86k
    }
    256
    257
    void *
    258
    fido_hid_open(const char *path)
    259
    0
    {
    260
    0
            struct hid_linux *ctx;
    261
    0
            struct hidraw_report_descriptor *hrd;
    262
    0
            struct timespec tv_pause;
    263
    0
            long interval_ms, retries = 0;
    264
    0
            bool looped;
    265
    266
    0
    retry:
    267
    0
            looped = false;
    268
    269
    0
            if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
    270
    0
                (ctx->fd = fido_hid_unix_open(path)) == -1) {
    271
    0
                    free(ctx);
    272
    0
                    return (NULL);
    273
    0
            }
    274
    275
    0
            while (flock(ctx->fd, LOCK_EX|LOCK_NB) == -1) {
    276
    0
                    if (errno != EWOULDBLOCK) {
    277
    0
                            fido_log_error(errno, "%s: flock", __func__);
    278
    0
                            fido_hid_close(ctx);
    279
    0
                            return (NULL);
    280
    0
                    }
    281
    0
                    looped = true;
    282
    0
                    if (retries++ >= 20) {
    283
    0
                            fido_log_debug("%s: flock timeout", __func__);
    284
    0
                            fido_hid_close(ctx);
    285
    0
                            return (NULL);
    286
    0
                    }
    287
    0
                    interval_ms = retries * 100000000L;
    288
    0
                    tv_pause.tv_sec = interval_ms / 1000000000L;
    289
    0
                    tv_pause.tv_nsec = interval_ms % 1000000000L;
    290
    0
                    if (nanosleep(&tv_pause, NULL) == -1) {
    291
    0
                            fido_log_error(errno, "%s: nanosleep", __func__);
    292
    0
                            fido_hid_close(ctx);
    293
    0
                            return (NULL);
    294
    0
                    }
    295
    0
            }
    296
    297
    0
            if (looped) {
    298
    0
                    fido_log_debug("%s: retrying", __func__);
    299
    0
                    fido_hid_close(ctx);
    300
    0
                    goto retry;
    301
    0
            }
    302
    303
    0
            if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
    304
    0
                get_report_descriptor(ctx->fd, hrd) < 0 ||
    305
    0
                fido_hid_get_report_len(hrd->value, hrd->size, &ctx->report_in_len,
    306
    0
                &ctx->report_out_len) < 0 || ctx->report_in_len == 0 ||
    307
    0
                ctx->report_out_len == 0) {
    308
    0
                    fido_log_debug("%s: using default report sizes", __func__);
    309
    0
                    ctx->report_in_len = CTAP_MAX_REPORT_LEN;
    310
    0
                    ctx->report_out_len = CTAP_MAX_REPORT_LEN;
    311
    0
            }
    312
    313
    0
            free(hrd);
    314
    315
    0
            return (ctx);
    316
    0
    }
    317
    318
    void
    319
    fido_hid_close(void *handle)
    320
    0
    {
    321
    0
            struct hid_linux *ctx = handle;
    322
    323
    0
            if (close(ctx->fd) == -1)
    324
    0
                    fido_log_error(errno, "%s: close", __func__);
    325
    326
    0
            free(ctx);
    327
    0
    }
    328
    329
    int
    330
    fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
    331
    0
    {
    332
    0
            struct hid_linux *ctx = handle;
    333
    334
    0
            ctx->sigmask = *sigmask;
    335
    0
            ctx->sigmaskp = &ctx->sigmask;
    336
    337
    0
            return (FIDO_OK);
    338
    0
    }
    339
    340
    int
    341
    fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
    342
    0
    {
    343
    0
            struct hid_linux        *ctx = handle;
    344
    0
            ssize_t                  r;
    345
    346
    0
            if (len != ctx->report_in_len) {
    347
    0
                    fido_log_debug("%s: len %zu", __func__, len);
    348
    0
                    return (-1);
    349
    0
            }
    350
    351
    0
            if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
    352
    0
                    fido_log_debug("%s: fd not ready", __func__);
    353
    0
                    return (-1);
    354
    0
            }
    355
    356
    0
            if ((r = read(ctx->fd, buf, len)) == -1) {
    357
    0
                    fido_log_error(errno, "%s: read", __func__);
    358
    0
                    return (-1);
    359
    0
            }
    360
    361
    0
            if (r < 0 || (size_t)r != len) {
    362
    0
                    fido_log_debug("%s: %zd != %zu", __func__, r, len);
    363
    0
                    return (-1);
    364
    0
            }
    365
    366
    0
            return ((int)r);
    367
    0
    }
    368
    369
    int
    370
    fido_hid_write(void *handle, const unsigned char *buf, size_t len)
    371
    0
    {
    372
    0
            struct hid_linux        *ctx = handle;
    373
    0
            ssize_t                  r;
    374
    375
    0
            if (len != ctx->report_out_len + 1) {
    376
    0
                    fido_log_debug("%s: len %zu", __func__, len);
    377
    0
                    return (-1);
    378
    0
            }
    379
    380
    0
            if ((r = write(ctx->fd, buf, len)) == -1) {
    381
    0
                    fido_log_error(errno, "%s: write", __func__);
    382
    0
                    return (-1);
    383
    0
            }
    384
    385
    0
            if (r < 0 || (size_t)r != len) {
    386
    0
                    fido_log_debug("%s: %zd != %zu", __func__, r, len);
    387
    0
                    return (-1);
    388
    0
            }
    389
    390
    0
            return ((int)r);
    391
    0
    }
    392
    393
    size_t
    394
    fido_hid_report_in_len(void *handle)
    395
    0
    {
    396
    0
            struct hid_linux *ctx = handle;
    397
    398
    0
            return (ctx->report_in_len);
    399
    0
    }
    400
    401
    size_t
    402
    fido_hid_report_out_len(void *handle)
    403
    0
    {
    404
    0
            struct hid_linux *ctx = handle;
    405
    406
    0
            return (ctx->report_out_len);
    407
    0
    }