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>
This commit is contained in:
Leah Rowe
2026-03-24 19:44:22 +00:00
parent bf5a3df796
commit 88ff5f7380
2 changed files with 115 additions and 1 deletions

View File

@@ -513,6 +513,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

@@ -19,10 +19,16 @@
#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"
@@ -655,6 +661,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,6 +764,94 @@ 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)
{