b9a53a
From 080d3b14470f6ac59f4cfb97a4200ed18df5c260 Mon Sep 17 00:00:00 2001
b9a53a
From: Fabian Henneke <fabian@henneke.me>
b9a53a
Date: Wed, 21 Aug 2019 11:17:59 +0200
b9a53a
Subject: [PATCH] udev: Add id program and rule for FIDO security tokens
b9a53a
b9a53a
Add a fido_id program meant to be run for devices in the hidraw
b9a53a
subsystem via an IMPORT directive. The program parses the HID report
b9a53a
descriptor and assigns the ID_SECURITY_TOKEN environment variable if a
b9a53a
declared usage matches the FIDO_CTAPHID_USAGE declared in the FIDO CTAP
b9a53a
specification. This replaces the previous approach of whitelisting all
b9a53a
known security token models manually.
b9a53a
b9a53a
This commit is accompanied by a test suite and a fuzzer target for the
b9a53a
descriptor parsing routine.
b9a53a
b9a53a
Fixes: #11996.
b9a53a
(cherry picked from commit d45ee2f31a8358db0accde2e7c81777cedadc3c2)
b9a53a
b9a53a
Resolves: #1753369
b9a53a
---
b9a53a
 rules/60-fido-id.rules              |   7 ++
b9a53a
 rules/meson.build                   |   1 +
b9a53a
 src/fuzz/fuzz-fido-id-desc.c        |  23 +++++++
b9a53a
 src/fuzz/fuzz-fido-id-desc.dict     |   6 ++
b9a53a
 src/fuzz/meson.build                |   4 ++
b9a53a
 src/test/meson.build                |   4 ++
b9a53a
 src/test/test-fido-id-desc.c        |  85 +++++++++++++++++++++++
b9a53a
 src/udev/fido_id/fido_id.c          | 103 ++++++++++++++++++++++++++++
b9a53a
 src/udev/fido_id/fido_id_desc.c     |  92 +++++++++++++++++++++++++
b9a53a
 src/udev/fido_id/fido_id_desc.h     |   8 +++
b9a53a
 src/udev/meson.build                |   3 +
b9a53a
 test/fuzz/fuzz-fido-id-desc/crash0  |   1 +
b9a53a
 test/fuzz/fuzz-fido-id-desc/crash1  |   1 +
b9a53a
 test/fuzz/fuzz-fido-id-desc/report0 | Bin 0 -> 71 bytes
b9a53a
 test/fuzz/fuzz-fido-id-desc/report1 | Bin 0 -> 34 bytes
b9a53a
 15 files changed, 338 insertions(+)
b9a53a
 create mode 100644 rules/60-fido-id.rules
b9a53a
 create mode 100644 src/fuzz/fuzz-fido-id-desc.c
b9a53a
 create mode 100644 src/fuzz/fuzz-fido-id-desc.dict
b9a53a
 create mode 100644 src/test/test-fido-id-desc.c
b9a53a
 create mode 100644 src/udev/fido_id/fido_id.c
b9a53a
 create mode 100644 src/udev/fido_id/fido_id_desc.c
b9a53a
 create mode 100644 src/udev/fido_id/fido_id_desc.h
b9a53a
 create mode 100644 test/fuzz/fuzz-fido-id-desc/crash0
b9a53a
 create mode 100644 test/fuzz/fuzz-fido-id-desc/crash1
b9a53a
 create mode 100644 test/fuzz/fuzz-fido-id-desc/report0
b9a53a
 create mode 100644 test/fuzz/fuzz-fido-id-desc/report1
b9a53a
b9a53a
diff --git a/rules/60-fido-id.rules b/rules/60-fido-id.rules
b9a53a
new file mode 100644
b9a53a
index 0000000000..fcf5079704
b9a53a
--- /dev/null
b9a53a
+++ b/rules/60-fido-id.rules
b9a53a
@@ -0,0 +1,7 @@
b9a53a
+# do not edit this file, it will be overwritten on update
b9a53a
+
b9a53a
+ACTION=="remove", GOTO="fido_id_end"
b9a53a
+
b9a53a
+SUBSYSTEM=="hidraw", IMPORT{program}="fido_id"
b9a53a
+
b9a53a
+LABEL="fido_id_end"
b9a53a
diff --git a/rules/meson.build b/rules/meson.build
b9a53a
index b6aae596b6..6363f8bf2e 100644
b9a53a
--- a/rules/meson.build
b9a53a
+++ b/rules/meson.build
b9a53a
@@ -7,6 +7,7 @@ rules = files('''
b9a53a
         60-cdrom_id.rules
b9a53a
         60-drm.rules
b9a53a
         60-evdev.rules
b9a53a
+        60-fido-id.rules
b9a53a
         60-input-id.rules
b9a53a
         60-persistent-alsa.rules
b9a53a
         60-persistent-input.rules
b9a53a
diff --git a/src/fuzz/fuzz-fido-id-desc.c b/src/fuzz/fuzz-fido-id-desc.c
b9a53a
new file mode 100644
b9a53a
index 0000000000..cf98dee044
b9a53a
--- /dev/null
b9a53a
+++ b/src/fuzz/fuzz-fido-id-desc.c
b9a53a
@@ -0,0 +1,23 @@
b9a53a
+/* SPDX-License-Identifier: LGPL-2.1+ */
b9a53a
+
b9a53a
+#include <linux/hid.h>
b9a53a
+#include <stdbool.h>
b9a53a
+#include <stdint.h>
b9a53a
+#include <stdlib.h>
b9a53a
+
b9a53a
+#include "fido_id/fido_id_desc.h"
b9a53a
+#include "fuzz.h"
b9a53a
+#include "log.h"
b9a53a
+
b9a53a
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
b9a53a
+        /* We don't want to fill the logs with messages about parse errors.
b9a53a
+         * Disable most logging if not running standalone */
b9a53a
+        if (!getenv("SYSTEMD_LOG_LEVEL"))
b9a53a
+                log_set_max_level(LOG_CRIT);
b9a53a
+
b9a53a
+        if (size > HID_MAX_DESCRIPTOR_SIZE)
b9a53a
+                return 0;
b9a53a
+        (void) is_fido_security_token_desc(data, size);
b9a53a
+
b9a53a
+        return 0;
b9a53a
+}
b9a53a
diff --git a/src/fuzz/fuzz-fido-id-desc.dict b/src/fuzz/fuzz-fido-id-desc.dict
b9a53a
new file mode 100644
b9a53a
index 0000000000..d2d2679e18
b9a53a
--- /dev/null
b9a53a
+++ b/src/fuzz/fuzz-fido-id-desc.dict
b9a53a
@@ -0,0 +1,6 @@
b9a53a
+"\xfe"
b9a53a
+"\x00"
b9a53a
+"\x01"
b9a53a
+"\xf1"
b9a53a
+"\xd0"
b9a53a
+"\xf1\xd0\x00\x01"
b9a53a
diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build
b9a53a
index 1dbe28e57e..483a952421 100644
b9a53a
--- a/src/fuzz/meson.build
b9a53a
+++ b/src/fuzz/meson.build
b9a53a
@@ -47,4 +47,8 @@ fuzzers += [
b9a53a
          [libsystemd_journal_remote,
b9a53a
           libshared],
b9a53a
          []],
b9a53a
+        [['src/fuzz/fuzz-fido-id-desc.c',
b9a53a
+          'src/udev/fido_id/fido_id_desc.c'],
b9a53a
+         [],
b9a53a
+         []]
b9a53a
 ]
b9a53a
diff --git a/src/test/meson.build b/src/test/meson.build
b9a53a
index 0998f59897..4259421f98 100644
b9a53a
--- a/src/test/meson.build
b9a53a
+++ b/src/test/meson.build
b9a53a
@@ -663,6 +663,10 @@ tests += [
b9a53a
         [['src/test/test-bus-util.c'],
b9a53a
          [],
b9a53a
          []],
b9a53a
+        [['src/test/test-fido-id-desc.c',
b9a53a
+          'src/udev/fido_id/fido_id_desc.c'],
b9a53a
+         [],
b9a53a
+         []],
b9a53a
 ]
b9a53a
 
b9a53a
 ############################################################
b9a53a
diff --git a/src/test/test-fido-id-desc.c b/src/test/test-fido-id-desc.c
b9a53a
new file mode 100644
b9a53a
index 0000000000..cf55dd3266
b9a53a
--- /dev/null
b9a53a
+++ b/src/test/test-fido-id-desc.c
b9a53a
@@ -0,0 +1,85 @@
b9a53a
+/* SPDX-License-Identifier: LGPL-2.1+ */
b9a53a
+
b9a53a
+#include <stdint.h>
b9a53a
+#include <stdlib.h>
b9a53a
+
b9a53a
+#include "fido_id/fido_id_desc.h"
b9a53a
+#include "macro.h"
b9a53a
+
b9a53a
+static void test_is_fido_security_token_desc__fido(void) {
b9a53a
+        static const uint8_t FIDO_HID_DESC_1[] = {
b9a53a
+                0x06, 0xd0, 0xf1, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
b9a53a
+                0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
b9a53a
+                0x40, 0x91, 0x02, 0xc0,
b9a53a
+        };
b9a53a
+        assert_se(is_fido_security_token_desc(FIDO_HID_DESC_1, sizeof(FIDO_HID_DESC_1)) > 0);
b9a53a
+
b9a53a
+        static const uint8_t FIDO_HID_DESC_2[] = {
b9a53a
+                0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25,
b9a53a
+                0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
b9a53a
+                0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91,
b9a53a
+                0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65,
b9a53a
+                0x81, 0x00, 0x09, 0x03, 0x75, 0x08, 0x95, 0x08, 0xb1, 0x02, 0xc0,
b9a53a
+                0x06, 0xd0, 0xf1, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
b9a53a
+                0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
b9a53a
+                0x40, 0x91, 0x02, 0xc0,
b9a53a
+        };
b9a53a
+        assert_se(is_fido_security_token_desc(FIDO_HID_DESC_2, sizeof(FIDO_HID_DESC_2)) > 0);
b9a53a
+}
b9a53a
+
b9a53a
+static void test_is_fido_security_token_desc__non_fido(void) {
b9a53a
+        /* Wrong usage page */
b9a53a
+        static const uint8_t NON_FIDO_HID_DESC_1[] = {
b9a53a
+                0x06, 0xd0, 0xf0, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
b9a53a
+                0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
b9a53a
+                0x40, 0x91, 0x02, 0xc0,
b9a53a
+        };
b9a53a
+        assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_1, sizeof(NON_FIDO_HID_DESC_1)) == 0);
b9a53a
+
b9a53a
+        /* Wrong usage */
b9a53a
+        static const uint8_t NON_FIDO_HID_DESC_2[] = {
b9a53a
+                0x06, 0xd0, 0xf1, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
b9a53a
+                0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
b9a53a
+                0x40, 0x91, 0x02, 0xc0,
b9a53a
+        };
b9a53a
+        assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_2, sizeof(NON_FIDO_HID_DESC_2)) == 0);
b9a53a
+
b9a53a
+        static const uint8_t NON_FIDO_HID_DESC_3[] = {
b9a53a
+                0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25,
b9a53a
+                0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
b9a53a
+                0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91,
b9a53a
+                0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65,
b9a53a
+                0x81, 0x00, 0x09, 0x03, 0x75, 0x08, 0x95, 0x08, 0xb1, 0x02, 0xc0,
b9a53a
+        };
b9a53a
+        assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_3, sizeof(NON_FIDO_HID_DESC_3)) == 0);
b9a53a
+}
b9a53a
+
b9a53a
+static void test_is_fido_security_token_desc__invalid(void) {
b9a53a
+        /* Size coded on 1 byte, but no byte given */
b9a53a
+        static const uint8_t INVALID_HID_DESC_1[] = { 0x01 };
b9a53a
+        assert_se(is_fido_security_token_desc(INVALID_HID_DESC_1, sizeof(INVALID_HID_DESC_1)) < 0);
b9a53a
+
b9a53a
+        /* Size coded on 2 bytes, but only 1 byte given */
b9a53a
+        static const uint8_t INVALID_HID_DESC_2[] = { 0x02, 0x01 };
b9a53a
+        assert_se(is_fido_security_token_desc(INVALID_HID_DESC_2, sizeof(INVALID_HID_DESC_2)) < 0);
b9a53a
+
b9a53a
+        /* Size coded on 4 bytes, but only 3 bytes given */
b9a53a
+        static const uint8_t INVALID_HID_DESC_3[] = { 0x03, 0x01, 0x02, 0x03 };
b9a53a
+        assert_se(is_fido_security_token_desc(INVALID_HID_DESC_3, sizeof(INVALID_HID_DESC_3)) < 0);
b9a53a
+
b9a53a
+        /* Long item without a size byte */
b9a53a
+        static const uint8_t INVALID_HID_DESC_4[] = { 0xfe };
b9a53a
+        assert_se(is_fido_security_token_desc(INVALID_HID_DESC_4, sizeof(INVALID_HID_DESC_4)) < 0);
b9a53a
+
b9a53a
+        /* Usage pages are coded on at most 2 bytes */
b9a53a
+        static const uint8_t INVALID_HID_DESC_5[] = { 0x07, 0x01, 0x02, 0x03, 0x04 };
b9a53a
+        assert_se(is_fido_security_token_desc(INVALID_HID_DESC_5, sizeof(INVALID_HID_DESC_5)) < 0);
b9a53a
+}
b9a53a
+
b9a53a
+int main(int argc, char *argv[]) {
b9a53a
+        test_is_fido_security_token_desc__fido();
b9a53a
+        test_is_fido_security_token_desc__non_fido();
b9a53a
+        test_is_fido_security_token_desc__invalid();
b9a53a
+
b9a53a
+        return EXIT_SUCCESS;
b9a53a
+}
b9a53a
diff --git a/src/udev/fido_id/fido_id.c b/src/udev/fido_id/fido_id.c
b9a53a
new file mode 100644
b9a53a
index 0000000000..7e1cc804f2
b9a53a
--- /dev/null
b9a53a
+++ b/src/udev/fido_id/fido_id.c
b9a53a
@@ -0,0 +1,103 @@
b9a53a
+/* SPDX-License-Identifier: LGPL-2.1+ */
b9a53a
+/*
b9a53a
+ * Identifies FIDO CTAP1 ("U2F")/CTAP2 security tokens based on the usage declared in their report
b9a53a
+ * descriptor and outputs suitable environment variables.
b9a53a
+ *
b9a53a
+ * Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c'
b9a53a
+ */
b9a53a
+
b9a53a
+#include <errno.h>
b9a53a
+#include <fcntl.h>
b9a53a
+#include <linux/hid.h>
b9a53a
+#include <stdio.h>
b9a53a
+#include <stdlib.h>
b9a53a
+#include <sys/types.h>
b9a53a
+#include <unistd.h>
b9a53a
+
b9a53a
+#include "sd-device.h"
b9a53a
+
b9a53a
+#include "device-internal.h"
b9a53a
+#include "device-private.h"
b9a53a
+#include "device-util.h"
b9a53a
+#include "fd-util.h"
b9a53a
+#include "fido_id_desc.h"
b9a53a
+#include "log.h"
b9a53a
+#include "macro.h"
b9a53a
+#include "path-util.h"
b9a53a
+#include "string-util.h"
b9a53a
+#include "udev-util.h"
b9a53a
+
b9a53a
+static int run(int argc, char **argv) {
b9a53a
+        _cleanup_(sd_device_unrefp) struct sd_device *device = NULL;
b9a53a
+        _cleanup_free_ char *desc_path = NULL;
b9a53a
+        _cleanup_close_ int fd = -1;
b9a53a
+
b9a53a
+        struct sd_device *hid_device;
b9a53a
+        const char *sys_path;
b9a53a
+        uint8_t desc[HID_MAX_DESCRIPTOR_SIZE];
b9a53a
+        ssize_t desc_len;
b9a53a
+
b9a53a
+        int r;
b9a53a
+
b9a53a
+        log_set_target(LOG_TARGET_AUTO);
b9a53a
+        udev_parse_config();
b9a53a
+        log_parse_environment();
b9a53a
+        log_open();
b9a53a
+
b9a53a
+        if (argc > 2)
b9a53a
+                return log_error_errno(EINVAL, "Usage: %s [SYSFS_PATH]", program_invocation_short_name);
b9a53a
+
b9a53a
+        if (argc == 1) {
b9a53a
+                r = device_new_from_strv(&device, environ);
b9a53a
+                if (r < 0)
b9a53a
+                        return log_error_errno(r, "Failed to get current device from environment: %m");
b9a53a
+        } else {
b9a53a
+                r = sd_device_new_from_syspath(&device, argv[1]);
b9a53a
+                if (r < 0)
b9a53a
+                        return log_error_errno(r, "Failed to get device from syspath: %m");
b9a53a
+        }
b9a53a
+
b9a53a
+        r = sd_device_get_parent(device, &hid_device);
b9a53a
+        if (r < 0)
b9a53a
+                return log_device_error_errno(device, r, "Failed to get parent HID device: %m");
b9a53a
+
b9a53a
+        r = sd_device_get_syspath(hid_device, &sys_path);
b9a53a
+        if (r < 0)
b9a53a
+                return log_device_error_errno(hid_device, r, "Failed to get syspath for HID device: %m");
b9a53a
+
b9a53a
+        desc_path = path_join(NULL, sys_path, "report_descriptor");
b9a53a
+        if (!desc_path)
b9a53a
+                return log_oom();
b9a53a
+
b9a53a
+        fd = open(desc_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
b9a53a
+        if (fd < 0)
b9a53a
+                return log_device_error_errno(hid_device, errno,
b9a53a
+                                              "Failed to open report descriptor at '%s': %m", desc_path);
b9a53a
+
b9a53a
+        desc_len = read(fd, desc, sizeof(desc));
b9a53a
+        if (desc_len < 0)
b9a53a
+                return log_device_error_errno(hid_device, errno,
b9a53a
+                                              "Failed to read report descriptor at '%s': %m", desc_path);
b9a53a
+        if (desc_len == 0)
b9a53a
+                return log_device_debug_errno(hid_device, EINVAL,
b9a53a
+                                              "Empty report descriptor at '%s'.", desc_path);
b9a53a
+
b9a53a
+        r = is_fido_security_token_desc(desc, desc_len);
b9a53a
+        if (r < 0)
b9a53a
+                return log_device_debug_errno(hid_device, r,
b9a53a
+                                              "Failed to parse report descriptor at '%s'.", desc_path);
b9a53a
+        if (r > 0) {
b9a53a
+                printf("ID_FIDO_TOKEN=1\n");
b9a53a
+                printf("ID_SECURITY_TOKEN=1\n");
b9a53a
+        }
b9a53a
+
b9a53a
+        return 0;
b9a53a
+}
b9a53a
+
b9a53a
+int main(int argc, char *argv[]) {
b9a53a
+    int r;
b9a53a
+
b9a53a
+    r = run(argc, argv);
b9a53a
+
b9a53a
+    return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
b9a53a
+}
b9a53a
diff --git a/src/udev/fido_id/fido_id_desc.c b/src/udev/fido_id/fido_id_desc.c
b9a53a
new file mode 100644
b9a53a
index 0000000000..bbfcf93709
b9a53a
--- /dev/null
b9a53a
+++ b/src/udev/fido_id/fido_id_desc.c
b9a53a
@@ -0,0 +1,92 @@
b9a53a
+/* SPDX-License-Identifier: LGPL-2.1+ */
b9a53a
+/* Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c' */
b9a53a
+
b9a53a
+#include <errno.h>
b9a53a
+#include <stdbool.h>
b9a53a
+#include <stddef.h>
b9a53a
+#include <stdint.h>
b9a53a
+
b9a53a
+#include "fido_id_desc.h"
b9a53a
+
b9a53a
+#define HID_RPTDESC_FIRST_BYTE_LONG_ITEM 0xfeu
b9a53a
+#define HID_RPTDESC_TYPE_GLOBAL 0x1u
b9a53a
+#define HID_RPTDESC_TYPE_LOCAL 0x2u
b9a53a
+#define HID_RPTDESC_TAG_USAGE_PAGE 0x0u
b9a53a
+#define HID_RPTDESC_TAG_USAGE 0x0u
b9a53a
+
b9a53a
+/*
b9a53a
+ * HID usage for FIDO CTAP1 ("U2F") and CTAP2 security tokens.
b9a53a
+ * https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-u2f-u2f_hid.h-v1.0-ps-20141009.txt
b9a53a
+ * https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#usb-discovery
b9a53a
+ * https://www.usb.org/sites/default/files/hutrr48.pdf
b9a53a
+ */
b9a53a
+#define FIDO_FULL_USAGE_CTAPHID 0xf1d00001u
b9a53a
+
b9a53a
+/*
b9a53a
+ * Parses a HID report descriptor and identifies FIDO CTAP1 ("U2F")/CTAP2 security tokens based on their
b9a53a
+ * declared usage.
b9a53a
+ * A positive return value indicates that the report descriptor belongs to a FIDO security token.
b9a53a
+ * https://www.usb.org/sites/default/files/documents/hid1_11.pdf (Section 6.2.2)
b9a53a
+ */
b9a53a
+int is_fido_security_token_desc(const uint8_t *desc, size_t desc_len) {
b9a53a
+        uint32_t usage = 0;
b9a53a
+
b9a53a
+        for (size_t pos = 0; pos < desc_len; ) {
b9a53a
+                uint8_t tag, type, size_code;
b9a53a
+                size_t size;
b9a53a
+                uint32_t value;
b9a53a
+
b9a53a
+                /* Report descriptors consists of short items (1-5 bytes) and long items (3-258 bytes). */
b9a53a
+                if (desc[pos] == HID_RPTDESC_FIRST_BYTE_LONG_ITEM) {
b9a53a
+                        /* No long items are defined in the spec; skip them.
b9a53a
+                         * The length of the data in a long item is contained in the byte after the long
b9a53a
+                         * item tag. The header consists of three bytes: special long item tag, length,
b9a53a
+                         * actual tag. */
b9a53a
+                        if (pos + 1 >= desc_len)
b9a53a
+                                return -EINVAL;
b9a53a
+                        pos += desc[pos + 1] + 3;
b9a53a
+                        continue;
b9a53a
+                }
b9a53a
+
b9a53a
+                /* The first byte of a short item encodes tag, type and size. */
b9a53a
+                tag = desc[pos] >> 4;          /* Bits 7 to 4 */
b9a53a
+                type = (desc[pos] >> 2) & 0x3; /* Bits 3 and 2 */
b9a53a
+                size_code = desc[pos] & 0x3;   /* Bits 1 and 0 */
b9a53a
+                /* Size is coded as follows:
b9a53a
+                 * 0 -> 0 bytes, 1 -> 1 byte, 2 -> 2 bytes, 3 -> 4 bytes
b9a53a
+                 */
b9a53a
+                size = size_code < 3 ? size_code : 4;
b9a53a
+                /* Consume header byte. */
b9a53a
+                pos++;
b9a53a
+
b9a53a
+                /* Extract the item value coded on size bytes. */
b9a53a
+                if (pos + size > desc_len)
b9a53a
+                        return -EINVAL;
b9a53a
+                value = 0;
b9a53a
+                for (size_t i = 0; i < size; i++)
b9a53a
+                        value |= (uint32_t) desc[pos + i] << (8 * i);
b9a53a
+                /* Consume value bytes. */
b9a53a
+                pos += size;
b9a53a
+
b9a53a
+                if (type == HID_RPTDESC_TYPE_GLOBAL && tag == HID_RPTDESC_TAG_USAGE_PAGE) {
b9a53a
+                        /* A usage page is a 16 bit value coded on at most 16 bits. */
b9a53a
+                        if (size > 2)
b9a53a
+                                return -EINVAL;
b9a53a
+                        /* A usage page sets the upper 16 bits of a following usage. */
b9a53a
+                        usage = (value & 0x0000ffffu) << 16;
b9a53a
+                }
b9a53a
+
b9a53a
+                if (type == HID_RPTDESC_TYPE_LOCAL && tag == HID_RPTDESC_TAG_USAGE) {
b9a53a
+                        /* A usage is a 32 bit value, but is prepended with the current usage page if
b9a53a
+                         * coded on less than 4 bytes (that is, at most 2 bytes). */
b9a53a
+                        if (size == 4)
b9a53a
+                                usage = value;
b9a53a
+                        else
b9a53a
+                                usage = (usage & 0xffff0000u) | (value & 0x0000ffffu);
b9a53a
+                        if (usage == FIDO_FULL_USAGE_CTAPHID)
b9a53a
+                                return 1;
b9a53a
+                }
b9a53a
+        }
b9a53a
+
b9a53a
+        return 0;
b9a53a
+}
b9a53a
diff --git a/src/udev/fido_id/fido_id_desc.h b/src/udev/fido_id/fido_id_desc.h
b9a53a
new file mode 100644
b9a53a
index 0000000000..c813a3a454
b9a53a
--- /dev/null
b9a53a
+++ b/src/udev/fido_id/fido_id_desc.h
b9a53a
@@ -0,0 +1,8 @@
b9a53a
+/* SPDX-License-Identifier: LGPL-2.1+ */
b9a53a
+
b9a53a
+#pragma once
b9a53a
+
b9a53a
+#include <stddef.h>
b9a53a
+#include <stdint.h>
b9a53a
+
b9a53a
+int is_fido_security_token_desc(const uint8_t *desc, size_t desc_len);
b9a53a
diff --git a/src/udev/meson.build b/src/udev/meson.build
b9a53a
index 3bcd2bd3d7..5931a6da7d 100644
b9a53a
--- a/src/udev/meson.build
b9a53a
+++ b/src/udev/meson.build
b9a53a
@@ -160,6 +160,9 @@ libudev_core = static_library(
b9a53a
 foreach prog : [['ata_id/ata_id.c'],
b9a53a
                 ['cdrom_id/cdrom_id.c'],
b9a53a
                 ['collect/collect.c'],
b9a53a
+                ['fido_id/fido_id.c',
b9a53a
+                 'fido_id/fido_id_desc.c',
b9a53a
+                 'fido_id/fido_id_desc.h'],
b9a53a
                 ['scsi_id/scsi_id.c',
b9a53a
                  'scsi_id/scsi_id.h',
b9a53a
                  'scsi_id/scsi_serial.c',
b9a53a
diff --git a/test/fuzz/fuzz-fido-id-desc/crash0 b/test/fuzz/fuzz-fido-id-desc/crash0
b9a53a
new file mode 100644
b9a53a
index 0000000000..e066656502
b9a53a
--- /dev/null
b9a53a
+++ b/test/fuzz/fuzz-fido-id-desc/crash0
b9a53a
@@ -0,0 +1 @@
b9a53a
+????Ì?
b9a53a
\ No newline at end of file
b9a53a
diff --git a/test/fuzz/fuzz-fido-id-desc/crash1 b/test/fuzz/fuzz-fido-id-desc/crash1
b9a53a
new file mode 100644
b9a53a
index 0000000000..aef3e18335
b9a53a
--- /dev/null
b9a53a
+++ b/test/fuzz/fuzz-fido-id-desc/crash1
b9a53a
@@ -0,0 +1 @@
b9a53a
+øûøûûÜ
b9a53a
\ No newline at end of file
b9a53a
diff --git a/test/fuzz/fuzz-fido-id-desc/report0 b/test/fuzz/fuzz-fido-id-desc/report0
b9a53a
new file mode 100644
b9a53a
index 0000000000000000000000000000000000000000..48757cba682ffddd5a1ddd8988bb8bcdc7db0a7a
b9a53a
GIT binary patch
b9a53a
literal 71
b9a53a
zcmZQ&<YZgO$jUDHK=ZjMgDPVw<5Z4Drm2jj9F2@qSxXsNIV2f1Sto)-m?tt$Wh>
b9a53a
Xs!9c_XV6S-WZ+~j<(SH`k?8;c6l@Pq
b9a53a
b9a53a
literal 0
b9a53a
HcmV?d00001
b9a53a
b9a53a
diff --git a/test/fuzz/fuzz-fido-id-desc/report1 b/test/fuzz/fuzz-fido-id-desc/report1
b9a53a
new file mode 100644
b9a53a
index 0000000000000000000000000000000000000000..b70b7fb871aeccf4074ccbd20e3cdbaca42e23b3
b9a53a
GIT binary patch
b9a53a
literal 34
b9a53a
icmZR(@R5^oAtR@PD1+L6hEk5H4vkElig3
b9a53a
b9a53a
literal 0
b9a53a
HcmV?d00001
b9a53a