Compare commits

...

13 Commits

Author SHA1 Message Date
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
13 changed files with 390 additions and 154 deletions

View File

@@ -287,6 +287,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
@@ -489,17 +494,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 +518,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 *suffix;
size_t suffix_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)
suffix = template;
else
suffix = "tmp.XXXXXXXXXX";
if (slen(suffix, maxlen, &suffix_len) < 0)
goto err;
/* sizeof adds an extra byte, useful
* because we also want '.' or '/'
*/
destlen = dirlen + sizeof(suffix);
destlen = dirlen + 1 + suffix_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, suffix, suffix_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
*/
@@ -567,7 +604,7 @@ mkhtemp(int *fd,
if_err(len >= max_len, EMSGSIZE)
||
if_err_sys(slen(fname, max_len, &fname_len)) ||
if_err(fname == 0, EINVAL) ||
if_err(fname == NULL, EINVAL) ||
if_err(strrchr(fname, '/') != NULL, EINVAL))
return -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,6 +795,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)
{

View File

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

@@ -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

@@ -119,7 +119,7 @@ slen(const char *s,
/* 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 +141,10 @@ err_no_cleanup(int nvm_errval, const char *msg, ...)
vfprintf(stderr, msg, args);
va_end(args);
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) {
getopt(argc, argv, "qdp:")) != -1) {
switch(c) {
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:
err_no_cleanup(EINVAL,
"usage: mkhtemp [-d]\n");
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 < 6)
err_no_cleanup(stfu, EINVAL,
"template must end in at least 6 X");
}
/* 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);