diff options
Diffstat (limited to 'src/fuzz')
26 files changed, 1049 insertions, 0 deletions
diff --git a/src/fuzz/fuzz-bus-message.c b/src/fuzz/fuzz-bus-message.c new file mode 100644 index 0000000..9842c62 --- /dev/null +++ b/src/fuzz/fuzz-bus-message.c @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <errno.h> +#include <stdio.h> + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-message.h" +#include "env-util.h" +#include "fd-util.h" +#include "fuzz.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 *g = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ void *buffer = NULL; + int r; + + /* 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); + + r = sd_bus_new(&bus); + assert_se(r >= 0); + + assert_se(buffer = memdup(data, size)); + + r = bus_message_from_malloc(bus, buffer, size, NULL, 0, NULL, &m); + if (r == -EBADMSG) + return 0; + assert_se(r >= 0); + TAKE_PTR(buffer); + + if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0) + assert_se(g = open_memstream(&out, &out_size)); + + bus_message_dump(m, g ?: stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + r = sd_bus_message_rewind(m, true); + assert_se(r >= 0); + + return 0; +} diff --git a/src/fuzz/fuzz-catalog.c b/src/fuzz/fuzz-catalog.c new file mode 100644 index 0000000..7ee9750 --- /dev/null +++ b/src/fuzz/fuzz-catalog.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#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_hashmap_free_free_free_ Hashmap *h = NULL; + + if (!getenv("SYSTEMD_LOG_LEVEL")) + log_set_max_level(LOG_CRIT); + + assert_se(h = 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..9c5dfc9 --- /dev/null +++ b/src/fuzz/fuzz-compress.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <errno.h> + +#include "alloc-util.h" +#include "compress.h" +#include "fuzz.h" + +static int compress(int alg, + const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { + + if (alg == OBJECT_COMPRESSED_LZ4) + return compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size); + if (alg == OBJECT_COMPRESSED_XZ) + return compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size); + return -EOPNOTSUPP; +} + +typedef struct header { + uint32_t alg:2; /* We have only two 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", + object_compressed_to_string(alg) ?: "(none)", + 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(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 (!buf) { + log_oom(); + return 0; + } + + size_t sw_len = MIN(data_len - 1, h->sw_len); + + r = decompress_startswith(alg, buf, csize, &buf2, &sw_alloc, h->data, sw_len, h->data[sw_len]); + assert_se(r > 0); + + return 0; +} diff --git a/src/fuzz/fuzz-dhcp-server.c b/src/fuzz/fuzz-dhcp-server.c new file mode 100644 index 0000000..01fe350 --- /dev/null +++ b/src/fuzz/fuzz-dhcp-server.c @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fuzz.h" + +#include "sd-dhcp-server.c" + +/* stub out network so that the server doesn't send */ +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { + return len; +} + +ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) { + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + struct in_addr address = {.s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; + static const uint8_t chaddr[] = {3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}; + uint8_t *client_id; + DHCPLease *lease; + int pool_offset; + + if (size < sizeof(DHCPMessage)) + return 0; + + assert_se(sd_dhcp_server_new(&server, 1) >= 0); + server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY); + assert_se(server->fd >= 0); + assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0); + + /* add a lease to the pool to expose additional code paths */ + client_id = malloc(2); + assert_se(client_id); + client_id[0] = 2; + client_id[1] = 2; + lease = new0(DHCPLease, 1); + assert_se(lease); + lease->client_id.length = 2; + lease->client_id.data = client_id; + lease->address = htobe32(UINT32_C(10) << 24 | UINT32_C(2)); + lease->gateway = htobe32(UINT32_C(10) << 24 | UINT32_C(1)); + lease->expiration = UINT64_MAX; + memcpy(lease->chaddr, chaddr, 16); + pool_offset = get_pool_offset(server, lease->address); + server->bound_leases[pool_offset] = lease; + assert_se(hashmap_put(server->leases_by_client_id, &lease->client_id, lease) >= 0); + + (void) dhcp_server_handle_message(server, (DHCPMessage*)data, size); + + return 0; +} diff --git a/src/fuzz/fuzz-dhcp-server.options b/src/fuzz/fuzz-dhcp-server.options new file mode 100644 index 0000000..5c330e5 --- /dev/null +++ b/src/fuzz/fuzz-dhcp-server.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 600 diff --git a/src/fuzz/fuzz-dhcp6-client.c b/src/fuzz/fuzz-dhcp6-client.c new file mode 100644 index 0000000..c9bc2b3 --- /dev/null +++ b/src/fuzz/fuzz-dhcp6-client.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <unistd.h> + +#include "sd-dhcp6-client.h" +#include "sd-event.h" + +#include "dhcp6-internal.h" +#include "dhcp6-protocol.h" +#include "fd-util.h" +#include "fuzz.h" + +static int test_dhcp_fd[2] = { -1, -1 }; + +int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, + const void *packet, size_t len) { + return len; +} + +int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { + assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) >= 0); + return test_dhcp_fd[0]; +} + +static void fuzz_client(const uint8_t *data, size_t size, bool is_information_request_enabled) { + _cleanup_(sd_event_unrefp) sd_event *e; + _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; + struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }; + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_dhcp6_client_new(&client) >= 0); + assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0); + assert_se(sd_dhcp6_client_set_ifindex(client, 42) == 0); + assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); + assert_se(sd_dhcp6_client_set_information_request(client, is_information_request_enabled) == 0); + + assert_se(sd_dhcp6_client_start(client) >= 0); + + if (size >= sizeof(DHCP6Message)) + assert_se(sd_dhcp6_client_set_transaction_id(client, htobe32(0x00ffffff) & ((const DHCP6Message *) data)->transaction_id) == 0); + + assert_se(write(test_dhcp_fd[1], data, size) == (ssize_t) size); + + sd_event_run(e, (uint64_t) -1); + + assert_se(sd_dhcp6_client_stop(client) >= 0); + + test_dhcp_fd[1] = safe_close(test_dhcp_fd[1]); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + /* This triggers client_receive_advertise */ + fuzz_client(data, size, false); + + /* This triggers client_receive_reply */ + fuzz_client(data, size, true); + + return 0; +} diff --git a/src/fuzz/fuzz-dns-packet.c b/src/fuzz/fuzz-dns-packet.c new file mode 100644 index 0000000..c150c81 --- /dev/null +++ b/src/fuzz/fuzz-dns-packet.c @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fuzz.h" +#include "resolved-dns-packet.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + + if (size > DNS_PACKET_SIZE_MAX) + return 0; + + assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX) >= 0); + p->size = 0; /* by default append starts after the header, undo that */ + assert_se(dns_packet_append_blob(p, data, size, NULL) >= 0); + if (size < DNS_PACKET_HEADER_SIZE) { + /* make sure we pad the packet back up to the minimum header size */ + assert_se(p->allocated >= DNS_PACKET_HEADER_SIZE); + memzero(DNS_PACKET_DATA(p) + size, DNS_PACKET_HEADER_SIZE - size); + p->size = DNS_PACKET_HEADER_SIZE; + } + (void) dns_packet_extract(p); + + return 0; +} diff --git a/src/fuzz/fuzz-dns-packet.options b/src/fuzz/fuzz-dns-packet.options new file mode 100644 index 0000000..0824b19 --- /dev/null +++ b/src/fuzz/fuzz-dns-packet.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 65535 diff --git a/src/fuzz/fuzz-journal-remote.c b/src/fuzz/fuzz-journal-remote.c new file mode 100644 index 0000000..3ab4eb0 --- /dev/null +++ b/src/fuzz/fuzz-journal-remote.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fuzz.h" + +#include <sys/mman.h> + +#include "sd-journal.h" + +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "journal-remote.h" +#include "logs-show.h" +#include "memfd-util.h" +#include "strv.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_fclose_ FILE *dev_null = NULL; + RemoteServer s = {}; + char name[] = "/tmp/fuzz-journal-remote.XXXXXX.journal"; + void *mem; + int fdin; /* will be closed by journal_remote handler after EOF */ + _cleanup_close_ int fdout = -1; + sd_journal *j; + OutputMode mode; + int r; + + if (size <= 2) + return 0; + + if (!getenv("SYSTEMD_LOG_LEVEL")) + log_set_max_level(LOG_CRIT); + + assert_se((fdin = memfd_new_and_map("fuzz-journal-remote", size, &mem)) >= 0); + memcpy(mem, data, size); + assert_se(munmap(mem, size) == 0); + + fdout = mkostemps(name, STRLEN(".journal"), O_CLOEXEC); + assert_se(fdout >= 0); + + /* In */ + + assert_se(journal_remote_server_init(&s, name, JOURNAL_WRITE_SPLIT_NONE, false, false) >= 0); + + assert_se(journal_remote_add_source(&s, fdin, (char*) "fuzz-data", false) > 0); + + while (s.active) { + r = journal_remote_handle_raw_source(NULL, fdin, 0, &s); + assert_se(r >= 0); + } + + journal_remote_server_destroy(&s); + assert_se(close(fdin) < 0 && errno == EBADF); /* Check that the fd is closed already */ + + /* Out */ + + r = sd_journal_open_files(&j, (const char**) STRV_MAKE(name), 0); + assert_se(r >= 0); + + if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0) + assert_se(dev_null = fopen("/dev/null", "we")); + + for (mode = 0; mode < _OUTPUT_MODE_MAX; mode++) { + if (!dev_null) + log_info("/* %s */", output_mode_to_string(mode)); + r = show_journal(dev_null ?: stdout, j, mode, 0, 0, -1, 0, NULL); + assert_se(r >= 0); + + r = sd_journal_seek_head(j); + assert_se(r >= 0); + } + + sd_journal_close(j); + unlink(name); + + return 0; +} diff --git a/src/fuzz/fuzz-journal-remote.options b/src/fuzz/fuzz-journal-remote.options new file mode 100644 index 0000000..678d526 --- /dev/null +++ b/src/fuzz/fuzz-journal-remote.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 65536 diff --git a/src/fuzz/fuzz-journald-audit.c b/src/fuzz/fuzz-journald-audit.c new file mode 100644 index 0000000..3f3ce7e --- /dev/null +++ b/src/fuzz/fuzz-journald-audit.c @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fuzz.h" +#include "fuzz-journald.h" +#include "journald-audit.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + Server s; + + dummy_server_init(&s, data, size); + process_audit_string(&s, 0, s.buffer, size); + server_done(&s); + + return 0; +} diff --git a/src/fuzz/fuzz-journald-kmsg.c b/src/fuzz/fuzz-journald-kmsg.c new file mode 100644 index 0000000..f7426c8 --- /dev/null +++ b/src/fuzz/fuzz-journald-kmsg.c @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fuzz.h" +#include "fuzz-journald.h" +#include "journald-kmsg.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + Server s; + + if (size == 0) + return 0; + + dummy_server_init(&s, data, size); + dev_kmsg_record(&s, s.buffer, size); + server_done(&s); + + return 0; +} diff --git a/src/fuzz/fuzz-journald-native-fd.c b/src/fuzz/fuzz-journald-native-fd.c new file mode 100644 index 0000000..8e3e850 --- /dev/null +++ b/src/fuzz/fuzz-journald-native-fd.c @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fd-util.h" +#include "fs-util.h" +#include "fuzz-journald.h" +#include "fuzz.h" +#include "journald-native.h" +#include "memfd-util.h" +#include "process-util.h" +#include "tmpfile-util.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + Server s; + _cleanup_close_ int sealed_fd = -1, unsealed_fd = -1; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/fuzz-journald-native-fd.XXXXXX"; + char *label = NULL; + size_t label_len = 0; + struct ucred ucred; + struct timeval *tv = NULL; + + if (!getenv("SYSTEMD_LOG_LEVEL")) + log_set_max_level(LOG_CRIT); + + dummy_server_init(&s, NULL, 0); + + sealed_fd = memfd_new(NULL); + assert_se(sealed_fd >= 0); + assert_se(write(sealed_fd, data, size) == (ssize_t) size); + assert_se(memfd_set_sealed(sealed_fd) >= 0); + assert_se(lseek(sealed_fd, 0, SEEK_SET) == 0); + ucred = (struct ucred) { + .pid = getpid_cached(), + .uid = geteuid(), + .gid = getegid(), + }; + server_process_native_file(&s, sealed_fd, &ucred, tv, label, label_len); + + unsealed_fd = mkostemp_safe(name); + assert_se(unsealed_fd >= 0); + assert_se(write(unsealed_fd, data, size) == (ssize_t) size); + assert_se(lseek(unsealed_fd, 0, SEEK_SET) == 0); + server_process_native_file(&s, unsealed_fd, &ucred, tv, label, label_len); + + server_done(&s); + + return 0; +} diff --git a/src/fuzz/fuzz-journald-native.c b/src/fuzz/fuzz-journald-native.c new file mode 100644 index 0000000..f4de5fd --- /dev/null +++ b/src/fuzz/fuzz-journald-native.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fuzz.h" +#include "fuzz-journald.h" +#include "journald-native.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + fuzz_journald_processing_function(data, size, server_process_native_message); + return 0; +} diff --git a/src/fuzz/fuzz-journald-stream.c b/src/fuzz/fuzz-journald-stream.c new file mode 100644 index 0000000..5d6c8eb --- /dev/null +++ b/src/fuzz/fuzz-journald-stream.c @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <linux/sockios.h> +#include <sys/ioctl.h> + +#include "fd-util.h" +#include "fuzz.h" +#include "fuzz-journald.h" +#include "journald-stream.h" + +static int stream_fds[2] = { -1, -1 }; + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + Server s; + StdoutStream *stream; + int v; + + if (size == 0) + return 0; + + if (!getenv("SYSTEMD_LOG_LEVEL")) + log_set_max_level(LOG_CRIT); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, stream_fds) >= 0); + dummy_server_init(&s, NULL, 0); + assert_se(stdout_stream_install(&s, stream_fds[0], &stream) >= 0); + assert_se(write(stream_fds[1], data, size) == (ssize_t) size); + while (ioctl(stream_fds[0], SIOCINQ, &v) == 0 && v) + sd_event_run(s.event, (uint64_t) -1); + if (s.n_stdout_streams) + stdout_stream_destroy(stream); + server_done(&s); + stream_fds[1] = safe_close(stream_fds[1]); + + return 0; +} diff --git a/src/fuzz/fuzz-journald-syslog.c b/src/fuzz/fuzz-journald-syslog.c new file mode 100644 index 0000000..100f0ce --- /dev/null +++ b/src/fuzz/fuzz-journald-syslog.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fuzz.h" +#include "fuzz-journald.h" +#include "journald-syslog.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + fuzz_journald_processing_function(data, size, server_process_syslog_message); + return 0; +} diff --git a/src/fuzz/fuzz-journald.c b/src/fuzz/fuzz-journald.c new file mode 100644 index 0000000..950e885 --- /dev/null +++ b/src/fuzz/fuzz-journald.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "alloc-util.h" +#include "fuzz-journald.h" +#include "journald-server.h" +#include "sd-event.h" + +void dummy_server_init(Server *s, const uint8_t *buffer, size_t size) { + *s = (Server) { + .syslog_fd = -1, + .native_fd = -1, + .stdout_fd = -1, + .dev_kmsg_fd = -1, + .audit_fd = -1, + .hostname_fd = -1, + .notify_fd = -1, + .storage = STORAGE_NONE, + .line_max = 64, + }; + assert_se(sd_event_default(&s->event) >= 0); + + if (buffer) { + s->buffer = memdup_suffix0(buffer, size); + assert_se(s->buffer); + s->buffer_size = size + 1; + } +} + +void fuzz_journald_processing_function( + const uint8_t *data, + size_t size, + void (*f)(Server *s, const char *buf, size_t raw_len, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len) + ) { + Server s; + char *label = NULL; + size_t label_len = 0; + struct ucred *ucred = NULL; + struct timeval *tv = NULL; + + if (size == 0) + return; + + dummy_server_init(&s, data, size); + (*f)(&s, s.buffer, size, ucred, tv, label, label_len); + server_done(&s); +} diff --git a/src/fuzz/fuzz-journald.h b/src/fuzz/fuzz-journald.h new file mode 100644 index 0000000..77e3b0c --- /dev/null +++ b/src/fuzz/fuzz-journald.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "journald-server.h" + +void dummy_server_init(Server *s, const uint8_t *buffer, size_t size); + +void fuzz_journald_processing_function( + const uint8_t *data, + size_t size, + void (*f)(Server *s, const char *buf, size_t raw_len, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len) +); diff --git a/src/fuzz/fuzz-json.c b/src/fuzz/fuzz-json.c new file mode 100644 index 0000000..3aa9d08 --- /dev/null +++ b/src/fuzz/fuzz-json.c @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "alloc-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; + + if (size == 0) + return 0; + + f = fmemopen((char*) data, size, "re"); + assert_se(f); + + if (json_parse_file(f, NULL, &v, NULL, NULL) < 0) + return 0; + + g = open_memstream(&out, &out_size); + assert_se(g); + + json_variant_dump(v, 0, g, NULL); + json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, g, NULL); + + return 0; +} diff --git a/src/fuzz/fuzz-lldp.c b/src/fuzz/fuzz-lldp.c new file mode 100644 index 0000000..b9291d4 --- /dev/null +++ b/src/fuzz/fuzz-lldp.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <errno.h> +#include <unistd.h> + +#include "sd-event.h" +#include "sd-lldp.h" + +#include "fd-util.h" +#include "fuzz.h" +#include "lldp-network.h" + +static int test_fd[2] = { -1, -1 }; + +int lldp_network_bind_raw_socket(int ifindex) { + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0) + return -errno; + + return test_fd[0]; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL; + + assert_se(sd_event_new(&e) == 0); + assert_se(sd_lldp_new(&lldp) >= 0); + assert_se(sd_lldp_set_ifindex(lldp, 42) >= 0); + assert_se(sd_lldp_attach_event(lldp, e, 0) >= 0); + assert_se(sd_lldp_start(lldp) >= 0); + + assert_se(write(test_fd[1], data, size) == (ssize_t) size); + assert_se(sd_event_run(e, 0) >= 0); + + assert_se(sd_lldp_stop(lldp) >= 0); + assert_se(sd_lldp_detach_event(lldp) >= 0); + test_fd[1] = safe_close(test_fd[1]); + + return 0; +} diff --git a/src/fuzz/fuzz-main.c b/src/fuzz/fuzz-main.c new file mode 100644 index 0000000..d5c9984 --- /dev/null +++ b/src/fuzz/fuzz-main.c @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "alloc-util.h" +#include "log.h" +#include "fileio.h" +#include "fuzz.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 MIN_NUMBER_OF_RUNS 4 + +int main(int argc, char **argv) { + int i, r; + size_t size; + char *name; + + test_setup_logging(LOG_DEBUG); + + for (i = 1; i < argc; i++) { + _cleanup_free_ char *buf = NULL; + + 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 (int j = 0; j < MIN_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-ndisc-rs.c b/src/fuzz/fuzz-ndisc-rs.c new file mode 100644 index 0000000..3a1e60f --- /dev/null +++ b/src/fuzz/fuzz-ndisc-rs.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <arpa/inet.h> +#include <netinet/icmp6.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "icmp6-util.h" +#include "fuzz.h" +#include "sd-ndisc.h" +#include "socket-util.h" +#include "ndisc-internal.h" + +static int test_fd[2] = { -1, -1 }; + +int icmp6_bind_router_solicitation(int index) { + assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0); + return test_fd[0]; +} + +int icmp6_bind_router_advertisement(int index) { + return -ENOSYS; +} + +int icmp6_receive(int fd, void *iov_base, size_t iov_len, + struct in6_addr *dst, triple_timestamp *timestamp) { + assert_se(read(fd, iov_base, iov_len) == (ssize_t) iov_len); + + if (timestamp) + triple_timestamp_get(timestamp); + + return 0; +} + +int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + struct ether_addr mac_addr = { + .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} + }; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); + assert_se(sd_ndisc_start(nd) >= 0); + assert_se(write(test_fd[1], data, size) == (ssize_t) size); + (void) sd_event_run(e, (uint64_t) -1); + assert_se(sd_ndisc_stop(nd) >= 0); + close(test_fd[1]); + + return 0; +} diff --git a/src/fuzz/fuzz-udev-rules.c b/src/fuzz/fuzz-udev-rules.c new file mode 100644 index 0000000..e894fa8 --- /dev/null +++ b/src/fuzz/fuzz-udev-rules.c @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <errno.h> +#include <sched.h> +#include <sys/mount.h> +#include <unistd.h> + +#include "fd-util.h" +#include "fs-util.h" +#include "fuzz.h" +#include "log.h" +#include "mkdir.h" +#include "missing.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tests.h" +#include "udev.h" + +static struct fakefs { + const char *target; + bool ignore_mount_error; + bool is_mounted; +} fakefss[] = { + { "/sys", false, false }, + { "/dev", false, false }, + { "/run", false, false }, + { "/etc", false, false }, + { UDEVLIBEXECDIR "/rules.d", true, false }, +}; + +static int setup_mount_namespace(void) { + static thread_local bool is_namespaced = false; + + if (is_namespaced) + return 1; + + if (unshare(CLONE_NEWNS) < 0) + return log_error_errno(errno, "Failed to call unshare(): %m"); + + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) + return log_error_errno(errno, "Failed to mount / as private: %m"); + + is_namespaced = true; + + return 1; +} + +static int setup_fake_filesystems(const char *runtime_dir) { + for (unsigned i = 0; i < ELEMENTSOF(fakefss); i++) { + if (mount(runtime_dir, fakefss[i].target, NULL, MS_BIND, NULL) < 0) { + log_full_errno(fakefss[i].ignore_mount_error ? LOG_DEBUG : LOG_ERR, errno, "Failed to mount %s: %m", fakefss[i].target); + if (!fakefss[i].ignore_mount_error) + return -errno; + } else + fakefss[i].is_mounted = true; + } + + return 0; +} + +static int cleanup_fake_filesystems(const char *runtime_dir) { + for (unsigned i = 0; i < ELEMENTSOF(fakefss); i++) { + if (!fakefss[i].is_mounted) + continue; + + if (umount(fakefss[i].target) < 0) { + log_full_errno(fakefss[i].ignore_mount_error ? LOG_DEBUG : LOG_ERR, errno, "Failed to umount %s: %m", fakefss[i].target); + if (!fakefss[i].ignore_mount_error) + return -errno; + } else + fakefss[i].is_mounted = false; + } + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(udev_rules_freep) UdevRules *rules = NULL; + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + FILE *f = NULL; + + (void) setup_mount_namespace(); + + assert_se(runtime_dir = setup_fake_runtime_dir()); + + if (setup_fake_filesystems(runtime_dir) < 0) { +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + return EXIT_TEST_SKIP; +#endif + } + + if (!getenv("SYSTEMD_LOG_LEVEL")) { + log_set_max_level_realm(LOG_REALM_UDEV, LOG_CRIT); + log_set_max_level_realm(LOG_REALM_SYSTEMD, LOG_CRIT); + } + + assert_se(mkdir_p("/etc/udev/rules.d", 0755) >= 0); + f = fopen("/etc/udev/rules.d/fuzz.rules", "we"); + assert_se(f); + if (size != 0) + assert_se(fwrite(data, size, 1, f) == 1); + assert_se(fclose(f) == 0); + + assert_se(udev_rules_new(&rules, RESOLVE_NAME_EARLY) == 0); + + assert_se(cleanup_fake_filesystems(runtime_dir) >= 0); + return 0; +} diff --git a/src/fuzz/fuzz-unit-file.c b/src/fuzz/fuzz-unit-file.c new file mode 100644 index 0000000..93de501 --- /dev/null +++ b/src/fuzz/fuzz-unit-file.c @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "conf-parser.h" +#include "fd-util.h" +#include "fileio.h" +#include "fuzz.h" +#include "install.h" +#include "load-fragment.h" +#include "string-util.h" +#include "unit.h" +#include "utf8.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_free_ char *p = NULL; + UnitType t; + _cleanup_(manager_freep) Manager *m = NULL; + Unit *u; + const char *name; + long offset; + + if (size == 0) + return 0; + + f = fmemopen((char*) data, size, "re"); + assert_se(f); + + if (read_line(f, LINE_MAX, &p) < 0) + return 0; + + t = unit_type_from_string(p); + if (t < 0) + return 0; + + if (!unit_vtable[t]->load) + return 0; + + offset = ftell(f); + assert_se(offset >= 0); + + for (;;) { + _cleanup_free_ char *l = NULL; + const char *ll; + + if (read_line(f, LONG_LINE_MAX, &l) <= 0) + break; + + ll = startswith(l, UTF8_BYTE_ORDER_MARK) ?: l; + ll = ll + strspn(ll, WHITESPACE); + + if (HAS_FEATURE_MEMORY_SANITIZER && startswith(ll, "ListenNetlink")) { + /* ListenNetlink causes a false positive in msan, + * let's skip this for now. */ + log_notice("Skipping test because ListenNetlink= is present"); + return 0; + } + } + + assert_se(fseek(f, offset, SEEK_SET) == 0); + + /* 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); + + assert_se(manager_new(UNIT_FILE_SYSTEM, MANAGER_TEST_RUN_MINIMAL, &m) >= 0); + + name = strjoina("a.", unit_type_to_string(t)); + assert_se(unit_new_for_name(m, unit_vtable[t]->object_size, name, &u) >= 0); + + (void) config_parse(name, name, f, + UNIT_VTABLE(u)->sections, + config_item_perf_lookup, load_fragment_gperf_lookup, + CONFIG_PARSE_ALLOW_INCLUDE, u); + + g = open_memstream(&out, &out_size); + assert_se(g); + + unit_dump(u, g, ""); + + return 0; +} diff --git a/src/fuzz/fuzz.h b/src/fuzz/fuzz.h new file mode 100644 index 0000000..1e56526 --- /dev/null +++ b/src/fuzz/fuzz.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <stddef.h> +#include <stdint.h> + +/* The entry point into the fuzzer */ +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build new file mode 100644 index 0000000..f628001 --- /dev/null +++ b/src/fuzz/meson.build @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: LGPL-2.1+ + +fuzzers += [ + [['src/fuzz/fuzz-bus-message.c'], + [libshared], + []], + + [['src/fuzz/fuzz-catalog.c'], + [libjournal_core, + libshared], + []], + + [['src/fuzz/fuzz-dns-packet.c', + dns_type_headers], + [libsystemd_resolve_core, + libshared], + [libgcrypt, + libgpg_error, + libm]], + + [['src/fuzz/fuzz-dhcp6-client.c', + 'src/libsystemd-network/dhcp-identifier.h', + 'src/libsystemd-network/dhcp-identifier.c', + 'src/libsystemd-network/dhcp6-internal.h', + 'src/systemd/sd-dhcp6-client.h'], + [libshared, + libsystemd_network], + []], + + [['src/fuzz/fuzz-dhcp-server.c'], + [libsystemd_network, + libshared], + []], + + [['src/fuzz/fuzz-lldp.c'], + [libshared, + libsystemd_network], + []], + + [['src/fuzz/fuzz-ndisc-rs.c', + 'src/libsystemd-network/dhcp-identifier.h', + 'src/libsystemd-network/dhcp-identifier.c', + 'src/libsystemd-network/icmp6-util.h', + 'src/systemd/sd-dhcp6-client.h', + 'src/systemd/sd-ndisc.h'], + [libshared, + libsystemd_network], + []], + + [['src/fuzz/fuzz-json.c'], + [libshared], + []], + + [['src/fuzz/fuzz-unit-file.c'], + [libcore, + libshared], + [libmount]], + + [['src/fuzz/fuzz-journald-audit.c', + 'src/fuzz/fuzz-journald.c'], + [libjournal_core, + libshared], + [libselinux]], + + [['src/fuzz/fuzz-journald-kmsg.c', + 'src/fuzz/fuzz-journald.c'], + [libjournal_core, + libshared], + [libselinux]], + + [['src/fuzz/fuzz-journald-native.c', + 'src/fuzz/fuzz-journald.c'], + [libjournal_core, + libshared], + [libselinux]], + + [['src/fuzz/fuzz-journald-native-fd.c', + 'src/fuzz/fuzz-journald.c'], + [libjournal_core, + libshared], + [libselinux]], + + [['src/fuzz/fuzz-journald-stream.c', + 'src/fuzz/fuzz-journald.c'], + [libjournal_core, + libshared], + [libselinux]], + + [['src/fuzz/fuzz-journald-syslog.c', + 'src/fuzz/fuzz-journald.c'], + [libjournal_core, + libshared], + [libselinux]], + + [['src/fuzz/fuzz-journal-remote.c'], + [libsystemd_journal_remote, + libshared], + []], + + [['src/fuzz/fuzz-udev-rules.c'], + [libudev_core, + libudev_static, + libsystemd_network, + libshared], + [threads, + libacl]], + + [['src/fuzz/fuzz-compress.c'], + [libshared], + []], +] |