All of lore.kernel.org
 help / color / mirror / Atom feed
From: Babis Chalios <bchalios@amazon.es>
To: Olivia Mackall <olivia@selenic.com>,
	Herbert Xu <herbert@gondor.apana.org.au>,
	"Michael S. Tsirkin" <mst@redhat.com>,
	"Jason Wang" <jasowang@redhat.com>,
	Babis Chalios <bchalios@amazon.es>,
	<linux-crypto@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<virtualization@lists.linux-foundation.org>
Cc: <sgarzare@redhat.com>, <amit@kernel.org>, <graf@amazon.de>,
	<Jason@zx2c4.com>, <xmarcalx@amazon.co.uk>
Subject: [PATCH v2 1/2] virtio-rng: implement entropy leak feature
Date: Tue, 31 Jan 2023 15:55:42 +0100	[thread overview]
Message-ID: <20230131145543.86369-2-bchalios@amazon.es> (raw)
In-Reply-To: <20230131145543.86369-1-bchalios@amazon.es>

Implement the virtio-rng feature that allows a guest driver to request
from the device to perform certain operations in the event of an
"entropy leak", such as when taking a VM snapshot or restoring a VM from
a snapshot. The guest can request one of two operations: (i) fill a
buffer with random bytes, or (ii) perform a memory copy between two
bytes.

The feature is similar to Microsoft's Virtual Machine Generation ID and
it can be used to (1) avoid the race-condition that exists in our
current VMGENID implementation, between the time vcpus are resumed and
the ACPI notification is being handled and (2) provide mechanisms for
notifying user-space about snapshot-related events.

This commit implements the protocol between guest and device.
Additionally, it makes sure there is always a request for random bytes
in the event of entropy leak in-flight. Once such an event is observed,
the driver feeds these bytes to as entropy using
`add_device_randomness`.

Keep in mind that this commit does not solve the race-condition issue,
it adds fresh entropy whenever the driver handles the used buffer from
the fill-on-leak request. In order to close the race window, we need to
expose some API so that other kernel subsystems can request directly
notifications from the device.

Signed-off-by: Babis Chalios <bchalios@amazon.es>
---
 drivers/char/hw_random/virtio-rng.c | 200 +++++++++++++++++++++++++++-
 include/uapi/linux/virtio_rng.h     |   3 +
 2 files changed, 196 insertions(+), 7 deletions(-)

diff --git a/drivers/char/hw_random/virtio-rng.c b/drivers/char/hw_random/virtio-rng.c
index a6f3a8a2aca6..154f68a1e326 100644
--- a/drivers/char/hw_random/virtio-rng.c
+++ b/drivers/char/hw_random/virtio-rng.c
@@ -4,12 +4,12 @@
  *  Copyright (C) 2007, 2008 Rusty Russell IBM Corporation
  */
 
-#include <linux/err.h>
 #include <linux/hw_random.h>
 #include <linux/scatterlist.h>
 #include <linux/spinlock.h>
 #include <linux/virtio.h>
 #include <linux/virtio_rng.h>
+#include <linux/random.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 
@@ -18,6 +18,12 @@ static DEFINE_IDA(rng_index_ida);
 struct virtrng_info {
 	struct hwrng hwrng;
 	struct virtqueue *vq;
+	/* Leak queues */
+	bool has_leakqs;
+	struct virtqueue *leakq[2];
+	spinlock_t lock;
+	int active_leakq;
+
 	char name[25];
 	int index;
 	bool hwrng_register_done;
@@ -29,27 +35,159 @@ struct virtrng_info {
 	/* minimal size returned by rng_buffer_size() */
 #if SMP_CACHE_BYTES < 32
 	u8 data[32];
+	u8 leak_data[32];
 #else
 	u8 data[SMP_CACHE_BYTES];
+	u8 leak_data[SMP_CACHE_BYTES];
 #endif
 };
 
+/* Swaps the queues and returns the new active leak queue. */
+static struct virtqueue *swap_leakqs(struct virtrng_info *vi)
+{
+	vi->active_leakq = 1 - vi->active_leakq;
+	return vi->leakq[vi->active_leakq];
+}
+
+static struct virtqueue *get_active_leakq(struct virtrng_info *vi)
+{
+	return vi->leakq[vi->active_leakq];
+}
+
+static int add_fill_on_leak_request(struct virtrng_info *vi, struct virtqueue *vq, void *data, size_t len)
+{
+	struct scatterlist sg;
+	int ret;
+
+	sg_init_one(&sg, data, len);
+	ret = virtqueue_add_inbuf(vq, &sg, 1, data, GFP_KERNEL);
+	if (ret)
+		goto err;
+
+err:
+	return ret;
+}
+
+static int virtrng_fill_on_leak(struct virtrng_info *vi, void *data, size_t len)
+{
+	struct virtqueue *vq;
+	unsigned long flags;
+	int ret;
+
+	if (!vi->has_leakqs)
+		return -EOPNOTSUPP;
+
+	spin_lock_irqsave(&vi->lock, flags);
+
+	vq = get_active_leakq(vi);
+	ret = add_fill_on_leak_request(vi, vq, data, len);
+	if (ret)
+		virtqueue_kick(vq);
+
+	spin_unlock_irqrestore(&vi->lock, flags);
+
+	return ret;
+}
+
+static int add_copy_on_leak_request(struct virtrng_info *vi, struct virtqueue *vq,
+		void *to, void *from, size_t len)
+{
+	int ret;
+	struct scatterlist out, in, *sgs[2];
+
+	sg_init_one(&out, from, len);
+	sgs[0] = &out;
+	sg_init_one(&in, to, len);
+	sgs[1] = &in;
+
+	ret = virtqueue_add_sgs(vq, sgs, 1, 1, to, GFP_KERNEL);
+	if (ret)
+		goto err;
+
+err:
+	return ret;
+}
+
+static int virtrng_copy_on_leak(struct virtrng_info *vi, void *to, void *from, size_t len)
+{
+	struct virtqueue *vq;
+	unsigned long flags;
+	int ret;
+
+	if (!vi->has_leakqs)
+		return -EOPNOTSUPP;
+
+	spin_lock_irqsave(&vi->lock, flags);
+
+	vq = get_active_leakq(vi);
+	ret = add_copy_on_leak_request(vi, vq, to, from, len);
+	if (ret)
+		virtqueue_kick(vq);
+
+	spin_unlock_irqrestore(&vi->lock, flags);
+
+	return ret;
+}
+
+static void entropy_leak_detected(struct virtqueue *vq)
+{
+	struct virtrng_info *vi = vq->vdev->priv;
+	struct virtqueue *activeq;
+	unsigned int len;
+	unsigned long flags;
+	void *buffer;
+	bool kick_activeq = false;
+
+	spin_lock_irqsave(&vi->lock, flags);
+
+	activeq = get_active_leakq(vi);
+	/* Drain all the used buffers from the queue */
+	while ((buffer = virtqueue_get_buf(vq, &len)) != NULL) {
+		if (vq == activeq) {
+			pr_debug("%s: entropy leak detected!", vi->name);
+			activeq = swap_leakqs(vi);
+		}
+
+		if (buffer == vi->leak_data) {
+			add_device_randomness(vi->leak_data, sizeof(vi->leak_data));
+
+			/* Ensure we always have a pending request for random bytes on entropy
+			 * leak. Do it here, after we have swapped leak queues, so it gets handled
+			 * with the next entropy leak event.
+			 */
+			add_fill_on_leak_request(vi, activeq, vi->leak_data, sizeof(vi->leak_data));
+			kick_activeq = true;
+		}
+	}
+
+	if (kick_activeq)
+		virtqueue_kick(activeq);
+
+	spin_unlock_irqrestore(&vi->lock, flags);
+}
+
 static void random_recv_done(struct virtqueue *vq)
 {
 	struct virtrng_info *vi = vq->vdev->priv;
+	unsigned long flags;
 
+	spin_lock_irqsave(&vi->lock, flags);
 	/* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */
 	if (!virtqueue_get_buf(vi->vq, &vi->data_avail))
-		return;
+		goto unlock;
 
 	vi->data_idx = 0;
 
 	complete(&vi->have_data);
+
+unlock:
+	spin_unlock_irqrestore(&vi->lock, flags);
 }
 
 static void request_entropy(struct virtrng_info *vi)
 {
 	struct scatterlist sg;
+	unsigned long flags;
 
 	reinit_completion(&vi->have_data);
 	vi->data_avail = 0;
@@ -57,10 +195,12 @@ static void request_entropy(struct virtrng_info *vi)
 
 	sg_init_one(&sg, vi->data, sizeof(vi->data));
 
+	spin_lock_irqsave(&vi->lock, flags);
 	/* There should always be room for one buffer. */
 	virtqueue_add_inbuf(vi->vq, &sg, 1, vi->data, GFP_KERNEL);
 
 	virtqueue_kick(vi->vq);
+	spin_unlock_irqrestore(&vi->lock, flags);
 }
 
 static unsigned int copy_data(struct virtrng_info *vi, void *buf,
@@ -126,6 +266,40 @@ static void virtio_cleanup(struct hwrng *rng)
 	complete(&vi->have_data);
 }
 
+static int init_virtqueues(struct virtrng_info *vi, struct virtio_device *vdev)
+{
+	int ret = -ENOMEM, total_vqs = 1;
+	struct virtqueue *vqs[3];
+	const char *names[3];
+	vq_callback_t *callbacks[3];
+
+	if (vi->has_leakqs)
+		total_vqs = 3;
+
+	callbacks[0] = random_recv_done;
+	names[0] = "input";
+	if (vi->has_leakqs) {
+		callbacks[1] = entropy_leak_detected;
+		names[1] = "leakq.1";
+		callbacks[2] = entropy_leak_detected;
+		names[2] = "leakq.2";
+	}
+
+	ret = virtio_find_vqs(vdev, total_vqs, vqs, callbacks, names, NULL);
+	if (ret)
+		goto err;
+
+	vi->vq = vqs[0];
+
+	if (vi->has_leakqs) {
+		vi->leakq[0] = vqs[1];
+		vi->leakq[1] = vqs[2];
+	}
+
+err:
+	return ret;
+}
+
 static int probe_common(struct virtio_device *vdev)
 {
 	int err, index;
@@ -152,18 +326,24 @@ static int probe_common(struct virtio_device *vdev)
 	};
 	vdev->priv = vi;
 
-	/* We expect a single virtqueue. */
-	vi->vq = virtio_find_single_vq(vdev, random_recv_done, "input");
-	if (IS_ERR(vi->vq)) {
-		err = PTR_ERR(vi->vq);
-		goto err_find;
+	vi->has_leakqs = virtio_has_feature(vdev, VIRTIO_RNG_F_LEAK);
+	if (vi->has_leakqs) {
+		spin_lock_init(&vi->lock);
+		vi->active_leakq = 0;
 	}
 
+	err = init_virtqueues(vi, vdev);
+	if (err)
+		goto err_find;
+
 	virtio_device_ready(vdev);
 
 	/* we always have a pending entropy request */
 	request_entropy(vi);
 
+	/* we always have a fill_on_leak request pending */
+	virtrng_fill_on_leak(vi, vi->leak_data, sizeof(vi->leak_data));
+
 	return 0;
 
 err_find:
@@ -246,7 +426,13 @@ static const struct virtio_device_id id_table[] = {
 	{ 0 },
 };
 
+static unsigned int features[] = {
+	VIRTIO_RNG_F_LEAK,
+};
+
 static struct virtio_driver virtio_rng_driver = {
+	.feature_table = features,
+	.feature_table_size = ARRAY_SIZE(features),
 	.driver.name =	KBUILD_MODNAME,
 	.driver.owner =	THIS_MODULE,
 	.id_table =	id_table,
diff --git a/include/uapi/linux/virtio_rng.h b/include/uapi/linux/virtio_rng.h
index c4d5de896f0c..d9774951547e 100644
--- a/include/uapi/linux/virtio_rng.h
+++ b/include/uapi/linux/virtio_rng.h
@@ -5,4 +5,7 @@
 #include <linux/virtio_ids.h>
 #include <linux/virtio_config.h>
 
+/* The feature bitmap for virtio entropy device */
+#define VIRTIO_RNG_F_LEAK 0
+
 #endif /* _LINUX_VIRTIO_RNG_H */
-- 
2.38.1

Amazon Spain Services sociedad limitada unipersonal, Calle Ramirez de Prado 5, 28045 Madrid. Registro Mercantil de Madrid . Tomo 22458 . Folio 102 . Hoja M-401234 . CIF B84570936


  reply	other threads:[~2023-01-31 14:57 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-01-31 14:55 [PATCH v2 0/2] [RFC] virtio-rng entropy leak reporting feature Babis Chalios
2023-01-31 14:55 ` Babis Chalios [this message]
2023-01-31 14:55 ` [PATCH v2 2/2] virtio-rng: add sysfs entries for leak detection Babis Chalios
2023-01-31 16:27 ` [PATCH v2 0/2] [RFC] virtio-rng entropy leak reporting feature Jason A. Donenfeld
2023-01-31 17:06   ` bchalios
2023-03-02 16:55   ` Amit Shah
2023-03-02 16:55     ` Amit Shah
2023-03-13 10:42     ` bchalios
2023-03-13 18:05       ` Amit Shah
2023-03-13 18:05         ` Amit Shah
2023-03-20 10:42         ` Amit Shah
2023-03-20 10:42           ` Amit Shah

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=20230131145543.86369-2-bchalios@amazon.es \
    --to=bchalios@amazon.es \
    --cc=Jason@zx2c4.com \
    --cc=amit@kernel.org \
    --cc=graf@amazon.de \
    --cc=herbert@gondor.apana.org.au \
    --cc=jasowang@redhat.com \
    --cc=linux-crypto@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mst@redhat.com \
    --cc=olivia@selenic.com \
    --cc=sgarzare@redhat.com \
    --cc=virtualization@lists.linux-foundation.org \
    --cc=xmarcalx@amazon.co.uk \
    /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.