summaryrefslogtreecommitdiff
path: root/util/libreboot-utils/mkhtemp.c
diff options
context:
space:
mode:
authorLeah Rowe <leah@libreboot.org>2026-03-20 04:02:51 +0000
committerLeah Rowe <leah@libreboot.org>2026-03-25 12:32:57 +0000
commit210922bc9174bcce3444f9bc2782b033622b4c70 (patch)
tree7153f7b848bf1ebc60baae09f4384c7a43d83d0a /util/libreboot-utils/mkhtemp.c
parentf50ffd6bb13c04cb185fb6311f8875582bf18388 (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.c211
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 )
+ /| |\
+ / \
+
+
+
+
+
+ */
+
+
+
+
+
+
+
+
+
+
+
+