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>
This commit is contained in:
Leah Rowe
2026-03-24 00:28:15 +00:00
parent afcd535816
commit f2544d094b
18 changed files with 211 additions and 54 deletions

View File

@@ -6,7 +6,7 @@
cbcfgsdir="config/coreboot" cbcfgsdir="config/coreboot"
tmpromdel="$XBMK_CACHE/DO_NOT_FLASH" tmpromdel="$XBMK_CACHE/DO_NOT_FLASH"
nvmutil="util/nvmutil/nvmutil" nvmutil="util/libreboot-utils/nvmutil"
ifdtool="elf/coreboot/default/ifdtool" ifdtool="elf/coreboot/default/ifdtool"
checkvars="CONFIG_GBE_BIN_PATH" checkvars="CONFIG_GBE_BIN_PATH"
@@ -197,8 +197,8 @@ modify_mac()
x_ cp "${CONFIG_GBE_BIN_PATH##*../}" "$xbtmp/gbe" x_ cp "${CONFIG_GBE_BIN_PATH##*../}" "$xbtmp/gbe"
if [ -n "$new_mac" ] && [ "$new_mac" != "restore" ]; then if [ -n "$new_mac" ] && [ "$new_mac" != "restore" ]; then
x_ make -C util/nvmutil clean x_ make -C util/libreboot-utils clean
x_ make -C util/nvmutil x_ make -C util/libreboot-utils
x_ "$nvmutil" "$xbtmp/gbe" setmac "$new_mac" x_ "$nvmutil" "$xbtmp/gbe" setmac "$new_mac"
fi fi

View File

@@ -1,5 +1,6 @@
/nvm /nvm
/nvmutil /nvmutil
/mkhtemp
*.bin *.bin
*.o *.o
*.d *.d

View File

@@ -24,8 +24,9 @@ STRICT = $(WARN) -std=c90 -pedantic -Werror
HELLFLAGS = $(STRICT) -Weverything HELLFLAGS = $(STRICT) -Weverything
PROG = nvmutil PROG = nvmutil
PROGMKH = mkhtemp
OBJS = \ OBJS_NVMUTIL = \
obj/nvmutil.o \ obj/nvmutil.o \
obj/lib/state.o \ obj/lib/state.o \
obj/lib/file.o \ obj/lib/file.o \
@@ -38,17 +39,28 @@ OBJS = \
obj/lib/word.o \ obj/lib/word.o \
obj/lib/mkhtemp.o obj/lib/mkhtemp.o
OBJS_MKHTEMP = \
obj/mkhtemp.o \
obj/lib/file.o \
obj/lib/string.o \
obj/lib/num.o \
obj/lib/mkhtemp.o
# default mode # default mode
CFLAGS_MODE = $(PORTABLE) CFLAGS_MODE = $(PORTABLE)
CC_MODE = $(CC) CC_MODE = $(CC)
all: $(PROG) all: $(PROG) $(PROGMKH)
$(PROG): $(OBJS) $(PROG): $(OBJS_NVMUTIL)
$(CC_MODE) $(OBJS) -o $(PROG) $(LDFLAGS) $(CC_MODE) $(OBJS_NVMUTIL) -o $(PROG) $(LDFLAGS)
$(PROGMKH): $(OBJS_MKHTEMP)
$(CC_MODE) $(OBJS_MKHTEMP) -o $(PROGMKH) $(LDFLAGS)
# ensure obj directory exists # ensure obj directory exists
$(OBJS): obj $(OBJS_NVMUTIL): obj
$(OBJS_MKHTEMP): obj
obj: obj:
mkdir obj || true mkdir obj || true
@@ -59,6 +71,9 @@ obj:
obj/nvmutil.o: nvmutil.c obj/nvmutil.o: nvmutil.c
$(CC_MODE) $(CFLAGS_MODE) -c nvmutil.c -o obj/nvmutil.o $(CC_MODE) $(CFLAGS_MODE) -c nvmutil.c -o obj/nvmutil.o
obj/mkhtemp.o: mkhtemp.c
$(CC_MODE) $(CFLAGS_MODE) -c mkhtemp.c -o obj/mkhtemp.o
# library/helper objects # library/helper objects
obj/lib/state.o: lib/state.c obj/lib/state.o: lib/state.c
@@ -93,16 +108,19 @@ obj/lib/mkhtemp.o: lib/mkhtemp.c
# install # install
install: $(PROG) install: $(PROG) $(PROGMKH)
$(INSTALL) -d $(DESTDIR)$(PREFIX)/bin $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin
$(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG) $(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG) chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG)
$(INSTALL) $(PROGMKH) $(DESTDIR)$(PREFIX)/bin/$(PROGMKH)
chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROGMKH)
uninstall: uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG)
rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGMKH)
clean: clean:
rm -f $(PROG) $(OBJS) rm -f $(PROG) $(PROGMKH) $(OBJS_NVMUTIL) $(OBJS_MKHTEMP)
distclean: clean distclean: clean

View File

@@ -1,5 +1,9 @@
/* SPDX-License-Identifier: MIT /* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
TODO: this file should be split, into headers for each
C source file specifically. it was originally just
for nvmutil, until i added mkhtemp to the mix
*/ */
@@ -495,8 +499,8 @@ const char *getnvmprogname(void);
int new_tmpfile(int *fd, char **path); int new_tmpfile(int *fd, char **path);
int new_tmpdir(int *fd, char **path); int new_tmpdir(int *fd, char **path);
static int new_tmp_common(int *fd, char **path, int type); int new_tmp_common(int *fd, char **path, int type);
static int mkhtemp_try_create(int dirfd, int mkhtemp_try_create(int dirfd,
struct stat *st_dir_initial, struct stat *st_dir_initial,
char *fname_copy, char *fname_copy,
char *p, char *p,

View File

@@ -39,7 +39,7 @@ new_tmpdir(int *fd, char **path)
return new_tmp_common(fd, path, MKHTEMP_DIR); return new_tmp_common(fd, path, MKHTEMP_DIR);
} }
static int int
new_tmp_common(int *fd, char **path, int type) new_tmp_common(int *fd, char **path, int type)
{ {
#if defined(PATH_LEN) && \ #if defined(PATH_LEN) && \
@@ -466,13 +466,13 @@ sticky_hell:
/* mk(h)temp - hardened mktemp. /* mk(h)temp - hardened mktemp.
* like mkstemp, but (MUCH) harder. * like mkstemp, but (MUCH) harder.
* *
* designed to resist TOCTOU attacsk * designed to resist TOCTOU attacks
* e.g. directory race / symlink attack * e.g. directory race / symlink attack
* *
* extremely strict and even implements * extremely strict and even implements
* some limited userspace-level sandboxing, * some limited userspace-level sandboxing,
* similar to openbsd unveil (which you * similar in spirit to openbsd unveil,
* can also use with this in your program) * though unveil is from kernel space.
* *
* supports both files and directories. * supports both files and directories.
* file: type = MKHTEMP_FILE (0) * file: type = MKHTEMP_FILE (0)
@@ -661,7 +661,7 @@ success:
return (*fd >= 0) ? *fd : -1; return (*fd >= 0) ? *fd : -1;
} }
static int int
mkhtemp_try_create(int dirfd, mkhtemp_try_create(int dirfd,
struct stat *st_dir_initial, struct stat *st_dir_initial,
char *fname_copy, char *fname_copy,

View File

@@ -152,33 +152,6 @@ xstatus(void)
return x; return x;
} }
/* early init functions that
should not access state
WARNING:
does not do cleanup. only
call this during pre-init
*/
void
err_no_cleanup(int nvm_errval, const char *msg, ...)
{
va_list args;
if (errno == 0)
errno = nvm_errval;
if (!errno)
errno = ECANCELED;
fprintf(stderr, "nvmutil: ");
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, ": %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
void void
err(int nvm_errval, const char *msg, ...) err(int nvm_errval, const char *msg, ...)
{ {

View File

@@ -8,8 +8,11 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <errno.h> #include <errno.h>
#include <stdarg.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include "../include/common.h" #include "../include/common.h"
@@ -112,3 +115,32 @@ slen(const char *s,
*rval = ch; *rval = ch;
return 0; return 0;
} }
/* the one for nvmutil state is in state.c */
/* this one just exits */
void
err_no_cleanup(int nvm_errval, const char *msg, ...)
{
va_list args;
#if defined(__OpenBSD__) && defined(OpenBSD)
#if (OpenBSD) >= 509
if (pledge("stdio", NULL) == -1)
fprintf(stderr, "pledge failure during exit");
#endif
#endif
if (!errno)
errno = ECANCELED;
fprintf(stderr, "nvmutil: ");
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, ": %s\n", strerror(errno));
exit(EXIT_FAILURE);
}

View File

@@ -0,0 +1,139 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* WORK IN PROGRESS (proof of concept), or, v0.0000001
*
* Mkhtemp - Hardened mktemp. Create files and directories
* randomly as determined by user's TMPDIR, or fallback. It
* attemps to provide mitigation against several TOCTOU-based
* attacks e.g. directory rename / symlink attacks, and it
* generally provides much higher strictness than previous
* implementations such as mktemp, mkstemp or even mkdtemp.
*
* 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
* level options e.g. OBSD pledge where available), constant
* identity/ownership checks on files, MUCH stricter ownership
* restrictions (e.g. enforce sticky bit policy on world-
* writeable tmpdirs), preventing operation on other people's
* files (only your own files) - even root is restricted,
* depending on how the code is compiled. Please read the code.
*
* This is the utility version, which makes use of the also-
* included library. No docs yet - source code are the docs,
* and the (ever evolving, and hardening) specification.
*
* This was written from scratch, for use in nvmutil, and
* it is designed to be portable (BSD, Linux). Patches
* very much welcome.
*
* WARNING: This is MUCH stricter than every other mktemp
* implementation, even more so than mkdtemp or
* the OpenBSD version of mkstemp. It *will* break,
* or more specifically, reveal the flaws in, almost
* every major critical infrastructure, because most
* people already use mktemp extremely insecurely.
*
* This tool is written by me, for me, and also Libreboot, but
* it will be summitted for review to various Linux distros
* and BSD projects once it has reached maturity.
*/
#if defined(__linux__) && !defined(_GNU_SOURCE)
/* for openat2 on linux */
#define _GNU_SOURCE 1
#endif
#ifdef __OpenBSD__
#include <sys/param.h> /* pledge(2) */
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "include/common.h"
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
/* 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");
#endif
#endif
while ((c =
getopt(argc, argv, "d")) != -1) {
switch(c) {
case 'd':
type = MKHTEMP_DIR;
break;
default:
err_no_cleanup(EINVAL,
"usage: mkhtemp [-d]\n");
}
}
if (new_tmp_common(&fd, &s, type) < 0)
err_no_cleanup(errno, NULL);
#if defined(__OpenBSD__) && defined(OpenBSD)
#if (OpenBSD) >= 509
if (pledge("stdio", NULL) == -1)
err_no_cleanup(errno, "pledge, exit");
#endif
#endif
if (s == NULL)
err_no_cleanup(EFAULT, "bad string initialisation");
if (*s == '\0')
err_no_cleanup(EFAULT, "empty string initialisation");
if (slen(s, maxlen, &len) < 0)
err_no_cleanup(EFAULT, "unterminated string initialisation");
printf("%s\n", s);
return EXIT_SUCCESS;
}/*
( >:3 )
/| |\
/ \
*/

View File

@@ -35,16 +35,6 @@ main(int argc, char *argv[])
size_t c; size_t c;
int rval;
char *test = NULL;
int fd = -1;
rval = new_tmpdir(&fd, &test);
if (rval < 0)
err_no_cleanup(errno, "TESTERR: ");
printf("TEST: %s\n", test);
exit(1);
/* https://man.openbsd.org/pledge.2 /* https://man.openbsd.org/pledge.2
https://man.openbsd.org/unveil.2 */ https://man.openbsd.org/unveil.2 */
#if defined(__OpenBSD__) && defined(OpenBSD) #if defined(__OpenBSD__) && defined(OpenBSD)