lkml.org 
[lkml]   [2022]   [Aug]   [3]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH 2/2] virt: vmgenid: add support for generation counter
Date
From: Babis Chalios <bchalios@amazon.es>

VM Generation ID provides a means of reseeding kernel's RNG using a
128-bit UUID when a VM fork occurs, thus avoiding issues running
multiple VMs with the exact same RNG state. However, user-space
applications, such as user-space PRNGs and applications that maintain
world-unique data, need a mechanism to handle VM fork events as well.

To handle the user-space use-case, this: <url> qemu patch extends
Microsoft's original vmgenid specification adding an extra page which
holds a single 32-bit generation counter, which increases every time a
VM gets restored from a snapshot.

This patch exposes the generation counter through a character device
(`/dev/vmgenid`) that provides a `read` and `mmap` interface, for
user-space applications to consume. Userspace applications should read
this value before starting a transaction involving cached random bits
and ensure that it has not changed while committing the transaction.

It can be used from qemu using the `-device vmgenid,guid=auto,genctr=42`
parameter to start a VM with a generation counter with value 42.
Reading 4 bytes from `/dev/vmgenid` will return the value 42. Next, use
`savevm my_snapshot` in the monitor to snapshot the VM. Now, start
another VM using `-device vmgenid,guid=auto,genctr=43 -loadvm
my_snapshot`. Reading now from `/dev/vmgenid` will return 43.

Signed-off-by: Babis Chalios <bchalios@amazon.es>
---
Documentation/virt/vmgenid.rst | 120 +++++++++++++++++++++++++++++++++
drivers/virt/vmgenid.c | 103 +++++++++++++++++++++++++++-
2 files changed, 221 insertions(+), 2 deletions(-)
create mode 100644 Documentation/virt/vmgenid.rst

diff --git a/Documentation/virt/vmgenid.rst b/Documentation/virt/vmgenid.rst
new file mode 100644
index 000000000..61c29e4a7
--- /dev/null
+++ b/Documentation/virt/vmgenid.rst
@@ -0,0 +1,120 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=======
+VMGENID
+=======
+
+The VM Generation ID (VMGENID) is a feature from Microsoft
+(https://go.microsoft.com/fwlink/?LinkId=260709) supported by multiple
+hypervisor vendors.
+
+Its purpose is to help tackle issues occurying by duplication of the state
+of a Virtual Machine (VM) during events that cause a VM to "return back in
+time", like snapshot and restore. It exposes a generation ID inside the VM so
+that applications that rely on world-wide unique or random data can check if
+that value has changed before committing transactions.
+
+Problem Definition
+------------------
+
+Often in its lifetime, a VM will get snapshotted and later it will be restored
+in that previous state. Moreover, one or more new VMs can be spawned from this
+snapshot. Both scenarios result in one or more VMs running with same RNG state,
+which makes early operations after restore that rely on randomness predictable,
+and thus render them insecure, for example TLS.
+
+Userspace PRNGs, as well as code that caches streams of random bits, to speed
+up latency critical applications, suffer from similar issues.
+
+Apart from concerns related with cryptography, userspace applications operating
+with (what they consider to be) unique data, such as UUIDs, are affected by
+spawning of multiple VMs from the same snapshot.
+
+VMGENID tackles the issue by providing a unique (not random) 128-bits
+identifier every time a VM is restored from a snapshot. The identifier is used
+to reseed the kernel's RNG ensuring that different VMs spawned from the same
+snapshot will observe different streams of random data.
+
+Notice that VMGENID does not eliminate the problem but it significantly reduces
+the window in which the system's RNG will produce identical data across
+different VMs.
+
+Reseeding the kernel's RNG tackles the issue of duplicated random values
+provided by the kernel, however it does little to address the issue of
+userspace applications that use world-unique data. The UUID defined by the
+original VMGENID specification is used to reseed the RNG, so it cannot be
+exposed to the userspace. This class of applications need a separate API which
+they can consume in order to detect VM restore events and adapt accordingly.
+
+In that front, VMGENID has been extended to expose to userspace an additional
+32 bits generation counter, which acts as a notification mechanism for restore
+events. The value of the counter after a VM restore will be different than
+its value when the snapshot was taken in order to signal to userspace that
+a VM restore has occurred.
+
+VMGENID in Linux
+----------------
+
+Linux kernel uses the 128-bits UUID of VMGENID to reseed the RNG every time an
+ACPI notification arrives. Moreover, it exposes the 32-bits generation counter
+through a character device ``/dev/vmgenid``. The device supports ``read()``
+and ``mmap`` for user space applications to monitor restore events:
+
+``read()``:
+Read always returns the first 4 bytes of the page including the generation
+counter. Partial reads and reads in offset other than 0 are not allowed and
+return ``EINVAL``.
+
+``mmap()``:
+It maps a single page in the address space of the userspace application. The
+driver supports ``PROT_READ`` and ``MAP_SHARED``. Mapping with ``PROT_WRITE``
+will result in ``EPERM``, whereas mapping past the first page will result in
+``EINVAL``.
+
+A userspace application that caches random bits from the kernel should ensure
+that the moment it actually wants to consume some of these bits the value of
+the generation counter equals its value when the bits were initially cached.
+For example:
+
+```
+uint32_t *gen_cntr = mmaped_gen_counter();
+uint32_t cached_gen_cntr = *gen_cntr;
+char *secret;
+
+for(;;) {
+ secret = get_secret();
+
+ // All good, not restore has happened.
+ if (cached_gen_cntr == *gen_cntr)
+ break;
+
+ // Generation counter has changed. We need to recreate caches and try again
+
+ cached_gen_cntr = *gen_cntr;
+ barrier();
+
+ // recreate secrets' cache
+ rebuild_cache();
+}
+
+consume_secret(secret);
+
+```
+
+The driver for VMGENID lives under ``drivers/virt/vmgenid.c``.
+
+Using VMGENID
+-------------
+
+https://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/specs/vmgenid.txt;hb=refs/heads/master
+describes how the VMGENID device can be used. First we start a VM passing the
+parameter `-device vmgenid,guid=auto,genctr=42`. With this the UUID value of
+VMGENID will be populated with a UUID created by qemu and a generation counter
+of 42. Next, we can save the VM state from the monitor using the `savevm`
+command.
+
+Now, we can start another VM from the same snapshot using the `-device
+vmgenid,guid=auto,genctr=43 -loadvm {snapshot}` options. This will update the
+UUID with a new value generated by qemu and 43 for the generation counter in
+memory before resuming the vcpus and then send an appropriate ACPI notification
+to the guest.
diff --git a/drivers/virt/vmgenid.c b/drivers/virt/vmgenid.c
index 0cc2fe0f4..1cb0b3560 100644
--- a/drivers/virt/vmgenid.c
+++ b/drivers/virt/vmgenid.c
@@ -11,6 +11,10 @@
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/random.h>
+#include "linux/container_of.h"
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/mm.h>

ACPI_MODULE_NAME("vmgenid");

@@ -19,6 +23,69 @@ enum { VMGENID_SIZE = 16 };
struct vmgenid_state {
u8 *next_id;
u8 this_id[VMGENID_SIZE];
+
+ phys_addr_t gen_cntr_addr;
+ u32 *next_counter;
+
+ int misc_enabled;
+ struct miscdevice misc;
+};
+
+static int vmgenid_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+ struct vmgenid_state *state = filep->private_data;
+
+ if (vma->vm_pgoff || vma_pages(vma) > 1)
+ return -EINVAL;
+
+ if ((vma->vm_flags & VM_WRITE))
+ return -EPERM;
+
+ vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+ vma->vm_flags &= ~VM_MAYWRITE;
+
+ return vm_iomap_memory(vma, state->gen_cntr_addr, PAGE_SIZE);
+}
+
+static ssize_t vmgenid_read(struct file *filep, char __user *buff, size_t count,
+ loff_t *offp)
+{
+ struct vmgenid_state *state = filep->private_data;
+
+ if (count == 0)
+ return 0;
+
+ /* We don't allow partial reads */
+ if (count != sizeof(u32))
+ return -EINVAL;
+
+ if (put_user(*state->next_counter, (u32 __user *)buff))
+ return -EFAULT;
+
+ return sizeof(u32);
+}
+
+static int vmgenid_open(struct inode *inode, struct file *filep)
+{
+ struct vmgenid_state *state =
+ container_of(filep->private_data, struct vmgenid_state, misc);
+
+ filep->private_data = state;
+ return 0;
+}
+
+static const struct file_operations fops = {
+ .owner = THIS_MODULE,
+ .open = vmgenid_open,
+ .read = vmgenid_read,
+ .mmap = vmgenid_mmap,
+ .llseek = noop_llseek,
+};
+
+static struct miscdevice vmgenid_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "vmgenid",
+ .fops = &fops,
};

static int parse_vmgenid_address(struct acpi_device *device, acpi_string object_name,
@@ -57,7 +124,7 @@ static int vmgenid_add(struct acpi_device *device)
phys_addr_t phys_addr;
int ret;

- state = devm_kmalloc(&device->dev, sizeof(*state), GFP_KERNEL);
+ state = devm_kzalloc(&device->dev, sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;

@@ -74,6 +141,27 @@ static int vmgenid_add(struct acpi_device *device)

device->driver_data = state;

+ /* Backwards compatibility. If CTRA is not there we just don't expose
+ * the char device
+ */
+ ret = parse_vmgenid_address(device, "CTRA", &state->gen_cntr_addr);
+ if (ret)
+ return 0;
+
+ state->next_counter = devm_memremap(&device->dev, state->gen_cntr_addr,
+ sizeof(u32), MEMREMAP_WB);
+ if (IS_ERR(state->next_counter))
+ return 0;
+
+ memcpy(&state->misc, &vmgenid_misc, sizeof(state->misc));
+ ret = misc_register(&state->misc);
+ if (ret) {
+ devm_memunmap(&device->dev, state->next_counter);
+ return 0;
+ }
+
+ state->misc_enabled = 1;
+
return 0;
}

@@ -89,6 +177,16 @@ static void vmgenid_notify(struct acpi_device *device, u32 event)
add_vmfork_randomness(state->this_id, sizeof(state->this_id));
}

+static int vmgenid_remove(struct acpi_device *device)
+{
+ struct vmgenid_state *state = device->driver_data;
+
+ if (state->misc_enabled)
+ misc_deregister(&state->misc);
+
+ return 0;
+}
+
static const struct acpi_device_id vmgenid_ids[] = {
{ "VMGENCTR", 0 },
{ "VM_GEN_COUNTER", 0 },
@@ -101,7 +199,8 @@ static struct acpi_driver vmgenid_driver = {
.owner = THIS_MODULE,
.ops = {
.add = vmgenid_add,
- .notify = vmgenid_notify
+ .notify = vmgenid_notify,
+ .remove = vmgenid_remove
}
};

--
2.32.1 (Apple Git-133)
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

\
 
 \ /
  Last update: 2022-08-03 17:22    [W:0.115 / U:0.132 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site