simple_fuzzers += files(
'sd-bus/fuzz-bus-match.c',
'sd-bus/fuzz-bus-message.c',
+ 'sd-hwdb/fuzz-hwdb.c',
)
--- /dev/null
+/* 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;
+}
Copyright © 2008 Alan Jenkins <alan.christopher.jenkins@googlemail.com>
***/
+#include <errno.h>
#include <fnmatch.h>
#include <stdio.h>
#include <stdlib.h>
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 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) {
- return hwdb->map + le64toh(off);
+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) {
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;
}
assert(hwdb);
key = trie_string(hwdb, entry->key_off);
+ if (!key)
+ return -EBADMSG;
/*
* Silently ignore all properties which do not start with a
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;
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);
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;
}
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);
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);
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);
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)
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;
}
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;
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;
}
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;
.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) {
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) \