diff options
| author | Leah Rowe <leah@libreboot.org> | 2026-03-24 00:28:15 +0000 |
|---|---|---|
| committer | Leah Rowe <leah@libreboot.org> | 2026-03-24 01:25:53 +0000 |
| commit | f2544d094ba88e1cfbb7993ad67444852cfd5efd (patch) | |
| tree | 252172594a1284e59e88c73c22f0201508809022 /util/libreboot-utils/lib/num.c | |
| parent | afcd535816b45fd7b0e0d07c1a8580f6f462f5e4 (diff) | |
util/mkhtemp: new utility (hardened mktemp)
part of the same code library as nvmutil.
as part of this, i renamed util/nvmutil
to util/libreboot-utils/ because it is
now a multi-utility codebase.
this is more efficient, since i also wish
to use mkhtemp (function) in nvmutil.
Signed-off-by: Leah Rowe <leah@libreboot.org>
Diffstat (limited to 'util/libreboot-utils/lib/num.c')
| -rw-r--r-- | util/libreboot-utils/lib/num.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/util/libreboot-utils/lib/num.c b/util/libreboot-utils/lib/num.c new file mode 100644 index 00000000..343350b0 --- /dev/null +++ b/util/libreboot-utils/lib/num.c @@ -0,0 +1,444 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> + * + * Numerical functions. + */ + +/* +TODO: properly handle errno in this file + */ + +#ifdef __OpenBSD__ +#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) || \ + defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__)) +#include <fcntl.h> /* if not arc4random: /dev/urandom */ +#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" + +/* TODO: + * make this and errno handling more + * flexible + + in particular: + hextonum could be modified to + write into a buffer instead, + with the converted numbers, + of an arbitrary length + */ +unsigned short +hextonum(char ch_s) +{ + int saved_errno = errno; + + /* rlong() can return error, + but preserves errno if no + error. we need to detect + this because it handles + /dev/urandom sometimes + + therefore, if it's zero + at start, we know if there + was an err at the end, by + return value zero, if errno + was set; this is technically + valid, since zero is also + a valid random number! + + it's an edge case that i had + to fix. i'll rewrite the code + better later. for now, it + should be ok. + */ + errno = 0; + + unsigned char ch; + size_t rval; + + ch = (unsigned char)ch_s; + + if ((unsigned int)(ch - '0') <= 9) { + + rval = ch - '0'; + goto hextonum_success; + } + + ch |= 0x20; + + if ((unsigned int)(ch - 'a') <= 5) { + + rval = ch - 'a' + 10; + goto hextonum_success; + } + + if (ch == '?' || ch == 'x') { + + rval = rlong(); + if (errno > 0) + goto err_hextonum; + + goto hextonum_success; + } + + goto err_hextonum; + +hextonum_success: + + errno = saved_errno; + return (unsigned short)rval & 0xf; + +err_hextonum: + + if (errno == saved_errno) + errno = EINVAL; + else + return 17; /* 17 indicates getrandom/urandom fail */ + + return 16; /* invalid character */ + + /* caller just checks >15. */ +} + +/* Random numbers + */ + +/* when calling this: save errno + * first, then set errno to zero. + * on error, this function will + * set errno and possibly return + * + * rlong also preserves errno + * and leaves it unchanged on + * success, so if you do it + * right, you can detect error. + * this is because it uses + * /dev/urandom which can err. + * ditto getrandom (EINTR), + * theoretically. + */ + +size_t +rlong(void) +{ +#if !(defined(FALLBACK_RAND_1989) && \ + ((FALLBACK_RAND_1989) > 0)) +#if (defined(__OpenBSD__) && (OpenBSD) >= 201) || \ + defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__) + + int saved_errno = errno; + size_t rval; + + arc4random_buf(&rval, sizeof(size_t)); + + errno = saved_errno; + return rval; +#else + static int fd = -1; + static ssize_t nr = -1; + static size_t 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 + size_t rval; + ssize_t new_nr; + + int retries = 0; + int max_retries = 100; + + int saved_errno = errno; + +#if defined(__linux__) +#if defined(HAVE_GETRANDOM) || \ + defined(HAVE_GETRANDOM_SYSCALL) + + /* linux getrandom() + * + * we *can* use arc4random on + * modern linux, but not on + * every libc. better use the + * official linux function + */ + + if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0) { + errno = saved_errno; + 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 err_rlong; + + if (nr < 0 || nr < (ssize_t)sizeof(size_t)) { + + if (fd < 0) { + + fd = open("/dev/urandom", + O_RDONLY | O_BINARY | O_NOFOLLOW | + O_CLOEXEC | O_NOCTTY); + +#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 | O_NOCTTY); +#endif +#endif + + 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 (new_nr < 0 || new_nr < (ssize_t)sizeof(rbuf)) + goto retry_urandom_read; + + /* only reset buffer after successful refill */ + nr = new_nr; + off = 0; + + /* to mitigate file descriptor + * injection, we do not re-use + * the same descriptor each time + */ + (void) close_on_eintr(fd); + fd = -1; + } + + fd = -1; + retries = 0; + + memcpy(&rval, rbuf + off, sizeof(size_t)); + + nr -= (ssize_t)sizeof(size_t); + off += sizeof(size_t); + + errno = saved_errno; + + return rval; + +err_rlong: + + if (errno == saved_errno) + errno = EIO; + + return 0; + +#endif +#else /* FALLBACK_RAND_1989 */ + /* your computer is from a museum + */ + size_t mix = 0; + int nr = 0; + int saved_errno = errno; + + /* 100 times, for entropy + */ + for (nr = 0; nr < 100; nr++) + mix ^= fallback_rand_1989(); + + errno = saved_errno; + return mix; +#endif +} + +#if !(defined(FALLBACK_RAND_1989) && \ + ((FALLBACK_RAND_1989) > 0)) +#if defined(__linux__) +#if defined(HAVE_GETRANDOM) || \ + defined(HAVE_GETRANDOM_SYSCALL) +int /* yes, linux is a fallback */ +fallback_rand_getrandom(void *buf, size_t len) +{ + size_t off = 0; + ssize_t rval = -1; + int saved_errno = errno; + + if (!len) { + errno = EINVAL; + return -1; + } + + if (buf == NULL) { + errno = EFAULT; + return -1; + } + +#if defined(HAVE_GETRANDOM) || \ + defined(HAVE_GETRANDOM_SYSCALL) + + while (off < len) { + +#if defined(HAVE_GETRANDOM) + rval = (ssize_t)getrandom((char *)buf + off, len - off, 0); +#elif defined(HAVE_GETRANDOM_SYSCALL) + rval = (ssize_t)syscall(SYS_getrandom, + (char *)buf + off, len - off, 0); +#endif + + if (rval < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + errno = EIO; + return -1; /* unsupported by kernel */ + } + + if (rval == 0) { + errno = EIO; + return -1; + } + + off += (size_t)rval; + } + + errno = saved_errno; + return 0; + +#else + (void)buf; + (void)len; + + errno = EIO; + return -1; +#endif +} +#endif +#endif +#else +size_t +fallback_rand_1989(void) +{ + static size_t mix = 0; + static size_t counter = 0; + + struct timeval tv; + + /* nobody should use this + * (not crypto-safe) + */ + + gettimeofday(&tv, NULL); + + mix ^= (size_t)tv.tv_sec + ^ (size_t)tv.tv_usec + ^ (size_t)getpid() + ^ (size_t)&mix + ^ counter++ + ^ entropy_jitter(); + + /* + * Stack addresses can vary between + * calls, thus increasing entropy. + */ + mix ^= (size_t)&mix; + mix ^= (size_t)&tv; + mix ^= (size_t)&counter; + + return mix; +} + +size_t +entropy_jitter(void) +{ + size_t mix; + + struct timeval a, b; + ssize_t 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 = (ssize_t)(b.tv_usec - a.tv_usec); + if (mix_diff < 0) + mix_diff = -mix_diff; + + mix ^= (size_t)(mix_diff); + + mix ^= (size_t)&mix; + + } + + return mix; +} +#endif + +void +check_bin(size_t a, const char *a_name) +{ + if (a > 1) + err(EINVAL, "%s must be 0 or 1, but is %lu", + a_name, (size_t)a); +} |
