From 65c112007a59cbb2e959a30e6ccd6fadadd75fc7 Mon Sep 17 00:00:00 2001 From: Armaan Sandhu Date: Mon, 31 Aug 2026 22:21:19 -0400 Subject: [PATCH] fuzz: add a fuzzer for the qcow2 converter systemd-importd runs qcow2_detect()/qcow2_convert() over images it has just downloaded (see pull-raw.c), so the header, the L1/L2 tables and every offset and cluster descriptor in them are attacker controlled. There is a manual test for the happy path, but nothing that feeds it a malformed image. The corpus is two images, a plain data cluster and a zlib-compressed one (i.e. decompress_zlib_raw()), at the 512 byte minimum cluster size and trimmed to the last byte the converter reads, so 2K each. A zero cluster and a QCOW3 header are a flipped descriptor bit and a bumped version field away from these, so they are left to the fuzzer rather than carried in the tree. --- src/import/fuzz-qcow2.c | 32 ++++++++++++++++++++++++ src/import/meson.build | 4 +++ test/fuzz/fuzz-qcow2/compressed-cluster | Bin 0 -> 2048 bytes test/fuzz/fuzz-qcow2/plain-cluster | Bin 0 -> 2048 bytes 4 files changed, 36 insertions(+) create mode 100644 src/import/fuzz-qcow2.c create mode 100644 test/fuzz/fuzz-qcow2/compressed-cluster create mode 100644 test/fuzz/fuzz-qcow2/plain-cluster diff --git a/src/import/fuzz-qcow2.c b/src/import/fuzz-qcow2.c new file mode 100644 index 0000000000..36f11b42e8 --- /dev/null +++ b/src/import/fuzz-qcow2.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fd-util.h" +#include "fuzz.h" +#include "memfd-util.h" +#include "qcow2-util.h" + +/* systemd-importd runs this over images it just downloaded (see pull-raw.c), so the header, the L1/L2 + * tables and every offset in them are attacker controlled. */ + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_close_ int qcow2_fd = -EBADF, raw_fd = -EBADF; + + if (size < sizeof(uint32_t)) + return 0; + + fuzz_setup_logging(); + + qcow2_fd = memfd_new_and_seal("fuzz-qcow2-in", data, size); + if (qcow2_fd < 0) + return 0; + + raw_fd = memfd_new("fuzz-qcow2-out"); + if (raw_fd < 0) + return 0; + + if (qcow2_detect(qcow2_fd) <= 0) + return 0; + + (void) qcow2_convert(qcow2_fd, raw_fd); + return 0; +} diff --git a/src/import/meson.build b/src/import/meson.build index a1ac50594f..a8fc08f855 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -93,6 +93,10 @@ executables += [ 'sources' : files('test-oci-util.c'), 'import' : ['systemd-importd'], }, + fuzz_template + { + 'sources' : files('fuzz-qcow2.c'), + 'import' : ['systemd-importd'], + }, ] install_data('org.freedesktop.import1.conf', diff --git a/test/fuzz/fuzz-qcow2/compressed-cluster b/test/fuzz/fuzz-qcow2/compressed-cluster new file mode 100644 index 0000000000000000000000000000000000000000..caeb4733d56abe38aad72b9579a515166543d0d1 GIT binary patch literal 2048 zcmWG^^Zd=gz`z8=$bb{V5rBy?LKx`EVA2HX41y{~6%0rSG=Obo84$ZiEgkp}Z~!}j bZQva;>Vg3Yf#d`UR%OWnv3k_fK@b7}MzRD* literal 0 HcmV?d00001 diff --git a/test/fuzz/fuzz-qcow2/plain-cluster b/test/fuzz/fuzz-qcow2/plain-cluster new file mode 100644 index 0000000000000000000000000000000000000000..72fd329042af1162cd3ac5db57a63326c27fa8ce GIT binary patch literal 2048 zcmWG^^Zd=gz`z8=$bb{V5rBy?LKx`EVA2HX41y{~6%0rSG=Obo84$ZiEgkp}XaGBb lZQva;>Vg3Y0S{LnAAf~FXa8^`1sBH<#{qG~sHFoI0sv*K2c`f3 literal 0 HcmV?d00001