Address multiple problems with rnd(4)/cprng(9):

1) Add a per-cpu CPRNG to handle short reads from /dev/urandom so that
   programs like perl don't drain the entropy pool dry by repeatedly
   opening, reading 4 bytes, closing.

2) Really fix the locking around reseeds and destroys.

3) Fix the opportunistic-reseed strategy so it actually works, reseeding
   existing RNGs once each (as they are used, so idle RNGs don't get
   reseeded) until the pool is half empty or newly full again.
This commit is contained in:
tls 2012-04-17 02:50:38 +00:00
parent 8d59073313
commit 8e1a1c9f45
7 changed files with 188 additions and 55 deletions

View File

@ -1,4 +1,4 @@
.\" $NetBSD: rnd.4,v 1.18 2011/12/17 21:21:59 wiz Exp $
.\" $NetBSD: rnd.4,v 1.19 2012/04/17 02:50:39 tls Exp $
.\"
.\" Copyright (c) 1997 Michael Graff
.\" All rights reserved.
@ -52,13 +52,32 @@ SP 800-90) which is used to generate values returned to userspace when
the pseudo-device is read.
.Pp
The pseudodevice is cloning, which means that each time it is opened,
a new instance of the stream generator is created.
a new instance of the stream generator may be created.
Interposing a stream
generator between the entropy pool and readers in this manner protects
readers from each other (each reader's random stream is generated from a
unique key) and protects all users of the entropy pool from any attack
which might correlate its successive outputs to each other, such as
iterative guessing attacks.
.Pp
Certain programs make very short reads from
.Pa /dev/urandom
each time they begin execution. One program with this behavior is
.Xr perl 1 .
If such a program is run repeatedly (for example from a network
service or shell script), the resulting repeated keying of the stream
generator can quickly drain the entropy pool dry. As an optimization
for such cases, a separate per-CPU instance of the stream generator
is used to handle reads from
.Pa /dev/urandom
which are smaller than the key length of the underlying cipher. Any
read of a larger size causes an immediate allocation of a private
instance of the stream generator for the reader. Since all stream
generators are automatically rekeyed upon use when sufficient entropy
is available, the shared short-request generators do still offer
some protection against other consumers of
.Pa /dev/urandom ,
though less than is provided for consumers making larger requests.
.Sh USER ACCESS
User code can obtain random values from the kernel in two ways.
.Pp
@ -190,6 +209,20 @@ The device is a tape device.
The device is a terminal, mouse, or other user input device.
.It Dv RND_TYPE_RNG
The device is a random number generator.
.It Dv RND_TYPE_SKEW
The "device" is a measurement of the skew between two clocks, such as a
periodic device interrupt and the system timecounter, a timecounter and
an audio codec, or some other source of pairs of events where each
member of each pair is derived from a different instance of some
recurring physical process.
.It Dv RND_TYPE_ENV
The device is an environmental sensor such as a temperature sensor or
a fan speed sensor.
.It Dv RND_TYPE_VM
The "device" consists of timings of virtual memory system events.
.It Dv RND_TYPE_POWER
The device is a sensor returning changes in the power state of the
system, such as battery charge state or A/C adapter state.
.El
.Pp
.Va flags

View File

@ -1,4 +1,4 @@
/* $NetBSD: rndpseudo.c,v 1.7 2012/03/30 20:15:18 drochner Exp $ */
/* $NetBSD: rndpseudo.c,v 1.8 2012/04/17 02:50:38 tls Exp $ */
/*-
* Copyright (c) 1997-2011 The NetBSD Foundation, Inc.
@ -30,7 +30,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rndpseudo.c,v 1.7 2012/03/30 20:15:18 drochner Exp $");
__KERNEL_RCSID(0, "$NetBSD: rndpseudo.c,v 1.8 2012/04/17 02:50:38 tls Exp $");
#if defined(_KERNEL_OPT)
#include "opt_compat_netbsd.h"
@ -54,6 +54,7 @@ __KERNEL_RCSID(0, "$NetBSD: rndpseudo.c,v 1.7 2012/03/30 20:15:18 drochner Exp $
#include <sys/pool.h>
#include <sys/kauth.h>
#include <sys/cprng.h>
#include <sys/cpu.h>
#include <sys/stat.h>
#include <sys/rnd.h>
@ -95,6 +96,11 @@ extern int rnd_debug;
static pool_cache_t rp_pc;
static pool_cache_t rp_cpc;
/*
* The per-CPU RNGs used for short requests
*/
cprng_strong_t **rp_cpurngs;
/*
* A context. cprng plus a smidge.
*/
@ -193,6 +199,9 @@ rndattach(int num)
mutex_spin_enter(&rndpool_mtx);
rndpool_add_data(&rnd_pool, &c, sizeof(u_int32_t), 1);
mutex_spin_exit(&rndpool_mtx);
rp_cpurngs = kmem_zalloc(maxcpus * sizeof(cprng_strong_t *),
KM_SLEEP);
}
int
@ -251,8 +260,10 @@ rnd_read(struct file * fp, off_t *offp, struct uio *uio,
kauth_cred_t cred, int flags)
{
rp_ctx_t *ctx = fp->f_data;
cprng_strong_t *cprng;
u_int8_t *bf;
int strength, ret;
struct cpu_info *ci = curcpu();
DPRINTF(RND_DEBUG_READ,
("Random: Read of %zu requested, flags 0x%08x\n",
@ -261,14 +272,35 @@ rnd_read(struct file * fp, off_t *offp, struct uio *uio,
if (uio->uio_resid == 0)
return (0);
if (ctx->cprng == NULL) {
rnd_alloc_cprng(ctx);
if (__predict_false(ctx->cprng == NULL)) {
return EIO;
if (ctx->hard || uio->uio_resid > NIST_BLOCK_KEYLEN_BYTES) {
if (ctx->cprng == NULL) {
rnd_alloc_cprng(ctx);
}
cprng = ctx->cprng;
} else {
int index = cpu_index(ci);
if (__predict_false(rp_cpurngs[index] == NULL)) {
char rngname[32];
snprintf(rngname, sizeof(rngname),
"%s-short", cpu_name(ci));
rp_cpurngs[index] =
cprng_strong_create(rngname, IPL_NONE,
CPRNG_INIT_ANY |
CPRNG_REKEY_ANY);
}
cprng = rp_cpurngs[index];
}
if (__predict_false(cprng == NULL)) {
printf("NULL rng!\n");
return EIO;
}
strength = cprng_strong_strength(ctx->cprng);
KASSERT(!mutex_owned(&cprng->reseed.mtx));
strength = cprng_strong_strength(cprng);
ret = 0;
bf = pool_cache_get(rp_pc, PR_WAITOK);
while (uio->uio_resid > 0) {
@ -284,7 +316,7 @@ rnd_read(struct file * fp, off_t *offp, struct uio *uio,
n = want;
}
nread = cprng_strong(ctx->cprng, bf, n,
nread = cprng_strong(cprng, bf, n,
(fp->f_flag & FNONBLOCK) ? FNONBLOCK : 0);
if (nread != n) {
if (fp->f_flag & FNONBLOCK) {
@ -294,6 +326,7 @@ rnd_read(struct file * fp, off_t *offp, struct uio *uio,
}
goto out;
}
/* KASSERT(!mutex_owned(&cprng->reseed.mtx)); */
ret = uiomove((void *)bf, nread, uio);
if (ret != 0 || n < want) {
goto out;
@ -302,9 +335,10 @@ rnd_read(struct file * fp, off_t *offp, struct uio *uio,
out:
if (ctx->bytesonkey >= strength) {
/* Force reseed of underlying DRBG (prediction resistance) */
cprng_strong_deplete(ctx->cprng);
cprng_strong_deplete(cprng);
ctx->bytesonkey = 0;
}
pool_cache_put(rp_pc, bf);
return (ret);
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: kern_rndpool.c,v 1.1 2012/02/02 19:43:07 tls Exp $ */
/* $NetBSD: kern_rndpool.c,v 1.2 2012/04/17 02:50:38 tls Exp $ */
/*-
* Copyright (c) 1997 The NetBSD Foundation, Inc.
@ -31,7 +31,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_rndpool.c,v 1.1 2012/02/02 19:43:07 tls Exp $");
__KERNEL_RCSID(0, "$NetBSD: kern_rndpool.c,v 1.2 2012/04/17 02:50:38 tls Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@ -52,7 +52,8 @@ __KERNEL_RCSID(0, "$NetBSD: kern_rndpool.c,v 1.1 2012/02/02 19:43:07 tls Exp $")
/*
* Let others know: the pool is full.
*/
int rnd_full;
int rnd_full = 0; /* Flag: is the pool full? */
int rnd_filled = 0; /* Count: how many times filled? */
static inline void rndpool_add_one_word(rndpool_t *, u_int32_t);
@ -216,6 +217,7 @@ rndpool_add_data(rndpool_t *rp, void *p, u_int32_t len, u_int32_t entropy)
if (rp->stats.curentropy > RND_POOLBITS) {
rp->stats.discarded += (rp->stats.curentropy - RND_POOLBITS);
rp->stats.curentropy = RND_POOLBITS;
rnd_filled++;
rnd_full = 1;
}
}
@ -246,7 +248,9 @@ rndpool_extract_data(rndpool_t *rp, void *p, u_int32_t len, u_int32_t mode)
buf = p;
remain = len;
rnd_full = 0;
if (rp->stats.curentropy < RND_POOLBITS / 2) {
rnd_full = 0;
}
if (mode == RND_EXTRACT_ANY)
good = 1;

View File

@ -1,4 +1,4 @@
/* $NetBSD: kern_rndq.c,v 1.2 2012/04/10 14:02:27 tls Exp $ */
/* $NetBSD: kern_rndq.c,v 1.3 2012/04/17 02:50:38 tls Exp $ */
/*-
* Copyright (c) 1997-2011 The NetBSD Foundation, Inc.
@ -32,7 +32,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_rndq.c,v 1.2 2012/04/10 14:02:27 tls Exp $");
__KERNEL_RCSID(0, "$NetBSD: kern_rndq.c,v 1.3 2012/04/17 02:50:38 tls Exp $");
#include <sys/param.h>
#include <sys/ioctl.h>
@ -221,6 +221,16 @@ rnd_wakeup_readers(void)
*/
mutex_spin_enter(&rndsink_mtx);
TAILQ_FOREACH_SAFE(sink, &rnd_sinks, tailq, tsink) {
if (!mutex_tryenter(&sink->mtx)) {
#ifdef RND_VERBOSE
printf("rnd_wakeup_readers: "
"skipping busy rndsink\n");
#endif
continue;
}
KASSERT(RSTATE_PENDING == sink->state);
if ((sink->len + RND_ENTROPY_THRESHOLD) * 8 <
rndpool_get_entropy_count(&rnd_pool)) {
/* We have enough entropy to sink some here. */
@ -230,13 +240,12 @@ rnd_wakeup_readers(void)
panic("could not extract estimated "
"entropy from pool");
}
/* Skip if busy, else mark in-progress */
if (!mutex_tryenter(&sink->mtx)) {
continue;
}
sink->state = RSTATE_HASBITS;
/* Move this sink to the list of pending callbacks */
TAILQ_REMOVE(&rnd_sinks, sink, tailq);
TAILQ_INSERT_HEAD(&sunk, sink, tailq);
} else {
mutex_exit(&sink->mtx);
}
}
mutex_spin_exit(&rndsink_mtx);
@ -269,6 +278,7 @@ rnd_wakeup_readers(void)
" (cb %p, arg %p).\n",
(int)sink->len, sink->name, sink->cb, sink->arg);
#endif
sink->state = RSTATE_HASBITS;
sink->cb(sink->arg);
TAILQ_REMOVE(&sunk, sink, tailq);
mutex_spin_exit(&sink->mtx);
@ -976,16 +986,19 @@ rndsink_attach(rndsink_t *rs)
#endif
KASSERT(mutex_owned(&rs->mtx));
KASSERT(rs->state = RSTATE_PENDING);
mutex_spin_enter(&rndsink_mtx);
TAILQ_INSERT_TAIL(&rnd_sinks, rs, tailq);
mutex_spin_exit(&rndsink_mtx);
mutex_spin_enter(&rnd_mtx);
if (rnd_timeout_pending == 0) {
rnd_timeout_pending = 1;
callout_schedule(&rnd_callout, 1);
}
mutex_spin_exit(&rnd_mtx);
}
void
@ -995,7 +1008,6 @@ rndsink_detach(rndsink_t *rs)
#ifdef RND_VERBOSE
printf("rnd: entropy sink \"%s\" no longer wants data.\n", rs->name);
#endif
KASSERT(mutex_owned(&rs->mtx));
mutex_spin_enter(&rndsink_mtx);

View File

@ -1,4 +1,4 @@
/* $NetBSD: subr_cprng.c,v 1.7 2012/04/10 15:12:40 tls Exp $ */
/* $NetBSD: subr_cprng.c,v 1.8 2012/04/17 02:50:39 tls Exp $ */
/*-
* Copyright (c) 2011 The NetBSD Foundation, Inc.
@ -46,7 +46,7 @@
#include <sys/cprng.h>
__KERNEL_RCSID(0, "$NetBSD: subr_cprng.c,v 1.7 2012/04/10 15:12:40 tls Exp $");
__KERNEL_RCSID(0, "$NetBSD: subr_cprng.c,v 1.8 2012/04/17 02:50:39 tls Exp $");
void
cprng_init(void)
@ -71,42 +71,83 @@ cprng_counter(void)
return (tv.tv_sec * 1000000 + tv.tv_usec);
}
static void
cprng_strong_doreseed(cprng_strong_t *const c)
{
uint32_t cc = cprng_counter();
KASSERT(mutex_owned(&c->mtx));
KASSERT(mutex_owned(&c->reseed.mtx));
KASSERT(c->reseed.len == NIST_BLOCK_KEYLEN_BYTES);
if (nist_ctr_drbg_reseed(&c->drbg, c->reseed.data, c->reseed.len,
&cc, sizeof(cc))) {
panic("cprng %s: nist_ctr_drbg_reseed failed.", c->name);
}
#ifdef RND_VERBOSE
printf("cprng %s: reseeded with rnd_filled = %d\n", c->name,
rnd_filled);
#endif
c->entropy_serial = rnd_filled;
c->reseed.state = RSTATE_IDLE;
if (c->flags & CPRNG_USE_CV) {
cv_broadcast(&c->cv);
}
selnotify(&c->selq, 0, 0);
}
static void
cprng_strong_sched_reseed(cprng_strong_t *const c)
{
KASSERT(mutex_owned(&c->mtx));
if (!(c->reseed_pending) && mutex_tryenter(&c->reseed.mtx)) {
c->reseed_pending = 1;
c->reseed.len = NIST_BLOCK_KEYLEN_BYTES;
rndsink_attach(&c->reseed);
if (mutex_tryenter(&c->reseed.mtx)) {
switch (c->reseed.state) {
case RSTATE_IDLE:
c->reseed.state = RSTATE_PENDING;
c->reseed.len = NIST_BLOCK_KEYLEN_BYTES;
rndsink_attach(&c->reseed);
break;
case RSTATE_HASBITS:
/* Just rekey the underlying generator now. */
cprng_strong_doreseed(c);
break;
case RSTATE_PENDING:
if (c->entropy_serial != rnd_filled) {
rndsink_detach(&c->reseed);
rndsink_attach(&c->reseed);
}
break;
default:
panic("cprng %s: bad reseed state %d",
c->name, c->reseed.state);
break;
}
mutex_spin_exit(&c->reseed.mtx);
}
#ifdef RND_VERBOSE
else {
printf("cprng %s: skipping sched_reseed, sink busy\n",
c->name);
}
#endif
}
static void
cprng_strong_reseed(void *const arg)
{
cprng_strong_t *c = arg;
uint8_t key[NIST_BLOCK_KEYLEN_BYTES];
uint32_t cc = cprng_counter();
KASSERT(mutex_owned(&c->reseed.mtx));
KASSERT(RSTATE_HASBITS == c->reseed.state);
if (!mutex_tryenter(&c->mtx)) {
#ifdef RND_VERBOSE
printf("cprng: sink %s cprng busy, no reseed\n", c->reseed.name);
#endif
return;
}
if (c->reseed.len != sizeof(key)) {
panic("cprng_strong_reseed: bad entropy length %d "
" (expected %d)", (int)c->reseed.len, (int)sizeof(key));
}
if (nist_ctr_drbg_reseed(&c->drbg, c->reseed.data, c->reseed.len,
&cc, sizeof(cc))) {
panic("cprng %s: nist_ctr_drbg_reseed failed.", c->name);
}
c->reseed_pending = 0;
if (c->flags & CPRNG_USE_CV) {
cv_broadcast(&c->cv);
}
selnotify(&c->selq, 0, 0);
cprng_strong_doreseed(c);
mutex_exit(&c->mtx);
}
@ -124,9 +165,10 @@ cprng_strong_create(const char *const name, int ipl, int flags)
}
c->flags = flags;
strlcpy(c->name, name, sizeof(c->name));
c->reseed_pending = 0;
c->reseed.state = RSTATE_IDLE;
c->reseed.cb = cprng_strong_reseed;
c->reseed.arg = c;
c->entropy_serial = rnd_filled;
mutex_init(&c->reseed.mtx, MUTEX_DEFAULT, IPL_VM);
strlcpy(c->reseed.name, name, sizeof(c->reseed.name));
@ -253,15 +295,15 @@ cprng_strong(cprng_strong_t *const c, void *const p, size_t len, int flags)
if (__predict_false(c->drbg.reseed_counter >
(NIST_CTR_DRBG_RESEED_INTERVAL / 2))) {
cprng_strong_sched_reseed(c);
}
if (rnd_full) {
if (!c->rekeyed_on_full) {
c->rekeyed_on_full++;
} else if (rnd_full) {
if (c->entropy_serial != rnd_filled) {
#ifdef RND_VERBOSE
printf("cprng %s: reseeding from full pool "
"(serial %d vs pool %d)\n", c->name,
c->entropy_serial, rnd_filled);
#endif
cprng_strong_sched_reseed(c);
}
} else {
c->rekeyed_on_full = 0;
}
mutex_exit(&c->mtx);
@ -280,7 +322,7 @@ cprng_strong_destroy(cprng_strong_t *c)
}
seldestroy(&c->selq);
if (c->reseed_pending) {
if (RSTATE_PENDING == c->reseed.state) {
rndsink_detach(&c->reseed);
}
mutex_spin_exit(&c->reseed.mtx);

View File

@ -1,4 +1,4 @@
/* $NetBSD: cprng.h,v 1.4 2011/12/17 20:05:40 tls Exp $ */
/* $NetBSD: cprng.h,v 1.5 2012/04/17 02:50:39 tls Exp $ */
/*-
* Copyright (c) 2011 The NetBSD Foundation, Inc.
@ -85,7 +85,7 @@ typedef struct _cprng_strong {
int flags;
char name[16];
int reseed_pending;
int rekeyed_on_full;
int entropy_serial;
rndsink_t reseed;
} cprng_strong_t;

View File

@ -1,4 +1,4 @@
/* $NetBSD: rnd.h,v 1.30 2012/04/10 14:02:28 tls Exp $ */
/* $NetBSD: rnd.h,v 1.31 2012/04/17 02:50:39 tls Exp $ */
/*-
* Copyright (c) 1997 The NetBSD Foundation, Inc.
@ -127,9 +127,16 @@ typedef struct krndsource {
rngtest_t *test; /* test data for RNG type sources */
} krndsource_t;
enum rsink_st {
RSTATE_IDLE = 0,
RSTATE_PENDING,
RSTATE_HASBITS
};
typedef struct rndsink {
TAILQ_ENTRY(rndsink) tailq; /* the queue */
kmutex_t mtx; /* lock to seed or unregister */
enum rsink_st state; /* in-use? filled? */
void (*cb)(void *); /* callback function when ready */
void *arg; /* callback function argument */
char name[16]; /* sink name */
@ -178,6 +185,7 @@ rnd_add_uint32(krndsource_t *kr, uint32_t val)
}
extern int rnd_full;
extern int rnd_filled;
#endif /* _KERNEL */