This patch implements the harness for the QEMU driver to fuzz XML parsing
and command line generation. The harness uses existing test driver setup
and test utilities under testutilsqemu and qemuxmlconftest.
Three binaries are generated based on three protobuf definitions:
xml_domain, xml_domain_disk_only and xml_domain_interface_only. This is
useful to acheive deeper coverage of interfaces and disk definitions.
The fuzzers also have the fake network, storage and secret drivers set up.
Signed-off-by: Rayhan Faizel <rayhan.faizel(a)gmail.com>
---
tests/fuzz/meson.build | 20 +++
tests/fuzz/proto_to_xml.cc | 18 ++
tests/fuzz/proto_to_xml.h | 3 +
tests/fuzz/qemu_xml_domain_fuzz.cc | 277 +++++++++++++++++++++++++++++
4 files changed, 318 insertions(+)
create mode 100644 tests/fuzz/qemu_xml_domain_fuzz.cc
diff --git a/tests/fuzz/meson.build b/tests/fuzz/meson.build
index d0488c3e36..12f9a719f2 100644
--- a/tests/fuzz/meson.build
+++ b/tests/fuzz/meson.build
@@ -31,6 +31,26 @@ fuzz_autogen_xml_domain_dep = declare_dependency(
]
)
+if conf.has('WITH_QEMU')
+ fuzzer_src = [
+ 'qemu_xml_domain_fuzz.cc',
+ 'proto_to_xml.cc',
+ ]
+
+ qemu_libs = [
+ test_qemu_driver_lib,
+ test_utils_lib,
+ test_utils_qemu_lib,
+ libvirt_lib,
+ ]
+
+ xml_fuzzers += [
+ { 'name': 'qemu_xml_domain_fuzz', 'src': [ fuzzer_src,
xml_domain_proto_src ], 'libs': qemu_libs, 'macro':
'-DXML_DOMAIN', 'deps': [ fuzz_autogen_xml_domain_dep ] },
+ { 'name': 'qemu_xml_domain_fuzz_disk', 'src': [ fuzzer_src,
xml_domain_disk_only_proto_src ], 'libs': qemu_libs, 'macro':
'-DXML_DOMAIN_DISK_ONLY', 'deps': [ fuzz_autogen_xml_domain_dep ] },
+ { 'name': 'qemu_xml_domain_fuzz_interface', 'src': [
fuzzer_src, xml_domain_interface_only_proto_src ], 'libs': qemu_libs,
'macro': '-DXML_DOMAIN_INTERFACE_ONLY', 'deps': [
fuzz_autogen_xml_domain_dep ] },
+ ]
+endif
+
foreach fuzzer: xml_fuzzers
xml_domain_fuzz = executable(fuzzer['name'],
fuzzer['src'],
diff --git a/tests/fuzz/proto_to_xml.cc b/tests/fuzz/proto_to_xml.cc
index 13393ecb34..36ad1028b1 100644
--- a/tests/fuzz/proto_to_xml.cc
+++ b/tests/fuzz/proto_to_xml.cc
@@ -198,6 +198,24 @@ convertProtoToXMLInternal(const Message &message,
}
+void convertProtoToQEMUXMLDomain(const libvirt::MainObj &message,
+ std::string arch,
+ std::string &xml)
+{
+ xml = "<domain type='qemu'>\n"
+ " <name>MyGuest</name>\n"
+ "
<uuid>4dea22b3-1d52-d8f3-2516-782e98ab3fa0</uuid>\n"
+ " <os>\n"
+ " <type arch='" + arch +
"'>hvm</type>\n"
+ " </os>\n"
+ " <memory>4096</memory>\n";
+
+ convertProtoToXMLInternal(message, xml, true);
+
+ xml += "</domain>\n";
+}
+
+
void convertProtoToXML(const libvirt::MainObj &message,
std::string &xml)
{
diff --git a/tests/fuzz/proto_to_xml.h b/tests/fuzz/proto_to_xml.h
index 7fe9597a19..a87547a319 100644
--- a/tests/fuzz/proto_to_xml.h
+++ b/tests/fuzz/proto_to_xml.h
@@ -24,5 +24,8 @@
#include "port/protobuf.h"
+void convertProtoToQEMUXMLDomain(const libvirt::MainObj &message,
+ std::string arch,
+ std::string &xml);
void convertProtoToXML(const libvirt::MainObj &message,
std::string &xml);
diff --git a/tests/fuzz/qemu_xml_domain_fuzz.cc b/tests/fuzz/qemu_xml_domain_fuzz.cc
new file mode 100644
index 0000000000..e1618561cf
--- /dev/null
+++ b/tests/fuzz/qemu_xml_domain_fuzz.cc
@@ -0,0 +1,277 @@
+/*
+ * qemu_xml_domain_fuzz.cc: QEMU XML domain fuzzing harness
+ *
+ * Copyright (C) 2024 Rayhan Faizel
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <
http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include "proto_header_common.h"
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+extern "C" {
+#include "internal.h"
+#include "viralloc.h"
+#include "viridentity.h"
+#include "qemu/qemu_block.h"
+#include "qemu/qemu_capabilities.h"
+#include "qemu/qemu_domain.h"
+#include "qemu/qemu_migration.h"
+#include "qemu/qemu_process.h"
+#include "qemu/qemu_slirp.h"
+#include "datatypes.h"
+#include "conf/storage_conf.h"
+#include "virfilewrapper.h"
+#include "configmake.h"
+
+#include "testutils.h"
+#include "testutilsqemu.h"
+}
+
+#include "port/protobuf.h"
+#include "proto_to_xml.h"
+#include "src/libfuzzer/libfuzzer_macro.h"
+
+uint64_t parse_pass = 0;
+uint64_t format_pass = 0;
+uint64_t command_line_pass = 0;
+uint64_t success = 0;
+
+extern std::string emulator;
+
+bool enable_xml_dump = false;
+bool enable_xml_format = false;
+
+static virCommand *
+fuzzGenerateCommandLine(virQEMUDriver *driver,
+ virDomainObj *vm)
+{
+ if (qemuProcessCreatePretendCmdPrepare(driver, vm, NULL,
+ VIR_QEMU_PROCESS_START_COLD) < 0)
+ return NULL;
+
+ return qemuProcessCreatePretendCmdBuild(vm, NULL);
+}
+
+
+static void
+fuzzXMLToCommandLine(virQEMUDriver *driver,
+ const char *xml_string)
+{
+ virDomainDef *def = NULL;
+ g_autofree char *formatted_xml = NULL;
+ virDomainObj *vm = NULL;
+ virCommand *command = NULL;
+
+ parse_pass++;
+ if (!(def = virDomainDefParseString(xml_string, driver->xmlopt, NULL,
+ VIR_DOMAIN_DEF_PARSE_INACTIVE)))
+ goto cleanup;
+
+ if (enable_xml_format) {
+ format_pass++;
+ if (!(formatted_xml = virDomainDefFormat(def, driver->xmlopt,
+ VIR_DOMAIN_DEF_FORMAT_SECURE)))
+ goto cleanup;
+ }
+
+ if (!(vm = virDomainObjNew(driver->xmlopt)))
+ goto cleanup;
+
+ vm->def = def;
+
+ command_line_pass++;
+ if (!(command = fuzzGenerateCommandLine(driver, vm)))
+ goto cleanup;
+
+ success++;
+
+ cleanup:
+ virCommandFree(command);
+
+ if (vm) {
+ vm->def = NULL;
+ virObjectUnref(vm);
+ }
+
+ virDomainDefFree(def);
+}
+
+
+static int
+setupTestInfo(testQemuInfo *info, struct testQemuConf *testConf, ...)
+{
+ va_list ap;
+
+ info->name = "fuzz";
+ info->conf = testConf;
+
+ va_start(ap, testConf);
+ testQemuInfoSetArgs(info, ap);
+ va_end(ap);
+
+ if (testQemuInfoInitArgs(info) < 0)
+ return -1;
+
+ return 0;
+}
+
+/* Fake drivers identical to the one in the test suite.
+ * TODO: Try to replace some of the handlers such that file I/O is avoided.
+ */
+
+static virStoragePoolPtr
+fuzzStoragePoolLookupByName(virConnectPtr conn,
+ const char *name G_GNUC_UNUSED)
+{
+ return fakeStoragePoolLookupByName(conn, "pool-fs");
+}
+
+
+static virNetworkPtr
+fuzzNetworkLookupByName(virConnectPtr conn,
+ const char *name G_GNUC_UNUSED)
+{
+ return fakeNetworkLookupByName(conn, "nat-network;plug-network-basic");
+}
+
+static virStorageDriver fakeStorageDriver = {
+ .storagePoolLookupByName = fuzzStoragePoolLookupByName,
+ .storagePoolGetXMLDesc = fakeStoragePoolGetXMLDesc,
+ .storageVolLookupByName = fakeStorageVolLookupByName,
+ .storageVolGetInfo = fakeStorageVolGetInfo,
+ .storageVolGetPath = fakeStorageVolGetPath,
+ .storagePoolIsActive = fakeStoragePoolIsActive,
+};
+
+static virNetworkDriver fakeNetworkDriver = {
+ .networkLookupByName = fuzzNetworkLookupByName,
+ .networkGetXMLDesc = fakeNetworkGetXMLDesc,
+ .networkPortCreateXML = fakeNetworkPortCreateXML,
+ .networkPortGetXMLDesc = fakeNetworkPortGetXMLDesc,
+};
+
+static virSecretDriver fakeSecretDriver = {
+ .connectNumOfSecrets = NULL,
+ .connectListSecrets = NULL,
+ .secretLookupByUUID = fakeSecretLookupByUUID,
+ .secretLookupByUsage = fakeSecretLookupByUsage,
+ .secretDefineXML = NULL,
+ .secretGetXMLDesc = NULL,
+ .secretSetValue = NULL,
+ .secretGetValue = fakeSecretGetValue,
+ .secretUndefine = NULL,
+};
+
+DEFINE_PROTO_FUZZER(const libvirt::MainObj &message)
+{
+ static GHashTable *capscache = virHashNew(virObjectUnref);
+ static GHashTable *capslatest = testQemuGetLatestCaps();
+ static GHashTable *qapiSchemaCache = virHashNew((GDestroyNotify)
g_hash_table_unref);
+
+ static struct testQemuConf testConf = { .capscache = capscache,
+ .capslatest = capslatest,
+ .qapiSchemaCache = qapiSchemaCache };
+
+ static virQEMUDriver driver;
+ static virConnect *conn;
+ static bool initialized = false;
+
+ static testQemuInfo *info = g_new0(testQemuInfo, 1);
+
+ static const char *arch_env = g_getenv("LPM_FUZZ_ARCH");
+ static const char *dump_xml_env = g_getenv("LPM_XML_DUMP_INPUT");
+ static const char *format_xml_env = g_getenv("LPM_XML_FORMAT_ENABLE");
+
+ static std::string arch = "";
+
+ std::string xml = "";
+ int ret = 0;
+
+ /*
+ * One-time setup of QEMU driver and testQemuInfo. Re-running them in every
+ * iteration incurs a significant penalty to the speed of the fuzzer.
+ */
+ if (!initialized) {
+ FUZZ_COMMON_INIT();
+
+ if (qemuTestDriverInit(&driver) < 0)
+ exit(EXIT_FAILURE);
+
+ if (!(conn = virGetConnect()))
+ exit(EXIT_FAILURE);
+
+ conn->networkDriver = &fakeNetworkDriver;
+ conn->storageDriver = &fakeStorageDriver;
+ conn->secretDriver = &fakeSecretDriver;
+
+ virSetConnectNetwork(conn);
+ virSetConnectStorage(conn);
+ virSetConnectSecret(conn);
+
+ if (arch_env) {
+ arch = arch_env;
+ } else {
+ arch = "x86_64";
+ }
+
+ ret = setupTestInfo(info, &testConf, ARG_CAPS_ARCH, arch.c_str(),
+ ARG_CAPS_VER, "latest",
+ ARG_END);
+
+ emulator = "/usr/bin/qemu-system-" + arch;
+
+ if (ret < 0) {
+ printf("Unable to set up test information (invalid arch?)\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* QEMU Capabilities Cache Setup */
+ virFileCacheClear(driver.qemuCapsCache);
+ if (qemuTestCapsCacheInsert(driver.qemuCapsCache, info->qemuCaps) < 0)
+ exit(EXIT_FAILURE);
+
+ /* Enable printing of XML to stdout (useful for debugging crashes) */
+ if (dump_xml_env && STREQ(dump_xml_env, "YES"))
+ enable_xml_dump = true;
+
+ /* Enable fuzzing of XML formatting */
+ if (format_xml_env && STREQ(format_xml_env, "YES"))
+ enable_xml_format = true;
+
+ initialized = true;
+ }
+
+ convertProtoToQEMUXMLDomain(message, arch, xml);
+
+ if (enable_xml_dump)
+ printf("%s\n", xml.c_str());
+
+ fuzzXMLToCommandLine(&driver, xml.c_str());
+
+ if (parse_pass % 1000 == 0)
+ printf("[FUZZ METRICS] Parse: %lu, Format: %lu, Cmdline: %lu, Success:
%lu\n",
+ parse_pass, format_pass, command_line_pass, success);
+}
--
2.34.1