]> git.proxmox.com Git - qemu-server.git/commitdiff
fix #2264: add virtio-rng device
authorStefan Reiter <s.reiter@proxmox.com>
Thu, 20 Feb 2020 17:10:44 +0000 (18:10 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Fri, 6 Mar 2020 17:09:04 +0000 (18:09 +0100)
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@proxmox.com>
PVE/QemuServer.pm
PVE/QemuServer/PCI.pm

index 050c03b06b760c81f1137720060bb36c9e0f974f..dc65a30bc765de520c24d04203f5e90fce494d22 100644 (file)
@@ -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 = {
@@ -2347,6 +2390,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) = @_;
@@ -3826,6 +3879,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) {
index c3b4716566effefc102202b39cc57658a2cac0a2..4d9ce2640b87f6a45dbd76461585809d212aca0d 100644 (file)
@@ -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 },