[RFC PATCH] tests/qtest/fuzz: Add direct MemoryRegion fuzzer

Philippe Mathieu-Daudé posted 1 patch 3 years, 11 months ago
Test docker-mingw@fedora passed
Test checkpatch failed
Test asan passed
Test docker-quick@centos7 passed
Test FreeBSD passed
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20200525172704.32142-1-f4bug@amsat.org
tests/qtest/fuzz/memory_region_fuzz.c | 230 ++++++++++++++++++++++++++
tests/qtest/fuzz/Makefile.include     |   1 +
2 files changed, 231 insertions(+)
create mode 100644 tests/qtest/fuzz/memory_region_fuzz.c
[RFC PATCH] tests/qtest/fuzz: Add direct MemoryRegion fuzzer
Posted by Philippe Mathieu-Daudé 3 years, 11 months ago
This is a proof-of-concept fuzzer that resolve a MemoryRegion
Object within the fuzzed QEMU process, then directly do
memory accesses using memory_region_dispatch_read/write calls.

We avoid first the serialization of QTest strings, then the
FlatView resolve and AddressSpace dispatch.

Various TODO/FIXME left.

In particular we need to be able to parse command line argv[]
for machine/device/region_name.

This PoC will fuzz the e1000e network device on a Q35 machine,
bypassing the PCI bus.

Run on a single i7-8650U core, it takes less than 1min to find:

 $ time \
   i386-softmmu/qemu-fuzz-i386 --fuzz-target=mr-fuzz \
   -detect_leaks=0 -max_len=64 /tmp/mycorpus
 [...]
 #995301 REDUCE cov: 1042 ft: 3262 corp: 622/13348b lim: 64 exec/s: 23697 rss: 349Mb L: 10/64 MS: 2 CopyPart-EraseBytes-
 #999227 REDUCE cov: 1042 ft: 3262 corp: 622/13345b lim: 64 exec/s: 23791 rss: 350Mb L: 40/64 MS: 1 EraseBytes-
 #1024575        REDUCE cov: 1043 ft: 3272 corp: 629/13702b lim: 64 exec/s: 23827 rss: 353Mb L: 25/64 MS: 1 EraseBytes-
 #1025436        REDUCE cov: 1043 ft: 3272 corp: 629/13699b lim: 64 exec/s: 23847 rss: 353Mb L: 39/64 MS: 1 EraseBytes-
 qemu: hardware error: e1000e: PSRCTL.BSIZE0 cannot be zero
 CPU #0:
 EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000663
 ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
 EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
 ES =0000 00000000 0000ffff 00009300
 CS =f000 ffff0000 0000ffff 00009b00
 SS =0000 00000000 0000ffff 00009300
 DS =0000 00000000 0000ffff 00009300
 FS =0000 00000000 0000ffff 00009300
 GS =0000 00000000 0000ffff 00009300
 LDT=0000 00000000 0000ffff 00008200
 TR =0000 00000000 0000ffff 00008b00
 GDT=     00000000 0000ffff
 IDT=     00000000 0000ffff
 CR0=60000010 CR2=00000000 CR3=00000000 CR4=00000000
 DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
 DR6=ffff0ff0 DR7=00000400
 EFER=0000000000000000
 FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
 FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
 FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
 FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
 FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
 XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
 XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
 XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
 XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
 ==31659== ERROR: libFuzzer: deadly signal
     #0 0x557a8f6f0430 in __sanitizer_print_stack_trace (i386-softmmu/qemu-fuzz-i386+0xcb1430)
     #1 0x557a8f5ff421 in fuzzer::PrintStackTrace() (i386-softmmu/qemu-fuzz-i386+0xbc0421)
     #2 0x557a8f5dfcaf in fuzzer::Fuzzer::CrashCallback() (i386-softmmu/qemu-fuzz-i386+0xba0caf)
     #3 0x557a8f5dfd03 in fuzzer::Fuzzer::StaticCrashSignalCallback() (i386-softmmu/qemu-fuzz-i386+0xba0d03)
     #4 0x7ff5aea7fc6f  (/lib64/libpthread.so.0+0x12c6f)
     #5 0x7ff5ae8bee34 in __GI_raise (/lib64/libc.so.6+0x37e34)
     #6 0x7ff5ae8a9894 in __GI_abort (/lib64/libc.so.6+0x22894)
     #7 0x557a8f8bb5dd in hw_error (i386-softmmu/qemu-fuzz-i386+0xe7c5dd)
     #8 0x557a90425f42 in e1000e_set_psrctl (i386-softmmu/qemu-fuzz-i386+0x19e6f42)
     #9 0x557a904144df in e1000e_core_write (i386-softmmu/qemu-fuzz-i386+0x19d54df)
     #10 0x557a9040c796 in e1000e_mmio_write (i386-softmmu/qemu-fuzz-i386+0x19cd796)
     #11 0x557a8f8fbc67 in memory_region_write_accessor (i386-softmmu/qemu-fuzz-i386+0xebcc67)
     #12 0x557a8f8fb7d3 in access_with_adjusted_size (i386-softmmu/qemu-fuzz-i386+0xebc7d3)
     #13 0x557a8f8fa374 in memory_region_dispatch_write (i386-softmmu/qemu-fuzz-i386+0xebb374)
     #14 0x557a90ef4e5f in memory_region_fuzz_one (i386-softmmu/qemu-fuzz-i386+0x24b5e5f)
     #15 0x557a90eef1ae in memory_region_fuzz (i386-softmmu/qemu-fuzz-i386+0x24b01ae)
     #16 0x557a90eed0d0 in LLVMFuzzerTestOneInput (i386-softmmu/qemu-fuzz-i386+0x24ae0d0)
     #17 0x557a8f5e01b8 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (i386-softmmu/qemu-fuzz-i386+0xba11b8)
     #18 0x557a8f5e358e in fuzzer::Fuzzer::MutateAndTestOne() (i386-softmmu/qemu-fuzz-i386+0xba458e)
     #19 0x557a8f5e418f in fuzzer::Fuzzer::Loop(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, fuzzer::fuzzer_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&) (i386-softmmu/qemu-fuzz-i386+0xba518f)
     #20 0x557a8f5d6f91 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (i386-softmmu/qemu-fuzz-i386+0xb97f91)
     #21 0x557a8f5cdc76 in main (i386-softmmu/qemu-fuzz-i386+0xb8ec76)
     #22 0x7ff5ae8aaf42 in __libc_start_main (/lib64/libc.so.6+0x23f42)
     #23 0x557a8f5cdccd in _start (i386-softmmu/qemu-fuzz-i386+0xb8eccd)

 NOTE: libFuzzer has rudimentary signal handlers.
       Combine libFuzzer with AddressSanitizer or similar for better crash reports.
 SUMMARY: libFuzzer: deadly signal
 MS: 1 CopyPart-; base unit: 5ce018d6638e560881cffa145fedad441d5ef356
 0x0,0x46,0x0,0x26,0x0,0x1,0xe2,0x67,0x8,0x0,0x0,0x0,0x74,0x27,0x70,0x21,0x16,0xe1,0xd8,0x0,0x90,0x59,0x1e,0x0,0x27,0x70,0x21,0x0,0x74,0x27,0x70,0x21,0x16,0xe1,0xd8,0x0,0x90,0x59,0x1e,0x0,0x27,0x70,0x21,0x2b,0xe1,0xd8,0x0,0x90,0xd6,0x1e,0x0,0x27,0xa,0x1,0xaa,0xc9,0x1,0xb0,0xc0,0x0,0x0,0xda,0x56,0x68,
 \x00F\x00&\x00\x01\xe2g\x08\x00\x00\x00t'p!\x16\xe1\xd8\x00\x90Y\x1e\x00'p!\x00t'p!\x16\xe1\xd8\x00\x90Y\x1e\x00'p!+\xe1\xd8\x00\x90\xd6\x1e\x00'\x0a\x01\xaa\xc9\x01\xb0\xc0\x00\x00\xdaVh
 artifact_prefix='./'; Test unit written to ./crash-fafe2e3e3412d9a722b06f43bd57c3c43fe9ae99
 Base64: AEYAJgAB4mcIAAAAdCdwIRbh2ACQWR4AJ3AhAHQncCEW4dgAkFkeACdwISvh2ACQ1h4AJwoBqskBsMAAANpWaA==
 real    0m44.191s
 user    0m43.696s
 sys     0m0.624s

libFuzzer report about 24K executions of resetting device and
processing packets in a 64 bytes input payload.

Inspired-by: Alexander Bulekov <alxndr@bu.edu>
Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
---
 tests/qtest/fuzz/memory_region_fuzz.c | 230 ++++++++++++++++++++++++++
 tests/qtest/fuzz/Makefile.include     |   1 +
 2 files changed, 231 insertions(+)
 create mode 100644 tests/qtest/fuzz/memory_region_fuzz.c

diff --git a/tests/qtest/fuzz/memory_region_fuzz.c b/tests/qtest/fuzz/memory_region_fuzz.c
new file mode 100644
index 0000000000..4e542d1772
--- /dev/null
+++ b/tests/qtest/fuzz/memory_region_fuzz.c
@@ -0,0 +1,230 @@
+/*
+ * MMIO Fuzzing Target
+ *
+ * Resolve MemoryRegion Object in process, then directly
+ * access it using memory_region_dispatch_read/write calls.
+ *
+ * Copyright 2020 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "exec/memory.h"
+#include "hw/qdev-core.h"
+#include "fuzz.h"
+
+enum {
+    MEM8WRITE8  = 0x00,
+    MEM8WRITE16,
+    MEM8WRITE32,
+    MEM8WRITE64,
+
+    MEM16WRITE8,
+    MEM16WRITE16,
+    MEM16WRITE32,
+    MEM16WRITE64,
+
+    MEM32WRITE8,
+    MEM32WRITE16,
+    MEM32WRITE32,
+    MEM32WRITE64,
+
+    MEM8READ8   = 0x10,
+    MEM8READ16,
+    MEM8READ32,
+    MEM8READ64,
+
+    MEM16READ8,
+    MEM16READ16,
+    MEM16READ32,
+    MEM16READ64,
+
+    MEM32READ8,
+    MEM32READ16,
+    MEM32READ32,
+    MEM32READ64,
+
+    OP_MASK     = 0x1f
+};
+
+/* We interpret the fuzzer input as a sequence of packets */
+typedef struct {
+    union {
+        uint8_t opcode;
+    };
+    union {
+        struct {
+            uint8_t addr;
+            union {
+                uint8_t val8[8];
+                uint16_t val16[4];
+                uint32_t val32[2];
+                uint64_t val64[1];
+            };
+        } QEMU_PACKED mem8;
+        struct {
+            uint16_t addr;
+            union {
+                uint8_t val8[8];
+                uint16_t val16[4];
+                uint32_t val32[2];
+                uint64_t val64[1];
+            };
+        } QEMU_PACKED mem16;
+        struct {
+            uint32_t addr;
+            union {
+                uint8_t val8[8];
+                uint16_t val16[4];
+                uint32_t val32[2];
+                uint64_t val64[1];
+            };
+        } QEMU_PACKED mem32;
+        /* mem64 not supported */
+    };
+} QEMU_PACKED pkt;
+
+static void memory_region_fuzz_one(QTestState *s,
+                                   DeviceState *dev,
+                                   MemoryRegion *mr,
+                                   const unsigned char *Data,
+                                   size_t Size,
+                                   bool do_not_reset)
+{
+    pkt *a;
+    size_t sz;
+    uint64_t addr;
+    uint64_t iosize;
+    uint64_t iomask;
+    uint64_t rdval;
+
+    /* TODO check .valid.min/max_access_size */
+
+    iosize = memory_region_size(mr);
+    if (iosize < 0x100) {
+        /* 8-bit address */
+        iosize = 0x100;
+    } else if (iosize < 0x10000) {
+        /* 16-bit address */
+        iosize = 0x10000;
+    } else {
+        /* 32-bit address */
+        assert(is_power_of_2(iosize));
+    }
+    iomask = iosize - 1;
+
+    if (!do_not_reset) {
+        device_cold_reset(dev);
+    }
+
+    /* process all packets */
+    while (Size != 0) {
+        a = (pkt *)Data;
+        switch (a->opcode & OP_MASK) {
+
+        /* ugly but efficient macros... */
+#define CASE_OP_READ(OP, OPTYPE, ADDRW, DATAW, MR) \
+        case OP##ADDRW##READ##DATAW:\
+            sz = sizeof(a->opcode)\
+               + sizeof(a->OPTYPE##ADDRW.addr)\
+               + sizeof(uint##DATAW##_t);\
+            if (Size < sz) {\
+                return;\
+            }\
+            addr = a->OPTYPE##ADDRW.addr & iomask;\
+            memory_region_dispatch_read(MR, addr, &rdval,\
+                                        size_memop(sizeof(uint##DATAW##_t)),\
+                                        MEMTXATTRS_UNSPECIFIED);\
+            break
+#define CASE_OP_WRITE(OP, OPTYPE, ADDRW, DATAW, MR) \
+        case OP##ADDRW##WRITE##DATAW:\
+            sz = sizeof(a->opcode)\
+               + sizeof(a->OPTYPE##ADDRW.addr)\
+               + sizeof(uint##DATAW##_t);\
+            if (Size < sz) {\
+                return;\
+            }\
+            addr = a->OPTYPE##ADDRW.addr & iomask;\
+            memory_region_dispatch_write(MR, addr,\
+                                         a->OPTYPE##ADDRW.val##DATAW[0],\
+                                         size_memop(sizeof(uint##DATAW##_t)),\
+                                         MEMTXATTRS_UNSPECIFIED);\
+            break
+
+        /* ... now the macro make more sense? */
+#define CASE_MEMOP(ADDRW, DATAW) \
+        CASE_OP_READ(MEM, mem, ADDRW, DATAW, mr);\
+        CASE_OP_WRITE(MEM, mem, ADDRW, DATAW, mr)
+        CASE_MEMOP(8, 8);
+        CASE_MEMOP(8, 16);
+        CASE_MEMOP(8, 32);
+        CASE_MEMOP(8, 64);
+        CASE_MEMOP(16, 8);
+        CASE_MEMOP(16, 16);
+        CASE_MEMOP(16, 32);
+        CASE_MEMOP(16, 64);
+        CASE_MEMOP(32, 8);
+        CASE_MEMOP(32, 16);
+        CASE_MEMOP(32, 32);
+        CASE_MEMOP(32, 64);
+        default:
+            return;
+        }
+        Size -= sz;
+        Data += sz;
+    }
+    flush_events(s);
+}
+
+/* Global context, ideally instead of QTestState *s */
+static struct {
+    Object *dev;
+    Object *mr;
+    bool do_not_reset;
+} g_ctx;
+
+/* FIXME get this from command line ... */
+const char *machine_name = "q35";
+const char *type_name = "e1000e";
+/* FIXME enumerate and select by index? */
+const char *mr_name = "e1000e-mmio[0]";
+
+static void memory_region_fuzz(QTestState *s,
+                               const unsigned char *Data,
+                               size_t Size)
+{
+
+    if (!g_ctx.dev || !g_ctx.mr) {
+        g_ctx.dev = object_resolve_path_type("", type_name, NULL);
+        assert(g_ctx.dev);
+        g_ctx.mr  = object_resolve_path_component(g_ctx.dev, mr_name);
+        assert(g_ctx.mr);
+    }
+    memory_region_fuzz_one(s, DEVICE(g_ctx.dev),
+                           MEMORY_REGION(g_ctx.mr),
+                           Data, Size,
+                           g_ctx.do_not_reset);
+}
+
+static const char *memory_region_fuzz_argv(FuzzTarget *t)
+{
+    return g_strdup_printf("%s -machine %s,accel=qtest "
+                           "-m 0 -display none -seed 42",
+                           t->name, machine_name);
+}
+
+static const FuzzTarget memory_region_fuzz_target = {
+    .name = "mr-fuzz",
+    .description = "Fuzz doing I/O access to a MemoryRegion",
+    .get_init_cmdline = memory_region_fuzz_argv,
+    .fuzz = memory_region_fuzz
+};
+
+static void register_memory_region_fuzz_targets(void)
+{
+    fuzz_add_target(&memory_region_fuzz_target);
+}
+
+fuzz_target_init(register_memory_region_fuzz_targets);
diff --git a/tests/qtest/fuzz/Makefile.include b/tests/qtest/fuzz/Makefile.include
index f259d866c9..dd2f6a4474 100644
--- a/tests/qtest/fuzz/Makefile.include
+++ b/tests/qtest/fuzz/Makefile.include
@@ -7,6 +7,7 @@ fuzz-obj-y += tests/qtest/fuzz/fork_fuzz.o
 fuzz-obj-y += tests/qtest/fuzz/qos_fuzz.o
 
 # Targets
+fuzz-obj-y += tests/qtest/fuzz/memory_region_fuzz.o
 fuzz-obj-$(CONFIG_PCI_I440FX) += tests/qtest/fuzz/i440fx_fuzz.o
 fuzz-obj-$(CONFIG_VIRTIO_NET) += tests/qtest/fuzz/virtio_net_fuzz.o
 fuzz-obj-$(CONFIG_SCSI) += tests/qtest/fuzz/virtio_scsi_fuzz.o
-- 
2.21.3


Re: [RFC PATCH] tests/qtest/fuzz: Add direct MemoryRegion fuzzer
Posted by no-reply@patchew.org 3 years, 11 months ago
Patchew URL: https://patchew.org/QEMU/20200525172704.32142-1-f4bug@amsat.org/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Message-id: 20200525172704.32142-1-f4bug@amsat.org
Subject: [RFC PATCH] tests/qtest/fuzz: Add direct MemoryRegion fuzzer
Type: series

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Switched to a new branch 'test'
91f34c3 tests/qtest/fuzz: Add direct MemoryRegion fuzzer

=== OUTPUT BEGIN ===
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#118: 
new file mode 100644

ERROR: Macros with multiple statements should be enclosed in a do - while loop
#250: FILE: tests/qtest/fuzz/memory_region_fuzz.c:128:
+#define CASE_OP_READ(OP, OPTYPE, ADDRW, DATAW, MR) \
+        case OP##ADDRW##READ##DATAW:\
+            sz = sizeof(a->opcode)\
+               + sizeof(a->OPTYPE##ADDRW.addr)\
+               + sizeof(uint##DATAW##_t);\
+            if (Size < sz) {\
+                return;\
+            }\
+            addr = a->OPTYPE##ADDRW.addr & iomask;\
+            memory_region_dispatch_read(MR, addr, &rdval,\
+                                        size_memop(sizeof(uint##DATAW##_t)),\
+                                        MEMTXATTRS_UNSPECIFIED);\
+            break

ERROR: space required after that close brace '}'
#257: FILE: tests/qtest/fuzz/memory_region_fuzz.c:135:
+            }\

ERROR: Macros with multiple statements should be enclosed in a do - while loop
#263: FILE: tests/qtest/fuzz/memory_region_fuzz.c:141:
+#define CASE_OP_WRITE(OP, OPTYPE, ADDRW, DATAW, MR) \
+        case OP##ADDRW##WRITE##DATAW:\
+            sz = sizeof(a->opcode)\
+               + sizeof(a->OPTYPE##ADDRW.addr)\
+               + sizeof(uint##DATAW##_t);\
+            if (Size < sz) {\
+                return;\
+            }\
+            addr = a->OPTYPE##ADDRW.addr & iomask;\
+            memory_region_dispatch_write(MR, addr,\
+                                         a->OPTYPE##ADDRW.val##DATAW[0],\
+                                         size_memop(sizeof(uint##DATAW##_t)),\
+                                         MEMTXATTRS_UNSPECIFIED);\
+            break

ERROR: space required after that close brace '}'
#270: FILE: tests/qtest/fuzz/memory_region_fuzz.c:148:
+            }\

ERROR: Macros with multiple statements should be enclosed in a do - while loop
#279: FILE: tests/qtest/fuzz/memory_region_fuzz.c:157:
+#define CASE_MEMOP(ADDRW, DATAW) \
+        CASE_OP_READ(MEM, mem, ADDRW, DATAW, mr);\
+        CASE_OP_WRITE(MEM, mem, ADDRW, DATAW, mr)

total: 5 errors, 1 warnings, 237 lines checked

Commit 91f34c31fe1d (tests/qtest/fuzz: Add direct MemoryRegion fuzzer) has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20200525172704.32142-1-f4bug@amsat.org/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com
Re: [RFC PATCH] tests/qtest/fuzz: Add direct MemoryRegion fuzzer
Posted by Alexander Bulekov 3 years, 11 months ago
On 200525 1927, Philippe Mathieu-Daudé wrote:
> This is a proof-of-concept fuzzer that resolve a MemoryRegion
> Object within the fuzzed QEMU process, then directly do
> memory accesses using memory_region_dispatch_read/write calls.
> 
> We avoid first the serialization of QTest strings, then the
> FlatView resolve and AddressSpace dispatch.

Hi Phil,
This seems like a great performance win. It looks like using
device_cold_reset is an enormous gain, alone, since you don't need to
reboot the entire VM or fork. This is 10 times faster than some of my
harnesses _without_ AddressSanitizer. It would be interesting to see if
there is some way of tracking "dirty" devices so that you could
automatically reset only the devices/ram that you touch.

I am close to sending out an RFC series which tries to address DMA
inputs by hooking functions used by devices to read data from memory
(dma_read_read, address_space_ld*,...) and populating that memory with
fuzzed data just in time for the device to read it. Some inputs lead to
the e1000e performing 100k+ small DMA reads, where skipping the overhead
from qtest parsing made a big difference.

> 
> Various TODO/FIXME left.
> In particular we need to be able to parse command line argv[]
> for machine/device/region_name.
> 
> This PoC will fuzz the e1000e network device on a Q35 machine,
> bypassing the PCI bus.
> 
> Run on a single i7-8650U core, it takes less than 1min to find:
> 
>  $ time \
>    i386-softmmu/qemu-fuzz-i386 --fuzz-target=mr-fuzz \
>    -detect_leaks=0 -max_len=64 /tmp/mycorpus
>  [...]
>  #995301 REDUCE cov: 1042 ft: 3262 corp: 622/13348b lim: 64 exec/s: 23697 rss: 349Mb L: 10/64 MS: 2 CopyPart-EraseBytes-
>  #999227 REDUCE cov: 1042 ft: 3262 corp: 622/13345b lim: 64 exec/s: 23791 rss: 350Mb L: 40/64 MS: 1 EraseBytes-
>  #1024575        REDUCE cov: 1043 ft: 3272 corp: 629/13702b lim: 64 exec/s: 23827 rss: 353Mb L: 25/64 MS: 1 EraseBytes-
>  #1025436        REDUCE cov: 1043 ft: 3272 corp: 629/13699b lim: 64 exec/s: 23847 rss: 353Mb L: 39/64 MS: 1 EraseBytes-
>  qemu: hardware error: e1000e: PSRCTL.BSIZE0 cannot be zero
>  CPU #0:
>  EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000663
>  ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
>  EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
>  ES =0000 00000000 0000ffff 00009300
>  CS =f000 ffff0000 0000ffff 00009b00
>  SS =0000 00000000 0000ffff 00009300
>  DS =0000 00000000 0000ffff 00009300
>  FS =0000 00000000 0000ffff 00009300
>  GS =0000 00000000 0000ffff 00009300
>  LDT=0000 00000000 0000ffff 00008200
>  TR =0000 00000000 0000ffff 00008b00
>  GDT=     00000000 0000ffff
>  IDT=     00000000 0000ffff
>  CR0=60000010 CR2=00000000 CR3=00000000 CR4=00000000
>  DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
>  DR6=ffff0ff0 DR7=00000400
>  EFER=0000000000000000
>  FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
>  FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
>  FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
>  FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
>  FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
>  XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
>  XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
>  XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
>  XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
>  ==31659== ERROR: libFuzzer: deadly signal
>      #0 0x557a8f6f0430 in __sanitizer_print_stack_trace (i386-softmmu/qemu-fuzz-i386+0xcb1430)
>      #1 0x557a8f5ff421 in fuzzer::PrintStackTrace() (i386-softmmu/qemu-fuzz-i386+0xbc0421)
>      #2 0x557a8f5dfcaf in fuzzer::Fuzzer::CrashCallback() (i386-softmmu/qemu-fuzz-i386+0xba0caf)
>      #3 0x557a8f5dfd03 in fuzzer::Fuzzer::StaticCrashSignalCallback() (i386-softmmu/qemu-fuzz-i386+0xba0d03)
>      #4 0x7ff5aea7fc6f  (/lib64/libpthread.so.0+0x12c6f)
>      #5 0x7ff5ae8bee34 in __GI_raise (/lib64/libc.so.6+0x37e34)
>      #6 0x7ff5ae8a9894 in __GI_abort (/lib64/libc.so.6+0x22894)
>      #7 0x557a8f8bb5dd in hw_error (i386-softmmu/qemu-fuzz-i386+0xe7c5dd)
>      #8 0x557a90425f42 in e1000e_set_psrctl (i386-softmmu/qemu-fuzz-i386+0x19e6f42)
>      #9 0x557a904144df in e1000e_core_write (i386-softmmu/qemu-fuzz-i386+0x19d54df)
>      #10 0x557a9040c796 in e1000e_mmio_write (i386-softmmu/qemu-fuzz-i386+0x19cd796)
>      #11 0x557a8f8fbc67 in memory_region_write_accessor (i386-softmmu/qemu-fuzz-i386+0xebcc67)
>      #12 0x557a8f8fb7d3 in access_with_adjusted_size (i386-softmmu/qemu-fuzz-i386+0xebc7d3)
>      #13 0x557a8f8fa374 in memory_region_dispatch_write (i386-softmmu/qemu-fuzz-i386+0xebb374)
>      #14 0x557a90ef4e5f in memory_region_fuzz_one (i386-softmmu/qemu-fuzz-i386+0x24b5e5f)
>      #15 0x557a90eef1ae in memory_region_fuzz (i386-softmmu/qemu-fuzz-i386+0x24b01ae)
>      #16 0x557a90eed0d0 in LLVMFuzzerTestOneInput (i386-softmmu/qemu-fuzz-i386+0x24ae0d0)
>      #17 0x557a8f5e01b8 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (i386-softmmu/qemu-fuzz-i386+0xba11b8)
>      #18 0x557a8f5e358e in fuzzer::Fuzzer::MutateAndTestOne() (i386-softmmu/qemu-fuzz-i386+0xba458e)
>      #19 0x557a8f5e418f in fuzzer::Fuzzer::Loop(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, fuzzer::fuzzer_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&) (i386-softmmu/qemu-fuzz-i386+0xba518f)
>      #20 0x557a8f5d6f91 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (i386-softmmu/qemu-fuzz-i386+0xb97f91)
>      #21 0x557a8f5cdc76 in main (i386-softmmu/qemu-fuzz-i386+0xb8ec76)
>      #22 0x7ff5ae8aaf42 in __libc_start_main (/lib64/libc.so.6+0x23f42)
>      #23 0x557a8f5cdccd in _start (i386-softmmu/qemu-fuzz-i386+0xb8eccd)
> 
>  NOTE: libFuzzer has rudimentary signal handlers.
>        Combine libFuzzer with AddressSanitizer or similar for better crash reports.
>  SUMMARY: libFuzzer: deadly signal
>  MS: 1 CopyPart-; base unit: 5ce018d6638e560881cffa145fedad441d5ef356
>  0x0,0x46,0x0,0x26,0x0,0x1,0xe2,0x67,0x8,0x0,0x0,0x0,0x74,0x27,0x70,0x21,0x16,0xe1,0xd8,0x0,0x90,0x59,0x1e,0x0,0x27,0x70,0x21,0x0,0x74,0x27,0x70,0x21,0x16,0xe1,0xd8,0x0,0x90,0x59,0x1e,0x0,0x27,0x70,0x21,0x2b,0xe1,0xd8,0x0,0x90,0xd6,0x1e,0x0,0x27,0xa,0x1,0xaa,0xc9,0x1,0xb0,0xc0,0x0,0x0,0xda,0x56,0x68,
>  \x00F\x00&\x00\x01\xe2g\x08\x00\x00\x00t'p!\x16\xe1\xd8\x00\x90Y\x1e\x00'p!\x00t'p!\x16\xe1\xd8\x00\x90Y\x1e\x00'p!+\xe1\xd8\x00\x90\xd6\x1e\x00'\x0a\x01\xaa\xc9\x01\xb0\xc0\x00\x00\xdaVh
>  artifact_prefix='./'; Test unit written to ./crash-fafe2e3e3412d9a722b06f43bd57c3c43fe9ae99
>  Base64: AEYAJgAB4mcIAAAAdCdwIRbh2ACQWR4AJ3AhAHQncCEW4dgAkFkeACdwISvh2ACQ1h4AJwoBqskBsMAAANpWaA==
>  real    0m44.191s
>  user    0m43.696s
>  sys     0m0.624s
> 
> libFuzzer report about 24K executions of resetting device and
> processing packets in a 64 bytes input payload.
> 
> Inspired-by: Alexander Bulekov <alxndr@bu.edu>
> Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
> ---
>  tests/qtest/fuzz/memory_region_fuzz.c | 230 ++++++++++++++++++++++++++
>  tests/qtest/fuzz/Makefile.include     |   1 +
>  2 files changed, 231 insertions(+)
>  create mode 100644 tests/qtest/fuzz/memory_region_fuzz.c
> 
> diff --git a/tests/qtest/fuzz/memory_region_fuzz.c b/tests/qtest/fuzz/memory_region_fuzz.c
> new file mode 100644
> index 0000000000..4e542d1772
> --- /dev/null
> +++ b/tests/qtest/fuzz/memory_region_fuzz.c
> @@ -0,0 +1,230 @@
> +/*
> + * MMIO Fuzzing Target
> + *
> + * Resolve MemoryRegion Object in process, then directly
> + * access it using memory_region_dispatch_read/write calls.
> + *
> + * Copyright 2020 Philippe Mathieu-Daudé <f4bug@amsat.org>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "exec/memory.h"
> +#include "hw/qdev-core.h"
> +#include "fuzz.h"
> +
> +enum {
> +    MEM8WRITE8  = 0x00,
> +    MEM8WRITE16,
> +    MEM8WRITE32,
> +    MEM8WRITE64,
> +
> +    MEM16WRITE8,
> +    MEM16WRITE16,
> +    MEM16WRITE32,
> +    MEM16WRITE64,
> +
> +    MEM32WRITE8,
> +    MEM32WRITE16,
> +    MEM32WRITE32,
> +    MEM32WRITE64,
> +
> +    MEM8READ8   = 0x10,
> +    MEM8READ16,
> +    MEM8READ32,
> +    MEM8READ64,
> +
> +    MEM16READ8,
> +    MEM16READ16,
> +    MEM16READ32,
> +    MEM16READ64,
> +
> +    MEM32READ8,
> +    MEM32READ16,
> +    MEM32READ32,
> +    MEM32READ64,
> +
> +    OP_MASK     = 0x1f
> +};
> +
> +/* We interpret the fuzzer input as a sequence of packets */
> +typedef struct {
> +    union {
> +        uint8_t opcode;
> +    };
> +    union {
> +        struct {
> +            uint8_t addr;
> +            union {
> +                uint8_t val8[8];
> +                uint16_t val16[4];
> +                uint32_t val32[2];
> +                uint64_t val64[1];
> +            };
> +        } QEMU_PACKED mem8;
> +        struct {
> +            uint16_t addr;
> +            union {
> +                uint8_t val8[8];
> +                uint16_t val16[4];
> +                uint32_t val32[2];
> +                uint64_t val64[1];
> +            };
> +        } QEMU_PACKED mem16;
> +        struct {
> +            uint32_t addr;
> +            union {
> +                uint8_t val8[8];
> +                uint16_t val16[4];
> +                uint32_t val32[2];
> +                uint64_t val64[1];
> +            };
> +        } QEMU_PACKED mem32;
> +        /* mem64 not supported */
> +    };
> +} QEMU_PACKED pkt;
> +
> +static void memory_region_fuzz_one(QTestState *s,
> +                                   DeviceState *dev,
> +                                   MemoryRegion *mr,
> +                                   const unsigned char *Data,
> +                                   size_t Size,
> +                                   bool do_not_reset)
> +{
> +    pkt *a;
> +    size_t sz;
> +    uint64_t addr;
> +    uint64_t iosize;
> +    uint64_t iomask;
> +    uint64_t rdval;
> +
> +    /* TODO check .valid.min/max_access_size */
> +
> +    iosize = memory_region_size(mr);
> +    if (iosize < 0x100) {
> +        /* 8-bit address */
> +        iosize = 0x100;
> +    } else if (iosize < 0x10000) {
> +        /* 16-bit address */
> +        iosize = 0x10000;
> +    } else {
> +        /* 32-bit address */
> +        assert(is_power_of_2(iosize));
> +    }
> +    iomask = iosize - 1;
> +
> +    if (!do_not_reset) {
> +        device_cold_reset(dev);
> +    }
> +
> +    /* process all packets */
> +    while (Size != 0) {
> +        a = (pkt *)Data;
> +        switch (a->opcode & OP_MASK) {
> +
> +        /* ugly but efficient macros... */
> +#define CASE_OP_READ(OP, OPTYPE, ADDRW, DATAW, MR) \
> +        case OP##ADDRW##READ##DATAW:\
> +            sz = sizeof(a->opcode)\
> +               + sizeof(a->OPTYPE##ADDRW.addr)\
> +               + sizeof(uint##DATAW##_t);\
> +            if (Size < sz) {\
> +                return;\
> +            }\
> +            addr = a->OPTYPE##ADDRW.addr & iomask;\
> +            memory_region_dispatch_read(MR, addr, &rdval,\
> +                                        size_memop(sizeof(uint##DATAW##_t)),\
> +                                        MEMTXATTRS_UNSPECIFIED);\
> +            break
> +#define CASE_OP_WRITE(OP, OPTYPE, ADDRW, DATAW, MR) \
> +        case OP##ADDRW##WRITE##DATAW:\
> +            sz = sizeof(a->opcode)\
> +               + sizeof(a->OPTYPE##ADDRW.addr)\
> +               + sizeof(uint##DATAW##_t);\
> +            if (Size < sz) {\
> +                return;\
> +            }\
> +            addr = a->OPTYPE##ADDRW.addr & iomask;\
> +            memory_region_dispatch_write(MR, addr,\
> +                                         a->OPTYPE##ADDRW.val##DATAW[0],\
> +                                         size_memop(sizeof(uint##DATAW##_t)),\
> +                                         MEMTXATTRS_UNSPECIFIED);\
> +            break
> +
> +        /* ... now the macro make more sense? */
> +#define CASE_MEMOP(ADDRW, DATAW) \
> +        CASE_OP_READ(MEM, mem, ADDRW, DATAW, mr);\
> +        CASE_OP_WRITE(MEM, mem, ADDRW, DATAW, mr)
> +        CASE_MEMOP(8, 8);
> +        CASE_MEMOP(8, 16);
> +        CASE_MEMOP(8, 32);
> +        CASE_MEMOP(8, 64);
> +        CASE_MEMOP(16, 8);
> +        CASE_MEMOP(16, 16);
> +        CASE_MEMOP(16, 32);
> +        CASE_MEMOP(16, 64);
> +        CASE_MEMOP(32, 8);
> +        CASE_MEMOP(32, 16);
> +        CASE_MEMOP(32, 32);
> +        CASE_MEMOP(32, 64);
> +        default:
> +            return;
> +        }
> +        Size -= sz;
> +        Data += sz;
> +    }
> +    flush_events(s);
> +}
> +
> +/* Global context, ideally instead of QTestState *s */
> +static struct {
> +    Object *dev;
> +    Object *mr;
> +    bool do_not_reset;
> +} g_ctx;
> +
> +/* FIXME get this from command line ... */
> +const char *machine_name = "q35";
> +const char *type_name = "e1000e";
> +/* FIXME enumerate and select by index? */
> +const char *mr_name = "e1000e-mmio[0]";
> +
> +static void memory_region_fuzz(QTestState *s,
> +                               const unsigned char *Data,
> +                               size_t Size)
> +{
> +
> +    if (!g_ctx.dev || !g_ctx.mr) {
> +        g_ctx.dev = object_resolve_path_type("", type_name, NULL);
> +        assert(g_ctx.dev);
> +        g_ctx.mr  = object_resolve_path_component(g_ctx.dev, mr_name);
> +        assert(g_ctx.mr);
> +    }

Should the same fuzzer be used for all regions associated with the
device (e1000e-io, e1000e-msix, pci registers)?

Do you think there should be an option to translate these ops to qtest
commands to build reproducers?

It seems that memory-region-fuzz will already do a good job for many
devices. With a main_loop(and maybe clock_step) calls, it can even cover
devices with bottom-halves. Theres a possibility that the resetting code
for some devices isn't complete, but the performance gains seem to
overshadow this.

-Alex