Since Linux v3.17, getrandom() has been created as a new and more secure interface for pseudorandom data requests. It attempted to solve three problems as compared to /dev/urandom: 1. the need to access filesystem paths, which can fail, e.g. under a chroot 2. the need to open a file descriptor, which can fail under file descriptor exhaustion attacks 3. the possibility to get not-so-random data from /dev/urandom, due to an incompletely initialized kernel entropy pool To solve the third problem, getrandom(2) was made to block until a proper amount of entropy has been accumulated. This basically made the system call have no guaranteed upper-bound for its waiting time. As was said in c6e9d6f38894 (random: introduce getrandom(2) system call): "Any userspace program which uses this new functionality must take care to assure that if it is used during the boot process, that it will not cause the init scripts or other portions of the system startup to hang indefinitely." Meanwhile, user-facing Linux documentation, e.g. the urandom(4) and getrandom(2) manpages, didn't add such explicit warnings. It didn't also help that glibc, since v2.25, implemented an "OpenBSD-like" getentropy(3) in terms of getrandom(2). OpenBSD getentropy(2) never blocked though, while linux-glibc version did, possibly indefinitely. Since that glibc change, even more applications at the boot-path began to implicitly reques randomness through getrandom(2); e.g., for an Xorg/Xwayland MIT cookie. OpenBSD genentropy(2) never blocked because, as stated in its rnd(4) manpages, it saves entropy to disk on shutdown and restores it on boot. Moreover, the NetBSD bootloader, as shown in its boot(8), even have special commands to load a random seed file and pass it to the kernel. Meanwhile on a Linux systemd userland, systemd-random-seed(8) preserved a random seed across reboots at /var/lib/systemd/random-seed, but it never had the actual code, until very recently at v243, to ask the kernel to credit such entropy through an RNDADDENTROPY ioctl. >From a mix of the above factors, it began to be common for Embedded Linux systems to "get stuck at boot" unless a daemon like haveged is installed, or the BSP provider enabling the necessary hwrng driver in question and crediting its entropy; e.g. 62f95ae805fa (hwrng: omap - Set default quality). Over time, the issue began to even creep into consumer-level x86 laptops: mainstream distributions, like debian buster, began to recommend installing haveged as a workaround. Thus, on certain setups where there is no hwrng (embedded systems or VMs on a host lacking virtio-rng), or the hwrng is not trusted by some users (intel RDRAND), or sometimes it's just broken (amd RDRAND), the system boot can be *reliably* blocked. It can therefore be argued that there is no way to use getrandom() on Linux correctly, especially from shared libraries: GRND_NONBLOCK has to be used, and a fallback to some other interface like /dev/urandom is required, thus making the net result no better than just using /dev/urandom unconditionally. The issue is further exaggerated by recent file-system optimizations, e.g. b03755ad6f33 (ext4: make __ext4_get_inode_loc plug), which merges directory lookup code inode table IO, and thus minimizes the number of disk interrupts and entropy during boot. After that commit, a blocked boot can be reliably reproduced on a Thinkpad E480 laptop with standard ArchLinux user-space. Thus, don't trust user-space on calling getrandom(2) from the right context. Never block, by default, and just return data from the urandom source if entropy is not yet available. This is an explicit decision not to let user-space work around this through busy loops on error-codes. Note: this lowers the quality of random data returned by getrandom(2) to the level of randomness returned by /dev/urandom, with all the original security implications coming out of that, as discussed in problem "3." at the top of this commit log. If this is not desirable, offer users a fallback to old behavior, by CONFIG_RANDOM_BLOCK=y, or random.getrandom_block=true bootparam. [tytso@xxxxxxx: make the change to a non-blocking getrandom(2) optional] Link: https://lkml.kernel.org/r/20190914222432.GC19710@xxxxxxx Link: https://lkml.kernel.org/r/20190911173624.GI2740@xxxxxxx Link: https://factorable.net ("Widespread Weak Keys in Network Devices") Suggested-by: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx> Link: https://lkml.kernel.org/r/CAHk-=wjyH910+JRBdZf_Y9G54c1M=LBF8NKXB6vJcm9XjLnRfg@xxxxxxxxxxxxxx Rreported-by: Ahmed S. Darwish <darwish.07@xxxxxxxxx> Link: https://lkml.kernel.org/r/20190912034421.GA2085@darwi-home-pc Signed-off-by: Ahmed S. Darwish <darwish.07@xxxxxxxxx> --- Notes: changelog-v2: - tytso: make blocking optional changelog-v3: - more detailed commit log + historical context (thanks patrakov) - remove WARN_ON_ONCE. It's pretty excessive, and the first caller is systemd-random-seed(8), which we know it will not change. Just print errors in the kernel log. $dmesg | grep random: [0.235843] random: get_random_bytes called from start_kernel+0x30f/0x4d7 with crng_init=0 [0.685682] random: fast init done [2.405263] random: lvm: CRNG uninitialized (4 bytes read) [2.480686] random: systemd-random-: getrandom (512 bytes): CRNG not yet initialized [2.480687] random: systemd-random-: CRNG uninitialized (512 bytes read) [3.265201] random: dbus-daemon: CRNG uninitialized (12 bytes read) [3.835066] urandom_read: 1 callbacks suppressed [3.835068] random: polkitd: CRNG uninitialized (8 bytes read) [3.835509] random: polkitd: CRNG uninitialized (8 bytes read) [3.835577] random: polkitd: CRNG uninitialized (8 bytes read) [4.190653] random: gnome-session-b: getrandom (16 bytes): CRNG not yet initialized [4.190658] random: gnome-session-b: getrandom (16 bytes): CRNG not yet initialized [4.190662] random: gnome-session-b: getrandom (16 bytes): CRNG not yet initialized [4.952299] random: crng init done [4.952311] random: 3 urandom warning(s) missed due to ratelimiting [4.952314] random: 1 getrandom warning(s) missed due to ratelimiting drivers/char/Kconfig | 33 +++++++++++++++++++++++++++++++-- drivers/char/random.c | 33 ++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 3e866885a405..337baeca5ebc 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -557,8 +557,6 @@ config ADI and SSM (Silicon Secured Memory). Intended consumers of this driver include crash and makedumpfile. -endmenu - config RANDOM_TRUST_CPU bool "Trust the CPU manufacturer to initialize Linux's CRNG" depends on X86 || S390 || PPC @@ -573,3 +571,34 @@ config RANDOM_TRUST_CPU has not installed a hidden back door to compromise the CPU's random number generation facilities. This can also be configured at boot with "random.trust_cpu=on/off". + +config RANDOM_BLOCK + bool "Block if getrandom is called before CRNG is initialized" + help + Say Y here if you want userspace programs which call + getrandom(2) before the Cryptographic Random Number + Generator (CRNG) is initialized to block until + secure random numbers are available. + + Say N if you believe usability is more important than + security, so if getrandom(2) is called before the CRNG is + initialized, it should not block, but instead return "best + effort" randomness which might not be very secure or random + at all; but at least the system boot will not be delayed by + minutes or hours. + + This can also be controlled at boot with + "random.getrandom_block=on/off". + + Ideally, systems would be configured with hardware random + number generators, and/or configured to trust CPU-provided + RNG's. In addition, userspace should generate cryptographic + keys only as late as possible, when they are needed, instead + of during early boot. (For non-cryptographic use cases, + such as dictionary seeds or MIT Magic Cookies, other + mechanisms such as /dev/urandom or random(3) may be more + appropropriate.) This config option controls what the + kernel should do as a fallback when the non-ideal case + presents itself. + +endmenu diff --git a/drivers/char/random.c b/drivers/char/random.c index 4a50ee2c230d..689fdb486785 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -511,6 +511,8 @@ static struct ratelimit_state unseeded_warning = RATELIMIT_STATE_INIT("warn_unseeded_randomness", HZ, 3); static struct ratelimit_state urandom_warning = RATELIMIT_STATE_INIT("warn_urandom_randomness", HZ, 3); +static struct ratelimit_state getrandom_warning = + RATELIMIT_STATE_INIT("warn_getrandom_randomness", HZ, 3); static int ratelimit_disable __read_mostly; @@ -854,12 +856,19 @@ static void invalidate_batched_entropy(void); static void numa_crng_init(void); static bool trust_cpu __ro_after_init = IS_ENABLED(CONFIG_RANDOM_TRUST_CPU); +static bool getrandom_block __ro_after_init = IS_ENABLED(CONFIG_RANDOM_BLOCK); static int __init parse_trust_cpu(char *arg) { return kstrtobool(arg, &trust_cpu); } early_param("random.trust_cpu", parse_trust_cpu); +static int __init parse_block(char *arg) +{ + return kstrtobool(arg, &getrandom_block); +} +early_param("random.getrandom_block", parse_block); + static void crng_initialize(struct crng_state *crng) { int i; @@ -1053,6 +1062,12 @@ static void crng_reseed(struct crng_state *crng, struct entropy_store *r) urandom_warning.missed); urandom_warning.missed = 0; } + if (getrandom_warning.missed) { + pr_notice("random: %d getrandom warning(s) missed " + "due to ratelimiting\n", + getrandom_warning.missed); + getrandom_warning.missed = 0; + } } } @@ -1915,6 +1930,7 @@ int __init rand_initialize(void) crng_global_init_time = jiffies; if (ratelimit_disable) { urandom_warning.interval = 0; + getrandom_warning.interval = 0; unseeded_warning.interval = 0; } return 0; @@ -1984,8 +2000,8 @@ urandom_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) if (!crng_ready() && maxwarn > 0) { maxwarn--; if (__ratelimit(&urandom_warning)) - printk(KERN_NOTICE "random: %s: uninitialized " - "urandom read (%zd bytes read)\n", + pr_err("random: %s: CRNG uninitialized " + "(%zd bytes read)\n", current->comm, nbytes); spin_lock_irqsave(&primary_crng.lock, flags); crng_init_cnt = 0; @@ -2152,9 +2168,16 @@ SYSCALL_DEFINE3(getrandom, char __user *, buf, size_t, count, if (!crng_ready()) { if (flags & GRND_NONBLOCK) return -EAGAIN; - ret = wait_for_random_bytes(); - if (unlikely(ret)) - return ret; + + if (__ratelimit(&getrandom_warning)) + pr_err("random: %s: getrandom (%zd bytes): CRNG not " + "yet initialized", current->comm, count); + + if (getrandom_block) { + ret = wait_for_random_bytes(); + if (unlikely(ret)) + return ret; + } } return urandom_read(NULL, buf, count, NULL); } -- darwi http://darwish.chasingpointers.com