diff options
Diffstat (limited to '')
-rw-r--r-- | src/fuzz/fuzz-bootspec-gen.py | 21 | ||||
-rw-r--r-- | src/fuzz/fuzz-bootspec.c | 125 | ||||
-rw-r--r-- | src/fuzz/fuzz-bootspec.options | 2 | ||||
-rw-r--r-- | src/fuzz/fuzz-bus-label.c | 18 | ||||
-rw-r--r-- | src/fuzz/fuzz-calendarspec.c | 58 | ||||
-rw-r--r-- | src/fuzz/fuzz-catalog.c | 26 | ||||
-rw-r--r-- | src/fuzz/fuzz-compress.c | 69 | ||||
-rw-r--r-- | src/fuzz/fuzz-env-file.c | 31 | ||||
-rw-r--r-- | src/fuzz/fuzz-env-file.options | 2 | ||||
-rw-r--r-- | src/fuzz/fuzz-hostname-setup.c | 23 | ||||
-rw-r--r-- | src/fuzz/fuzz-json.c | 116 | ||||
-rw-r--r-- | src/fuzz/fuzz-main.c | 56 | ||||
-rw-r--r-- | src/fuzz/fuzz-time-util.c | 28 | ||||
-rw-r--r-- | src/fuzz/fuzz-udev-database.c | 26 | ||||
-rw-r--r-- | src/fuzz/fuzz-varlink.c | 130 | ||||
-rw-r--r-- | src/fuzz/fuzz.h | 32 | ||||
-rw-r--r-- | src/fuzz/meson.build | 25 |
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')], +] |