Re: [PATCH RFC v2] random: optionally block in getrandom(2) when the CRNG is uninitialized

From: Linus Torvalds
Date: Sun Sep 15 2019 - 13:32:39 EST


[ Added Lennart, who was active in the other thread ]

On Sat, Sep 14, 2019 at 10:22 PM Theodore Y. Ts'o <tytso@xxxxxxx> wrote:
>
> Thus, add an optional configuration option which stops getrandom(2)
> from blocking, but instead returns "best efforts" randomness, which
> might not be random or secure at all.

So I hate having a config option for something like this.

How about this attached patch instead? It only changes the waiting
logic, and I'll quote the comment in full, because I think that
explains not only the rationale, it explains every part of the patch
(and is most of the patch anyway):

* We refuse to wait very long for a blocking getrandom().
*
* The crng may not be ready during boot, but if you ask for
* blocking random numbers very early, there is no guarantee
* that you'll ever get any timely entropy.
*
* If you are sure you need entropy and that you can generate
* it, you need to ask for non-blocking random state, and then
* if that fails you must actively _do_something_ that causes
* enough system activity, perhaps asking the user to type
* something on the keyboard.
*
* Just asking for blocking random numbers is completely and
* fundamentally wrong, and the kernel will not play that game.
*
* We will block for at most 15 seconds at a time, and if called
* sequentially will decrease the blocking amount so that we'll
* block for at most 30s total - and if people continue to ask
* for blocking, at that point we'll just return whatever random
* state we have acquired.
*
* This will also complain loudly if the timeout happens, to let
* the distribution or system admin know about the problem.
*
* The process that gets the -EAGAIN will hopefully also log the
* error, to raise awareness that there may be use of random
* numbers without sufficient entropy.

Hmm? No strange behavior. No odd config variables. A bounded total
boot-time wait of 30s (which is a completely random number, but I
claimed it as the "big red button" time).

And if you only do it once and fall back to something else it will
only wait for 15s, and you'll have your error value so that you can
log it properly.

Yes, a single boot-time wait of 15s at boot is still "darn annoying",
but it likely

(a) isn't so long that people consider it a boot failure and give up
(but hopefully annoying enough that they'll report it)

(b) long enough that *if* the thing that is waiting is not actually
blocking the boot sequence, the non-blocked part of the boot sequence
should have time to do sufficient IO to get better randomness.

So (a) is the "the system is still usable" part. While (b) is the
"give it a chance, and even if it fails and you fall back on urandom
or whatever, you'll actually be getting good randomness even if we
can't perhaps _guarantee_ entropy".

Also, if you have some user that wants to do the old-timey ssh-keygen
thing with user input etc, we now have a documented way to do that:
just do the nonblocking thing, and then make really really sure that
you actually have something that generates more entropy if that
nonblocking thing returns EAGAIN. But it's also very clear that at
that point the program that wants this entropy guarantee has to _work_
for it.

Because just being lazy and say "block" without any entropy will
return EAGAIN for a (continually decreasing) while, but then at some
point stop and say "you're broken", and just give you the urandom
data.

Because if you really do nothing at all, and there is no activity
what-so-ever for 15s because you blocked the boot, then I claim that
it's better to return an error than to wait forever. And if you ignore
the error and just retry, eventually we'll do the fallback for you.

Of course, if you have something like rdrand, and told us you trust
it, none of this matters at all, since we'll have initialized the pool
long before.

So this is unconditional, but it's basically "unconditionally somewhat
flexibly reasonable". It should only ever trigger for the case where
the boot sequence was fundamentally broken. And it will complain
loudly (both at a kernel level, and hopefully at a systemd journal
level too) if it ever triggers.

And hey, if some distro wants to then revert this because they feel
uncomfortable with this, that's now _their_ problem, not the problem
of the upstream kernel. The upstream kernel tries to do something that
I think is arguably fairly reasonable in all situations.

Linus
drivers/char/random.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 52 insertions(+), 3 deletions(-)

diff --git a/drivers/char/random.c b/drivers/char/random.c
index 5d5ea4ce1442..0d7dc86e0f60 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -2118,6 +2118,55 @@ const struct file_operations urandom_fops = {
.llseek = noop_llseek,
};

+/*
+ * We refuse to wait very long for a blocking getrandom().
+ *
+ * The crng may not be ready during boot, but if you ask for
+ * blocking random numbers very early, there is no guarantee
+ * that you'll ever get any timely entropy.
+ *
+ * If you are sure you need entropy and that you can generate
+ * it, you need to ask for non-blocking random state, and then
+ * if that fails you must actively _do_something_ that causes
+ * enough system activity, perhaps asking the user to type
+ * something on the keyboard.
+ *
+ * Just asking for blocking random numbers is completely and
+ * fundamentally wrong, and the kernel will not play that game.
+ *
+ * We will block for at most 15 seconds at a time, and if called
+ * sequentially will decrease the blocking amount so that we'll
+ * block for at most 30s total - and if people continue to ask
+ * for blocking, at that point we'll just return whatever random
+ * state we have acquired.
+ *
+ * This will also complain loudly if the timeout happens, to let
+ * the distribution or system admin know about the problem.
+ *
+ * The process that gets the -EAGAIN will hopefully also log the
+ * error, to raise awareness that there may be use of random
+ * numbers without sufficient entropy.
+ */
+static int getrandom_wait(void)
+{
+ static unsigned int getrandom_timeout = 15*HZ;
+ unsigned int timeout = READ_ONCE(getrandom_timeout);
+ int ret;
+
+ /* "At some point you just have to give up on broken boot scripts" */
+ if (!timeout)
+ return 0;
+
+ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), timeout);
+ /* Success (>0) or interrupted (<0)? */
+ if (likely(ret))
+ return ret > 0 ? 0 : ret;
+
+ WRITE_ONCE(getrandom_timeout, timeout >> 1);
+ WARN_ONCE(1, "getrandom() timed out with no entropy");
+ return -EAGAIN;
+}
+
SYSCALL_DEFINE3(getrandom, char __user *, buf, size_t, count,
unsigned int, flags)
{
@@ -2132,11 +2181,11 @@ SYSCALL_DEFINE3(getrandom, char __user *, buf, size_t, count,
if (flags & GRND_RANDOM)
return _random_read(flags & GRND_NONBLOCK, buf, count);

- if (!crng_ready()) {
+ if (unlikely(!crng_ready())) {
if (flags & GRND_NONBLOCK)
return -EAGAIN;
- ret = wait_for_random_bytes();
- if (unlikely(ret))
+ ret = getrandom_wait();
+ if (ret)
return ret;
}
return urandom_read(NULL, buf, count, NULL);