[pve-devel] [PATCH qemu-server 4/5] fix #2264: add virtio-rng device

Stefan Reiter s.reiter at proxmox.com
Thu Feb 20 18:10:44 CET 2020


Allow a user to add a virtio-rng-pci (an emulated hardware random
number generator) to a VM with the rng0 setting. The setting is
version_guard()-ed.

Limit the selection of entropy source to one of three:
/dev/urandom (preferred): Non-blocking kernel entropy source
/dev/random: Blocking kernel source
/dev/hwrng: Hardware RNG on the host for passthrough

QEMU itself defaults to /dev/urandom (or the equivalent getrandom()
call) if no source file is given, but I don't fully trust that
behaviour to stay constant, considering the documentation [0] already
disagrees with the code [1], so let's always specify the file ourselves.

/dev/urandom is preferred, since it prevents host entropy starvation.
The quality of randomness is still good enough to emulate a hwrng, since
a) it's still seeded from the kernel's true entropy pool periodically
and b) it's mixed with true entropy in the guest as well.

Additionally, all sources about entropy predicition attacks I could find
mention that to predict /dev/urandom results, /dev/random has to be
accessed or manipulated in one way or the other - this is not possible
from a VM however, as the entropy we're talking about comes from the
*hosts* blocking pool.

More about the entropy and security implications of the non-blocking
interface in [2] and [3].

Note further that only one /dev/hwrng exists at any given time, if
multiple RNGs are available, only the one selected in
'/sys/devices/virtual/misc/hw_random/rng_current' will feed the file.
Selecting this is left as an exercise to the user, if at all required.

We limit the available entropy to 1 KiB/s by default, but allow the user
to override this. Interesting to note is that the limiter does not work
linearly, i.e. max_bytes=1024/period=1000 means that up to 1 KiB of data
becomes available on a 1000 millisecond timer, not that 1 KiB is
streamed to the guest over the course of one second - hence the
configurable period.

The default used here is the same as given in the QEMU documentation [0]
and has been verified to affect entropy availability in a guest by
measuring /dev/random throughput. 1 KiB/s is enough to avoid any
early-boot entropy shortages, and already has a significant impact on
/dev/random availability in the guest.

[0] https://wiki.qemu.org/Features/VirtIORNG
[1] https://git.qemu.org/?p=qemu.git;a=blob;f=crypto/random-platform.c;h=f92f96987d7d262047c7604b169a7fdf11236107;hb=HEAD
[2] https://lwn.net/Articles/261804/
[3] https://lwn.net/Articles/808575/

Signed-off-by: Stefan Reiter <s.reiter at proxmox.com>
---

Includes a version bump currently set to 4.1+pve2. Would need to be changed if
applied later.

Tested with all three options (luckily enough, my system has a hwrng called
'ccp-1-rng', which seems to come from the AMD CPU's Cryptographic Coprocessor).

/dev/random does indeed lead to entropy starvation, while /dev/urandom does not.
rngtest (from rng-tools) reports high-quality randomness inside VMs, no matter
what source is selected.

 PVE/QemuServer.pm     | 73 +++++++++++++++++++++++++++++++++++++++++++
 PVE/QemuServer/PCI.pm |  1 +
 2 files changed, 74 insertions(+)

diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 23176dd..0065a3c 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -219,6 +219,43 @@ my $spice_enhancements_fmt = {
     },
 };
 
+my $rng_fmt = {
+    source => {
+	type => 'string',
+	enum => ['/dev/urandom', '/dev/random', '/dev/hwrng'],
+	default_key => 1,
+	description => "The file on the host to gather entropy from. In most"
+		     . " cases /dev/urandom should be preferred over /dev/random"
+		     . " to avoid entropy-starvation issues on the host. Using"
+		     . " urandom does *not* decrease security in any meaningful"
+		     . " way, as it's still seeded from real entropy, and the"
+		     . " bytes provided will most likely be mixed with real"
+		     . " entropy on the guest as well. /dev/hwrng can be used"
+		     . " to pass through a hardware RNG from the host.",
+    },
+    max_bytes => {
+	type => 'integer',
+	description => "Maximum bytes of entropy injected into the guest every"
+		     . " 'period' milliseconds. Prefer a lower value when using"
+		     . " /dev/random as source. Use 0 to disable limiting"
+		     . " (potentially dangerous!).",
+	optional => 1,
+
+	# default is 1 KiB/s, provides enough entropy to the guest to avoid
+	# boot-starvation issues (e.g. systemd etc...) while allowing no chance
+	# of overwhelming the host, provided we're reading from /dev/urandom
+	default => 1024,
+    },
+    period => {
+	type => 'integer',
+	description => "Every 'period' milliseconds the entropy-injection quota"
+		     . " is reset, allowing the guest to retrieve another"
+		     . " 'max_bytes' of entropy.",
+	optional => 1,
+	default => 1000,
+    },
+};
+
 my $confdesc = {
     onboot => {
 	optional => 1,
@@ -607,6 +644,12 @@ EODESCR
 	description => 'Tags of the VM. This is only meta information.',
 	optional => 1,
     },
+    rng0 => {
+	type => 'string',
+	format => $rng_fmt,
+	description => "Configure a VirtIO-based Random Number Generator.",
+	optional => 1,
+    },
 };
 
 my $cicustom_fmt = {
@@ -2348,6 +2391,16 @@ sub parse_vga {
     return $res;
 }
 
+sub parse_rng {
+    my ($value) = @_;
+
+    return undef if !$value;
+
+    my $res = eval { PVE::JSONSchema::parse_property_string($rng_fmt, $value) };
+    warn $@ if $@;
+    return $res;
+}
+
 PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
 sub verify_usb_device {
     my ($value, $noerr) = @_;
@@ -3827,6 +3880,26 @@ sub config_to_command {
 	}
     }
 
+    my $rng = parse_rng($conf->{rng0}) if $conf->{rng0};
+    if ($rng && &$version_guard(4, 1, 2)) {
+	my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};
+	my $period = $rng->{period} // $rng_fmt->{period}->{default};
+
+	my $limiter_str = "";
+	if ($max_bytes) {
+	    $limiter_str = ",max-bytes=$max_bytes,period=$period";
+	}
+
+	# mostly relevant for /dev/hwrng, but doesn't hurt to check others too
+	die "cannot create VirtIO RNG device: source file '$rng->{source}' doesn't exist\n"
+	    if ! -e $rng->{source};
+
+	my $rng_addr = print_pci_addr("rng0", $bridges, $arch, $machine_type);
+
+	push @$devices, '-object', "rng-random,filename=$rng->{source},id=rng0";
+	push @$devices, '-device', "virtio-rng-pci,rng=rng0$limiter_str$rng_addr";
+    }
+
     my $spice_port;
 
     if ($qxlnum) {
diff --git a/PVE/QemuServer/PCI.pm b/PVE/QemuServer/PCI.pm
index c3b4716..4d9ce26 100644
--- a/PVE/QemuServer/PCI.pm
+++ b/PVE/QemuServer/PCI.pm
@@ -72,6 +72,7 @@ sub get_pci_addr_map {
 	'net31' => { bus => 1, addr => 26 },
 	'xhci' => { bus => 1, addr => 27 },
 	'pci.4' => { bus => 1, addr => 28 },
+	'rng0' => { bus => 1, addr => 29 },
 	'virtio6' => { bus => 2, addr => 1 },
 	'virtio7' => { bus => 2, addr => 2 },
 	'virtio8' => { bus => 2, addr => 3 },
-- 
2.20.1




More information about the pve-devel mailing list