diff options
author | Damien Miller <djm@cvs.openbsd.org> | 2008-03-02 21:29:08 +0000 |
---|---|---|
committer | Damien Miller <djm@cvs.openbsd.org> | 2008-03-02 21:29:08 +0000 |
commit | df974a94d10240b2159aa0ae4842955efd9252c6 (patch) | |
tree | d0fef5d93ab5796bad8487ebb0dd043e731d25fa /sys/dev | |
parent | 54b368c4e9f5d7310129b5079d26619a2b48b14c (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.c | 51 | ||||
-rw-r--r-- | sys/dev/rndvar.h | 3 |
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 */ |