From nobody Sat Nov 23 18:01:37 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 1724084898164898.9929173142983; Mon, 19 Aug 2024 09:28:18 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 136B71279; Mon, 19 Aug 2024 12:28:17 -0400 (EDT) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id C7D9F16E0; Mon, 19 Aug 2024 12:20:33 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 996) id E45E41677; Mon, 19 Aug 2024 12:20:25 -0400 (EDT) Received: from mail-ed1-f51.google.com (mail-ed1-f51.google.com [209.85.208.51]) (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 1F4B415E2 for ; Mon, 19 Aug 2024 12:20:03 -0400 (EDT) Received: by mail-ed1-f51.google.com with SMTP id 4fb4d7f45d1cf-5bed83487aeso3217914a12.2 for ; Mon, 19 Aug 2024 09:20:03 -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.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 Aug 2024 09:20:01 -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=1724084402; x=1724689202; 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=PBUUJnlan3sjIrjy4IqGTJaPH1Xt96XJBmHknOhRulw=; b=GcQeCZW4xR6d5FsjYXWSUB9eudZndx84fIdGuo6GCE5YN4fQFpOyQVPtdSW+Hi8Wwq jV4P0CPSbGSm4t/0jaPGAzseiKfrNXctL48kffx4+hj3hfuNCRkXjEOkZNBVmW698OUQ W9P5n3d9Y/aBGRRvYrvq+YvEQahupYm8E7jkDoyePjjpjsBR1KdplCGMoGVXdvoA57Bj D0JsvOWxaMGpHbzD8HGYma9r7rtfvxLhtFWkU4sIpQ4uN26iJnNx1/idXPQuv8mkNYL9 lVOTBXABWU+xGzmhUVmqu2qLLUuxrVVjiaiMlSHOQOfv5dHKO+GtMJFomITYol/jKDvO 5nfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724084402; x=1724689202; 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=PBUUJnlan3sjIrjy4IqGTJaPH1Xt96XJBmHknOhRulw=; b=QbNqjoBB2ttYGU3+wEmJIosI6F/0+2dDmiutTHTW8LXfwRNDDPVPV1/6CiPiXRRxHO USIsQnpMQmf+y1+eH6L2O/mFX+oejdJXGCuvq6tGJ7PufMCXLAA6M2QW1aFgBRXRFTeR wgmMokDtBb4vERCx+zdiz0lS2MDG+JXrHym8KVLEY5yrTbura5001MBSb5YuQ+2OqZyl jgGSDD9vB3vBWo15FE/wB4FLzMR7Z6mNAxxYAlmN6v80nsK34o+dNGdvHLzOboMVNQs2 3W60oU49o+DYSmbZV6bKctZE1LJi7S0jzUiMpGZxrZEneFn1nrOq8CGR9dbNGER2VzcS /1Hg== X-Gm-Message-State: AOJu0YxqxY+dpQaBCZk5c7FPANjFRRNYUHNT/TvLcbuppXMkv/jpQHov y0yyL6Ktp2sN5ZUMlKIZ5WdRcfJ8PJ1iKedulouToNCEmtldAWq8IFqeOw== X-Google-Smtp-Source: AGHT+IFJijcux2d9lvNUFh38NLzPgIYNGjaZiBdqBFOBY+fCGaRTpH+zfvmqkuKqj3ICqqiG6JOaYw== X-Received: by 2002:aa7:c54d:0:b0:5a4:6dec:cd41 with SMTP id 4fb4d7f45d1cf-5beca76e4ebmr6422578a12.28.1724084401410; Mon, 19 Aug 2024 09:20:01 -0700 (PDT) From: Rayhan Faizel To: devel@lists.libvirt.org Subject: [PATCH 08/14] fuzz: Implement QEMU XML hotplug fuzzer Date: Mon, 19 Aug 2024 21:39:46 +0530 Message-Id: <20240819160952.351383-9-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: QSYKOO4XBG7EMVO5MS4HVXA77FP7ZWB5 X-Message-ID-Hash: QSYKOO4XBG7EMVO5MS4HVXA77FP7ZWB5 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: 1724084898919116600 Content-Type: text/plain; charset="utf-8" This patch implements the harness for hotplugging devices under the QEMU driver. The fuzzer exercises both attachment and detachment of various devices. It uses its own proto file xml_hotplug to fuzz only one device type at a time. Monitor setup is done such that QMP commands are processed indefinitely and removal wait times are eliminated. A table of dummy responses is referred to for generating QMP responses. A semi-hardcoded domain XML is used to hotplug and unhotplug device XMLs against it. It consists of various controllers to allow successful hotplugg= ing. Depending on the architecture selected, some controllers may be omitted or modified. LeakSanitizer slows down the fuzzer severely, so we disable it in run_fuzz for now. Signed-off-by: Rayhan Faizel --- src/conf/domain_conf.h | 2 + src/qemu/qemu_hotplug.c | 4 + tests/fuzz/meson.build | 6 + tests/fuzz/proto_header_common.h | 4 + tests/fuzz/protos/meson.build | 1 + tests/fuzz/protos/xml_hotplug.proto | 38 ++++ tests/fuzz/qemu_xml_hotplug_fuzz.cc | 340 ++++++++++++++++++++++++++++ tests/fuzz/run_fuzz.in | 5 + tests/qemumonitortestutils.c | 48 ++++ tests/qemumonitortestutils.h | 6 + 10 files changed, 454 insertions(+) create mode 100644 tests/fuzz/protos/xml_hotplug.proto create mode 100644 tests/fuzz/qemu_xml_hotplug_fuzz.cc diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 3e97dd6293..1327604fbd 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3288,6 +3288,8 @@ struct _virDomainObj { int taint; size_t ndeprecations; char **deprecations; + + bool fuzz; }; =20 G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainObj, virObjectUnref); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 75b97cf736..a4a5a32346 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -5455,6 +5455,10 @@ qemuDomainWaitForDeviceRemoval(virDomainObj *vm) qemuDomainObjPrivate *priv =3D vm->privateData; unsigned long long until; =20 + /* Skip the wait entirely if fuzzing is active */ + if (vm->fuzz) + return 1; + if (virTimeMillisNow(&until) < 0) return 1; until +=3D qemuDomainGetUnplugTimeout(vm); diff --git a/tests/fuzz/meson.build b/tests/fuzz/meson.build index 12f9a719f2..9579f56749 100644 --- a/tests/fuzz/meson.build +++ b/tests/fuzz/meson.build @@ -37,6 +37,11 @@ if conf.has('WITH_QEMU') 'proto_to_xml.cc', ] =20 + hotplug_src =3D [ + 'qemu_xml_hotplug_fuzz.cc', + 'proto_to_xml.cc', + ] + qemu_libs =3D [ test_qemu_driver_lib, test_utils_lib, @@ -48,6 +53,7 @@ if conf.has('WITH_QEMU') { 'name': 'qemu_xml_domain_fuzz', 'src': [ fuzzer_src, xml_domain_prot= o_src ], 'libs': qemu_libs, 'macro': '-DXML_DOMAIN', 'deps': [ fuzz_autogen= _xml_domain_dep ] }, { 'name': 'qemu_xml_domain_fuzz_disk', 'src': [ fuzzer_src, xml_domain= _disk_only_proto_src ], 'libs': qemu_libs, 'macro': '-DXML_DOMAIN_DISK_ONLY= ', 'deps': [ fuzz_autogen_xml_domain_dep ] }, { 'name': 'qemu_xml_domain_fuzz_interface', 'src': [ fuzzer_src, xml_d= omain_interface_only_proto_src ], 'libs': qemu_libs, 'macro': '-DXML_DOMAIN= _INTERFACE_ONLY', 'deps': [ fuzz_autogen_xml_domain_dep ] }, + { 'name': 'qemu_xml_hotplug_fuzz', 'src': [ hotplug_src, xml_hotplug_p= roto_src ], 'libs': [ qemu_libs, test_utils_qemu_monitor_lib ], 'macro': '-= DXML_HOTPLUG', 'deps': [ fuzz_autogen_xml_domain_dep ] }, ] endif =20 diff --git a/tests/fuzz/proto_header_common.h b/tests/fuzz/proto_header_com= mon.h index 5ee510896d..3f135c48e1 100644 --- a/tests/fuzz/proto_header_common.h +++ b/tests/fuzz/proto_header_common.h @@ -35,6 +35,10 @@ #include "xml_domain_interface_only.pb.h" #endif =20 +#ifdef XML_HOTPLUG +#include "xml_hotplug.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 42c3a7f6a9..0731ef1eca 100644 --- a/tests/fuzz/protos/meson.build +++ b/tests/fuzz/protos/meson.build @@ -3,6 +3,7 @@ protos =3D [ 'xml_domain.proto', 'xml_domain_disk_only.proto', 'xml_domain_interface_only.proto', + 'xml_hotplug.proto', ] =20 autogen_proto_xml_domain_proto =3D custom_target('autogen_xml_domain.proto= ', diff --git a/tests/fuzz/protos/xml_hotplug.proto b/tests/fuzz/protos/xml_ho= tplug.proto new file mode 100644 index 0000000000..0490b2fdb6 --- /dev/null +++ b/tests/fuzz/protos/xml_hotplug.proto @@ -0,0 +1,38 @@ +syntax =3D "proto2"; + +import "autogen_xml_domain.proto"; + +package libvirt; + +message MainObj { + oneof new_device { + domainTag.devicesTag.soundTag T_sound =3D 1; + domainTag.devicesTag.filesystemTag T_filesystem =3D 2; + domainTag.devicesTag.inputTag T_input =3D 3; + domainTag.devicesTag.diskTag T_disk =3D 4; + domainTag.devicesTag.interfaceTag T_interface =3D 5; + domainTag.devicesTag.graphicsTag T_graphics =3D 6; + domainTag.devicesTag.serialTag T_serial =3D 7; + domainTag.devicesTag.parallelTag T_parallel =3D 8; + domainTag.devicesTag.channelTag T_channel =3D 9; + domainTag.devicesTag.consoleTag T_console =3D 10; + domainTag.devicesTag.controllerTag T_controller =3D 11; + domainTag.devicesTag.videoTag T_video =3D 12; + domainTag.devicesTag.rngTag T_rng =3D 13; + domainTag.devicesTag.watchdogTag T_watchdog =3D 14; + domainTag.devicesTag.memballoonTag T_memballoon =3D 15; + domainTag.devicesTag.smartcardTag T_smartcard =3D 16; + domainTag.devicesTag.redirdevTag T_redirdev =3D 17; + domainTag.devicesTag.audioTag T_audio =3D 18; + domainTag.devicesTag.cryptoTag T_crypto =3D 19; + domainTag.devicesTag.panicTag T_panic =3D 20; + domainTag.devicesTag.tpmTag T_tpm =3D 21; + domainTag.devicesTag.shmemTag T_shmem =3D 22; + domainTag.devicesTag.hostdevTag T_hostdev =3D 23; + domainTag.devicesTag.leaseTag T_lease =3D 24; + domainTag.devicesTag.redirfilterTag T_redirfilter =3D 25; + domainTag.devicesTag.iommuTag T_iommu =3D 26; + domainTag.devicesTag.vsockTag T_vsock =3D 27; + domainTag.devicesTag.nvramTag T_nvram =3D 28; + } +} diff --git a/tests/fuzz/qemu_xml_hotplug_fuzz.cc b/tests/fuzz/qemu_xml_hotp= lug_fuzz.cc new file mode 100644 index 0000000000..6e391c1f51 --- /dev/null +++ b/tests/fuzz/qemu_xml_hotplug_fuzz.cc @@ -0,0 +1,340 @@ +/* + * qemu_xml_hotplug_fuzz.cc: QEMU hotplug 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 + +#include +#include +#include +#include +#include + +extern "C" { +#include "qemu/qemu_alias.h" +#include "qemu/qemu_conf.h" +#include "qemu/qemu_hotplug.h" +#include "qemumonitortestutils.h" +#include "testutils.h" +#include "testutilsqemu.h" +#include "testutilsqemuschema.h" +#include "virhostdev.h" +#include "virfile.h" +} + +#include "port/protobuf.h" +#include "proto_to_xml.h" +#include "src/libfuzzer/libfuzzer_macro.h" + +#define QEMU_HOTPLUG_FUZZ_DOMAIN_ID 7 + +uint64_t device_parse_pass =3D 0; +uint64_t device_attach_pass =3D 0; +uint64_t success_pass =3D 0; +uint64_t detach_success_pass =3D 0; + +bool enable_xml_dump =3D false; + +typedef struct { + virQEMUCaps *caps; + GHashTable *schema; +} qemuFuzzHotplugData; + +#define QMP_OK "{\"return\": {}}" +#define QMP_EMPTY_ARRAY "{\"return\": []}" + +std::string getBaseDomainXML(std::string arch) { + std::string result =3D "\n" + " MyGuest\n" + " 4dea22b3-1d52-d8f3-2516-782e98ab3fa0\n" + " \n" + " hvm\n" + " \n" + " 4096\n" + " \n"; + + if (arch =3D=3D "aarch64" || arch =3D=3D "armv7l" || + arch =3D=3D "riscv64" || + arch =3D=3D "loongarch64") { + result +=3D "\n"; + } else { + result +=3D "\n"; + } + + /* s390x does not support USB */ + if (arch !=3D "s390x") { + result +=3D "\n"; + result +=3D "\n"; + } + + /* SATA is not supported on s390x and SPARC */ + if (arch !=3D "s390x" && arch !=3D "sparc") + result +=3D "\n"; + + result +=3D "\n"; + result +=3D "\n"; + + result +=3D "/usr/bin/qemu-system-" + arch + "\n" + "\n" + "\n"; + + return result; +} + + +std::string getDeviceDeletedResponse(std::string dev) { + std::string result =3D "{" \ + " \"timestamp\": {" \ + " \"seconds\": 1374137171," \ + " \"microseconds\": 2659" \ + " }," \ + " \"event\": \"DEVICE_DELETED\"," \ + " \"data\": {" \ + " \"device\": \"" + dev + "\"," \ + " \"path\": \"/machine/peripheral/" + dev + "\"" \ + " }" \ + "}\r\n"; + + return result; +} + + +/* Table of QMP commands and dummy responses */ +std::unordered_map qmp_cmd_table =3D { + {"device_add", QMP_OK}, + {"object-add", QMP_OK}, + {"object-del", QMP_OK}, + {"netdev_add", QMP_OK}, + {"netdev_del", QMP_OK}, + {"chardev-add", QMP_OK}, + {"chardev-remove", QMP_OK}, + {"blockdev-add", QMP_OK}, + {"blockdev-del", QMP_OK}, + {"qom-list", QMP_EMPTY_ARRAY}, + {"query-block", QMP_EMPTY_ARRAY}, + {"query-fds", QMP_EMPTY_ARRAY}, + {"set-action", QMP_OK}, + {"set_link", QMP_OK}, + {"query-fdsets", QMP_EMPTY_ARRAY}, + {"add-fd", "{ \"return\": { \"fdset-id\": 1, \"fd\": 95 }}"}, + {"block_set_io_throttle", QMP_OK}, +}; + + +char *qemuMonitorFuzzGetResponse(const char *command, + virJSONValue *cmdargs) +{ + if (STREQ(command, "device_del")) { + const char *id =3D virJSONValueObjectGetString(cmdargs, "id"); + std::string result =3D getDeviceDeletedResponse(id); + + return g_strdup(result.c_str()); + } + + if (qmp_cmd_table.find(command) !=3D qmp_cmd_table.end()) { + return g_strdup(qmp_cmd_table[command].c_str()); + } + + /* If QMP command is unknown, assume QMP_OK and warn the user of the s= ame. */ + printf("[FUZZ WARN]: Unknown QMP command: %s\n", command); + + return g_strdup(QMP_OK); +} + + +static void +fuzzXMLHotplug(virQEMUDriver *driver, + qemuFuzzHotplugData *hotplugData, + const char *domain_xml_string, + const char *device_xml_string) +{ + virDomainObj *vm =3D NULL; + virDomainDef *def =3D NULL; + virDomainDeviceDef *dev =3D NULL; + virDomainDeviceDef *remove_dev =3D NULL; + qemuDomainObjPrivate *priv =3D NULL; + g_autoptr(qemuMonitorTest) test_mon =3D NULL; + bool attach_success =3D false; + + if (!(def =3D virDomainDefParseString(domain_xml_string, driver->xmlop= t, NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE))) { + + printf("Failed to parse domain XML!\n"); + exit(EXIT_FAILURE); + } + + if (!(vm =3D virDomainObjNew(driver->xmlopt))) + goto cleanup; + + vm->def =3D def; + priv =3D (qemuDomainObjPrivate *) vm->privateData; + priv->qemuCaps =3D hotplugData->caps; + + if (qemuDomainAssignAddresses(vm->def, hotplugData->caps, + driver, vm, true) < 0) { + goto cleanup; + } + + if (qemuAssignDeviceAliases(vm->def) < 0) + goto cleanup; + + vm->def->id =3D QEMU_HOTPLUG_FUZZ_DOMAIN_ID; + vm->fuzz =3D true; + + if (qemuDomainSetPrivatePaths(driver, vm) < 0) + goto cleanup; + + device_parse_pass++; + if (!(dev =3D virDomainDeviceDefParse(device_xml_string, vm->def, + driver->xmlopt, NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE))) + goto cleanup; + + /* Initialize test monitor + * + * Keep it after virDomainDeviceDefParse to avoid wasting time with mo= nitor + * creation. + */ + + if (!(test_mon =3D qemuMonitorTestNew(driver->xmlopt, vm, NULL, hotplu= gData->schema))) + goto cleanup; + + /* Enable fuzzing mode of the test monitor */ + qemuMonitorTestFuzzSetup(test_mon, qemuMonitorFuzzGetResponse); + + priv->mon =3D qemuMonitorTestGetMonitor(test_mon); + + virObjectUnlock(priv->mon); + + device_attach_pass++; + + if (qemuDomainAttachDeviceLive(vm, dev, driver) =3D=3D 0) { + success_pass++; + attach_success =3D true; + } + + if (attach_success) { + /* The previous virDomainDeviceDefParse cleared out the data in de= v, so + * we need to reparse it before doing the detachment. + */ + remove_dev =3D virDomainDeviceDefParse(device_xml_string, vm->def, + driver->xmlopt, NULL, + 0); + + if (remove_dev && qemuDomainDetachDeviceLive(vm, remove_dev, drive= r, false) =3D=3D 0) { + detach_success_pass++; + } + } + + virObjectLock(priv->mon); + + cleanup: + if (vm) { + priv->qemuCaps =3D NULL; + priv->mon =3D NULL; + vm->def =3D NULL; + + virDomainObjEndAPI(&vm); + } + + virDomainDeviceDefFree(remove_dev); + virDomainDeviceDefFree(dev); + virDomainDefFree(def); +} + + +DEFINE_PROTO_FUZZER(const libvirt::MainObj &message) +{ + static GHashTable *capscache =3D virHashNew(virObjectUnref); + static GHashTable *capslatest =3D testQemuGetLatestCaps(); + static GHashTable *qapiSchemaCache =3D virHashNew((GDestroyNotify) g_h= ash_table_unref); + + static qemuFuzzHotplugData *hotplugData =3D g_new0(qemuFuzzHotplugData= , 1); + + static virQEMUDriver driver; + static bool initialized =3D false; + + static const char *arch_env =3D g_getenv("LPM_FUZZ_ARCH"); + static const char *dump_xml_env =3D g_getenv("LPM_XML_DUMP_INPUT"); + + static std::string arch =3D ""; + static std::string domain_xml =3D ""; + + std::string device_xml =3D ""; + + /* + * One-time setup of QEMU driver. Re-running them in every + * iteration incurs a significant penalty to the speed of the fuzzer. + */ + if (!initialized) { + FUZZ_COMMON_INIT(); + + if (qemuTestDriverInit(&driver) < 0) + exit(EXIT_FAILURE); + + driver.lockManager =3D virLockManagerPluginNew("nop", "qemu", + driver.config->config= BaseDir, + 0); + + if (arch_env) { + arch =3D arch_env; + } else { + arch =3D "x86_64"; + } + + if (!(hotplugData->caps =3D testQemuGetRealCaps(arch.c_str(), "lat= est", "", + capslatest, capscach= e, + qapiSchemaCache, &ho= tplugData->schema))) { + printf("Failed to setup QEMU capabilities (invalid arch?)\n"); + exit(EXIT_FAILURE); + } + + if (qemuTestCapsCacheInsert(driver.qemuCapsCache, hotplugData->cap= s) < 0) + exit(EXIT_FAILURE); + + if (!(driver.hostdevMgr =3D virHostdevManagerGetDefault())) + exit(EXIT_FAILURE); + + virEventRegisterDefaultImpl(); + + domain_xml =3D getBaseDomainXML(arch); + + /* 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, device_xml); + + if (enable_xml_dump) + printf("%s\n", device_xml.c_str()); + + fuzzXMLHotplug(&driver, hotplugData, domain_xml.c_str(), device_xml.c_= str()); + + if (device_parse_pass % 1000 =3D=3D 0) + printf("[FUZZ METRICS] Device parse: %lu, Device Attach: %lu, Atta= ched: %lu, Detached: %lu\n", + device_parse_pass, device_attach_pass, success_pass, detach= _success_pass); +} diff --git a/tests/fuzz/run_fuzz.in b/tests/fuzz/run_fuzz.in index da3c7935b7..414b99b6cf 100644 --- a/tests/fuzz/run_fuzz.in +++ b/tests/fuzz/run_fuzz.in @@ -113,6 +113,11 @@ env["LPM_EXE_PATH"] =3D exe_path =20 process_args.extend(["-print_funcs=3D-1"]) =20 +if args.fuzzer =3D=3D "qemu_xml_hotplug_fuzz": + # LSAN slows down the hotplug fuzzer to a crawl, + # so we have to disable LeakSanitizer + env["ASAN_OPTIONS"] =3D "detect_leaks=3D0" + if args.libfuzzer_options: process_args.extend([x for x in args.libfuzzer_options.split(' ') if x= !=3D '']) =20 diff --git a/tests/qemumonitortestutils.c b/tests/qemumonitortestutils.c index 88a369188e..448710957e 100644 --- a/tests/qemumonitortestutils.c +++ b/tests/qemumonitortestutils.c @@ -83,8 +83,14 @@ struct _qemuMonitorTest { =20 virDomainObj *vm; GHashTable *qapischema; + + bool fuzz; + qemuMonitorFuzzResponseCallback fuzz_response_cb; }; =20 +static int +qemuMonitorFuzzProcessCommandDefault(qemuMonitorTest *test, + const char *cmdstr); =20 static void qemuMonitorTestItemFree(qemuMonitorTestItem *item) @@ -227,6 +233,11 @@ qemuMonitorTestProcessCommand(qemuMonitorTest *test, =20 VIR_DEBUG("Processing string from monitor handler: '%s", cmdstr); =20 + /* In fuzzing mode, process indefinite number of commands */ + if (test->fuzz) { + return qemuMonitorFuzzProcessCommandDefault(test, cmdstr); + } + if (test->nitems =3D=3D 0) { qemuMonitorTestError("unexpected command: '%s'", cmdstr); } else { @@ -595,6 +606,34 @@ qemuMonitorTestAddItem(qemuMonitorTest *test, } =20 =20 +static int +qemuMonitorFuzzProcessCommandDefault(qemuMonitorTest *test, + const char *cmdstr) +{ + g_autoptr(virJSONValue) val =3D NULL; + virJSONValue *cmdargs =3D NULL; + + const char *cmdname =3D NULL; + g_autofree char *response =3D NULL; + + if (!(val =3D virJSONValueFromString(cmdstr))) + return -1; + + if (!(cmdname =3D virJSONValueObjectGetString(val, "execute"))) { + qemuMonitorTestError("Missing command name in %s", cmdstr); + return -1; + } + + cmdargs =3D virJSONValueObjectGet(val, "arguments"); + + response =3D test->fuzz_response_cb(cmdname, cmdargs); + + qemuMonitorTestAddResponse(test, response); + + return 0; +} + + static int qemuMonitorTestProcessCommandVerbatim(qemuMonitorTest *test, qemuMonitorTestItem *item, @@ -1021,6 +1060,15 @@ qemuMonitorTestSkipDeprecatedValidation(qemuMonitorT= est *test, } =20 =20 +void +qemuMonitorTestFuzzSetup(qemuMonitorTest *test, + qemuMonitorFuzzResponseCallback response_cb) +{ + test->fuzz =3D true; + test->fuzz_response_cb =3D response_cb; +} + + static int qemuMonitorTestFullAddItem(qemuMonitorTest *test, const char *filename, diff --git a/tests/qemumonitortestutils.h b/tests/qemumonitortestutils.h index 6d26526f60..105bd10486 100644 --- a/tests/qemumonitortestutils.h +++ b/tests/qemumonitortestutils.h @@ -29,6 +29,8 @@ typedef struct _qemuMonitorTestItem qemuMonitorTestItem; typedef int (*qemuMonitorTestResponseCallback)(qemuMonitorTest *test, qemuMonitorTestItem *item, const char *message); +typedef char *(*qemuMonitorFuzzResponseCallback)(const char *command, + virJSONValue *cmdargs); =20 void qemuMonitorTestAddHandler(qemuMonitorTest *test, @@ -61,6 +63,10 @@ void qemuMonitorTestSkipDeprecatedValidation(qemuMonitorTest *test, bool allowRemoved); =20 +void +qemuMonitorTestFuzzSetup(qemuMonitorTest *test, + qemuMonitorFuzzResponseCallback response_cb); + int qemuMonitorTestAddItem(qemuMonitorTest *test, const char *command_name, --=20 2.34.1