summaryrefslogtreecommitdiff
path: root/util/libreboot-utils/lib/num.c
diff options
context:
space:
mode:
Diffstat (limited to 'util/libreboot-utils/lib/num.c')
-rw-r--r--util/libreboot-utils/lib/num.c444
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);
+}