[libvirt] [PATCH] random: don't mix RAND_MAX with random_r

Daniel P. Berrange berrange at redhat.com
Fri Aug 30 09:36:45 UTC 2013


On Thu, Aug 29, 2013 at 05:19:21PM -0600, Eric Blake wrote:
> FreeBSD 10 recently changed their definition of RAND_MAX, to try
> and cover the fact that their evenly distributed results really are
> a smaller range than a full power of 2.  As a result, I did some
> investigation, and learned:
> 
> 1. POSIX requires random() to be evenly distributed across exactly
> 31 bits.  glibc also guarantees this for rand(), but the two are
> unrelated, and POSIX only associates RAND_MAX with rand().
> Avoiding RAND_MAX altogether thus avoids a build failure on
> FreeBSD 10.
> 
> 2. Concatenating random bits from a PRNG will NOT provide uniform
> coverage over the larger value UNLESS the period of the original
> PRNG is at least as large as the number of bits being concatenated.
> Simple example: suppose that RAND_MAX were 1 with a period of 2**1
> (which means that the PRNG merely alternates between 0 and 1).
> Concatenating two successive rand() calls would then invariably
> result in 01 or 10, which is a rather non-uniform distribution
> (00 and 11 are impossible) and an even worse period (2**0, since
> our second attempt will get the same number as our first attempt).
> But a RAND_MAX of 1 with a period of 2**2 (alternating between
> 0, 1, 1, 0) provides sane coverage of all four values, if properly
> tempered.  (Back-to-back calls would still only see half the values
> if we don't do some tempering).  We therefore want to guarantee a
> period of at least 2**64, preferably larger (as a tempering factor);
> POSIX only makes this guarantee for random() with 256 bytes of info.
> 
> * src/util/virrandom.c (virRandomBits): Use constants that are
> accurate for the PRNG we are using, not an unrelated PRNG.
> (randomState): Ensure the period of our PRNG exceeds our usage.
> 
> Signed-off-by: Eric Blake <eblake at redhat.com>
> ---
> 
> I debated pushing this under the build-breaker rule, but would
> like to get feedback from an actual FreeBSD build first if possible.
> 
>  src/util/virrandom.c | 28 +++++++++++++++++++---------
>  1 file changed, 19 insertions(+), 9 deletions(-)
> 
> diff --git a/src/util/virrandom.c b/src/util/virrandom.c
> index c233732..37d1a82 100644
> --- a/src/util/virrandom.c
> +++ b/src/util/virrandom.c
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright (C) 2012 Red Hat, Inc.
> + * Copyright (C) 2012-2013 Red Hat, Inc.
>   *
>   * This library is free software; you can redistribute it and/or
>   * modify it under the terms of the GNU Lesser General Public
> @@ -36,7 +36,7 @@
> 
>  #define VIR_FROM_THIS VIR_FROM_NONE
> 
> -static char randomState[128];
> +static char randomState[256];
>  static struct random_data randomData;
>  static virMutex randomLock;
> 
> @@ -70,9 +70,20 @@ virRandomOnceInit(void)
> 
>  VIR_ONCE_GLOBAL_INIT(virRandom)
> 
> -/* The algorithm of virRandomBits requires that RAND_MAX == 2^n-1 for
> - * some n; gnulib's random_r meets this property. */
> -verify(((RAND_MAX + 1U) & RAND_MAX) == 0);
> +/* The algorithm of virRandomBits relies on gnulib's guarantee that
> + * random_r() matches the POSIX requirements on random() of being
> + * evenly distributed among exactly [0, 2**31) (that is, we always get
> + * exactly 31 bits).  While this happens to be the value of RAND_MAX
> + * on glibc, note that POSIX only requires RAND_MAX to be tied to the
> + * weaker rand(), so there are platforms where RAND_MAX is smaller
> + * than the range of random_r().  For the results to be evenly
> + * distributed among up to 64 bits, we also rely on the period of
> + * random_r() to be at least 2**64, which POSIX only guarantees for
> + * random() if you use 256 bytes of state.  */
> +enum {
> +    RANDOM_BITS_PER_ITER = 31,
> +    RANDOM_BITS_MASK = (1U << RANDOM_BITS_PER_ITER) - 1,
> +};

Using an enum feels a bit wierd for this. Seems like these are
simply 2 constants to #define.

> 
>  /**
>   * virRandomBits:
> @@ -85,7 +96,6 @@ verify(((RAND_MAX + 1U) & RAND_MAX) == 0);
>   */
>  uint64_t virRandomBits(int nbits)
>  {
> -    int bits_per_iter = count_one_bits(RAND_MAX);
>      uint64_t ret = 0;
>      int32_t bits;
> 
> @@ -98,10 +108,10 @@ uint64_t virRandomBits(int nbits)
> 
>      virMutexLock(&randomLock);
> 
> -    while (nbits > bits_per_iter) {
> +    while (nbits > RANDOM_BITS_PER_ITER) {
>          random_r(&randomData, &bits);
> -        ret = (ret << bits_per_iter) | (bits & RAND_MAX);
> -        nbits -= bits_per_iter;
> +        ret = (ret << RANDOM_BITS_PER_ITER) | (bits & RANDOM_BITS_MASK);
> +        nbits -= RANDOM_BITS_PER_ITER;
>      }
> 
>      random_r(&randomData, &bits);

ACK whether you change the enum or not.


Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|




More information about the libvir-list mailing list