From nobody Sat Nov 23 17:42:02 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 1724085075435347.43572138341824; Mon, 19 Aug 2024 09:31:15 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 5BF701707; Mon, 19 Aug 2024 12:31:14 -0400 (EDT) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id 4AAAD16FB; Mon, 19 Aug 2024 12:20:52 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 672011609; Mon, 19 Aug 2024 12:20:43 -0400 (EDT) Received: from mail-lj1-f174.google.com (mail-lj1-f174.google.com [209.85.208.174]) (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 CE05F161A for ; Mon, 19 Aug 2024 12:20:09 -0400 (EDT) Received: by mail-lj1-f174.google.com with SMTP id 38308e7fff4ca-2f3cd4ebf84so25557151fa.3 for ; Mon, 19 Aug 2024 09:20:09 -0700 (PDT) Received: from localhost.localdomain ([37.186.51.21]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-5bebbde4964sm5738298a12.24.2024.08.19.09.20.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 Aug 2024 09:20:07 -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=1724084408; x=1724689208; 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=bxozF8c0y3WpPJuN2tfu9REXrj4N76GIme9sTGR+wcs=; b=KcYsSwS1WDsOWcBaafREoIKjVHiobgQIBFdEYjeO3QbgMMex7giESbhWTi57LyM3+7 1xZj2j/04aoDdYFrTAVbXm8Dhhk46nLOBW+gvxC6DHUeVYcv9YqVXkP7D4dvWtyyDPuG UzK+CifIbIkWhUVo0Mqq9nJbRls9RJCiMSOxKzjNxR5U2ZrH1XimVxhzfk+Q+IiVXIuy KqrqZfLT8UFIUs7Se63UYx2dyNYZXvRsLS6zW7YUmVWhQF77WT6Zqvj/8Oniidr26lyW jXil8T5qoWmk+qMtLCYiCgGGq1keUsraJ42an7paBXL4wEgcf1dDObteMKa2rt642yK/ NNXw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724084408; x=1724689208; 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=bxozF8c0y3WpPJuN2tfu9REXrj4N76GIme9sTGR+wcs=; b=JJ926cYblHRHwF3LQY8X3xlJgrJicR4QvnL1Bh+9mjbOcVpp5TNEUvYcVmt4f23b9e LYgLJVDszNOSY2bsB8GwzvBZx+0wF0EvIn4d7igWAEivjTogLQdxMs/qXcLIGqfj8qqG ZevOkOPanbNxCWQqfykrHZiX6hDMpxRlcoi481p+UGiLRcthEY8+TiSdKWtUn0Vt8YYi 24KTl7VUi0eej4Na/BnG9+Ac8T1tlncfXKlxmX+YLjUfSPJOPNXW/rtNAQwfm4RRw6M0 pyR3sKC9WUJ7u3e3tyjSl5Nd/KWQT+4lUkFRVLp042TsGlKwKUC8QQBwk5UKcwpqtixf U3DQ== X-Gm-Message-State: AOJu0YyU6GSi9ONwmFOLuo8BhIl1CLYxYVUIdYR1IqImN/WM5r7RBvms HAaDn0G0mNr3SZ+cu0STLZnNkOabi0JNWtgzZYrE64pJQDVk4TfcELvyvw== X-Google-Smtp-Source: AGHT+IEPJtytK8HtZpuMvAz/xvZaAOEi8Na+uLd33+EzcS8lMawwZMX44uxVX5KJGAhnPc50RUpudQ== X-Received: by 2002:a05:651c:4ca:b0:2f3:d008:a54e with SMTP id 38308e7fff4ca-2f3d008a692mr41139131fa.36.1724084407684; Mon, 19 Aug 2024 09:20:07 -0700 (PDT) From: Rayhan Faizel To: devel@lists.libvirt.org Subject: [PATCH 13/14] fuzz: Implement NWFilter XML fuzzer Date: Mon, 19 Aug 2024 21:39:51 +0530 Message-Id: <20240819160952.351383-14-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: R4KDGVGTBMLKR7BYOIYJEHC47F7QNDDK X-Message-ID-Hash: R4KDGVGTBMLKR7BYOIYJEHC47F7QNDDK 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: 1724085075694116600 Content-Type: text/plain; charset="utf-8" This patch includes the harness for the network filter driver. It fuzzes one or more rules in the definition at a time. NWFilter protobuf definitions are generated separately and linked to this fuzzer. This patch also includes handling of some datatypes to fuzz certain attributes: IPSet, TCPFlag, ARP opcode, state flags, etc. Signed-off-by: Rayhan Faizel --- scripts/relaxng-to-proto.py | 16 +++ tests/fuzz/meson.build | 34 ++++++ tests/fuzz/proto_custom_datatypes.cc | 88 +++++++++++++++ tests/fuzz/proto_header_common.h | 4 + tests/fuzz/protos/meson.build | 9 ++ tests/fuzz/protos/xml_datatypes.proto | 21 ++++ tests/fuzz/protos/xml_nwfilter.proto | 9 ++ tests/fuzz/xml_nwfilter_fuzz.cc | 149 ++++++++++++++++++++++++++ 8 files changed, 330 insertions(+) create mode 100644 tests/fuzz/protos/xml_nwfilter.proto create mode 100644 tests/fuzz/xml_nwfilter_fuzz.cc diff --git a/scripts/relaxng-to-proto.py b/scripts/relaxng-to-proto.py index f13d6f7e40..9c1203ff1b 100644 --- a/scripts/relaxng-to-proto.py +++ b/scripts/relaxng-to-proto.py @@ -51,6 +51,22 @@ custom_ref_table =3D { "irq": {"type": "uint32"}, "iobase": {"type": "uint32"}, "uniMacAddr": {"type": "MacAddr"}, + + # NWFilter types + + "addrIP": {"type": "IPAddr"}, + "addrIPv6": {"type": "IPAddr"}, + "addrMAC": {"type": "MacAddr"}, + "uint16range": {"type": "uint32"}, + "uint32range": {"type": "uint32"}, + "sixbitrange": {"type": "uint32"}, + "stateflags-type": {"type": "StateFlags"}, + "tcpflags-type": {"type": "TCPFlags"}, + "ipset-flags-type": {"type": "IPSetFlags"}, + "arpOpcodeType": {"types": ["uint32", "DummyString"], + "values": ["reply", "request", "repl= y_reverse", "request_reverse", + "DRARP_reply", "DRARP_req= uest", "DRARP_error", "INARP_request", + "ARP_NAK"]}, } =20 net_model_names =3D ["virtio", "virtio-transitional", "virtio-non-transiti= onal", "e1000", "e1000e", "igb", diff --git a/tests/fuzz/meson.build b/tests/fuzz/meson.build index 417b8dc1ef..2e796b5726 100644 --- a/tests/fuzz/meson.build +++ b/tests/fuzz/meson.build @@ -31,6 +31,23 @@ fuzz_autogen_xml_domain_dep =3D declare_dependency( ] ) =20 +fuzz_autogen_xml_nwfilter_lib =3D static_library( + 'fuzz_autogen_xml_nwfilter_lib', + [ + autogen_xml_nwfilter_src, + xml_datatypes_proto_src, + 'proto_custom_datatypes.cc', + ], + dependencies: [ fuzz_dep ], +) + +fuzz_autogen_xml_nwfilter_dep =3D declare_dependency( + link_whole: [ fuzz_autogen_xml_nwfilter_lib ], + include_directories: [ + fuzz_autogen_xml_nwfilter_lib.private_dir_include(), + ] +) + if conf.has('WITH_QEMU') fuzzer_src =3D [ 'qemu_xml_domain_fuzz.cc', @@ -108,6 +125,23 @@ if conf.has('WITH_LIBXL') ] endif =20 +if conf.has('WITH_NWFILTER') + fuzzer_src =3D [ + 'xml_nwfilter_fuzz.cc', + 'proto_to_xml.cc', + ] + + nwfilter_libs =3D [ + test_utils_lib, + libvirt_lib, + nwfilter_driver_impl, + ] + + xml_fuzzers +=3D [ + { 'name': 'xml_nwfilter_fuzz', 'src': [ fuzzer_src, xml_nwfilter_proto= _src ], 'libs': nwfilter_libs, 'macro': '-DXML_NWFILTER', 'deps': [ fuzz_au= togen_xml_nwfilter_dep ] }, + ] +endif + foreach fuzzer: xml_fuzzers xml_domain_fuzz =3D executable(fuzzer['name'], fuzzer['src'], diff --git a/tests/fuzz/proto_custom_datatypes.cc b/tests/fuzz/proto_custom= _datatypes.cc index d89a6d4f59..a4a54c0116 100644 --- a/tests/fuzz/proto_custom_datatypes.cc +++ b/tests/fuzz/proto_custom_datatypes.cc @@ -87,6 +87,29 @@ std::string convertIPAddr(const Message &message) { } =20 =20 +static +std::string convertIPSetFlags(const Message &message) +{ + std::string value =3D ""; + const libvirt::IPSetFlags &ipset_flags =3D (libvirt::IPSetFlags &) mes= sage; + + uint32_t max_count =3D ipset_flags.max_count() % 7; + uint32_t bitmap =3D ipset_flags.bitarray() & 0x1f; + + for (size_t i =3D 0; i < max_count; i++) { + if ((bitmap >> i) & 1) + value +=3D "src,"; + else + value +=3D "dst,"; + } + + if (value !=3D "") + value.pop_back(); + + return value; +} + + static std::string convertMacAddr(const Message &message) { char value[64] =3D {0}; @@ -104,6 +127,34 @@ std::string convertMacAddr(const Message &message) { } =20 =20 +static +std::string convertStateFlags(const Message &message) +{ + std::string value =3D ""; + const libvirt::StateFlags &state_flags =3D (libvirt::StateFlags &) mes= sage; + + if (state_flags.newflag()) + value +=3D "NEW,"; + + if (state_flags.established()) + value +=3D "ESTABLISHED,"; + + if (state_flags.related()) + value +=3D "RELATED,"; + + if (state_flags.invalid()) + value +=3D "INVALID,"; + + if (value =3D=3D "") + return "NONE"; + + /* Remove trailing comma */ + value.pop_back(); + + return value; +} + + static std::string convertDiskTarget(const Message &message) { @@ -118,12 +169,49 @@ std::string convertDiskTarget(const Message &message) } =20 =20 +static +std::string convertTCPFlags(const Message &message) +{ + std::string value =3D ""; + const libvirt::TCPFlags &tcp_flags =3D (libvirt::TCPFlags &) message; + + if (tcp_flags.syn()) + value +=3D "SYN,"; + + if (tcp_flags.ack()) + value +=3D "ACK,"; + + if (tcp_flags.urg()) + value +=3D "URG,"; + + if (tcp_flags.psh()) + value +=3D "PSH,"; + + if (tcp_flags.fin()) + value +=3D "FIN,"; + + if (tcp_flags.rst()) + value +=3D "RST,"; + + if (value =3D=3D "") + return "NONE"; + + /* Remove trailing comma */ + value.pop_back(); + + return value; +} + + std::unordered_map type_handler_table =3D { {"libvirt.CPUSet", convertCPUSet}, {"libvirt.EmulatorString", convertEmulatorString}, {"libvirt.IPAddr", convertIPAddr}, + {"libvirt.IPSetFlags", convertIPSetFlags}, {"libvirt.MacAddr", convertMacAddr}, + {"libvirt.StateFlags", convertStateFlags}, {"libvirt.TargetDev", convertDiskTarget}, + {"libvirt.TCPFlags", convertTCPFlags}, }; =20 =20 diff --git a/tests/fuzz/proto_header_common.h b/tests/fuzz/proto_header_com= mon.h index 3f135c48e1..4e4beb787b 100644 --- a/tests/fuzz/proto_header_common.h +++ b/tests/fuzz/proto_header_common.h @@ -39,6 +39,10 @@ #include "xml_hotplug.pb.h" #endif =20 +#ifdef XML_NWFILTER +#include "xml_nwfilter.pb.h" +#endif + =20 #define FUZZ_COMMON_INIT(...) \ if (virErrorInitialize() < 0) \ diff --git a/tests/fuzz/protos/meson.build b/tests/fuzz/protos/meson.build index 0731ef1eca..df276aee8b 100644 --- a/tests/fuzz/protos/meson.build +++ b/tests/fuzz/protos/meson.build @@ -4,6 +4,7 @@ protos =3D [ 'xml_domain_disk_only.proto', 'xml_domain_interface_only.proto', 'xml_hotplug.proto', + 'xml_nwfilter.proto', ] =20 autogen_proto_xml_domain_proto =3D custom_target('autogen_xml_domain.proto= ', @@ -12,6 +13,12 @@ autogen_proto_xml_domain_proto =3D custom_target('autoge= n_xml_domain.proto', command : [relaxng_to_proto_prog, '@INPUT@', '@OUTPUT@'], ) =20 +autogen_proto_xml_nwfilter_proto =3D custom_target('autogen_xml_nwfilter.p= roto', + output : 'autogen_xml_nwfilter.proto', + input : meson.project_source_root() / 'src' / 'conf' / 'schemas' / 'nwfi= lter.rng', + command : [relaxng_to_proto_prog, '@INPUT@', '@OUTPUT@'], +) + protoc_generator =3D generator(protoc_prog, output: [ '@BASENAME@.pb.cc', @@ -25,10 +32,12 @@ protoc_generator =3D generator(protoc_prog, ], depends: [ autogen_proto_xml_domain_proto, + autogen_proto_xml_nwfilter_proto, ], ) =20 autogen_xml_domain_proto_src =3D protoc_generator.process(autogen_proto_xm= l_domain_proto) +autogen_xml_nwfilter_src =3D protoc_generator.process(autogen_proto_xml_nw= filter_proto) =20 foreach proto: protos proto_src_name =3D proto.split('.')[0].underscorify() diff --git a/tests/fuzz/protos/xml_datatypes.proto b/tests/fuzz/protos/xml_= datatypes.proto index 1229b9810f..7bf19051cd 100644 --- a/tests/fuzz/protos/xml_datatypes.proto +++ b/tests/fuzz/protos/xml_datatypes.proto @@ -70,3 +70,24 @@ message CPUSet { } =20 message EmulatorString {} + +message TCPFlags { + required bool syn =3D 1; + required bool ack =3D 2; + required bool urg =3D 3; + required bool psh =3D 4; + required bool fin =3D 5; + required bool rst =3D 6; +} + +message StateFlags { + required bool newflag =3D 1; + required bool established =3D 2; + required bool related =3D 3; + required bool invalid =3D 4; +} + +message IPSetFlags { + required uint32 max_count =3D 1; + required uint32 bitarray =3D 2; +} diff --git a/tests/fuzz/protos/xml_nwfilter.proto b/tests/fuzz/protos/xml_n= wfilter.proto new file mode 100644 index 0000000000..459a10f840 --- /dev/null +++ b/tests/fuzz/protos/xml_nwfilter.proto @@ -0,0 +1,9 @@ +syntax =3D "proto2"; + +import "autogen_xml_nwfilter.proto"; + +package libvirt; + +message MainObj { + required filterTag T_filter =3D 1; +} diff --git a/tests/fuzz/xml_nwfilter_fuzz.cc b/tests/fuzz/xml_nwfilter_fuzz= .cc new file mode 100644 index 0000000000..a2c25a38eb --- /dev/null +++ b/tests/fuzz/xml_nwfilter_fuzz.cc @@ -0,0 +1,149 @@ +/* + * xml_nwfilter_fuzz.cc: NWFilter 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 + * . + */ + +#include +#include "proto_header_common.h" + +#include +#include +#include + +extern "C" { +#include "testutils.h" +#include "nwfilter/nwfilter_ebiptables_driver.h" +#include "virbuffer.h" + +#define LIBVIRT_VIRCOMMANDPRIV_H_ALLOW +#include "vircommandpriv.h" +} + +#include "port/protobuf.h" +#include "proto_to_xml.h" +#include "src/libfuzzer/libfuzzer_macro.h" + +bool enable_xml_dump =3D false; + +uint64_t parse_pass =3D 0; +uint64_t apply_rules_pass =3D 0; +uint64_t success =3D 0; + +static int +fuzzNWFilterDefToRules(virNWFilterDef *def) +{ + size_t i; + virNWFilterRuleDef *rule; + virNWFilterRuleInst *ruleinst; + + virNWFilterRuleInst **ruleinsts =3D NULL; + size_t nrules =3D 0; + + g_auto(virBuffer) buf =3D VIR_BUFFER_INITIALIZER; + g_autoptr(virCommandDryRunToken) dryRunToken =3D virCommandDryRunToken= New(); + + int ret =3D -1; + + /* This line is needed to avoid actually running iptables/ebtables */ + virCommandSetDryRun(dryRunToken, &buf, true, true, NULL, NULL); + + for (i =3D 0; i < (size_t) def->nentries; i++) { + /* We handle only elements. is ignored */ + if (!(rule =3D def->filterEntries[i]->rule)) + continue; + + ruleinst =3D g_new0(virNWFilterRuleInst, 1); + + ruleinst->chainSuffix =3D def->chainsuffix; + ruleinst->chainPriority =3D def->chainPriority; + ruleinst->def =3D rule; + ruleinst->priority =3D rule->priority; + ruleinst->vars =3D virHashNew(virNWFilterVarValueHashFree); + + VIR_APPEND_ELEMENT(ruleinsts, nrules, ruleinst); + } + + + if (ebiptables_driver.applyNewRules("vnet0", ruleinsts, nrules) < 0) + goto cleanup; + + ret =3D 0; + + cleanup: + for (i =3D 0; i < nrules; i++) { + g_clear_pointer(&ruleinsts[i]->vars, g_hash_table_unref); + g_free(ruleinsts[i]); + ruleinsts[i] =3D NULL; + } + + if (nrules !=3D 0) + g_free(ruleinsts); + + return ret; +} + + +static void +fuzzNWFilterXML(const char *xml) +{ + virNWFilterDef *def =3D NULL; + + parse_pass++; + if (!(def =3D virNWFilterDefParse(xml, NULL, 0))) + goto cleanup; + + apply_rules_pass++; + + if (fuzzNWFilterDefToRules(def) < 0) + goto cleanup; + + success++; + + cleanup: + virNWFilterDefFree(def); +} + + +DEFINE_PROTO_FUZZER(const libvirt::MainObj &message) +{ + static bool initialized =3D false; + static const char *dump_xml_env =3D g_getenv("LPM_XML_DUMP_INPUT"); + + std::string xml =3D ""; + + if (!initialized) { + FUZZ_COMMON_INIT(); + + /* Enable printing of XML to stdout (useful for debugging crashes)= */ + if (dump_xml_env && STREQ(dump_xml_env, "YES")) + enable_xml_dump =3D true; + + initialized =3D true; + } + + convertProtoToXML(message, xml); + + if (enable_xml_dump) + printf("%s\n", xml.c_str()); + + fuzzNWFilterXML(xml.c_str()); + + if (parse_pass % 1000 =3D=3D 0) + printf("[FUZZ METRICS] Parse: %lu, Apply Rules: %lu, Success: %lu\= n", + parse_pass, apply_rules_pass, success); +} --=20 2.34.1