diff options
Diffstat (limited to 'util/nvmutil/lib/num.c')
| -rw-r--r-- | util/nvmutil/lib/num.c | 288 |
1 files changed, 269 insertions, 19 deletions
diff --git a/util/nvmutil/lib/num.c b/util/nvmutil/lib/num.c index 0442b86c..bbb5a83e 100644 --- a/util/nvmutil/lib/num.c +++ b/util/nvmutil/lib/num.c @@ -1,5 +1,4 @@ /* SPDX-License-Identifier: MIT - * * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> * * Numerical functions. @@ -9,6 +8,10 @@ #include <sys/param.h> #endif #include <sys/types.h> +#if defined(FALLBACK_RAND_1989) && \ + (FALLBACK_RAND_1989) > 0 +#include <sys/time.h> +#endif #include <errno.h> #if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \ @@ -18,6 +21,11 @@ #endif #include <limits.h> #include <stddef.h> +#include <string.h> +#if defined(FALLBACK_RAND_1989) && \ + (FALLBACK_RAND_1989) > 0 +#include <time.h> +#endif #include <unistd.h> #include "../include/common.h" @@ -45,9 +53,12 @@ hextonum(char ch_s) /* Random numbers */ + unsigned long rlong(void) { +#if !(defined(FALLBACK_RAND_1989) && \ + ((FALLBACK_RAND_1989) > 0)) #if (defined(__OpenBSD__) && (OpenBSD) >= 201) || \ defined(__FreeBSD__) || \ defined(__NetBSD__) || defined(__APPLE__) @@ -57,39 +68,278 @@ rlong(void) return rval; #else - int fd; + static int fd = -1; + static long nr = -1; + static unsigned long off = 0; +#if defined (BUFSIZ) + static char rbuf[BUFSIZ]; +#else +#ifndef PORTABLE + static char rbuf[4096]; +#elif ((PORTABLE) > 0) + static char rbuf[256]; /* scarce memory on old systems */ +#else + static char rbuf[4096]; /* typical 32-bit BUFSIZ */ +#endif +#endif + unsigned long rval; + long new_nr; - long nr; + int retries = 0; + int max_retries = 100; - unsigned long rval; +#if defined(__linux__) +#if defined(HAVE_GETRANDOM) || \ + defined(HAVE_GETRANDOM_SYSCALL) - fd = open("/dev/urandom", O_RDONLY | O_BINARY); + /* linux getrandom() + * + * we *can* use arc4random on + * modern linux, but not on + * every libc. better use the + * official linux function + * + * similar benefits to arc4random + * e.g. works in chroot, blocks + * until it has enough entropy, + * and works even when /dev/urandom + * is available (doesn't use it); + * it's generally more reliable + */ -#ifdef __OpenBSD__ - if (fd < 0) /* old openbsd */ - fd = open("/dev/arandom", O_RDONLY | O_BINARY); + if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0) + return rval; + + /* + * now fall back to urandom if getrandom failed: + */ +#endif +#endif + + /* reading from urandom is inherently + * unreliable on old systems, even if + * newer systems make it more reliable + * + * modern linux/bsd make it safe, but + * we have to assume that someone is + * compiling this on linux from 1999 + * + * this logic therefore applies various + * tricks to mitigate possible os bugs + */ + +retry_urandom_read: + + if (++retries > max_retries) + goto rlong_next; + + if (nr < 0 || nr < (long)sizeof(unsigned long)) { + + if (fd < 0) { + + fd = open("/dev/urandom", + O_RDONLY | O_BINARY | O_NOFOLLOW | + O_CLOEXEC); + +#ifdef USE_OLD_DEV_RANDOM +#if (USE_OLD_DEV_RANDOM) > 0 + /* WARNING: + * /dev/random may block + * forever and does **NOT** + * guarantee better entropy + * on old systems + * + * only use it if needed + */ + + if (fd < 0) + fd = open("/dev/random", + O_RDONLY | O_BINARY | O_NOFOLLOW | + O_CLOEXEC); +#endif #endif - if (fd < 0) - fd = open("/dev/random", O_RDONLY | O_BINARY); + if (fd < 0) + goto retry_urandom_read; + + retries = 0; + } + + new_nr = rw_file_exact(fd, (unsigned char *)rbuf, + sizeof(rbuf), 0, IO_READ, LOOP_EAGAIN, + LOOP_EINTR, MAX_ZERO_RW_RETRY, OFF_ERR); - if (fd < 0) - err(errno, "can't open random device"); + if (new_nr < 0 || new_nr < (long)sizeof(rbuf)) + goto retry_urandom_read; - nr = rw_file_exact(fd, (unsigned char *)&rval, - sizeof(unsigned long), 0, IO_READ, LOOP_EAGAIN, - LOOP_EINTR, MAX_ZERO_RW_RETRY, OFF_ERR); + /* only reset buffer after successful refill */ + nr = new_nr; + off = 0; - if (close_on_eintr(fd) < 0) - err(errno, "Can't close randomness fd"); + /* to mitigate file descriptor + * injection, we do not re-use + * the same descriptor each time + */ + (void) close_on_eintr(fd); + fd = -1; + } - if (nr != sizeof(unsigned long)) - err(errno, "Incomplete read from random device"); + fd = -1; + retries = 0; + + memcpy(&rval, rbuf + off, sizeof(unsigned long)); + + nr -= (long)sizeof(unsigned long); + off += sizeof(unsigned long); return rval; + +rlong_next: + + fd = -1; + off = 0; + nr = -1; + + err(EIO, "Can't read from /dev/[ua]random"); + return 0; + +#endif +#else /* FALLBACK_RAND_1989 */ + /* your computer is from a museum + */ + unsigned long mix = 0; + int nr; + + /* 100 times, for entropy + */ + for (nr = 0; nr < 100; nr++) + mix ^= fallback_rand_1989(); + + /* 101 times ;) + */ + return fallback_rand_1989(); #endif } +#if !(defined(FALLBACK_RAND_1989) && \ + ((FALLBACK_RAND_1989) > 0)) +#if defined(__linux__) +#if defined(HAVE_GETRANDOM) || \ + defined(HAVE_GETRANDOM_SYSCALL) +int +fallback_rand_getrandom(void *buf, unsigned long len) +{ + unsigned long off = 0; + long rval = -1; + + if (!len) + return -1; + + if (buf == NULL) + return -1; + +#if defined(HAVE_GETRANDOM) || \ + defined(HAVE_GETRANDOM_SYSCALL) + + while (off < len) { + +#if defined(HAVE_GETRANDOM) + rval = (long)getrandom((char *)buf + off, len - off, 0); +#elif defined(HAVE_GETRANDOM_SYSCALL) + rval = (long)syscall(SYS_getrandom, + (char *)buf + off, len - off, 0); +#endif + + if (rval < 0) { + if (errno == EINTR) + continue; + + return -1; /* unsupported by kernel */ + } + + off += (unsigned long)rval; + } + + return 0; + +#else + (void)buf; + (void)len; + + return -1; +#endif +} +#endif +#endif +#else +/* nobody should use this + * (not crypto-safe) + */ +unsigned long +fallback_rand_1989(void) +{ + static unsigned long mix = 0; + static unsigned long counter = 0; + + struct timeval tv; + + gettimeofday(&tv, NULL); + + mix ^= (unsigned long)tv.tv_sec + ^ (unsigned long)tv.tv_usec + ^ (unsigned long)getpid() + ^ (unsigned long)&mix + ^ counter++ + ^ entropy_jitter(); + + /* + * Stack addresses can vary between + * calls, thus increasing entropy. + */ + mix ^= (unsigned long)&mix; + mix ^= (unsigned long)&tv; + mix ^= (unsigned long)&counter; + + return mix; +} + +unsigned long +entropy_jitter(void) +{ + unsigned long mix; + + struct timeval a, b; + long mix_diff; + + int c; + + mix = 0; + + gettimeofday(&a, NULL); + + for (c = 0; c < 32; c++) { + + getpid(); + gettimeofday(&b, NULL); + + /* + * prevent negative numbers to prevent overflow, + * which would bias rand to large numbers + */ + mix_diff = (long)(b.tv_usec - a.tv_usec); + if (mix_diff < 0) + mix_diff = -mix_diff; + + mix ^= (unsigned long)(mix_diff); + + mix ^= (unsigned long)&mix; + + } + + return mix; +} +#endif + void check_bin(unsigned long a, const char *a_name) { |
