summaryrefslogtreecommitdiffstats
path: root/src/fuzz
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/fuzz/fuzz-bootspec-gen.py21
-rw-r--r--src/fuzz/fuzz-bootspec.c125
-rw-r--r--src/fuzz/fuzz-bootspec.options2
-rw-r--r--src/fuzz/fuzz-bus-label.c18
-rw-r--r--src/fuzz/fuzz-calendarspec.c58
-rw-r--r--src/fuzz/fuzz-catalog.c26
-rw-r--r--src/fuzz/fuzz-compress.c69
-rw-r--r--src/fuzz/fuzz-env-file.c31
-rw-r--r--src/fuzz/fuzz-env-file.options2
-rw-r--r--src/fuzz/fuzz-hostname-setup.c23
-rw-r--r--src/fuzz/fuzz-json.c116
-rw-r--r--src/fuzz/fuzz-main.c56
-rw-r--r--src/fuzz/fuzz-time-util.c28
-rw-r--r--src/fuzz/fuzz-udev-database.c26
-rw-r--r--src/fuzz/fuzz-varlink.c130
-rw-r--r--src/fuzz/fuzz.h32
-rw-r--r--src/fuzz/meson.build25
17 files changed, 788 insertions, 0 deletions
diff --git a/src/fuzz/fuzz-bootspec-gen.py b/src/fuzz/fuzz-bootspec-gen.py
new file mode 100644
index 0000000..99af3f5
--- /dev/null
+++ b/src/fuzz/fuzz-bootspec-gen.py
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+"""Generate sample input for fuzz-bootspec"""
+
+import json
+import os
+import sys
+
+config = open(sys.argv[1]).read()
+loader = [entry for entry in open(sys.argv[2], encoding='utf-16-le').read().split('\0')
+ if len(entry) > 2] # filter out fluff from bad decoding
+entries = [(os.path.basename(name), open(name).read())
+ for name in sys.argv[3:]]
+
+data = {
+ 'config': config,
+ 'entries': entries,
+ 'loader': loader,
+}
+
+print(json.dumps(data, indent=4))
diff --git a/src/fuzz/fuzz-bootspec.c b/src/fuzz/fuzz-bootspec.c
new file mode 100644
index 0000000..c08f76c
--- /dev/null
+++ b/src/fuzz/fuzz-bootspec.c
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <string.h>
+
+#include "bootspec.h"
+#include "env-util.h"
+#include "escape.h"
+#include "fuzz.h"
+#include "fd-util.h"
+#include "json.h"
+
+static int json_dispatch_config(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ BootConfig *config = ASSERT_PTR(userdata);
+
+ const char *s = json_variant_string(variant);
+ if (!s)
+ return -EINVAL;
+
+ _cleanup_fclose_ FILE *f = NULL;
+ assert_se(f = data_to_file((const uint8_t*) s, strlen(s)));
+
+ (void) boot_loader_read_conf(config, f, "memstream");
+ return 0;
+}
+
+static int json_dispatch_entries(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ BootConfig *config = ASSERT_PTR(userdata);
+ JsonVariant *entry;
+
+ JSON_VARIANT_ARRAY_FOREACH(entry, variant) {
+ if (!json_variant_is_array(entry) ||
+ json_variant_elements(entry) < 1)
+ return -EINVAL;
+
+ JsonVariant *v;
+ const char *id = NULL, *raw = NULL;
+ _cleanup_free_ char *data = NULL;
+ ssize_t len = -ENODATA;
+
+ v = json_variant_by_index(entry, 0);
+ if (v)
+ id = json_variant_string(v);
+ if (!id)
+ continue;
+
+ v = json_variant_by_index(entry, 1);
+ if (v)
+ raw = json_variant_string(v);
+ if (raw)
+ len = cunescape(raw, UNESCAPE_RELAX | UNESCAPE_ACCEPT_NUL, &data);
+ if (len >= 0) {
+ _cleanup_fclose_ FILE *f = NULL;
+ assert_se(f = data_to_file((const uint8_t*) data, len));
+
+ assert_se(boot_config_load_type1(config, f, "/", "/entries", id) != -ENOMEM);
+ }
+ }
+
+ return 0;
+}
+
+static int json_dispatch_loader(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ BootConfig *config = ASSERT_PTR(userdata);
+ _cleanup_strv_free_ char **entries = NULL;
+ int r;
+
+ r = json_dispatch_strv(name, variant, flags, &entries);
+ if (r < 0)
+ return r;
+
+ (void) boot_config_augment_from_loader(config, entries, false);
+ return 0;
+}
+
+static const JsonDispatch data_dispatch[] = {
+ { "config", JSON_VARIANT_STRING, json_dispatch_config, 0, 0 },
+ { "entries", JSON_VARIANT_ARRAY, json_dispatch_entries, 0, 0 },
+ { "loader", JSON_VARIANT_ARRAY, json_dispatch_loader, 0, 0 },
+ {}
+};
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_free_ const char *datadup = NULL;
+ _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
+ int r;
+
+ if (outside_size_range(size, 0, 65536))
+ return 0;
+
+ /* Disable most logging if not running standalone */
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ assert_se(datadup = memdup_suffix0(data, size));
+
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ r = json_parse(datadup, 0, &v, NULL, NULL);
+ if (r < 0)
+ return 0;
+
+ r = json_dispatch(v, data_dispatch, NULL, 0, &config);
+ if (r < 0)
+ return 0;
+
+ assert_se(boot_config_finalize(&config) >= 0);
+
+ (void) boot_config_select_special_entries(&config, /* skip_efivars= */ false);
+
+ _cleanup_close_ int orig_stdout_fd = -1;
+ if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0) {
+ orig_stdout_fd = fcntl(fileno(stdout), F_DUPFD_CLOEXEC, 3);
+ if (orig_stdout_fd < 0)
+ log_warning_errno(orig_stdout_fd, "Failed to duplicate fd 1: %m");
+ else
+ assert_se(freopen("/dev/null", "w", stdout));
+ }
+
+ (void) show_boot_entries(&config, JSON_FORMAT_OFF);
+ (void) show_boot_entries(&config, JSON_FORMAT_PRETTY);
+
+ if (orig_stdout_fd >= 0)
+ assert_se(freopen(FORMAT_PROC_FD_PATH(orig_stdout_fd), "w", stdout));
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-bootspec.options b/src/fuzz/fuzz-bootspec.options
new file mode 100644
index 0000000..678d526
--- /dev/null
+++ b/src/fuzz/fuzz-bootspec.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/src/fuzz/fuzz-bus-label.c b/src/fuzz/fuzz-bus-label.c
new file mode 100644
index 0000000..93bac9a
--- /dev/null
+++ b/src/fuzz/fuzz-bus-label.c
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "alloc-util.h"
+#include "bus-label.h"
+#include "fuzz.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_free_ char *unescaped = NULL, *escaped = NULL;
+
+ unescaped = bus_label_unescape_n((const char*)data, size);
+ assert_se(unescaped != NULL);
+ escaped = bus_label_escape(unescaped);
+ assert_se(escaped != NULL);
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-calendarspec.c b/src/fuzz/fuzz-calendarspec.c
new file mode 100644
index 0000000..ea027b8
--- /dev/null
+++ b/src/fuzz/fuzz-calendarspec.c
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "calendarspec.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "string-util.h"
+#include "time-util.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(calendar_spec_freep) CalendarSpec *cspec = NULL;
+ _cleanup_free_ char *str = NULL;
+ int r;
+
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ str = memdup_suffix0(data, size);
+
+ size_t l1 = strlen(str);
+ const char* usecs = l1 < size ? str + l1 + 1 : "";
+
+ r = calendar_spec_from_string(str, &cspec);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse \"%s\": %m", str);
+ return 0;
+ }
+
+ _cleanup_free_ char *p = NULL;
+ assert_se(calendar_spec_valid(cspec));
+ assert_se(calendar_spec_to_string(cspec, &p) == 0);
+ assert(p);
+
+ log_debug("spec: %s → %s", str, p);
+
+ _cleanup_(calendar_spec_freep) CalendarSpec *cspec2 = NULL;
+ assert_se(calendar_spec_from_string(p, &cspec2) >= 0);
+ assert_se(calendar_spec_valid(cspec2));
+
+ usec_t usec = 0;
+ (void) parse_time(usecs, &usec, 1);
+
+ /* If timezone is set, calendar_spec_next_usec() would fork, bleh :(
+ * Let's not try that. */
+ cspec->timezone = mfree(cspec->timezone);
+
+ log_debug("00: %s", strna(FORMAT_TIMESTAMP(usec)));
+ for (unsigned i = 1; i <= 20; i++) {
+ r = calendar_spec_next_usec(cspec, usec, &usec);
+ if (r < 0) {
+ log_debug_errno(r, "%02u: %m", i);
+ break;
+ }
+ log_debug("%02u: %s", i, FORMAT_TIMESTAMP(usec));
+ }
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-catalog.c b/src/fuzz/fuzz-catalog.c
new file mode 100644
index 0000000..f013455
--- /dev/null
+++ b/src/fuzz/fuzz-catalog.c
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "catalog.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "fuzz.h"
+#include "tmpfile-util.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(unlink_tempfilep) char name[] = "/tmp/fuzz-catalog.XXXXXX";
+ _cleanup_close_ int fd = -1;
+ _cleanup_ordered_hashmap_free_free_free_ OrderedHashmap *h = NULL;
+
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ assert_se(h = ordered_hashmap_new(&catalog_hash_ops));
+
+ fd = mkostemp_safe(name);
+ assert_se(fd >= 0);
+ assert_se(write(fd, data, size) == (ssize_t) size);
+
+ (void) catalog_import_file(h, name);
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-compress.c b/src/fuzz/fuzz-compress.c
new file mode 100644
index 0000000..10956cc
--- /dev/null
+++ b/src/fuzz/fuzz-compress.c
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+
+#include "alloc-util.h"
+#include "compress.h"
+#include "fuzz.h"
+
+typedef struct header {
+ uint32_t alg:2; /* We have only three compression algorithms so far, but we might add more in the
+ * future. Let's make this a bit wider so our fuzzer cases remain stable in the
+ * future. */
+ uint32_t sw_len;
+ uint32_t sw_alloc;
+ uint32_t reserved[3]; /* Extra space to keep fuzz cases stable in case we need to
+ * add stuff in the future. */
+ uint8_t data[];
+} header;
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_free_ void *buf = NULL, *buf2 = NULL;
+ int r;
+
+ if (size < offsetof(header, data) + 1)
+ return 0;
+
+ const header *h = (struct header*) data;
+ const size_t data_len = size - offsetof(header, data);
+
+ int alg = h->alg;
+
+ /* We don't want to fill the logs with messages about parse errors.
+ * Disable most logging if not running standalone */
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ log_info("Using compression %s, data size=%zu",
+ compression_to_string(alg),
+ data_len);
+
+ buf = malloc(MAX(size, 128u)); /* Make the buffer a bit larger for very small data */
+ if (!buf) {
+ log_oom();
+ return 0;
+ }
+
+ size_t csize;
+ r = compress_blob_explicit(alg, h->data, data_len, buf, size, &csize);
+ if (r < 0) {
+ log_error_errno(r, "Compression failed: %m");
+ return 0;
+ }
+
+ log_debug("Compressed %zu bytes to → %zu bytes", data_len, csize);
+
+ size_t sw_alloc = MAX(h->sw_alloc, 1u);
+ buf2 = malloc(sw_alloc);
+ if (!buf2) {
+ log_oom();
+ return 0;
+ }
+
+ size_t sw_len = MIN(data_len - 1, h->sw_len);
+
+ r = decompress_startswith(alg, buf, csize, &buf2, h->data, sw_len, h->data[sw_len]);
+ assert_se(r > 0);
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-env-file.c b/src/fuzz/fuzz-env-file.c
new file mode 100644
index 0000000..6217361
--- /dev/null
+++ b/src/fuzz/fuzz-env-file.c
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "env-file.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "strv.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_strv_free_ char **rl = NULL, **rlp = NULL;
+
+ if (outside_size_range(size, 0, 65536))
+ return 0;
+
+ f = data_to_file(data, size);
+ assert_se(f);
+
+ /* We don't want to fill the logs with messages about parse errors.
+ * Disable most logging if not running standalone */
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ (void) load_env_file(f, NULL, &rl);
+ assert_se(fseek(f, 0, SEEK_SET) == 0);
+ (void) load_env_file_pairs(f, NULL, &rlp);
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-env-file.options b/src/fuzz/fuzz-env-file.options
new file mode 100644
index 0000000..678d526
--- /dev/null
+++ b/src/fuzz/fuzz-env-file.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536
diff --git a/src/fuzz/fuzz-hostname-setup.c b/src/fuzz/fuzz-hostname-setup.c
new file mode 100644
index 0000000..d7c23ee
--- /dev/null
+++ b/src/fuzz/fuzz-hostname-setup.c
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "hostname-setup.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *ret = NULL;
+
+ f = data_to_file(data, size);
+ assert_se(f);
+
+ /* We don't want to fill the logs with messages about parse errors.
+ * Disable most logging if not running standalone */
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ (void) read_etc_hostname_stream(f, &ret);
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-json.c b/src/fuzz/fuzz-json.c
new file mode 100644
index 0000000..c393fcf
--- /dev/null
+++ b/src/fuzz/fuzz-json.c
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "json.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_free_ char *out = NULL; /* out should be freed after g */
+ size_t out_size;
+ _cleanup_fclose_ FILE *f = NULL, *g = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ /* Disable most logging if not running standalone */
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ f = data_to_file(data, size);
+ assert_se(f);
+
+ r = json_parse_file(f, NULL, 0, &v, NULL, NULL);
+ if (r < 0) {
+ log_debug_errno(r, "failed to parse input: %m");
+ return 0;
+ }
+
+ if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0)
+ assert_se(g = open_memstream_unlocked(&out, &out_size));
+
+ json_variant_dump(v, 0, g ?: stdout, NULL);
+ json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, g ?: stdout, NULL);
+
+ bool sorted = json_variant_is_sorted(v);
+ log_debug("json_variant_is_sorted: %s", yes_no(sorted));
+
+ r = json_variant_sort(&v);
+ log_debug_errno(r, "json_variant_sort: %d/%m", r);
+
+ sorted = json_variant_is_sorted(v);
+ log_debug("json_variant_is_sorted: %s", yes_no(sorted));
+ assert_se(r < 0 || sorted);
+
+ bool normalized = json_variant_is_normalized(v);
+ log_debug("json_variant_is_normalized: %s", yes_no(normalized));
+
+ r = json_variant_normalize(&v);
+ log_debug_errno(r, "json_variant_normalize: %d/%m", r);
+
+ normalized = json_variant_is_normalized(v);
+ log_debug("json_variant_is_normalized: %s", yes_no(normalized));
+ assert_se(r < 0 || normalized);
+
+ double real = json_variant_real(v);
+ log_debug("json_variant_real: %lf", real);
+
+ bool negative = json_variant_is_negative(v);
+ log_debug("json_variant_is_negative: %s", yes_no(negative));
+
+ bool blank = json_variant_is_blank_object(v);
+ log_debug("json_variant_is_blank_object: %s", yes_no(blank));
+
+ blank = json_variant_is_blank_array(v);
+ log_debug("json_variant_is_blank_array: %s", yes_no(blank));
+
+ size_t elements = json_variant_elements(v);
+ log_debug("json_variant_elements: %zu", elements);
+
+ for (size_t i = 0; i <= elements + 2; i++)
+ (void) json_variant_by_index(v, i);
+
+ assert_se(json_variant_equal(v, v));
+ assert_se(!json_variant_equal(v, NULL));
+ assert_se(!json_variant_equal(NULL, v));
+
+ bool sensitive = json_variant_is_sensitive(v);
+ log_debug("json_variant_is_sensitive: %s", yes_no(sensitive));
+
+ json_variant_sensitive(v);
+
+ sensitive = json_variant_is_sensitive(v);
+ log_debug("json_variant_is_sensitive: %s", yes_no(sensitive));
+
+ const char *source;
+ unsigned line, column;
+ assert_se(json_variant_get_source(v, &source, &line, &column) == 0);
+ log_debug("json_variant_get_source: %s:%u:%u", source ?: "-", line, column);
+
+ r = json_variant_set_field_string(&v, "a", "string-a");
+ log_debug_errno(r, "json_set_field_string: %d/%m", r);
+
+ r = json_variant_set_field_integer(&v, "b", -12345);
+ log_debug_errno(r, "json_set_field_integer: %d/%m", r);
+
+ r = json_variant_set_field_unsigned(&v, "c", 12345);
+ log_debug_errno(r, "json_set_field_unsigned: %d/%m", r);
+
+ r = json_variant_set_field_boolean(&v, "d", false);
+ log_debug_errno(r, "json_set_field_boolean: %d/%m", r);
+
+ r = json_variant_set_field_strv(&v, "e", STRV_MAKE("e-1", "e-2", "e-3"));
+ log_debug_errno(r, "json_set_field_strv: %d/%m", r);
+
+ r = json_variant_filter(&v, STRV_MAKE("a", "b", "c", "d", "e"));
+ log_debug_errno(r, "json_variant_filter: %d/%m", r);
+
+ /* I assume we can merge v with itself… */
+ r = json_variant_merge(&v, v);
+ log_debug_errno(r, "json_variant_merge: %d/%m", r);
+
+ r = json_variant_append_array(&v, v);
+ log_debug_errno(r, "json_variant_append_array: %d/%m", r);
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-main.c b/src/fuzz/fuzz-main.c
new file mode 100644
index 0000000..cf70424
--- /dev/null
+++ b/src/fuzz/fuzz-main.c
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "fileio.h"
+#include "fuzz.h"
+#include "log.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "tests.h"
+
+/* This is a test driver for the systemd fuzzers that provides main function
+ * for regression testing outside of oss-fuzz (https://github.com/google/oss-fuzz)
+ *
+ * It reads files named on the command line and passes them one by one into the
+ * fuzzer that it is compiled into. */
+
+/* This one was borrowed from
+ * https://github.com/google/oss-fuzz/blob/646fca1b506b056db3a60d32c4a1a7398f171c94/infra/base-images/base-runner/bad_build_check#L19
+ */
+#define NUMBER_OF_RUNS 4
+
+int main(int argc, char **argv) {
+ int r;
+
+ test_setup_logging(LOG_DEBUG);
+
+ unsigned number_of_runs = NUMBER_OF_RUNS;
+
+ const char *v = getenv("SYSTEMD_FUZZ_RUNS");
+ if (!isempty(v)) {
+ r = safe_atou(v, &number_of_runs);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse SYSTEMD_FUZZ_RUNS=%s: %m", v);
+ }
+
+ for (int i = 1; i < argc; i++) {
+ _cleanup_free_ char *buf = NULL;
+ size_t size;
+ char *name;
+
+ name = argv[i];
+ r = read_full_file(name, &buf, &size);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open '%s': %m", name);
+ return EXIT_FAILURE;
+ }
+ printf("%s... ", name);
+ fflush(stdout);
+ for (unsigned j = 0; j < number_of_runs; j++)
+ if (LLVMFuzzerTestOneInput((uint8_t*)buf, size) == EXIT_TEST_SKIP)
+ return EXIT_TEST_SKIP;
+ printf("ok\n");
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/fuzz/fuzz-time-util.c b/src/fuzz/fuzz-time-util.c
new file mode 100644
index 0000000..bf2a663
--- /dev/null
+++ b/src/fuzz/fuzz-time-util.c
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "time-util.h"
+#include "util.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_free_ char *str = NULL;
+ usec_t usec;
+
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ str = memdup_suffix0(data, size);
+
+ (void) parse_timestamp(str, &usec);
+ (void) parse_sec(str, &usec);
+ (void) parse_sec_fix_0(str, &usec);
+ (void) parse_sec_def_infinity(str, &usec);
+ (void) parse_time(str, &usec, USEC_PER_SEC);
+ (void) parse_nsec(str, &usec);
+
+ (void) timezone_is_valid(str, LOG_DEBUG);
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz-udev-database.c b/src/fuzz/fuzz-udev-database.c
new file mode 100644
index 0000000..2a48c14
--- /dev/null
+++ b/src/fuzz/fuzz-udev-database.c
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "device-internal.h"
+#include "device-private.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "fuzz.h"
+#include "tmpfile-util.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+ _cleanup_(unlink_tempfilep) char filename[] = "/tmp/fuzz-udev-database.XXXXXX";
+ _cleanup_fclose_ FILE *f = NULL;
+
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ assert_se(fmkostemp_safe(filename, "r+", &f) == 0);
+ if (size != 0)
+ assert_se(fwrite(data, size, 1, f) == 1);
+
+ fflush(f);
+ assert_se(device_new_aux(&dev) >= 0);
+ (void) device_read_db_internal_filename(dev, filename);
+ return 0;
+}
diff --git a/src/fuzz/fuzz-varlink.c b/src/fuzz/fuzz-varlink.c
new file mode 100644
index 0000000..397c20d
--- /dev/null
+++ b/src/fuzz/fuzz-varlink.c
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fuzz.h"
+#include "hexdecoct.h"
+#include "io-util.h"
+#include "varlink.h"
+#include "log.h"
+
+static FILE *null = NULL;
+
+static int method_something(Varlink *v, JsonVariant *p, VarlinkMethodFlags flags, void *userdata) {
+ json_variant_dump(p, JSON_FORMAT_NEWLINE|JSON_FORMAT_PRETTY, null, NULL);
+ return 0;
+}
+
+static int reply_callback(Varlink *v, JsonVariant *p, const char *error_id, VarlinkReplyFlags flags, void *userdata) {
+ json_variant_dump(p, JSON_FORMAT_NEWLINE|JSON_FORMAT_PRETTY, null, NULL);
+ return 0;
+}
+
+static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ struct iovec *iov = ASSERT_PTR(userdata);
+ bool write_eof = false, read_eof = false;
+
+ assert(s);
+ assert(fd >= 0);
+
+ if ((revents & (EPOLLOUT|EPOLLHUP|EPOLLERR)) && iov->iov_len > 0) {
+ ssize_t n;
+
+ /* never write more than 143 bytes a time, to make broken up recv()s on the other side more
+ * likely, and thus test some additional code paths. */
+ n = send(fd, iov->iov_base, MIN(iov->iov_len, 143U), MSG_NOSIGNAL|MSG_DONTWAIT);
+ if (n < 0) {
+ if (ERRNO_IS_DISCONNECT(errno))
+ write_eof = true;
+ else
+ assert_se(errno == EAGAIN);
+ } else
+ IOVEC_INCREMENT(iov, 1, n);
+ }
+
+ if (revents & EPOLLIN) {
+ char c[137];
+ ssize_t n;
+
+ n = recv(fd, c, sizeof(c), MSG_DONTWAIT);
+ if (n < 0) {
+ if (ERRNO_IS_DISCONNECT(errno))
+ read_eof = true;
+ else
+ assert_se(errno == EAGAIN);
+ } else if (n == 0)
+ read_eof = true;
+ else
+ hexdump(null, c, (size_t) n);
+ }
+
+ /* After we wrote everything we could turn off EPOLLOUT. And if we reached read EOF too turn off the
+ * whole thing. */
+ if (write_eof || iov->iov_len == 0) {
+
+ if (read_eof)
+ assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0);
+ else
+ assert_se(sd_event_source_set_io_events(s, EPOLLIN) >= 0);
+ }
+
+ return 0;
+}
+
+static int idle_callback(sd_event_source *s, void *userdata) {
+ assert(s);
+
+ /* Called as idle callback when there's nothing else to do anymore */
+ sd_event_exit(sd_event_source_get_event(s), 0);
+ return 0;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ struct iovec server_iov = IOVEC_MAKE((void*) data, size), client_iov = IOVEC_MAKE((void*) data, size);
+ /* Important: the declaration order matters here! we want that the fds are closed on return after the
+ * event sources, hence we declare the fds first, the event sources second */
+ _cleanup_close_pair_ int server_pair[2] = { -1, -1 }, client_pair[2] = { -1, -1 };
+ _cleanup_(sd_event_source_unrefp) sd_event_source *idle_event_source = NULL,
+ *server_event_source = NULL, *client_event_source = NULL;
+ _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
+ _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+
+ log_set_max_level(LOG_CRIT);
+ log_parse_environment();
+
+ assert_se(null = fopen("/dev/null", "we"));
+
+ assert_se(sd_event_default(&e) >= 0);
+
+ /* Test one: write the data as method call to a server */
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, server_pair) >= 0);
+ assert_se(varlink_server_new(&s, 0) >= 0);
+ assert_se(varlink_server_set_description(s, "myserver") >= 0);
+ assert_se(varlink_server_attach_event(s, e, 0) >= 0);
+ assert_se(varlink_server_add_connection(s, server_pair[0], NULL) >= 0);
+ TAKE_FD(server_pair[0]);
+ assert_se(varlink_server_bind_method(s, "io.test.DoSomething", method_something) >= 0);
+ assert_se(sd_event_add_io(e, &server_event_source, server_pair[1], EPOLLIN|EPOLLOUT, io_callback, &server_iov) >= 0);
+
+ /* Test two: write the data as method response to a client */
+ assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, client_pair) >= 0);
+ assert_se(varlink_connect_fd(&c, client_pair[0]) >= 0);
+ TAKE_FD(client_pair[0]);
+ assert_se(varlink_set_description(c, "myclient") >= 0);
+ assert_se(varlink_attach_event(c, e, 0) >= 0);
+ assert_se(varlink_bind_reply(c, reply_callback) >= 0);
+ assert_se(varlink_invoke(c, "io.test.DoSomething", NULL) >= 0);
+ assert_se(sd_event_add_io(e, &client_event_source, client_pair[1], EPOLLIN|EPOLLOUT, io_callback, &client_iov) >= 0);
+
+ assert_se(sd_event_add_defer(e, &idle_event_source, idle_callback, NULL) >= 0);
+ assert_se(sd_event_source_set_priority(idle_event_source, SD_EVENT_PRIORITY_IDLE) >= 0);
+
+ assert_se(sd_event_loop(e) >= 0);
+
+ null = safe_fclose(null);
+
+ return 0;
+}
diff --git a/src/fuzz/fuzz.h b/src/fuzz/fuzz.h
new file mode 100644
index 0000000..77e0ad9
--- /dev/null
+++ b/src/fuzz/fuzz.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "env-util.h"
+#include "fileio.h"
+
+/* The entry point into the fuzzer */
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static inline FILE* data_to_file(const uint8_t *data, size_t size) {
+ if (size == 0)
+ return fopen("/dev/null", "re");
+ else
+ return fmemopen_unlocked((char*) data, size, "r");
+}
+
+/* Check if we are within the specified size range.
+ * The upper limit is ignored if FUZZ_USE_SIZE_LIMIT is unset.
+ */
+static inline bool outside_size_range(size_t size, size_t lower, size_t upper) {
+ if (size < lower)
+ return true;
+ if (size > upper)
+ return FUZZ_USE_SIZE_LIMIT;
+ return false;
+}
+
+/* Force value to not be optimized away. */
+#define DO_NOT_OPTIMIZE(value) ({ asm volatile("" : : "g"(value) : "memory"); })
diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build
new file mode 100644
index 0000000..6f36536
--- /dev/null
+++ b/src/fuzz/meson.build
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+fuzzers += [
+ [files('fuzz-bootspec.c')],
+
+ [files('fuzz-bus-label.c')],
+
+ [files('fuzz-calendarspec.c')],
+
+ [files('fuzz-catalog.c')],
+
+ [files('fuzz-compress.c')],
+
+ [files('fuzz-env-file.c')],
+
+ [files('fuzz-hostname-setup.c')],
+
+ [files('fuzz-json.c')],
+
+ [files('fuzz-time-util.c')],
+
+ [files('fuzz-udev-database.c')],
+
+ [files('fuzz-varlink.c')],
+]