Compare commits

...

9 Commits

Author SHA1 Message Date
Leah Rowe
6db9514c95 libreboot-utils: tidy up the rand code
Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 11:28:44 +00:00
Leah Rowe
49cc239884 util/mkhtemp: allow zero as a rand value
yes, zero is a valid response.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 11:13:56 +00:00
Leah Rowe
7bff5712b4 util/libreboot-utils: simplified rand
only use the getrandom syscall on linux,
or arc4random.

the /dev/urandom fallback is removed, and
we use the syscall; failure is almost certainly
unlikely, but if it fails, we abort. this
provides therefore the same guarantee as
bsd arc4random, since it will never return
under fault conditions. it will only ever
return success, or abort.

nobody should be using /dev/urandom in 2026.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 11:08:24 +00:00
Leah Rowe
a6da25ad0b libreboot-utils: remove 1989 rand
added as an academic exercise, but pointless
in the year 2026.

or even the year 1989.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 10:38:37 +00:00
Leah Rowe
f7f1856969 libreboot-utils: move rand to own file
Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 10:34:37 +00:00
Leah Rowe
e1ff02f323 util/libreboot-utils: added more string functions
also reset pointer values

because i can

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 07:51:51 +00:00
Leah Rowe
5f93b7faec lib/mkhtemp: fix bad string comparison
Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 04:58:23 +00:00
Leah Rowe
7efdc40cab lib/mkhtemp: rename suffix to template
suffix is a gnu term, for its mktemp version.
it goes after the template.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 00:44:13 +00:00
Leah Rowe
1228f7e0e5 util/mkhtemp: require at least 3 X
the GNU one requires 3. we should be compatible
with them. i'm going to work on the compatibility
mode - this is phase one!

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 00:30:39 +00:00
7 changed files with 265 additions and 441 deletions

View File

@@ -37,14 +37,16 @@ OBJS_NVMUTIL = \
obj/lib/io.o \
obj/lib/checksum.o \
obj/lib/word.o \
obj/lib/mkhtemp.o
obj/lib/mkhtemp.o \
obj/lib/rand.o
OBJS_MKHTEMP = \
obj/mkhtemp.o \
obj/lib/file.o \
obj/lib/string.o \
obj/lib/num.o \
obj/lib/mkhtemp.o
obj/lib/mkhtemp.o \
obj/lib/rand.o
# default mode
CFLAGS_MODE = $(PORTABLE)
@@ -106,6 +108,9 @@ obj/lib/word.o: lib/word.c
obj/lib/mkhtemp.o: lib/mkhtemp.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/mkhtemp.c -o obj/lib/mkhtemp.o
obj/lib/rand.o: lib/rand.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/rand.c -o obj/lib/rand.o
# install
install: $(PROG) $(PROGMKH)

View File

@@ -13,24 +13,13 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <errno.h>
/* for linux getrandom
*/
#if defined(__linux__)
#include <errno.h>
#if defined(__has_include)
#if __has_include(<sys/random.h>)
#include <sys/random.h>
#define HAVE_GETRANDOM 1
#endif
#endif
#if !defined(HAVE_GETRANDOM)
#include <sys/syscall.h>
#if defined(SYS_getrandom)
#define HAVE_GETRANDOM_SYSCALL 1
#endif
#endif
#endif
#define items(x) (sizeof((x)) / sizeof((x)[0]))
@@ -384,24 +373,18 @@ int slen(const char *scmp, size_t maxlen,
size_t *rval);
int scmp(const char *a, const char *b,
size_t maxlen, int *rval);
int sdup(const char *s,
size_t n, char **dest);
int scat(const char *s1, const char *s2,
size_t n, char **dest);
int dcat(const char *s, size_t n,
size_t off, char **dest1,
char **dest2);
/* numerical functions
*/
unsigned short hextonum(char ch_s);
size_t rlong(void);
#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, size_t len);
#endif
#endif
#else
size_t fallback_rand_1989(void);
size_t entropy_jitter(void);
#endif
/* Helper functions for command: dump
*/

View File

@@ -79,8 +79,8 @@ new_tmp_common(int *fd, char **path, int type,
#endif
struct stat st;
const char *suffix;
size_t suffix_len;
const char *templatestr;
size_t templatestr_len;
size_t dirlen;
size_t destlen;
@@ -144,17 +144,17 @@ new_tmp_common(int *fd, char **path, int type,
goto err;
if (template != NULL)
suffix = template;
templatestr = template;
else
suffix = "tmp.XXXXXXXXXX";
templatestr = "tmp.XXXXXXXXXX";
if (slen(suffix, maxlen, &suffix_len) < 0)
if (slen(templatestr, maxlen, &templatestr_len) < 0)
goto err;
/* sizeof adds an extra byte, useful
* because we also want '.' or '/'
*/
destlen = dirlen + 1 + suffix_len;
destlen = dirlen + 1 + templatestr_len;
if (destlen > maxlen - 1) {
errno = EOVERFLOW;
goto err;
@@ -168,7 +168,7 @@ new_tmp_common(int *fd, char **path, int type,
memcpy(dest, tmpdir, dirlen);
*(dest + dirlen) = '/';
memcpy(dest + dirlen + 1, suffix, suffix_len);
memcpy(dest + dirlen + 1, templatestr, templatestr_len);
*(dest + destlen) = '\0';
fname = dest + dirlen + 1;
@@ -603,7 +603,7 @@ mkhtemp(int *fd,
if_err_sys(slen(template, max_len, &len) < 0) ||
if_err(len >= max_len, EMSGSIZE)
||
if_err_sys(slen(fname, max_len, &fname_len)) ||
if_err_sys(slen(fname, max_len, &fname_len) < 0) ||
if_err(fname == NULL, EINVAL) ||
if_err(strrchr(fname, '/') != NULL, EINVAL))
return -1;
@@ -611,7 +611,7 @@ mkhtemp(int *fd,
for (end = template + len; /* count X */
end > template && *--end == 'X'; xc++);
if (if_err(xc < 6 || xc > len, EINVAL) ||
if (if_err(xc < 3 || xc > len, EINVAL) ||
if_err(fname_len > len, EOVERFLOW))
return -1;
@@ -622,7 +622,7 @@ mkhtemp(int *fd,
if((fname_copy = malloc(fname_len + 1)) == NULL)
goto err;
/* fname_copy = suffix region only; p points to trailing XXXXXX */
/* fname_copy = templatestr region only; p points to trailing XXXXXX */
memcpy(fname_copy,
template + len - fname_len,
fname_len + 1);
@@ -886,66 +886,37 @@ err:
int
mkhtemp_fill_random(char *p, size_t xc)
{
size_t chx = 0;
int rand_failures = 0;
size_t r;
int saved_rand_error = 0;
static char ch[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
size_t chx = 0;
size_t r;
/* clamp rand to prevent modulo bias
* (reduced risk of entropy leak)
*/
size_t limit = ((size_t)-1) - (((size_t)-1) % (sizeof(ch) - 1));
int saved_errno = errno;
if (p == NULL) {
errno = EFAULT;
goto err_mkhtemp_fill_random;
}
if (if_err(p == NULL, EFAULT))
return -1;
for (chx = 0; chx < xc; chx++) {
do {
saved_rand_error = errno;
rand_failures = 0;
retry_rand:
errno = 0;
/* on bsd: uses arc4random
on linux: uses getrandom
on OLD linux: /dev/urandom
on old/other unix: /dev/urandom
*never returns error*
*/
r = rlong();
if (errno > 0) {
if (++rand_failures <= 8)
r = rlong(); /* always returns successful */
if (r >= limit)
goto retry_rand;
goto err_mkhtemp_fill_random;
}
rand_failures = 0;
errno = saved_rand_error;
} while (r >= limit);
p[chx] = ch[r % (sizeof(ch) - 1)];
}
errno = saved_errno;
return 0;
err_mkhtemp_fill_random:
if (errno == saved_errno)
errno = ECANCELED;
return -1;
}
/* WARNING: **ONCE** per file.

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Numerical functions.
* NOTE: randomness was moved to rand.c
*/
/*
@@ -12,10 +13,6 @@ TODO: properly handle errno in this file
#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) || \
@@ -26,10 +23,6 @@ TODO: properly handle errno in this file
#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"
@@ -117,321 +110,6 @@ err_hextonum:
/* 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;
/* don't re-use same fd, to mitaget
* fd injection */
close_no_err(&fd);
}
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)
{

View File

@@ -0,0 +1,112 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Random number generation
*/
#ifndef RAND_H
#define RAND_H
#ifdef __OpenBSD__
#include <sys/param.h>
#endif
#include <sys/types.h>
#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>
#include <unistd.h>
#include <stdlib.h>
#include "../include/common.h"
/* 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.
*/
/* for the linux version: we use only the
* syscall, because we cannot trust /dev/urandom
* to be as robust, and some libc implementations
* may default to /dev/urandom under fault conditions.
*
* for general high reliability, we must abort on
* failure. in practise, it will likely never fail.
* the arc4random call on bsd never returns error.
*/
size_t
rlong(void)
{
size_t rval;
int saved_errno = errno;
errno = 0;
#if (defined(__OpenBSD__) && (OpenBSD) >= 201) || \
defined(__FreeBSD__) || \
defined(__NetBSD__) || defined(__APPLE__)
arc4random_buf(&rval, sizeof(size_t));
goto out;
#elif defined(__linux__)
size_t off = 0;
size_t len = sizeof(rval);
ssize_t rc;
retry_rand:
rc = (ssize_t)syscall(SYS_getrandom,
(char *)&rval + off, len - off, 0);
if (rc < 0) {
if (errno == EINTR || errno == EAGAIN)
goto retry_rand;
goto err; /* possibly unsupported by kernel */
}
if ((off += (size_t)rc) < len)
goto retry_rand;
goto out;
err:
/*
* getrandom can return with error, butt arc4random
* doesn't. generally, getrandom will be reliably,
* but we of course have to maintain parity with
* BSD. So a rand failure is to be interpreted as
* a major systems failure, and we act accordingly.
*/
err_no_cleanup(1, ECANCELED,
"Randomisation failure, possibly unsupported in your kernel.");
exit(EXIT_FAILURE);
#else
#error Unsupported operating system (possibly unsecure randomisation)
#endif
out:
errno = saved_errno;
return rval;
}
#endif

View File

@@ -14,25 +14,12 @@
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <stdint.h>
#include "../include/common.h"
/* scmp() - strict string comparison
*
* strict string comparison
* similar to strncmp, but null and
* unterminated inputs do not produce
* a return value; on error, errno is
* set and -1 is returned.
*
* the real return value is stored in
* the 4th argument by pointer.
*
* the value at rval pointer is set,
* only upon success. callers should
* check the return value accordingly.
*/
/* strict strcmp */
int
scmp(const char *a,
const char *b,
@@ -46,9 +33,8 @@ scmp(const char *a,
if (a == NULL ||
b == NULL ||
rval == NULL) {
errno = EFAULT;
return -1;
goto err;
}
for (ch = 0; ch < maxlen; ch++) {
@@ -67,27 +53,14 @@ scmp(const char *a,
}
}
/* block unterminated strings */
err:
errno = EFAULT;
if (rval != NULL)
*rval = -1;
return -1;
}
/* slen() - strict strict length
*
* strict string length calculation
* similar to strnlen, but null and
* unterminated inputs do not produce
* a return value; on error, errno is
* set and -1 is returned.
*
* the real return value is stored in
* the 3rd argument by pointer.
*
* the value at rval pointer is set,
* only upon success. callers should
* check the return value accordingly.
*/
/* strict strlen */
int
slen(const char *s,
size_t maxlen,
@@ -97,9 +70,8 @@ slen(const char *s,
if (s == NULL ||
rval == NULL) {
errno = EFAULT;
return -1;
goto err;
}
for (ch = 0;
@@ -109,11 +81,114 @@ slen(const char *s,
if (ch == maxlen) {
/* unterminated */
errno = EFAULT;
return -1;
goto err;
}
*rval = ch;
return 0;
err:
if (rval != NULL)
*rval = 0;
return -1;
}
/* strict strdup */
int
sdup(const char *s,
size_t n, char **dest)
{
size_t size;
char *rval;
if (dest == NULL ||
slen(s, n, &size) < 0 ||
if_err(size == SIZE_MAX, EOVERFLOW) ||
(rval = malloc(size + 1)) == NULL) {
if (dest != NULL)
*dest = NULL;
return -1;
}
memcpy(rval, s, size);
*(rval + size) = '\0';
*dest = rval;
return 0;
}
/* strict strcat */
int
scat(const char *s1, const char *s2,
size_t n, char **dest)
{
size_t size1;
size_t size2;
char *rval;
if (dest == NULL ||
slen(s1, n, &size1) < 0 ||
slen(s2, n, &size2) < 0 ||
if_err(size1 > SIZE_MAX - size2 - 1, EOVERFLOW) ||
(rval = malloc(size1 + size2 + 1)) == NULL) {
if (dest != NULL)
*dest = NULL;
return -1;
}
memcpy(rval, s1, size1);
memcpy(rval + size1, s2, size2);
*(rval + size1 + size2) = '\0';
*dest = rval;
return 0;
}
/* strict split/de-cat - off is where
2nd buffer will start from */
int
dcat(const char *s, size_t n,
size_t off, char **dest1,
char **dest2)
{
size_t size;
char *rval1 = NULL;
char *rval2 = NULL;
if (dest1 == NULL || dest2 == NULL ||
slen(s, n, &size) < 0 ||
if_err(size == SIZE_MAX, EOVERFLOW) ||
if_err(off >= size, EOVERFLOW) ||
(rval1 = malloc(off + 1)) == NULL ||
(rval2 = malloc(size - off + 1)) == NULL) {
goto err;
}
memcpy(rval1, s, off);
*(rval1 + off) = '\0';
memcpy(rval2, s + off, size - off);
*(rval2 + size - off) = '\0';
*dest1 = rval1;
*dest2 = rval2;
return 0;
err:
if (rval1 != NULL)
free(rval1);
if (rval2 != NULL)
free(rval2);
if (dest1 != NULL)
*dest1 = NULL;
if (dest2 != NULL)
*dest2 = NULL;
return -1;
}
/* the one for nvmutil state is in state.c */

View File

@@ -140,9 +140,9 @@ main(int argc, char *argv[])
for (p = template + tlen;
p > template && *--p == 'X'; xc++);
if (xc < 6)
if (xc < 3) /* the gnu mktemp errs on less than 3 */
err_no_cleanup(stfu, EINVAL,
"template must end in at least 6 X");
"template must have 3 X or more on end (12+ advised");
}
/* user supplied -p PATH - WARNING: