diff options
author | Conrad Meyer <cem@FreeBSD.org> | 2019-04-15 18:40:36 +0000 |
---|---|---|
committer | Conrad Meyer <cem@FreeBSD.org> | 2019-04-15 18:40:36 +0000 |
commit | 13774e82285e8d5eb3afeff63760725f747f8581 (patch) | |
tree | 4e63f007ef63568f5bed09510f908fbcaa77f22c /sys/dev/random | |
parent | 16fba2f38a8258ea656bba74d52e55436464e66b (diff) | |
download | src-13774e82285e8d5eb3afeff63760725f747f8581.tar.gz src-13774e82285e8d5eb3afeff63760725f747f8581.zip |
random(4): Block read_random(9) on initial seeding
read_random() is/was used, mostly without error checking, in a lot of
very sensitive places in the kernel -- including seeding the widely used
arc4random(9).
Most uses, especially arc4random(9), should block until the device is seeded
rather than proceeding with a bogus or empty seed. I did not spy any
obvious kernel consumers where blocking would be inappropriate (in the
sense that lack of entropy would be ok -- I did not investigate locking
angle thoroughly). In many instances, arc4random_buf(9) or that family
of APIs would be more appropriate anyway; that work was done in r345865.
A minor cleanup was made to the implementation of the READ_RANDOM function:
instead of using a variable-length array on the stack to temporarily store
all full random blocks sufficient to satisfy the requested 'len', only store
a single block on the stack. This has some benefit in terms of reducing
stack usage, reducing memcpy overhead and reducing devrandom output leakage
via the stack. Additionally, the stack block is now safely zeroed if it was
used.
One caveat of this change is that the kern.arandom sysctl no longer returns
zero bytes immediately if the random device is not seeded. This means that
FreeBSD-specific userspace applications which attempted to handle an
unseeded random device may be broken by this change. If such behavior is
needed, it can be replaced by the more portable getrandom(2) GRND_NONBLOCK
option.
On any typical FreeBSD system, entropy is persisted on read/write media and
used to seed the random device very early in boot, and blocking is never a
problem.
This change primarily impacts the behavior of /dev/random on embedded
systems with read-only media that do not configure "nodevice random". We
toggle the default from 'charge on blindly with no entropy' to 'block
indefinitely.' This default is safer, but may cause frustration. Embedded
system designers using FreeBSD have several options. The most obvious is to
plan to have a small writable NVRAM or NAND to persist entropy, like larger
systems. Early entropy can be fed from any loader, or by writing directly
to /dev/random during boot. Some embedded SoCs now provide a fast hardware
entropy source; this would also work for quickly seeding Fortuna. A 3rd
option would be creating an embedded-specific, more simplistic random
module, like that designed by DJB in [1] (this design still requires a small
rewritable media for forward secrecy). Finally, the least preferred option
might be "nodevice random", although I plan to remove this in a subsequent
revision.
To help developers emulate the behavior of these embedded systems on
ordinary workstations, the tunable kern.random.block_seeded_status was
added. When set to 1, it blocks the random device.
I attempted to document this change in random.4 and random.9 and ran into a
bunch of out-of-date or irrelevant or inaccurate content and ended up
rototilling those documents more than I intended to. Sorry. I think
they're in a better state now.
PR: 230875
Reviewed by: delphij, markm (earlier version)
Approved by: secteam(delphij), devrandom(markm)
Relnotes: yes
Differential Revision: https://reviews.freebsd.org/D19744
Notes
Notes:
svn path=/head/; revision=346250
Diffstat (limited to 'sys/dev/random')
-rw-r--r-- | sys/dev/random/fortuna.c | 16 | ||||
-rw-r--r-- | sys/dev/random/random_harvestq.c | 5 | ||||
-rw-r--r-- | sys/dev/random/random_infra.c | 20 | ||||
-rw-r--r-- | sys/dev/random/randomdev.c | 113 | ||||
-rw-r--r-- | sys/dev/random/randomdev.h | 2 |
5 files changed, 98 insertions, 58 deletions
diff --git a/sys/dev/random/fortuna.c b/sys/dev/random/fortuna.c index e8a407525138..a6cfccaa3a1a 100644 --- a/sys/dev/random/fortuna.c +++ b/sys/dev/random/fortuna.c @@ -451,16 +451,22 @@ random_fortuna_read(uint8_t *buf, u_int bytecount) RANDOM_RESEED_UNLOCK(); } +#ifdef _KERNEL +static bool block_seeded_status = false; +SYSCTL_BOOL(_kern_random, OID_AUTO, block_seeded_status, CTLFLAG_RWTUN, + &block_seeded_status, 0, + "If non-zero, pretend Fortuna is in an unseeded state. By setting " + "this as a tunable, boot can be tested as if the random device is " + "unavailable."); +#endif + bool random_fortuna_seeded(void) { #ifdef _KERNEL - /* When set, act as if we are not seeded. */ - KFAIL_POINT_CODE(DEBUG_FP, random_fortuna_seeded, { - if (RETURN_VALUE != 0) - fortuna_state.fs_counter = UINT128_ZERO; - }); + if (block_seeded_status) + return (false); #endif return (!uint128_is_zero(fortuna_state.fs_counter)); diff --git a/sys/dev/random/random_harvestq.c b/sys/dev/random/random_harvestq.c index 3f5ecc7ddea3..d31a6a89a8b7 100644 --- a/sys/dev/random/random_harvestq.c +++ b/sys/dev/random/random_harvestq.c @@ -421,11 +421,6 @@ random_harvestq_prime(void *unused __unused) if (keyfile != NULL) { data = preload_fetch_addr(keyfile); size = preload_fetch_size(keyfile); - /* skip the first bit of the stash so others like arc4 can also have some. */ - if (size > RANDOM_CACHED_SKIP_START) { - data += RANDOM_CACHED_SKIP_START; - size -= RANDOM_CACHED_SKIP_START; - } /* Trim the size. If the admin has a file with a funny size, we lose some. Tough. */ size -= (size % sizeof(event.he_entropy)); if (data != NULL && size != 0) { diff --git a/sys/dev/random/random_infra.c b/sys/dev/random/random_infra.c index d31b84b24582..324c40dcecd4 100644 --- a/sys/dev/random/random_infra.c +++ b/sys/dev/random/random_infra.c @@ -57,12 +57,18 @@ struct random_algorithm *p_random_alg_context = &random_alg_context; #if defined(RANDOM_LOADABLE) +static void +null_read_random(void *dummy __unused, u_int dummy2 __unused) +{ + panic("%s: no random module is loaded", __func__); +} + struct random_readers { int (*read_random_uio)(struct uio *, bool); - u_int (*read_random)(void *, u_int); + void (*read_random)(void *, u_int); } random_reader_context = { (int (*)(struct uio *, bool))nullop, - (u_int (*)(void *, u_int))nullop, + null_read_random, }; struct sx randomdev_config_lock; @@ -76,7 +82,7 @@ random_infra_sysinit(void *dummy __unused) SYSINIT(random_device_h_init, SI_SUB_RANDOM, SI_ORDER_FIRST, random_infra_sysinit, NULL); void -random_infra_init(int (*p_random_read_uio)(struct uio *, bool), u_int (*p_random_read)(void *, u_int)) +random_infra_init(int (*p_random_read_uio)(struct uio *, bool), void (*p_random_read)(void *, u_int)) { RANDOM_CONFIG_X_LOCK(); @@ -91,7 +97,7 @@ random_infra_uninit(void) RANDOM_CONFIG_X_LOCK(); random_reader_context.read_random_uio = (int (*)(struct uio *, bool))nullop; - random_reader_context.read_random = (u_int (*)(void *, u_int))nullop; + random_reader_context.read_random = null_read_random; RANDOM_CONFIG_X_UNLOCK(); } @@ -114,15 +120,13 @@ read_random_uio(struct uio *uio, bool nonblock) return (retval); } -u_int +void read_random(void *buf, u_int len) { - u_int retval; RANDOM_CONFIG_S_LOCK(); - retval = random_reader_context.read_random(buf, len); + random_reader_context.read_random(buf, len); RANDOM_CONFIG_S_UNLOCK(); - return (retval); } #endif /* defined(RANDOM_LOADABLE) */ diff --git a/sys/dev/random/randomdev.c b/sys/dev/random/randomdev.c index 94bd2f1a4147..19a73b28151a 100644 --- a/sys/dev/random/randomdev.c +++ b/sys/dev/random/randomdev.c @@ -63,7 +63,7 @@ __FBSDID("$FreeBSD$"); #define READ_RANDOM_UIO _read_random_uio #define READ_RANDOM _read_random static int READ_RANDOM_UIO(struct uio *, bool); -static u_int READ_RANDOM(void *, u_int); +static void READ_RANDOM(void *, u_int); #else #define READ_RANDOM_UIO read_random_uio #define READ_RANDOM read_random @@ -124,11 +124,53 @@ randomdev_read(struct cdev *dev __unused, struct uio *uio, int flags) return (READ_RANDOM_UIO(uio, (flags & O_NONBLOCK) != 0)); } +/* + * If the random device is not seeded, blocks until it is seeded. + * + * Returns zero when the random device is seeded. + * + * If the 'interruptible' parameter is true, and the device is unseeded, this + * routine may be interrupted. If interrupted, it will return either ERESTART + * or EINTR. + */ +#define SEEDWAIT_INTERRUPTIBLE true +#define SEEDWAIT_UNINTERRUPTIBLE false +static int +randomdev_wait_until_seeded(bool interruptible) +{ + int error, spamcount, slpflags; + + slpflags = interruptible ? PCATCH : 0; + + error = 0; + spamcount = 0; + while (!p_random_alg_context->ra_seeded()) { + /* keep tapping away at the pre-read until we seed/unblock. */ + p_random_alg_context->ra_pre_read(); + /* Only bother the console every 10 seconds or so */ + if (spamcount == 0) + printf("random: %s unblock wait\n", __func__); + spamcount = (spamcount + 1) % 100; + error = tsleep(&random_alg_context, slpflags, "randseed", + hz / 10); + if (error == ERESTART || error == EINTR) { + KASSERT(interruptible, + ("unexpected wake of non-interruptible sleep")); + break; + } + /* Squash tsleep timeout condition */ + if (error == EWOULDBLOCK) + error = 0; + KASSERT(error == 0, ("unexpected tsleep error %d", error)); + } + return (error); +} + int READ_RANDOM_UIO(struct uio *uio, bool nonblock) { uint8_t *random_buf; - int error, spamcount; + int error; ssize_t read_len, total_read, c; /* 16 MiB takes about 0.08 s CPU time on my 2017 AMD Zen CPU */ #define SIGCHK_PERIOD (16 * 1024 * 1024) @@ -140,26 +182,13 @@ READ_RANDOM_UIO(struct uio *uio, bool nonblock) random_buf = malloc(PAGE_SIZE, M_ENTROPY, M_WAITOK); p_random_alg_context->ra_pre_read(); error = 0; - spamcount = 0; /* (Un)Blocking logic */ - while (!p_random_alg_context->ra_seeded()) { - if (nonblock) { + if (!p_random_alg_context->ra_seeded()) { + if (nonblock) error = EWOULDBLOCK; - break; - } - /* keep tapping away at the pre-read until we seed/unblock. */ - p_random_alg_context->ra_pre_read(); - /* Only bother the console every 10 seconds or so */ - if (spamcount == 0) - printf("random: %s unblock wait\n", __func__); - spamcount = (spamcount + 1)%100; - error = tsleep(&random_alg_context, PCATCH, "randseed", hz/10); - if (error == ERESTART || error == EINTR) - break; - /* Squash tsleep timeout condition */ - if (error == EWOULDBLOCK) - error = 0; - KASSERT(error == 0, ("unexpected tsleep error %d", error)); + else + error = randomdev_wait_until_seeded( + SEEDWAIT_INTERRUPTIBLE); } if (error == 0) { read_rate_increment((uio->uio_resid + sizeof(uint32_t))/sizeof(uint32_t)); @@ -210,30 +239,36 @@ READ_RANDOM_UIO(struct uio *uio, bool nonblock) * It cannot assumed that random_buf is a multiple of * RANDOM_BLOCKSIZE bytes. */ -u_int +void READ_RANDOM(void *random_buf, u_int len) { - u_int read_len; - uint8_t local_buf[len + RANDOM_BLOCKSIZE]; + u_int read_directly_len; KASSERT(random_buf != NULL, ("No suitable random buffer in %s", __func__)); p_random_alg_context->ra_pre_read(); - /* (Un)Blocking logic; if not seeded, return nothing. */ - if (p_random_alg_context->ra_seeded()) { - read_rate_increment((len + sizeof(uint32_t))/sizeof(uint32_t)); - if (len > 0) { - /* - * Belt-and-braces. - * Round up the read length to a crypto block size multiple, - * which is what the underlying generator is expecting. - */ - read_len = roundup(len, RANDOM_BLOCKSIZE); - p_random_alg_context->ra_read(local_buf, read_len); - memcpy(random_buf, local_buf, len); - } - } else - len = 0; - return (len); + /* (Un)Blocking logic */ + if (!p_random_alg_context->ra_seeded()) + (void)randomdev_wait_until_seeded(SEEDWAIT_UNINTERRUPTIBLE); + read_rate_increment(roundup2(len, sizeof(uint32_t))); + if (len == 0) + return; + /* + * The underlying generator expects multiples of + * RANDOM_BLOCKSIZE. + */ + read_directly_len = rounddown(len, RANDOM_BLOCKSIZE); + if (read_directly_len > 0) + p_random_alg_context->ra_read(random_buf, read_directly_len); + if (read_directly_len < len) { + uint8_t remainder_buf[RANDOM_BLOCKSIZE]; + + p_random_alg_context->ra_read(remainder_buf, + sizeof(remainder_buf)); + memcpy((char *)random_buf + read_directly_len, remainder_buf, + len - read_directly_len); + + explicit_bzero(remainder_buf, sizeof(remainder_buf)); + } } static __inline void diff --git a/sys/dev/random/randomdev.h b/sys/dev/random/randomdev.h index 0f3b359d1f1f..41300f237aaf 100644 --- a/sys/dev/random/randomdev.h +++ b/sys/dev/random/randomdev.h @@ -118,7 +118,7 @@ extern struct sx randomdev_config_lock; #define RANDOM_CONFIG_S_LOCK(x) sx_slock(&randomdev_config_lock) #define RANDOM_CONFIG_S_UNLOCK(x) sx_sunlock(&randomdev_config_lock) #define RANDOM_CONFIG_DEINIT_LOCK(x) sx_destroy(&randomdev_config_lock) -void random_infra_init(int (*)(struct uio *, bool), u_int (*)(void *, u_int)); +void random_infra_init(int (*)(struct uio *, bool), void (*)(void *, u_int)); void random_infra_uninit(void); #endif |