hwdb: bounds-check trie offsets against the mmap before dereferencing
Fixes a family of OOB reads in the trie walker where attacker-controlled
offsets are loaded from a (possibly hostile) hwdb.bin and added to
hwdb->map without first checking that the resulting pointer stays inside
the mapping. Crash sites:
- trie_fnmatch_f sd-hwdb.c:187 (#42340)
- trie_search_f sd-hwdb.c:233 (#42341)
- trie_children_cmp_f /bsearch sd-hwdb.c:94 (#42342)
- hwdb_add_property sd-hwdb.c:121 (#42343)
- trie_node_from_off (UBSAN) sd-hwdb.c:83
- trie_fnmatch_f stack overflow on cyclic children (CIFuzz finding)
Commit b45a897edc ("hwdb: reject out-of-bounds fnmatch prefixes")
plugged the first site only at the prefix_off-content level, but the
crash on the read of node->prefix_off itself still happens when node is
already OOB. This is the structural fix: a single hwdb_at() helper
validates that [off, off + size) lies inside the mapping, and
trie_node_from_off() / trie_string() return NULL on OOB; every caller
treats the result as nullable and surfaces -EBADMSG.
trie_fnmatch_f() additionally caps its recursion depth at 2048; without
the cap a corrupt trie whose child offsets form a cycle (or just a deep
linear chain) drives the parser into stack-overflow rather than
returning -EBADMSG (caught by CIFuzz address sanitizer on this branch).
Additions:
- A fuzz-hwdb libFuzzer harness that drives sd_hwdb_new_from_path,
sd_hwdb_get, sd_hwdb_seek, and sd_hwdb_enumerate on attacker bytes.
- Five hand-crafted unit tests in test-sd-hwdb.c (one per crash
bucket, plus the cyclic-trie recursion case) that build a malformed
in-memory hwdb.bin and assert -EBADMSG rather than SIGSEGV /
stack-overflow.
- Regression corpus files under test/fuzz/fuzz-hwdb/ pinning each
fixed bucket, including the CIFuzz stack-overflow reproducer.
Closes #42340.
Closes #42341.
Closes #42342.
Closes #42343.
Reported-by: AI-assisted libFuzzer campaign
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -311,4 +311,5 @@ endif
|
||||
simple_fuzzers += files(
|
||||
'sd-bus/fuzz-bus-match.c',
|
||||
'sd-bus/fuzz-bus-message.c',
|
||||
'sd-hwdb/fuzz-hwdb.c',
|
||||
)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
/* Fuzz target for the sd-hwdb binary trie parser.
|
||||
*
|
||||
* sd-hwdb consumes an mmap'd hwdb.bin produced by `systemd-hwdb update`. The
|
||||
* file is walked via offset-chasing into struct trie_node_f / trie_child_entry_f
|
||||
* / trie_value_entry_f records. A malformed file can drive the walker outside
|
||||
* the mapped region; the validators in trie_node_from_off / trie_string don't
|
||||
* guard every dereference site.
|
||||
*
|
||||
* Expected input: bytes that look like an `hwdb.bin` file (header magic
|
||||
* "KSLPHHRH" + trie). The harness writes the fuzzer bytes to a temp file,
|
||||
* mmaps it via sd_hwdb_new_from_path, then exercises sd_hwdb_get (which
|
||||
* drives trie_search_f / trie_fnmatch_f) and sd_hwdb_seek + sd_hwdb_enumerate
|
||||
* (capped to bound infinite loops via crafted offset cycles).
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-hwdb.h"
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fuzz.h"
|
||||
#include "tests.h"
|
||||
#include "tmpfile-util.h"
|
||||
|
||||
#define MAX_ENUMERATE 1024
|
||||
|
||||
static const char * const modaliases[] = {
|
||||
"pci:v00008086d00000A04",
|
||||
"usb:v1234p5678",
|
||||
"acpi:PNP0C0D:",
|
||||
"dmi:bvnLENOVO:bvrR0KET20W:bd02/01/2020:svnLENOVO:pnThinkPadX1:",
|
||||
"*",
|
||||
};
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
_cleanup_(unlink_tempfilep) char filename[] = "/tmp/fuzz-hwdb.XXXXXX";
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
|
||||
|
||||
if (outside_size_range(size, 0, 4 * 1024 * 1024))
|
||||
return 0;
|
||||
|
||||
fuzz_setup_logging();
|
||||
|
||||
ASSERT_OK(fmkostemp_safe(filename, "r+", &f));
|
||||
if (size != 0)
|
||||
ASSERT_OK_EQ_ERRNO((ssize_t) fwrite(data, size, 1, f), (ssize_t) 1);
|
||||
ASSERT_OK(fflush_and_check(f));
|
||||
|
||||
if (sd_hwdb_new_from_path(filename, &hwdb) < 0)
|
||||
return 0;
|
||||
|
||||
FOREACH_ELEMENT(modalias, modaliases) {
|
||||
const char *key, *value;
|
||||
size_t n = 0;
|
||||
|
||||
(void) sd_hwdb_get(hwdb, *modalias, "ID_VENDOR", &value);
|
||||
|
||||
if (sd_hwdb_seek(hwdb, *modalias) < 0)
|
||||
continue;
|
||||
while (sd_hwdb_enumerate(hwdb, &key, &value) > 0) {
|
||||
DO_NOT_OPTIMIZE(key);
|
||||
DO_NOT_OPTIMIZE(value);
|
||||
if (++n >= MAX_ENUMERATE)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
Copyright © 2008 Alan Jenkins <alan.christopher.jenkins@googlemail.com>
|
||||
***/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fnmatch.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -79,12 +80,74 @@ static const struct trie_value_entry_f *trie_node_value(sd_hwdb *hwdb, const str
|
||||
return (const struct trie_value_entry_f *)base;
|
||||
}
|
||||
|
||||
static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) {
|
||||
return (const struct trie_node_f *)(hwdb->map + le64toh(off));
|
||||
static const void* hwdb_at(sd_hwdb *hwdb, uint64_t off, uint64_t size) {
|
||||
assert(hwdb);
|
||||
|
||||
uint64_t file_size = hwdb->st.st_size;
|
||||
|
||||
/* off == file_size is rejected too: a read at EOF is always OOB, and this keeps the
|
||||
* boundary unambiguous even for a size == 0 caller (which would otherwise get a
|
||||
* one-past-the-end pointer). */
|
||||
if (off >= file_size)
|
||||
return NULL;
|
||||
if (file_size - off < size)
|
||||
return NULL;
|
||||
|
||||
return (const uint8_t*) hwdb->map + off;
|
||||
}
|
||||
|
||||
static const char *trie_string(sd_hwdb *hwdb, le64_t off) {
|
||||
return hwdb->map + le64toh(off);
|
||||
static const struct trie_node_f* trie_node_from_off(sd_hwdb *hwdb, le64_t off) {
|
||||
assert(hwdb);
|
||||
assert(hwdb->head);
|
||||
|
||||
uint64_t offset = le64toh(off);
|
||||
uint64_t node_size = le64toh(hwdb->head->node_size);
|
||||
uint64_t child_entry_size = le64toh(hwdb->head->child_entry_size);
|
||||
uint64_t value_entry_size = le64toh(hwdb->head->value_entry_size);
|
||||
|
||||
if (node_size < sizeof(struct trie_node_f))
|
||||
return NULL;
|
||||
if (child_entry_size < sizeof(struct trie_child_entry_f))
|
||||
return NULL;
|
||||
if (value_entry_size < sizeof(struct trie_value_entry_f))
|
||||
return NULL;
|
||||
|
||||
const struct trie_node_f *node = hwdb_at(hwdb, offset, node_size);
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
uint64_t children_bytes, values_bytes, total;
|
||||
if (!MUL_SAFE(&children_bytes, (uint64_t) node->children_count, child_entry_size))
|
||||
return NULL;
|
||||
if (!MUL_SAFE(&values_bytes, le64toh(node->values_count), value_entry_size))
|
||||
return NULL;
|
||||
if (!ADD_SAFE(&total, node_size, children_bytes))
|
||||
return NULL;
|
||||
if (!INC_SAFE(&total, values_bytes))
|
||||
return NULL;
|
||||
|
||||
if (!hwdb_at(hwdb, offset, total))
|
||||
return NULL;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static const char* trie_string(sd_hwdb *hwdb, le64_t off) {
|
||||
assert(hwdb);
|
||||
|
||||
uint64_t file_size = hwdb->st.st_size;
|
||||
uint64_t offset = le64toh(off);
|
||||
|
||||
const char *p = hwdb_at(hwdb, offset, /* size= */ 1);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
/* Clamp to SIZE_MAX so memchr()'s size_t arg cannot truncate on 32-bit. */
|
||||
size_t avail = (size_t) MIN(file_size - offset, (uint64_t) SIZE_MAX);
|
||||
if (!memchr(p, '\0', avail))
|
||||
return NULL;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static int trie_children_cmp_f(const void *v1, const void *v2) {
|
||||
@@ -102,7 +165,9 @@ static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_
|
||||
child = bsearch(&search, (const char *)node + le64toh(hwdb->head->node_size), node->children_count,
|
||||
le64toh(hwdb->head->child_entry_size), trie_children_cmp_f);
|
||||
if (child)
|
||||
/* Treat corrupt child offsets like lookup misses. */
|
||||
return trie_node_from_off(hwdb, child->child_off);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -113,6 +178,8 @@ static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *ent
|
||||
assert(hwdb);
|
||||
|
||||
key = trie_string(hwdb, entry->key_off);
|
||||
if (!key)
|
||||
return -EBADMSG;
|
||||
|
||||
/*
|
||||
* Silently ignore all properties which do not start with a
|
||||
@@ -173,8 +240,14 @@ static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *ent
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Cap recursion depth so a corrupt hwdb.bin whose children offsets form a
|
||||
* cycle (or just a deep linear chain) cannot exhaust the stack. Real-world
|
||||
* hwdb files do not approach this; the deepest legitimate trie key is well
|
||||
* under a kilobyte. */
|
||||
#define HWDB_RECURSION_MAX 2048U
|
||||
|
||||
static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p,
|
||||
struct linebuf *buf, const char *search) {
|
||||
struct linebuf *buf, const char *search, unsigned depth) {
|
||||
size_t len;
|
||||
size_t i;
|
||||
const char *prefix;
|
||||
@@ -183,21 +256,31 @@ static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t
|
||||
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;
|
||||
if (depth >= HWDB_RECURSION_MAX)
|
||||
return -EBADMSG;
|
||||
|
||||
prefix = trie_string(hwdb, node->prefix_off);
|
||||
len = strnlen(prefix + p, file_size - prefix_off - p);
|
||||
if (!prefix)
|
||||
return -EBADMSG;
|
||||
|
||||
len = strlen(prefix);
|
||||
if (p > len)
|
||||
return -EBADMSG;
|
||||
len -= 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);
|
||||
const struct trie_node_f *child_node;
|
||||
|
||||
linebuf_add_char(buf, child->c);
|
||||
err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search);
|
||||
child_node = trie_node_from_off(hwdb, child->child_off);
|
||||
if (!child_node)
|
||||
return -EBADMSG;
|
||||
|
||||
err = trie_fnmatch_f(hwdb, child_node, 0, buf, search, depth + 1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
linebuf_rem_char(buf);
|
||||
@@ -223,16 +306,24 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) {
|
||||
linebuf_init(&buf);
|
||||
|
||||
node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off);
|
||||
if (!node)
|
||||
return -EBADMSG;
|
||||
|
||||
while (node) {
|
||||
const struct trie_node_f *child;
|
||||
size_t p = 0;
|
||||
|
||||
if (node->prefix_off) {
|
||||
const char *prefix;
|
||||
char c;
|
||||
|
||||
for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) {
|
||||
prefix = trie_string(hwdb, node->prefix_off);
|
||||
if (!prefix)
|
||||
return -EBADMSG;
|
||||
|
||||
for (; (c = prefix[p]); p++) {
|
||||
if (IN_SET(c, '*', '?', '['))
|
||||
return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p);
|
||||
return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p, 0);
|
||||
if (c != search[i + p])
|
||||
return 0;
|
||||
}
|
||||
@@ -242,7 +333,7 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) {
|
||||
child = node_lookup_f(hwdb, node, '*');
|
||||
if (child) {
|
||||
linebuf_add_char(&buf, '*');
|
||||
err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
|
||||
err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
linebuf_rem_char(&buf);
|
||||
@@ -251,7 +342,7 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) {
|
||||
child = node_lookup_f(hwdb, node, '?');
|
||||
if (child) {
|
||||
linebuf_add_char(&buf, '?');
|
||||
err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
|
||||
err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
linebuf_rem_char(&buf);
|
||||
@@ -260,7 +351,7 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) {
|
||||
child = node_lookup_f(hwdb, node, '[');
|
||||
if (child) {
|
||||
linebuf_add_char(&buf, '[');
|
||||
err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i);
|
||||
err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
linebuf_rem_char(&buf);
|
||||
@@ -379,14 +470,15 @@ static int properties_prepare(sd_hwdb *hwdb, const char *modalias) {
|
||||
return trie_search_f(hwdb, modalias);
|
||||
}
|
||||
|
||||
_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) {
|
||||
_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **ret) {
|
||||
const struct trie_value_entry_f *entry;
|
||||
const char *value;
|
||||
int r;
|
||||
|
||||
assert_return(hwdb, -EINVAL);
|
||||
assert_return(hwdb->f, -EINVAL);
|
||||
assert_return(modalias, -EINVAL);
|
||||
assert_return(_value, -EINVAL);
|
||||
assert_return(ret, -EINVAL);
|
||||
|
||||
r = properties_prepare(hwdb, modalias);
|
||||
if (r < 0)
|
||||
@@ -396,7 +488,11 @@ _public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, c
|
||||
if (!entry)
|
||||
return -ENOENT;
|
||||
|
||||
*_value = trie_string(hwdb, entry->value_off);
|
||||
value = trie_string(hwdb, entry->value_off);
|
||||
if (!value)
|
||||
return -EBADMSG;
|
||||
|
||||
*ret = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -418,13 +514,14 @@ _public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) {
|
||||
_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **ret_key, const char **ret_value) {
|
||||
const struct trie_value_entry_f *entry;
|
||||
const char *v;
|
||||
const void *k;
|
||||
|
||||
assert_return(hwdb, -EINVAL);
|
||||
assert_return(key, -EINVAL);
|
||||
assert_return(value, -EINVAL);
|
||||
assert_return(ret_key, -EINVAL);
|
||||
assert_return(ret_value, -EINVAL);
|
||||
|
||||
if (hwdb->properties_modified)
|
||||
return -EAGAIN;
|
||||
@@ -432,8 +529,12 @@ _public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **val
|
||||
if (!ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, (void **)&entry, &k))
|
||||
return 0;
|
||||
|
||||
*key = k;
|
||||
*value = trie_string(hwdb, entry->value_off);
|
||||
v = trie_string(hwdb, entry->value_off);
|
||||
if (!v)
|
||||
return -EBADMSG;
|
||||
|
||||
*ret_key = k;
|
||||
*ret_value = v;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -76,10 +76,82 @@ TEST(sd_hwdb_new_from_path) {
|
||||
assert_se(r >= 0);
|
||||
}
|
||||
|
||||
TEST(sd_hwdb_seek_rejects_invalid_fnmatch_prefix) {
|
||||
static sd_hwdb* hwdb_new_from_blob(const void *data, size_t size) {
|
||||
_cleanup_(rm_rf_physical_and_freep) char *tmp = NULL;
|
||||
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
|
||||
_cleanup_free_ char *path = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
ASSERT_OK(mkdtemp_malloc(/* template= */ NULL, &tmp));
|
||||
_cleanup_free_ char *path = ASSERT_NOT_NULL(path_join(tmp, "hwdb.bin"));
|
||||
|
||||
fd = ASSERT_OK_ERRNO(open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC, 0644));
|
||||
ASSERT_OK_EQ_ERRNO(write(fd, data, size), (ssize_t) size);
|
||||
fd = safe_close(fd);
|
||||
|
||||
ASSERT_OK(sd_hwdb_new_from_path(path, &hwdb));
|
||||
|
||||
return TAKE_PTR(hwdb);
|
||||
}
|
||||
|
||||
TEST(sd_hwdb_seek_rejects_invalid_fnmatch_child_node) {
|
||||
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = 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 wildcard_node;
|
||||
struct trie_child_entry_f child3;
|
||||
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, wildcard_node)),
|
||||
},
|
||||
.wildcard_node = {
|
||||
.prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")),
|
||||
.children_count = 1,
|
||||
},
|
||||
.child3 = {
|
||||
.c = 'x',
|
||||
.child_off = htole64(0xDEAD0000),
|
||||
},
|
||||
.strings = "usb:",
|
||||
};
|
||||
|
||||
hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data)));
|
||||
|
||||
ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:vx"), EBADMSG);
|
||||
}
|
||||
|
||||
TEST(sd_hwdb_seek_rejects_invalid_fnmatch_prefix) {
|
||||
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
|
||||
|
||||
struct invalid_hwdb {
|
||||
struct trie_header_f header;
|
||||
@@ -124,15 +196,165 @@ TEST(sd_hwdb_seek_rejects_invalid_fnmatch_prefix) {
|
||||
.strings = "usb:",
|
||||
};
|
||||
|
||||
ASSERT_OK(mkdtemp_malloc(NULL, &tmp));
|
||||
ASSERT_NOT_NULL(path = path_join(tmp, "hwdb.bin"));
|
||||
hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data)));
|
||||
|
||||
_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_ERROR(sd_hwdb_seek(hwdb, "usb:v*"), EBADMSG);
|
||||
}
|
||||
|
||||
ASSERT_OK(sd_hwdb_new_from_path(path, &hwdb));
|
||||
ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:v*"), EINVAL);
|
||||
TEST(sd_hwdb_seek_rejects_invalid_search_prefix) {
|
||||
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
|
||||
|
||||
struct invalid_hwdb {
|
||||
struct trie_header_f header;
|
||||
struct trie_node_f root_node;
|
||||
char strings[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(sizeof(struct trie_node_f)),
|
||||
.strings_len = htole64(1),
|
||||
},
|
||||
.root_node = {
|
||||
.prefix_off = htole64(0xDEAD0000),
|
||||
},
|
||||
};
|
||||
|
||||
hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data)));
|
||||
|
||||
ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:v1234"), EBADMSG);
|
||||
}
|
||||
|
||||
TEST(sd_hwdb_seek_rejects_truncated_children_array) {
|
||||
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
|
||||
|
||||
struct invalid_hwdb {
|
||||
struct trie_header_f header;
|
||||
struct trie_node_f root_node;
|
||||
} _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(sizeof(struct trie_node_f)),
|
||||
},
|
||||
.root_node = {
|
||||
.children_count = 1,
|
||||
},
|
||||
};
|
||||
|
||||
hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data)));
|
||||
|
||||
ASSERT_ERROR(sd_hwdb_seek(hwdb, "x"), EBADMSG);
|
||||
}
|
||||
|
||||
TEST(sd_hwdb_seek_rejects_invalid_property_key) {
|
||||
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
|
||||
|
||||
struct invalid_hwdb {
|
||||
struct trie_header_f header;
|
||||
struct trie_node_f root_node;
|
||||
struct trie_value_entry_f value;
|
||||
char strings[STRLEN("usb") + 1 + STRLEN("value") + 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 + STRLEN("value") + 1),
|
||||
},
|
||||
.root_node = {
|
||||
.prefix_off = htole64(offsetof(struct invalid_hwdb, strings)),
|
||||
.values_count = htole64(1),
|
||||
},
|
||||
.value = {
|
||||
.key_off = htole64(0xDEAD0000),
|
||||
.value_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb") + 1),
|
||||
},
|
||||
.strings = "usb\0value",
|
||||
};
|
||||
|
||||
hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data)));
|
||||
|
||||
ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb"), EBADMSG);
|
||||
}
|
||||
|
||||
TEST(sd_hwdb_seek_rejects_cyclic_trie) {
|
||||
_cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
|
||||
|
||||
/* Cyclic trie: the wildcard_node's only child points back to
|
||||
* itself, which would drive trie_fnmatch_f() into infinite
|
||||
* recursion (stack-overflow CVE-class bug found by CIFuzz).
|
||||
* The recursion-depth cap must turn this into a clean -EBADMSG. */
|
||||
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 wildcard_node;
|
||||
struct trie_child_entry_f wildcard_self_child;
|
||||
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, wildcard_node)),
|
||||
},
|
||||
.wildcard_node = {
|
||||
.prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")),
|
||||
.children_count = 1,
|
||||
},
|
||||
.wildcard_self_child = {
|
||||
.c = 'a',
|
||||
.child_off = htole64(offsetof(struct invalid_hwdb, wildcard_node)),
|
||||
},
|
||||
.strings = "usb:",
|
||||
};
|
||||
|
||||
hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data)));
|
||||
|
||||
ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:v*"), EBADMSG);
|
||||
}
|
||||
|
||||
static int intro(void) {
|
||||
|
||||
@@ -28,10 +28,10 @@ _SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_hwdb);
|
||||
int sd_hwdb_new(sd_hwdb **ret);
|
||||
int sd_hwdb_new_from_path(const char *path, sd_hwdb **ret);
|
||||
|
||||
int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **value);
|
||||
int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **ret);
|
||||
|
||||
int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias);
|
||||
int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value);
|
||||
int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **ret_key, const char **ret_value);
|
||||
|
||||
/* the inverse condition avoids ambiguity of dangling 'else' after the macro */
|
||||
#define SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) \
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user