]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
hwdb: reject out-of-bounds fnmatch prefixes
authorLuca Boccassi <luca.boccassi@gmail.com>
Thu, 21 May 2026 22:24:32 +0000 (23:24 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 22 May 2026 08:17:24 +0000 (09:17 +0100)
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 <github-copilot[bot]@users.noreply.github.com>
src/libsystemd/sd-hwdb/sd-hwdb.c
src/libsystemd/sd-hwdb/test-sd-hwdb.c

index 1197c4f954c80265c14e3f4bf9cfb83a281a7444..996f253fd908aefc010b5950e8b69348ce72d72c 100644 (file)
@@ -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);
index 0030096a7c4fd1fe3767d8f75940be84551c7f63..dff29c01e8fe059137f9bb780b5a588e206895dd 100644 (file)
@@ -1,11 +1,18 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <fcntl.h>
+#include <unistd.h>
+
 #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;