This patch sets up most of the baseline code which will be used by various
fuzzers as well as the build setup.
proto_to_xml.cc includes logic to convert protobuf to XML. It does in a
generalized fashion by using reflection APIs to recursively iterate through
all protobuf fields and generating XML tags, attributes or text based on their
identifiers and field types. This way, different protobuf definitions can be
used with minimal modifications.
proto_custom_datatypes.cc includes certain attribute datatypes that cannot be
encapsulated by existing protobuf datatypes, so we implement various handlers
to handle their XML serialization.
run_fuzz is a helper script which sets up necessary libFuzzer flags and
environment variables before executing the actual fuzzer binaries.
Three protobuf definitions are provided which serve as a base for all XML
domain fuzzers to follow:
1. xml_domain: Most of the XML domain format
2. xml_domain_disk_only: Only the <disk> definition
3. xml_domain_interface_only: Only the <interface> definition
Signed-off-by: Rayhan Faizel <rayhan.faizel@gmail.com>
---
meson.build | 44 ++++
meson_options.txt | 2 +
tests/fuzz/meson.build | 59 +++++
tests/fuzz/proto_custom_datatypes.cc | 146 +++++++++++++
tests/fuzz/proto_custom_datatypes.h | 30 +++
tests/fuzz/proto_header_common.h | 43 ++++
tests/fuzz/proto_to_xml.cc | 205 ++++++++++++++++++
tests/fuzz/proto_to_xml.h | 28 +++
tests/fuzz/protos/meson.build | 36 +++
tests/fuzz/protos/xml_datatypes.proto | 72 ++++++
tests/fuzz/protos/xml_domain.proto | 62 ++++++
tests/fuzz/protos/xml_domain_disk_only.proto | 21 ++
.../protos/xml_domain_interface_only.proto | 21 ++
tests/fuzz/run_fuzz.in | 122 +++++++++++
tests/meson.build | 5 +
15 files changed, 896 insertions(+)
create mode 100644 tests/fuzz/meson.build
create mode 100644 tests/fuzz/proto_custom_datatypes.cc
create mode 100644 tests/fuzz/proto_custom_datatypes.h
create mode 100644 tests/fuzz/proto_header_common.h
create mode 100644 tests/fuzz/proto_to_xml.cc
create mode 100644 tests/fuzz/proto_to_xml.h
create mode 100644 tests/fuzz/protos/meson.build
create mode 100644 tests/fuzz/protos/xml_datatypes.proto
create mode 100644 tests/fuzz/protos/xml_domain.proto
create mode 100644 tests/fuzz/protos/xml_domain_disk_only.proto
create mode 100644 tests/fuzz/protos/xml_domain_interface_only.proto
create mode 100644 tests/fuzz/run_fuzz.in
diff --git a/meson.build b/meson.build
index 904524abc6..abc67e0062 100644
--- a/meson.build
+++ b/meson.build
@@ -2153,6 +2153,49 @@ if get_option('test_coverage_clang')
endif
+# fuzzing
+
+if get_option('fuzz').enabled()
+ add_languages('cpp')
+ cxx = meson.get_compiler('cpp')
+
+ libprotobuf_mutator_dep = dependency('libprotobuf-mutator')
+
+ if get_option('external_protobuf_dir') != ''
+ external_protobuf_dir = get_option('external_protobuf_dir')
+ protoc_prog = find_program(external_protobuf_dir / 'bin' / 'protoc')
+
+ protobuf_inc = external_protobuf_dir / 'include'
+ protobuf_libdir = external_protobuf_dir / 'lib'
+
+ protobuf_list_cmd = run_command('find', protobuf_libdir, '-name', '*.a', '-printf', '%f\n', check: true)
+ protobuf_lib_files = protobuf_list_cmd.stdout().strip().split('\n')
+
+ protobuf_libs = []
+
+ foreach lib: protobuf_lib_files
+ protobuf_libs += cxx.find_library(lib.substring(3, -2), dirs: protobuf_libdir)
+ endforeach
+
+ protobuf_dep = declare_dependency(
+ dependencies: [ protobuf_libs ],
+ include_directories: [ protobuf_inc ],
+ )
+ else
+ protobuf_dep = dependency('protobuf')
+ protoc_prog = find_program('protoc')
+ endif
+
+ if cc.has_argument('-fsanitize=fuzzer') and cc.has_argument('-fsanitize=fuzzer-no-link')
+ add_project_arguments('-fsanitize=fuzzer-no-link', language: [ 'c', 'cpp' ])
+ else
+ error('Your compiler does not support libFuzzer')
+ endif
+
+ conf.set('WITH_FUZZ', 1)
+endif
+
+
# Various definitions
# Python3 < 3.7 treats the C locale as 7-bit only. We must force env vars so
@@ -2389,6 +2432,7 @@ summary(win_summary, section: 'Windows', bool_yn: true)
test_summary = {
'Expensive': use_expensive_tests,
'Coverage': coverage_flags.length() > 0,
+ 'Fuzzing': conf.has('WITH_FUZZ'),
}
summary(test_summary, section: 'Test suite', bool_yn: true)
diff --git a/meson_options.txt b/meson_options.txt
index 153e325cb5..b961192ee5 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -15,6 +15,8 @@ option('rpath', type: 'feature', value: 'auto', description: 'whether to include
option('docdir', type: 'string', value: '', description: 'documentation installation directory')
option('docs', type: 'feature', value: 'auto', description: 'whether to generate documentation')
option('tests', type: 'feature', value: 'auto', description: 'whether to build and run tests')
+option('fuzz', type: 'feature', value: 'auto', description: 'whether to build and run fuzzing engines')
+option('external_protobuf_dir', type: 'string', value: '', description: 'external protobuf directory for fuzzer compilation')
# build dependencies options
diff --git a/tests/fuzz/meson.build b/tests/fuzz/meson.build
new file mode 100644
index 0000000000..d0488c3e36
--- /dev/null
+++ b/tests/fuzz/meson.build
@@ -0,0 +1,59 @@
+fuzz_dep = declare_dependency(
+ dependencies: [
+ tests_dep,
+ libprotobuf_mutator_dep,
+ protobuf_dep,
+ ],
+ include_directories: [
+ test_dir,
+ ],
+ )
+
+subdir('protos')
+
+available_fuzzers = ''
+xml_fuzzers = []
+
+fuzz_autogen_xml_domain_lib = static_library(
+ 'fuzz_autogen_xml_domain_lib',
+ [
+ autogen_xml_domain_proto_src,
+ xml_datatypes_proto_src,
+ 'proto_custom_datatypes.cc',
+ ],
+ dependencies: [ fuzz_dep ],
+)
+
+fuzz_autogen_xml_domain_dep = declare_dependency(
+ link_whole: [ fuzz_autogen_xml_domain_lib ],
+ include_directories: [
+ fuzz_autogen_xml_domain_lib.private_dir_include(),
+ ]
+)
+
+foreach fuzzer: xml_fuzzers
+ xml_domain_fuzz = executable(fuzzer['name'],
+ fuzzer['src'],
+ dependencies: [ fuzz_dep, fuzzer['deps'] ],
+ cpp_args: [ '-fsanitize=fuzzer', fuzzer['macro'] ],
+ link_args: [ '-fsanitize=fuzzer', libvirt_no_indirect ],
+ link_with: fuzzer['libs'],
+ )
+
+ available_fuzzers += '"' + fuzzer['name'] + '"' + ','
+endforeach
+
+run_conf = configuration_data({
+ 'abs_builddir': meson.project_build_root(),
+ 'available_fuzzers': available_fuzzers,
+ 'sanitizers': get_option('b_sanitize'),
+ 'coverage_clang': get_option('test_coverage_clang'),
+})
+
+configure_file(
+ input: 'run_fuzz.in',
+ output: '@BASENAME@',
+ configuration: run_conf,
+)
+
+run_command('chmod', 'a+x', meson.current_build_dir() / 'run_fuzz', check: true)
diff --git a/tests/fuzz/proto_custom_datatypes.cc b/tests/fuzz/proto_custom_datatypes.cc
new file mode 100644
index 0000000000..d89a6d4f59
--- /dev/null
+++ b/tests/fuzz/proto_custom_datatypes.cc
@@ -0,0 +1,146 @@
+/*
+ * proto_custom_datatypes.cc: Custom protobuf-based datatype handlers
+ *
+ * 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 "xml_datatypes.pb.h"
+#include "port/protobuf.h"
+
+#include <unordered_map>
+
+using google::protobuf::Message;
+using google::protobuf::Reflection;
+using google::protobuf::EnumDescriptor;
+
+typedef std::string (*typeHandlerPtr)(const Message &);
+std::string emulator = "";
+
+static
+std::string convertCPUSet(const Message &message)
+{
+ std::string value;
+ const libvirt::CPUSet &cpuset = (libvirt::CPUSet &) message;
+ uint64_t bitmap = cpuset.bitmap();
+
+ int cpu = 0;
+ for (uint8_t bit = bitmap & 1; bitmap != 0; bitmap = bitmap >> 1, bit = bitmap & 1, cpu++)
+ {
+ if (bit) {
+ if (value.empty())
+ value += std::to_string(cpu);
+ else
+ value += "," + std::to_string(cpu);
+ }
+ }
+
+ return value;
+}
+
+
+static
+std::string convertEmulatorString(const Message &message)
+{
+ (void) message; /* Silence warning about unused parameter */
+ return emulator;
+}
+
+
+static
+std::string convertIPAddr(const Message &message) {
+ char value[64] = {0};
+ const libvirt::IPAddr &ip_addr = (libvirt::IPAddr &) message;
+
+ if (ip_addr.ipv6() == true) {
+ sprintf(value, "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
+ (uint16_t) ip_addr.lo_qword() & 0xffff,
+ (uint16_t) (ip_addr.lo_qword() >> 16) & 0xffff,
+ (uint16_t) (ip_addr.lo_qword() >> 32) & 0xffff,
+ (uint16_t) (ip_addr.lo_qword() >> 48) & 0xffff,
+ (uint16_t) ip_addr.hi_qword() & 0xffff,
+ (uint16_t) (ip_addr.hi_qword() >> 16) & 0xffff,
+ (uint16_t) (ip_addr.hi_qword() >> 32) & 0xffff,
+ (uint16_t) (ip_addr.hi_qword() >> 48) & 0xffff);
+ } else {
+ sprintf(value, "%d.%d.%d.%d",
+ (uint8_t) ip_addr.lo_qword() & 0xff,
+ (uint8_t) (ip_addr.lo_qword() >> 8) & 0xff,
+ (uint8_t) (ip_addr.lo_qword() >> 16) & 0xff,
+ (uint8_t) (ip_addr.lo_qword() >> 24) & 0xff);
+ }
+
+ return std::string(value);
+}
+
+
+static
+std::string convertMacAddr(const Message &message) {
+ char value[64] = {0};
+ const libvirt::MacAddr &mac_addr = (libvirt::MacAddr &) message;
+
+ sprintf(value, "%02x:%02x:%02x:%02x:%02x:%02x",
+ (uint8_t) mac_addr.qword() & 0xff,
+ (uint8_t) (mac_addr.qword() >> 8) & 0xff,
+ (uint8_t) (mac_addr.qword() >> 16) & 0xff,
+ (uint8_t) (mac_addr.qword() >> 24) & 0xff,
+ (uint8_t) (mac_addr.qword() >> 32) & 0xff,
+ (uint8_t) (mac_addr.qword() >> 40) & 0xff);
+
+ return std::string(value);
+}
+
+
+static
+std::string convertDiskTarget(const Message &message)
+{
+ std::string value;
+ const libvirt::TargetDev &target_dev = (libvirt::TargetDev &) message;
+ const EnumDescriptor *enum_desc = libvirt::TargetDev_TargetDevEnum_descriptor();
+ std::string prefix = enum_desc->FindValueByNumber(target_dev.prefix())->name();
+
+ value = prefix + "a";
+
+ return value;
+}
+
+
+std::unordered_map<std::string, typeHandlerPtr> type_handler_table = {
+ {"libvirt.CPUSet", convertCPUSet},
+ {"libvirt.EmulatorString", convertEmulatorString},
+ {"libvirt.IPAddr", convertIPAddr},
+ {"libvirt.MacAddr", convertMacAddr},
+ {"libvirt.TargetDev", convertDiskTarget},
+};
+
+
+void handleCustomDatatype(const Message &message,
+ std::string tag,
+ bool is_attr,
+ std::string &xml)
+{
+ std::string type_name = message.GetTypeName();
+ std::string value = "";
+
+ if (type_handler_table.find(type_name) != type_handler_table.end()) {
+ value = type_handler_table[type_name](message);
+
+ if (is_attr)
+ xml += " " + tag + "=" + "'" + value + "'";
+ else
+ xml += value;
+ }
+}
diff --git a/tests/fuzz/proto_custom_datatypes.h b/tests/fuzz/proto_custom_datatypes.h
new file mode 100644
index 0000000000..9446b6235b
--- /dev/null
+++ b/tests/fuzz/proto_custom_datatypes.h
@@ -0,0 +1,30 @@
+/*
+ * proto_custom_datatypes.h: Custom protobuf-based datatype handlers
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include "port/protobuf.h"
+
+using google::protobuf::Message;
+
+void handleCustomDatatype(const Message &message,
+ std::string tag,
+ bool is_attr,
+ std::string &xml);
diff --git a/tests/fuzz/proto_header_common.h b/tests/fuzz/proto_header_common.h
new file mode 100644
index 0000000000..5ee510896d
--- /dev/null
+++ b/tests/fuzz/proto_header_common.h
@@ -0,0 +1,43 @@
+/*
+ * proto_header_common.h: Protobuf header selection and common code
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#pragma GCC diagnostic ignored "-Wignored-attributes"
+#pragma GCC diagnostic ignored "-Wextern-c-compat"
+
+#ifdef XML_DOMAIN
+#include "xml_domain.pb.h"
+#endif
+
+#ifdef XML_DOMAIN_DISK_ONLY
+#include "xml_domain_disk_only.pb.h"
+#endif
+
+#ifdef XML_DOMAIN_INTERFACE_ONLY
+#include "xml_domain_interface_only.pb.h"
+#endif
+
+
+#define FUZZ_COMMON_INIT(...) \
+ if (virErrorInitialize() < 0) \
+ exit(EXIT_FAILURE); \
+ if (g_getenv("LPM_EXE_PATH")) \
+ virFileActivateDirOverrideForProg(g_getenv("LPM_EXE_PATH"));
diff --git a/tests/fuzz/proto_to_xml.cc b/tests/fuzz/proto_to_xml.cc
new file mode 100644
index 0000000000..13393ecb34
--- /dev/null
+++ b/tests/fuzz/proto_to_xml.cc
@@ -0,0 +1,205 @@
+/*
+ * proto_to_xml.cc: Protobuf to XML converter
+ *
+ * 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 "proto_header_common.h"
+#include "port/protobuf.h"
+#include "proto_custom_datatypes.h"
+
+
+using google::protobuf::Message;
+using google::protobuf::Descriptor;
+using google::protobuf::FieldDescriptor;
+using google::protobuf::EnumValueDescriptor;
+using google::protobuf::OneofDescriptor;
+using google::protobuf::Reflection;
+
+
+static void
+convertProtoFieldToXML(const Message &message,
+ const FieldDescriptor *field,
+ std::string tag,
+ bool is_attr,
+ std::string &xml)
+{
+ const Reflection *reflection = message.GetReflection();
+ const EnumValueDescriptor *enum_desc;
+ std::string value = "";
+ std::string alternate_value;
+ bool value_set = false;
+
+ switch (field->cpp_type()) {
+ case FieldDescriptor::CPPTYPE_MESSAGE:
+ {
+ const Message &field_message = reflection->GetMessage(message, field);
+ handleCustomDatatype(field_message, tag, is_attr, xml);
+ return;
+ }
+ break;
+ case FieldDescriptor::CPPTYPE_ENUM:
+ enum_desc = reflection->GetEnum(message, field);
+
+ /* Use extension to fill certain XML enum values containing special characters */
+ alternate_value = enum_desc->options().GetExtension(libvirt::real_value);
+
+ if (!alternate_value.empty()) {
+ value = alternate_value;
+ } else {
+ value = enum_desc->name();
+ }
+
+ value_set = true;
+ break;
+ case FieldDescriptor::CPPTYPE_BOOL:
+ value = reflection->GetBool(message, field) ? "yes" : "no";
+ value_set = true;
+ break;
+ case FieldDescriptor::CPPTYPE_UINT32:
+ value = std::to_string(reflection->GetUInt32(message, field));
+ value_set = true;
+ break;
+ case FieldDescriptor::CPPTYPE_UINT64:
+ value = std::to_string(reflection->GetUInt64(message, field));
+ value_set = true;
+ break;
+ case FieldDescriptor::CPPTYPE_STRING:
+ value = reflection->GetString(message, field);
+ value_set = true;
+ break;
+ default:
+ break;
+ }
+
+ if (value_set) {
+ if (is_attr)
+ xml += " " + tag + "='" + value + "'";
+ else
+ xml += value;
+ }
+}
+
+
+static int
+convertProtoToXMLInternal(const Message &message,
+ std::string &xml,
+ bool root)
+{
+ const Descriptor *descriptor = message.GetDescriptor();
+ const Reflection *reflection = message.GetReflection();
+ size_t field_count = descriptor->field_count();
+ int children = 0;
+ bool opening_tag_close = false;
+ size_t i, j;
+ size_t repeated_size = 0;
+
+ for (i = 0; i < field_count; i++) {
+ const FieldDescriptor *field = descriptor->field(i);
+ std::string field_name = field->name();
+ std::string tag = field_name.substr(2, std::string::npos);
+ char field_type = field_name[0];
+ std::string alternate_name = field->options().GetExtension(libvirt::real_name);
+
+ /* Fields inside oneof are prefixed with A_OPTXX_ */
+ if (tag.substr(0, 3) == "OPT")
+ tag = tag.substr(6, std::string::npos);
+
+ /* Use extension to generate XML tags with names having special characters */
+ if (!alternate_name.empty())
+ tag = alternate_name;
+
+ if (!field->is_repeated() && !reflection->HasField(message, field)) {
+ continue;
+ }
+
+ switch (field_type) {
+ /* Handle XML attributes/properties */
+ case 'A':
+ /* Attributes must come before inner tags in the protobuf definition */
+ assert(!opening_tag_close);
+
+ convertProtoFieldToXML(message, field, tag, true, xml);
+ break;
+
+ /* Handle inner tags of current XML tag */
+ case 'T':
+ /* We don't want to accidentally close off the tag on empty repeated fields */
+ if (field->is_repeated()) {
+ repeated_size = reflection->FieldSize(message, field);
+ if (repeated_size == 0)
+ continue;
+ }
+
+ if (!opening_tag_close && !root) {
+ xml += ">\n";
+ opening_tag_close = true;
+ }
+
+ if (field->is_repeated()) {
+ for (j = 0; j < repeated_size; j++) {
+ const Message &field_message = reflection->GetRepeatedMessage(message, field, j);
+ children += 1;
+ xml += "<" + tag;
+ int inner_children = convertProtoToXMLInternal(field_message, xml, false);
+ if (inner_children == 0) {
+ xml += "/>\n";
+ } else {
+ xml += "</" + tag + ">\n";
+ }
+ }
+ } else {
+ const Message &field_message = reflection->GetMessage(message, field);
+ children += 1;
+ xml += "<" + tag;
+ int inner_children = convertProtoToXMLInternal(field_message, xml, false);
+
+ if (inner_children == 0) {
+ xml += "/>\n";
+ } else {
+ xml += "</" + tag + ">\n";
+ }
+ }
+ break;
+
+ /* Handle XML text nodes. Eg: <tag>(text here)</tag> */
+ case 'V':
+ if (!opening_tag_close && !root) {
+ xml += ">";
+ opening_tag_close = true;
+ }
+ assert(children == 0);
+ children = 1;
+
+ convertProtoFieldToXML(message, field, "", false, xml);
+
+ return children;
+
+ default:
+ break;
+ }
+ }
+
+ return children;
+}
+
+
+void convertProtoToXML(const libvirt::MainObj &message,
+ std::string &xml)
+{
+ convertProtoToXMLInternal(message, xml, true);
+}
diff --git a/tests/fuzz/proto_to_xml.h b/tests/fuzz/proto_to_xml.h
new file mode 100644
index 0000000000..7fe9597a19
--- /dev/null
+++ b/tests/fuzz/proto_to_xml.h
@@ -0,0 +1,28 @@
+/*
+ * proto_to_xml.h: Protobuf to XML converter
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include "proto_header_common.h"
+#include "port/protobuf.h"
+
+
+void convertProtoToXML(const libvirt::MainObj &message,
+ std::string &xml);
diff --git a/tests/fuzz/protos/meson.build b/tests/fuzz/protos/meson.build
new file mode 100644
index 0000000000..42c3a7f6a9
--- /dev/null
+++ b/tests/fuzz/protos/meson.build
@@ -0,0 +1,36 @@
+protos = [
+ 'xml_datatypes.proto',
+ 'xml_domain.proto',
+ 'xml_domain_disk_only.proto',
+ 'xml_domain_interface_only.proto',
+]
+
+autogen_proto_xml_domain_proto = custom_target('autogen_xml_domain.proto',
+ output : 'autogen_xml_domain.proto',
+ input : meson.project_source_root() / 'src' / 'conf' / 'schemas' / 'domain.rng',
+ command : [relaxng_to_proto_prog, '@INPUT@', '@OUTPUT@'],
+)
+
+protoc_generator = generator(protoc_prog,
+ output: [
+ '@BASENAME@.pb.cc',
+ '@BASENAME@.pb.h',
+ ],
+ arguments: [
+ '--proto_path=@CURRENT_SOURCE_DIR@',
+ '--proto_path=@BUILD_ROOT@/tests/fuzz/protos/',
+ '--cpp_out=@BUILD_DIR@',
+ '@INPUT@',
+ ],
+ depends: [
+ autogen_proto_xml_domain_proto,
+ ],
+)
+
+autogen_xml_domain_proto_src = protoc_generator.process(autogen_proto_xml_domain_proto)
+
+foreach proto: protos
+ proto_src_name = proto.split('.')[0].underscorify()
+ proto_src = protoc_generator.process(proto)
+ set_variable('@0@_proto_src'.format(proto_src_name), proto_src)
+endforeach
diff --git a/tests/fuzz/protos/xml_datatypes.proto b/tests/fuzz/protos/xml_datatypes.proto
new file mode 100644
index 0000000000..1229b9810f
--- /dev/null
+++ b/tests/fuzz/protos/xml_datatypes.proto
@@ -0,0 +1,72 @@
+syntax = "proto2";
+package libvirt;
+
+import "google/protobuf/descriptor.proto";
+
+/*
+ * Use EnumValueOptions extension to insert XML attribute values that do
+ * contain characters outside [A-Za-z0-9_]
+ */
+
+extend google.protobuf.EnumValueOptions {
+ optional string real_value = 10000;
+}
+
+/*
+ * Use FieldOptions extension to create XML tags/attributes
+ * with identifiers containing characters outside [A-Za-z0-9_]
+ */
+
+extend google.protobuf.FieldOptions {
+ optional string real_name = 10000;
+}
+
+enum Switch {
+ on = 0;
+ off = 1;
+}
+
+/* If we use string, it could blow up the size of the protobuf in-memory */
+enum DummyString {
+ dummy = 0;
+ dummy2 = 1;
+ dummy3 = 2;
+ dummy4 = 3;
+}
+
+enum DummyUUID {
+ dummy_uuid = 0 [(real_value) = "df6fdea1-10c3-474c-ae62-e63def80de0b"];
+}
+
+message IPAddr {
+ required bool ipv6 = 1;
+ required uint64 lo_qword = 2;
+ required uint64 hi_qword = 3;
+}
+
+message MacAddr {
+ required uint64 qword = 1;
+}
+
+message TargetDev {
+ enum TargetDevEnum {
+ hd = 0;
+ sd = 1;
+ vd = 2;
+ xvd = 3;
+ ubd = 4;
+ }
+ required TargetDevEnum prefix = 1;
+}
+
+enum DummyPath {
+ devnull = 0 [(real_value) = "/dev/null"];
+ devzero = 1 [(real_value) = "/dev/zero"];
+ invalidpath = 2 [(real_value) = "/this/path/doesnt/exist"];
+}
+
+message CPUSet {
+ required uint64 bitmap = 1;
+}
+
+message EmulatorString {}
diff --git a/tests/fuzz/protos/xml_domain.proto b/tests/fuzz/protos/xml_domain.proto
new file mode 100644
index 0000000000..9720af70d3
--- /dev/null
+++ b/tests/fuzz/protos/xml_domain.proto
@@ -0,0 +1,62 @@
+syntax = "proto2";
+
+import "autogen_xml_domain.proto";
+import "xml_datatypes.proto";
+
+package libvirt;
+
+/*
+ * <devices> container tag
+ * For more effective coverage, it might be a good idea to comment
+ * out some protobuf fields and fuzz only a few devices at a time.
+ */
+
+message devicesTag {
+ repeated domainTag.devicesTag.soundTag T_sound = 1;
+ repeated domainTag.devicesTag.filesystemTag T_filesystem = 2;
+ repeated domainTag.devicesTag.inputTag T_input = 3;
+ repeated domainTag.devicesTag.diskTag T_disk = 4;
+ repeated domainTag.devicesTag.interfaceTag T_interface = 5;
+ repeated domainTag.devicesTag.graphicsTag T_graphics = 6;
+ repeated domainTag.devicesTag.serialTag T_serial = 7;
+ repeated domainTag.devicesTag.parallelTag T_parallel = 8;
+ repeated domainTag.devicesTag.channelTag T_channel = 9;
+ repeated domainTag.devicesTag.consoleTag T_console = 10;
+ repeated domainTag.devicesTag.controllerTag T_controller = 11;
+ repeated domainTag.devicesTag.videoTag T_video = 12;
+ repeated domainTag.devicesTag.rngTag T_rng = 13;
+ repeated domainTag.devicesTag.watchdogTag T_watchdog = 14;
+ repeated domainTag.devicesTag.memballoonTag T_memballoon = 15;
+ repeated domainTag.devicesTag.smartcardTag T_smartcard = 16;
+ repeated domainTag.devicesTag.redirdevTag T_redirdev = 17;
+ repeated domainTag.devicesTag.audioTag T_audio = 18;
+ repeated domainTag.devicesTag.cryptoTag T_crypto = 19;
+ repeated domainTag.devicesTag.panicTag T_panic = 20;
+ repeated domainTag.devicesTag.tpmTag T_tpm = 21;
+ repeated domainTag.devicesTag.shmemTag T_shmem = 22;
+ repeated domainTag.devicesTag.hostdevTag T_hostdev = 23;
+ repeated domainTag.devicesTag.leaseTag T_lease = 24;
+ repeated domainTag.devicesTag.redirfilterTag T_redirfilter = 25;
+ repeated domainTag.devicesTag.iommuTag T_iommu = 26;
+ repeated domainTag.devicesTag.vsockTag T_vsock = 27;
+ repeated domainTag.devicesTag.nvramTag T_nvram = 28;
+
+ message EmulatorTag {
+ required EmulatorString V_value = 1;
+ }
+ required EmulatorTag T_emulator = 1000;
+}
+
+/* Full XML Domain */
+
+message MainObj {
+ optional domainTag.clockTag T_clock = 1;
+ optional domainTag.vcpuTag T_vcpu = 2;
+ optional domainTag.vcpusTag T_vcpus = 3;
+ optional domainTag.cpuTag T_cpu = 4;
+ optional domainTag.cputuneTag T_vcputune = 5;
+ optional domainTag.memtuneTag T_memtune = 6;
+ optional domainTag.blkiotuneTag T_blkiotune = 7;
+
+ required devicesTag T_devices = 1000;
+}
diff --git a/tests/fuzz/protos/xml_domain_disk_only.proto b/tests/fuzz/protos/xml_domain_disk_only.proto
new file mode 100644
index 0000000000..a6af43003c
--- /dev/null
+++ b/tests/fuzz/protos/xml_domain_disk_only.proto
@@ -0,0 +1,21 @@
+syntax = "proto2";
+
+import "autogen_xml_domain.proto";
+import "xml_datatypes.proto";
+
+package libvirt;
+
+message devicesTag {
+ repeated domainTag.devicesTag.diskTag T_disk = 4;
+
+ message EmulatorTag {
+ required EmulatorString V_value = 1;
+ }
+ required EmulatorTag T_emulator = 1000;
+}
+
+/* Full XML Domain */
+
+message MainObj {
+ required devicesTag T_devices = 1000;
+}
diff --git a/tests/fuzz/protos/xml_domain_interface_only.proto b/tests/fuzz/protos/xml_domain_interface_only.proto
new file mode 100644
index 0000000000..3bac94bf79
--- /dev/null
+++ b/tests/fuzz/protos/xml_domain_interface_only.proto
@@ -0,0 +1,21 @@
+syntax = "proto2";
+
+import "autogen_xml_domain.proto";
+import "xml_datatypes.proto";
+
+package libvirt;
+
+message devicesTag {
+ repeated domainTag.devicesTag.interfaceTag T_interface = 5;
+
+ message EmulatorTag {
+ required EmulatorString V_value = 1;
+ }
+ required EmulatorTag T_emulator = 1000;
+}
+
+/* Full XML Domain */
+
+message MainObj {
+ required devicesTag T_devices = 1000;
+}
diff --git a/tests/fuzz/run_fuzz.in b/tests/fuzz/run_fuzz.in
new file mode 100644
index 0000000000..da3c7935b7
--- /dev/null
+++ b/tests/fuzz/run_fuzz.in
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+
+available_fuzzers = [ @available_fuzzers@ ]
+abs_builddir = "@abs_builddir@"
+fuzz_dir = f"{abs_builddir}/tests/fuzz/"
+llvm_symbolizer_path = f"{fuzz_dir}/llvm-symbolizer-wrapper"
+
+sanitizers = "@sanitizers@"
+coverage_clang = @coverage_clang@
+
+parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
+ description="Fuzzing Helper")
+
+parser.add_argument('fuzzer',
+ help='Select fuzzer binary to run',
+ choices=available_fuzzers)
+
+parser.add_argument('--testcase', nargs='+',
+ help='Individual test case')
+
+parser.add_argument('--corpus',
+ help='Corpus Directory')
+
+parser.add_argument('--dump-xml', action='store_true',
+ help='Log XML test cases')
+
+parser.add_argument('--format-xml', action='store_true',
+ help='Run XML domain format functions')
+
+parser.add_argument('--arch',
+ help='XML domain architecture to fuzz')
+
+parser.add_argument('--llvm-profile-file',
+ help='Collect coverage report')
+
+parser.add_argument('--total-time', type=int,
+ help='Fuzz for specific amount of time (in seconds)')
+
+parser.add_argument('--timeout', type=int,
+ help='Max duration for individual test case (in seconds)')
+
+parser.add_argument('-j', '--jobs', type=int, default=1,
+ help='Run parallel fuzzers')
+
+parser.add_argument('--parallel-mode', choices=["jobs", "fork"], default="jobs",
+ help='Set parallel fuzzing method')
+
+parser.add_argument('--use-value-profile', action='store_true',
+ help='Use value profile')
+
+parser.add_argument('--libfuzzer-options',
+ help='Pass additional raw libFuzzer flags')
+
+args = parser.parse_args()
+
+process_args = [fuzz_dir + args.fuzzer]
+env = os.environ
+
+if args.corpus and args.testcase:
+ print("--testcase and --corpus can't be used together")
+ sys.exit(1)
+
+if args.corpus:
+ process_args.extend([args.corpus])
+
+if args.testcase:
+ process_args.extend(args.testcase)
+
+if args.dump_xml:
+ env["LPM_XML_DUMP_INPUT"] = "YES"
+
+if args.format_xml:
+ env["LPM_XML_FORMAT_ENABLE"] = "YES"
+
+if args.arch:
+ env["LPM_FUZZ_ARCH"] = args.arch
+
+if args.llvm_profile_file:
+ if not coverage_clang:
+ print("Please recompile your build with -Dtest_coverage_clang=true")
+ sys.exit(1)
+ env["LLVM_PROFILE_FILE"] = args.llvm_profile_file
+
+ if not args.corpus:
+ print("Coverage report requires --corpus. If you do not have a corpus, do normal fuzzing to prepare one.")
+ sys.exit(1)
+
+ process_args.extend(["-runs=0"])
+
+if args.total_time:
+ process_args.extend([f"-max_total_time={str(args.total_time)}"])
+
+if args.timeout:
+ process_args.extend([f"-timeout={str(args.timeout)}"])
+
+if args.use_value_profile:
+ process_args.extend(["-use_value_profile=1"])
+
+if args.jobs > 1:
+ if args.parallel_mode == "fork":
+ process_args.extend([f"-fork={args.jobs}", "-ignore_crashes=1", "-ignore_ooms=1", "-ignore_timeouts=1"])
+ elif args.parallel_mode == "jobs":
+ process_args.extend([f"-jobs={args.jobs}", f"-workers={args.jobs}"])
+
+exe_path = fuzz_dir + args.fuzzer
+
+# If libvirt is not installed in system dir, some fuzzers (QEMU, etc.) will fail
+env["LPM_EXE_PATH"] = exe_path
+
+process_args.extend(["-print_funcs=-1"])
+
+if args.libfuzzer_options:
+ process_args.extend([x for x in args.libfuzzer_options.split(' ') if x != ''])
+
+print("Selected fuzzer:", args.fuzzer)
+print("Active sanitizers:", sanitizers)
+
+os.execvpe(exe_path, process_args, env)
diff --git a/tests/meson.build b/tests/meson.build
index 2f1eda1f95..18743ac45e 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -34,6 +34,7 @@ tests_dep = declare_dependency(
),
)
+test_dir = include_directories('.')
tests_env = [
'abs_builddir=@0@'.format(meson.current_build_dir()),
'abs_srcdir=@0@'.format(meson.current_source_dir()),
@@ -750,6 +751,10 @@ endforeach
testenv = runutf8
testenv += 'VIR_TEST_FILE_ACCESS=1'
+if conf.has('WITH_FUZZ')
+ subdir('fuzz')
+endif
+
add_test_setup(
'access',
env: testenv,
--
2.34.1
© 2016 - 2024 Red Hat, Inc.