Compare commits

...

22 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
Leah Rowe
f06db344ad mkhtemp: fix err()
calling it indirectly was out of the question.

must call it directly.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-25 00:03:31 +00:00
Leah Rowe
3ddd7a0d36 util/mkhtemp: add -q option (silence errors)
Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 23:31:51 +00:00
Leah Rowe
2cee58188c util/nvmutil: make tmp files dotfiles
so that they are hidden. yes.

mkhtemp can take any template now

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 22:44:43 +00:00
Leah Rowe
1ed2ca6b69 util/libreboot-utils: rename err() to b0rk()
it behaves a bit differently than err(), so it's
not good to confuse readers

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 22:44:27 +00:00
Leah Rowe
4fc4946f3c util/nvmutil: fix name size in fs_resolve_at
it's capped at 256 bytes

we need it configurable and in sync with
other limits in the code.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 22:21:22 +00:00
Leah Rowe
f8d9c51a36 util/mkhtemp: template support on util
just add a template like yo uwould on other mktemp.
it works perfectly now.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 22:19:40 +00:00
Leah Rowe
b8a045ef86 util/mkhtemp: allow relative path with -p
but only -p

not inside the library. that way, we retain
security. symlinks resolved with use of -p;
a warning will be added about this to the
manpage, when written.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 21:31:33 +00:00
Leah Rowe
715723c7ce mkhtemp: harden tmpdir access control
faccessat used this way respects uid/gid,
handles ACLs (where used), and matches whatt
many real security tools might do.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 20:18:15 +00:00
Leah Rowe
b16bb6c445 util/mkhtemp: loosen execution restriction
Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 20:15:14 +00:00
Leah Rowe
75f03ea696 util/mkhtemp: add directory override (-p) option.
-p dir

to override TMPDIR

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 20:14:47 +00:00
Leah Rowe
88ff5f7380 util/mkhtemp: O_TMPFILE fast path on linux
linux itself provides much of the hardening we need,
and avoids the need for some of our tests. use this
on linux (fall back to openat still, on e.g. bsd)

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 19:57:12 +00:00
Leah Rowe
bf5a3df796 mkhtemp: fix bad comparison
pointers are null, not zero.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 19:57:07 +00:00
Leah Rowe
3522a23587 util/nvmutil: use renameat for atomic write
not rename(). use renameat()

this re-uses the logic added for mkhtemp.

this will later enable more stringent
integrity checks, though we already verify
the integrity of a file after writing it
back, and renameat is always tied to the
descriptor, so it's fine.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-24 19:19:33 +00:00
15 changed files with 646 additions and 586 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]))
@@ -287,6 +276,11 @@ struct xfile {
unsigned char bufcmp[GBE_BUF_SIZE]; /* compare gbe/tmp/reads */
unsigned char pad[GBE_WORK_SIZE]; /* the file that wouldn't die */
/* we later rename in-place, using old fd. renameat() */
int dirfd;
char *base;
char *tmpbase;
};
/* Command table, MAC address, files
@@ -379,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
*/
@@ -489,17 +477,22 @@ int try_err(int loop_err, int errval);
*/
void usage(void);
void err_no_cleanup(int nvm_errval, const char *msg, ...);
void err(int nvm_errval, const char *msg, ...);
void err_no_cleanup(int stfu, int nvm_errval, const char *msg, ...);
void b0rk(int nvm_errval, const char *msg, ...);
int exit_cleanup(void);
const char *getnvmprogname(void);
void err_mkhtemp(int stfu, int errval, const char *msg, ...);
/* libc hardening
*/
int new_tmpfile(int *fd, char **path, char *tmpdir);
int new_tmpdir(int *fd, char **path, char *tmpdir);
int new_tmp_common(int *fd, char **path, int type, char *tmpdir);
int new_tmpfile(int *fd, char **path, char *tmpdir,
const char *template);
int new_tmpdir(int *fd, char **path, char *tmpdir,
const char *template);
int new_tmp_common(int *fd, char **path, int type,
char *tmpdir, const char *template);
int mkhtemp_try_create(int dirfd,
struct stat *st_dir_initial,
char *fname_copy,
@@ -508,6 +501,14 @@ int mkhtemp_try_create(int dirfd,
int *fd,
struct stat *st,
int type);
int
mkhtemp_tmpfile_linux(int dirfd,
struct stat *st_dir_initial,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st);
int mkhtemp(int *fd, struct stat *st,
char *template, int dirfd, const char *fname,
struct stat *st_dir_initial, int type);

View File

@@ -59,10 +59,10 @@ read_checksums(void)
if (_num_invalid >= _max_invalid) {
if (_max_invalid == 1)
err(ECANCELED, "%s: part %lu has a bad checksum",
b0rk(ECANCELED, "%s: part %lu has a bad checksum",
f->fname, (size_t)f->part);
err(ECANCELED, "%s: No valid checksum found in file",
b0rk(ECANCELED, "%s: No valid checksum found in file",
f->fname);
}
}

View File

@@ -46,27 +46,27 @@ sanitize_command_index(size_t c)
check_command_num(c);
if (cmd->argc < 3)
err(EINVAL, "cmd index %lu: argc below 3, %d",
b0rk(EINVAL, "cmd index %lu: argc below 3, %d",
(size_t)c, cmd->argc);
if (cmd->str == NULL)
err(EINVAL, "cmd index %lu: NULL str",
b0rk(EINVAL, "cmd index %lu: NULL str",
(size_t)c);
if (*cmd->str == '\0')
err(EINVAL, "cmd index %lu: empty str",
b0rk(EINVAL, "cmd index %lu: empty str",
(size_t)c);
if (slen(cmd->str, MAX_CMD_LEN +1, &rval) < 0)
err(errno, "Could not get command length");
b0rk(errno, "Could not get command length");
if (rval > MAX_CMD_LEN) {
err(EINVAL, "cmd index %lu: str too long: %s",
b0rk(EINVAL, "cmd index %lu: str too long: %s",
(size_t)c, cmd->str);
}
if (cmd->run == NULL)
err(EINVAL, "cmd index %lu: cmd ptr null",
b0rk(EINVAL, "cmd index %lu: cmd ptr null",
(size_t)c);
check_bin(cmd->arg_part, "cmd.arg_part");
@@ -80,19 +80,19 @@ sanitize_command_index(size_t c)
case NVM_SIZE:
break;
default:
err(EINVAL, "Unsupported rw_size: %lu",
b0rk(EINVAL, "Unsupported rw_size: %lu",
(size_t)gbe_rw_size);
}
if (gbe_rw_size > GBE_PART_SIZE)
err(EINVAL, "rw_size larger than GbE part: %lu",
b0rk(EINVAL, "rw_size larger than GbE part: %lu",
(size_t)gbe_rw_size);
_flag = (cmd->flags & O_ACCMODE);
if (_flag != O_RDONLY &&
_flag != O_RDWR)
err(EINVAL, "invalid cmd.flags setting");
b0rk(EINVAL, "invalid cmd.flags setting");
}
void
@@ -110,7 +110,7 @@ set_cmd(int argc, char *argv[])
cmd = x->cmd[c].str;
if (scmp(argv[2], cmd, MAX_CMD_LEN, &rval) < 0)
err_no_cleanup(EINVAL,
err_no_cleanup(0, EINVAL,
"could not compare command strings");
if (rval != 0)
continue; /* not the right command */
@@ -123,7 +123,7 @@ set_cmd(int argc, char *argv[])
return;
}
err_no_cleanup(EINVAL,
err_no_cleanup(0, EINVAL,
"Too few args on command '%s'", cmd);
}
@@ -148,11 +148,11 @@ set_cmd_args(int argc, char *argv[])
/* Maintainer bug
*/
if (cmd->arg_part && argc < 4)
err(EINVAL,
b0rk(EINVAL,
"arg_part set for command that needs argc4");
if (cmd->arg_part && i == CMD_SETMAC)
err(EINVAL,
b0rk(EINVAL,
"arg_part set on CMD_SETMAC");
if (i == CMD_SETMAC) {
@@ -174,13 +174,13 @@ conv_argv_part_num(const char *part_str)
unsigned char ch;
if (part_str[0] == '\0' || part_str[1] != '\0')
err(EINVAL, "Partnum string '%s' wrong length", part_str);
b0rk(EINVAL, "Partnum string '%s' wrong length", part_str);
/* char signedness is implementation-defined
*/
ch = (unsigned char)part_str[0];
if (ch < '0' || ch > '1')
err(EINVAL, "Bad part number (%c)", ch);
b0rk(EINVAL, "Bad part number (%c)", ch);
return (size_t)(ch - '0');
}
@@ -189,7 +189,7 @@ void
check_command_num(size_t c)
{
if (!valid_command(c))
err(EINVAL, "Invalid run_cmd arg: %lu",
b0rk(EINVAL, "Invalid run_cmd arg: %lu",
(size_t)c);
}
@@ -205,7 +205,7 @@ valid_command(size_t c)
cmd = &x->cmd[c];
if (c != cmd->chk)
err(EINVAL,
b0rk(EINVAL,
"Invalid cmd chk value (%lu) vs arg: %lu",
cmd->chk, c);
@@ -240,10 +240,10 @@ parse_mac_string(void)
size_t rval;
if (slen(x->mac.str, 18, &rval) < 0)
err(EINVAL, "Could not determine MAC length");
b0rk(EINVAL, "Could not determine MAC length");
if (rval != 17)
err(EINVAL, "MAC address is the wrong length");
b0rk(EINVAL, "MAC address is the wrong length");
memset(mac->mac_buf, 0, sizeof(mac->mac_buf));
@@ -251,10 +251,10 @@ parse_mac_string(void)
set_mac_byte(mac_byte);
if ((mac->mac_buf[0] | mac->mac_buf[1] | mac->mac_buf[2]) == 0)
err(EINVAL, "Must not specify all-zeroes MAC address");
b0rk(EINVAL, "Must not specify all-zeroes MAC address");
if (mac->mac_buf[0] & 1)
err(EINVAL, "Must not specify multicast MAC address");
b0rk(EINVAL, "Must not specify multicast MAC address");
}
void
@@ -272,7 +272,7 @@ set_mac_byte(size_t mac_byte_pos)
if (mac_str_pos < 15) {
if ((separator = mac->str[mac_str_pos + 2]) != ':')
err(EINVAL, "Invalid MAC address separator '%c'",
b0rk(EINVAL, "Invalid MAC address separator '%c'",
separator);
}
@@ -294,9 +294,9 @@ set_mac_nib(size_t mac_str_pos,
if ((hex_num = hextonum(mac_ch)) > 15) {
if (hex_num >= 17)
err(EIO, "Randomisation failure");
b0rk(EIO, "Randomisation failure");
else
err(EINVAL, "Invalid character '%c'",
b0rk(EINVAL, "Invalid character '%c'",
mac->str[mac_str_pos + mac_nib_pos]);
}
@@ -509,7 +509,7 @@ cat(size_t nff)
if ((size_t)x->cat != nff) {
err(ECANCELED, "erroneous call to cat");
b0rk(ECANCELED, "erroneous call to cat");
}
fflush(NULL);
@@ -532,12 +532,12 @@ void
cat_buf(unsigned char *b)
{
if (b == NULL)
err(errno, "null pointer in cat command");
b0rk(errno, "null pointer in cat command");
if (rw_file_exact(STDOUT_FILENO, b,
GBE_PART_SIZE, 0, IO_WRITE, LOOP_EAGAIN, LOOP_EINTR,
MAX_ZERO_RW_RETRY, OFF_ERR) < 0)
err(errno, "stdout: cat");
b0rk(errno, "stdout: cat");
}
void
check_cmd(void (*fn)(void),
@@ -547,7 +547,7 @@ check_cmd(void (*fn)(void),
size_t i = x->i;
if (x->cmd[i].run != fn)
err(ECANCELED, "Running %s, but cmd %s is set",
b0rk(ECANCELED, "Running %s, but cmd %s is set",
name, x->cmd[i].str);
/* prevent second command
@@ -559,6 +559,6 @@ check_cmd(void (*fn)(void),
void
cmd_helper_err(void)
{
err(ECANCELED,
b0rk(ECANCELED,
"Erroneously running command twice");
}

View File

@@ -96,16 +96,16 @@ void
xopen(int *fd_ptr, const char *path, int flags, struct stat *st)
{
if ((*fd_ptr = open(path, flags)) < 0)
err(errno, "%s", path);
err_no_cleanup(0, errno, "%s", path);
if (fstat(*fd_ptr, st) < 0)
err(errno, "%s: stat", path);
err_no_cleanup(0, errno, "%s: stat", path);
if (!S_ISREG(st->st_mode))
err(errno, "%s: not a regular file", path);
err_no_cleanup(0, errno, "%s: not a regular file", path);
if (lseek_on_eintr(*fd_ptr, 0, SEEK_CUR, 1, 1) == (off_t)-1)
err(errno, "%s: file not seekable", path);
err_no_cleanup(0, errno, "%s: file not seekable", path);
}
/* fsync() the directory of a file,
@@ -787,7 +787,12 @@ fs_resolve_at(int dirfd, const char *path, int flags)
int nextfd = -1;
int curfd;
const char *p;
char name[256];
#if defined(PATH_LEN) && \
((PATH_LEN) >= 256)
char name[PATH_LEN];
#else
char name[4096];
#endif
int saved_errno = errno;
int r;
int is_last;

View File

@@ -32,16 +32,16 @@ open_gbe_file(void)
O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, &f->gbe_st);
if (f->gbe_st.st_nlink > 1)
err(EINVAL,
b0rk(EINVAL,
"%s: warning: file has multiple (%lu) hard links\n",
f->fname, (size_t)f->gbe_st.st_nlink);
if (f->gbe_st.st_nlink == 0)
err(EIO, "%s: file unlinked while open", f->fname);
b0rk(EIO, "%s: file unlinked while open", f->fname);
_flags = fcntl(f->gbe_fd, F_GETFL);
if (_flags == -1)
err(errno, "%s: fcntl(F_GETFL)", f->fname);
b0rk(errno, "%s: fcntl(F_GETFL)", f->fname);
/* O_APPEND allows POSIX write() to ignore
* the current write offset and write at EOF,
@@ -49,7 +49,7 @@ open_gbe_file(void)
*/
if (_flags & O_APPEND)
err(EIO, "%s: O_APPEND flag", f->fname);
b0rk(EIO, "%s: O_APPEND flag", f->fname);
f->gbe_file_size = f->gbe_st.st_size;
@@ -59,11 +59,11 @@ open_gbe_file(void)
case SIZE_128KB:
break;
default:
err(EINVAL, "File size must be 8KB, 16KB or 128KB");
b0rk(EINVAL, "File size must be 8KB, 16KB or 128KB");
}
if (lock_file(f->gbe_fd, cmd->flags) == -1)
err(errno, "%s: can't lock", f->fname);
b0rk(errno, "%s: can't lock", f->fname);
}
void
@@ -98,7 +98,7 @@ read_file(void)
MAX_ZERO_RW_RETRY, OFF_ERR);
if (_r < 0)
err(errno, "%s: read failed", f->fname);
b0rk(errno, "%s: read failed", f->fname);
/* copy to tmpfile
*/
@@ -107,34 +107,34 @@ read_file(void)
MAX_ZERO_RW_RETRY, OFF_ERR);
if (_r < 0)
err(errno, "%s: %s: copy failed",
b0rk(errno, "%s: %s: copy failed",
f->fname, f->tname);
/* file size comparison
*/
if (fstat(f->tmp_fd, &_st) == -1)
err(errno, "%s: stat", f->tname);
b0rk(errno, "%s: stat", f->tname);
f->gbe_tmp_size = _st.st_size;
if (f->gbe_tmp_size != f->gbe_file_size)
err(EIO, "%s: %s: not the same size",
b0rk(EIO, "%s: %s: not the same size",
f->fname, f->tname);
/* needs sync, for verification
*/
if (fsync_on_eintr(f->tmp_fd) == -1)
err(errno, "%s: fsync (tmpfile copy)", f->tname);
b0rk(errno, "%s: fsync (tmpfile copy)", f->tname);
_r = rw_file_exact(f->tmp_fd, f->bufcmp, f->gbe_file_size,
0, IO_PREAD, NO_LOOP_EAGAIN, LOOP_EINTR,
MAX_ZERO_RW_RETRY, OFF_ERR);
if (_r < 0)
err(errno, "%s: read failed (cmp)", f->tname);
b0rk(errno, "%s: read failed (cmp)", f->tname);
if (memcmp(f->buf, f->bufcmp, f->gbe_file_size) != 0)
err(errno, "%s: %s: read contents differ (pre-test)",
b0rk(errno, "%s: %s: read contents differ (pre-test)",
f->fname, f->tname);
}
@@ -152,10 +152,10 @@ write_gbe_file(void)
return;
if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0)
err(errno, "%s: file inode/device changed", f->tname);
b0rk(errno, "%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
err(errno, "%s: file has changed", f->fname);
b0rk(errno, "%s: file has changed", f->fname);
update_checksum = cmd->chksum_write;
@@ -188,7 +188,7 @@ rw_gbe_file_part(size_t p, int rw_type,
gbe_rw_size = cmd->rw_size;
if (rw_type < IO_PREAD || rw_type > IO_PWRITE)
err(errno, "%s: %s: part %lu: invalid rw_type, %d",
b0rk(errno, "%s: %s: part %lu: invalid rw_type, %d",
f->fname, rw_type_str, (size_t)p, rw_type);
mem_offset = gbe_mem_offset(p, rw_type_str);
@@ -198,11 +198,11 @@ rw_gbe_file_part(size_t p, int rw_type,
gbe_rw_size, file_offset, rw_type);
if (rval == -1)
err(errno, "%s: %s: part %lu",
b0rk(errno, "%s: %s: part %lu",
f->fname, rw_type_str, (size_t)p);
if ((size_t)rval != gbe_rw_size)
err(EIO, "%s: partial %s: part %lu",
b0rk(EIO, "%s: partial %s: part %lu",
f->fname, rw_type_str, (size_t)p);
}
@@ -226,7 +226,7 @@ write_to_gbe_bin(void)
*/
if (fsync_on_eintr(f->tmp_fd) == -1)
err(errno, "%s: fsync (pre-verification)",
b0rk(errno, "%s: fsync (pre-verification)",
f->tname);
check_written_part(0);
@@ -235,7 +235,7 @@ write_to_gbe_bin(void)
report_io_err_rw();
if (f->io_err_gbe)
err(EIO, "%s: bad write", f->fname);
b0rk(EIO, "%s: bad write", f->fname);
saved_errno = errno;
@@ -307,10 +307,10 @@ check_written_part(size_t p)
memset(f->pad, 0xff, sizeof(f->pad));
if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0)
err(errno, "%s: file inode/device changed", f->tname);
b0rk(errno, "%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
err(errno, "%s: file changed during write", f->fname);
b0rk(errno, "%s: file changed during write", f->fname);
rval = rw_gbe_file_exact(f->tmp_fd, f->pad,
gbe_rw_size, file_offset, IO_PREAD);
@@ -432,32 +432,12 @@ gbe_mv(void)
saved_errno = errno;
/* TODO: remove this path-based rename,
use fd */
rval = rename(f->tname, f->fname);
if (rval > -1) {
/* rename on same filesystem
*/
rval = fs_rename_at(f->dirfd, f->tmpbase,
f->dirfd, f->base);
if (rval > -1)
tmp_gbe_bin_exists = 0;
/*
if (fsync(dest_fd) < 0) {
f->io_err_gbe_bin = 1;
rval = -1;
}
*/
goto ret_gbe_mv;
}
if (errno != EXDEV)
goto ret_gbe_mv;
err(errno, "BUG: cross-filesystem move (this shouldn't happen)");
ret_gbe_mv:
/* TODO: this whole section is bloat.
@@ -560,11 +540,11 @@ gbe_x_offset(size_t p, const char *f_op, const char *d_type,
off = ((off_t)p) * (off_t)nsize;
if (off > ncmp - GBE_PART_SIZE)
err(ECANCELED, "%s: GbE %s %s out of bounds",
b0rk(ECANCELED, "%s: GbE %s %s out of bounds",
f->fname, d_type, f_op);
if (off != 0 && off != ncmp >> 1)
err(ECANCELED, "%s: GbE %s %s at bad offset",
b0rk(ECANCELED, "%s: GbE %s %s at bad offset",
f->fname, d_type, f_op);
return off;

View File

@@ -19,26 +19,36 @@
#include <string.h>
#include <unistd.h>
/* for openat2: */
/* for openat2 / fast path: */
#ifdef __linux__
#include <linux/openat2.h>
#include <sys/syscall.h>
#ifndef O_TMPFILE
#define O_TMPFILE 020000000
#endif
#ifndef AT_EMPTY_PATH
#define AT_EMPTY_PATH 0x1000
#endif
#endif
#include "../include/common.h"
/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */
int
new_tmpfile(int *fd, char **path, char *tmpdir)
new_tmpfile(int *fd, char **path, char *tmpdir,
const char *template)
{
return new_tmp_common(fd, path, MKHTEMP_FILE, tmpdir);
return new_tmp_common(fd, path, MKHTEMP_FILE,
tmpdir, template);
}
/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */
int
new_tmpdir(int *fd, char **path, char *tmpdir)
new_tmpdir(int *fd, char **path, char *tmpdir,
const char *template)
{
return new_tmp_common(fd, path, MKHTEMP_DIR, tmpdir);
return new_tmp_common(fd, path, MKHTEMP_DIR,
tmpdir, template);
}
/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */
@@ -59,7 +69,7 @@ new_tmpdir(int *fd, char **path, char *tmpdir)
*/
int
new_tmp_common(int *fd, char **path, int type,
char *tmpdir)
char *tmpdir, const char *template)
{
#if defined(PATH_LEN) && \
(PATH_LEN) >= 256
@@ -69,7 +79,8 @@ new_tmp_common(int *fd, char **path, int type,
#endif
struct stat st;
char suffix[] = "tmp.XXXXXXXXXX";
const char *templatestr;
size_t templatestr_len;
size_t dirlen;
size_t destlen;
@@ -132,10 +143,18 @@ new_tmp_common(int *fd, char **path, int type,
if (*tmpdir != '/')
goto err;
if (template != NULL)
templatestr = template;
else
templatestr = "tmp.XXXXXXXXXX";
if (slen(templatestr, maxlen, &templatestr_len) < 0)
goto err;
/* sizeof adds an extra byte, useful
* because we also want '.' or '/'
*/
destlen = dirlen + sizeof(suffix);
destlen = dirlen + 1 + templatestr_len;
if (destlen > maxlen - 1) {
errno = EOVERFLOW;
goto err;
@@ -149,7 +168,7 @@ new_tmp_common(int *fd, char **path, int type,
memcpy(dest, tmpdir, dirlen);
*(dest + dirlen) = '/';
memcpy(dest + dirlen + 1, suffix, sizeof(suffix) - 1);
memcpy(dest + dirlen + 1, templatestr, templatestr_len);
*(dest + destlen) = '\0';
fname = dest + dirlen + 1;
@@ -427,11 +446,23 @@ world_writeable_and_sticky(
/* must be fully executable
* by everyone, or openat2
* becomes unreliable**
*
* TODO: loosen these, as a toggle.
* execution rights isn't
* really a requirement for
* TMPDIR, except maybe search,
* but this function will be
* generalised at some point
* for use in other tools
* besides just mkhtemp.
*/
/*
if (!(st.st_mode & S_IXUSR) ||
!(st.st_mode & S_IXGRP) ||
!(st.st_mode & S_IXOTH)) {
*/
/* just require it for *you*, for now */
if (!(st.st_mode & S_IXUSR)) {
errno = EACCES;
goto sticky_hell;
}
@@ -457,6 +488,12 @@ world_writeable_and_sticky(
goto sticky_hell; /* not sticky */
}
/* if anyone even looks at you funny, drop
* everything on the floor and refuse to function
*/
if (faccessat(dirfd, ".", X_OK, AT_EACCESS) < 0)
goto sticky_hell;
/* non-world-writeable, so
* stickiness is do-not-care
*/
@@ -566,15 +603,15 @@ 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(fname == 0, EINVAL) ||
if_err_sys(slen(fname, max_len, &fname_len) < 0) ||
if_err(fname == NULL, EINVAL) ||
if_err(strrchr(fname, '/') != NULL, EINVAL))
return -1;
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;
@@ -585,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);
@@ -655,6 +692,18 @@ mkhtemp_try_create(int dirfd,
goto err;
if (type == MKHTEMP_FILE) {
#ifdef __linux__
/* try O_TMPFILE fast path */
if (mkhtemp_tmpfile_linux(dirfd,
st_dir_initial, fname_copy,
p, xc, fd, st) == 0) {
errno = saved_errno;
rval = 1;
goto out;
}
#endif
*fd = openat2p(dirfd, fname_copy,
O_RDWR | O_CREAT | O_EXCL |
O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, 0600);
@@ -746,56 +795,121 @@ out:
return rval;
}
/* linux has its own special hardening
available specifically for tmpfiles,
which eliminates many race conditions.
we still use openat() on bsd, which is
still ok with our other mitigations
*/
#ifdef __linux__
int
mkhtemp_tmpfile_linux(int dirfd,
struct stat *st_dir_initial,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st)
{
int saved_errno = errno;
int tmpfd = -1;
size_t retries;
int linked = 0;
if (fd == NULL || st == NULL ||
fname_copy == NULL || p == NULL ||
st_dir_initial == NULL) {
errno = EFAULT;
return -1;
}
/* create unnamed tmpfile */
tmpfd = openat(dirfd, ".",
O_TMPFILE | O_RDWR | O_CLOEXEC, 0600);
if (tmpfd < 0)
return -1;
if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
goto err;
for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) {
if (mkhtemp_fill_random(p, xc) < 0)
goto err;
if (fd_verify_dir_identity(dirfd,
st_dir_initial) < 0)
goto err;
if (linkat(tmpfd, "",
dirfd, fname_copy,
AT_EMPTY_PATH) == 0) {
linked = 1; /* file created */
if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
goto err;
/* success */
*fd = tmpfd;
if (fstat(*fd, st) < 0)
goto err;
if (secure_file(fd, st, st,
O_APPEND, 1, 1, 0600) < 0)
goto err;
errno = saved_errno;
return 0;
}
if (errno != EEXIST)
goto err;
/* retry on collision */
}
errno = EEXIST;
err:
if (linked)
(void) unlinkat(dirfd, fname_copy, 0);
close_no_err(&tmpfd);
return -1;
}
#endif
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
*/
r = rlong();
if (errno > 0) {
if (++rand_failures <= 8)
goto retry_rand;
goto err_mkhtemp_fill_random;
}
rand_failures = 0;
errno = saved_rand_error;
} while (r >= limit);
/* on bsd: uses arc4random
on linux: uses getrandom
*never returns error*
*/
r = rlong(); /* always returns successful */
if (r >= limit)
goto retry_rand;
p[chx] = ch[r % (sizeof(ch) - 1)];
}
@@ -803,12 +917,6 @@ retry_rand:
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,325 +110,10 @@ 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)
{
if (a > 1)
err(EINVAL, "%s must be 0 or 1, but is %lu",
err_no_cleanup(0, EINVAL, "%s must be 0 or 1, but is %lu",
a_name, (size_t)a);
}

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

@@ -29,6 +29,8 @@ xstart(int argc, char *argv[])
static char *dir = NULL;
static char *base = NULL;
char *realdir = NULL;
char *tmpdir = NULL;
char *tmpbase_local = NULL;
static struct xstate us = {
{
@@ -96,9 +98,9 @@ xstart(int argc, char *argv[])
return &us;
if (argc < 3)
err_no_cleanup(EINVAL, "xstart: Too few arguments");
err_no_cleanup(0, EINVAL, "xstart: Too few arguments");
if (argv == NULL)
err_no_cleanup(EINVAL, "xstart: NULL argv");
err_no_cleanup(0, EINVAL, "xstart: NULL argv");
first_run = 0;
@@ -111,23 +113,41 @@ xstart(int argc, char *argv[])
us.f.tname = NULL;
if ((realdir = realpath(us.f.fname, NULL)) == NULL)
err_no_cleanup(errno, "xstart: can't get realpath of %s",
err_no_cleanup(0, errno, "xstart: can't get realpath of %s",
us.f.fname);
if (fs_dirname_basename(realdir, &dir, &base, 0) < 0)
err_no_cleanup(errno, "xstart: don't know CWD of %s",
err_no_cleanup(0, errno, "xstart: don't know CWD of %s",
us.f.fname);
if (new_tmpfile(&us.f.tmp_fd, &us.f.tname, dir) < 0)
err_no_cleanup(errno, "%s", us.f.tname);
if ((us.f.base = strdup(base)) == NULL)
err_no_cleanup(0, errno, "strdup base");
us.f.dirfd = fs_open(dir,
O_RDONLY | O_DIRECTORY);
if (us.f.dirfd < 0)
err_no_cleanup(0, errno, "%s: open dir", dir);
if (new_tmpfile(&us.f.tmp_fd, &us.f.tname, dir, ".gbe.XXXXXXXXXX") < 0)
err_no_cleanup(0, errno, "%s", us.f.tname);
if (fs_dirname_basename(us.f.tname,
&tmpdir, &tmpbase_local, 0) < 0)
err_no_cleanup(0, errno, "tmp basename");
us.f.tmpbase = strdup(tmpbase_local);
if (us.f.tmpbase == NULL)
err_no_cleanup(0, errno, "strdup tmpbase");
free_if_null(&tmpdir);
if (us.f.tname == NULL)
err_no_cleanup(errno, "x->f.tname null");
err_no_cleanup(0, errno, "x->f.tname null");
if (*us.f.tname == '\0')
err_no_cleanup(errno, "x->f.tname empty");
err_no_cleanup(0, errno, "x->f.tname empty");
if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0)
err_no_cleanup(errno, "%s: stat", us.f.tname);
err_no_cleanup(0, errno, "%s: stat", us.f.tname);
memset(us.f.real_buf, 0, sizeof(us.f.real_buf));
memset(us.f.bufcmp, 0, sizeof(us.f.bufcmp));
@@ -144,13 +164,13 @@ xstatus(void)
struct xstate *x = xstart(0, NULL);
if (x == NULL)
err_no_cleanup(EACCES, "NULL pointer to xstate");
err_no_cleanup(0, EACCES, "NULL pointer to xstate");
return x;
}
void
err(int nvm_errval, const char *msg, ...)
b0rk(int nvm_errval, const char *msg, ...)
{
struct xstate *x = xstatus();
@@ -197,6 +217,10 @@ exit_cleanup(void)
if (f->tname != NULL)
if (unlink(f->tname) == -1)
close_err = 1;
close_no_err(&f->dirfd);
free_if_null(&f->base);
free_if_null(&f->tmpbase);
}
if (saved_errno)

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,17 +81,120 @@ 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 */
/* this one just exits */
void
err_no_cleanup(int nvm_errval, const char *msg, ...)
err_no_cleanup(int stfu, int nvm_errval, const char *msg, ...)
{
va_list args;
int saved_errno = errno;
@@ -141,7 +216,10 @@ err_no_cleanup(int nvm_errval, const char *msg, ...)
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, ": %s\n", strerror(errno));
if (p != NULL)
fprintf(stderr, ": %s\n", strerror(errno));
else
fprintf(stderr, "%s\n", strerror(errno));
exit(EXIT_FAILURE);
}

View File

@@ -26,5 +26,5 @@ usage(void)
util, util, util, util,
util, util, util);
err(EINVAL, "Too few arguments");
b0rk(EINVAL, "Too few arguments");
}

View File

@@ -63,6 +63,6 @@ check_nvm_bound(size_t c, size_t p)
check_bin(p, "part number");
if (c >= NVM_WORDS)
err(ECANCELED, "check_nvm_bound: out of bounds %lu",
b0rk(ECANCELED, "check_nvm_bound: out of bounds %lu",
(size_t)c);
}

View File

@@ -10,6 +10,10 @@
* generally provides much higher strictness than previous
* implementations such as mktemp, mkstemp or even mkdtemp.
*
* It uses several modern features by default, e.g. openat2
* and O_TMPFILE on Linux, with additional hardening; BSD
* projects only have openat so the code uses that there.
*
* Many programs rely on mktemp, and they use TMPDIR in a way
* that is quite insecure. Mkhtemp intends to change that,
* quite dramatically, with: userspace sandbox (and use OS
@@ -67,67 +71,120 @@
int
main(int argc, char *argv[])
{
char *s = NULL;
int fd = -1;
char c;
int type = MKHTEMP_FILE;
size_t len;
#if defined (PATH_LEN) && \
(PATH_LEN) >= 256
size_t maxlen = PATH_LEN;
#else
size_t maxlen = 4096;
#endif
size_t len;
size_t tlen;
size_t xc = 0;
char *tmpdir = NULL;
char *template = NULL;
char *p;
char *s = NULL;
char *rp;
char resolved[maxlen];
char c;
int fd = -1;
int type = MKHTEMP_FILE;
int stfu = 0; /* -q option */
if (lbgetprogname(argv[0]) == NULL)
err_no_cleanup(errno, "could not set progname");
err_no_cleanup(stfu, errno, "could not set progname");
/* https://man.openbsd.org/pledge.2 */
#if defined(__OpenBSD__) && defined(OpenBSD)
#if (OpenBSD) >= 509
if (pledge("stdio flock rpath wpath cpath", NULL) == -1)
err_no_cleanup(errno, "pledge, main");
goto err_usage;
#endif
#endif
while ((c =
getopt(argc, argv, "d")) != -1) {
while ((c =
getopt(argc, argv, "qdp:")) != -1) {
switch(c) {
switch (c) {
case 'd':
type = MKHTEMP_DIR;
break;
default:
err_no_cleanup(EINVAL,
"usage: mkhtemp [-d]\n");
case 'p':
tmpdir = optarg;
break;
case 'q': /* don't print errors */
/* (exit status unchanged) */
stfu = 1;
break;
default:
goto err_usage;
}
}
if (new_tmp_common(&fd, &s, type, NULL) < 0)
err_no_cleanup(errno, "%s", s);
if (optind < argc)
template = argv[optind];
if (optind + 1 < argc)
goto err_usage;
/* custom template e.g. foo.XXXXXXXXXXXXXXXXXXXXX */
if (template != NULL) {
if (slen(template, maxlen, &tlen) < 0)
err_no_cleanup(stfu, EINVAL,
"invalid template");
for (p = template + tlen;
p > template && *--p == 'X'; xc++);
if (xc < 3) /* the gnu mktemp errs on less than 3 */
err_no_cleanup(stfu, EINVAL,
"template must have 3 X or more on end (12+ advised");
}
/* user supplied -p PATH - WARNING:
* this permits symlinks, but only here,
* not in the library, so they are resolved
* here first, and *only here*. the mkhtemp
* library blocks them. be careful
* when using -p
*/
if (tmpdir != NULL) {
rp = realpath(tmpdir, resolved);
if (rp == NULL)
err_no_cleanup(stfu, errno, "%s", tmpdir);
tmpdir = resolved;
}
if (new_tmp_common(&fd, &s, type,
tmpdir, template) < 0)
err_no_cleanup(stfu, errno, "%s", s);
#if defined(__OpenBSD__) && defined(OpenBSD)
#if (OpenBSD) >= 509
if (pledge("stdio", NULL) == -1)
err_no_cleanup(errno, "pledge, exit");
err_no_cleanup(stfu, errno, "pledge, exit");
#endif
#endif
if (s == NULL)
err_no_cleanup(EFAULT, "bad string initialisation");
err_no_cleanup(stfu, EFAULT, "bad string initialisation");
if (*s == '\0')
err_no_cleanup(EFAULT, "empty string initialisation");
err_no_cleanup(stfu, EFAULT, "empty string initialisation");
if (slen(s, maxlen, &len) < 0)
err_no_cleanup(EFAULT, "unterminated string initialisation");
err_no_cleanup(stfu, EFAULT, "unterminated string initialisiert");
printf("%s\n", s);
return EXIT_SUCCESS;
err_usage:
err_no_cleanup(stfu, EINVAL,
"usage: %s [-d] [-p dir] [template]\n", getnvmprogname());
}/*
@@ -140,3 +197,15 @@ main(int argc, char *argv[])
*/

View File

@@ -36,34 +36,34 @@ main(int argc, char *argv[])
size_t c;
if (lbgetprogname(argv[0]) == NULL)
err_no_cleanup(errno, "could not set progname");
err_no_cleanup(0, errno, "could not set progname");
/* https://man.openbsd.org/pledge.2
https://man.openbsd.org/unveil.2 */
#if defined(__OpenBSD__) && defined(OpenBSD)
#if (OpenBSD) >= 604
if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1)
err_no_cleanup(errno, "pledge plus unveil, main");
err_no_cleanup(0, errno, "pledge plus unveil, main");
if (unveil("/dev/null", "r") == -1)
err_no_cleanup(errno, "unveil r: /dev/null");
err_no_cleanup(0, errno, "unveil r: /dev/null");
#elif (OpenBSD) >= 509
if (pledge("stdio flock rpath wpath cpath", NULL) == -1)
err_no_cleanup(errno, "pledge, main");
err_no_cleanup(0, errno, "pledge, main");
#endif
#endif
#ifndef S_ISREG
err_no_cleanup(ECANCELED,
err_no_cleanup(0, ECANCELED,
"Can't determine file types (S_ISREG undefined)");
#endif
#if ((CHAR_BIT) != 8)
err_no_cleanup(ECANCELED, "Unsupported char size");
err_no_cleanup(0, ECANCELED, "Unsupported char size");
#endif
x = xstart(argc, argv);
if (x == NULL)
err_no_cleanup(ECANCELED, "NULL state on init");
err_no_cleanup(0, ECANCELED, "NULL state on init");
/* parse user command */
/* TODO: CHECK ACCESSES VIA xstatus() */
@@ -80,29 +80,29 @@ main(int argc, char *argv[])
if ((us.cmd[i].flags & O_ACCMODE) == O_RDONLY) {
if (unveil(us.f.fname, "r") == -1)
err(errno, "%s: unveil r", us.f.fname);
b0rk(errno, "%s: unveil r", us.f.fname);
} else {
if (unveil(us.f.fname, "rwc") == -1)
err(errno, "%s: unveil rw", us.f.fname);
b0rk(errno, "%s: unveil rw", us.f.fname);
}
if (unveil(us.f.tname, "rwc") == -1)
err(errno, "unveil rwc: %s", us.f.tname);
b0rk(errno, "unveil rwc: %s", us.f.tname);
if (unveil(NULL, NULL) == -1)
err(errno, "unveil block (rw)");
b0rk(errno, "unveil block (rw)");
if (pledge("stdio flock rpath wpath cpath", NULL) == -1)
err(errno, "pledge (kill unveil)");
b0rk(errno, "pledge (kill unveil)");
#elif (OpenBSD) >= 509
if (pledge("stdio flock rpath wpath cpath", NULL) == -1)
err(errno, "pledge");
b0rk(errno, "pledge");
#endif
#endif
if (cmd->run == NULL)
err(errno, "Command not set");
b0rk(errno, "Command not set");
sanitize_command_list();
@@ -121,10 +121,10 @@ main(int argc, char *argv[])
write_to_gbe_bin();
if (exit_cleanup() == -1)
err(EIO, "%s: close", f->fname);
b0rk(EIO, "%s: close", f->fname);
if (f->io_err_gbe_bin)
err(EIO, "%s: error writing final file");
b0rk(EIO, "%s: error writing final file");
free_if_null(&f->tname);