hw/misc: Add a virtual PCILeech device

Zero Tang posted 1 patch 3 months, 3 weeks ago
There is a newer version of this series
3 files changed, 289 insertions(+)
hw/misc: Add a virtual PCILeech device
Posted by Zero Tang 3 months, 3 weeks ago
This virtual PCI device is intended for security researchers to attack the
guest via DMA.
This is my first-time contribution and I hope I am doing things correctly.

Signed-off-by: Zero Tang <zero.tangptr@gmail.com>
---
 hw/misc/Kconfig     |   5 +
 hw/misc/meson.build |   1 +
 hw/misc/pcileech.c  | 283
++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 289 insertions(+)

diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1e08785b83..0bdbe26f26 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -30,6 +30,11 @@ config EDU
     default y if TEST_DEVICES
     depends on PCI && MSI_NONBROKEN

+config PCI_LEECH
+    bool
+    default y
+    depends on PCI
+
 config PCA9552
     bool
     depends on I2C
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 2ca8717be2..7178eafa75 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -1,5 +1,6 @@
 system_ss.add(when: 'CONFIG_APPLESMC', if_true: files('applesmc.c'))
 system_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
+system_ss.add(when: 'CONFIG_PCI_LEECH', if_true: files('pcileech.c'))
 system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
 system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
 system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
diff --git a/hw/misc/pcileech.c b/hw/misc/pcileech.c
new file mode 100644
index 0000000000..f6782d055e
--- /dev/null
+++ b/hw/misc/pcileech.c
@@ -0,0 +1,283 @@
+/*
+ * QEMU Virtual PCILeech Device
+ *
+ * Copyright (c) 2024 Zero Tang
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
"Software"),
+ * to deal in the Software without restriction, including without
limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/pci/pci.h"
+#include "hw/hw.h"
+#include "hw/pci/msi.h"
+#include "qemu/timer.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "qom/object.h"
+#include "qemu/main-loop.h" /* iothread mutex */
+#include "qemu/module.h"
+#include "qapi/visitor.h"
+
+#define TYPE_PCILEECH_DEVICE "pcileech"
+
+struct LeechRequestHeader {
+    uint8_t endianness; /* 0 - Little, 1 - Big */
+    uint8_t command;    /* 0 - Read, 1 - Write */
+    uint8_t reserved[6];
+    /* Variable Endianness */
+    uint64_t address;
+    uint64_t length;
+};
+
+struct LeechResponseHeader {
+    uint8_t endianness; /* 0 - Little, 1 - Big */
+    uint8_t reserved[3];
+    MemTxResult result;
+    uint64_t length;    /* Indicates length of data followed by header */
+};
+
+static_assert(sizeof(struct LeechRequestHeader) == 24);
+static_assert(sizeof(struct LeechResponseHeader) == 16);
+
+struct PciLeechState {
+    /* Internal State */
+    PCIDevice device;
+    QemuThread thread;
+    QemuMutex mutex;
+    bool endianness;
+    bool stopping;
+    /* Communication */
+    char *host;
+    uint16_t port;
+    int sockfd;
+};
+
+typedef struct LeechRequestHeader LeechRequestHeader;
+typedef struct PciLeechState PciLeechState;
+
+DECLARE_INSTANCE_CHECKER(PciLeechState, PCILEECH, TYPE_PCILEECH_DEVICE)
+
+static void pci_leech_process_write_request(PciLeechState *state,
+                                            LeechRequestHeader *request,
+                                            int incoming)
+{
+    char buff[1024];
+    for (uint64_t i = 0; i < request->length; i += sizeof(buff)) {
+        struct LeechResponseHeader response = { 0 };
+        char* response_buffer = (char *)&response;
+        const uint64_t writelen = (request->length - i) <= sizeof(buff) ?
+                                         (request->length - i) :
sizeof(buff);
+        ssize_t recvlen = 0, sendlen = 0;
+        while (recvlen < writelen) {
+            recvlen += recv(incoming, &buff[recvlen], writelen - recvlen,
0);
+        }
+        response.endianness = state->endianness;
+        response.result = pci_dma_write(&state->device, request->address +
i,
+                                                            buff,
writelen);
+        printf("PCILeech: Write MemTxResult: 0x%X\n", response.result);
+        response.length = 0;
+        while (sendlen < sizeof(struct LeechResponseHeader)) {
+            sendlen += send(incoming, &response_buffer[sendlen],
+                            sizeof(struct LeechResponseHeader) - sendlen,
0);
+        }
+    }
+}
+
+static void pci_leech_process_read_request(PciLeechState *state,
+                                            LeechRequestHeader *request,
+                                            int incoming)
+{
+    char buff[1024];
+    for (uint64_t i = 0; i < request->length; i += sizeof(buff)) {
+        struct LeechResponseHeader response = { 0 };
+        char* response_buffer = (char *)&response;
+        const uint64_t readlen = (request->length - i) <= sizeof(buff) ?
+                                    (request->length - i) : sizeof(buff);
+        ssize_t sendlen = 0;
+        response.endianness = state->endianness;
+        response.result = pci_dma_read(&state->device, request->address +
i,
+                                                            buff, readlen);
+        printf("PCILeech: Read MemTxResult: 0x%X\n", response.result);
+        response.length = (request->endianness != state->endianness) ?
+                                            bswap64(readlen) : readlen;
+        while (sendlen < sizeof(struct LeechResponseHeader)) {
+            sendlen += send(incoming, &response_buffer[sendlen],
+                            sizeof(struct LeechResponseHeader) - sendlen,
0);
+        }
+        sendlen = 0;
+        while (sendlen < readlen) {
+            sendlen += send(incoming, &buff[sendlen], readlen - sendlen,
0);
+        }
+    }
+}
+
+static void *pci_leech_worker_thread(void *opaque)
+{
+    PciLeechState *state = PCILEECH(opaque);
+    while (1) {
+        LeechRequestHeader request;
+        char *request_buffer = (char *)&request;
+        ssize_t received = 0;
+        int incoming;
+        struct sockaddr address;
+        socklen_t addrlen;
+        /* Check if we are stopping. */
+        qemu_mutex_lock(&state->mutex);
+        if (state->stopping) {
+            qemu_mutex_unlock(&state->mutex);
+            break;
+        }
+        qemu_mutex_unlock(&state->mutex);
+        /* Accept PCILeech requests. */
+        incoming = accept(state->sockfd, &address, &addrlen);
+        if (incoming < 0) {
+            puts("WARNING: Failed to accept socket for PCILeech! Skipping "
+                 "Request...\n");
+            continue;
+        }
+        /* Get PCILeech requests. */
+        while (received < sizeof(LeechRequestHeader)) {
+            received += recv(incoming, &request_buffer[received],
+                            sizeof(LeechRequestHeader) - received, 0);
+        }
+        /* Swap endianness */
+        if (request.endianness != state->endianness) {
+            request.address = bswap64(request.address);
+            request.length = bswap64(request.length);
+        }
+        /* Process PCILeech requests. */
+        qemu_mutex_lock(&state->mutex);
+        if (request.command) {
+            pci_leech_process_write_request(state, &request, incoming);
+        } else {
+            pci_leech_process_read_request(state, &request, incoming);
+        }
+        qemu_mutex_unlock(&state->mutex);
+        close(incoming);
+    }
+    return NULL;
+}
+
+static void pci_leech_realize(PCIDevice *pdev, Error **errp)
+{
+    PciLeechState *state = PCILEECH(pdev);
+    struct sockaddr_in sock_addr;
+    char host_ip[16];
+    struct hostent *he = gethostbyname(state->host);
+    if (he == NULL) {
+        puts("gethostbyname failed!");
+        exit(EXIT_FAILURE);
+    }
+    /* Initialize the socket for PCILeech.*/
+    state->sockfd = socket(AF_INET, SOCK_STREAM, 0);
+    if (state->sockfd < 0) {
+        puts("Failed to initialize socket for PCILeech!");
+        exit(EXIT_FAILURE);
+    }
+    sock_addr.sin_family = AF_INET;
+    sock_addr.sin_addr = *(struct in_addr *)he->h_addr;
+    sock_addr.sin_port = htons(state->port);
+    inet_ntop(AF_INET, &sock_addr.sin_addr, host_ip, sizeof(host_ip));
+    if (bind(state->sockfd, (struct sockaddr *)&sock_addr,
sizeof(sock_addr))
+                                                                    < 0) {
+        puts("Failed to bind socket for PCILeech!");
+        close(state->sockfd);
+        exit(EXIT_FAILURE);
+    }
+    if (listen(state->sockfd, 10) < 0) {
+        puts("Failed to listen to socket for PCILeech!");
+        close(state->sockfd);
+        exit(EXIT_FAILURE);
+    }
+    printf("INFO: PCILeech is listening on %s:%u...\n", host_ip,
state->port);
+    /* Initialize the thread for PCILeech. */
+    qemu_mutex_init(&state->mutex);
+    qemu_thread_create(&state->thread, "pcileech", pci_leech_worker_thread,
+                                            state, QEMU_THREAD_JOINABLE);
+}
+
+static void pci_leech_finalize(PCIDevice *pdev)
+{
+    PciLeechState *state = PCILEECH(pdev);
+    puts("Stopping PCILeech Worker...");
+    qemu_mutex_lock(&state->mutex);
+    state->stopping = true;
+    qemu_mutex_unlock(&state->mutex);
+    close(state->sockfd);
+    qemu_thread_join(&state->thread);
+    qemu_mutex_destroy(&state->mutex);
+}
+
+char pci_leech_default_host[] = "0.0.0.0";
+
+static void pci_leech_instance_init(Object *obj)
+{
+    int x = 1;
+    char* y = (char *)&x;
+    PciLeechState *state = PCILEECH(obj);
+    /* QEMU's String-Property can't specify default value. */
+    /* So we have to set the default on our own. */
+    if (state->host == NULL) {
+        state->host = pci_leech_default_host;
+    }
+    /* Save Endianness */
+    state->endianness = (*y == 0);
+}
+
+static Property leech_properties[] = {
+    DEFINE_PROP_UINT16("port", PciLeechState, port, 6789),
+    DEFINE_PROP_STRING("host", PciLeechState, host),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pci_leech_class_init(ObjectClass *class, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(class);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
+    k->realize = pci_leech_realize;
+    k->exit = pci_leech_finalize;
+    /* Change the Vendor/Device ID to your favor. */
+    /* These are the default values from PCILeech-FPGA. */
+    k->vendor_id = PCI_VENDOR_ID_XILINX;
+    k->device_id = 0x0666;
+    k->revision = 0;
+    k->class_id = PCI_CLASS_NETWORK_ETHERNET;
+    device_class_set_props(dc, leech_properties);
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static void pci_leech_register_types(void)
+{
+    static InterfaceInfo interfaces[] = {
+        {INTERFACE_CONVENTIONAL_PCI_DEVICE},
+        {},
+    };
+    static const TypeInfo leech_info = {
+        .name = TYPE_PCILEECH_DEVICE,
+        .parent = TYPE_PCI_DEVICE,
+        .instance_size = sizeof(PciLeechState),
+        .instance_init = pci_leech_instance_init,
+        .class_init = pci_leech_class_init,
+        .interfaces = interfaces,
+    };
+    type_register_static(&leech_info);
+}
+
+type_init(pci_leech_register_types)