Opened 5 years ago

Closed 17 months ago

#14937 closed bug (fixed)

Tiny chunks of entropy from /dev/urandom when using virtio RNG

Reported by: kallisti5 Owned by: nobody
Priority: normal Milestone: R1/beta4
Component: Drivers Version: R1/Development
Keywords: urandom Cc:
Blocked By: Blocking:
Platform: All

Description (last modified by kallisti5)

/dev/urandom on Haiku seems to only offer up small 16 byte chunks of entropy. This behaviour differs from other platforms. I saw this while working on WireGuard under Haiku.

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <inttypes.h>


int
main() {
        int len = 128;
        int ret = 0;

        uint8_t key[len];

        int fd = open("/dev/urandom", O_RDONLY);
        if (fd < 0) {
                fprintf(stderr, "Unable to open /dev/urandom!");
                return fd;
        }

        int attempts = 0;
        while (ret < len) {
                ssize_t remaining = len - ret;
                ssize_t got = read(fd, key + ret, remaining);
                ret += got;
                if (attempts > 512) {
                        fprintf(stderr, "Unable to get enough entropy from /dev/urandom!");
                        close(fd);
                        return -1;
                }
                fprintf(stdout, "Attempt %d: Got %d, giving %d total!\n", attempts, got, ret);
                attempts++;
        }
        close(fd);
        return ret;
}

Result on Haiku:

~> ./a.out 
Attempt 0: Got 8, giving 8 total!
Attempt 1: Got 16, giving 24 total!
Attempt 2: Got 16, giving 40 total!
Attempt 3: Got 16, giving 56 total!
Attempt 4: Got 16, giving 72 total!
Attempt 5: Got 16, giving 88 total!
Attempt 6: Got 16, giving 104 total!
Attempt 7: Got 16, giving 120 total!
Attempt 8: Got 8, giving 128 total!

Result on Linux:

$ ./a.out 
Attempt 0: Got 128, giving 128 total!
$ ./a.out 
Attempt 0: Got 128, giving 128 total!
$ ./a.out 
Attempt 0: Got 128, giving 128 total!
$ ./a.out 
Attempt 0: Got 128, giving 128 total!
$ ./a.out 
Attempt 0: Got 128, giving 128 total!
$ ./a.out 
Attempt 0: Got 128, giving 128 total!
$ ./a.out 
Attempt 0: Got 128, giving 128 total!

Change History (10)

comment:1 by kallisti5, 5 years ago

Description: modified (diff)

comment:2 by waddlesplash, 5 years ago

We should probably replace our /dev/random and /dev/urandom with OpenBSD's, or some variant thereof.

comment:3 by pulkomandy, 5 years ago

We need to finish an arc4random implementation (we have a WIP one in Gerrit but it needs to be actually connected to entropy sources) and then we can easily use that for feeding the devices.

comment:4 by waddlesplash, 5 years ago

No, this is only for userland. The kernel random device behaves differently than this in the BSDs for a reason. For one, random and urandom behave the same way, and they both block only during the boot before the pool has been seeded. That way entropy is never "exhausted" like on Linux.

comment:5 by kallisti5, 4 years ago

rng-tools offers some neat test programs for random sources of data's... randomness.

Here are a few examples from my linux machine...

[root@eris ~]# cat /dev/hwrng | rngtest -t 10
rngtest 6.9
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

rngtest: starting FIPS tests...
rngtest: bits received from input: 500040032
rngtest: FIPS 140-2 successes: 24987
rngtest: FIPS 140-2 failures: 15
rngtest: FIPS 140-2(2001-10-10) Monobit: 0
rngtest: FIPS 140-2(2001-10-10) Poker: 3
rngtest: FIPS 140-2(2001-10-10) Runs: 7
rngtest: FIPS 140-2(2001-10-10) Long run: 5
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=992.744; avg=71153.961; max=19531250.000)Kibits/s
rngtest: FIPS tests speed: (min=58.869; avg=152.150; max=185.179)Mibits/s
rngtest: Program run time: 10016917 microseconds
[root@eris ~]# cat /dev/random | rngtest -t 10
rngtest 6.9
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

rngtest: starting FIPS tests...
rngtest: bits received from input: 72700032
rngtest: FIPS 140-2 successes: 3632
rngtest: FIPS 140-2 failures: 3
rngtest: FIPS 140-2(2001-10-10) Monobit: 1
rngtest: FIPS 140-2(2001-10-10) Poker: 1
rngtest: FIPS 140-2(2001-10-10) Runs: 1
rngtest: FIPS 140-2(2001-10-10) Long run: 0
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=2.064; avg=7.433; max=8.504)Mibits/s
rngtest: FIPS tests speed: (min=56.936; avg=103.244; max=183.399)Mibits/s
rngtest: Program run time: 10000423 microseconds
[root@eris ~]# cat /dev/urandom | rngtest -t 10
rngtest 6.9
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

rngtest: starting FIPS tests...
rngtest: bits received from input: 1788660032
rngtest: FIPS 140-2 successes: 89373
rngtest: FIPS 140-2 failures: 60
rngtest: FIPS 140-2(2001-10-10) Monobit: 9
rngtest: FIPS 140-2(2001-10-10) Poker: 12
rngtest: FIPS 140-2(2001-10-10) Runs: 18
rngtest: FIPS 140-2(2001-10-10) Long run: 21
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=476.837; avg=20203.708; max=19073.486)Mibits/s
rngtest: FIPS tests speed: (min=68.857; avg=172.168; max=185.179)Mibits/s
rngtest: Program run time: 10001792 microseconds

And my TrueRNG USB dongle.

[root@eris ~]# cat /dev/TrueRNG | rngtest -t 10
rngtest 6.9
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

rngtest: starting FIPS tests...
rngtest: bits received from input: 3360032
rngtest: FIPS 140-2 successes: 168
rngtest: FIPS 140-2 failures: 0
rngtest: FIPS 140-2(2001-10-10) Monobit: 0
rngtest: FIPS 140-2(2001-10-10) Poker: 0
rngtest: FIPS 140-2(2001-10-10) Runs: 0
rngtest: FIPS 140-2(2001-10-10) Long run: 0
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=325.570; avg=329.150; max=335.508)Kibits/s
rngtest: FIPS tests speed: (min=70.905; avg=97.157; max=174.986)Mibits/s
rngtest: Program run time: 10003878 microseconds

comment:6 by pulkomandy, 3 years ago

Looking at our random implementation, by default we use the Yarrow random number generator which will always fill the passed buffer completely.

It looks like we also have a virtio driver, which will get randomness from the host (added in 8d2bf6953e851d431fc67de1bc970c40afa79e9f). The commit message mentions that "this can have the undesired effect of rendering /dev/urandom slow".

Were you using that when you made this test? Because I tried to run your code and did not reproduce your problem without enabling virtio random. Then I tried enabling virtio random in qemu (using the command line from the commit message) and I got a SMAP violation on boot. Apparently the virtio random driver was never updated to use user_memcpy?

I think the virtio random should be used as an additional or alternative entropy source for the Yarrow RNG, and not replace it completely.

comment:7 by pulkomandy, 3 years ago

Summary: Tiny chunks of entropy from /dev/urandomTiny chunks of entropy from /dev/urandom when using virtio RNG

comment:8 by waddlesplash, 3 years ago

We should really just adopt OpenBSD's model, which Linux is now adopting, which is to read a fixed amount of data from the RNG and use it to seed a CSPRNG.

comment:9 by pulkomandy, 3 years ago

OpenBSD implementation for reference: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/dev/rnd.c?rev=1.221&content-type=text/x-cvsweb-markup

Key differences:

  • Our CSPRNG is Yarrow (it was used by FreeBSD and Apple, they both switched to Fortuna). OpenBSD uses ChaCha20.
  • Our single entropy source is kernel scheduling by calling thread_yield and reading system_time. OpenBSD uses a mix of previous boot randomness (stored on disk and reloaded at boot), randomness from the bootloader, hardware RNG is available, device probing, and interrupt timings

The architecture is otherwise generally the same. I don't know how it took so long for Linux to do it as well. Yet again one thing we got right long before them.

What we need is connect more entropy sources, and perhaps switching to a different CSPRNG algorithm. But we already use the correct model in the default case.

With virtio, however, the way things were done is that we rely completely on the host to provide randomness. We should change that so virtio is only an entropy source used to seed our own CSPRNG.

comment:10 by waddlesplash, 17 months ago

Milestone: UnscheduledR1/beta4
Resolution: fixed
Status: newclosed

This seems to be fixed by korli's recent refactors to the random device. At least the test given in the original ticket now matches Linux's behavior. I merged the first patch at least into the beta4 branch.

Note: See TracTickets for help on using tickets.