diff mbox series

[RFC,1/1] virtio-rng: implement entropy leak feature

Message ID 20230403105245.29499-2-bchalios@amazon.es
State New
Headers show
Series Implement entropy leak reporting for virtio-rng | expand

Commit Message

Babis Chalios April 3, 2023, 10:52 a.m. UTC
Entropy leak reporting is a virtio-rng proposed feature [1], which
allows a virtio-rng device to report events during which entropy has
decreased, for example when a VM gets snapshotted or resumed from a
snapshot.

Guests can request from the virtio-rng device to perform two types of
events upon such events: 1. fill a buffer with random bytes or 2. copy
the contents of one buffer to another. A guest adds requests to "leak
queues" which the device will handle only when an "entropy leak" event
happens.

This patch extends the virtio-rng device with the pair of leak queues
defined in the specification proposal and implements the two types of
requests. At the moment, it triggers handling of requests during
post_save() and post_load() hooks.

In the current version, "fill-on-leak" request makes use of getrandom()
to get random bytes. However, a proper implementation should probably
consider extending the RngBackend API to include a synchronous call.

[1] https://www.mail-archive.com/virtio-dev@lists.oasis-open.org/msg09016.html

Signed-off-by: Babis Chalios <bchalios@amazon.es>
---
 hw/virtio/virtio-rng.c                      | 170 +++++++++++++++++++-
 include/hw/virtio/virtio-rng.h              |   9 +-
 include/standard-headers/linux/virtio_rng.h |   3 +
 3 files changed, 179 insertions(+), 3 deletions(-)
diff mbox series

Patch

diff --git a/hw/virtio/virtio-rng.c b/hw/virtio/virtio-rng.c
index 7e12fc03bf..def1b1d994 100644
--- a/hw/virtio/virtio-rng.c
+++ b/hw/virtio/virtio-rng.c
@@ -9,6 +9,7 @@ 
  * top-level directory.
  */
 
+#include <sys/random.h>
 #include "qemu/osdep.h"
 #include "qapi/error.h"
 #include "qemu/iov.h"
@@ -20,7 +21,11 @@ 
 #include "sysemu/rng.h"
 #include "sysemu/runstate.h"
 #include "qom/object_interfaces.h"
+#include "migration/misc.h"
 #include "trace.h"
+#include <stdint.h>
+
+#define VIRTIO_RNG_VM_VERSION  1
 
 static bool is_guest_ready(VirtIORNG *vrng)
 {
@@ -43,6 +48,112 @@  static size_t get_request_size(VirtQueue *vq, unsigned quota)
 
 static void virtio_rng_process(VirtIORNG *vrng);
 
+static VirtQueue *get_active_leak_queue(VirtIORNG *vrng)
+{
+    size_t queue = vrng->active_leak_queue;
+    return vrng->leakq[queue];
+}
+
+static size_t swap_active_leak_queue(VirtIORNG *vrng)
+{
+    size_t old_active = vrng->active_leak_queue;
+    vrng->active_leak_queue = (old_active + 1) % 2;
+    return old_active;
+}
+
+static VirtQueue *get_signaled_leak_queue(VirtIORNG *vrng)
+{
+    int32_t signaled_leak_queue = vrng->signaled_leak_queue;
+
+    if (signaled_leak_queue == -1) {
+        return NULL;
+    }
+
+    return vrng->leakq[signaled_leak_queue];
+}
+
+static size_t handle_fill_on_leak_command(VirtIORNG *vrng, VirtQueue *vq,
+                                          VirtQueueElement *elem)
+{
+    size_t bytes = iov_size(elem->in_sg, elem->in_num);
+    uint8_t *buffer = g_new0(uint8_t, bytes);
+
+    /*
+     * Probably, the correct thing to do is add a synchronous
+     * API call to RngBackend and use it here.
+     */
+    if (getrandom(buffer, bytes, 0) != bytes) {
+        fprintf(stderr, "qemu-virtio-rng: could not get random bytes");
+        return 0;
+    }
+
+    iov_from_buf(elem->in_sg, elem->in_num, 0, buffer, bytes);
+
+    return bytes;
+}
+
+static size_t handle_copy_on_leak_command(VirtIORNG *vrng, VirtQueue *vq,
+                                          VirtQueueElement *elem)
+{
+    size_t out_size, in_size, offset = 0;
+
+    out_size = iov_size(elem->out_sg, elem->out_num);
+    in_size = iov_size(elem->in_sg, elem->in_num);
+
+    if (out_size != in_size) {
+        return 0;
+    }
+
+    for (int i = 0; i < elem->out_num; ++i) {
+        struct iovec *iov = &elem->out_sg[i];
+        offset += iov_from_buf(elem->in_sg, elem->in_num, offset, iov->iov_base,
+                               iov->iov_len);
+    }
+
+    return offset;
+}
+
+static void virtio_rng_process_leak(VirtIORNG *vrng, VirtQueue *vq)
+{
+    VirtQueueElement *elem;
+    VirtIODevice *vdev = VIRTIO_DEVICE(vrng);
+    size_t len;
+
+    if (!runstate_check(RUN_STATE_RUNNING)) {
+        return;
+    }
+
+    while ((elem = virtqueue_pop(vq, sizeof(VirtQueueElement)))) {
+        /*
+         * If we have a write buffer this is a copy-on-leak command
+         * otherwise a fill-on-leak command
+         */
+        if (elem->out_num) {
+            len = handle_copy_on_leak_command(vrng, vq, elem);
+        } else {
+            len = handle_fill_on_leak_command(vrng, vq, elem);
+        }
+
+        virtqueue_push(vq, elem, len);
+        g_free(elem);
+    }
+    virtio_notify(vdev, vq);
+}
+
+static int signal_entropy_leak(VirtIORNG *vrng)
+{
+    VirtQueue *activeq = get_active_leak_queue(vrng);
+
+    /*
+     * Process all the buffers in the active leak queue
+     * and then swap active leak queues.
+     */
+    virtio_rng_process_leak(vrng, activeq);
+    vrng->signaled_leak_queue = swap_active_leak_queue(vrng);
+
+    return 0;
+}
+
 /* Send data from a char device over to the guest */
 static void chr_read(void *opaque, const void *buf, size_t size)
 {
@@ -128,9 +239,29 @@  static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
     virtio_rng_process(vrng);
 }
 
+static void handle_leakq(VirtIODevice *vdev, VirtQueue *vq)
+{
+    VirtIORNG *vrng = VIRTIO_RNG(vdev);
+    VirtQueue *signaled_queue = get_signaled_leak_queue(vrng);
+
+    if (!is_guest_ready(vrng)) {
+        return;
+    }
+
+    /*
+     * If we received a request on an already signalled leak queue
+     * we need to handle it immediately, otherwise we leave the buffer(s)
+     * in the virtqueue and we will handle them once an entropy leak event
+     * occurs.
+     */
+    if (vq == signaled_queue) {
+        virtio_rng_process_leak(vrng, vq);
+    }
+}
+
 static uint64_t get_features(VirtIODevice *vdev, uint64_t f, Error **errp)
 {
-    return f;
+    return f | (1 << VIRTIO_RNG_F_LEAK);
 }
 
 static void virtio_rng_vm_state_change(void *opaque, bool running,
@@ -218,11 +349,14 @@  static void virtio_rng_device_realize(DeviceState *dev, Error **errp)
     virtio_init(vdev, VIRTIO_ID_RNG, 0);
 
     vrng->vq = virtio_add_queue(vdev, 8, handle_input);
+    vrng->leakq[0] = virtio_add_queue(vdev, 8, handle_leakq);
+    vrng->leakq[1] = virtio_add_queue(vdev, 8, handle_leakq);
+    vrng->active_leak_queue = 0;
+    vrng->signaled_leak_queue = -1;
     vrng->quota_remaining = vrng->conf.max_bytes;
     vrng->rate_limit_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
                                                check_rate_limit, vrng);
     vrng->activate_timer = true;
-
     vrng->vmstate = qemu_add_vm_change_state_handler(virtio_rng_vm_state_change,
                                                      vrng);
 }
@@ -235,9 +369,40 @@  static void virtio_rng_device_unrealize(DeviceState *dev)
     qemu_del_vm_change_state_handler(vrng->vmstate);
     timer_free(vrng->rate_limit_timer);
     virtio_del_queue(vdev, 0);
+    virtio_del_queue(vdev, 1);
+    virtio_del_queue(vdev, 2);
     virtio_cleanup(vdev);
 }
 
+/*
+ * After saving the VM state or loading a VM from a snapshot,
+ * we need to signal the guest for a leak event
+ */
+static int virtio_rng_post_save_device(void *opaque)
+{
+    VirtIORNG *vrng = opaque;
+    return signal_entropy_leak(vrng);
+}
+
+static int virtio_rng_post_load_device(void *opaque, int version_id)
+{
+    VirtIORNG *vrng = opaque;
+    return signal_entropy_leak(vrng);
+}
+
+static const VMStateDescription vmstate_virtio_rng_device = {
+    .name = "virtio-rng-device",
+    .version_id = VIRTIO_RNG_VM_VERSION,
+    .minimum_version_id = VIRTIO_RNG_VM_VERSION,
+    .post_save = virtio_rng_post_save_device,
+    .post_load = virtio_rng_post_load_device,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(active_leak_queue, VirtIORNG),
+        VMSTATE_INT32(signaled_leak_queue, VirtIORNG),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
 static const VMStateDescription vmstate_virtio_rng = {
     .name = "virtio-rng",
     .minimum_version_id = 1,
@@ -272,6 +437,7 @@  static void virtio_rng_class_init(ObjectClass *klass, void *data)
     vdc->unrealize = virtio_rng_device_unrealize;
     vdc->get_features = get_features;
     vdc->set_status = virtio_rng_set_status;
+    vdc->vmsd = &vmstate_virtio_rng_device;
 }
 
 static const TypeInfo virtio_rng_info = {
diff --git a/include/hw/virtio/virtio-rng.h b/include/hw/virtio/virtio-rng.h
index 82734255d9..0d492c0ac7 100644
--- a/include/hw/virtio/virtio-rng.h
+++ b/include/hw/virtio/virtio-rng.h
@@ -31,8 +31,15 @@  struct VirtIORNGConf {
 struct VirtIORNG {
     VirtIODevice parent_obj;
 
-    /* Only one vq - guest puts buffer(s) on it when it needs entropy */
+    /*
+     * One vq for entropy requests and a pair of vqs for the reporting entropy
+     * leak events
+     */
     VirtQueue *vq;
+    VirtQueue *leakq[2];
+
+    uint32_t active_leak_queue;
+    int32_t signaled_leak_queue;
 
     VirtIORNGConf conf;
 
diff --git a/include/standard-headers/linux/virtio_rng.h b/include/standard-headers/linux/virtio_rng.h
index 60fc798bde..ffe0c6841d 100644
--- a/include/standard-headers/linux/virtio_rng.h
+++ b/include/standard-headers/linux/virtio_rng.h
@@ -5,4 +5,7 @@ 
 #include "standard-headers/linux/virtio_ids.h"
 #include "standard-headers/linux/virtio_config.h"
 
+/* Feature bits */
+#define VIRTIO_RNG_F_LEAK 0
+
 #endif /* _LINUX_VIRTIO_RNG_H */