From b45a897edc1a11b8cf2f876f00dbb73f82fe5b6a Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 21 May 2026 23:24:32 +0100 Subject: [PATCH] hwdb: reject out-of-bounds fnmatch prefixes Ensure that fnmatch traversal doesn't go off the hwdb.bin in case of a corrupted file Originally reported on yeswehack.com as #YWH-PGM9780-70 For the unit test: Co-developed-by: GitHub Copilot --- src/libsystemd/sd-hwdb/sd-hwdb.c | 13 +++++- src/libsystemd/sd-hwdb/test-sd-hwdb.c | 66 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c index 1197c4f954c..996f253fd90 100644 --- a/src/libsystemd/sd-hwdb/sd-hwdb.c +++ b/src/libsystemd/sd-hwdb/sd-hwdb.c @@ -180,9 +180,18 @@ static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t const char *prefix; int err; + assert(hwdb); + assert(node); + + /* Ensure the prefix is within bounds */ + uint64_t file_size = hwdb->st.st_size, prefix_off = le64toh(node->prefix_off); + if (prefix_off >= file_size || p >= file_size - prefix_off) + return -EINVAL; + prefix = trie_string(hwdb, node->prefix_off); - len = strlen(prefix + p); - linebuf_add(buf, prefix + p, len); + len = strnlen(prefix + p, file_size - prefix_off - p); + if (!linebuf_add(buf, prefix + p, len)) + return -EINVAL; for (i = 0; i < node->children_count; i++) { const struct trie_child_entry_f *child = trie_node_child(hwdb, node, i); diff --git a/src/libsystemd/sd-hwdb/test-sd-hwdb.c b/src/libsystemd/sd-hwdb/test-sd-hwdb.c index 0030096a7c4..dff29c01e8f 100644 --- a/src/libsystemd/sd-hwdb/test-sd-hwdb.c +++ b/src/libsystemd/sd-hwdb/test-sd-hwdb.c @@ -1,11 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include + #include "sd-hwdb.h" #include "errno-util.h" +#include "fd-util.h" #include "hwdb-internal.h" #include "nulstr-util.h" +#include "path-util.h" +#include "rm-rf.h" #include "tests.h" +#include "tmpfile-util.h" TEST(failed_enumerate) { _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; @@ -69,6 +76,65 @@ TEST(sd_hwdb_new_from_path) { assert_se(r >= 0); } +TEST(sd_hwdb_seek_rejects_invalid_fnmatch_prefix) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + _cleanup_free_ char *path = NULL; + + struct invalid_hwdb { + struct trie_header_f header; + struct trie_node_f root_node; + struct trie_child_entry_f child1; + struct trie_node_f mid_node; + struct trie_child_entry_f child2; + struct trie_node_f crash_node; + char strings[STRLEN("usb:") + 1]; + } _packed_ data = { + .header = { + .signature = HWDB_SIG, + .tool_version = htole64(PROJECT_VERSION), + .file_size = htole64(sizeof(struct invalid_hwdb)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .nodes_root_off = htole64(offsetof(struct invalid_hwdb, root_node)), + .nodes_len = htole64(offsetof(struct invalid_hwdb, strings) - offsetof(struct invalid_hwdb, root_node)), + .strings_len = htole64(STRLEN("usb:") + 1), + }, + .root_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings)), + .children_count = 1, + }, + .child1 = { + .c = 'v', + .child_off = htole64(offsetof(struct invalid_hwdb, mid_node)), + }, + .mid_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")), + .children_count = 1, + }, + .child2 = { + .c = '*', + .child_off = htole64(offsetof(struct invalid_hwdb, crash_node)), + }, + .crash_node = { + .prefix_off = htole64(0xDEAD0000), + }, + .strings = "usb:", + }; + + ASSERT_OK(mkdtemp_malloc(NULL, &tmp)); + ASSERT_NOT_NULL(path = path_join(tmp, "hwdb.bin")); + + _cleanup_close_ int fd = ASSERT_OK_ERRNO(open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC, 0644)); + assert_se(write(fd, &data, sizeof(data)) == (ssize_t) sizeof(data)); + fd = safe_close(fd); + + ASSERT_OK(sd_hwdb_new_from_path(path, &hwdb)); + ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:v*"), EINVAL); +} + static int intro(void) { _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; int r; -- 2.47.3