This patch adds the harness for the xen driver to fuzz XML parsing, domconfig generation and JSON conversion. libxlmock.so is required to make the libXL fuzzer work outside of xen hosts. However, LD_PRELOAD breaks the fuzzer when ASAN/UBSAN is present. This is because LD_PRELOAD gets propagated to llvm-symbolizer which is undesirable and causes various undefined symbol errors. We add a wrapper binary which execs the real llvm-symbolizer with the environment unset. However, LD_PRELOAD still breaks parallel fuzzing so it is disabled for the time being. Signed-off-by: Rayhan Faizel <rayhan.faizel@xxxxxxxxx> --- tests/fuzz/libxl_xml_domain_fuzz.cc | 159 +++++++++++++++++++++++++++ tests/fuzz/llvm_symbolizer_wrapper.c | 11 ++ tests/fuzz/meson.build | 31 ++++++ tests/fuzz/proto_to_xml.cc | 18 +++ tests/fuzz/proto_to_xml.h | 3 + tests/fuzz/run_fuzz.in | 15 +++ 6 files changed, 237 insertions(+) create mode 100644 tests/fuzz/libxl_xml_domain_fuzz.cc create mode 100644 tests/fuzz/llvm_symbolizer_wrapper.c diff --git a/tests/fuzz/libxl_xml_domain_fuzz.cc b/tests/fuzz/libxl_xml_domain_fuzz.cc new file mode 100644 index 0000000000..a8fcb62d06 --- /dev/null +++ b/tests/fuzz/libxl_xml_domain_fuzz.cc @@ -0,0 +1,159 @@ +/* + * libxl_xml_domain_fuzz.cc: libXL 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 "testutils.h" +#include "internal.h" +#include "libxl/libxl_conf.h" +#include "testutilsxen.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 config_pass = 0; +uint64_t json_pass = 0; +uint64_t command_line_pass = 0; +uint64_t success = 0; + +bool enable_xml_dump = false; +bool enable_xml_format = false; + +static void +fuzzXMLToCommandLine(libxlDriverPrivate *driver, + const char *xml_string) +{ + virDomainDef *def = NULL; + const char *formatted_xml = NULL; + g_autofree char *json_string = NULL; + libxl_domain_config config; + virPortAllocatorRange *gports = NULL; + bool config_init = false; + + g_autoptr(libxlDriverConfig) cfg = libxlDriverConfigGet(driver); + + 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; + } + + libxl_domain_config_init(&config); + config_init = true; + + if (!(gports = virPortAllocatorRangeNew("vnc", 5900, 6000))) + goto cleanup; + + config_pass++; + if (libxlBuildDomainConfig(gports, def, cfg, &config) < 0) + goto cleanup; + + json_pass++; + if (!(json_string = libxl_domain_config_to_json(cfg->ctx, &config))) + goto cleanup; + + success++; + + cleanup: + + virPortAllocatorRangeFree(gports); + if (config_init) + libxl_domain_config_dispose(&config); + virDomainDefFree(def); +} + + +DEFINE_PROTO_FUZZER(const libvirt::MainObj &message) +{ + static libxlDriverPrivate *driver = NULL; + + static bool initialized = false; + + 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 = ""; + + + /* + * One-time setup of libXL driver. Re-running them in every + * iteration incurs a significant penalty to the speed of the fuzzer. + */ + if (!initialized) { + FUZZ_COMMON_INIT(); + + /* NOTE: Driver initialization will fail without libxlmock */ + if ((driver = testXLInitDriver()) == NULL) + exit(EXIT_FAILURE); + + if (arch_env) { + arch = arch_env; + } else { + arch = "x86_64"; + } + + /* 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; + } + + convertProtoTolibXLXMLDomain(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, Config: %lu, JSON: %lu, Success: %lu\n", + parse_pass, format_pass, config_pass, json_pass, success); + +} diff --git a/tests/fuzz/llvm_symbolizer_wrapper.c b/tests/fuzz/llvm_symbolizer_wrapper.c new file mode 100644 index 0000000000..d0aeba61fe --- /dev/null +++ b/tests/fuzz/llvm_symbolizer_wrapper.c @@ -0,0 +1,11 @@ +#include <config.h> + +#include <stdlib.h> +#include <unistd.h> + +int main(int argc, char **argv) { + // Avoid unused error. + (void) argc; + + execve("/usr/bin/llvm-symbolizer", argv, NULL); +} diff --git a/tests/fuzz/meson.build b/tests/fuzz/meson.build index 88b5fe103c..417b8dc1ef 100644 --- a/tests/fuzz/meson.build +++ b/tests/fuzz/meson.build @@ -90,6 +90,24 @@ if conf.has('WITH_VMX') ] endif +if conf.has('WITH_LIBXL') + fuzzer_src = [ + 'libxl_xml_domain_fuzz.cc', + 'proto_to_xml.cc', + ] + + libxl_libs = [ + test_xen_driver_lib, + test_utils_xen_lib, + test_utils_lib, + libvirt_lib, + ] + + xml_fuzzers += [ + { 'name': 'libxl_xml_domain_fuzz', 'src': [ fuzzer_src, xml_domain_proto_src ], 'libs': libxl_libs, 'macro': '-DXML_DOMAIN', 'deps': [ fuzz_autogen_xml_domain_dep, libxl_dep ] }, + ] +endif + foreach fuzzer: xml_fuzzers xml_domain_fuzz = executable(fuzzer['name'], fuzzer['src'], @@ -102,6 +120,19 @@ foreach fuzzer: xml_fuzzers available_fuzzers += '"' + fuzzer['name'] + '"' + ',' endforeach +# Wrapper binary which execs the real llvm-symbolizer but +# unsets the env vars to avoid propagating LD_PRELOAD to the +# real llvm-symbolizer and causing undefined symbol errors when ASAN/UBSAN +# is enabled. + +llvm_symbolizer_wrapper = executable( + 'llvm-symbolizer-wrapper', + 'llvm_symbolizer_wrapper.c', + dependencies: [ + tests_dep, + ], +) + run_conf = configuration_data({ 'abs_builddir': meson.project_build_root(), 'available_fuzzers': available_fuzzers, diff --git a/tests/fuzz/proto_to_xml.cc b/tests/fuzz/proto_to_xml.cc index 983256bcae..fde3394cc7 100644 --- a/tests/fuzz/proto_to_xml.cc +++ b/tests/fuzz/proto_to_xml.cc @@ -252,6 +252,24 @@ void convertProtoToVMXXMLDomain(const libvirt::MainObj &message, } +void convertProtoTolibXLXMLDomain(const libvirt::MainObj &message, + std::string arch, + std::string &xml) +{ + xml = "<domain type='xen'>\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 89e6726611..b4849890cc 100644 --- a/tests/fuzz/proto_to_xml.h +++ b/tests/fuzz/proto_to_xml.h @@ -32,5 +32,8 @@ void convertProtoToCHXMLDomain(const libvirt::MainObj &message, void convertProtoToVMXXMLDomain(const libvirt::MainObj &message, std::string arch, std::string &xml); +void convertProtoTolibXLXMLDomain(const libvirt::MainObj &message, + std::string arch, + std::string &xml); void convertProtoToXML(const libvirt::MainObj &message, std::string &xml); diff --git a/tests/fuzz/run_fuzz.in b/tests/fuzz/run_fuzz.in index 414b99b6cf..a2b0c66c32 100644 --- a/tests/fuzz/run_fuzz.in +++ b/tests/fuzz/run_fuzz.in @@ -118,6 +118,21 @@ if args.fuzzer == "qemu_xml_hotplug_fuzz": # so we have to disable LeakSanitizer env["ASAN_OPTIONS"] = "detect_leaks=0" +if args.fuzzer == "libxl_xml_domain_fuzz": + # To make LD_PRELOAD work without running into undefined symbol + # errors, we need to override ASAN_SYMBOLIZER_PATH with a wrapper + # that unsets LD_PRELOAD before running the real llvm-symbolizer. + + env["ASAN_SYMBOLIZER_PATH"] = llvm_symbolizer_path + env["LD_PRELOAD"] = f"{abs_builddir}/tests/libxlmock.so" + + # Note: Above workaround breaks multithreaded mode, + # so inform the user of the same. + + if args.jobs > 1: + print("libxl_xml_domain_fuzz cannot be used in parallel mode.") + sys.exit(1) + if args.libfuzzer_options: process_args.extend([x for x in args.libfuzzer_options.split(' ') if x != '']) -- 2.34.1