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