From nobody Sat Nov 23 17:15:38 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=fail(p=none dis=none) header.from=gmail.com Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1724084794684148.9218295955776; Mon, 19 Aug 2024 09:26:34 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 52189161D; Mon, 19 Aug 2024 12:26:26 -0400 (EDT) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id 9B51C146E; Mon, 19 Aug 2024 12:20:28 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 76ECA1459; Mon, 19 Aug 2024 12:20:21 -0400 (EDT) Received: from mail-ed1-f47.google.com (mail-ed1-f47.google.com [209.85.208.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 7DCE015BE for ; Mon, 19 Aug 2024 12:20:00 -0400 (EDT) Received: by mail-ed1-f47.google.com with SMTP id 4fb4d7f45d1cf-5bec7ee6f44so3967340a12.0 for ; Mon, 19 Aug 2024 09:20:00 -0700 (PDT) Received: from localhost.localdomain ([37.186.51.21]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-5bebbde4964sm5738298a12.24.2024.08.19.09.19.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 Aug 2024 09:19:58 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-0.6 required=5.0 tests=DKIM_ADSP_CUSTOM_MED, DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,T_SCC_BODY_TEXT_LINE autolearn=unavailable autolearn_force=no version=3.4.4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724084399; x=1724689199; darn=lists.libvirt.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=MuJfop0yQJ6CoS9dJtFL0hhW5+dFtj+ff/M0fQyEPqo=; b=ces3JFpp+GAnzByjkw829T4pIQ7PSV6cBs2o1RX1gR0F19vzLNbSgMiP7mqSbpN3QF sQ30yR01k2xju2qypIPao7mImhgF26FHrTKZWH483oQOQ8UYMD5JhARZZEFxmUtYqFVX M540LY/0UVcNf1utrcHfqQ/4K1dnfp5Ykou2DqWdOGkz0VSZSPhOKxTfzIxEScFQ0vOP GmcBHNML7wdVJIIyBRuEDFF3/EuqrK4VXCjEPe/AcrqZvKR0muz/l3r1UQHGKY8HTR+d Mpi2u30iIx6E8LoMXS34eXs2sjZ9lAXEI4LXND8NuSUwIsCM+A014YEB10R5mZoR4DqH STTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724084399; x=1724689199; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=MuJfop0yQJ6CoS9dJtFL0hhW5+dFtj+ff/M0fQyEPqo=; b=r7QEjfDVKAOQpXmMBgBl/DY49Y2zsFBwdZpxF8GH9RX7mQ83SmaTeGQi9dIIdOzqYU artouf1ryzan6wZKIMhMbiWmknsC1s24nMhnRSFWn9hOeOoDcajRMOtZVt/YkALUHaQ/ iE2ln5kqAdXPovHLCPpdC/3zKom5A7TGNJSszU6Rx7YqI1Du4MnHZGOmFZ0N/Vmu0ier 2ZpR2Kd59sVSWLdMcHmVFsUpl9PT1UN+hkSpFQu6TEvhsqZs+t+TyR889+PFRjQnsqLj Vdnge1G5wCZfUKEoFD+ze7kjgeL2VzBFO2Yr6qGq57KjLTfxqiZlXI244cST1WVw75tx z3ug== X-Gm-Message-State: AOJu0YykgC88OuCF7s/vBLCzACyvR4EneqvxXcU/kdYN+4Is6AtYUdNH kqyjNq81zjFDXNDObqnrhMU0vflWd2eypDKQJXq6yxL8Pc0L+vKTJN/xnA== X-Google-Smtp-Source: AGHT+IHEbcNRZZVoqw+oCuP0J5wDrtJaGIGBBtwAmPf9U3LnKC8pxmKXCdC/JOlBQFPiDaaVU+VUhQ== X-Received: by 2002:a05:6402:1ec3:b0:5a1:83c4:c5a8 with SMTP id 4fb4d7f45d1cf-5beca592b86mr7734259a12.14.1724084398615; Mon, 19 Aug 2024 09:19:58 -0700 (PDT) From: Rayhan Faizel To: devel@lists.libvirt.org Subject: [PATCH 06/14] fuzz: Implement base fuzzing setup for XML domain Date: Mon, 19 Aug 2024 21:39:44 +0530 Message-Id: <20240819160952.351383-7-rayhan.faizel@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240819160952.351383-1-rayhan.faizel@gmail.com> References: <20240819160952.351383-1-rayhan.faizel@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Message-ID-Hash: F2RGGE53GGQ6TPAURSSCZBN4I2KPJKWW X-Message-ID-Hash: F2RGGE53GGQ6TPAURSSCZBN4I2KPJKWW X-MailFrom: rayhan.faizel@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; header-match-devel.lists.libvirt.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header CC: Rayhan Faizel X-Mailman-Version: 3.2.2 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1724084796641116600 Content-Type: text/plain; charset="utf-8" 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 th= eir 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 handle= rs 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 definition 3. xml_domain_interface_only: Only the definition Signed-off-by: Rayhan Faizel --- 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 =20 =20 +# fuzzing + +if get_option('fuzz').enabled() + add_languages('cpp') + cxx =3D meson.get_compiler('cpp') + + libprotobuf_mutator_dep =3D dependency('libprotobuf-mutator') + + if get_option('external_protobuf_dir') !=3D '' + external_protobuf_dir =3D get_option('external_protobuf_dir') + protoc_prog =3D find_program(external_protobuf_dir / 'bin' / 'protoc') + + protobuf_inc =3D external_protobuf_dir / 'include' + protobuf_libdir =3D external_protobuf_dir / 'lib' + + protobuf_list_cmd =3D run_command('find', protobuf_libdir, '-name', '*= .a', '-printf', '%f\n', check: true) + protobuf_lib_files =3D protobuf_list_cmd.stdout().strip().split('\n') + + protobuf_libs =3D [] + + foreach lib: protobuf_lib_files + protobuf_libs +=3D cxx.find_library(lib.substring(3, -2), dirs: prot= obuf_libdir) + endforeach + + protobuf_dep =3D declare_dependency( + dependencies: [ protobuf_libs ], + include_directories: [ protobuf_inc ], + ) + else + protobuf_dep =3D dependency('protobuf') + protoc_prog =3D find_program('protoc') + endif + + if cc.has_argument('-fsanitize=3Dfuzzer') and cc.has_argument('-fsanitiz= e=3Dfuzzer-no-link') + add_project_arguments('-fsanitize=3Dfuzzer-no-link', language: [ 'c', = 'cpp' ]) + else + error('Your compiler does not support libFuzzer') + endif + + conf.set('WITH_FUZZ', 1) +endif + + # Various definitions =20 # 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: tru= e) test_summary =3D { 'Expensive': use_expensive_tests, 'Coverage': coverage_flags.length() > 0, + 'Fuzzing': conf.has('WITH_FUZZ'), } summary(test_summary, section: 'Test suite', bool_yn: true) =20 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', descripti= on: 'whether to include option('docdir', type: 'string', value: '', description: 'documentation in= stallation directory') option('docs', type: 'feature', value: 'auto', description: 'whether to ge= nerate documentation') option('tests', type: 'feature', value: 'auto', description: 'whether to b= uild and run tests') +option('fuzz', type: 'feature', value: 'auto', description: 'whether to bu= ild and run fuzzing engines') +option('external_protobuf_dir', type: 'string', value: '', description: 'e= xternal protobuf directory for fuzzer compilation') =20 =20 # 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 =3D declare_dependency( + dependencies: [ + tests_dep, + libprotobuf_mutator_dep, + protobuf_dep, + ], + include_directories: [ + test_dir, + ], + ) + +subdir('protos') + +available_fuzzers =3D '' +xml_fuzzers =3D [] + +fuzz_autogen_xml_domain_lib =3D 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 =3D 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 =3D executable(fuzzer['name'], + fuzzer['src'], + dependencies: [ fuzz_dep, fuzzer['deps'] ], + cpp_args: [ '-fsanitize=3Dfuzzer', fuzzer['macro'] ], + link_args: [ '-fsanitize=3Dfuzzer', libvirt_no_indirect ], + link_with: fuzzer['libs'], + ) + + available_fuzzers +=3D '"' + fuzzer['name'] + '"' + ',' +endforeach + +run_conf =3D 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 + * . + */ + +#include "xml_datatypes.pb.h" +#include "port/protobuf.h" + +#include + +using google::protobuf::Message; +using google::protobuf::Reflection; +using google::protobuf::EnumDescriptor; + +typedef std::string (*typeHandlerPtr)(const Message &); +std::string emulator =3D ""; + +static +std::string convertCPUSet(const Message &message) +{ + std::string value; + const libvirt::CPUSet &cpuset =3D (libvirt::CPUSet &) message; + uint64_t bitmap =3D cpuset.bitmap(); + + int cpu =3D 0; + for (uint8_t bit =3D bitmap & 1; bitmap !=3D 0; bitmap =3D bitmap >> 1= , bit =3D bitmap & 1, cpu++) + { + if (bit) { + if (value.empty()) + value +=3D std::to_string(cpu); + else + value +=3D "," + 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] =3D {0}; + const libvirt::IPAddr &ip_addr =3D (libvirt::IPAddr &) message; + + if (ip_addr.ipv6() =3D=3D 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] =3D {0}; + const libvirt::MacAddr &mac_addr =3D (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 =3D (libvirt::TargetDev &) messag= e; + const EnumDescriptor *enum_desc =3D libvirt::TargetDev_TargetDevEnum_d= escriptor(); + std::string prefix =3D enum_desc->FindValueByNumber(target_dev.prefix(= ))->name(); + + value =3D prefix + "a"; + + return value; +} + + +std::unordered_map type_handler_table =3D { + {"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 =3D message.GetTypeName(); + std::string value =3D ""; + + if (type_handler_table.find(type_name) !=3D type_handler_table.end()) { + value =3D type_handler_table[type_name](message); + + if (is_attr) + xml +=3D " " + tag + "=3D" + "'" + value + "'"; + else + xml +=3D 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 + * . + */ + +#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_com= mon.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 + * . + */ + +#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 + * . + */ + +#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 =3D message.GetReflection(); + const EnumValueDescriptor *enum_desc; + std::string value =3D ""; + std::string alternate_value; + bool value_set =3D false; + + switch (field->cpp_type()) { + case FieldDescriptor::CPPTYPE_MESSAGE: + { + const Message &field_message =3D reflection->GetMessage(messag= e, field); + handleCustomDatatype(field_message, tag, is_attr, xml); + return; + } + break; + case FieldDescriptor::CPPTYPE_ENUM: + enum_desc =3D reflection->GetEnum(message, field); + + /* Use extension to fill certain XML enum values containing sp= ecial characters */ + alternate_value =3D enum_desc->options().GetExtension(libvirt:= :real_value); + + if (!alternate_value.empty()) { + value =3D alternate_value; + } else { + value =3D enum_desc->name(); + } + + value_set =3D true; + break; + case FieldDescriptor::CPPTYPE_BOOL: + value =3D reflection->GetBool(message, field) ? "yes" : "no"; + value_set =3D true; + break; + case FieldDescriptor::CPPTYPE_UINT32: + value =3D std::to_string(reflection->GetUInt32(message, field)= ); + value_set =3D true; + break; + case FieldDescriptor::CPPTYPE_UINT64: + value =3D std::to_string(reflection->GetUInt64(message, field)= ); + value_set =3D true; + break; + case FieldDescriptor::CPPTYPE_STRING: + value =3D reflection->GetString(message, field); + value_set =3D true; + break; + default: + break; + } + + if (value_set) { + if (is_attr) + xml +=3D " " + tag + "=3D'" + value + "'"; + else + xml +=3D value; + } +} + + +static int +convertProtoToXMLInternal(const Message &message, + std::string &xml, + bool root) +{ + const Descriptor *descriptor =3D message.GetDescriptor(); + const Reflection *reflection =3D message.GetReflection(); + size_t field_count =3D descriptor->field_count(); + int children =3D 0; + bool opening_tag_close =3D false; + size_t i, j; + size_t repeated_size =3D 0; + + for (i =3D 0; i < field_count; i++) { + const FieldDescriptor *field =3D descriptor->field(i); + std::string field_name =3D field->name(); + std::string tag =3D field_name.substr(2, std::string::npos); + char field_type =3D field_name[0]; + std::string alternate_name =3D field->options().GetExtension(libvi= rt::real_name); + + /* Fields inside oneof are prefixed with A_OPTXX_ */ + if (tag.substr(0, 3) =3D=3D "OPT") + tag =3D tag.substr(6, std::string::npos); + + /* Use extension to generate XML tags with names having special ch= aracters */ + if (!alternate_name.empty()) + tag =3D 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 defi= nition */ + 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 re= peated fields */ + if (field->is_repeated()) { + repeated_size =3D reflection->FieldSize(message, field); + if (repeated_size =3D=3D 0) + continue; + } + + if (!opening_tag_close && !root) { + xml +=3D ">\n"; + opening_tag_close =3D true; + } + + if (field->is_repeated()) { + for (j =3D 0; j < repeated_size; j++) { + const Message &field_message =3D reflection->GetRepeat= edMessage(message, field, j); + children +=3D 1; + xml +=3D "<" + tag; + int inner_children =3D convertProtoToXMLInternal(field= _message, xml, false); + if (inner_children =3D=3D 0) { + xml +=3D "/>\n"; + } else { + xml +=3D "\n"; + } + } + } else { + const Message &field_message =3D reflection->GetMessage(me= ssage, field); + children +=3D 1; + xml +=3D "<" + tag; + int inner_children =3D convertProtoToXMLInternal(field_mes= sage, xml, false); + + if (inner_children =3D=3D 0) { + xml +=3D "/>\n"; + } else { + xml +=3D "\n"; + } + } + break; + + /* Handle XML text nodes. Eg: (text here) */ + case 'V': + if (!opening_tag_close && !root) { + xml +=3D ">"; + opening_tag_close =3D true; + } + assert(children =3D=3D 0); + children =3D 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 + * . + */ + +#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 =3D [ + 'xml_datatypes.proto', + 'xml_domain.proto', + 'xml_domain_disk_only.proto', + 'xml_domain_interface_only.proto', +] + +autogen_proto_xml_domain_proto =3D custom_target('autogen_xml_domain.proto= ', + output : 'autogen_xml_domain.proto', + input : meson.project_source_root() / 'src' / 'conf' / 'schemas' / 'doma= in.rng', + command : [relaxng_to_proto_prog, '@INPUT@', '@OUTPUT@'], +) + +protoc_generator =3D generator(protoc_prog, + output: [ + '@BASENAME@.pb.cc', + '@BASENAME@.pb.h', + ], + arguments: [ + '--proto_path=3D@CURRENT_SOURCE_DIR@', + '--proto_path=3D@BUILD_ROOT@/tests/fuzz/protos/', + '--cpp_out=3D@BUILD_DIR@', + '@INPUT@', + ], + depends: [ + autogen_proto_xml_domain_proto, + ], +) + +autogen_xml_domain_proto_src =3D protoc_generator.process(autogen_proto_xm= l_domain_proto) + +foreach proto: protos + proto_src_name =3D proto.split('.')[0].underscorify() + proto_src =3D 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 =3D "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 =3D 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 =3D 10000; +} + +enum Switch { + on =3D 0; + off =3D 1; +} + +/* If we use string, it could blow up the size of the protobuf in-memory */ +enum DummyString { + dummy =3D 0; + dummy2 =3D 1; + dummy3 =3D 2; + dummy4 =3D 3; +} + +enum DummyUUID { + dummy_uuid =3D 0 [(real_value) =3D "df6fdea1-10c3-474c-ae62-e63def80de0b= "]; +} + +message IPAddr { + required bool ipv6 =3D 1; + required uint64 lo_qword =3D 2; + required uint64 hi_qword =3D 3; +} + +message MacAddr { + required uint64 qword =3D 1; +} + +message TargetDev { + enum TargetDevEnum { + hd =3D 0; + sd =3D 1; + vd =3D 2; + xvd =3D 3; + ubd =3D 4; + } + required TargetDevEnum prefix =3D 1; +} + +enum DummyPath { + devnull =3D 0 [(real_value) =3D "/dev/null"]; + devzero =3D 1 [(real_value) =3D "/dev/zero"]; + invalidpath =3D 2 [(real_value) =3D "/this/path/doesnt/exist"]; +} + +message CPUSet { + required uint64 bitmap =3D 1; +} + +message EmulatorString {} diff --git a/tests/fuzz/protos/xml_domain.proto b/tests/fuzz/protos/xml_dom= ain.proto new file mode 100644 index 0000000000..9720af70d3 --- /dev/null +++ b/tests/fuzz/protos/xml_domain.proto @@ -0,0 +1,62 @@ +syntax =3D "proto2"; + +import "autogen_xml_domain.proto"; +import "xml_datatypes.proto"; + +package libvirt; + +/* + * 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 =3D 1; + repeated domainTag.devicesTag.filesystemTag T_filesystem =3D 2; + repeated domainTag.devicesTag.inputTag T_input =3D 3; + repeated domainTag.devicesTag.diskTag T_disk =3D 4; + repeated domainTag.devicesTag.interfaceTag T_interface =3D 5; + repeated domainTag.devicesTag.graphicsTag T_graphics =3D 6; + repeated domainTag.devicesTag.serialTag T_serial =3D 7; + repeated domainTag.devicesTag.parallelTag T_parallel =3D 8; + repeated domainTag.devicesTag.channelTag T_channel =3D 9; + repeated domainTag.devicesTag.consoleTag T_console =3D 10; + repeated domainTag.devicesTag.controllerTag T_controller =3D 11; + repeated domainTag.devicesTag.videoTag T_video =3D 12; + repeated domainTag.devicesTag.rngTag T_rng =3D 13; + repeated domainTag.devicesTag.watchdogTag T_watchdog =3D 14; + repeated domainTag.devicesTag.memballoonTag T_memballoon =3D 15; + repeated domainTag.devicesTag.smartcardTag T_smartcard =3D 16; + repeated domainTag.devicesTag.redirdevTag T_redirdev =3D 17; + repeated domainTag.devicesTag.audioTag T_audio =3D 18; + repeated domainTag.devicesTag.cryptoTag T_crypto =3D 19; + repeated domainTag.devicesTag.panicTag T_panic =3D 20; + repeated domainTag.devicesTag.tpmTag T_tpm =3D 21; + repeated domainTag.devicesTag.shmemTag T_shmem =3D 22; + repeated domainTag.devicesTag.hostdevTag T_hostdev =3D 23; + repeated domainTag.devicesTag.leaseTag T_lease =3D 24; + repeated domainTag.devicesTag.redirfilterTag T_redirfilter =3D 25; + repeated domainTag.devicesTag.iommuTag T_iommu =3D 26; + repeated domainTag.devicesTag.vsockTag T_vsock =3D 27; + repeated domainTag.devicesTag.nvramTag T_nvram =3D 28; + + message EmulatorTag { + required EmulatorString V_value =3D 1; + } + required EmulatorTag T_emulator =3D 1000; +} + +/* Full XML Domain */ + +message MainObj { + optional domainTag.clockTag T_clock =3D 1; + optional domainTag.vcpuTag T_vcpu =3D 2; + optional domainTag.vcpusTag T_vcpus =3D 3; + optional domainTag.cpuTag T_cpu =3D 4; + optional domainTag.cputuneTag T_vcputune =3D 5; + optional domainTag.memtuneTag T_memtune =3D 6; + optional domainTag.blkiotuneTag T_blkiotune =3D 7; + + required devicesTag T_devices =3D 1000; +} diff --git a/tests/fuzz/protos/xml_domain_disk_only.proto b/tests/fuzz/prot= os/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 =3D "proto2"; + +import "autogen_xml_domain.proto"; +import "xml_datatypes.proto"; + +package libvirt; + +message devicesTag { + repeated domainTag.devicesTag.diskTag T_disk =3D 4; + + message EmulatorTag { + required EmulatorString V_value =3D 1; + } + required EmulatorTag T_emulator =3D 1000; +} + +/* Full XML Domain */ + +message MainObj { + required devicesTag T_devices =3D 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 =3D "proto2"; + +import "autogen_xml_domain.proto"; +import "xml_datatypes.proto"; + +package libvirt; + +message devicesTag { + repeated domainTag.devicesTag.interfaceTag T_interface =3D 5; + + message EmulatorTag { + required EmulatorString V_value =3D 1; + } + required EmulatorTag T_emulator =3D 1000; +} + +/* Full XML Domain */ + +message MainObj { + required devicesTag T_devices =3D 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 =3D [ @available_fuzzers@ ] +abs_builddir =3D "@abs_builddir@" +fuzz_dir =3D f"{abs_builddir}/tests/fuzz/" +llvm_symbolizer_path =3D f"{fuzz_dir}/llvm-symbolizer-wrapper" + +sanitizers =3D "@sanitizers@" +coverage_clang =3D @coverage_clang@ + +parser =3D argparse.ArgumentParser(formatter_class=3Dargparse.RawDescripti= onHelpFormatter, + description=3D"Fuzzing Helper") + +parser.add_argument('fuzzer', + help=3D'Select fuzzer binary to run', + choices=3Davailable_fuzzers) + +parser.add_argument('--testcase', nargs=3D'+', + help=3D'Individual test case') + +parser.add_argument('--corpus', + help=3D'Corpus Directory') + +parser.add_argument('--dump-xml', action=3D'store_true', + help=3D'Log XML test cases') + +parser.add_argument('--format-xml', action=3D'store_true', + help=3D'Run XML domain format functions') + +parser.add_argument('--arch', + help=3D'XML domain architecture to fuzz') + +parser.add_argument('--llvm-profile-file', + help=3D'Collect coverage report') + +parser.add_argument('--total-time', type=3Dint, + help=3D'Fuzz for specific amount of time (in seconds)') + +parser.add_argument('--timeout', type=3Dint, + help=3D'Max duration for individual test case (in seco= nds)') + +parser.add_argument('-j', '--jobs', type=3Dint, default=3D1, + help=3D'Run parallel fuzzers') + +parser.add_argument('--parallel-mode', choices=3D["jobs", "fork"], default= =3D"jobs", + help=3D'Set parallel fuzzing method') + +parser.add_argument('--use-value-profile', action=3D'store_true', + help=3D'Use value profile') + +parser.add_argument('--libfuzzer-options', + help=3D'Pass additional raw libFuzzer flags') + +args =3D parser.parse_args() + +process_args =3D [fuzz_dir + args.fuzzer] +env =3D 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"] =3D "YES" + +if args.format_xml: + env["LPM_XML_FORMAT_ENABLE"] =3D "YES" + +if args.arch: + env["LPM_FUZZ_ARCH"] =3D args.arch + +if args.llvm_profile_file: + if not coverage_clang: + print("Please recompile your build with -Dtest_coverage_clang=3Dtr= ue") + sys.exit(1) + env["LLVM_PROFILE_FILE"] =3D args.llvm_profile_file + + if not args.corpus: + print("Coverage report requires --corpus. If you do not have a cor= pus, do normal fuzzing to prepare one.") + sys.exit(1) + + process_args.extend(["-runs=3D0"]) + +if args.total_time: + process_args.extend([f"-max_total_time=3D{str(args.total_time)}"]) + +if args.timeout: + process_args.extend([f"-timeout=3D{str(args.timeout)}"]) + +if args.use_value_profile: + process_args.extend(["-use_value_profile=3D1"]) + +if args.jobs > 1: + if args.parallel_mode =3D=3D "fork": + process_args.extend([f"-fork=3D{args.jobs}", "-ignore_crashes=3D1"= , "-ignore_ooms=3D1", "-ignore_timeouts=3D1"]) + elif args.parallel_mode =3D=3D "jobs": + process_args.extend([f"-jobs=3D{args.jobs}", f"-workers=3D{args.jo= bs}"]) + +exe_path =3D fuzz_dir + args.fuzzer + +# If libvirt is not installed in system dir, some fuzzers (QEMU, etc.) wil= l fail +env["LPM_EXE_PATH"] =3D exe_path + +process_args.extend(["-print_funcs=3D-1"]) + +if args.libfuzzer_options: + process_args.extend([x for x in args.libfuzzer_options.split(' ') if x= !=3D '']) + +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 =3D declare_dependency( ), ) =20 +test_dir =3D include_directories('.') tests_env =3D [ 'abs_builddir=3D@0@'.format(meson.current_build_dir()), 'abs_srcdir=3D@0@'.format(meson.current_source_dir()), @@ -750,6 +751,10 @@ endforeach testenv =3D runutf8 testenv +=3D 'VIR_TEST_FILE_ACCESS=3D1' =20 +if conf.has('WITH_FUZZ') + subdir('fuzz') +endif + add_test_setup( 'access', env: testenv, --=20 2.34.1