summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authorDamien Miller <djm@cvs.openbsd.org>2008-03-02 21:29:08 +0000
committerDamien Miller <djm@cvs.openbsd.org>2008-03-02 21:29:08 +0000
commitdf974a94d10240b2159aa0ae4842955efd9252c6 (patch)
treed0fef5d93ab5796bad8487ebb0dd043e731d25fa /sys/dev
parent54b368c4e9f5d7310129b5079d26619a2b48b14c (diff)
Add a arc4random_uniform() that returns a uniformly distributed number
in the range 0 <= x < upper_bound Please use this new API instead of "arc4random() % upper_bound", as it avoids the "modulo bias" that favours small results when upper_bound is not a power of two. feedback deraadt@ mcbride@; ok deraadt@
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/rnd.c51
-rw-r--r--sys/dev/rndvar.h3
2 files changed, 51 insertions, 3 deletions
diff --git a/sys/dev/rnd.c b/sys/dev/rnd.c
index 139a5a114f2..60df674f0aa 100644
--- a/sys/dev/rnd.c
+++ b/sys/dev/rnd.c
@@ -1,9 +1,10 @@
-/* $OpenBSD: rnd.c,v 1.86 2007/12/29 08:03:05 dlg Exp $ */
+/* $OpenBSD: rnd.c,v 1.87 2008/03/02 21:29:07 djm Exp $ */
/*
* rnd.c -- A strong random number generator
*
* Copyright (c) 1996, 1997, 2000-2002 Michael Shalayeff.
+ * Copyright (c) 2008 Damien Miller.
*
* Version 1.89, last modified 19-Sep-99
*
@@ -241,6 +242,7 @@
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/disk.h>
+#include <sys/limits.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/fcntl.h>
@@ -574,7 +576,7 @@ arc4random_bytes_large(void *buf, size_t n)
mtx_leave(&rndlock);
rc4_keysetup(&lctx, lbuf, sizeof(lbuf));
- rc4_skip(&lctx, 256 * 4);
+ rc4_skip(&lctx, 256 * 4);
rc4_getbytes(&lctx, (u_char*)buf, n);
bzero(lbuf, sizeof(lbuf));
bzero(&lctx, sizeof(lctx));
@@ -598,6 +600,51 @@ arc4random_bytes(void *buf, size_t n)
mtx_leave(&rndlock);
}
+/*
+ * Calculate a uniformly distributed random number less than upper_bound
+ * avoiding "modulo bias".
+ *
+ * Uniformity is achieved by generating new random numbers until the one
+ * returned is outside the range [0, 2**32 % upper_bound). This
+ * guarantees the selected random number will be inside
+ * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
+ * after reduction modulo upper_bound.
+ */
+u_int32_t
+arc4random_uniform(u_int32_t upper_bound)
+{
+ u_int32_t r, min;
+
+ if (upper_bound < 2)
+ return 0;
+
+#if (ULONG_MAX > 0xffffffffUL)
+ min = 0x100000000UL % upper_bound;
+#else
+ /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
+ if (upper_bound > 0x80000000)
+ min = 1 + ~upper_bound; /* 2**32 - upper_bound */
+ else {
+ /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
+ min = ((0xffffffff - (upper_bound << 2)) + 1) % upper_bound;
+ }
+#endif
+
+ /*
+ * This could theoretically loop forever but each retry has
+ * p > 0.5 (worst case, usually far better) of selecting a
+ * number inside the range we need, so it should rarely need
+ * to re-roll.
+ */
+ for (;;) {
+ r = arc4random();
+ if (r >= min)
+ break;
+ }
+
+ return r % upper_bound;
+}
+
void
randomattach(void)
{
diff --git a/sys/dev/rndvar.h b/sys/dev/rndvar.h
index 60305d48fa6..c6831502c0b 100644
--- a/sys/dev/rndvar.h
+++ b/sys/dev/rndvar.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: rndvar.h,v 1.19 2003/11/03 18:24:28 tedu Exp $ */
+/* $OpenBSD: rndvar.h,v 1.20 2008/03/02 21:29:07 djm Exp $ */
/*
* Copyright (c) 1996,2000 Michael Shalayeff.
@@ -87,6 +87,7 @@ void enqueue_randomness(int, int);
void get_random_bytes(void *, size_t);
void arc4random_bytes(void *, size_t);
u_int32_t arc4random(void);
+u_int32_t arc4random_uniform(u_int32_t);
#endif /* _KERNEL */