/* SPDX-License-Identifier: MIT * Copyright (c) 2026 Leah Rowe * * Numerical functions. */ /* TODO: properly handle errno in this file */ #ifdef __OpenBSD__ #include #endif #include #if defined(FALLBACK_RAND_1989) && \ (FALLBACK_RAND_1989) > 0 #include #endif #include #if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \ defined(__FreeBSD__) || \ defined(__NetBSD__) || defined(__APPLE__)) #include /* if not arc4random: /dev/urandom */ #endif #include #include #include #if defined(FALLBACK_RAND_1989) && \ (FALLBACK_RAND_1989) > 0 #include #endif #include #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); }