diff options
| author | Leah Rowe <leah@libreboot.org> | 2026-03-20 04:02:51 +0000 |
|---|---|---|
| committer | Leah Rowe <leah@libreboot.org> | 2026-03-25 12:32:57 +0000 |
| commit | 210922bc9174bcce3444f9bc2782b033622b4c70 (patch) | |
| tree | 7153f7b848bf1ebc60baae09f4384c7a43d83d0a /util/libreboot-utils/mkhtemp.c | |
| parent | f50ffd6bb13c04cb185fb6311f8875582bf18388 (diff) | |
util/mkhtemp: extremely hardened mkhtemp
This will also be used in lbmk itself at some point,
which currently just uses regular mktemp, for tmpdir
handling during the build process.
Renamed util/nvmutil to util/libreboot-utils, which
now contains two tools. The new tool, mkhtemp, is a
hardened implementation of mktemp, which nvmutil
also uses now. Still experimental, but good enough
for nvmutil.
Mkhtemp attempts to provide TOCTOU resistance on
Linux, by using modern features in Linux such as
Openat2 (syscall) with O_EXCL and O_TMPFILE,
and many various security checks e.g.
inode/dev during creation. Checks are done constantly,
to try to detect race conditions. The code is very
strict about things like sticky bits in world writeable
directories, also ownership (it can be made to bar even
root access on files and directories it doesn't own).
It's a security-first implementation of mktemp, likely
even more secure than the OpenBSD mkstemp, but more
auditing and testing is needed - more features are
also planned, including a compatibility mode to make
it also work like traditional mktemp/mkstemp. The
intention, once this becomes stable, is that it will
become a modern drop-in replacement for mkstemp on
Linux and BSD systems.
Some legacy code has been removed, and in general
cleaned up. I wrote mkhtemp for nvmutil, as part of
its atomic write behaviour, but mktemp was the last
remaining liability, so I rewrote that too!
Docs/manpage/website will be made for mkhtemp once
the code is mature.
Other changes have also been made. This is from another
experimental branch of Libreboot, that I'm pushing
early. For example, nvmutil's state machine has been
tidied up, moving more logic back into main.
Mktemp is historically prone to race conditions,
e.g. symlink attacks, directory replacement, remounting
during operation, all sorts of things. Mkhtemp has
been written to solve, or otherwise mitigate, that
problem. Mkhtemp is currently experimental and will
require a major cleanup at some point, but it
already works well enough, and you can in fact use
it; at this time, the -d, -p and -q flags are
supported, and you can add a custom template at
the end, e.g.
mkhtemp -p test -d
Eventually, I will make this have complete parity
with the GNU and BSD implementations, so that it is
fully useable on existing setups, while optionally
providing the hardening as well.
A lot of code has also been tidied up. I didn't
track the changes I made with this one, because
it was a major re-write of nvmutil; it is now
libreboot-utils, and I will continue to write
more programs in here over time. It's basically
now a bunch of hardened wrappers around various
libc functions, e.g. there is also a secure I/O
wrapper for read/write.
There is a custom randomisation function, rlong,
which simply uses arc4random or getrandom, on
BSD and Linux respectively. Efforts are made to
make it as reliable as possible, to the extent
that it never returns with failure; in the unlikely
event that it fails, it aborts. It also sleeps
between failure, to mitigate certain DoS attacks.
You can just go in util/libreboot-utils and
type make, then you will have the nvmutil and
mkhtemp binaries, which you can just use. It
all works. Everything was massively rewritten.
Signed-off-by: Leah Rowe <leah@libreboot.org>
Diffstat (limited to 'util/libreboot-utils/mkhtemp.c')
| -rw-r--r-- | util/libreboot-utils/mkhtemp.c | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/util/libreboot-utils/mkhtemp.c b/util/libreboot-utils/mkhtemp.c new file mode 100644 index 00000000..261227cb --- /dev/null +++ b/util/libreboot-utils/mkhtemp.c @@ -0,0 +1,211 @@ +/* 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. + * + * 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 + * 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[]) +{ +#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(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) + goto err_usage; +#endif +#endif + + while ((c = + getopt(argc, argv, "qdp:")) != -1) { + + switch (c) { + case 'd': + type = MKHTEMP_DIR; + break; + + case 'p': + tmpdir = optarg; + break; + + case 'q': /* don't print errors */ + /* (exit status unchanged) */ + stfu = 1; + break; + + default: + goto err_usage; + } + } + + 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(stfu, errno, "pledge, exit"); +#endif +#endif + + if (s == NULL) + err_no_cleanup(stfu, EFAULT, "bad string initialisation"); + if (*s == '\0') + err_no_cleanup(stfu, EFAULT, "empty string initialisation"); + if (slen(s, maxlen, &len) < 0) + 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()); +}/* + + + ( >:3 ) + /| |\ + / \ + + + + + + */ + + + + + + + + + + + + |
