All of lore.kernel.org
 help / color / mirror / Atom feed
From: Alexander Bulekov <alxndr@bu.edu>
To: qemu-devel@nongnu.org
Cc: Laurent Vivier <lvivier@redhat.com>,
	thuth@redhat.com, Alexander Bulekov <alxndr@bu.edu>,
	f4bug@amsat.org, darren.kenny@oracle.com, bsd@redhat.com,
	dstepanov.src@gmail.com, stefanha@redhat.com,
	andrew@coatesdev.com, Paolo Bonzini <pbonzini@redhat.com>
Subject: [PATCH 04/12] fuzz: Add DMA support to the generic-fuzzer
Date: Wed, 22 Jul 2020 23:39:25 -0400	[thread overview]
Message-ID: <20200723033933.21883-5-alxndr@bu.edu> (raw)
In-Reply-To: <20200723033933.21883-1-alxndr@bu.edu>

When a virtual-device tries to access some buffer in memory over DMA, we
add call-backs into the fuzzer(next commit). The fuzzer checks verifies
that the DMA request maps to a physical RAM address and fills the memory
with fuzzer-provided data. The patterns that we use to fill this memory
are specified using add_dma_pattern and clear_dma_patterns operations.

Signed-off-by: Alexander Bulekov <alxndr@bu.edu>
---
 tests/qtest/fuzz/general_fuzz.c | 177 ++++++++++++++++++++++++++++++++
 1 file changed, 177 insertions(+)

diff --git a/tests/qtest/fuzz/general_fuzz.c b/tests/qtest/fuzz/general_fuzz.c
index e715b77d59..4b6967c5d2 100644
--- a/tests/qtest/fuzz/general_fuzz.c
+++ b/tests/qtest/fuzz/general_fuzz.c
@@ -27,6 +27,7 @@
 #include "tests/qtest/libqos/pci.h"
 #include "tests/qtest/libqos/pci-pc.h"
 #include "hw/pci/pci.h"
+#include "hw/boards.h"
 
 /*
  * CMD_SEP is a random 32-bit value used to separate "commands" in the fuzz
@@ -34,6 +35,7 @@
  */
 #define CMD_SEP "\x84\x05\x5C\x5E"
 #define DEFAULT_TIMEOUT_US 100000
+#define MAX_DMA_FILL_SIZE 0x10000
 
 #define PCI_HOST_BRIDGE_CFG 0xcf8
 #define PCI_HOST_BRIDGE_DATA 0xcfc
@@ -44,6 +46,24 @@ typedef struct {
 } address_range;
 
 static useconds_t timeout = 100000;
+/*
+ * A pattern used to populate a DMA region or perform a memwrite. This is
+ * useful for e.g. populating tables of unique addresses.
+ * Example {.index = 1; .stride = 2; .len = 3; .data = "\x00\x01\x02"}
+ * Renders as: 00 01 02   00 03 03   00 05 03   00 07 03 ...
+ */
+typedef struct {
+    uint8_t index;      /* Index of a byte to increment by stride */
+    uint8_t stride;     /* Increment each index'th byte by this amount */
+    size_t len;
+    const uint8_t *data;
+} pattern;
+
+/* Avoid filling the same DMA region between MMIO/PIO commands ? */
+static bool avoid_double_fetches;
+
+static QTestState *qts_global; /* Need a global for the DMA callback */
+
 /*
  * List of memory regions that are children of QOM objects specified by the
  * user for fuzzing.
@@ -51,6 +71,122 @@ static useconds_t timeout = 100000;
 static GPtrArray *fuzzable_memoryregions;
 static GPtrArray *fuzzable_pci_devices;
 
+/*
+ * List of dma regions populated since the last fuzzing command. Used to ensure
+ * that we only write to each DMA address once, to avoid race conditions when
+ * building reproducers.
+ */
+static GArray *dma_regions;
+
+static GArray *dma_patterns;
+int dma_pattern_index;
+
+/*
+ * Allocate a block of memory and populate it with a pattern.
+ */
+static void *pattern_alloc(pattern p, size_t len)
+{
+    int i;
+    uint8_t *buf = g_malloc(len);
+    uint8_t sum = 0;
+
+    for (i = 0; i < len; ++i) {
+        buf[i] = p.data[i % p.len];
+        if ((i % p.len) == p.index) {
+            buf[i] += sum;
+            sum += p.stride;
+        }
+    }
+    return buf;
+}
+
+/*
+ * Call-back for functions that perform DMA reads from guest memory. Confirm
+ * that the region has not already been populated since the last loop in
+ * general_fuzz(), avoiding potential race-conditions, which we don't have
+ * a good way for reproducing right now.
+ */
+void fuzz_dma_read_cb(size_t addr, size_t len, MemoryRegion *mr, bool is_write)
+{
+    /* Are we in the general-fuzzer or are we using another fuzz-target? */
+    if (!qts_global) {
+        return;
+    }
+
+    /*
+     * If the device is trying to read from a ROM, exit early. We do not want
+     * to fuzz devices using data that we have no control over.
+     */
+    if (mr->readonly) {
+        _Exit(0);
+    }
+
+    /*
+     * Return immediately if:
+     * - We have no DMA patterns defined
+     * - The length of the DMA read request is zero
+     * - The DMA read is hitting an MR other than the machine's main RAM
+     * - The DMA request is not a read (what happens for a address_space_map
+     *   with is_write=True? Can the device use the same pointer to do reads?)
+     * - The DMA request hits past the bounds of our RAM
+     */
+    if (dma_patterns->len == 0
+        || len == 0
+        || mr != MACHINE(qdev_get_machine())->ram
+        || is_write
+        || addr > current_machine->ram_size) {
+        return;
+    }
+
+    /*
+     * If we overlap with any existing dma_regions, split the range and only
+     * populate the non-overlapping parts.
+     */
+    for (int i = 0; i < dma_regions->len && !avoid_double_fetches; ++i) {
+        address_range region = g_array_index(dma_regions, address_range, i);
+        if (addr < region.addr + region.len && addr + len > region.addr) {
+            if (addr < region.addr) {
+                fuzz_dma_read_cb(addr, region.addr - addr, mr, is_write);
+            }
+            if (addr + len > region.addr + region.len) {
+                fuzz_dma_read_cb(region.addr + region.len,
+                        addr + len - (region.addr + region.len), mr, is_write);
+            }
+            return;
+        }
+    }
+
+    /* Cap the length of the DMA access to something reasonable */
+    len = MIN(len, MAX_DMA_FILL_SIZE);
+
+    address_range ar = {addr, len};
+    g_array_append_val(dma_regions, ar);
+    pattern p = g_array_index(dma_patterns, pattern, dma_pattern_index);
+    void *buf = pattern_alloc(p, ar.len);
+    if (getenv("QTEST_LOG")) {
+        /*
+         * With QTEST_LOG, use a normal, slow QTest memwrite. Prefix the log
+         * that will be written by qtest.c with a DMA tag, so we can reorder
+         * the resulting QTest trace so the DMA fills precede the last PIO/MMIO
+         * command.
+         */
+        fprintf(stderr, "[DMA] ");
+        fflush(stderr);
+        qtest_memwrite(qts_global, ar.addr, buf, ar.len);
+    } else {
+       /*
+        * Populate the region using address_space_write_rom to avoid writing to
+        * any IO MemoryRegions
+        */
+        address_space_write_rom(first_cpu->as, ar.addr, MEMTXATTRS_UNSPECIFIED,
+                buf, ar.len);
+    }
+    free(buf);
+
+    /* Increment the index of the pattern for the next DMA access */
+    dma_pattern_index = (dma_pattern_index + 1) % dma_patterns->len;
+}
+
 /*
  * Here we want to convert a fuzzer-provided [io-region-index, offset] to
  * a physical address. To do this, we iterate over all of the matched
@@ -334,6 +470,35 @@ static void op_pci_write(QTestState *s, const unsigned char * data, size_t len)
     }
 }
 
+static void op_add_dma_pattern(QTestState *s,
+                               const unsigned char *data, size_t len)
+{
+    struct {
+        /*
+         * index and stride can be used to increment the index-th byte of the
+         * pattern by the value stride, for each loop of the pattern.
+         */
+        uint8_t index;
+        uint8_t stride;
+    } a;
+
+    if (len < sizeof(a) + 1) {
+        return;
+    }
+    memcpy(&a, data, sizeof(a));
+    pattern p = {a.index, a.stride, len - sizeof(a), data + sizeof(a)};
+    p.index = a.index % p.len;
+    g_array_append_val(dma_patterns, p);
+    return;
+}
+
+static void op_clear_dma_patterns(QTestState *s,
+                                  const unsigned char *data, size_t len)
+{
+    g_array_set_size(dma_patterns, 0);
+    dma_pattern_index = 0;
+}
+
 static void op_clock_step(QTestState *s, const unsigned char *data, size_t len)
 {
     qtest_clock_step_next(s);
@@ -380,6 +545,8 @@ static void general_fuzz(QTestState *s, const unsigned char *Data, size_t Size)
         op_write,
         op_pci_read,
         op_pci_write,
+        op_add_dma_pattern,
+        op_clear_dma_patterns,
         op_clock_step,
     };
     const unsigned char *cmd = Data;
@@ -433,6 +600,9 @@ static void usage(void)
     printf("QEMU_FUZZ_ARGS= the command line arguments passed to qemu\n");
     printf("QEMU_FUZZ_OBJECTS= "
             "a space separated list of QOM type names for objects to fuzz\n");
+    printf("Optionally: QEMU_AVOID_DOUBLE_FETCH= "
+            "Try to avoid racy DMA double fetch bugs? %d by default\n",
+            avoid_double_fetches);
     printf("Optionally: QEMU_FUZZ_TIMEOUT= Specify a custom timeout (us). "
             "0 to disable. %d by default\n", timeout);
     exit(0);
@@ -502,9 +672,16 @@ static void general_pre_fuzz(QTestState *s)
     if (!getenv("QEMU_FUZZ_OBJECTS")) {
         usage();
     }
+    if (getenv("QEMU_AVOID_DOUBLE_FETCH")) {
+        avoid_double_fetches = 1;
+    }
     if (getenv("QEMU_FUZZ_TIMEOUT")) {
         timeout = g_ascii_strtoll(getenv("QEMU_FUZZ_TIMEOUT"), NULL, 0);
     }
+    qts_global = s;
+
+    dma_regions = g_array_new(false, false, sizeof(address_range));
+    dma_patterns = g_array_new(false, false, sizeof(pattern));
 
     fuzzable_memoryregions = g_ptr_array_new();
     fuzzable_pci_devices = g_ptr_array_new();
-- 
2.27.0



  parent reply	other threads:[~2020-07-23  3:44 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-07-23  3:39 [PATCH 00/12] Add a General Virtual Device Fuzzer Alexander Bulekov
2020-07-23  3:39 ` [PATCH 01/12] fuzz: Change the way we write qtest log to stderr Alexander Bulekov
2020-07-23 13:15   ` Darren Kenny
2020-07-23  3:39 ` [PATCH 02/12] fuzz: Add general virtual-device fuzzer Alexander Bulekov
2020-07-24  1:35   ` Alexander Bulekov
2020-07-23  3:39 ` [PATCH 03/12] fuzz: Add PCI features to the general fuzzer Alexander Bulekov
2020-07-23  3:39 ` Alexander Bulekov [this message]
2020-07-23  3:39 ` [PATCH 05/12] fuzz: Declare DMA Read callback function Alexander Bulekov
2020-07-23  3:39 ` [PATCH 06/12] fuzz: Add fuzzer callbacks to DMA-read functions Alexander Bulekov
2020-07-23  3:39 ` [PATCH 07/12] scripts/oss-fuzz: Add wrapper program for generic fuzzer Alexander Bulekov
2020-07-23  3:39 ` [PATCH 08/12] scripts/oss-fuzz: Add general-fuzzer build script Alexander Bulekov
2020-07-23  3:39 ` [PATCH 09/12] scripts/oss-fuzz: Add general-fuzzer configs for oss-fuzz Alexander Bulekov
2020-07-23  3:39 ` [PATCH 10/12] scripts/oss-fuzz: build the general-fuzzer configs Alexander Bulekov
2020-07-23  3:39 ` [PATCH 11/12] scripts/oss-fuzz: Add script to reorder a general-fuzzer trace Alexander Bulekov
2020-07-23  3:39 ` [PATCH 12/12] scripts/oss-fuzz: Add crash trace minimization script Alexander Bulekov
2020-08-26 11:10 ` [PATCH 00/12] Add a General Virtual Device Fuzzer Dima Stepanov
2020-09-21  2:35   ` Alexander Bulekov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200723033933.21883-5-alxndr@bu.edu \
    --to=alxndr@bu.edu \
    --cc=andrew@coatesdev.com \
    --cc=bsd@redhat.com \
    --cc=darren.kenny@oracle.com \
    --cc=dstepanov.src@gmail.com \
    --cc=f4bug@amsat.org \
    --cc=lvivier@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    --cc=thuth@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.