diff mbox series

random: allow writes to /dev/urandom to influence fast init

Message ID 20220322191436.110963-1-Jason@zx2c4.com (mailing list archive)
State Not Applicable
Delegated to: Herbert Xu
Headers show
Series random: allow writes to /dev/urandom to influence fast init | expand

Commit Message

Jason A. Donenfeld March 22, 2022, 7:14 p.m. UTC
For as far back as I can tell, writing to /dev/urandom or /dev/random
will put entropy into the pool, but won't immediately use it, and won't
credit it either. Instead, crediting is controlled by the ioctls
RNDADDTOENTCNT and RNDADDENTROPY. If, however, this happens during early
boot before the input pool is ready, we have a dangerous situation with
seed files as commonly used by shell scripts:

  dd if=seedfile of=/dev/urandom # write seed into pool
  dd if=/dev/urandom of=seedfile count=1 bs=32 # read new seed for next boot

Since the entropy in seedfile isn't credited there, this won't cause the
RNG to transition from crng_init=0 to crng_init=2, and so when we make a
new seedfile for next boot, we'll still be getting crng_init=0-quality
randomness, which may well regress from the original seedfile.

I fixed a related issue in systemd with
<https://github.com/systemd/systemd/commit/da2862ef06f22fc8d31dafced6d2d6dc14f2ee0b>,
which is a clean way of doing it, but it doesn't really help with
existing shell scripts. And the behavior here remains bad and
surprising.

So this patch fixes the issue by including /dev/urandom writes as part
of the "fast init", but not crediting it as part of the fast init
counter. This is more or less exactly what's already done for
kernel-sourced entropy whose quality we don't know, when we use
add_device_randomness(), which both contributes to the input pool and to
the fast init key.

There is one caveat to consider, which is what happens if the user
additionally calls RNDADDTOENTCNT after having written to /dev/urandom,
expecting to credit that write. That might give way to this pathological
pattern:

   - write(urandom_fd, &single_byte, 1);
   - ioctl(urandom_fd, RNDADDTOENTCNT, 8);
   - attacker brute forces that single byte.
   - write(urandom_fd, &single_byte, 1);
   - ioctl(urandom_fd, RNDADDTOENTCNT, 8);
   - attacker brute forces that single byte.
   - write(urandom_fd, &single_byte, 1);
   - ioctl(urandom_fd, RNDADDTOENTCNT, 8);
   - attacker brute forces that single byte.
   - write(urandom_fd, &single_byte, 1);
   - ioctl(urandom_fd, RNDADDTOENTCNT, 8);
   - attacker brute forces that single byte.
   - write(urandom_fd, &single_byte, 1);
   - ioctl(urandom_fd, RNDADDTOENTCNT, 8);
   - attacker brute forces that single byte.
   - ...

After this goes 32 times, the crng has now initialized, except an
attacker was able to bruteforce the whole state, making for a sort of
boot time "premature next" problem.

This is bad, but the case above is pretty pathological. Part of this
stems from the fact that /dev/urandom write + RNDADDTOENTCNT is a poor
interface, because it's unclear whether the crediting will follow the
writing, whereas we know in the add_device_randomness() case that we
_won't_ credit it, so we don't have to worry about that.

The better interface for userspace is RNDADDENTROPY, which takes the
input buffer and the entropy credit all at once, so we can make the
right decision. For the RNDADDENTROPY, we do not take part in fast init
if entropy is being credited.

And perhaps we might consider attempting to deprecate RNDADDTOENTCNT at
some point in the future.

Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Dominik Brodowski <linux@dominikbrodowski.net>
Cc: Theodore Ts'o <tytso@mit.edu>
Cc: Jann Horn <jannh@google.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
---
This isn't a "perfect" solution, and the of entropy accounting sort of
points both to problems there and to how the "fast init" phase fits in
to the overall design. So I'll think this over a bit for a few days, and
maybe it might lead to more improvements in fast init handling later.

 drivers/char/random.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

Comments

Linus Torvalds March 22, 2022, 8:42 p.m. UTC | #1
On Tue, Mar 22, 2022 at 12:15 PM Jason A. Donenfeld <Jason@zx2c4.com> wrote:
>
> @@ -1507,6 +1507,8 @@ static int write_pool(const char __user *ubuf, size_t count)
>                 }
>                 count -= len;
>                 ubuf += len;
> +               if (unlikely(crng_init == 0 && !will_credit))
> +                       crng_pre_init_inject(block, len, false);
>                 mix_pool_bytes(block, len);
>                 cond_resched();
>         }

Ugh. I hate that whole crng_pre_init_inject() dance.

We already mix the data into the input_pool with that 'mix_pool_bytes()' call.

So what I think the real fix is, is to just make urandom_read() use
the input_pool data directly for initializing the state.

IOW, why isn't the patch along the lines of just making
crng_make_state() take the data from the input pool instead, when
crng_ready() isn't set?

As a broken example patch, something like the appended (except that
doesn't build, because 'input_pool' is declared later)?

So take this purely as a conceptual patch, not a real patch.

(Yeah, I think this also means that code that currently does that

                crng_pre_init_inject(pool, sizeof(pool), true);
                mix_pool_bytes(pool, sizeof(pool));

should do those two operations in the reverse order, so that the input
pool is always updated before that crng_pre_init_inject() dance).

Maybe I'm missing something. But it seems kind of silly to use
base_crng AT ALL before crng_ready(). Why not use the pool we have
that *is* actually updated (that 'input_pool')?

                Linus

@@ -374,19 +374,14 @@ static void crng_make_state(u32
chacha_state[CHACHA_STATE_WORDS],
        /*
         * For the fast path, we check whether we're ready, unlocked first, and
         * then re-check once locked later. In the case where we're really not
-        * ready, we do fast key erasure with the base_crng directly, because
-        * this is what crng_pre_init_inject() mutates during early init.
+        * ready, we do fast key erasure with the input pool directly.
         */
        if (!crng_ready()) {
-               bool ready;
-
-               spin_lock_irqsave(&base_crng.lock, flags);
-               ready = crng_ready();
-               if (!ready)
-                       crng_fast_key_erasure(base_crng.key, chacha_state,
-                                             random_data, random_data_len);
-               spin_unlock_irqrestore(&base_crng.lock, flags);
-               if (!ready)
+               spin_lock_irqsave(&input_pool.lock, flags);
+               crng_fast_key_erasure(input_pool.key, chacha_state,
+                                     random_data, random_data_len);
+               spin_unlock_irqrestore(&input_pool.lock, flags);
+               if (!crng_ready())
                        return;
        }
Jason A. Donenfeld March 22, 2022, 11:54 p.m. UTC | #2
Hey Linus,

On Tue, Mar 22, 2022 at 2:42 PM Linus Torvalds
<torvalds@linux-foundation.org> wrote:
>
> On Tue, Mar 22, 2022 at 12:15 PM Jason A. Donenfeld <Jason@zx2c4.com> wrote:
> >
> > @@ -1507,6 +1507,8 @@ static int write_pool(const char __user *ubuf, size_t count)
> >                 }
> >                 count -= len;
> >                 ubuf += len;
> > +               if (unlikely(crng_init == 0 && !will_credit))
> > +                       crng_pre_init_inject(block, len, false);
> >                 mix_pool_bytes(block, len);
> >                 cond_resched();
> >         }
>
> Ugh. I hate that whole crng_pre_init_inject() dance.

Yea it's not great, but it's a helluva lot better than what was there
before 5.18, when there were two different ways of mixing into the
crng key, both of which were insecure. At least now it's a hash
function. As I mentioned in that pull request, I tried to shore up the
existing design as much as I could without departing from it in any
large fundamental ways. It may well be time to revisit the pre init
situation though.

Maybe it'd help if I described what the goals are of the current
approach and how it attempts to accomplish that.

The chacha key that's used for random bytes is separate from the input
pool, which is where entropy gets dumped. The reason for that
separation is because it's important that we decide when and under
what conditions to extract from the input pool into a new chacha key.
If we do it too often or prematurely, then we risk updating the chacha
key with very few new bits of entropy. If there aren't enough new
bits, and an attacker knows the old state (say, because the system
just booted), then it's trivial to bruteforce those new bits, since
read access to /dev/urandom is unprivileged. That's very bad.

For that reason, we only reseed when we've collected 256 bits of
entropy, and at a schedule of uptime/2 for the first 10 minutes, and
then every 5 minutes after that.

The question, here, is what to do before we've collected 256 bits of
entropy. 80 bits is still better than 0, for example. The existing
scheme (from Ted) is that we maintain a state variable, crng_init.
When crng_init=0, we direct all entropy into the chacha key directly.
If that entropy is creditable, then we account for up to 64 bytes of
input total, regardless of how many "bits" of entropy we would
otherwise be crediting were it not pre-init phase. That's kinda weird,
but the motivating mantra has always been, "fast init is garbage, but
it's better than nothing." Then, once we hit 64 bytes of fast init
accounting, we transition to crng_init=1. At this stage, we're putting
bytes into the normal input pool and crediting it as usual, not
updating the crng key directly anymore with injection, and the crng is
still blocked. When we hit 256 bits of accounted entropy from the
crng_init=1 stage, then we're ready to go, and we transition to
crng_init=2.

So this patch helps with the crng_init=0 case. It doesn't address the
crng_init=1 case, when the bytes written to /dev/urandom won't be
directly injected as they are for crng_init=0. In any case, the one
thing we're trying to preserve in all of this is that when we do
transition to crng_init=2, it's with a pool that contains 256 bits of
entropy that haven't previously been exposed anywhere, so that they
can't be brute forced.

With that as background, to answer your question:

> Maybe I'm missing something. But it seems kind of silly to use
> base_crng AT ALL before crng_ready(). Why not use the pool we have
> that *is* actually updated (that 'input_pool')?

In this case, base_crng.key is really just doubling as a separate
pool. The "pre init pool", the "fast init pool", the "super terrible
but at least not zero pool", the "all bets are off pool", ... whatever
you want to call it. Why a separate pool for pre init? Because the
real input pool needs to accumulate 256 bits of entropy before it's
safe to use.

Your suggestion is to instead not have a separate pool, but perhaps
just do separate accounting. That might work to some degree, but the
devil is in the details, and that sounds a lot harder and messier to
code. For example, you wrote:

> +               crng_fast_key_erasure(input_pool.key, chacha_state,
> +                                     random_data, random_data_len);

Except there is no input_pool.key, because the input_pool's state is
actually an un-finalized hash function. So what you wind up with
instead is calling extract_entropy() on every read. But then you have
to decide a certain point when you stop doing that, so it can
accumulate 256 bits prior to exposure, and things quickly get
extremely messy. So I don't think your suggestion improves much.

The long term solution to this stuff might be doing away with the
entropy accounting entirely, as I suggested in the other thread. The
shorter term solution, though, might be reimagining how the
crng_init=1/2 states work to begin with. (And the shortest-term "fix"
is this patch, which while not perfect is at least something.)

Just sort of spitballing what the shorter term solution might be, we
could do something exponential, where we get rid of the
pre_init_inject stuff entirely, but call `crng_reseed(force=true)` at
1 bit, 2 bits, 4 bits, 8 bits, 16 bits, 32 bits, ... 256 bits, the
same way we do now with the time. (This is somewhat similar to what NT
does, fwiw.) A downside is that the gap between, say, 64 bits and 128
bits is much "longer" than what we have now with pre_init_inject.

While the above might be an improvement, that doesn't help with our
/dev/urandom problem, though. Perhaps for that we could have some
messy heuristic, like `if (capable(CAP_SYS_ADMIN) && !crng_ready())
crng_reseed(force=true);`. That's sort of ugly too, but it would help
with the /dev/urandom shellscript seeders.

I'll think on it some more, but these two spitball ideas together
might result in a nice simplification by eliminating the fast pool
entirely. Happy to hear more ideas from you too if the above inspires
anything.

Jason
David Laight March 23, 2022, 2:15 a.m. UTC | #3
From: Jason A. Donenfeld
> Sent: 22 March 2022 19:15
> 
> For as far back as I can tell, writing to /dev/urandom or /dev/random
> will put entropy into the pool, but won't immediately use it, and won't
> credit it either. Instead, crediting is controlled by the ioctls
> RNDADDTOENTCNT and RNDADDENTROPY. If, however, this happens during early
> boot before the input pool is ready, we have a dangerous situation with
> seed files as commonly used by shell scripts:
> 
>   dd if=seedfile of=/dev/urandom # write seed into pool
>   dd if=/dev/urandom of=seedfile count=1 bs=32 # read new seed for next boot
> 
> Since the entropy in seedfile isn't credited there, this won't cause the
> RNG to transition from crng_init=0 to crng_init=2, and so when we make a
> new seedfile for next boot, we'll still be getting crng_init=0-quality
> randomness, which may well regress from the original seedfile.

Never mind scripts that try to immediately save a new seedfile [1].

What about code run by later startup scripts that wants random numbers.
They really do want the seedfile data to be used.
If it isn't used then they are likely to get very weak random numbers.

You can't really expect startup scripts to be issuing ioctl requests.

[1] I suspect the initial 'save' is just there to ensure that the
random numbers don't exactly repeat if the system crashes.
The seedfile will be written again during normal shutdown.


	David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
Jason A. Donenfeld March 23, 2022, 2:50 a.m. UTC | #4
Hi David (and Linus),

On Tue, Mar 22, 2022 at 8:16 PM David Laight <David.Laight@aculab.com> wrote:
> Never mind scripts that try to immediately save a new seedfile [1].
>
> What about code run by later startup scripts that wants random numbers.
> They really do want the seedfile data to be used.
> If it isn't used then they are likely to get very weak random numbers.
>
> You can't really expect startup scripts to be issuing ioctl requests.

To be clear, this "expect[ation]" of yours is very much a new
expectation. Crediting bits has required an ioctl since forever. Those
shell scripts have been broken forever. The proposal here is to add new
behavior to support those old broken shell scripts.

Fortunately, it seems sort of fixable. But only sort of. There are also
a lot of complications, as detailed above. Part of it is that some
people use /dev/urandom writes expecting it not to credit, while others
use that and then later manually credit it with the ioctl. Both of those
in general seem like not a very good interface for seeding the rng. The
correct interface to use is RNDADDENTROPY, which takes both the data and
whether it should be credited, since then the kernel knows what the
intentions are and can do something smart with it. Barring that
knowledge, we're in this vague middle ground where it's unclear what the
user intends to do.

"But I want my shell scripts to work, even if they have never worked,"
you fairly protest. I'll do my best here to figure something out, but
note that introducing sane behavior to /dev/urandom writes might imply
breaking compatibility with the behavior /dev/urandom writes have always
had in the past. So the "perfect" solution for a shell script seeding
interface might prove elusive, and we'll be left with something merely,
"not terrible."

However, if you do think you have a "perfect" solution that takes into
account all these concerns and doesn't break any prior contracts, please
do pipe up.

Two more thoughts, also for Linus' consideration:

- Had we not needed to revert the /dev/urandom + /dev/random unification
  patch, we wouldn't be facing this problem. So a last minute creative
  solution to save that effort would be quite welcome. I'm not
  optimistic about us finding that right away, but if a lightbulb goes
  off, I'd be quite happy.

- Since these seeding shell scripts have always been broken, because
  this is how the rng has always been, rather than trying to bolt on a
  very imperfect fix in the kernel for something that never worked
  right, we could suggest shell scripts take the path that I implemented
  for systemd:
  https://github.com/systemd/systemd/commit/da2862ef06f22fc8d31dafced6d2d6dc14f2ee0b
  In shell, this would look like:

    #!/bin/bash
    cat seedfile > /dev/urandom
    { cat seedfile; head -c 32 /dev/urandom; } | sha256sum | cut -d ' ' -f 1 > seedfile
 
  This seems better in nearly every way to trying to retroactively fix it
  in the kernel by changing the semantics of an extremely old interface.
  The more I think about it, the more I'm inclined to go with this "do
  nothing" approach. Open to hearing your thoughts though.

Jason
Theodore Ts'o March 23, 2022, 3:35 a.m. UTC | #5
On Tue, Mar 22, 2022 at 01:14:36PM -0600, Jason A. Donenfeld wrote:
> So this patch fixes the issue by including /dev/urandom writes as part
> of the "fast init", but not crediting it as part of the fast init
> counter. This is more or less exactly what's already done for
> kernel-sourced entropy whose quality we don't know, when we use
> add_device_randomness(), which both contributes to the input pool and to
> the fast init key.

One of the big issues with /dev/urandom writes is that *anyone*,
including malicious user space, can force specific bytes to be mixed
in.  That's the source of the reluctance to immediate use inputs from
writes into /dev/[u]random until there is a chance for it to be mixed
in with other entropy which is hopefully not under the control of
malicious userspace.

Now, I recognize that things are a bit special in early boot, and if
we have a malicious script running in a systemd unit script, we might
as well go home.  But something to consider is whether we want to do
soemthing special if the process writing to /dev/[u]random has
CAP_SYS_ADMIN, or some such.

> There is one caveat to consider, which is what happens if the user
> additionally calls RNDADDTOENTCNT after having written to /dev/urandom,
> expecting to credit that write. That might give way to this pathological
> pattern:

Yeah, no one should ever ver ever be using RNDADDTOENTCNT.  It's an
ioctl which requires root privilegs, and if it breaks, you get to keep
both pieces.

> The better interface for userspace is RNDADDENTROPY, which takes the
> input buffer and the entropy credit all at once, so we can make the
> right decision. For the RNDADDENTROPY, we do not take part in fast init
> if entropy is being credited.
> 
> And perhaps we might consider attempting to deprecate RNDADDTOENTCNT at
> some point in the future.

That would be a good idea.  :-)

						- Ted
Jason A. Donenfeld March 23, 2022, 4 a.m. UTC | #6
Hi Ted,

On Tue, Mar 22, 2022 at 9:35 PM Theodore Ts'o <tytso@mit.edu> wrote:
> One of the big issues with /dev/urandom writes is that *anyone*,
> including malicious user space, can force specific bytes to be mixed
> in.  That's the source of the reluctance to immediate use inputs from
> writes into /dev/[u]random until there is a chance for it to be mixed
> in with other entropy which is hopefully not under the control of
> malicious userspace.

Right, sort of. Since we now always use a cryptographic hash function,
we can haphazardly mix whatever any user wants, without too much
concern. The issue is whether we _credit_ those bits. Were we to credit
those bits, a malicious unpriv'd user could credit 248 bits of known
input, and then bruteforce 8 bits of unknown input, and repeat, and in
that way destroy the security of the thing. So, yea, the current
reluctance does make sense.

> Now, I recognize that things are a bit special in early boot, and if
> we have a malicious script running in a systemd unit script, we might
> as well go home.  But something to consider is whether we want to do
> soemthing special if the process writing to /dev/[u]random has
> CAP_SYS_ADMIN, or some such.

Exactly. So one way of approaching this would be to simply credit writes
to /dev/urandom if it's CAP_SYS_ADMIN and maybe if also !crng_ready(),
and just skip the crng_pre_init_inject() part that this current patch
adds. I'll attach a sample patch of what this might look like at the end
of this email.

The problem with that approach, though, is that various userspaces might
already write garbage into /dev/urandom, not expecting them to be
credited -- for example, some userspace hardware configuration component
that writes some serial number there. So I'm sort of hesitant to
_change_ the behavior that we've had for so long.

Another variation on that would be to do what this current patch does,
but only crng_pre_init_inject() on CAP_SYS_ADMIN. But this has the same
pitfall of only working as intended at cnrg_init=0 but not crng_init=1.
That's better than nothing, but it's not perfect, and it introduces that
problem with RNDADDTOENTCNT.

Also, to echo the point I made in the email I just sent to David, this
has _always_ been broken, and those shell scripts have _always_ been
vulnerable. Maybe the kernel should fix that, but due to the ambiguity
of the /dev/urandom write interface, maybe the best fix is actually in
userspace itself, which means it'd work on old kernels too (which are
rather common for the embedded devices that tend to have those types of
shell scripts). Specifically, I'm talking about this fix I did for
systemd:

https://github.com/systemd/systemd/commit/da2862ef06f22fc8d31dafced6d2d6dc14f2ee0b

And then the same fix that I just submitted this evening for Buildroot:

https://lists.buildroot.org/pipermail/buildroot/2022-March/639359.html

It seems like hashing the old seed together with the new one ought to be
a good standard practice to mitigate against all sorts of bugs.

> Yeah, no one should ever ver ever be using RNDADDTOENTCNT.  It's an
> ioctl which requires root privilegs, and if it breaks, you get to keep
> both pieces.
>
> > And perhaps we might consider attempting to deprecate RNDADDTOENTCNT at
> > some point in the future.
>
> That would be a good idea.  :-)

Oh cool, I'm glad you agree. Let's do that then. Have a preferred path?
Maybe just a pr_once() saying not to use it?

Jason

--------8<-------------------------------------------

The CAP_SYS_ADMIN idea, maybe not so good, but here for illustration:

diff --git a/drivers/char/random.c b/drivers/char/random.c
index 706f08edf0dc..d1dc46366647 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -1493,12 +1493,16 @@ static __poll_t random_poll(struct file *file, poll_table *wait)
 	return mask;
 }
 
-static int write_pool(const char __user *ubuf, size_t count)
+static int write_pool(const char __user *ubuf, size_t count, bool credit_at_boot)
 {
-	size_t len;
+	size_t len, bits;
 	int ret = 0;
 	u8 block[BLAKE2S_BLOCK_SIZE];
 
+	if (count > SIZE_MAX / 8)
+		return -EINVAL;
+	bits = count * 8;
+
 	while (count) {
 		len = min(count, sizeof(block));
 		if (copy_from_user(block, ubuf, len)) {
@@ -1511,6 +1515,9 @@ static int write_pool(const char __user *ubuf, size_t count)
 		cond_resched();
 	}
 
+	if (credit_at_boot && !crng_ready() && capable(CAP_SYS_ADMIN))
+		credit_entropy_bits(bits);
+
 out:
 	memzero_explicit(block, sizeof(block));
 	return ret;
@@ -1521,7 +1528,7 @@ static ssize_t random_write(struct file *file, const char __user *buffer,
 {
 	int ret;
 
-	ret = write_pool(buffer, count);
+	ret = write_pool(buffer, count, true);
 	if (ret)
 		return ret;
 
@@ -1584,7 +1591,7 @@ static long random_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
 			return -EINVAL;
 		if (get_user(size, p++))
 			return -EFAULT;
-		retval = write_pool((const char __user *)p, size);
+		retval = write_pool((const char __user *)p, size, false);
 		if (retval < 0)
 			return retval;
 		credit_entropy_bits(ent_count);
Alex Xu (Hello71) March 23, 2022, 4:30 a.m. UTC | #7
I searched for users of RNDADDTOENTCNT using 
(?s:ioctl.{1,500}RNDADDTOENTCNT) on Debian Code Search and 
"/(?s)ioctl.{1,40},\s*RNDADDTOENTCNT/ -path:incfs_test.c" on GitHub Code 
Search (beta).

Several programs use it for testing purposes, without writing any 
entropy to /dev/random or /dev/urandom, including rauc, wireguard, and 
openSUSE kdump. Several programs use it as intended, after writing 
entropy to /dev/random or /dev/urandom. Of the latter group,

- kata-containers is a lightweight VM implementation. Its guest-side 
  agent offers a gRPC endpoint which will write the provided data to 
  /dev/random, then call RNDADDTOENTCNT with the length of the data, 
  then call RNDRESEEDRNG. As far as I can tell, this endpoint is 
  made available to users on the host, but is not used by 
  kata-containers itself.

- aws-nitro-enclaves-sdk-c is an SDK for building lightweight VMs to be 
  used with AWS Nitro Enclaves. kmstool-enclave is a sample application 
  provided, which writes "up to 256 bytes" (from where?) to /dev/random, 
  then calls RNDADDTOENTCNT, then repeats the process until it reaches 
  1024 bytes.

- sandy-harris/maxwell is a "jitter entropy" daemon, similar to haveged. 
  It writes 4 bytes of "generated entropy" to /dev/random, then calls 
  RNDADDTOENTCNT, then repeats.

- guix is, among other things, a "GNU/"Linux distribution. The provided 
  base services write the seed file to /dev/urandom, then call 
  RNDADDTOENTCNT, then write 512 bytes from /dev/hwrng to /dev/urandom, 
  then call RNDADDTOENTCNT, then "immediately" read 512 bytes from 
  /dev/urandom and write it to the seed file. On shutdown, 512 bytes are 
  read from /dev/urandom and written to the seed file.

I was unable to locate any other public non-archived usages of 
RNDADDTOENTCNT on Debian or GitHub Code Search.

I don't have any particular expertise with the random subsystem or 
conclusions to make from this data, but I hope this helps inform the 
discussion.

Cheers,
Alex.
Jason A. Donenfeld March 23, 2022, 4:47 a.m. UTC | #8
Hey Alex,

Thanks a bunch for doing that research.

On Tue, Mar 22, 2022 at 10:30 PM Alex Xu (Hello71) <alex_y_xu@yahoo.ca> wrote:
> Several programs use it for testing purposes, without writing any
> entropy to /dev/random or /dev/urandom, including rauc, wireguard

The WireGuard use case is sitting in my tree but unpushed, and indeed
it's done as a hack. I have no qualms about changing that before
pushing to net or net-next.

> - kata-containers is a lightweight VM implementation. Its guest-side
>   agent offers a gRPC endpoint which will write the provided data to
>   /dev/random, then call RNDADDTOENTCNT with the length of the data,
>   then call RNDRESEEDRNG.

Sounds like this usage is safe, and that this patch wouldn't really
fix much there.

> - aws-nitro-enclaves-sdk-c is an SDK for building lightweight VMs to be
>   used with AWS Nitro Enclaves. kmstool-enclave is a sample application
>   provided, which writes "up to 256 bytes" (from where?) to /dev/random,
>   then calls RNDADDTOENTCNT, then repeats the process until it reaches
>   1024 bytes.

Looks like another safe use case, which the patch here doesn't help.
Actually this patch might _hurt_ that use case if some of those writes
are short (less than 7 bytes or so). So that might be a data point
that indicates we shouldn't merge this patch, and instead should go
with the "do nothing" route.

> - sandy-harris/maxwell is a "jitter entropy" daemon, similar to haveged.
>   It writes 4 bytes of "generated entropy" to /dev/random, then calls
>   RNDADDTOENTCNT, then repeats.

Okay bingo. The existence of this means that this patch will
definitely introduce a new vulnerability. It means that an attacker
can brute force all of Sandy's (CC'd now) inputs 32 bits at a time. So
I don't think I can merge this patch as-is.

A potential fix for this would be to change:

+               if (unlikely(crng_init == 0 && !will_credit))
+                       crng_pre_init_inject(block, len, false);

into

+               if (unlikely(crng_init == 0 && !will_credit && count
>= 16 && capable(CAP_SYS_ADMIN)))
+                       crng_pre_init_inject(block, len, false);

Or something like that. But that doesn't account for the case where
what's written to /dev/urandom is 300 bytes long but only has 3 bits
of unknown data. So it's better as far as heuristics go, but it
doesn't definitely solve the problem, which stems from the fact of
separating entropy writing from entropy crediting into separate calls.
(Plus as I already mentioned to David, the patch still wouldn't help
the crng_init=1 case.)

> - guix is, among other things, a "GNU/"Linux distribution. The provided
>   base services write the seed file to /dev/urandom, then call
>   RNDADDTOENTCNT, then write 512 bytes from /dev/hwrng to /dev/urandom,
>   then call RNDADDTOENTCNT, then "immediately" read 512 bytes from
>   /dev/urandom and write it to the seed file. On shutdown, 512 bytes are
>   read from /dev/urandom and written to the seed file.

That also sounds like a safe usage of RNDADDTOENTCNT.

> I don't have any particular expertise with the random subsystem or
> conclusions to make from this data, but I hope this helps inform the
> discussion.

Very much so, thanks again. What I take away from your results is:

- RNDADDTOENTCNT is in active use in a safe way. Sure, RNDADDENTROPY
is still much better, but RNDADDTOENTCNT isn't entirely broken in the
above configurations either.
- This patch would make RNDADDTOENTCNT unsafe for some of the above
configurations in a way that it currently isn't unsafe.
- Plenty of things are seeding the RNG correctly, and buildroot's
shell script is just "doing it wrong".

On that last point, I should reiterate that buildroot's shell script
still isn't actually initializing the RNG, despite what it says in its
echo; there's never been a way to initialize the RNG from a shell
script, without calling out to various special purpose ioctl-aware
binaries.

Jason
Rasmus Villemoes March 23, 2022, 8:43 a.m. UTC | #9
On 23/03/2022 03.50, Jason A. Donenfeld wrote:

> - Since these seeding shell scripts have always been broken, because
>   this is how the rng has always been, rather than trying to bolt on a
>   very imperfect fix in the kernel for something that never worked
>   right, we could suggest shell scripts take the path that I implemented
>   for systemd:
>   https://github.com/systemd/systemd/commit/da2862ef06f22fc8d31dafced6d2d6dc14f2ee0b
>   In shell, this would look like:
> 
>     #!/bin/bash
>     cat seedfile > /dev/urandom
>     { cat seedfile; head -c 32 /dev/urandom; } | sha256sum | cut -d ' ' -f 1 > seedfile

Maybe stating the obvious, but in the interest of preventing
proliferation of more broken shell scripts: The tail of the above should
be spelled

  ...  > seedfile.tmp && mv seedfile.tmp seedfile

or seedfile would be truncated before cat had a chance to read it.

Rasmus
David Laight March 23, 2022, 11:45 a.m. UTC | #10
From: Jason A. Donenfeld
> Sent: 23 March 2022 02:51
> 
> On Tue, Mar 22, 2022 at 8:16 PM David Laight <David.Laight@aculab.com> wrote:
> > Never mind scripts that try to immediately save a new seedfile [1].
> >
> > What about code run by later startup scripts that wants random numbers.
> > They really do want the seedfile data to be used.
> > If it isn't used then they are likely to get very weak random numbers.
> >
> > You can't really expect startup scripts to be issuing ioctl requests.
> 
> To be clear, this "expect[ation]" of yours is very much a new
> expectation. Crediting bits has required an ioctl since forever. Those
> shell scripts have been broken forever. The proposal here is to add new
> behavior to support those old broken shell scripts.
...

I personally won't have expected the behaviour for long!
I was only trying to get a buildroot system to initialise the
random number generator last year.
But I'm sure I read some of the documentation as well as looking
at the scripts and the kernel sources.

The buildroot scripts actually need fixing so they actually
add entropy on older kernel.

I do remember looking at one of the kernel entropy stores
(probably the Linux one) a few years back and thinking
that it was over-complex and probably didn't actually work
that well in reality.
IIRC it saved 'entropy bytes' and the number of bits of entropy
they represented - and then read out enough bytes to get
the required entropy to reseed the PRNG.
Now if your PRNG has N bits of state. In principle at least
after you've output N bits someone can solve the simultaneous
equations and determine the PRNG state.
But as soon as you use a cryptographic hash function that
is not really possible in any reasonable timeframe.
(Is even MD5 that broken?)

But the 'entropy store' can just stir in new bytes and
count the number of entropy bits.
Then it doesn't really care how random the bytes are.
(Apart from an estimation of how 'full' it is.)
Copy bits to the PRNG and you reduce the number of
bits in the entropy store - but continue just stirring
in new data.

I've often wondered whether the RC5 algorithm would
make a good entropy store.
Just cycle the algorithm with each entropy byte as
is done when setting each key byte.
Clearly you don't want to use the RC5 output as random data.
But it ought to be plenty good enough to keep entropy.
The only real problem is that RC5 is pretty horrid on
the data cache, and probably a bit big for per-cpu data.

	David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
Theodore Ts'o March 23, 2022, 12:31 p.m. UTC | #11
On Tue, Mar 22, 2022 at 10:00:49PM -0600, Jason A. Donenfeld wrote:
> 
> Another variation on that would be to do what this current patch does,
> but only crng_pre_init_inject() on CAP_SYS_ADMIN. But this has the same
> pitfall of only working as intended at cnrg_init=0 but not crng_init=1.
> That's better than nothing, but it's not perfect, and it introduces that
> problem with RNDADDTOENTCNT.

Well, one could argue that "RNDADDTOENTCNT" is a problem that has
always been there, and it already requires CAP_SYS_ADMIN.  So I'm not
sure it makes it any worse.

> > > And perhaps we might consider attempting to deprecate RNDADDTOENTCNT at
> > > some point in the future.
> >
> > That would be a good idea.  :-)
> 
> Oh cool, I'm glad you agree. Let's do that then. Have a preferred path?
> Maybe just a pr_once() saying not to use it?

Probably.  We could get more aggressive (e.g., WARN), but the first
Google search on RNDADDTOENTCNT returned:

	https://github.com/jumpnow/rndaddtoentcnt

So I'm now regretting not silently making it vanish a decade or more ago...

       	   	      	  	   	     - Ted
David Laight March 23, 2022, 2:01 p.m. UTC | #12
From: Jason A. Donenfeld
> Sent: 23 March 2022 04:48
...
> - Plenty of things are seeding the RNG correctly, and buildroot's
> shell script is just "doing it wrong".
> 
> On that last point, I should reiterate that buildroot's shell script
> still isn't actually initializing the RNG, despite what it says in its
> echo; there's never been a way to initialize the RNG from a shell
> script, without calling out to various special purpose ioctl-aware
> binaries.

Perhaps the very first write after boot could be assumed to
be valid initialisation data?
(On top of a few other tests.)

Then I need a patch against 5.10 :-)

	David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
Jason A. Donenfeld March 23, 2022, 7:53 p.m. UTC | #13
Hi David,

On Wed, Mar 23, 2022 at 8:01 AM David Laight <David.Laight@aculab.com> wrote:
>
> From: Jason A. Donenfeld
> > Sent: 23 March 2022 04:48
> ...
> > - Plenty of things are seeding the RNG correctly, and buildroot's
> > shell script is just "doing it wrong".
> >
> > On that last point, I should reiterate that buildroot's shell script
> > still isn't actually initializing the RNG, despite what it says in its
> > echo; there's never been a way to initialize the RNG from a shell
> > script, without calling out to various special purpose ioctl-aware
> > binaries.
>
> Perhaps the very first write after boot could be assumed to
> be valid initialisation data?
> (On top of a few other tests.)

I addressed this already earlier. That approach does not work. Too
many things already pass in garbage, not expecting for it to be
credited, but just contributory. /dev/urandom writes simply has never
had the semantics one would want for credited seeding. Adding a
heuristic like this will break users.

Jason
Jason A. Donenfeld March 24, 2022, 3:18 a.m. UTC | #14
Hi all,

On Tue, Mar 22, 2022 at 10:47 PM Jason A. Donenfeld <Jason@zx2c4.com> wrote:
> Very much so, thanks again. What I take away from your results is:
>
> - RNDADDTOENTCNT is in active use in a safe way. Sure, RNDADDENTROPY
> is still much better, but RNDADDTOENTCNT isn't entirely broken in the
> above configurations either.
> - This patch would make RNDADDTOENTCNT unsafe for some of the above
> configurations in a way that it currently isn't unsafe.
> - Plenty of things are seeding the RNG correctly, and buildroot's
> shell script is just "doing it wrong".
>
> On that last point, I should reiterate that buildroot's shell script
> still isn't actually initializing the RNG, despite what it says in its
> echo; there's never been a way to initialize the RNG from a shell
> script, without calling out to various special purpose ioctl-aware
> binaries.

Based on this, the fact that shell scripts cannot seed the RNG anyway,
and due to the hazards in trying to retrofit some heuristics onto an
interface that was never designed to work like this, I'm convinced at
this point that the right course of action here is to leave this
alone. There's no combination of /dev/urandom write hacks/heuristics
that do the right thing without creating some big problem elsewhere.
It just does not have the right semantics for it, and changing the
existing semantics will break existing users.

In light of that conclusion, I'm going to work with every userspace
downstream I can find to help them fix their file-based seeding, if it
has bugs. I've started talking with the buildroot folks, and then I'll
speak with the OpenRC people (being a Gentoo dev, that should be easy
going). Systemd does the right thing already.

I wrote a little utility for potential inclusion in
busybox/util-linux/whatever when it matures beyond its current age of
being half hour old:
- https://git.zx2c4.com/seedrng/about/
- https://git.zx2c4.com/seedrng/tree/seedrng.c
So I'll see what the buildroot people think of this and take it from there.

The plus side of doing all this is that, if the efforts pan out, it
means there'll actually be proper seeding on devices that don't
currently do that, which then might lead to a better ecosystem and
less boot time blocking and all that jazz.

Jason
Jason A. Donenfeld March 24, 2022, 2:12 p.m. UTC | #15
Hi Rasmus,

On Wed, Mar 23, 2022 at 2:43 AM Rasmus Villemoes
<linux@rasmusvillemoes.dk> wrote:
>
> On 23/03/2022 03.50, Jason A. Donenfeld wrote:
>
> > - Since these seeding shell scripts have always been broken, because
> >   this is how the rng has always been, rather than trying to bolt on a
> >   very imperfect fix in the kernel for something that never worked
> >   right, we could suggest shell scripts take the path that I implemented
> >   for systemd:
> >   https://github.com/systemd/systemd/commit/da2862ef06f22fc8d31dafced6d2d6dc14f2ee0b
> >   In shell, this would look like:
> >
> >     #!/bin/bash
> >     cat seedfile > /dev/urandom
> >     { cat seedfile; head -c 32 /dev/urandom; } | sha256sum | cut -d ' ' -f 1 > seedfile
>
> Maybe stating the obvious, but in the interest of preventing
> proliferation of more broken shell scripts: The tail of the above should
> be spelled
>
>   ...  > seedfile.tmp && mv seedfile.tmp seedfile
>
> or seedfile would be truncated before cat had a chance to read it.

You're not wrong. The actual thing that got committed is:
https://git.buildroot.net/buildroot/commit/?id=f0986de551f46e72268857fd817986e9be697cd0
which thankfully doesn't have this issue.

Jason
Alex Xu (Hello71) March 24, 2022, 4:28 p.m. UTC | #16
Excerpts from Jason A. Donenfeld's message of March 23, 2022 11:18 pm:
> Hi all,
> 
> [...]
>
> In light of that conclusion, I'm going to work with every userspace
> downstream I can find to help them fix their file-based seeding, if it
> has bugs. I've started talking with the buildroot folks, and then I'll
> speak with the OpenRC people (being a Gentoo dev, that should be easy
> going). Systemd does the right thing already.
> 
> I wrote a little utility for potential inclusion in
> busybox/util-linux/whatever when it matures beyond its current age of
> being half hour old:
> - https://git.zx2c4.com/seedrng/about/
> - https://git.zx2c4.com/seedrng/tree/seedrng.c
> So I'll see what the buildroot people think of this and take it from there.
> 
> The plus side of doing all this is that, if the efforts pan out, it
> means there'll actually be proper seeding on devices that don't
> currently do that, which then might lead to a better ecosystem and
> less boot time blocking and all that jazz.
> 
> Jason
> 

The issue, in systemd developers' opinion, is that counting seed file 
towards entropy initialization potentially causes repeated RNG output if 
a system is cloned without resetting the seed file. This is discussed at 
length in https://github.com/systemd/systemd/pull/4513. A few years ago, 
I wrote most of a program to check machine ID, disk ID, DMI ID, and some 
other things in order to avoid this issue. Since then, systemd decided 
to store the random seed in EFI variables, I assume on the basis that 
machine cloning typically does not clone the EFI variables? In my 
opinion, since the same argument applies to machine ID, ssh keys, and 
any other persistent cryptographic (or even non-cryptographic) material, 
this falls outside the scope of random seeding and into a general 
machine cloning "sysprep"-like utility.

Cheers,
Alex.
Jason A. Donenfeld March 24, 2022, 5:20 p.m. UTC | #17
Hi Alex,

On Thu, Mar 24, 2022 at 10:29 AM Alex Xu (Hello71) <alex_y_xu@yahoo.ca> wrote:
>
> Excerpts from Jason A. Donenfeld's message of March 23, 2022 11:18 pm:
> > Hi all,
> >
> > [...]
> >
> > In light of that conclusion, I'm going to work with every userspace
> > downstream I can find to help them fix their file-based seeding, if it
> > has bugs. I've started talking with the buildroot folks, and then I'll
> > speak with the OpenRC people (being a Gentoo dev, that should be easy
> > going). Systemd does the right thing already.
> >
> > I wrote a little utility for potential inclusion in
> > busybox/util-linux/whatever when it matures beyond its current age of
> > being half hour old:
> > - https://git.zx2c4.com/seedrng/about/
> > - https://git.zx2c4.com/seedrng/tree/seedrng.c
> > So I'll see what the buildroot people think of this and take it from there.
> >
> > The plus side of doing all this is that, if the efforts pan out, it
> > means there'll actually be proper seeding on devices that don't
> > currently do that, which then might lead to a better ecosystem and
> > less boot time blocking and all that jazz.
> >
> > Jason
> >
>
> The issue, in systemd developers' opinion, is that counting seed file
> towards entropy initialization potentially causes repeated RNG output if
> a system is cloned without resetting the seed file. This is discussed at
> length in https://github.com/systemd/systemd/pull/4513. A few years ago,
> I wrote most of a program to check machine ID, disk ID, DMI ID, and some
> other things in order to avoid this issue. Since then, systemd decided
> to store the random seed in EFI variables, I assume on the basis that
> machine cloning typically does not clone the EFI variables? In my
> opinion, since the same argument applies to machine ID, ssh keys, and
> any other persistent cryptographic (or even non-cryptographic) material,
> this falls outside the scope of random seeding and into a general
> machine cloning "sysprep"-like utility.

systemd's seed utility will credit a seed file if the seed file was
generated properly (e.g. after the RNG was initialized). For that they
use the user.random-seed-creditable xattr, which is a reasonable way of
deciding. If that attribute is present, it's credited; if it's not, it's
not. Here's their source:

        /* If we got this random seed data from getrandom() the data is suitable for crediting
         * entropy later on. Let's keep that in mind by setting an extended attribute. on the file */
        if (getrandom_worked)
                if (fsetxattr(seed_fd, "user.random-seed-creditable", "1", 1, 0) < 0)
                        log_full_errno(ERRNO_IS_NOT_SUPPORTED(errno) ? LOG_DEBUG : LOG_WARNING, errno,
                                      "Failed to mark seed file as creditable, ignoring: %m");

Since my seedrng.c is designed for more minimal systems (running
buildroot or openrc or whatever), which might not have xattrs available,
it distinguishes just based on the filename:

	if (new_seed_creditable && rename(NON_CREDITABLE_SEED, CREDITABLE_SEED) < 0) {
		fprintf(stderr, "ERROR: Unable to make new seed creditable: %s\n", strerror(errno));
		program_ret |= 1 << 6;
	}

It's no surprise that these are very similar; I've read systemd's
seeding logic and contributed a fix to it.

By the way, if you think something is different or wrong or whatever in
seedrng.c, please feel free to send me a patch for it. It already
received its first contribution this morning (from the buildroot
maintainer). Hopefully the code will reach a good settling point soon,
and then various projects that want it can just copy and paste it
verbatim into their environment, and tweak idiomatic things as needed.

Jason
Eric Biggers March 24, 2022, 6:01 p.m. UTC | #18
On Wed, Mar 23, 2022 at 01:53:03PM -0600, Jason A. Donenfeld wrote:
> Hi David,
> 
> On Wed, Mar 23, 2022 at 8:01 AM David Laight <David.Laight@aculab.com> wrote:
> >
> > From: Jason A. Donenfeld
> > > Sent: 23 March 2022 04:48
> > ...
> > > - Plenty of things are seeding the RNG correctly, and buildroot's
> > > shell script is just "doing it wrong".
> > >
> > > On that last point, I should reiterate that buildroot's shell script
> > > still isn't actually initializing the RNG, despite what it says in its
> > > echo; there's never been a way to initialize the RNG from a shell
> > > script, without calling out to various special purpose ioctl-aware
> > > binaries.
> >
> > Perhaps the very first write after boot could be assumed to
> > be valid initialisation data?
> > (On top of a few other tests.)
> 
> I addressed this already earlier. That approach does not work. Too
> many things already pass in garbage, not expecting for it to be
> credited, but just contributory. /dev/urandom writes simply has never
> had the semantics one would want for credited seeding. Adding a
> heuristic like this will break users.
> 

Just to give an example of this, one of the first things that Android does at
boot time is copy the kernel command line into /dev/urandom:
https://android.googlesource.com/platform/system/core/+/refs/heads/android12-release/rootdir/init.rc#122

It would certainly be undesirable if that started crediting entropy, given that
the command line might not contain much entropy, or any at all.

(And yes, copying the kernel command line into /dev/urandom is redundant in
kernels v4.14 and later due to https://git.kernel.org/linus/33d72f3822d7ff8a.
But this is going to be kept around for a while longer due to older kernels.)

- Eric
Eric Biggers March 24, 2022, 6:26 p.m. UTC | #19
On Wed, Mar 23, 2022 at 09:18:16PM -0600, Jason A. Donenfeld wrote:
> In light of that conclusion, I'm going to work with every userspace
> downstream I can find to help them fix their file-based seeding, if it
> has bugs. I've started talking with the buildroot folks, and then I'll
> speak with the OpenRC people (being a Gentoo dev, that should be easy
> going). Systemd does the right thing already.
> 
> I wrote a little utility for potential inclusion in
> busybox/util-linux/whatever when it matures beyond its current age of
> being half hour old:
> - https://git.zx2c4.com/seedrng/about/
> - https://git.zx2c4.com/seedrng/tree/seedrng.c
> So I'll see what the buildroot people think of this and take it from there.
> 

The example in random(4) needs to be fixed too, right?

https://man7.org/linux/man-pages/man4/random.4.html

- Eric
Jason A. Donenfeld March 24, 2022, 6:31 p.m. UTC | #20
Hi Eric,

On Thu, Mar 24, 2022 at 12:27 PM Eric Biggers <ebiggers@kernel.org> wrote:
> The example in random(4) needs to be fixed too, right?
>
> https://man7.org/linux/man-pages/man4/random.4.html

Yes, that's always been wrong, at least for all of 2.6.... There are a
number of issues with the man page that I plan on addressing all at
once with a series.

Jason
Alex Xu (Hello71) March 24, 2022, 7:03 p.m. UTC | #21
Excerpts from Jason A. Donenfeld's message of March 24, 2022 1:20 pm:
> Hi Alex,
> 
> On Thu, Mar 24, 2022 at 10:29 AM Alex Xu (Hello71) <alex_y_xu@yahoo.ca> wrote:
>> The issue, in systemd developers' opinion, is that counting seed file
>> towards entropy initialization potentially causes repeated RNG output if
>> a system is cloned without resetting the seed file. This is discussed at
>> length in https://github.com/systemd/systemd/pull/4513. A few years ago,
>> I wrote most of a program to check machine ID, disk ID, DMI ID, and some
>> other things in order to avoid this issue. Since then, systemd decided
>> to store the random seed in EFI variables, I assume on the basis that
>> machine cloning typically does not clone the EFI variables? In my
>> opinion, since the same argument applies to machine ID, ssh keys, and
>> any other persistent cryptographic (or even non-cryptographic) material,
>> this falls outside the scope of random seeding and into a general
>> machine cloning "sysprep"-like utility.
> 
> systemd's seed utility will credit a seed file if the seed file was
> generated properly (e.g. after the RNG was initialized). For that they
> use the user.random-seed-creditable xattr, which is a reasonable way of
> deciding. If that attribute is present, it's credited; if it's not, it's
> not. Here's their source:
> 
>         /* If we got this random seed data from getrandom() the data is suitable for crediting
>          * entropy later on. Let's keep that in mind by setting an extended attribute. on the file */
>         if (getrandom_worked)
>                 if (fsetxattr(seed_fd, "user.random-seed-creditable", "1", 1, 0) < 0)
>                         log_full_errno(ERRNO_IS_NOT_SUPPORTED(errno) ? LOG_DEBUG : LOG_WARNING, errno,
>                                       "Failed to mark seed file as creditable, ignoring: %m");
> 
> Since my seedrng.c is designed for more minimal systems (running
> buildroot or openrc or whatever), which might not have xattrs available,
> it distinguishes just based on the filename:
> 
> 	if (new_seed_creditable && rename(NON_CREDITABLE_SEED, CREDITABLE_SEED) < 0) {
> 		fprintf(stderr, "ERROR: Unable to make new seed creditable: %s\n", strerror(errno));
> 		program_ret |= 1 << 6;
> 	}
> 
> It's no surprise that these are very similar; I've read systemd's
> seeding logic and contributed a fix to it.
> 
> By the way, if you think something is different or wrong or whatever in
> seedrng.c, please feel free to send me a patch for it. It already
> received its first contribution this morning (from the buildroot
> maintainer). Hopefully the code will reach a good settling point soon,
> and then various projects that want it can just copy and paste it
> verbatim into their environment, and tweak idiomatic things as needed.
> 
> Jason
> 

Right, but I'm saying that even if the seed file is good when you wrote 
it, it becomes bad if you copy it to multiple machines (e.g. by VM 
cloning). For various reasons, I think this is outside the scope of the 
random seed services to handle, but I think it's good to keep in mind.

Cheers,
Alex.
Eric Biggers March 24, 2022, 7:53 p.m. UTC | #22
On Tue, Mar 22, 2022 at 01:14:36PM -0600, Jason A. Donenfeld wrote:
> For as far back as I can tell, writing to /dev/urandom or /dev/random
> will put entropy into the pool, but won't immediately use it, and won't
> credit it either.

Did you check kernels v4.7 and earlier?  It looks like this actually changed in
v4.8 when the ChaCha20 CRNG was introduced.  v4.7 would mix the data written to
/dev/{u,}random into {non,}blocking_pool, which would immediately be reflected
in reads from /dev/{u,}random, sys_getrandom(), and get_random_bytes().  Writes
to /dev/{u,}random didn't affect the input_pool, which was separate.

Was the change in behavior in v4.8 a regression?

- Eric
Jason A. Donenfeld March 24, 2022, 8:25 p.m. UTC | #23
Hi Eric,

On 3/24/22, Eric Biggers <ebiggers@kernel.org> wrote:
> On Tue, Mar 22, 2022 at 01:14:36PM -0600, Jason A. Donenfeld wrote:
>> For as far back as I can tell, writing to /dev/urandom or /dev/random
>> will put entropy into the pool, but won't immediately use it, and won't
>> credit it either.
>
> Did you check kernels v4.7 and earlier?  It looks like this actually changed
> in
> v4.8 when the ChaCha20 CRNG was introduced.  v4.7 would mix the data written
> to
> /dev/{u,}random into {non,}blocking_pool, which would immediately be
> reflected
> in reads from /dev/{u,}random, sys_getrandom(), and get_random_bytes().
> Writes
> to /dev/{u,}random didn't affect the input_pool, which was separate.

Oh, I suppose you might be right, actually, that v4.7 and below would
hash the non blocking pool, and let /dev/urandom write directly into
it, as something distinct from the input pool. This changed with v4.8,
6 years ago, and now there are no LTS kernels that old, with most
small devices even having vendor kernels v4.9+. v4.8 apparently did
this while fixing a more extreme vulnerability of allowing
unprivileged users to bruteforce input bytes (in addition to allowing
unbounded unprivileged lock contention). Of those who have been
seeding via /dev/random, the ones who additionally issued the ioctl to
credit those bits haven't been affected since the crediting solved the
issue by invoking a reseeding. And those who didn't issue the ioctl
never had their RNG initialize in the first place, causing getrandom()
to block until entropy was collected from elsewhere, until it was
safe, so the harm there was minimal. So it's not great, but it's not
horrific either, and I still think the cons strongly outweigh the pros
in trying to change the behavior from what it is now.

Jason
Pavel Machek May 23, 2022, 5:59 p.m. UTC | #24
Hi!

> > One of the big issues with /dev/urandom writes is that *anyone*,
> > including malicious user space, can force specific bytes to be mixed
> > in.  That's the source of the reluctance to immediate use inputs from
> > writes into /dev/[u]random until there is a chance for it to be mixed
> > in with other entropy which is hopefully not under the control of
> > malicious userspace.
> 
> Right, sort of. Since we now always use a cryptographic hash function,
> we can haphazardly mix whatever any user wants, without too much
> concern. The issue is whether we _credit_ those bits. Were we to credit
> those bits, a malicious unpriv'd user could credit 248 bits of known
> input, and then bruteforce 8 bits of unknown input, and repeat, and in
> that way destroy the security of the thing. So, yea, the current
> reluctance does make sense.
> 
> > Now, I recognize that things are a bit special in early boot, and if
> > we have a malicious script running in a systemd unit script, we might
> > as well go home.  But something to consider is whether we want to do
> > soemthing special if the process writing to /dev/[u]random has
> > CAP_SYS_ADMIN, or some such.
> 
> Exactly. So one way of approaching this would be to simply credit writes
> to /dev/urandom if it's CAP_SYS_ADMIN and maybe if also !crng_ready(),
> and just skip the crng_pre_init_inject() part that this current patch
> adds. I'll attach a sample patch of what this might look like at the end
> of this email.

CAP_* should not really work like that. They should control if process
can do the operation, but should not really silently change what
syscall does based on the CAP_*...

(And yes, I'm a bit late).

Best regards,
								Pavel
Pavel Machek June 19, 2022, 4:44 p.m. UTC | #25
Hi!

> > Very much so, thanks again. What I take away from your results is:
> >
> > - RNDADDTOENTCNT is in active use in a safe way. Sure, RNDADDENTROPY
> > is still much better, but RNDADDTOENTCNT isn't entirely broken in the
> > above configurations either.
> > - This patch would make RNDADDTOENTCNT unsafe for some of the above
> > configurations in a way that it currently isn't unsafe.
> > - Plenty of things are seeding the RNG correctly, and buildroot's
> > shell script is just "doing it wrong".
> >
> > On that last point, I should reiterate that buildroot's shell script
> > still isn't actually initializing the RNG, despite what it says in its
> > echo; there's never been a way to initialize the RNG from a shell
> > script, without calling out to various special purpose ioctl-aware
> > binaries.
> 
> Based on this, the fact that shell scripts cannot seed the RNG anyway,
> and due to the hazards in trying to retrofit some heuristics onto an
> interface that was never designed to work like this, I'm convinced at
> this point that the right course of action here is to leave this
> alone. There's no combination of /dev/urandom write hacks/heuristics
> that do the right thing without creating some big problem elsewhere.
> It just does not have the right semantics for it, and changing the
> existing semantics will break existing users.
> 
> In light of that conclusion, I'm going to work with every userspace
> downstream I can find to help them fix their file-based seeding, if it
> has bugs. I've started talking with the buildroot folks, and then I'll
> speak with the OpenRC people (being a Gentoo dev, that should be easy
> going). Systemd does the right thing already.
> 
> I wrote a little utility for potential inclusion in
> busybox/util-linux/whatever when it matures beyond its current age of
> being half hour old:
> - https://git.zx2c4.com/seedrng/about/
> - https://git.zx2c4.com/seedrng/tree/seedrng.c
> So I'll see what the buildroot people think of this and take it from there.

You could put it into the kernel into tools/ directory...

Best regards,
									Pavel
Pavel Machek June 19, 2022, 4:56 p.m. UTC | #26
Hi!
> > On Tue, Mar 22, 2022 at 01:14:36PM -0600, Jason A. Donenfeld wrote:
> >> For as far back as I can tell, writing to /dev/urandom or /dev/random
> >> will put entropy into the pool, but won't immediately use it, and won't
> >> credit it either.
> >
> > Did you check kernels v4.7 and earlier?  It looks like this actually changed
> > in
> > v4.8 when the ChaCha20 CRNG was introduced.  v4.7 would mix the data written
> > to
> > /dev/{u,}random into {non,}blocking_pool, which would immediately be
> > reflected
> > in reads from /dev/{u,}random, sys_getrandom(), and get_random_bytes().
> > Writes
> > to /dev/{u,}random didn't affect the input_pool, which was separate.
> 
> Oh, I suppose you might be right, actually, that v4.7 and below would
> hash the non blocking pool, and let /dev/urandom write directly into
> it, as something distinct from the input pool. This changed with v4.8,
> 6 years ago, and now there are no LTS kernels that old, with most
> small devices even having vendor kernels v4.9+. v4.8 apparently did

We are still maintaining 4.4 for -cip project, and people running android probably still 
maintain that, too.

> this while fixing a more extreme vulnerability of allowing unprivileged users to 
> bruteforce input bytes (in addition to allowing unbounded unprivileged lock contention). 

I assume this got fixed during the 4.4-stable series?

Best regards,
										Pavel
diff mbox series

Patch

diff --git a/drivers/char/random.c b/drivers/char/random.c
index 706f08edf0dc..7b7f5e6596c2 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -1493,7 +1493,7 @@  static __poll_t random_poll(struct file *file, poll_table *wait)
 	return mask;
 }
 
-static int write_pool(const char __user *ubuf, size_t count)
+static int write_pool(const char __user *ubuf, size_t count, bool will_credit)
 {
 	size_t len;
 	int ret = 0;
@@ -1507,6 +1507,8 @@  static int write_pool(const char __user *ubuf, size_t count)
 		}
 		count -= len;
 		ubuf += len;
+		if (unlikely(crng_init == 0 && !will_credit))
+			crng_pre_init_inject(block, len, false);
 		mix_pool_bytes(block, len);
 		cond_resched();
 	}
@@ -1521,7 +1523,13 @@  static ssize_t random_write(struct file *file, const char __user *buffer,
 {
 	int ret;
 
-	ret = write_pool(buffer, count);
+	/*
+	 * We set "will_credit" to false here, which might be wrong if the
+	 * user subsequently calls RNDADDTOENTCNT. But it accounts for the
+	 * more common case of shell scripts writing to /dev/urandom and
+	 * then immediately reading back a seed file.
+	 */
+	ret = write_pool(buffer, count, false);
 	if (ret)
 		return ret;
 
@@ -1584,7 +1592,7 @@  static long random_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
 			return -EINVAL;
 		if (get_user(size, p++))
 			return -EFAULT;
-		retval = write_pool((const char __user *)p, size);
+		retval = write_pool((const char __user *)p, size, ent_count > 0);
 		if (retval < 0)
 			return retval;
 		credit_entropy_bits(ent_count);