mirror of
https://github.com/systemd/systemd.git
synced 2025-01-09 01:18:19 +03:00
udev: Add id program and rule for FIDO security tokens
Add a fido_id program meant to be run for devices in the hidraw subsystem via an IMPORT directive. The program parses the HID report descriptor and assigns the ID_SECURITY_TOKEN environment variable if a declared usage matches the FIDO_CTAPHID_USAGE declared in the FIDO CTAP specification. This replaces the previous approach of whitelisting all known security token models manually. This commit is accompanied by a test suite and a fuzzer target for the descriptor parsing routine. Fixes: #11996.
This commit is contained in:
parent
1e19f5ac0d
commit
d45ee2f31a
7
rules/60-fido-id.rules
Normal file
7
rules/60-fido-id.rules
Normal file
@ -0,0 +1,7 @@
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="fido_id_end"
|
||||
|
||||
SUBSYSTEM=="hidraw", IMPORT{program}="fido_id"
|
||||
|
||||
LABEL="fido_id_end"
|
@ -5,6 +5,7 @@ rules = files('''
|
||||
60-cdrom_id.rules
|
||||
60-drm.rules
|
||||
60-evdev.rules
|
||||
60-fido-id.rules
|
||||
60-input-id.rules
|
||||
60-persistent-alsa.rules
|
||||
60-persistent-input.rules
|
||||
|
23
src/fuzz/fuzz-fido-id-desc.c
Normal file
23
src/fuzz/fuzz-fido-id-desc.c
Normal file
@ -0,0 +1,23 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <linux/hid.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fido_id/fido_id_desc.h"
|
||||
#include "fuzz.h"
|
||||
#include "log.h"
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
/* We don't want to fill the logs with messages about parse errors.
|
||||
* Disable most logging if not running standalone */
|
||||
if (!getenv("SYSTEMD_LOG_LEVEL"))
|
||||
log_set_max_level(LOG_CRIT);
|
||||
|
||||
if (size > HID_MAX_DESCRIPTOR_SIZE)
|
||||
return 0;
|
||||
(void) is_fido_security_token_desc(data, size);
|
||||
|
||||
return 0;
|
||||
}
|
6
src/fuzz/fuzz-fido-id-desc.dict
Normal file
6
src/fuzz/fuzz-fido-id-desc.dict
Normal file
@ -0,0 +1,6 @@
|
||||
"\xfe"
|
||||
"\x00"
|
||||
"\x01"
|
||||
"\xf1"
|
||||
"\xd0"
|
||||
"\xf1\xd0\x00\x01"
|
@ -146,4 +146,9 @@ fuzzers += [
|
||||
[['src/fuzz/fuzz-time-util.c'],
|
||||
[libshared],
|
||||
[]],
|
||||
|
||||
[['src/fuzz/fuzz-fido-id-desc.c',
|
||||
'src/udev/fido_id/fido_id_desc.c'],
|
||||
[],
|
||||
[]]
|
||||
]
|
||||
|
@ -771,6 +771,11 @@ tests += [
|
||||
[['src/test/test-local-addresses.c'],
|
||||
[],
|
||||
[]],
|
||||
|
||||
[['src/test/test-fido-id-desc.c',
|
||||
'src/udev/fido_id/fido_id_desc.c'],
|
||||
[],
|
||||
[]],
|
||||
]
|
||||
|
||||
############################################################
|
||||
|
85
src/test/test-fido-id-desc.c
Normal file
85
src/test/test-fido-id-desc.c
Normal file
@ -0,0 +1,85 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fido_id/fido_id_desc.h"
|
||||
#include "macro.h"
|
||||
|
||||
static void test_is_fido_security_token_desc__fido(void) {
|
||||
static const uint8_t FIDO_HID_DESC_1[] = {
|
||||
0x06, 0xd0, 0xf1, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
|
||||
0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
|
||||
0x40, 0x91, 0x02, 0xc0,
|
||||
};
|
||||
assert_se(is_fido_security_token_desc(FIDO_HID_DESC_1, sizeof(FIDO_HID_DESC_1)) > 0);
|
||||
|
||||
static const uint8_t FIDO_HID_DESC_2[] = {
|
||||
0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25,
|
||||
0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
|
||||
0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91,
|
||||
0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65,
|
||||
0x81, 0x00, 0x09, 0x03, 0x75, 0x08, 0x95, 0x08, 0xb1, 0x02, 0xc0,
|
||||
0x06, 0xd0, 0xf1, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
|
||||
0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
|
||||
0x40, 0x91, 0x02, 0xc0,
|
||||
};
|
||||
assert_se(is_fido_security_token_desc(FIDO_HID_DESC_2, sizeof(FIDO_HID_DESC_2)) > 0);
|
||||
}
|
||||
|
||||
static void test_is_fido_security_token_desc__non_fido(void) {
|
||||
/* Wrong usage page */
|
||||
static const uint8_t NON_FIDO_HID_DESC_1[] = {
|
||||
0x06, 0xd0, 0xf0, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
|
||||
0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
|
||||
0x40, 0x91, 0x02, 0xc0,
|
||||
};
|
||||
assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_1, sizeof(NON_FIDO_HID_DESC_1)) == 0);
|
||||
|
||||
/* Wrong usage */
|
||||
static const uint8_t NON_FIDO_HID_DESC_2[] = {
|
||||
0x06, 0xd0, 0xf1, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
|
||||
0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
|
||||
0x40, 0x91, 0x02, 0xc0,
|
||||
};
|
||||
assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_2, sizeof(NON_FIDO_HID_DESC_2)) == 0);
|
||||
|
||||
static const uint8_t NON_FIDO_HID_DESC_3[] = {
|
||||
0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25,
|
||||
0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
|
||||
0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91,
|
||||
0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65,
|
||||
0x81, 0x00, 0x09, 0x03, 0x75, 0x08, 0x95, 0x08, 0xb1, 0x02, 0xc0,
|
||||
};
|
||||
assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_3, sizeof(NON_FIDO_HID_DESC_3)) == 0);
|
||||
}
|
||||
|
||||
static void test_is_fido_security_token_desc__invalid(void) {
|
||||
/* Size coded on 1 byte, but no byte given */
|
||||
static const uint8_t INVALID_HID_DESC_1[] = { 0x01 };
|
||||
assert_se(is_fido_security_token_desc(INVALID_HID_DESC_1, sizeof(INVALID_HID_DESC_1)) < 0);
|
||||
|
||||
/* Size coded on 2 bytes, but only 1 byte given */
|
||||
static const uint8_t INVALID_HID_DESC_2[] = { 0x02, 0x01 };
|
||||
assert_se(is_fido_security_token_desc(INVALID_HID_DESC_2, sizeof(INVALID_HID_DESC_2)) < 0);
|
||||
|
||||
/* Size coded on 4 bytes, but only 3 bytes given */
|
||||
static const uint8_t INVALID_HID_DESC_3[] = { 0x03, 0x01, 0x02, 0x03 };
|
||||
assert_se(is_fido_security_token_desc(INVALID_HID_DESC_3, sizeof(INVALID_HID_DESC_3)) < 0);
|
||||
|
||||
/* Long item without a size byte */
|
||||
static const uint8_t INVALID_HID_DESC_4[] = { 0xfe };
|
||||
assert_se(is_fido_security_token_desc(INVALID_HID_DESC_4, sizeof(INVALID_HID_DESC_4)) < 0);
|
||||
|
||||
/* Usage pages are coded on at most 2 bytes */
|
||||
static const uint8_t INVALID_HID_DESC_5[] = { 0x07, 0x01, 0x02, 0x03, 0x04 };
|
||||
assert_se(is_fido_security_token_desc(INVALID_HID_DESC_5, sizeof(INVALID_HID_DESC_5)) < 0);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
test_is_fido_security_token_desc__fido();
|
||||
test_is_fido_security_token_desc__non_fido();
|
||||
test_is_fido_security_token_desc__invalid();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
96
src/udev/fido_id/fido_id.c
Normal file
96
src/udev/fido_id/fido_id.c
Normal file
@ -0,0 +1,96 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
/*
|
||||
* Identifies FIDO CTAP1 ("U2F")/CTAP2 security tokens based on the usage declared in their report
|
||||
* descriptor and outputs suitable environment variables.
|
||||
*
|
||||
* Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c'
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/hid.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "device-internal.h"
|
||||
#include "device-private.h"
|
||||
#include "device-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fido_id_desc.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "main-func.h"
|
||||
#include "path-util.h"
|
||||
#include "string-util.h"
|
||||
#include "udev-util.h"
|
||||
|
||||
static int run(int argc, char **argv) {
|
||||
_cleanup_(sd_device_unrefp) struct sd_device *device = NULL;
|
||||
_cleanup_free_ char *desc_path = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
|
||||
struct sd_device *hid_device;
|
||||
const char *sys_path;
|
||||
uint8_t desc[HID_MAX_DESCRIPTOR_SIZE];
|
||||
ssize_t desc_len;
|
||||
|
||||
int r;
|
||||
|
||||
log_set_target(LOG_TARGET_AUTO);
|
||||
udev_parse_config();
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
if (argc > 2)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: hidraw_id [SYSFS_PATH]");
|
||||
|
||||
if (argc == 1) {
|
||||
r = device_new_from_strv(&device, environ);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get current device from environment: %m");
|
||||
} else {
|
||||
r = sd_device_new_from_syspath(&device, argv[1]);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get device from syspath: %m");
|
||||
}
|
||||
|
||||
r = sd_device_get_parent(device, &hid_device);
|
||||
if (r < 0)
|
||||
return log_device_error_errno(device, r, "Failed to get parent HID device: %m");
|
||||
|
||||
r = sd_device_get_syspath(hid_device, &sys_path);
|
||||
if (r < 0)
|
||||
return log_device_error_errno(hid_device, r, "Failed to get syspath for HID device: %m");
|
||||
|
||||
desc_path = path_join(sys_path, "report_descriptor");
|
||||
if (!desc_path)
|
||||
return log_oom();
|
||||
|
||||
fd = open(desc_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_device_error_errno(hid_device, errno,
|
||||
"Failed to open report descriptor at '%s': %m", desc_path);
|
||||
|
||||
desc_len = read(fd, desc, sizeof(desc));
|
||||
if (desc_len < 0)
|
||||
return log_device_error_errno(hid_device, errno,
|
||||
"Failed to read report descriptor at '%s': %m", desc_path);
|
||||
if (desc_len == 0)
|
||||
return log_device_debug_errno(hid_device, SYNTHETIC_ERRNO(EINVAL),
|
||||
"Empty report descriptor at '%s'.", desc_path);
|
||||
|
||||
r = is_fido_security_token_desc(desc, desc_len);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(hid_device, r,
|
||||
"Failed to parse report descriptor at '%s'.", desc_path);
|
||||
if (r > 0) {
|
||||
printf("ID_FIDO_TOKEN=1\n");
|
||||
printf("ID_SECURITY_TOKEN=1\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
92
src/udev/fido_id/fido_id_desc.c
Normal file
92
src/udev/fido_id/fido_id_desc.c
Normal file
@ -0,0 +1,92 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
/* Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c' */
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "fido_id_desc.h"
|
||||
|
||||
#define HID_RPTDESC_FIRST_BYTE_LONG_ITEM 0xfeu
|
||||
#define HID_RPTDESC_TYPE_GLOBAL 0x1u
|
||||
#define HID_RPTDESC_TYPE_LOCAL 0x2u
|
||||
#define HID_RPTDESC_TAG_USAGE_PAGE 0x0u
|
||||
#define HID_RPTDESC_TAG_USAGE 0x0u
|
||||
|
||||
/*
|
||||
* HID usage for FIDO CTAP1 ("U2F") and CTAP2 security tokens.
|
||||
* https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-u2f-u2f_hid.h-v1.0-ps-20141009.txt
|
||||
* https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#usb-discovery
|
||||
* https://www.usb.org/sites/default/files/hutrr48.pdf
|
||||
*/
|
||||
#define FIDO_FULL_USAGE_CTAPHID 0xf1d00001u
|
||||
|
||||
/*
|
||||
* Parses a HID report descriptor and identifies FIDO CTAP1 ("U2F")/CTAP2 security tokens based on their
|
||||
* declared usage.
|
||||
* A positive return value indicates that the report descriptor belongs to a FIDO security token.
|
||||
* https://www.usb.org/sites/default/files/documents/hid1_11.pdf (Section 6.2.2)
|
||||
*/
|
||||
int is_fido_security_token_desc(const uint8_t *desc, size_t desc_len) {
|
||||
uint32_t usage = 0;
|
||||
|
||||
for (size_t pos = 0; pos < desc_len; ) {
|
||||
uint8_t tag, type, size_code;
|
||||
size_t size;
|
||||
uint32_t value;
|
||||
|
||||
/* Report descriptors consists of short items (1-5 bytes) and long items (3-258 bytes). */
|
||||
if (desc[pos] == HID_RPTDESC_FIRST_BYTE_LONG_ITEM) {
|
||||
/* No long items are defined in the spec; skip them.
|
||||
* The length of the data in a long item is contained in the byte after the long
|
||||
* item tag. The header consists of three bytes: special long item tag, length,
|
||||
* actual tag. */
|
||||
if (pos + 1 >= desc_len)
|
||||
return -EINVAL;
|
||||
pos += desc[pos + 1] + 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The first byte of a short item encodes tag, type and size. */
|
||||
tag = desc[pos] >> 4; /* Bits 7 to 4 */
|
||||
type = (desc[pos] >> 2) & 0x3; /* Bits 3 and 2 */
|
||||
size_code = desc[pos] & 0x3; /* Bits 1 and 0 */
|
||||
/* Size is coded as follows:
|
||||
* 0 -> 0 bytes, 1 -> 1 byte, 2 -> 2 bytes, 3 -> 4 bytes
|
||||
*/
|
||||
size = size_code < 3 ? size_code : 4;
|
||||
/* Consume header byte. */
|
||||
pos++;
|
||||
|
||||
/* Extract the item value coded on size bytes. */
|
||||
if (pos + size > desc_len)
|
||||
return -EINVAL;
|
||||
value = 0;
|
||||
for (size_t i = 0; i < size; i++)
|
||||
value |= (uint32_t) desc[pos + i] << (8 * i);
|
||||
/* Consume value bytes. */
|
||||
pos += size;
|
||||
|
||||
if (type == HID_RPTDESC_TYPE_GLOBAL && tag == HID_RPTDESC_TAG_USAGE_PAGE) {
|
||||
/* A usage page is a 16 bit value coded on at most 16 bits. */
|
||||
if (size > 2)
|
||||
return -EINVAL;
|
||||
/* A usage page sets the upper 16 bits of a following usage. */
|
||||
usage = (value & 0x0000ffffu) << 16;
|
||||
}
|
||||
|
||||
if (type == HID_RPTDESC_TYPE_LOCAL && tag == HID_RPTDESC_TAG_USAGE) {
|
||||
/* A usage is a 32 bit value, but is prepended with the current usage page if
|
||||
* coded on less than 4 bytes (that is, at most 2 bytes). */
|
||||
if (size == 4)
|
||||
usage = value;
|
||||
else
|
||||
usage = (usage & 0xffff0000u) | (value & 0x0000ffffu);
|
||||
if (usage == FIDO_FULL_USAGE_CTAPHID)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
8
src/udev/fido_id/fido_id_desc.h
Normal file
8
src/udev/fido_id/fido_id_desc.h
Normal file
@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int is_fido_security_token_desc(const uint8_t *desc, size_t desc_len);
|
@ -165,6 +165,9 @@ libudev_core = static_library(
|
||||
|
||||
foreach prog : [['ata_id/ata_id.c'],
|
||||
['cdrom_id/cdrom_id.c'],
|
||||
['fido_id/fido_id.c',
|
||||
'fido_id/fido_id_desc.c',
|
||||
'fido_id/fido_id_desc.h'],
|
||||
['scsi_id/scsi_id.c',
|
||||
'scsi_id/scsi_id.h',
|
||||
'scsi_id/scsi_serial.c',
|
||||
|
1
test/fuzz/fuzz-fido-id-desc/crash0
Normal file
1
test/fuzz/fuzz-fido-id-desc/crash0
Normal file
@ -0,0 +1 @@
|
||||
<0F>
|
1
test/fuzz/fuzz-fido-id-desc/crash1
Normal file
1
test/fuzz/fuzz-fido-id-desc/crash1
Normal file
@ -0,0 +1 @@
|
||||
<EFBFBD><EFBFBD>鋿
|
BIN
test/fuzz/fuzz-fido-id-desc/report0
Normal file
BIN
test/fuzz/fuzz-fido-id-desc/report0
Normal file
Binary file not shown.
BIN
test/fuzz/fuzz-fido-id-desc/report1
Normal file
BIN
test/fuzz/fuzz-fido-id-desc/report1
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user