diff options
Diffstat (limited to 'lib/randread.c')
-rw-r--r-- | lib/randread.c | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/lib/randread.c b/lib/randread.c new file mode 100644 index 0000000..3eaf19e --- /dev/null +++ b/lib/randread.c @@ -0,0 +1,315 @@ +/* Generate buffers of random data. + + Copyright (C) 2006-2022 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Paul Eggert. */ + +/* FIXME: Improve performance by adding support for the RDRAND machine + instruction if available (e.g., Ivy Bridge processors). */ + +#include <config.h> + +#include "randread.h" + +#include <errno.h> +#include <error.h> +#include <exitfail.h> +#include <fcntl.h> +#include <quote.h> +#include <stdalign.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/random.h> + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +#include "minmax.h" +#include "rand-isaac.h" +#include "stdio-safer.h" +#include "unlocked-io.h" +#include "xalloc.h" + +#if _STRING_ARCH_unaligned || _STRING_INLINE_unaligned +# define ALIGNED_POINTER(ptr, type) true +#else +# define ALIGNED_POINTER(ptr, type) ((size_t) (ptr) % alignof (type) == 0) +#endif + +/* The maximum buffer size used for reads of random data. Using the + value 2 * ISAAC_BYTES makes this the largest power of two that + would not otherwise cause struct randread_source to grow. */ +#define RANDREAD_BUFFER_SIZE (2 * ISAAC_BYTES) + +/* A source of random data for generating random buffers. */ +struct randread_source +{ + /* Stream to read random bytes from. If null, the current + implementation uses an internal PRNG (ISAAC). */ + FILE *source; + + /* Function to call, and its argument, if there is an input error or + end of file when reading from the stream; errno is nonzero if + there was an error. If this function returns, it should fix the + problem before returning. The default handler assumes that + handler_arg is the file name of the source. */ + void (*handler) (void const *); + void const *handler_arg; + + /* The buffer for SOURCE. It's kept here to simplify storage + allocation and to make it easier to clear out buffered random + data. */ + union + { + /* The stream buffer, if SOURCE is not null. */ + char c[RANDREAD_BUFFER_SIZE]; + + /* The buffered ISAAC pseudorandom buffer, if SOURCE is null. */ + struct isaac + { + /* The number of bytes that are buffered at the end of data.b. */ + size_t buffered; + + /* State of the ISAAC generator. */ + struct isaac_state state; + + /* Up to a buffer's worth of pseudorandom data. */ + union + { + isaac_word w[ISAAC_WORDS]; + unsigned char b[ISAAC_BYTES]; + } data; + } isaac; + } buf; +}; + + +/* The default error handler. */ + +static void +randread_error (void const *file_name) +{ + if (file_name) + error (exit_failure, errno, + errno == 0 ? _("%s: end of file") : _("%s: read error"), + quote (file_name)); + abort (); +} + +/* Simply return a new randread_source object with the default error + handler. */ + +static struct randread_source * +simple_new (FILE *source, void const *handler_arg) +{ + struct randread_source *s = xmalloc (sizeof *s); + s->source = source; + s->handler = randread_error; + s->handler_arg = handler_arg; + return s; +} + +/* Put a nonce value into BUFFER, with size BUFSIZE. + Return true on success, false (setting errno) on failure. */ + +static bool +get_nonce (void *buffer, size_t bufsize) +{ + char *buf = buffer, *buflim = buf + bufsize; + while (buf < buflim) + { + ssize_t nbytes = getrandom (buf, buflim - buf, 0); + if (0 <= nbytes) + buf += nbytes; + else if (errno != EINTR) + return false; + } + return true; +} + +/* Body of randread_free, broken out to pacify gcc -Wmismatched-dealloc. */ + +static int +randread_free_body (struct randread_source *s) +{ + FILE *source = s->source; + explicit_bzero (s, sizeof *s); + free (s); + return source ? fclose (source) : 0; +} + +/* Create and initialize a random data source from NAME, or use a + reasonable default source if NAME is null. BYTES_BOUND is an upper + bound on the number of bytes that will be needed. If zero, it is a + hard bound; otherwise it is just an estimate. + + If NAME is not null, NAME is saved for use as the argument of the + default handler. Unless a non-default handler is used, NAME's + lifetime should be at least that of the returned value. + + Return NULL (setting errno) on failure. */ + +struct randread_source * +randread_new (char const *name, size_t bytes_bound) +{ + if (bytes_bound == 0) + return simple_new (NULL, NULL); + else + { + FILE *source = NULL; + struct randread_source *s; + + if (name) + if (! (source = fopen_safer (name, "rb"))) + return NULL; + + s = simple_new (source, name); + + if (source) + setvbuf (source, s->buf.c, _IOFBF, MIN (sizeof s->buf.c, bytes_bound)); + else + { + s->buf.isaac.buffered = 0; + if (! get_nonce (s->buf.isaac.state.m, + MIN (sizeof s->buf.isaac.state.m, bytes_bound))) + { + int e = errno; + randread_free_body (s); + errno = e; + return NULL; + } + isaac_seed (&s->buf.isaac.state); + } + + return s; + } +} + + +/* Set S's handler and its argument. HANDLER (HANDLER_ARG) is called + when there is a read error or end of file from the random data + source; errno is nonzero if there was an error. If HANDLER + returns, it should fix the problem before returning. The default + handler assumes that handler_arg is the file name of the source; it + does not return. */ + +void +randread_set_handler (struct randread_source *s, void (*handler) (void const *)) +{ + s->handler = handler; +} + +void +randread_set_handler_arg (struct randread_source *s, void const *handler_arg) +{ + s->handler_arg = handler_arg; +} + + +/* Place SIZE random bytes into the buffer beginning at P, using + the stream in S. */ + +static void +readsource (struct randread_source *s, unsigned char *p, size_t size) +{ + while (true) + { + size_t inbytes = fread (p, sizeof *p, size, s->source); + int fread_errno = errno; + p += inbytes; + size -= inbytes; + if (size == 0) + break; + errno = (ferror (s->source) ? fread_errno : 0); + s->handler (s->handler_arg); + } +} + + +/* Place SIZE pseudorandom bytes into the buffer beginning at P, using + the buffered ISAAC generator in ISAAC. */ + +static void +readisaac (struct isaac *isaac, void *p, size_t size) +{ + size_t inbytes = isaac->buffered; + + while (true) + { + char *char_p = p; + + if (size <= inbytes) + { + memcpy (p, isaac->data.b + ISAAC_BYTES - inbytes, size); + isaac->buffered = inbytes - size; + return; + } + + memcpy (p, isaac->data.b + ISAAC_BYTES - inbytes, inbytes); + p = char_p + inbytes; + size -= inbytes; + + /* If P is aligned, write to *P directly to avoid the overhead + of copying from the buffer. */ + if (ALIGNED_POINTER (p, isaac_word)) + { + isaac_word *wp = p; + while (ISAAC_BYTES <= size) + { + isaac_refill (&isaac->state, wp); + wp += ISAAC_WORDS; + size -= ISAAC_BYTES; + if (size == 0) + { + isaac->buffered = 0; + return; + } + } + p = wp; + } + + isaac_refill (&isaac->state, isaac->data.w); + inbytes = ISAAC_BYTES; + } +} + + +/* Consume random data from *S to generate a random buffer BUF of size + SIZE. */ + +void +randread (struct randread_source *s, void *buf, size_t size) +{ + if (s->source) + readsource (s, buf, size); + else + readisaac (&s->buf.isaac, buf, size); +} + + +/* Clear *S so that it no longer contains undelivered random data, and + deallocate any system resources associated with *S. Return 0 if + successful, a negative number (setting errno) if not (this is rare, + but can occur in theory if there is an input error). */ + +int +randread_free (struct randread_source *s) +{ + return randread_free_body (s); +} |