Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src Fix races in /dev/u?random initialization and accounting.



details:   https://anonhg.NetBSD.org/src/rev/57807483bce9
branches:  trunk
changeset: 787753:57807483bce9
user:      riastradh <riastradh%NetBSD.org@localhost>
date:      Mon Jul 01 15:22:00 2013 +0000

description:
Fix races in /dev/u?random initialization and accounting.

- Push /dev/random `information-theoretic' accounting into cprng(9).
- Use percpu(9) for the per-CPU CPRNGs.
- Use atomics with correct memory barriers for lazy CPRNG creation.
- Remove /dev/random file kmem grovelling from fstat(1).

diffstat:

 sys/dev/rndpseudo.c   |  407 ++++++++++++++++++++++++++-----------------------
 sys/kern/subr_cprng.c |   52 +++--
 sys/sys/cprng.h       |    7 +-
 sys/sys/rnd.h         |   15 +-
 usr.bin/fstat/misc.c  |   23 +--
 5 files changed, 257 insertions(+), 247 deletions(-)

diffs (truncated from 779 to 300 lines):

diff -r 619611a5a67e -r 57807483bce9 sys/dev/rndpseudo.c
--- a/sys/dev/rndpseudo.c       Mon Jul 01 15:16:33 2013 +0000
+++ b/sys/dev/rndpseudo.c       Mon Jul 01 15:22:00 2013 +0000
@@ -1,11 +1,12 @@
-/*     $NetBSD: rndpseudo.c,v 1.13 2013/06/23 02:35:24 riastradh Exp $ */
+/*     $NetBSD: rndpseudo.c,v 1.14 2013/07/01 15:22:00 riastradh Exp $ */
 
 /*-
- * Copyright (c) 1997-2011 The NetBSD Foundation, Inc.
+ * Copyright (c) 1997-2013 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code is derived from software contributed to The NetBSD Foundation
- * by Michael Graff <explorer%flame.org@localhost> and Thor Lancelot Simon.
+ * by Michael Graff <explorer%flame.org@localhost>, Thor Lancelot Simon, and
+ * Taylor R. Campbell.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -30,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: rndpseudo.c,v 1.13 2013/06/23 02:35:24 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: rndpseudo.c,v 1.14 2013/07/01 15:22:00 riastradh Exp $");
 
 #if defined(_KERNEL_OPT)
 #include "opt_compat_netbsd.h"
@@ -56,6 +57,7 @@
 #include <sys/cprng.h>
 #include <sys/cpu.h>
 #include <sys/stat.h>
+#include <sys/percpu.h>
 
 #include <sys/rnd.h>
 #ifdef COMPAT_50
@@ -88,18 +90,26 @@
 #endif
 
 /*
- * The size of a temporary buffer, kmem_alloc()ed when needed, and used for
- * reading and writing data.
+ * The size of a temporary buffer for reading and writing entropy.
  */
 #define        RND_TEMP_BUFFER_SIZE    512
 
-static pool_cache_t rp_pc;
-static pool_cache_t rp_cpc;
+static pool_cache_t rnd_temp_buffer_cache;
+
+/*
+ * Per-open state -- a lazily initialized CPRNG.
+ */
+struct rnd_ctx {
+       struct cprng_strong     *rc_cprng;
+       bool                    rc_hard;
+};
+
+static pool_cache_t rnd_ctx_cache;
 
 /*
  * The per-CPU RNGs used for short requests
  */
-cprng_strong_t **rp_cpurngs;
+static percpu_t *percpu_urandom_cprng;
 
 /*
  * Our random pool.  This is defined here rather than using the general
@@ -164,190 +174,223 @@
 }
 
 /*
- * "Attach" the random device. This is an (almost) empty stub, since
- * pseudo-devices don't get attached until after config, after the
- * entropy sources will attach. We just use the timing of this event
- * as another potential source of initial entropy.
+ * `Attach' the random device.  We use the timing of this event as
+ * another potential source of initial entropy.
  */
 void
 rndattach(int num)
 {
-       u_int32_t c;
+       uint32_t c;
 
-       /* Trap unwary players who don't call rnd_init() early */
+       /* Trap unwary players who don't call rnd_init() early.  */
        KASSERT(rnd_ready);
 
-       rp_pc = pool_cache_init(RND_TEMP_BUFFER_SIZE, 0, 0, 0,
-                               "rndtemp", NULL, IPL_NONE,
-                               NULL, NULL, NULL);
-       rp_cpc = pool_cache_init(sizeof(rp_ctx_t), 0, 0, 0,
-                                "rndctx", NULL, IPL_NONE,
-                                NULL, NULL, NULL);
+       rnd_temp_buffer_cache = pool_cache_init(RND_TEMP_BUFFER_SIZE, 0, 0, 0,
+           "rndtemp", NULL, IPL_NONE, NULL, NULL, NULL);
+       rnd_ctx_cache = pool_cache_init(sizeof(struct rnd_ctx), 0, 0, 0,
+           "rndctx", NULL, IPL_NONE, NULL, NULL, NULL);
+       percpu_urandom_cprng = percpu_alloc(sizeof(struct cprng_strong *));
 
-       /* mix in another counter */
+       /* Mix in another counter.  */
        c = rndpseudo_counter();
        mutex_spin_enter(&rndpool_mtx);
-       rndpool_add_data(&rnd_pool, &c, sizeof(u_int32_t), 1);
+       rndpool_add_data(&rnd_pool, &c, sizeof(c), 1);
        mutex_spin_exit(&rndpool_mtx);
-
-       rp_cpurngs = kmem_zalloc(maxcpus * sizeof(cprng_strong_t *),
-                                KM_SLEEP);
 }
 
 int
-rndopen(dev_t dev, int flag, int ifmt,
-    struct lwp *l)
+rndopen(dev_t dev, int flags, int fmt, struct lwp *l)
 {
-       rp_ctx_t *ctx;
-       file_t *fp;
-       int fd, hard, error = 0;
+       bool hard;
+       struct file *fp;
+       int fd;
+       int error;
 
        switch (minor(dev)) {
-           case RND_DEV_URANDOM:
-               hard = 0;
+       case RND_DEV_URANDOM:
+               hard = false;
                break;
-           case RND_DEV_RANDOM:
-               hard = 1;
+
+       case RND_DEV_RANDOM:
+               hard = true;
                break;
-           default:
+
+       default:
                return ENXIO;
        }
-       ctx = pool_cache_get(rp_cpc, PR_WAITOK);        
-       if ((error = fd_allocfile(&fp, &fd)) != 0) {
-           pool_cache_put(rp_cpc, ctx);
-           return error;
-       }
-       ctx->cprng = NULL;
-       ctx->hard = hard;
-       ctx->bytesonkey = 0;
-       mutex_init(&ctx->interlock, MUTEX_DEFAULT, IPL_NONE);
+
+       error = fd_allocfile(&fp, &fd);
+       if (error)
+               return error;
 
-       return fd_clone(fp, fd, flag, &rnd_fileops, ctx);
+       /*
+        * Allocate a context, but don't create a CPRNG yet -- do that
+        * lazily because it consumes entropy from the system entropy
+        * pool, which (currently) has the effect of depleting it and
+        * causing readers from /dev/random to block.  If this is
+        * /dev/urandom and the process is about to send only short
+        * reads to it, then we will be using a per-CPU CPRNG anyway.
+        */
+       struct rnd_ctx *const ctx = pool_cache_get(rnd_ctx_cache, PR_WAITOK);
+       ctx->rc_cprng = NULL;
+       ctx->rc_hard = hard;
+
+       error = fd_clone(fp, fd, flags, &rnd_fileops, ctx);
+       KASSERT(error == EMOVEFD);
+
+       return error;
 }
 
-static void
-rnd_alloc_cprng(rp_ctx_t *ctx)
+/*
+ * Fetch a /dev/u?random context's CPRNG, or create and save one if
+ * necessary.
+ */
+static struct cprng_strong *
+rnd_ctx_cprng(struct rnd_ctx *ctx)
 {
-       char personalization_buf[64];
-       struct lwp *l = curlwp;
-       int cflags = ctx->hard ? CPRNG_USE_CV :
-                                CPRNG_INIT_ANY|CPRNG_REKEY_ANY;
+       struct cprng_strong *cprng, *tmp = NULL;
+
+       /* Fast path: if someone has already allocated a CPRNG, use it.  */
+       cprng = ctx->rc_cprng;
+       if (__predict_true(cprng != NULL)) {
+               /* Make sure the CPU hasn't prefetched cprng's guts.  */
+               membar_consumer();
+               goto out;
+       }
+
+       /* Slow path: create a CPRNG.  Allocate before taking locks.  */
+       char name[64];
+       struct lwp *const l = curlwp;
+       (void)snprintf(name, sizeof(name), "%d %"PRIu64" %u",
+           (int)l->l_proc->p_pid, l->l_ncsw, l->l_cpticks);
+       const int flags = (ctx->rc_hard? (CPRNG_USE_CV | CPRNG_HARD) :
+           (CPRNG_INIT_ANY | CPRNG_REKEY_ANY));
+       tmp = cprng_strong_create(name, IPL_NONE, flags);
+
+       /* Publish cprng's guts before the pointer to them.  */
+       membar_producer();
+
+       /* Attempt to publish tmp, unless someone beat us.  */
+       cprng = atomic_cas_ptr(&ctx->rc_cprng, NULL, tmp);
+       if (__predict_false(cprng != NULL)) {
+               /* Make sure the CPU hasn't prefetched cprng's guts.  */
+               membar_consumer();
+               goto out;
+       }
+
+       /* Published.  Commit tmp.  */
+       cprng = tmp;
+       tmp = NULL;
+
+out:   if (tmp != NULL)
+               cprng_strong_destroy(tmp);
+       KASSERT(cprng != NULL);
+       return cprng;
+}
 
-       mutex_enter(&ctx->interlock);
-       if (__predict_true(ctx->cprng == NULL)) {
-               snprintf(personalization_buf,
-                        sizeof(personalization_buf),
-                        "%d%llud%d", l->l_proc->p_pid,
-                        (unsigned long long int)l->l_ncsw, l->l_cpticks);
-               ctx->cprng = cprng_strong_create(personalization_buf,
-                                                IPL_NONE, cflags);
-       }
-       membar_sync();
-       mutex_exit(&ctx->interlock);
+/*
+ * Fetch a per-CPU CPRNG, or create and save one if necessary.
+ */
+static struct cprng_strong *
+rnd_percpu_cprng(void)
+{
+       struct cprng_strong **cprngp, *cprng, *tmp;
+
+       /* Fast path: if there already is a CPRNG for this CPU, use it.  */
+       cprngp = percpu_getref(percpu_urandom_cprng);
+       cprng = *cprngp;
+       if (__predict_true(cprng != NULL))
+               goto out;
+       percpu_putref(percpu_urandom_cprng);
+
+       /*
+        * Slow path: create a CPRNG named by this CPU.
+        *
+        * XXX The CPU of the name may be different from the CPU to
+        * which it is assigned, because we need to choose a name and
+        * allocate a cprng while preemption is enabled.  This could be
+        * fixed by changing the cprng_strong API (e.g., by adding a
+        * cprng_strong_setname or by separating allocation from
+        * initialization), but it's not clear that's worth the
+        * trouble.
+        */
+       char name[32];
+       (void)snprintf(name, sizeof(name), "urandom%u", cpu_index(curcpu()));
+       tmp = cprng_strong_create(name, IPL_NONE,
+           (CPRNG_INIT_ANY | CPRNG_REKEY_ANY));
+
+       /* Try again, but we may have been preempted and lost a race.  */
+       cprngp = percpu_getref(percpu_urandom_cprng);
+       cprng = *cprngp;
+       if (__predict_false(cprng != NULL))
+               goto out;
+
+       /* Commit the CPRNG we just created.  */
+       cprng = tmp;
+       tmp = NULL;
+       *cprngp = cprng;
+
+out:   percpu_putref(percpu_urandom_cprng);
+       if (tmp != NULL)
+               cprng_strong_destroy(tmp);
+       KASSERT(cprng != NULL);
+       return cprng;
 }
 
 static int
-rnd_read(struct file * fp, off_t *offp, struct uio *uio,
-         kauth_cred_t cred, int flags)
+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();



Home | Main Index | Thread Index | Old Index