From 6836c0e9a2f89dfa9b8908fa4628a9a5ba5888b8 Mon Sep 17 00:00:00 2001 From: Boian Bonev Date: Tue, 4 Oct 2022 17:38:38 +0300 Subject: [PATCH 1/4] Rework dmi_memory_id from systemd --- .gitignore | 2 + configure.ac | 3 + src/Makefile.am | 2 + src/dmi_memory_id/Makefile.am | 16 + src/dmi_memory_id/dmi_memory_id.c | 741 ++++++++++++++++++++++++++++++ src/fido_id/Makefile.am | 19 + src/fido_id/fido_id.c | 95 ++++ src/fido_id/fido_id_desc.c | 92 ++++ src/fido_id/fido_id_desc.h | 8 + src/shared/fileio.c | 348 ++++++++++++++ src/shared/fileio.h | 302 ++++++++++++ src/shared/path-util.c | 75 +++ src/shared/path-util.h | 5 + src/shared/socket-util.h | 127 ++++- src/shared/unaligned.h | 99 ++++ src/shared/util.h | 358 +++++++++++++++ 16 files changed, 2286 insertions(+), 6 deletions(-) create mode 100644 src/dmi_memory_id/Makefile.am create mode 100644 src/dmi_memory_id/dmi_memory_id.c create mode 100644 src/fido_id/Makefile.am create mode 100644 src/fido_id/fido_id.c create mode 100644 src/fido_id/fido_id_desc.c create mode 100644 src/fido_id/fido_id_desc.h create mode 100644 src/shared/unaligned.h diff --git a/.gitignore b/.gitignore index 905a05755..396f43f37 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,8 @@ src/collect/collect src/mtd_probe/mtd_probe src/scsi_id/scsi_id src/v4l_id/v4l_id +src/dmi_memory_id/dmi_memory_id +src/fido_id/fido_id src/udev/keyboard-keys-from-name.gperf src/udev/keyboard-keys-from-name.h diff --git a/configure.ac b/configure.ac index 5bd8bf99e..a67f9c770 100644 --- a/configure.ac +++ b/configure.ac @@ -98,6 +98,7 @@ AC_CHECK_FUNCS( [], [AC_MSG_ERROR([*** POSIX function not found])] ) +AC_CHECK_FUNCS([explicit_bzero]) AC_SEARCH_LIBS([clock_gettime], [rt], [], [AC_MSG_ERROR([*** POSIX librt not found])]) LT_LIB_M @@ -299,6 +300,8 @@ AC_CONFIG_FILES([Makefile src/ata_id/Makefile src/cdrom_id/Makefile src/collect/Makefile + src/dmi_memory_id/Makefile + src/fido_id/Makefile src/mtd_probe/Makefile src/scsi_id/Makefile src/v4l_id/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index d8a75f88c..42fbbddfa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,6 +12,8 @@ PROGRAMS_SUBDIRS += \ ata_id \ cdrom_id \ collect \ + dmi_memory_id \ + fido_id \ scsi_id \ v4l_id diff --git a/src/dmi_memory_id/Makefile.am b/src/dmi_memory_id/Makefile.am new file mode 100644 index 000000000..69605cc6d --- /dev/null +++ b/src/dmi_memory_id/Makefile.am @@ -0,0 +1,16 @@ +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} + +AM_CPPFLAGS = \ + -I $(top_srcdir)/src/shared \ + -I $(top_srcdir)/src/udev \ + -I $(top_srcdir)/src/libudev + +udevlibexec_PROGRAMS = \ + dmi_memory_id + +dmi_memory_id_SOURCES = \ + dmi_memory_id.c + +dmi_memory_id_LDADD = \ + $(top_builddir)/src/libudev/libudev-private.la \ + $(top_builddir)/src/udev/libudev-core.la diff --git a/src/dmi_memory_id/dmi_memory_id.c b/src/dmi_memory_id/dmi_memory_id.c new file mode 100644 index 000000000..accd5f6f5 --- /dev/null +++ b/src/dmi_memory_id/dmi_memory_id.c @@ -0,0 +1,741 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * System Memory information + * + * Copyright (C) 2000-2002 Alan Cox + * Copyright (C) 2002-2020 Jean Delvare + * Copyright (C) 2020 Bastien Nocera + * + * Unless specified otherwise, all references are aimed at the "System + * Management BIOS Reference Specification, Version 3.2.0" document, + * available from http://www.dmtf.org/standards/smbios. + * + * Note to contributors: + * Please reference every value you add or modify, especially if the + * information does not come from the above mentioned specification. + * + * Additional references: + * - Intel AP-485 revision 36 + * "Intel Processor Identification and the CPUID Instruction" + * http://www.intel.com/support/processors/sb/cs-009861.htm + * - DMTF Common Information Model + * CIM Schema version 2.19.1 + * http://www.dmtf.org/standards/cim/ + * - IPMI 2.0 revision 1.0 + * "Intelligent Platform Management Interface Specification" + * http://developer.intel.com/design/servers/ipmi/spec.htm + * - AMD publication #25481 revision 2.28 + * "CPUID Specification" + * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/25481.pdf + * - BIOS Integrity Services Application Programming Interface version 1.0 + * http://www.intel.com/design/archives/wfm/downloads/bisspec.htm + * - DMTF DSP0239 version 1.1.0 + * "Management Component Transport Protocol (MCTP) IDs and Codes" + * http://www.dmtf.org/standards/pmci + * - "TPM Main, Part 2 TPM Structures" + * Specification version 1.2, level 2, revision 116 + * https://trustedcomputinggroup.org/tpm-main-specification/ + * - "PC Client Platform TPM Profile (PTP) Specification" + * Family "2.0", Level 00, Revision 00.43, January 26, 2015 + * https://trustedcomputinggroup.org/pc-client-platform-tpm-profile-ptp-specification/ + * - "RedFish Host Interface Specification" (DMTF DSP0270) + * https://www.dmtf.org/sites/default/files/DSP0270_1.0.1.pdf + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include + +#include "util.h" +#include "libudev.h" +#include "libudev-private.h" +#include "fileio.h" +#include "udev-util.h" +#include "unaligned.h" + +#define SUPPORTED_SMBIOS_VER 0x030300 + +#define OUT_OF_SPEC_STR "" + +#define SYS_FIRMWARE_DIR "/sys/firmware/dmi/tables" +#define SYS_ENTRY_FILE SYS_FIRMWARE_DIR "/smbios_entry_point" +#define SYS_TABLE_FILE SYS_FIRMWARE_DIR "/DMI" + +/* + * Per SMBIOS v2.8.0 and later, all structures assume a little-endian + * ordering convention. + */ +#define WORD(x) (unaligned_read_le16(x)) +#define DWORD(x) (unaligned_read_le32(x)) +#define QWORD(x) (unaligned_read_le64(x)) + +struct dmi_header { + uint8_t type; + uint8_t length; + uint16_t handle; + const uint8_t *data; +}; + +static const char *arg_source_file = NULL; + +static bool verify_checksum(const uint8_t *buf, size_t len) { + uint8_t sum = 0; + + for (size_t a = 0; a < len; a++) + sum += buf[a]; + return sum == 0; +} + +/* + * Type-independant Stuff + */ + +static const char *dmi_string(const struct dmi_header *dm, uint8_t s) { + const char *bp = (const char *) dm->data; + + if (s == 0) + return "Not Specified"; + + bp += dm->length; + for (;s > 1 && !isempty(bp); s--) + bp += strlen(bp) + 1; + + if (isempty(bp)) + return ""; + + return bp; +} + +typedef enum { + MEMORY_SIZE_UNIT_BYTES, + MEMORY_SIZE_UNIT_KB +} MemorySizeUnit; + +static void dmi_print_memory_size( + const char *attr_prefix, const char *attr_suffix, + int slot_num, uint64_t code, MemorySizeUnit unit) { + if (unit == MEMORY_SIZE_UNIT_KB) + code <<= 10; + + if (slot_num >= 0) + printf("%s_%u_%s=%"PRIu64"\n", attr_prefix, slot_num, attr_suffix, code); + else + printf("%s_%s=%"PRIu64"\n", attr_prefix, attr_suffix, code); +} + +/* + * 7.17 Physical Memory Array (Type 16) + */ + +static void dmi_memory_array_location(uint8_t code) { + /* 7.17.1 */ + static const char *location[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "System Board Or Motherboard", + [0x04] = "ISA Add-on Card", + [0x05] = "EISA Add-on Card", + [0x06] = "PCI Add-on Card", + [0x07] = "MCA Add-on Card", + [0x08] = "PCMCIA Add-on Card", + [0x09] = "Proprietary Add-on Card", + [0x0A] = "NuBus", + }; + static const char *location_0xA0[] = { + [0x00] = "PC-98/C20 Add-on Card", /* 0xA0 */ + [0x01] = "PC-98/C24 Add-on Card", /* 0xA1 */ + [0x02] = "PC-98/E Add-on Card", /* 0xA2 */ + [0x03] = "PC-98/Local Bus Add-on Card", /* 0xA3 */ + [0x04] = "CXL Flexbus 1.0", /* 0xA4 */ + }; + const char *str = OUT_OF_SPEC_STR; + + if (code < ELEMENTSOF(location) && location[code]) + str = location[code]; + else if (code >= 0xA0 && code < (ELEMENTSOF(location_0xA0) + 0xA0)) + str = location_0xA0[code - 0xA0]; + + printf("MEMORY_ARRAY_LOCATION=%s\n", str); +} + +static void dmi_memory_array_ec_type(uint8_t code) { + /* 7.17.3 */ + static const char *type[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "None", + [0x04] = "Parity", + [0x05] = "Single-bit ECC", + [0x06] = "Multi-bit ECC", + [0x07] = "CRC", + }; + + if (code != 0x03) /* Do not print "None". */ + printf("MEMORY_ARRAY_EC_TYPE=%s\n", + code < ELEMENTSOF(type) && type[code] ? type[code] : OUT_OF_SPEC_STR); +} + +/* + * 7.18 Memory Device (Type 17) + */ + +static void dmi_memory_device_string( + const char *attr_suffix, unsigned slot_num, + const struct dmi_header *h, uint8_t s) { + char *str; + + str = strdupa_safe(dmi_string(h, s)); + str = strstrip(str); + if (!isempty(str)) + printf("MEMORY_DEVICE_%u_%s=%s\n", slot_num, attr_suffix, str); +} + +static void dmi_memory_device_width( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + + /* If no memory module is present, width may be 0 */ + if (!IN_SET(code, 0, 0xFFFF)) + printf("MEMORY_DEVICE_%u_%s=%u\n", slot_num, attr_suffix, code); +} + +static void dmi_memory_device_size(unsigned slot_num, uint16_t code) { + if (code == 0) + return (void) printf("MEMORY_DEVICE_%u_PRESENT=0\n", slot_num); + if (code == 0xFFFF) + return; + + uint64_t s = code & 0x7FFF; + if (!(code & 0x8000)) + s <<= 10; + dmi_print_memory_size("MEMORY_DEVICE", "SIZE", slot_num, s, MEMORY_SIZE_UNIT_KB); +} + +static void dmi_memory_device_extended_size(unsigned slot_num, uint32_t code) { + uint64_t capacity = (uint64_t) code * 1024 * 1024; + + printf("MEMORY_DEVICE_%u_SIZE=%"PRIu64"\n", slot_num, capacity); +} + +static void dmi_memory_device_rank(unsigned slot_num, uint8_t code) { + code &= 0x0F; + if (code != 0) + printf("MEMORY_DEVICE_%u_RANK=%u\n", slot_num, code); +} + +static void dmi_memory_device_voltage_value( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + if (code == 0) + return; + if (code % 100 != 0) + printf("MEMORY_DEVICE_%u_%s=%g\n", slot_num, attr_suffix, (double)code / 1000); + else + printf("MEMORY_DEVICE_%u_%s=%.1g\n", slot_num, attr_suffix, (double)code / 1000); +} + +static void dmi_memory_device_form_factor(unsigned slot_num, uint8_t code) { + /* 7.18.1 */ + static const char *form_factor[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "SIMM", + [0x04] = "SIP", + [0x05] = "Chip", + [0x06] = "DIP", + [0x07] = "ZIP", + [0x08] = "Proprietary Card", + [0x09] = "DIMM", + [0x0A] = "TSOP", + [0x0B] = "Row Of Chips", + [0x0C] = "RIMM", + [0x0D] = "SODIMM", + [0x0E] = "SRIMM", + [0x0F] = "FB-DIMM", + [0x10] = "Die", + }; + + printf("MEMORY_DEVICE_%u_FORM_FACTOR=%s\n", slot_num, + code < ELEMENTSOF(form_factor) && form_factor[code] ? form_factor[code] : OUT_OF_SPEC_STR); +} + +static void dmi_memory_device_set(unsigned slot_num, uint8_t code) { + if (code == 0xFF) + printf("MEMORY_DEVICE_%u_SET=%s\n", slot_num, "Unknown"); + else if (code != 0) + printf("MEMORY_DEVICE_%u_SET=%"PRIu8"\n", slot_num, code); +} + +static void dmi_memory_device_type(unsigned slot_num, uint8_t code) { + /* 7.18.2 */ + static const char *type[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "DRAM", + [0x04] = "EDRAM", + [0x05] = "VRAM", + [0x06] = "SRAM", + [0x07] = "RAM", + [0x08] = "ROM", + [0x09] = "Flash", + [0x0A] = "EEPROM", + [0x0B] = "FEPROM", + [0x0C] = "EPROM", + [0x0D] = "CDRAM", + [0x0E] = "3DRAM", + [0x0F] = "SDRAM", + [0x10] = "SGRAM", + [0x11] = "RDRAM", + [0x12] = "DDR", + [0x13] = "DDR2", + [0x14] = "DDR2 FB-DIMM", + [0x15] = "Reserved", + [0x16] = "Reserved", + [0x17] = "Reserved", + [0x18] = "DDR3", + [0x19] = "FBD2", + [0x1A] = "DDR4", + [0x1B] = "LPDDR", + [0x1C] = "LPDDR2", + [0x1D] = "LPDDR3", + [0x1E] = "LPDDR4", + [0x1F] = "Logical non-volatile device", + [0x20] = "HBM", + [0x21] = "HBM2", + }; + + printf("MEMORY_DEVICE_%u_TYPE=%s\n", slot_num, + code < ELEMENTSOF(type) && type[code] ? type[code] : OUT_OF_SPEC_STR); +} + +static void dmi_memory_device_type_detail(unsigned slot_num, uint16_t code) { + /* 7.18.3 */ + static const char *detail[] = { + [1] = "Other", + [2] = "Unknown", + [3] = "Fast-paged", + [4] = "Static Column", + [5] = "Pseudo-static", + [6] = "RAMBus", + [7] = "Synchronous", + [8] = "CMOS", + [9] = "EDO", + [10] = "Window DRAM", + [11] = "Cache DRAM", + [12] = "Non-Volatile", + [13] = "Registered (Buffered)", + [14] = "Unbuffered (Unregistered)", + [15] = "LRDIMM", + }; + + if ((code & 0xFFFE) == 0) + printf("MEMORY_DEVICE_%u_TYPE_DETAIL=%s\n", slot_num, "None"); + else { + bool first_element = true; + + printf("MEMORY_DEVICE_%u_TYPE_DETAIL=", slot_num); + for (size_t i = 1; i < ELEMENTSOF(detail); i++) + if (code & (1 << i)) { + printf("%s%s", first_element ? "" : " ", detail[i]); + first_element = false; + } + printf("\n"); + } +} + +static void dmi_memory_device_speed( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + if (code != 0) + printf("MEMORY_DEVICE_%u_%s=%u\n", slot_num, attr_suffix, code); +} + +static void dmi_memory_device_technology(unsigned slot_num, uint8_t code) { + /* 7.18.6 */ + static const char * const technology[] = { + [0x01] = "Other", + [0x02] = "Unknown", + [0x03] = "DRAM", + [0x04] = "NVDIMM-N", + [0x05] = "NVDIMM-F", + [0x06] = "NVDIMM-P", + [0x07] = "Intel Optane DC persistent memory", + }; + + printf("MEMORY_DEVICE_%u_MEMORY_TECHNOLOGY=%s\n", slot_num, + code < ELEMENTSOF(technology) && technology[code] ? technology[code] : OUT_OF_SPEC_STR); +} + +static void dmi_memory_device_operating_mode_capability(unsigned slot_num, uint16_t code) { + /* 7.18.7 */ + static const char * const mode[] = { + [1] = "Other", + [2] = "Unknown", + [3] = "Volatile memory", + [4] = "Byte-accessible persistent memory", + [5] = "Block-accessible persistent memory", + }; + + if ((code & 0xFFFE) != 0) { + bool first_element = true; + + printf("MEMORY_DEVICE_%u_MEMORY_OPERATING_MODE_CAPABILITY=", slot_num); + for (size_t i = 1; i < ELEMENTSOF(mode); i++) + if (code & (1 << i)) { + printf("%s%s", first_element ? "" : " ", mode[i]); + first_element = false; + } + printf("\n"); + } +} + +static void dmi_memory_device_manufacturer_id( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + /* 7.18.8 */ + /* 7.18.10 */ + /* LSB is 7-bit Odd Parity number of continuation codes */ + if (code != 0) + printf("MEMORY_DEVICE_%u_%s=Bank %d, Hex 0x%02X\n", slot_num, attr_suffix, + (code & 0x7F) + 1, code >> 8); +} + +static void dmi_memory_device_product_id( + const char *attr_suffix, + unsigned slot_num, uint16_t code) { + /* 7.18.9 */ + /* 7.18.11 */ + if (code != 0) + printf("MEMORY_DEVICE_%u_%s=0x%04X\n", slot_num, attr_suffix, code); +} + +static void dmi_memory_device_size_detail( + const char *attr_suffix, + unsigned slot_num, uint64_t code) { + /* 7.18.12 */ + /* 7.18.13 */ + if (!IN_SET(code, 0x0LU, 0xFFFFFFFFFFFFFFFFLU)) + dmi_print_memory_size("MEMORY_DEVICE", attr_suffix, slot_num, code, MEMORY_SIZE_UNIT_BYTES); +} + +static void dmi_decode(const struct dmi_header *h, + unsigned *next_slot_num) { + const uint8_t *data = h->data; + unsigned slot_num; + + /* + * Note: DMI types 37 and 42 are untested + */ + switch (h->type) { + case 16: /* 7.17 Physical Memory Array */ + log_debug("Physical Memory Array"); + if (h->length < 0x0F) + break; + + if (data[0x05] != 0x03) /* 7.17.2, Use == "System Memory" */ + break; + + log_debug("Use: System Memory"); + dmi_memory_array_location(data[0x04]); + dmi_memory_array_ec_type(data[0x06]); + if (DWORD(data + 0x07) != 0x80000000) + dmi_print_memory_size("MEMORY_ARRAY", "MAX_CAPACITY", -1, DWORD(data + 0x07), MEMORY_SIZE_UNIT_KB); + else if (h->length >= 0x17) + dmi_print_memory_size("MEMORY_ARRAY", "MAX_CAPACITY", -1, QWORD(data + 0x0F), MEMORY_SIZE_UNIT_BYTES); + + break; + + case 17: /* 7.18 Memory Device */ + slot_num = *next_slot_num; + *next_slot_num = slot_num + 1; + + log_debug("Memory Device: %u", slot_num); + if (h->length < 0x15) + break; + + dmi_memory_device_width("TOTAL_WIDTH", slot_num, WORD(data + 0x08)); + dmi_memory_device_width("DATA_WIDTH", slot_num, WORD(data + 0x0A)); + if (h->length >= 0x20 && WORD(data + 0x0C) == 0x7FFF) + dmi_memory_device_extended_size(slot_num, DWORD(data + 0x1C)); + else + dmi_memory_device_size(slot_num, WORD(data + 0x0C)); + dmi_memory_device_form_factor(slot_num, data[0x0E]); + dmi_memory_device_set(slot_num, data[0x0F]); + dmi_memory_device_string("LOCATOR", slot_num, h, data[0x10]); + dmi_memory_device_string("BANK_LOCATOR", slot_num, h, data[0x11]); + dmi_memory_device_type(slot_num, data[0x12]); + dmi_memory_device_type_detail(slot_num, WORD(data + 0x13)); + if (h->length < 0x17) + break; + + dmi_memory_device_speed("SPEED_MTS", slot_num, WORD(data + 0x15)); + if (h->length < 0x1B) + break; + + dmi_memory_device_string("MANUFACTURER", slot_num, h, data[0x17]); + dmi_memory_device_string("SERIAL_NUMBER", slot_num, h, data[0x18]); + dmi_memory_device_string("ASSET_TAG", slot_num, h, data[0x19]); + dmi_memory_device_string("PART_NUMBER", slot_num, h, data[0x1A]); + if (h->length < 0x1C) + break; + + dmi_memory_device_rank(slot_num, data[0x1B]); + if (h->length < 0x22) + break; + + dmi_memory_device_speed("CONFIGURED_SPEED_MTS", slot_num, WORD(data + 0x20)); + if (h->length < 0x28) + break; + + dmi_memory_device_voltage_value("MINIMUM_VOLTAGE", slot_num, WORD(data + 0x22)); + dmi_memory_device_voltage_value("MAXIMUM_VOLTAGE", slot_num, WORD(data + 0x24)); + dmi_memory_device_voltage_value("CONFIGURED_VOLTAGE", slot_num, WORD(data + 0x26)); + if (h->length < 0x34) + break; + + dmi_memory_device_technology(slot_num, data[0x28]); + dmi_memory_device_operating_mode_capability(slot_num, WORD(data + 0x29)); + dmi_memory_device_string("FIRMWARE_VERSION", slot_num, h, data[0x2B]); + dmi_memory_device_manufacturer_id("MODULE_MANUFACTURER_ID", slot_num, WORD(data + 0x2C)); + dmi_memory_device_product_id("MODULE_PRODUCT_ID", slot_num, WORD(data + 0x2E)); + dmi_memory_device_manufacturer_id("MEMORY_SUBSYSTEM_CONTROLLER_MANUFACTURER_ID", + slot_num, WORD(data + 0x30)); + dmi_memory_device_product_id("MEMORY_SUBSYSTEM_CONTROLLER_PRODUCT_ID", + slot_num, WORD(data + 0x32)); + if (h->length < 0x3C) + break; + + dmi_memory_device_size_detail("NON_VOLATILE_SIZE", slot_num, QWORD(data + 0x34)); + if (h->length < 0x44) + break; + + dmi_memory_device_size_detail("VOLATILE_SIZE", slot_num, QWORD(data + 0x3C)); + if (h->length < 0x4C) + break; + + dmi_memory_device_size_detail("CACHE_SIZE", slot_num, QWORD(data + 0x44)); + if (h->length < 0x54) + break; + + dmi_memory_device_size_detail("LOGICAL_SIZE", slot_num, QWORD(data + 0x4C)); + + break; + } +} + +static void dmi_table_decode(const uint8_t *buf, size_t len, uint16_t num) { + const uint8_t *data = buf; + unsigned next_slot_num = 0; + + /* 4 is the length of an SMBIOS structure header */ + for (uint16_t i = 0; (i < num || num == 0) && data + 4 <= buf + len; i++) { + struct dmi_header h = (struct dmi_header) { + .type = data[0], + .length = data[1], + .handle = WORD(data + 2), + .data = data, + }; + bool display = !IN_SET(h.type, 126, 127); + const uint8_t *next; + + /* If a short entry is found (less than 4 bytes), not only it + * is invalid, but we cannot reliably locate the next entry. + * Better stop at this point, and let the user know their + * table is broken. */ + if (h.length < 4) + break; + + /* In quiet mode, stop decoding at end of table marker */ + if (h.type == 127) + break; + + /* Look for the next handle */ + next = data + h.length; + while ((size_t)(next - buf + 1) < len && (next[0] != 0 || next[1] != 0)) + next++; + next += 2; + + /* Make sure the whole structure fits in the table */ + if ((size_t)(next - buf) > len) + break; + + if (display) + dmi_decode(&h, &next_slot_num); + + data = next; + } + if (next_slot_num > 0) + printf("MEMORY_ARRAY_NUM_DEVICES=%u\n", next_slot_num); +} + +static int dmi_table(int64_t base, uint32_t len, uint16_t num, const char *devmem, bool no_file_offset) { + _cleanup_free_ uint8_t *buf = NULL; + size_t size; + int r; + + /* + * When reading from sysfs or from a dump file, the file may be + * shorter than announced. For SMBIOS v3 this is expected, as we + * only know the maximum table size, not the actual table size. + * For older implementations (and for SMBIOS v3 too), this + * would be the result of the kernel truncating the table on + * parse error. + */ + r = read_full_file_full(AT_FDCWD, devmem, no_file_offset ? 0 : base, len, + 0, NULL, (char **) &buf, &size); + if (r < 0) + return log_error_errno(r, "Failed to read table: %m"); + + dmi_table_decode(buf, size, num); + + return 0; +} + +/* Same thing for SMBIOS3 entry points */ +static int smbios3_decode(const uint8_t *buf, const char *devmem, bool no_file_offset) { + uint64_t offset; + + /* Don't let checksum run beyond the buffer */ + if (buf[0x06] > 0x20) + return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), + "Entry point length too large (%"PRIu8" bytes, expected %u).", + buf[0x06], 0x18U); + + if (!verify_checksum(buf, buf[0x06])) + return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), "Failed to verify checksum."); + + offset = QWORD(buf + 0x10); + +#if __SIZEOF_SIZE_T__ != 8 + if (!no_file_offset && (offset >> 32) != 0) + return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), "64-bit addresses not supported on 32-bit systems."); +#endif + + return dmi_table(offset, DWORD(buf + 0x0C), 0, devmem, no_file_offset); +} + +static int smbios_decode(const uint8_t *buf, const char *devmem, bool no_file_offset) { + /* Don't let checksum run beyond the buffer */ + if (buf[0x05] > 0x20) + return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), + "Entry point length too large (%"PRIu8" bytes, expected %u).", + buf[0x05], 0x1FU); + + if (!verify_checksum(buf, buf[0x05]) + || memcmp(buf + 0x10, "_DMI_", 5) != 0 + || !verify_checksum(buf + 0x10, 0x0F)) + return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), "Failed to verify checksum."); + + return dmi_table(DWORD(buf + 0x18), WORD(buf + 0x16), WORD(buf + 0x1C), + devmem, no_file_offset); +} + +static int legacy_decode(const uint8_t *buf, const char *devmem, bool no_file_offset) { + if (!verify_checksum(buf, 0x0F)) + return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), "Failed to verify checksum."); + + return dmi_table(DWORD(buf + 0x08), WORD(buf + 0x06), WORD(buf + 0x0C), + devmem, no_file_offset); +} + +static int help(void) { + printf("Usage: %s [options]\n" + " -F,--from-dump FILE read DMI information from a binary file\n" + " -h,--help print this help text\n\n", + program_invocation_short_name); + return 0; +} + +static int parse_argv(int argc, char * const *argv) { + static const struct option options[] = { + { "from-dump", required_argument, NULL, 'F' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + int c; + + while ((c = getopt_long(argc, argv, "F:hV", options, NULL)) >= 0) + switch (c) { + case 'F': + arg_source_file = optarg; + break; + case 'V': + printf("%s\n", VERSION); + return 0; + case 'h': + return help(); + case '?': + return -EINVAL; + default: + assert_not_reached("Unknown option"); + } + + return 1; +} + +int main(int argc, char* const* argv) { + _cleanup_udev_unref_ struct udev *udev; + _cleanup_free_ uint8_t *buf = NULL; + bool no_file_offset = false; + size_t size; + int r = 1; + + log_set_target(LOG_TARGET_AUTO); + //udev_parse_config(); + //log_parse_environment(); + log_open(); + + udev = udev_new(); + if (udev == NULL) + goto exit; + + /* + r = get_file_options(udev, NULL, NULL, &newargc, &newargv); + if (r < 0) { + r = 1; + goto exit; + } + if (r == 0) { + assert(newargv); + + if (set_options(udev, newargc, newargv, maj_min_dev) < 0) { + r = 2; + goto exit; + } + } + */ + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + /* Read from dump if so instructed */ + r = read_full_file_full(AT_FDCWD, + arg_source_file ?: SYS_ENTRY_FILE, + 0, 0x20, 0, NULL, (char **) &buf, &size); + if (r < 0) + return log_full_errno(!arg_source_file && r == -ENOENT ? LOG_DEBUG : LOG_ERR, + r, "Reading \"%s\" failed: %m", + arg_source_file ?: SYS_ENTRY_FILE); + + if (!arg_source_file) { + arg_source_file = SYS_TABLE_FILE; + no_file_offset = true; + } + + if (size >= 24 && memory_startswith(buf, size, "_SM3_")) + return smbios3_decode(buf, arg_source_file, no_file_offset); + if (size >= 31 && memory_startswith(buf, size, "_SM_")) + return smbios_decode(buf, arg_source_file, no_file_offset); + if (size >= 15 && memory_startswith(buf, size, "_DMI_")) + return legacy_decode(buf, arg_source_file, no_file_offset); + + return -EINVAL; +exit: + return r; +} + diff --git a/src/fido_id/Makefile.am b/src/fido_id/Makefile.am new file mode 100644 index 000000000..255c8393b --- /dev/null +++ b/src/fido_id/Makefile.am @@ -0,0 +1,19 @@ +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} + +AM_CPPFLAGS = \ + -I $(top_srcdir)/src/shared \ + -I $(top_srcdir)/src/libudev \ + -I $(top_srcdir)/src/udev + +udevlibexec_PROGRAMS = \ + fido_id + +fido_id_SOURCES =\ + fido_id.c \ + fido_id_desc.c \ + fido_id_desc.h + +fido_id_LDADD = \ + $(top_builddir)/src/libudev/libudev-private.la \ + $(top_builddir)/src/udev/libudev-core.la + diff --git a/src/fido_id/fido_id.c b/src/fido_id/fido_id.c new file mode 100644 index 000000000..a9f5f8f8a --- /dev/null +++ b/src/fido_id/fido_id.c @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#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: %s [SYSFS_PATH]", program_invocation_short_name); + + 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); diff --git a/src/fido_id/fido_id_desc.c b/src/fido_id/fido_id_desc.c new file mode 100644 index 000000000..2dfa75903 --- /dev/null +++ b/src/fido_id/fido_id_desc.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c' */ + +#include +#include +#include +#include + +#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; +} diff --git a/src/fido_id/fido_id_desc.h b/src/fido_id/fido_id_desc.h new file mode 100644 index 000000000..57af57e88 --- /dev/null +++ b/src/fido_id/fido_id_desc.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +#include +#include + +int is_fido_security_token_desc(const uint8_t *desc, size_t desc_len); diff --git a/src/shared/fileio.c b/src/shared/fileio.c index 5ea4c1913..1f687a795 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -19,11 +19,13 @@ #include #include +#include #include "fileio.h" #include "util.h" #include "strv.h" #include "utf8.h" #include "ctype.h" +#include "socket-util.h" int write_string_stream(FILE *f, const char *line) { assert(f); @@ -158,3 +160,349 @@ int read_full_file(const char *fn, char **contents, size_t *size) { return read_full_stream(f, contents, size); } + +int fopen_mode_to_flags(const char *mode) { + const char *p; + int flags; + + assert(mode); + + if ((p = startswith(mode, "r+"))) + flags = O_RDWR; + else if ((p = startswith(mode, "r"))) + flags = O_RDONLY; + else if ((p = startswith(mode, "w+"))) + flags = O_RDWR|O_CREAT|O_TRUNC; + else if ((p = startswith(mode, "w"))) + flags = O_WRONLY|O_CREAT|O_TRUNC; + else if ((p = startswith(mode, "a+"))) + flags = O_RDWR|O_CREAT|O_APPEND; + else if ((p = startswith(mode, "a"))) + flags = O_WRONLY|O_CREAT|O_APPEND; + else + return -EINVAL; + + for (; *p != 0; p++) { + + switch (*p) { + + case 'e': + flags |= O_CLOEXEC; + break; + + case 'x': + flags |= O_EXCL; + break; + + case 'm': + /* ignore this here, fdopen() might care later though */ + break; + + case 'c': /* not sure what to do about this one */ + default: + return -EINVAL; + } + } + + return flags; +} + +int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) { + FILE *f; + + /* A combination of fopen() with openat() */ + + if (dir_fd == AT_FDCWD && flags == 0) { + f = fopen(path, mode); + if (!f) + return -errno; + } else { + int fd, mode_flags; + + mode_flags = fopen_mode_to_flags(mode); + if (mode_flags < 0) + return mode_flags; + + fd = openat(dir_fd, path, mode_flags | flags); + if (fd < 0) + return -errno; + + f = fdopen(fd, mode); + if (!f) { + safe_close(fd); + return -errno; + } + } + + *ret = f; + return 0; +} + +int read_full_file_full( + int dir_fd, + const char *filename, + uint64_t offset, + size_t size, + ReadFullFileFlags flags, + const char *bind_name, + char **ret_contents, + size_t *ret_size) { + + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(filename); + assert(ret_contents); + + r = xfopenat(dir_fd, filename, "re", 0, &f); + if (r < 0) { + _cleanup_close_ int sk = -1; + + /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */ + if (r != -ENXIO) + return r; + + /* If this is enabled, let's try to connect to it */ + if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET)) + return -ENXIO; + + /* Seeking is not supported on AF_UNIX sockets */ + if (offset != UINT64_MAX) + return -ENXIO; + + sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (sk < 0) + return -errno; + + if (bind_name) { + /* If the caller specified a socket name to bind to, do so before connecting. This is + * useful to communicate some minor, short meta-information token from the client to + * the server. */ + union sockaddr_union bsa; + + r = sockaddr_un_set_path(&bsa.un, bind_name); + if (r < 0) + return r; + + if (bind(sk, &bsa.sa, r) < 0) + return -errno; + } + + r = connect_unix_path(sk, dir_fd, filename); + if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */ + return -ENXIO; + if (r < 0) + return r; + + if (shutdown(sk, SHUT_WR) < 0) + return -errno; + + f = fdopen(sk, "r"); + if (!f) + return -errno; + + TAKE_FD(sk); + } + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size); +} + +int read_full_stream_full( + FILE *f, + const char *filename, + uint64_t offset, + size_t size, + ReadFullFileFlags flags, + char **ret_contents, + size_t *ret_size) { + + _cleanup_free_ char *buf = NULL; + size_t n, n_next = 0, l; + int fd, r; + + assert(f); + assert(ret_contents); + assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)); + assert(size != SIZE_MAX || !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)); + + if (offset != UINT64_MAX && offset > LONG_MAX) /* fseek() can only deal with "long" offsets */ + return -ERANGE; + + fd = fileno(f); + if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see + * fmemopen()), let's optimize our buffering */ + struct stat st; + + if (fstat(fd, &st) < 0) + return -errno; + + if (S_ISREG(st.st_mode)) { + + /* Try to start with the right file size if we shall read the file in full. Note + * that we increase the size to read here by one, so that the first read attempt + * already makes us notice the EOF. If the reported size of the file is zero, we + * avoid this logic however, since quite likely it might be a virtual file in procfs + * that all report a zero file size. */ + + if (st.st_size > 0 && + (size == SIZE_MAX || FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER))) { + + uint64_t rsize = + LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset); + + if (rsize < SIZE_MAX) /* overflow check */ + n_next = rsize + 1; + } + + if (flags & READ_FULL_FILE_WARN_WORLD_READABLE) + (void) warn_file_is_world_accessible(filename, &st, NULL, 0); + } + } + + /* If we don't know how much to read, figure it out now. If we shall read a part of the file, then + * allocate the requested size. If we shall load the full file start with LINE_MAX. Note that if + * READ_FULL_FILE_FAIL_WHEN_LARGER we consider the specified size a safety limit, and thus also start + * with LINE_MAX, under assumption the file is most likely much shorter. */ + if (n_next == 0) + n_next = size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) ? size : LINE_MAX; + + /* Never read more than we need to determine that our own limit is hit */ + if (n_next > READ_FULL_BYTES_MAX) + n_next = READ_FULL_BYTES_MAX + 1; + + if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0) + return -errno; + + n = l = 0; + for (;;) { + char *t; + size_t k; + + /* If we shall fail when reading overly large data, then read exactly one byte more than the + * specified size at max, since that'll tell us if there's anymore data beyond the limit*/ + if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && n_next > size) + n_next = size + 1; + + if (flags & READ_FULL_FILE_SECURE) { + t = malloc(n_next + 1); + if (!t) { + r = -ENOMEM; + goto finalize; + } + memcpy_safe(t, buf, n); + explicit_bzero_safe(buf, n); + free(buf); + } else { + t = realloc(buf, n_next + 1); + if (!t) + return -ENOMEM; + } + + buf = t; + /* Unless a size has been explicitly specified, try to read as much as fits into the memory + * we allocated (minus 1, to leave one byte for the safety NUL byte) */ + n = size == SIZE_MAX ? MALLOC_SIZEOF_SAFE(buf) - 1 : n_next; + + errno = 0; + k = fread(buf + l, 1, n - l, f); + + assert(k <= n - l); + l += k; + + if (ferror(f)) { + r = errno_or_else(EIO); + goto finalize; + } + if (feof(f)) + break; + + if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */ + assert(l == size); + break; + } + + assert(k > 0); /* we can't have read zero bytes because that would have been EOF */ + + if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && l > size) { + r = -E2BIG; + goto finalize; + } + + if (n >= READ_FULL_BYTES_MAX) { + r = -E2BIG; + goto finalize; + } + + n_next = MIN(n * 2, READ_FULL_BYTES_MAX); + } + + if (flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) { + _cleanup_free_ void *decoded = NULL; + size_t decoded_size; + + buf[l++] = 0; + if (flags & READ_FULL_FILE_UNBASE64) + r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, &decoded, &decoded_size); + else + r = unhexmem_full(buf, l, flags & READ_FULL_FILE_SECURE, &decoded, &decoded_size); + if (r < 0) + goto finalize; + + if (flags & READ_FULL_FILE_SECURE) + explicit_bzero_safe(buf, n); + free_and_replace(buf, decoded); + n = l = decoded_size; + } + + if (!ret_size) { + /* Safety check: if the caller doesn't want to know the size of what we just read it will rely on the + * trailing NUL byte. But if there's an embedded NUL byte, then we should refuse operation as otherwise + * there'd be ambiguity about what we just read. */ + + if (memchr(buf, 0, l)) { + r = -EBADMSG; + goto finalize; + } + } + + buf[l] = 0; + *ret_contents = TAKE_PTR(buf); + + if (ret_size) + *ret_size = l; + + return 0; + +finalize: + if (flags & READ_FULL_FILE_SECURE) + explicit_bzero_safe(buf, n); + + return r; +} + +int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line) { + struct stat _st; + + if (!filename) + return 0; + + if (!st) { + if (stat(filename, &_st) < 0) + return -errno; + st = &_st; + } + + if ((st->st_mode & S_IRWXO) == 0) + return 0; + + //if (unit) + // log_syntax(unit, LOG_WARNING, filename, line, 0, + // "%s has %04o mode that is too permissive, please adjust the ownership and access mode.", + // filename, st->st_mode & 07777); + //else + log_warning("%s has %04o mode that is too permissive, please adjust the ownership and access mode.", + filename, st->st_mode & 07777); + return 0; +} diff --git a/src/shared/fileio.h b/src/shared/fileio.h index 7beed2eb8..48d52f2f5 100644 --- a/src/shared/fileio.h +++ b/src/shared/fileio.h @@ -21,11 +21,313 @@ #include #include +#include #include "macro.h" +#include "util.h" + +typedef enum { + READ_FULL_FILE_SECURE = 1 << 0, /* erase any buffers we employ internally, after use */ + READ_FULL_FILE_UNBASE64 = 1 << 1, /* base64 decode what we read */ + READ_FULL_FILE_UNHEX = 1 << 2, /* hex decode what we read */ + READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3, /* if regular file, log at LOG_WARNING level if access mode above 0700 */ + READ_FULL_FILE_CONNECT_SOCKET = 1 << 4, /* if socket inode, connect to it and read off it */ + READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */ +} ReadFullFileFlags; int write_string_stream(FILE *f, const char *line); int write_string_file(const char *fn, const char *line); int read_one_line_file(const char *fn, char **line); int read_full_file(const char *fn, char **contents, size_t *size); int read_full_stream(FILE *f, char **contents, size_t *size); +int read_full_stream_full(FILE *f, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, char **ret_contents, size_t *ret_size); + +static inline int unhex_next(const char **p, size_t *l) { + int r; + + assert(p); + assert(l); + + /* Find the next non-whitespace character, and decode it. We + * greedily skip all preceding and all following whitespace. */ + + for (;;) { + if (*l == 0) + return -EPIPE; + + if (!strchr(WHITESPACE, **p)) + break; + + /* Skip leading whitespace */ + (*p)++, (*l)--; + } + + r = unhexchar(**p); + if (r < 0) + return r; + + for (;;) { + (*p)++, (*l)--; + + if (*l == 0 || !strchr(WHITESPACE, **p)) + break; + + /* Skip following whitespace */ + } + + return r; +} + +static inline int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_len) { + _cleanup_free_ uint8_t *buf = NULL; + size_t buf_size; + const char *x; + uint8_t *z; + int r; + + assert(p || l == 0); + + if (l == SIZE_MAX) + l = strlen(p); + + /* Note that the calculation of memory size is an upper boundary, as we ignore whitespace while decoding */ + buf_size = (l + 1) / 2 + 1; + buf = malloc(buf_size); + if (!buf) + return -ENOMEM; + + for (x = p, z = buf;;) { + int a, b; + + a = unhex_next(&x, &l); + if (a == -EPIPE) /* End of string */ + break; + if (a < 0) { + r = a; + goto on_failure; + } + + b = unhex_next(&x, &l); + if (b < 0) { + r = b; + goto on_failure; + } + + *(z++) = (uint8_t) a << 4 | (uint8_t) b; + } + + *z = 0; + + if (ret_len) + *ret_len = (size_t) (z - buf); + if (ret) + *ret = TAKE_PTR(buf); + + return 0; + +on_failure: + if (secure) + explicit_bzero_safe(buf, buf_size); + + return r; +} + +static inline int unbase64char(char c) { + unsigned offset; + + if (c >= 'A' && c <= 'Z') + return c - 'A'; + + offset = 'Z' - 'A' + 1; + + if (c >= 'a' && c <= 'z') + return c - 'a' + offset; + + offset += 'z' - 'a' + 1; + + if (c >= '0' && c <= '9') + return c - '0' + offset; + + offset += '9' - '0' + 1; + + if (c == '+') + return offset; + + offset++; + + if (c == '/') + return offset; + + return -EINVAL; +} + +static inline int unbase64_next(const char **p, size_t *l) { + int ret; + + assert(p); + assert(l); + + /* Find the next non-whitespace character, and decode it. If we find padding, we return it as INT_MAX. We + * greedily skip all preceding and all following whitespace. */ + + for (;;) { + if (*l == 0) + return -EPIPE; + + if (!strchr(WHITESPACE, **p)) + break; + + /* Skip leading whitespace */ + (*p)++, (*l)--; + } + + if (**p == '=') + ret = INT_MAX; /* return padding as INT_MAX */ + else { + ret = unbase64char(**p); + if (ret < 0) + return ret; + } + + for (;;) { + (*p)++, (*l)--; + + if (*l == 0) + break; + if (!strchr(WHITESPACE, **p)) + break; + + /* Skip following whitespace */ + } + + return ret; +} + +static inline int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_size) { + _cleanup_free_ uint8_t *buf = NULL; + const char *x; + uint8_t *z; + size_t len; + int r; + + assert(p || l == 0); + + if (l == SIZE_MAX) + l = strlen(p); + + /* A group of four input bytes needs three output bytes, in case of padding we need to add two or three extra + * bytes. Note that this calculation is an upper boundary, as we ignore whitespace while decoding */ + len = (l / 4) * 3 + (l % 4 != 0 ? (l % 4) - 1 : 0); + + buf = malloc(len + 1); + if (!buf) + return -ENOMEM; + + for (x = p, z = buf;;) { + int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ + + a = unbase64_next(&x, &l); + if (a == -EPIPE) /* End of string */ + break; + if (a < 0) { + r = a; + goto on_failure; + } + if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */ + r = -EINVAL; + goto on_failure; + } + + b = unbase64_next(&x, &l); + if (b < 0) { + r = b; + goto on_failure; + } + if (b == INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */ + r = -EINVAL; + goto on_failure; + } + + c = unbase64_next(&x, &l); + if (c < 0) { + r = c; + goto on_failure; + } + + d = unbase64_next(&x, &l); + if (d < 0) { + r = d; + goto on_failure; + } + + if (c == INT_MAX) { /* Padding at the third character */ + + if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */ + r = -EINVAL; + goto on_failure; + } + + /* b == 00YY0000 */ + if (b & 15) { + r = -EINVAL; + goto on_failure; + } + + if (l > 0) { /* Trailing rubbish? */ + r = -ENAMETOOLONG; + goto on_failure; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ + break; + } + + if (d == INT_MAX) { + /* c == 00ZZZZ00 */ + if (c & 3) { + r = -EINVAL; + goto on_failure; + } + + if (l > 0) { /* Trailing rubbish? */ + r = -ENAMETOOLONG; + goto on_failure; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + break; + } + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ + } + + *z = 0; + + if (ret_size) + *ret_size = (size_t) (z - buf); + if (ret) + *ret = TAKE_PTR(buf); + + return 0; + +on_failure: + if (secure) + explicit_bzero_safe(buf, len); + + return r; +} + +int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line); + +int read_full_file_full( + int dir_fd, + const char *filename, + uint64_t offset, + size_t size, + ReadFullFileFlags flags, + const char *bind_name, + char **ret_contents, + size_t *ret_size); diff --git a/src/shared/path-util.c b/src/shared/path-util.c index 212e50243..63e3423f3 100644 --- a/src/shared/path-util.c +++ b/src/shared/path-util.c @@ -573,3 +573,78 @@ char *prefix_root(const char *root, const char *path) { strcpy(p, path); return n; } + +char* path_extend_internal(char **x, ...) { + size_t sz, old_sz; + char *q, *nx; + const char *p; + va_list ap; + bool slash; + + /* Joins all listed strings until the sentinel and places a "/" between them unless the strings end/begin + * already with one so that it is unnecessary. Note that slashes which are already duplicate won't be + * removed. The string returned is hence always equal to or longer than the sum of the lengths of each + * individual string. + * + * The first argument may be an already allocated string that is extended via realloc() if + * non-NULL. path_extend() and path_join() are macro wrappers around this function, making use of the + * first parameter to distinguish the two operations. + * + * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of which some + * are optional. + * + * Examples: + * + * path_join("foo", "bar") → "foo/bar" + * path_join("foo/", "bar") → "foo/bar" + * path_join("", "foo", "", "bar", "") → "foo/bar" */ + + sz = old_sz = x ? strlen_ptr(*x) : 0; + va_start(ap, x); + while ((p = va_arg(ap, char*)) != POINTER_MAX) { + size_t add; + + if (isempty(p)) + continue; + + add = 1 + strlen(p); + if (sz > SIZE_MAX - add) { /* overflow check */ + va_end(ap); + return NULL; + } + + sz += add; + } + va_end(ap); + + nx = realloc(x ? *x : NULL, GREEDY_ALLOC_ROUND_UP(sz+1)); + if (!nx) + return NULL; + if (x) + *x = nx; + + if (old_sz > 0) + slash = nx[old_sz-1] == '/'; + else { + nx[old_sz] = 0; + slash = true; /* no need to generate a slash anymore */ + } + + q = nx + old_sz; + + va_start(ap, x); + while ((p = va_arg(ap, char*)) != POINTER_MAX) { + if (isempty(p)) + continue; + + if (!slash && p[0] != '/') + *(q++) = '/'; + + q = stpcpy(q, p); + slash = endswith(p, "/"); + } + va_end(ap); + + return nx; +} + diff --git a/src/shared/path-util.h b/src/shared/path-util.h index 1d15d4c0f..2dea53b2f 100644 --- a/src/shared/path-util.h +++ b/src/shared/path-util.h @@ -41,3 +41,8 @@ int path_is_mount_point(const char *path, bool allow_symlink); bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); char *prefix_root(const char *root, const char *path); + +char* path_extend_internal(char **x, ...); +#define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX) +#define path_join(...) path_extend_internal(NULL, __VA_ARGS__, POINTER_MAX) + diff --git a/src/shared/socket-util.h b/src/shared/socket-util.h index 52b8dff66..9fa2efb1d 100644 --- a/src/shared/socket-util.h +++ b/src/shared/socket-util.h @@ -19,20 +19,135 @@ #pragma once -#include -#include -#include -#include #include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" union sockaddr_union { + /* The minimal, abstract version */ struct sockaddr sa; + + /* The libc provided version that allocates "enough room" for every protocol */ + struct sockaddr_storage storage; + + /* Protoctol-specific implementations */ struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr_un un; struct sockaddr_nl nl; - struct sockaddr_storage storage; struct sockaddr_ll ll; + //struct sockaddr_vm vm; + + /* Ensure there is enough space to store Infiniband addresses */ + uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + CONST_MAX(ETH_ALEN, INFINIBAND_ALEN)]; + + /* Ensure there is enough space after the AF_UNIX sun_path for one more NUL byte, just to be sure that the path + * component is always followed by at least one NUL byte. */ + uint8_t un_buffer[sizeof(struct sockaddr_un) + 1]; }; + +static inline int sockaddr_un_set_path(struct sockaddr_un *ret, const char *path) { + size_t l; + + assert(ret); + assert(path); + + /* Initialize ret->sun_path from the specified argument. This will interpret paths starting with '@' as + * abstract namespace sockets, and those starting with '/' as regular filesystem sockets. It won't accept + * anything else (i.e. no relative paths), to avoid ambiguities. Note that this function cannot be used to + * reference paths in the abstract namespace that include NUL bytes in the name. */ + + l = strlen(path); + if (l < 2) + return -EINVAL; + if (!IN_SET(path[0], '/', '@')) + return -EINVAL; + + /* Don't allow paths larger than the space in sockaddr_un. Note that we are a tiny bit more restrictive than + * the kernel is: we insist on NUL termination (both for abstract namespace and regular file system socket + * addresses!), which the kernel doesn't. We do this to reduce chance of incompatibility with other apps that + * do not expect non-NUL terminated file system path. */ + if (l+1 > sizeof(ret->sun_path)) + return path[0] == '@' ? -EINVAL : -ENAMETOOLONG; /* return a recognizable error if this is + * too long to fit into a sockaddr_un, but + * is a file system path, and thus might be + * connectible via O_PATH indirection. */ + + *ret = (struct sockaddr_un) { + .sun_family = AF_UNIX, + }; + + if (path[0] == '@') { + /* Abstract namespace socket */ + memcpy(ret->sun_path + 1, path + 1, l); /* copy *with* trailing NUL byte */ + return (int) (offsetof(struct sockaddr_un, sun_path) + l); /* 🔥 *don't* 🔥 include trailing NUL in size */ + + } else { + assert(path[0] == '/'); + + /* File system socket */ + memcpy(ret->sun_path, path, l + 1); /* copy *with* trailing NUL byte */ + return (int) (offsetof(struct sockaddr_un, sun_path) + l + 1); /* include trailing NUL in size */ + } +} + +static inline int connect_unix_path(int fd, int dir_fd, const char *path) { + _cleanup_close_ int inode_fd = -1; + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + }; + size_t path_len; + socklen_t salen; + + assert(fd >= 0); + assert(dir_fd == AT_FDCWD || dir_fd >= 0); + assert(path); + + /* Connects to the specified AF_UNIX socket in the file system. Works around the 108 byte size limit + * in sockaddr_un, by going via O_PATH if needed. This hence works for any kind of path. */ + + path_len = strlen(path); + + /* Refuse zero length path early, to make sure AF_UNIX stack won't mistake this for an abstract + * namespace path, since first char is NUL */ + if (path_len <= 0) + return -EINVAL; + + if (dir_fd == AT_FDCWD && path_len < sizeof(sa.un.sun_path)) { + memcpy(sa.un.sun_path, path, path_len + 1); + salen = offsetof(struct sockaddr_un, sun_path) + path_len + 1; + } else { + const char *proc; + size_t proc_len; + + /* If dir_fd is specified, then we need to go the indirect O_PATH route, because connectat() + * does not exist. If the path is too long, we also need to take the indirect route, since we + * can't fit this into a sockaddr_un directly. */ + + inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); + if (inode_fd < 0) + return -errno; + + proc = FORMAT_PROC_FD_PATH(inode_fd); + proc_len = strlen(proc); + + assert(proc_len < sizeof(sa.un.sun_path)); + memcpy(sa.un.sun_path, proc, proc_len + 1); + salen = offsetof(struct sockaddr_un, sun_path) + proc_len + 1; + } + + return RET_NERRNO(connect(fd, &sa.sa, salen)); +} diff --git a/src/shared/unaligned.h b/src/shared/unaligned.h new file mode 100644 index 000000000..4100be080 --- /dev/null +++ b/src/shared/unaligned.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +/* BE */ + +static inline uint16_t unaligned_read_be16(const void *_u) { + const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u; + + return be16toh(u->x); +} + +static inline uint32_t unaligned_read_be32(const void *_u) { + const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u; + + return be32toh(u->x); +} + +static inline uint64_t unaligned_read_be64(const void *_u) { + const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u; + + return be64toh(u->x); +} + +static inline void unaligned_write_be16(void *_u, uint16_t a) { + struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u; + + u->x = be16toh(a); +} + +static inline void unaligned_write_be32(void *_u, uint32_t a) { + struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u; + + u->x = be32toh(a); +} + +static inline void unaligned_write_be64(void *_u, uint64_t a) { + struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u; + + u->x = be64toh(a); +} + +/* LE */ + +static inline uint16_t unaligned_read_le16(const void *_u) { + const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u; + + return le16toh(u->x); +} + +static inline uint32_t unaligned_read_le32(const void *_u) { + const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u; + + return le32toh(u->x); +} + +static inline uint64_t unaligned_read_le64(const void *_u) { + const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u; + + return le64toh(u->x); +} + +static inline void unaligned_write_le16(void *_u, uint16_t a) { + struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u; + + u->x = le16toh(a); +} + +static inline void unaligned_write_le32(void *_u, uint32_t a) { + struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u; + + u->x = le32toh(a); +} + +static inline void unaligned_write_le64(void *_u, uint64_t a) { + struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u; + + u->x = le64toh(a); +} + +#if __BYTE_ORDER == __BIG_ENDIAN +#define unaligned_read_ne16 unaligned_read_be16 +#define unaligned_read_ne32 unaligned_read_be32 +#define unaligned_read_ne64 unaligned_read_be64 + +#define unaligned_write_ne16 unaligned_write_be16 +#define unaligned_write_ne32 unaligned_write_be32 +#define unaligned_write_ne64 unaligned_write_be64 +#else +#define unaligned_read_ne16 unaligned_read_le16 +#define unaligned_read_ne32 unaligned_read_le32 +#define unaligned_read_ne64 unaligned_read_le64 + +#define unaligned_write_ne16 unaligned_write_le16 +#define unaligned_write_ne32 unaligned_write_le32 +#define unaligned_write_ne64 unaligned_write_le64 +#endif diff --git a/src/shared/util.h b/src/shared/util.h index cbc9669e9..16eca577a 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "time-util.h" #include "missing.h" @@ -50,6 +51,8 @@ #define FORMAT_BYTES_MAX 8 +#define POINTER_MAX ((void*) UINTPTR_MAX) + size_t page_size(void) _pure_; #define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) @@ -318,6 +321,86 @@ _alloc_(2, 3) static inline void *realloc_multiply(void *p, size_t a, size_t b) return realloc(p, a * b); } +/* If for some reason more than 4M are allocated on the stack, let's abort immediately. It's better than + * proceeding and smashing the stack limits. Note that by default RLIMIT_STACK is 8M on Linux. */ +#define ALLOCA_MAX (4U*1024U*1024U) + +#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n))) + +#define alloca_safe(n) \ + ({ \ + size_t _nn_ = n; \ + assert(_nn_ <= ALLOCA_MAX); \ + alloca(_nn_ == 0 ? 1 : _nn_); \ + }) \ + +#define newa(t, n) \ + ({ \ + size_t _n_ = n; \ + assert(!size_multiply_overflow(sizeof(t), _n_)); \ + (t*) alloca_safe(sizeof(t)*_n_); \ + }) + +#define newa0(t, n) \ + ({ \ + size_t _n_ = n; \ + assert(!size_multiply_overflow(sizeof(t), _n_)); \ + (t*) alloca0((sizeof(t)*_n_)); \ + }) + +#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) + +#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n))) + +#define free_and_replace(a, b) \ + ({ \ + typeof(a)* _a = &(a); \ + typeof(b)* _b = &(b); \ + free(*_a); \ + *_a = *_b; \ + *_b = NULL; \ + 0; \ + }) + +/* These are like strdupa()/strndupa(), but honour ALLOCA_MAX */ +#define strdupa_safe(s) \ + ({ \ + const char *_t = (s); \ + (char*) memdupa_suffix0(_t, strlen(_t)); \ + }) + +#define strndupa_safe(s, n) \ + ({ \ + const char *_t = (s); \ + (char*) memdupa_suffix0(_t, strnlen(_t, (n))); \ + }) + +#define memdupa(p, l) \ + ({ \ + void *_q_; \ + size_t _l_ = l; \ + _q_ = alloca_safe(_l_); \ + memcpy_safe(_q_, p, _l_); \ + }) + +#define memdupa_suffix0(p, l) \ + ({ \ + void *_q_; \ + size_t _l_ = l; \ + _q_ = alloca_safe(_l_ + 1); \ + ((uint8_t*) _q_)[_l_] = 0; \ + memcpy_safe(_q_, p, _l_); \ + }) + +/* Normal memcpy() requires src to be nonnull. We do nothing if n is 0. */ +static inline void *memcpy_safe(void *dst, const void *src, size_t n) { + if (n == 0) + return dst; + assert(src); + return memcpy(dst, src, n); +} + + bool filename_is_valid(const char *p) _pure_; /** * Check if a string contains any glob patterns. @@ -436,3 +519,278 @@ union inotify_event_buffer { void cmsg_close_all(struct msghdr *mh); const char *eudev_basename(const char *filename); + +static inline char *delete_trailing_chars(char *s, const char *bad) { + char *c = s; + + /* Drops all specified bad characters, at the end of the string */ + + if (!s) + return NULL; + + if (!bad) + bad = WHITESPACE; + + for (char *p = s; *p; p++) + if (!strchr(bad, *p)) + c = p + 1; + + *c = 0; + + return s; +} + +static inline char *skip_leading_chars(const char *s, const char *bad) { + if (!s) + return NULL; + + if (!bad) + bad = WHITESPACE; + + return (char*) s + strspn(s, bad); +} + +static inline char *strstrip(char *s) { + if (!s) + return NULL; + + /* Drops trailing whitespace. Modifies the string in place. Returns pointer to first non-space character */ + + return delete_trailing_chars(skip_leading_chars(s, WHITESPACE), WHITESPACE); +} + +#define FLAGS_SET(v, flags) \ + ((~(v) & (flags)) == 0) + +/* Evaluates to (void) if _A or _B are not constant or of different types (being integers of different sizes + * is also OK as long as the signedness matches) */ +#define CONST_MAX(_A, _B) \ + (__builtin_choose_expr( \ + __builtin_constant_p(_A) && \ + __builtin_constant_p(_B) && \ + (__builtin_types_compatible_p(typeof(_A), typeof(_B)) || \ + (IS_UNSIGNED_INTEGER_TYPE(_A) && IS_UNSIGNED_INTEGER_TYPE(_B)) || \ + (IS_SIGNED_INTEGER_TYPE(_A) && IS_SIGNED_INTEGER_TYPE(_B))), \ + ((_A) > (_B)) ? (_A) : (_B), \ + VOID_0)) + +#define IS_UNSIGNED_INTEGER_TYPE(type) \ + (__builtin_types_compatible_p(typeof(type), unsigned char) || \ + __builtin_types_compatible_p(typeof(type), unsigned short) || \ + __builtin_types_compatible_p(typeof(type), unsigned) || \ + __builtin_types_compatible_p(typeof(type), unsigned long) || \ + __builtin_types_compatible_p(typeof(type), unsigned long long)) + +#define IS_SIGNED_INTEGER_TYPE(type) \ + (__builtin_types_compatible_p(typeof(type), signed char) || \ + __builtin_types_compatible_p(typeof(type), signed short) || \ + __builtin_types_compatible_p(typeof(type), signed) || \ + __builtin_types_compatible_p(typeof(type), signed long) || \ + __builtin_types_compatible_p(typeof(type), signed long long)) + +#ifndef __COVERITY__ +# define VOID_0 ((void)0) +#else +# define VOID_0 ((void*)0) +#endif + +/* Like TAKE_PTR() but for file descriptors, resetting them to -1 */ +#define TAKE_FD(fd) \ + ({ \ + int *_fd_ = &(fd); \ + int _ret_ = *_fd_; \ + *_fd_ = -1; \ + _ret_; \ + }) + +#define assert_return(expr, r) \ + do { \ + if (!assert_log(expr, #expr)) \ + return (r); \ + } while (false) + +static inline int negative_errno(void) { + /* This helper should be used to shut up gcc if you know 'errno' is + * negative. Instead of "return -errno;", use "return negative_errno();" + * It will suppress bogus gcc warnings in case it assumes 'errno' might + * be 0 and thus the caller's error-handling might not be triggered. */ + //assert_return(errno > 0, -EINVAL); + if (errno > 0) + return -EINVAL; + return -errno; +} + +static inline int RET_NERRNO(int ret) { + + /* Helper to wrap system calls in to make them return negative errno errors. This brings system call + * error handling in sync with how we usually handle errors in our own code, i.e. with immediate + * returning of negative errno. Usage is like this: + * + * … + * r = RET_NERRNO(unlink(t)); + * … + * + * or + * + * … + * fd = RET_NERRNO(open("/etc/fstab", O_RDONLY|O_CLOEXEC)); + * … + */ + + if (ret < 0) + return negative_errno(); + + return ret; +} + +#define STRLEN(x) (sizeof(""x"") - sizeof(typeof(x[0]))) + +/* The maximum length a buffer for a /proc/self/fd/ path needs */ +#define PROC_FD_PATH_MAX \ + (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)) + +#define snprintf_ok(buf, len, fmt, ...) \ + ({ \ + char *_buf = (buf); \ + size_t _len = (len); \ + int _snpf = snprintf(_buf, _len, (fmt), __VA_ARGS__); \ + _snpf >= 0 && (size_t) _snpf < _len ? _buf : NULL; \ + }) + +static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int fd) { + assert(buf); + assert(fd >= 0); + assert_se(snprintf_ok(buf, PROC_FD_PATH_MAX, "/proc/self/fd/%i", fd)); + return buf; +} + +#define FORMAT_PROC_FD_PATH(fd) \ + format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd)) + +#define READ_FULL_BYTES_MAX (64U*1024U*1024U - 1U) + +#define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b)) +#define __LESS_BY(aq, a, bq, b) \ + ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) - UNIQ_T(B, bq) : 0; \ + }) + +/* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time + * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */ +#define TAKE_PTR(ptr) \ + ({ \ + typeof(ptr) *_pptr_ = &(ptr); \ + typeof(ptr) _ptr_ = *_pptr_; \ + *_pptr_ = NULL; \ + _ptr_; \ + }) + +/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), in a way that + * is compatible with _FORTIFY_SOURCES. If _FORTIFY_SOURCES is used many memory operations will take the + * object size as returned by __builtin_object_size() into account. Hence, let's return the smaller size of + * malloc_usable_size() and __builtin_object_size() here, so that we definitely operate in safe territory by + * both the compiler's and libc's standards. Note that __builtin_object_size() evaluates to SIZE_MAX if the + * size cannot be determined, hence the MIN() expression should be safe with dynamically sized memory, + * too. Moreover, when NULL is passed malloc_usable_size() is documented to return zero, and + * __builtin_object_size() returns SIZE_MAX too, hence we also return a sensible value of 0 in this corner + * case. */ +#define MALLOC_SIZEOF_SAFE(x) \ + MIN(malloc_usable_size(x), __builtin_object_size(x, 0)) + +#if HAVE_EXPLICIT_BZERO +static inline void* explicit_bzero_safe(void *p, size_t l) { + if (l > 0) + explicit_bzero(p, l); + + return p; +} +#else +typedef void *(*memset_t)(void *,int,size_t); + +static volatile memset_t memset_func = memset; + +static inline void* explicit_bzero_safe(void *p, size_t l) { + if (l > 0) + memset_func(p, '\0', l); + + return p; +} +#endif + +static inline int errno_or_else(int fallback) { + /* To be used when invoking library calls where errno handling is not defined clearly: we return + * errno if it is set, and the specified error otherwise. The idea is that the caller initializes + * errno to zero before doing an API call, and then uses this helper to retrieve a somewhat useful + * error code */ + if (errno > 0) + return -errno; + + return -abs(fallback); +} + +/* Like startswith(), but operates on arbitrary memory blocks */ +static inline void *memory_startswith(const void *p, size_t sz, const char *token) { + assert(token); + + size_t n = strlen(token) * sizeof(char); + if (sz < n) + return NULL; + + assert(p); + + if (memcmp(p, token, n) != 0) + return NULL; + + return (uint8_t*) p + n; +} + +static inline size_t strlen_ptr(const char *s) { + if (!s) + return 0; + + return strlen(s); +} + +/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */ +static inline unsigned long ALIGN_POWER2(unsigned long u) { + + /* Avoid subtraction overflow */ + if (u == 0) + return 0; + + /* clz(0) is undefined */ + if (u == 1) + return 1; + + /* left-shift overflow is undefined */ + if (__builtin_clzl(u - 1UL) < 1) + return 0; + + return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL)); +} + +static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) { + size_t m; + + /* Round up allocation sizes a bit to some reasonable, likely larger value. This is supposed to be + * used for cases which are likely called in an allocation loop of some form, i.e. that repetitively + * grow stuff, for example strv_extend() and suchlike. + * + * Note the difference to GREEDY_REALLOC() here, as this helper operates on a single size value only, + * and rounds up to next multiple of 2, needing no further counter. + * + * Note the benefits of direct ALIGN_POWER2() usage: type-safety for size_t, sane handling for very + * small (i.e. <= 2) and safe handling for very large (i.e. > SSIZE_MAX) values. */ + + if (l <= 2) + return 2; /* Never allocate less than 2 of something. */ + + m = ALIGN_POWER2(l); + if (m == 0) /* overflow? */ + return l; + + return m; +} + From b359e32d0706906a69c8f19ac47a45335ce18784 Mon Sep 17 00:00:00 2001 From: Boian Bonev Date: Tue, 4 Oct 2022 17:39:05 +0300 Subject: [PATCH 2/4] WIP not compilable fido_id --- src/fido_id/fido_id.c | 102 +++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/src/fido_id/fido_id.c b/src/fido_id/fido_id.c index a9f5f8f8a..7f5f5cee8 100644 --- a/src/fido_id/fido_id.c +++ b/src/fido_id/fido_id.c @@ -6,6 +6,10 @@ * Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c' */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + #include #include #include @@ -14,23 +18,90 @@ #include #include -#include "device-private.h" -#include "device-util.h" -#include "fd-util.h" + +//#include "libudev.h" +//#include "libudev-private.h" +//#include "fido_id_desc.h" +//#include "udev-util.h" + + +#include "libudev.h" +#include "libudev-private.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 "main-func.h" #include "path-util.h" -#include "string-util.h" +//#include "string-util.h" +#include "udev.h" #include "udev-util.h" +#include "libudev.h" +#include "libudev-private.h" + +int device_new_aux(struct udev_device **ret) { + struct udev_device *device; + + assert(ret); + + device = new(struct udev_device, 1); + if (!device) + return -ENOMEM; + + *device = (struct udev_device) { + .n_ref = 1, + .watch_handle = -1, + .devmode = MODE_INVALID, + .devuid = UID_INVALID, + .devgid = GID_INVALID, + .action = _SD_DEVICE_ACTION_INVALID, + }; + + *ret = device; + return 0; +} + +int device_new_from_strv(struct udev_device **ret, char **strv) { + _cleanup_(udev_device_unrefp) struct udev_device *device = NULL; + const char *major = NULL, *minor = NULL; + int r; + + assert(ret); + assert(strv); + + r = device_new_aux(&device); + if (r < 0) + return r; + + STRV_FOREACH(key, strv) { + r = device_append(device, *key, &major, &minor); + if (r < 0) + return r; + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor); + } + + r = device_verify(device); + if (r < 0) + return r; + + *ret = TAKE_PTR(device); + + return 0; +} -static int run(int argc, char **argv) { - _cleanup_(sd_device_unrefp) struct sd_device *device = NULL; +int main(int argc, char **argv) { + _cleanup_(udev_device_unrefp) struct udev_device *device = NULL; _cleanup_free_ char *desc_path = NULL; _cleanup_close_ int fd = -1; - struct sd_device *hid_device; + struct udev_device *hid_device; const char *sys_path; uint8_t desc[HID_MAX_DESCRIPTOR_SIZE]; ssize_t desc_len; @@ -38,28 +109,28 @@ static int run(int argc, char **argv) { int r; log_set_target(LOG_TARGET_AUTO); - udev_parse_config(); - log_parse_environment(); + //udev_parse_config(); + //log_parse_environment(); log_open(); if (argc > 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s [SYSFS_PATH]", program_invocation_short_name); + return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), "Usage: %s [SYSFS_PATH]", program_invocation_short_name); 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]); + r = udev_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); + r = udev_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); + r = udev_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"); @@ -77,7 +148,7 @@ static int run(int argc, char **argv) { 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), + 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); @@ -92,4 +163,3 @@ static int run(int argc, char **argv) { return 0; } -DEFINE_MAIN_FUNCTION(run); From 0d94554d9e1104092cf0708558cc44f997c5177a Mon Sep 17 00:00:00 2001 From: Ralph Ronnquist Date: Sun, 13 Nov 2022 03:42:29 +0200 Subject: [PATCH 3/4] Rewrite using current API --- src/fido_id/fido_id.c | 108 ++++++++---------------------------------- 1 file changed, 21 insertions(+), 87 deletions(-) diff --git a/src/fido_id/fido_id.c b/src/fido_id/fido_id.c index 7f5f5cee8..3c23111eb 100644 --- a/src/fido_id/fido_id.c +++ b/src/fido_id/fido_id.c @@ -19,84 +19,18 @@ #include -//#include "libudev.h" -//#include "libudev-private.h" -//#include "fido_id_desc.h" -//#include "udev-util.h" - - -#include "libudev.h" -#include "libudev-private.h" -//#include "device-private.h" -//#include "device-util.h" -//#include "fd-util.h" #include "fido_id_desc.h" +#include "libudev-private.h" +#include "libudev.h" #include "log.h" #include "macro.h" -//#include "main-func.h" #include "path-util.h" -//#include "string-util.h" -#include "udev.h" #include "udev-util.h" -#include "libudev.h" -#include "libudev-private.h" - -int device_new_aux(struct udev_device **ret) { - struct udev_device *device; - - assert(ret); - - device = new(struct udev_device, 1); - if (!device) - return -ENOMEM; - - *device = (struct udev_device) { - .n_ref = 1, - .watch_handle = -1, - .devmode = MODE_INVALID, - .devuid = UID_INVALID, - .devgid = GID_INVALID, - .action = _SD_DEVICE_ACTION_INVALID, - }; - - *ret = device; - return 0; -} - -int device_new_from_strv(struct udev_device **ret, char **strv) { - _cleanup_(udev_device_unrefp) struct udev_device *device = NULL; - const char *major = NULL, *minor = NULL; - int r; - - assert(ret); - assert(strv); - - r = device_new_aux(&device); - if (r < 0) - return r; - - STRV_FOREACH(key, strv) { - r = device_append(device, *key, &major, &minor); - if (r < 0) - return r; - } - - if (major) { - r = device_set_devnum(device, major, minor); - if (r < 0) - return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor); - } - - r = device_verify(device); - if (r < 0) - return r; - - *ret = TAKE_PTR(device); +#include "udev.h" - return 0; -} int main(int argc, char **argv) { + struct udev *config = 0; _cleanup_(udev_device_unrefp) struct udev_device *device = NULL; _cleanup_free_ char *desc_path = NULL; _cleanup_close_ int fd = -1; @@ -117,43 +51,43 @@ int main(int argc, char **argv) { return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), "Usage: %s [SYSFS_PATH]", program_invocation_short_name); 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"); + device = udev_device_new_from_environment( config ); + if ( device == NULL ) + return log_error_errno(errno, "Failed to get current device from environment: %m"); } else { - r = udev_device_new_from_syspath(&device, argv[1]); - if (r < 0) + device = udev_device_new_from_syspath( config, argv[1] ); + if ( device == NULL ) return log_error_errno(r, "Failed to get device from syspath: %m"); } - r = udev_device_get_parent(device, &hid_device); - if (r < 0) - return log_device_error_errno(device, r, "Failed to get parent HID device: %m"); + hid_device = udev_device_get_parent( device ); + if ( hid_device == NULL ) + return log_error_errno(errno, "Failed to get parent HID device: %m"); - r = udev_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"); + sys_path = udev_device_get_syspath( hid_device ); + if ( sys_path == NULL ) + return log_error_errno(errno, "Failed to get syspath for HID device: %m"); - desc_path = path_join(sys_path, "report_descriptor"); - if (!desc_path) + desc_path = path_join( sys_path, "report_descriptor" ); + if ( desc_path == NULL ) return log_oom(); fd = open(desc_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd < 0) - return log_device_error_errno(hid_device, errno, + return log_error_errno(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, + return log_error_errno(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), + return log_debug_errno(/*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, + return log_debug_errno(errno, "Failed to parse report descriptor at '%s'.", desc_path); if (r > 0) { printf("ID_FIDO_TOKEN=1\n"); From c2a042eb1d70dcdcbf7f227fb264622914a6ce69 Mon Sep 17 00:00:00 2001 From: Boian Bonev Date: Sun, 13 Nov 2022 03:59:04 +0200 Subject: [PATCH 4/4] Allocate udev struct --- src/fido_id/fido_id.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/fido_id/fido_id.c b/src/fido_id/fido_id.c index 3c23111eb..ace441e52 100644 --- a/src/fido_id/fido_id.c +++ b/src/fido_id/fido_id.c @@ -30,8 +30,8 @@ int main(int argc, char **argv) { - struct udev *config = 0; _cleanup_(udev_device_unrefp) struct udev_device *device = NULL; + _cleanup_udev_unref_ struct udev *udev; _cleanup_free_ char *desc_path = NULL; _cleanup_close_ int fd = -1; @@ -47,29 +47,33 @@ int main(int argc, char **argv) { //log_parse_environment(); log_open(); + udev = udev_new(); + if (udev == NULL) + return 1; + if (argc > 2) return log_error_errno(/*SYNTHETIC_ERRNO*/(EINVAL), "Usage: %s [SYSFS_PATH]", program_invocation_short_name); if (argc == 1) { - device = udev_device_new_from_environment( config ); - if ( device == NULL ) + device = udev_device_new_from_environment(udev); + if (device == NULL) return log_error_errno(errno, "Failed to get current device from environment: %m"); } else { - device = udev_device_new_from_syspath( config, argv[1] ); - if ( device == NULL ) + device = udev_device_new_from_syspath(udev, argv[1]); + if (device == NULL) return log_error_errno(r, "Failed to get device from syspath: %m"); } - hid_device = udev_device_get_parent( device ); - if ( hid_device == NULL ) + hid_device = udev_device_get_parent(device); + if (hid_device == NULL) return log_error_errno(errno, "Failed to get parent HID device: %m"); - sys_path = udev_device_get_syspath( hid_device ); - if ( sys_path == NULL ) + sys_path = udev_device_get_syspath(hid_device); + if (sys_path == NULL) return log_error_errno(errno, "Failed to get syspath for HID device: %m"); - desc_path = path_join( sys_path, "report_descriptor" ); - if ( desc_path == NULL ) + desc_path = path_join(sys_path, "report_descriptor"); + if (desc_path == NULL) return log_oom(); fd = open(desc_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);