Virtio-RNG Inside Nanos
In the Nanos kernel, we have recently added support for the virtio-rng device. This virtio device allows applications to use it as a source of randomness without relying on dedicated processor instructions that may not be available in all clouds or hypervisors. This article talks about why applications require random numbers and looks at different sources of randomness. When applications run in the context of a VM, the situation is slightly different than in baremetal and thus other sources of randomness may be required. We present these sources and discuss benefits and drawbacks of each approach. In particular, we focus on the benefits of virtio-rng and what are the clouds that expose this device to the guest.
Applications require random numbers for secure operation. For example, when public and private key pairs are generated, random numbers are required for this operation. In the kernel, random numbers are used to randomize data, e.g., process PIDs. This makes kernel data less predictable and easier for attackers to break. Applications rely on different sources of randomness to generate these numbers, e.g., OS, dedicated hardware. For example, in Linux, a user-application can use the getrandom() system call to fill a buffer of random bytes. These bytes can then be used to seed user-space random number generators. The OS relies on different sources of randomness to provide those numbers.
When an OS is deployed as a VM, the kernel can use, as a source of randomness, dedicated instructions that are exposed to the guest or dedicated paravirtualized hardware. In the following, we look at these approaches.
A kernel can use the RDTSC instruction that is a precise counter to measure the time between somewhat randomly occurring events like interrupts. This instruction has been introduced since the Pentium processor so it is the preferable way to get randomness in legacy systems. The counter itself is not a source of entropy but can be used to capture entropy from other sources that are time-based like latency from hard-disk or network, mouse input, keyword input, etc. For example, the difference between two executions of the RDTSC instruction can be used as a source of entropy. When this instruction is virtualized, such a difference could be too little thus making it hard to use in the context of a VM. Also, by relying on the occurrence of random events like interruptions, the kernel may require time until it gets enough entropy. Since random numbers are required when booting up the kernel, this may end up increasing the booting time.
In many cases, VMs have no input devices or IO jitter. This is often the case for reducing the attack surface of the device model. The device model only contains a reduced number of devices that are exposed to the guest. In this case, the use of a counter to measure the time of random events as a source of entropy is useless and a different source needs to be used.
The problem of low-entropy forced Intel to include a new random number generator on the chip. This controller can be accessed by using the RDRAND instruction that is available in the Ivy Bridge processors that have been launched in 2012. The RDRAND instruction is not meant to seed a number generator but to provide a random number. This instruction loads a hardware generated random value and stores it in the destination register. The RDRAND instruction is available at all privilege levels and the instruction's default operation size is 32 bits.
To the purpose of seeding generator numbers, Intel introduced the RDSEED instruction that is meant to be used as a source of entropy. This instruction provides a lower access to the random number generator than the RDRAND instruction. The random value is generated from an Enhanced NRBG (Non Deterministic Random Bit Generator) that is compliant to NIST SP800-90B and NIST SP800-90C in the XOR construction mode. The RDSEED instruction is available in the Intel Boradwell CPU (2014) and the AMD Zen CPUs and is available at all privilege levels.
In KVM, the guest can be configured to enable or disable the RDRAND and RDSEED instructions. The use of these instructions generates a VM exit that KVM can trap. In case the instructions are not available, KVM simply triggers an invalid instruction exception. Note that not all hypervisors allow users to enable or disable these instructions.
In old x86 processors and non-x86 architectures, there are no RDTSC, RDRAND or RDSEED instructions for the guest to use. Also, a VM may have no input devices that can be used together with a counter as a source of entropy. Since not all processors support RDRAND or RDSEED, the host may not expose to guests these instructions because this could limit the ability to migrate this guest. In this context, a better approach is to expose to guests a paravirtualized device and let the host seed the guest through this device. In the following, we present the virtio-rng device that has been built to that goal.
Virtio-rng is a paravirtualized device that is exposed as a hardware RNG device to the guest. This is a simple virtio device that is made of one virtqueue. When the driver requires random bytes, it places the descriptor of one or more buffers in the queue and the device simply fills it with random data. On the host side, the virtio-rng device reads from the hosts’s /dev/random and feeds that into the guest. If hardware support exists, the source of random data can be modified from a real hardware RNG device. To have an idea how to setup this in QEMU, the following command line configures the virtio-rng to read from host’s /dev/urandom:
-object rng-random,filename=/dev/urandom,id=rng0 -device
The RDRAND instruction can be used as a source of entropy to pass on to the guest via virtio-rng. In the case of newer Linux kernels, virtio-rng is automatically used to provide continuous entropy to /dev/random. Also, Windows includes a driver for virtio-rng. By letting the host decide how to seed the guest, the guest only requires to include a virtio-rng driver to get a source of randomness.
In the nanos kernel, we built a virtio-rng driver to leverage this device as a source of entropy. The driver stores the entropy data in a double-buffered fashion. This data structure allows us to keep entropy available for randomised functions without the need to block. This is an important requirement since running out of random data may generate unexpected delays. This device is supported in most modern machine monitors like QEMU, Cloud-hypervisor, crosvm and libkrun. The integration of this device in Firecracker is still under discussion. The reason could be that Firecracker is meant to deploy micro VMs in which the device model footprint is minimalistic. These are mostly KVM-based virtual machine monitors. As far as we know, only GCP supports virtio-rng. In the case of AWS Lambda, the OS guest has to rely on RDRAND.
This article has presented different sources of randomness that are used when a kernel is deployed in the context of a VM. In this case, the situation is slightly different than in baremetal and sources depend on the VMM and the cloud in which the VM is deployed. We have shown that VMs rely on either dedicated instructions or a paravirtualized device like virtio-rng. The main issue when using dedicated instructions is that they may not be available in all architectures. In the Nanos kernel, we have decided to focus on the virtio-rng device as a source of randomness. The virtio-rng device is supported by GCE and VMMs like QEMU and possibly in the near future Firecracker. However, it is not still supported in AWS. By relying on a dedicated device, nanos’s users can migrate applications across different device-models without the need to reconfigure them.