Skip to content

Instantly share code, notes, and snippets.

@zarmin
Created October 14, 2020 08:25
Show Gist options
  • Save zarmin/168cd474433a179647c41ad57f0786f8 to your computer and use it in GitHub Desktop.
Save zarmin/168cd474433a179647c41ad57f0786f8 to your computer and use it in GitHub Desktop.
RDRAND

RDRAND

Check if supported

From shell

cat /proc/cpuinfo | fgrep rdrand

From CPUID

#include <stdio.h>
#include <cpuid.h>

/* %ecx */
#define bit_RDRND (1 << 30)

int main() {
    unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0;
    __get_cpuid(1, &eax, &ebx, &ecx, &edx);

    if (bit_RDRND & ecx) {
        puts("RDRAND supported\n");
    } else {
        puts("RDRAND NOT supported\n");
    }
}

remark: __builtin_cpu_supports GCC intrinsics does not support the rdrand bit

compile

gcc cpuinfo.c -o cpuinfo

example output

$ ./cpuinfo
RDRAND supported

Support in CPUs

In intel, it's supported from Ivy Bridge (i.e. second generation i*-2*** cpus).

On AMD it is supported from Zen (Gen 1.) architecture, but it had serious issues in the past

https://linuxreviews.org/AMD_Ryzen_3000_series_CPUs_can%27t_do_Random_on_boot_causing_Boot_Failure_on_newer_Linux_distributions

Enable / disable

RDRAND is enabled by default and cannot be disabled.

  • RDRAND only can be disabled via a microcode update on the CPU, which is undocumented by Intel.
  • Linux kernel can be started with nordrand or random.trust_cpu=0 option, which disables its usage in kernel functions (random, urandom, ASLR, KASLR, etc.)
  • There is no resource efficient way to filter out CPU opcodes from hardware. Only binary translation based virtualization or full software emulation can disable or modify the behaviour of RDRAND (example in fault testing part).

Test it out

Fortunately GCC supports RDRAND as intrinsics (with -mrdrnd), so no inline ASM is required.

#include <stdio.h>
#include <immintrin.h>

int main() {
    unsigned long long int result = 0ULL;

    for (int i = 0; i < 10; i++) {
        int rc = _rdrand64_step (&result);

        if (rc == 1) {
            printf("%016llx\n", result);
        } else {
            printf("failed to generate random number\n");
        }
    }

    return 0;
}

Build and test it out.

$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

$ gcc -mrdrnd rdrand.c -Wall -o rdrand

$ ./rdrand
97709d079ea66d4a
902e75564ae84836
8addf245b4861af5
73cb2ef2b5cca40e
6c47ae93df7feffc
118c774743b28a8a
f08718c072a6a878
042949e48f1d0bc0
0226bfc7e63ff258
2f58b21171457403

Check assembly output

# objdump -M intel -S ./rdrand | fgrep -A 4 -B 4 rdrand

./rdrand:     file format elf64-x86-64


Disassembly of section .init:

--
 720:	eb 4e                	jmp    770 <main+0x76>
 722:	48 8d 45 e8          	lea    rax,[rbp-0x18]
 726:	48 89 45 f0          	mov    QWORD PTR [rbp-0x10],rax
 72a:	48 8b 55 f0          	mov    rdx,QWORD PTR [rbp-0x10]
 72e:	48 0f c7 f0          	rdrand rax
 732:	48 89 02             	mov    QWORD PTR [rdx],rax
 735:	ba 01 00 00 00       	mov    edx,0x1
 73a:	0f 42 c2             	cmovb  eax,edx
 73d:	89 45 e4             	mov    DWORD PTR [rbp-0x1c],eax

RDRAND native instruction is called in line 0x72e.

Detailed documentations

https://software.intel.com/content/www/us/en/develop/articles/intel-digital-random-number-generator-drng-software-implementation-guide.html

Retry recommendations

Unlike the RDRAND instruction, the seed values come directly from the entropy conditioner, and it is possible for callers to invoke RDSEED faster than those values are generated. This means that applications must be designed robustly and be prepared for calls to RDSEED to fail because seeds are not available (CF=0).

If only one thread is calling RDSEED infrequently, it is very unlikely that a random seed will not be available. Only during periods of heavy demand, such as when one thread is calling RDSEED in rapid succession or multiple threads are calling RDSEED simultaneously, are underflows likely to occur. Because the RDSEED instruction does not have a fairness mechanism built into it, however, there are no guarantees as to how often a thread should retry the instruction, or how many retries might be needed, in order to obtain a random seed. In practice, this depends on the number of hardware threads on the CPU and how aggressively they are calling RDSEED.

Since there is no simple procedure for retrying the instruction to obtain a random seed, follow these basic guidelines.

RDRAND in docker

Docker will map / mount the host's cpuinfo for /proc/cpuinfo. No further configuration is needed. Also all of the checked solutions are not using /proc/cpuinfo for CPU feature detection, but CPUID native instruction (see the support check part).

Docker also can't filter the x64 instructions, so it can't disable the RDRAND instruction (see enable/disable part).

Testing the negative scenarios

How to test a system without RDRAND?

Get a 6+ year old intel CPU, before IvyBridge.

Or use QEMU to emulate a SandyBridge (latest CPU before RDRAND support) and install an Ubuntu 20 to it.

qemu-img create -f qcow2 hd_img.img 20g
qemu-system-x86_64 -cdrom ./ubuntu-20.04.1-live-server-amd64.iso -hda ./hd_img.img -m 2g -cpu SandyBridge -boot menu=on

SandyBridge emulation is resource intensive, it can't use VT-X, only runtime binary translation, which is slow on x64.

An install will took about an hour on a modern PC. (Alternatively you can use -cpu host option if the hosts system supports KVM, install the system with hardware assisted virtualization and reboot it using SandyBridge.)

Results

  • CPUID does not have the RDRAND bit (ecx reg, 30th bit)
  • rdrand missing from /proc/cpuinfo
  • RDRAND will throw a SIGILL error and the program is terminated

How to test it on faulty system?

"Faulty system" is a system, where random is weak, or has low entropy. It's quite hard to taint the CPU intentionally to corrupt the quality of the random, but there were occurences in the past, when a CPU bug caused an issue (especially shortly after booting the system).

It can be tested by modifying the target_ulong HELPER(rdrand)(CPUX86State *env) function in ./target/i386/int_helper.c in the source code of qemu, recompile it and give it a try.

Linux kernel and RDRAND

Newer linux kernels (from 4.19) are using a combination of RDRAND and an internal algorithm to produce random numbers for /dev/random and /dev/urandom.

https://research.jvroig.com/linuxrand/DevsJustUSeUrandPlease_Templated_2018-05-19.pdf

The NativePRNG is using /dev/random to seed the SecureRandom instance and /dev/urandom to salt the output after that.

https://github.com/openjdk/jdk/blob/270674ce1b1b8d44bbe92949c3f7db7b7c767cac/src/java.base/unix/classes/sun/security/provider/NativePRNG.java#L151

This may can be enough...

Java and RDRAND

The best way is to use a native library with JNI

https://github.com/cambecc/drnglib

  • This contains libraries for multiple operating system, so it will work and can be built on MacOS / Linux / Windows
  • This is using the CPUID based RDRND feature detection and not relies on /proc/cpuinfo and throws a Java exception if it's not supported
  • Needs simple GCC on linux (but not further deps) to build it, on windows it uses a preshipped DLL.

There are another hacky and more complicated alternative solutions, like

Testing the random quality

Usually this metric is measured by "diehard tests", like the rng-tools package.

Unfortunately these won't provide a simple GO / NOGO metric.

More info

The easiest way is to request a few hundres kbytes on the start from the Java app and compress (e.g. with the builtin GZIPOutputStream) it and it should not have more than 0.1% of size change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment