summaryrefslogtreecommitdiff
path: root/util/nvmutil/lib
diff options
context:
space:
mode:
Diffstat (limited to 'util/nvmutil/lib')
-rw-r--r--util/nvmutil/lib/file.c1150
-rw-r--r--util/nvmutil/lib/mkhtemp.c1133
2 files changed, 1133 insertions, 1150 deletions
diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c
index 6546c252..ea2bcd0b 100644
--- a/util/nvmutil/lib/file.c
+++ b/util/nvmutil/lib/file.c
@@ -5,10 +5,6 @@
* Be nice to the demon.
*/
-#if defined(__linux__) && !defined(_GNU_SOURCE)
-/* for openat2 syscall on linux */
-#define _GNU_SOURCE 1
-#endif
#include <sys/types.h>
#include <sys/stat.h>
@@ -242,1152 +238,6 @@ err_fsync_dir:
return -1;
}
-/* hardened tmpfile and tmpdir creation.
- * obsessively hardened, to the point that
- * you could even say it is unhinged.
- *
- * much stricter than mkstemp.
- *
- * userspace sandboxing. TOCTOU-aware,
- * much stricter than typical libc/mkstemp.
- * TODO: write docs! right now the documentation
- * is both tthe code and the specification.
- * this code will likely be hardened more, over
- * time, until a matured stable release can be
- * made. i intend for this to go in linux distros
- * and BSD source trees eventually, as a standalone
- * utility.
- *
- * NOTE TO LINUX DISTROS / BSDs: more testing
- * and auditing needed before putting this
- * in your project. this comment will be removed
- * after the code has matured for a while. this
- * is a rough implementation, already carefully
- * audited by one person: me
- *
- * expect bugs. one day, when this is ready,
- * i will make a lot more of the options configurable
- * at runtime, and add a special compatibility mode
- * so that it can also run like regular mktemp,
- * for compatibility; running mkhtemp with all the
- * hardening means you get very non-standard behaviour,
- * e.g. it's much stricter about ownership/permissions,
- * sticky bits, and regards as fault conditions, what
- * would be considered normal in mkstemp/mktemp.
- *
- * a full manual and specification will be written when
- * this code is fully matured, and then i will probably
- * start spamming your mailing lists myself ;)
- */
-
-/* returns opened fd, sets fd and *path at pointers *fd and *path */
-/* also sets external stat */
-
-int
-new_tmpfile(int *fd, char **path)
-{
- return new_tmp_common(fd, path, MKHTEMP_FILE);
-}
-
-int
-new_tmpdir(int *fd, char **path)
-{
- return new_tmp_common(fd, path, MKHTEMP_DIR);
-}
-
-static int
-new_tmp_common(int *fd, char **path, int type)
-{
-#if defined(PATH_LEN) && \
- (PATH_LEN) >= 256
- size_t maxlen = PATH_LEN;
-#else
- size_t maxlen = 4096;
-#endif
- struct stat st;
-
- char suffix[] =
- "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
- char *tmpdir = NULL;
-
- int close_errno;
- size_t dirlen;
- size_t destlen;
- char *dest = NULL; /* final path (will be written into "path") */
- int saved_errno = errno;
- int dirfd = -1;
- const char *fname = NULL;
-
- struct stat st_dir_initial;
-
- if (path == NULL || fd == NULL) {
- errno = EFAULT;
- goto err;
- }
-
- /* don't mess with someone elses file */
- if (*fd >= 0) {
- errno = EEXIST;
- goto err;
- }
-
- /* regarding **path:
- * the pointer (to the pointer)
- * must nott be null, but we don't
- * care about the pointer it points
- * to. you should expect it to be
- * replaced upon successful return
- *
- * (on error, it will not be touched)
- */
-
-
- *fd = -1;
-
-#if defined(PERMIT_NON_STICKY_ALWAYS) && \
- ((PERMIT_NON_STICKY_ALWAYS) > 0)
- tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS);
-#else
- tmpdir = env_tmpdir(0);
-#endif
- if (tmpdir == NULL)
- goto err;
-
- if (slen(tmpdir, maxlen, &dirlen) < 0)
- goto err;
- if (*tmpdir == '\0')
- goto err;
- if (*tmpdir != '/')
- goto err;
-
- /* sizeof adds an extra byte, useful
- * because we also want '.' or '/'
- */
- destlen = dirlen + sizeof(suffix);
- if (destlen > maxlen - 1) {
- errno = EOVERFLOW;
- goto err;
- }
-
- dest = malloc(destlen + 1);
- if (dest == NULL) {
- errno = ENOMEM;
- goto err;
- }
-
- memcpy(dest, tmpdir, dirlen);
- *(dest + dirlen) = '/';
- memcpy(dest + dirlen + 1, suffix, sizeof(suffix) - 1);
- *(dest + destlen) = '\0';
-
- fname = dest + dirlen + 1;
-
- dirfd = fs_open(tmpdir,
- O_RDONLY | O_DIRECTORY);
- if (dirfd < 0)
- goto err;
-
- if (fstat(dirfd, &st_dir_initial) < 0)
- goto err;
-
- *fd = mkhtemp(fd, &st, dest, dirfd,
- fname, &st_dir_initial, type);
- if (*fd < 0)
- goto err;
-
- if (dirfd >= 0) {
- close_errno = errno;
- (void) close_on_eintr(dirfd);
- errno = close_errno;
- dirfd = -1;
- }
-
- errno = saved_errno;
- *path = dest;
-
- return 0;
-
-err:
-
- if (errno != saved_errno)
- saved_errno = errno;
- else
- saved_errno = errno = EIO;
-
- if (dest != NULL) {
- free(dest);
- dest = NULL;
- }
-
- if (dirfd >= 0) {
- close_errno = errno;
- (void) close_on_eintr(dirfd);
- errno = close_errno;
- dirfd = -1;
- }
-
- if (*fd >= 0) {
- close_errno = errno;
- (void) close_on_eintr(*fd);
- errno = close_errno;
- *fd = -1;
- }
-
- errno = saved_errno;
- return -1;
-}
-
-
-/* hardened TMPDIR parsing
- */
-
-char *
-env_tmpdir(int bypass_all_sticky_checks)
-{
- char *t;
- int allow_noworld_unsticky;
- int saved_errno = errno;
-
- t = getenv("TMPDIR");
-
- if (t != NULL && *t != '\0') {
-
- if (tmpdir_policy(t,
- &allow_noworld_unsticky) < 0) {
- errno = EPERM;
- return NULL; /* errno already set */
- }
-
- if (!world_writeable_and_sticky(t,
- allow_noworld_unsticky,
- bypass_all_sticky_checks)) {
- errno = EPERM;
- return NULL;
- }
-
- errno = saved_errno;
- return t;
- }
-
- allow_noworld_unsticky = 0;
-
- if (world_writeable_and_sticky("/tmp",
- allow_noworld_unsticky,
- bypass_all_sticky_checks)) {
-
- errno = saved_errno;
- return "/tmp";
- }
-
- if (world_writeable_and_sticky("/var/tmp",
- allow_noworld_unsticky,
- bypass_all_sticky_checks)) {
-
- errno = saved_errno;
- return "/var/tmp";
- }
-
- if (errno == saved_errno)
- errno = EPERM;
-
- return NULL;
-}
-
-int
-tmpdir_policy(const char *path,
- int *allow_noworld_unsticky)
-{
- int saved_errno = errno;
- int r;
-
- if (path == NULL ||
- allow_noworld_unsticky == NULL) {
-
- errno = EFAULT;
- return -1;
- }
-
- *allow_noworld_unsticky = 1;
-
- r = same_dir(path, "/tmp");
- if (r < 0)
- goto err_tmpdir_policy;
- if (r > 0)
- *allow_noworld_unsticky = 0;
-
- r = same_dir(path, "/var/tmp");
- if (r < 0)
- goto err_tmpdir_policy;
- if (r > 0)
- *allow_noworld_unsticky = 0;
-
- errno = saved_errno;
- return 0;
-
-err_tmpdir_policy:
-
- if (errno == saved_errno)
- errno = EIO;
-
- return -1;
-}
-
-int
-same_dir(const char *a, const char *b)
-{
- int fd_a = -1;
- int fd_b = -1;
-
- struct stat st_a;
- struct stat st_b;
-
- int saved_errno = errno;
- int rval_scmp;
-
-#if defined(PATH_LEN) && \
- (PATH_LEN) >= 256
- size_t maxlen = (PATH_LEN);
-#else
- size_t maxlen = 4096;
-#endif
-
- /* optimisation: if both dirs
- are the same, we don't need
- to check anything. sehr schnell:
- */
- if (scmp(a, b, maxlen, &rval_scmp) < 0)
- goto err_same_dir;
- /* bonus: scmp checks null for us */
- if (rval_scmp == 0)
- goto success_same_dir;
-
- fd_a = fs_open(a, O_RDONLY | O_DIRECTORY);
- if (fd_a < 0)
- goto err_same_dir;
-
- fd_b = fs_open(b, O_RDONLY | O_DIRECTORY);
- if (fd_b < 0)
- goto err_same_dir;
-
- if (fstat(fd_a, &st_a) < 0)
- goto err_same_dir;
-
- if (fstat(fd_b, &st_b) < 0)
- goto err_same_dir;
-
- if (st_a.st_dev == st_b.st_dev &&
- st_a.st_ino == st_b.st_ino) {
-
- (void) close_on_eintr(fd_a);
- (void) close_on_eintr(fd_b);
-
-success_same_dir:
-
- /* SUCCESS
- */
-
- errno = saved_errno;
- return 1;
- }
-
- (void) close_on_eintr(fd_a);
- (void) close_on_eintr(fd_b);
-
- /* FAILURE (logical)
- */
-
- errno = saved_errno;
- return 0;
-
-err_same_dir:
-
- /* FAILURE (probably syscall)
- */
-
- if (fd_a >= 0)
- (void) close_on_eintr(fd_a);
- if (fd_b >= 0)
- (void) close_on_eintr(fd_b);
-
- if (errno == saved_errno)
- errno = EIO;
-
- return -1;
-}
-
-/* bypass_all_sticky_checks: if set,
- disable stickiness checks (libc behaviour)
- (if not set: leah behaviour)
-
- allow_noworld_unsticky:
- allow non-sticky files if not world-writeable
- (still block non-sticky in standard TMPDIR)
-*/
-int
-world_writeable_and_sticky(
- const char *s,
- int allow_noworld_unsticky,
- int bypass_all_sticky_checks)
-{
- struct stat st;
- int dirfd = -1;
-
- int saved_errno = errno;
-
- if (s == NULL || *s == '\0') {
- errno = EINVAL;
- goto sticky_hell;
- }
-
- /* mitigate symlink attacks*
- */
- dirfd = fs_open(s, O_RDONLY | O_DIRECTORY);
- if (dirfd < 0)
- goto sticky_hell;
-
- if (fstat(dirfd, &st) < 0)
- goto sticky_hell;
-
- if (!S_ISDIR(st.st_mode)) {
- errno = ENOTDIR;
- goto sticky_hell;
- }
-
- /* must be fully executable
- * by everyone, or openat2
- * becomes unreliable**
- */
- if (!(st.st_mode & S_IXUSR) ||
- !(st.st_mode & S_IXGRP) ||
- !(st.st_mode & S_IXOTH)) {
-
- errno = EACCES;
- goto sticky_hell;
- }
-
- /* *normal-**ish mode (libc):
- */
-
- if (bypass_all_sticky_checks)
- goto sticky_heaven; /* normal == no security */
-
- /* unhinged leah mode:
- */
-
- if (st.st_mode & S_IWOTH) { /* world writeable */
-
- /* if world-writeable, only
- * allow sticky files
- */
- if (st.st_mode & S_ISVTX)
- goto sticky_heaven; /* sticky */
-
- errno = EPERM;
- goto sticky_hell; /* not sticky */
- }
-
- /* non-world-writeable, so
- * stickiness is do-not-care
- */
- if (allow_noworld_unsticky)
- goto sticky_heaven; /* sticky! */
-
- goto sticky_hell; /* heaven visa denied */
-
-sticky_heaven:
-/* i like the one in hamburg better */
-
- if (dirfd >= 0)
- (void) close_on_eintr(dirfd);
-
- errno = saved_errno;
-
- return 1;
-
-sticky_hell:
-
- if (errno == saved_errno)
- errno = EPERM;
-
- saved_errno = errno;
-
- if (dirfd >= 0)
- (void) close_on_eintr(dirfd);
-
- errno = saved_errno;
-
- return 0;
-}
-
-/* mk(h)temp - hardened mktemp.
- * like mkstemp, but (MUCH) harder.
- *
- * designed to resist TOCTOU attacsk
- * e.g. directory race / symlink attack
- *
- * extremely strict and even implements
- * some limited userspace-level sandboxing,
- * similar to openbsd unveil (which you
- * can also use with this in your program)
- *
- * supports both files and directories.
- * file: type = MKHTEMP_FILE (0)
- * dir: type = MKHTEMP_DIR (1)
- *
- * DESIGN NOTES:
- *
- * caller is expected to handle
- * cleanup e.g. free(), on *st,
- * *template, *fname (all of the
- * pointers). ditto fd cleanup.
- *
- * some limited cleanup is
- * performed here, e.g. directory/file
- * cleanup on error in mkhtemp_try_create
- *
- * we only check if these are not NULL,
- * and the caller is expected to take
- * care; without too many conditions,
- * these functions are more flexible,
- * but some precauttions are taken:
- *
- * when used via the function new_tmpfile
- * or new_tmpdir, thtis is extremely strict,
- * much stricter than previous mktemp
- * variants. for example, it is much
- * stricter about stickiness on world
- * writeable directories, and it enforces
- * file ownership under hardened mode
- * (only lets you touch your own files/dirs)
- */
-int
-mkhtemp(int *fd,
- struct stat *st,
- char *template,
- int dirfd,
- const char *fname,
- struct stat *st_dir_initial,
- int type)
-{
- size_t len = 0;
- size_t xc = 0;
- size_t fname_len = 0;
-
- char *fname_copy = NULL;
- char *p;
-
- size_t retries;
-
- int close_errno;
- int saved_errno = errno;
-
-#if defined(PATH_LEN) && \
- (PATH_LEN) >= 256
- size_t max_len = PATH_LEN;
-#else
- size_t max_len = 4096;
-#endif
- int r;
- char *end;
-
- if (fd == NULL ||
- template == NULL ||
- fname == NULL ||
- st_dir_initial == NULL) {
-
- errno = EFAULT;
- return -1;
- }
-
- /* we do not mess with an
- open descriptor.
- */
- if (*fd >= 0) {
- errno = EEXIST; /* leave their file alone */
- return -1;
- }
-
- if (dirfd < 0) {
- errno = EBADF;
- return -1;
- }
-
- if (slen(template, max_len, &len) < 0)
- return -1;
-
- if (len >= max_len) {
- errno = EMSGSIZE;
- return -1;
- }
-
- if (slen(fname, max_len, &fname_len) < 0)
- return -1;
-
- if (fname_len == 0) {
- errno = EINVAL;
- return -1;
- }
-
- if (strrchr(fname, '/') != NULL) {
- errno = EINVAL;
- return -1;
- }
-
- /* count trailing X */
- end = template + len;
- while (end > template && *--end == 'X')
- xc++;
-
- if (xc < 12 || xc > len) {
- errno = EINVAL;
- return -1;
- }
-
- if (fname_len > len) {
- errno = EOVERFLOW;
- return -1;
- }
-
- if (memcmp(fname,
- template + len - fname_len,
- fname_len) != 0) {
- errno = EINVAL;
- return -1;
- }
-
- fname_copy = malloc(fname_len + 1);
- if (fname_copy == NULL) {
- errno = ENOMEM;
- goto err;
- }
-
- /* fname_copy = suffix region only; p points to trailing XXXXXX */
- memcpy(fname_copy,
- template + len - fname_len,
- fname_len + 1);
- p = fname_copy + fname_len - xc;
-
- for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) {
-
- r = mkhtemp_try_create(
- dirfd,
- st_dir_initial,
- fname_copy,
- p,
- xc,
- fd,
- st,
- type);
-
- if (r == 0) {
- if (retries >= MKHTEMP_SPIN_THRESHOLD) {
- /* usleep can return EINTR */
- close_errno = errno;
- usleep((useconds_t)rlong() & 0x3FF);
- errno = close_errno;
- }
- continue;
- }
- if (r < 0)
- goto err;
-
- /* success: copy final name back */
- memcpy(template + len - fname_len,
- fname_copy, fname_len);
-
- errno = saved_errno;
- goto success;
- }
-
- errno = EEXIST;
-
-err:
- if (*fd >= 0) {
- close_errno = errno;
- (void)close_on_eintr(*fd);
- errno = close_errno;
- *fd = -1;
- }
-
-success:
-
- if (fname_copy != NULL)
- free(fname_copy);
-
- return (*fd >= 0) ? *fd : -1;
-}
-
-static int
-mkhtemp_try_create(int dirfd,
- struct stat *st_dir_initial,
- char *fname_copy,
- char *p,
- size_t xc,
- int *fd,
- struct stat *st,
- int type)
-{
- struct stat st_open;
- int saved_errno = errno;
- int close_errno;
- int rval = -1;
-
- int file_created = 0;
- int dir_created = 0;
-
- if (fd == NULL || st == NULL || p == NULL || fname_copy == NULL ||
- st_dir_initial == NULL) {
- errno = EFAULT;
- goto err;
- } else if (*fd >= 0) { /* do not mess with someone else's file */
- errno = EEXIST;
- goto err;
- }
-
- if (mkhtemp_fill_random(p, xc) < 0)
- goto err;
-
- if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
- goto err;
-
- if (type == MKHTEMP_FILE) {
- *fd = openat2p(dirfd, fname_copy,
- O_RDWR | O_CREAT | O_EXCL |
- O_NOFOLLOW | O_CLOEXEC | O_NOCTTY,
- 0600);
-
- /* O_CREAT and O_EXCL guarantees
- * creation upon success
- */
- if (*fd >= 0)
- file_created = 1;
-
- } else { /* dir: MKHTEMP_DIR */
-
- if (mkdirat_on_eintr(dirfd, fname_copy, 0700) < 0)
- goto err;
-
- /* ^ NOTE: opening the directory here
- will never set errno=EEXIST,
- since we're not creating it */
-
- dir_created = 1;
-
- /* do it again (mitigate directory race) */
- if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
- goto err;
-
- *fd = openat2p(dirfd, fname_copy,
- O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0);
- if (*fd < 0)
- goto err;
-
- if (fstat(*fd, &st_open) < 0)
- goto err;
-
- if (!S_ISDIR(st_open.st_mode)) {
- errno = ENOTDIR;
- goto err;
- }
-
- /* NOTE: could check nlink count here,
- * but it's not very reliable here. skipped.
- */
-
- if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
- goto err;
-
- }
-
- /* NOTE: openat2p and mkdirat_on_eintr
- * already handled EINTR/EAGAIN looping
- */
-
- if (*fd < 0) {
- if (errno == EEXIST) {
-
- rval = 0;
- goto out;
- }
- goto err;
- }
-
- if (fstat(*fd, &st_open) < 0)
- goto err;
-
- if (type == MKHTEMP_FILE) {
-
- if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
- goto err;
-
- if (secure_file(fd, st, &st_open,
- O_APPEND, 1, 1, 0600) < 0) /* WARNING: only once */
- goto err;
-
- } else { /* dir: MKHTEMP_DIR */
-
- if (fd_verify_identity(*fd, &st_open, st_dir_initial) < 0)
- goto err;
-
- if (!S_ISDIR(st_open.st_mode)) {
- errno = ENOTDIR;
- goto err;
- }
-
- if (is_owner(&st_open) < 0)
- goto err;
-
- /* group/world writeable */
- if (st_open.st_mode & (S_IWGRP | S_IWOTH)) {
- errno = EPERM;
- goto err;
- }
- }
-
- errno = saved_errno;
- rval = 1;
- goto out;
-
-err:
- close_errno = errno;
-
- if (fd != NULL && *fd >= 0) {
- (void) close_on_eintr(*fd);
- *fd = -1;
- }
-
- if (file_created)
- (void) unlinkat(dirfd, fname_copy, 0);
-
- if (dir_created)
- (void) unlinkat(dirfd, fname_copy, AT_REMOVEDIR);
-
- errno = close_errno;
- rval = -1;
-out:
- return rval;
-}
-
-int
-mkhtemp_fill_random(char *p, size_t xc)
-{
- size_t chx = 0;
- int rand_failures = 0;
-
- size_t r;
-
- int saved_rand_error = 0;
- static char ch[] =
- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-
- /* clamp rand to prevent modulo bias
- * (reduced risk of entropy leak)
- */
- size_t limit = ((size_t)-1) - (((size_t)-1) % (sizeof(ch) - 1));
-
- int saved_errno = errno;
-
- if (p == NULL) {
- errno = EFAULT;
- goto err_mkhtemp_fill_random;
- }
-
- for (chx = 0; chx < xc; chx++) {
-
- do {
- saved_rand_error = errno;
- rand_failures = 0;
-retry_rand:
- errno = 0;
-
- /* on bsd: uses arc4random
- on linux: uses getrandom
- on OLD linux: /dev/urandom
- on old/other unix: /dev/urandom
- */
- r = rlong();
-
- if (errno > 0) {
- if (++rand_failures <= 8)
- goto retry_rand;
-
- goto err_mkhtemp_fill_random;
- }
-
- rand_failures = 0;
- errno = saved_rand_error;
-
- } while (r >= limit);
-
- p[chx] = ch[r % (sizeof(ch) - 1)];
- }
-
- errno = saved_errno;
- return 0;
-
-err_mkhtemp_fill_random:
-
- if (errno == saved_errno)
- errno = ECANCELED;
-
- return -1;
-}
-
-/* WARNING: **ONCE** per file.
- *
- * !!! DO NOT RUN TWICE PER FILE. BEWARE OF THE DEMON !!!
- * watch out for spikes!
- */
-int secure_file(int *fd,
- struct stat *st,
- struct stat *expected,
- int bad_flags,
- int check_seek,
- int do_lock,
- mode_t mode)
-{
- int flags;
- struct stat st_now;
- int saved_errno = errno;
- /* you have been warned */
- if (fd == NULL) {
- errno = EFAULT;
- goto err_demons;
- }
- if (*fd < 0) {
- errno = EBADF;
- goto err_demons;
- }
-
- if (st == NULL) {
- errno = EFAULT;
- goto err_demons;
- }
-
- flags = fcntl(*fd, F_GETFL);
-
- if (flags == -1)
- goto err_demons;
-
- if (bad_flags > 0) {
-
- /* e.g. O_APPEND breaks pwrite/pread
- * by allowing offsets to be ignored */
- if (flags & bad_flags) {
- errno = EPERM;
- goto err_demons;
- }
- }
-
- if (expected != NULL) {
- if (fd_verify_regular(*fd, expected, st) < 0)
- goto err_demons;
- } else {
- if (fstat(*fd, &st_now) == -1)
- goto err_demons;
-
- if (!S_ISREG(st_now.st_mode)) {
- errno = EBADF;
- goto err_demons;
- }
-
- *st = st_now;
- }
-
- if (check_seek) {
-
- /* check if it's seekable */
- if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1)
- goto err_demons;
- }
-
- /* don't release the demon
- */
- if (st->st_nlink != 1) { /***********/
- /* ( >:3 ) */
- errno = ELOOP; /* /| |\ */ /* don't let him out */
- goto err_demons; /* / \ */
- /***********/
- }
-
- if (st->st_uid != geteuid() && /* someone else's file */
- geteuid() != 0) { /* override for root */
-
- errno = EPERM;
- goto err_demons;
- }
- if (is_owner(st) < 0)
- goto err_demons;
-
- /* world-writeable or group-ownership.
- * if these are set, then others could
- * modify the file (not secure)
- */
- if (st->st_mode & (S_IWGRP | S_IWOTH)) {
- errno = EPERM;
- goto err_demons;
- }
-
- if (do_lock) {
- if (lock_file(*fd, flags) == -1)
- goto err_demons;
-
- if (expected != NULL) {
- if (fd_verify_identity(*fd, expected, &st_now) < 0)
- goto err_demons;
- }
- }
-
- if (fchmod(*fd, mode) == -1)
- goto err_demons;
-
- errno = saved_errno;
- return 0;
-
-err_demons:
-
- if (errno == saved_errno)
- errno = EIO;
-
- return -1;
-}
-
-int
-fd_verify_regular(int fd,
- const struct stat *expected,
- struct stat *out)
-{
- if (fd_verify_identity(fd, expected, out) < 0)
- return -1;
-
- if (!S_ISREG(out->st_mode)) {
- errno = EBADF;
- return -1;
- }
-
- return 0; /* regular file */
-}
-
-int
-fd_verify_identity(int fd,
- const struct stat *expected,
- struct stat *out)
-{
- struct stat st_now;
- int saved_errno = errno;
-
- if (fd < 0 || expected == NULL) {
- errno = EFAULT;
- return -1;
- }
-
- if (fstat(fd, &st_now) < 0)
- return -1;
-
- if (st_now.st_dev != expected->st_dev ||
- st_now.st_ino != expected->st_ino) {
- errno = ESTALE;
- return -1;
- }
-
- if (out != NULL)
- *out = st_now;
-
- errno = saved_errno;
- return 0;
-}
-
-int
-fd_verify_dir_identity(int fd,
- const struct stat *expected)
-{
- struct stat st_now;
- int saved_errno = errno;
-
- if (fd < 0 || expected == NULL) {
- errno = EFAULT;
- return -1;
- }
-
- if (fstat(fd, &st_now) < 0)
- return -1;
-
- if (st_now.st_dev != expected->st_dev ||
- st_now.st_ino != expected->st_ino) {
- errno = ESTALE;
- return -1;
- }
-
- if (!S_ISDIR(st_now.st_mode)) {
- errno = ENOTDIR;
- return -1;
- }
-
- errno = saved_errno;
- return 0;
-}
-
-int
-is_owner(struct stat *st)
-{
- if (st == NULL) {
-
- errno = EFAULT;
- return -1;
- }
-
-#if ALLOW_ROOT_OVERRIDE
- if (st->st_uid != geteuid() && /* someone else's file */
- geteuid() != 0) { /* override for root */
-#else
- if (st->st_uid != geteuid()) { /* someone else's file */
-#endif /* and no root override */
- errno = EPERM;
- return -1;
- }
-
- return 0;
-}
-
-int
-lock_file(int fd, int flags)
-{
- struct flock fl;
- int saved_errno = errno;
-
- if (fd < 0) {
- errno = EBADF;
- goto err_lock_file;
- }
-
- if (flags < 0) {
- errno = EINVAL;
- goto err_lock_file;
- }
-
- memset(&fl, 0, sizeof(fl));
-
- if ((flags & O_ACCMODE) == O_RDONLY)
- fl.l_type = F_RDLCK;
- else
- fl.l_type = F_WRLCK;
-
- fl.l_whence = SEEK_SET;
-
- if (fcntl(fd, F_SETLK, &fl) == -1)
- goto err_lock_file;
-
- saved_errno = errno;
- return 0;
-
-err_lock_file:
-
- if (errno == saved_errno)
- errno = EIO;
-
- return -1;
-}
-
/*
* Safe I/O functions wrapping around
* read(), write() and providing a portable
diff --git a/util/nvmutil/lib/mkhtemp.c b/util/nvmutil/lib/mkhtemp.c
new file mode 100644
index 00000000..2fcb894e
--- /dev/null
+++ b/util/nvmutil/lib/mkhtemp.c
@@ -0,0 +1,1133 @@
+/* SPDX-License-Identifier: MIT
+ * Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
+ *
+ * Hardened mktemp (be nice to the demon).
+ */
+
+#if defined(__linux__) && !defined(_GNU_SOURCE)
+/* for openat2 syscall on linux */
+#define _GNU_SOURCE 1
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* for openat2: */
+#ifdef __linux__
+#include <linux/openat2.h>
+#include <sys/syscall.h>
+#endif
+
+#include "../include/common.h"
+
+int
+new_tmpfile(int *fd, char **path)
+{
+ return new_tmp_common(fd, path, MKHTEMP_FILE);
+}
+
+int
+new_tmpdir(int *fd, char **path)
+{
+ return new_tmp_common(fd, path, MKHTEMP_DIR);
+}
+
+static int
+new_tmp_common(int *fd, char **path, int type)
+{
+#if defined(PATH_LEN) && \
+ (PATH_LEN) >= 256
+ size_t maxlen = PATH_LEN;
+#else
+ size_t maxlen = 4096;
+#endif
+ struct stat st;
+
+ char suffix[] =
+ "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
+ char *tmpdir = NULL;
+
+ int close_errno;
+ size_t dirlen;
+ size_t destlen;
+ char *dest = NULL; /* final path (will be written into "path") */
+ int saved_errno = errno;
+ int dirfd = -1;
+ const char *fname = NULL;
+
+ struct stat st_dir_initial;
+
+ if (path == NULL || fd == NULL) {
+ errno = EFAULT;
+ goto err;
+ }
+
+ /* don't mess with someone elses file */
+ if (*fd >= 0) {
+ errno = EEXIST;
+ goto err;
+ }
+
+ /* regarding **path:
+ * the pointer (to the pointer)
+ * must nott be null, but we don't
+ * care about the pointer it points
+ * to. you should expect it to be
+ * replaced upon successful return
+ *
+ * (on error, it will not be touched)
+ */
+
+
+ *fd = -1;
+
+#if defined(PERMIT_NON_STICKY_ALWAYS) && \
+ ((PERMIT_NON_STICKY_ALWAYS) > 0)
+ tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS);
+#else
+ tmpdir = env_tmpdir(0);
+#endif
+ if (tmpdir == NULL)
+ goto err;
+
+ if (slen(tmpdir, maxlen, &dirlen) < 0)
+ goto err;
+ if (*tmpdir == '\0')
+ goto err;
+ if (*tmpdir != '/')
+ goto err;
+
+ /* sizeof adds an extra byte, useful
+ * because we also want '.' or '/'
+ */
+ destlen = dirlen + sizeof(suffix);
+ if (destlen > maxlen - 1) {
+ errno = EOVERFLOW;
+ goto err;
+ }
+
+ dest = malloc(destlen + 1);
+ if (dest == NULL) {
+ errno = ENOMEM;
+ goto err;
+ }
+
+ memcpy(dest, tmpdir, dirlen);
+ *(dest + dirlen) = '/';
+ memcpy(dest + dirlen + 1, suffix, sizeof(suffix) - 1);
+ *(dest + destlen) = '\0';
+
+ fname = dest + dirlen + 1;
+
+ dirfd = fs_open(tmpdir,
+ O_RDONLY | O_DIRECTORY);
+ if (dirfd < 0)
+ goto err;
+
+ if (fstat(dirfd, &st_dir_initial) < 0)
+ goto err;
+
+ *fd = mkhtemp(fd, &st, dest, dirfd,
+ fname, &st_dir_initial, type);
+ if (*fd < 0)
+ goto err;
+
+ if (dirfd >= 0) {
+ close_errno = errno;
+ (void) close_on_eintr(dirfd);
+ errno = close_errno;
+ dirfd = -1;
+ }
+
+ errno = saved_errno;
+ *path = dest;
+
+ return 0;
+
+err:
+
+ if (errno != saved_errno)
+ saved_errno = errno;
+ else
+ saved_errno = errno = EIO;
+
+ if (dest != NULL) {
+ free(dest);
+ dest = NULL;
+ }
+
+ if (dirfd >= 0) {
+ close_errno = errno;
+ (void) close_on_eintr(dirfd);
+ errno = close_errno;
+ dirfd = -1;
+ }
+
+ if (*fd >= 0) {
+ close_errno = errno;
+ (void) close_on_eintr(*fd);
+ errno = close_errno;
+ *fd = -1;
+ }
+
+ errno = saved_errno;
+ return -1;
+}
+
+
+/* hardened TMPDIR parsing
+ */
+
+char *
+env_tmpdir(int bypass_all_sticky_checks)
+{
+ char *t;
+ int allow_noworld_unsticky;
+ int saved_errno = errno;
+
+ t = getenv("TMPDIR");
+
+ if (t != NULL && *t != '\0') {
+
+ if (tmpdir_policy(t,
+ &allow_noworld_unsticky) < 0) {
+ errno = EPERM;
+ return NULL; /* errno already set */
+ }
+
+ if (!world_writeable_and_sticky(t,
+ allow_noworld_unsticky,
+ bypass_all_sticky_checks)) {
+ errno = EPERM;
+ return NULL;
+ }
+
+ errno = saved_errno;
+ return t;
+ }
+
+ allow_noworld_unsticky = 0;
+
+ if (world_writeable_and_sticky("/tmp",
+ allow_noworld_unsticky,
+ bypass_all_sticky_checks)) {
+
+ errno = saved_errno;
+ return "/tmp";
+ }
+
+ if (world_writeable_and_sticky("/var/tmp",
+ allow_noworld_unsticky,
+ bypass_all_sticky_checks)) {
+
+ errno = saved_errno;
+ return "/var/tmp";
+ }
+
+ if (errno == saved_errno)
+ errno = EPERM;
+
+ return NULL;
+}
+
+int
+tmpdir_policy(const char *path,
+ int *allow_noworld_unsticky)
+{
+ int saved_errno = errno;
+ int r;
+
+ if (path == NULL ||
+ allow_noworld_unsticky == NULL) {
+
+ errno = EFAULT;
+ return -1;
+ }
+
+ *allow_noworld_unsticky = 1;
+
+ r = same_dir(path, "/tmp");
+ if (r < 0)
+ goto err_tmpdir_policy;
+ if (r > 0)
+ *allow_noworld_unsticky = 0;
+
+ r = same_dir(path, "/var/tmp");
+ if (r < 0)
+ goto err_tmpdir_policy;
+ if (r > 0)
+ *allow_noworld_unsticky = 0;
+
+ errno = saved_errno;
+ return 0;
+
+err_tmpdir_policy:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}
+
+int
+same_dir(const char *a, const char *b)
+{
+ int fd_a = -1;
+ int fd_b = -1;
+
+ struct stat st_a;
+ struct stat st_b;
+
+ int saved_errno = errno;
+ int rval_scmp;
+
+#if defined(PATH_LEN) && \
+ (PATH_LEN) >= 256
+ size_t maxlen = (PATH_LEN);
+#else
+ size_t maxlen = 4096;
+#endif
+
+ /* optimisation: if both dirs
+ are the same, we don't need
+ to check anything. sehr schnell:
+ */
+ if (scmp(a, b, maxlen, &rval_scmp) < 0)
+ goto err_same_dir;
+ /* bonus: scmp checks null for us */
+ if (rval_scmp == 0)
+ goto success_same_dir;
+
+ fd_a = fs_open(a, O_RDONLY | O_DIRECTORY);
+ if (fd_a < 0)
+ goto err_same_dir;
+
+ fd_b = fs_open(b, O_RDONLY | O_DIRECTORY);
+ if (fd_b < 0)
+ goto err_same_dir;
+
+ if (fstat(fd_a, &st_a) < 0)
+ goto err_same_dir;
+
+ if (fstat(fd_b, &st_b) < 0)
+ goto err_same_dir;
+
+ if (st_a.st_dev == st_b.st_dev &&
+ st_a.st_ino == st_b.st_ino) {
+
+ (void) close_on_eintr(fd_a);
+ (void) close_on_eintr(fd_b);
+
+success_same_dir:
+
+ /* SUCCESS
+ */
+
+ errno = saved_errno;
+ return 1;
+ }
+
+ (void) close_on_eintr(fd_a);
+ (void) close_on_eintr(fd_b);
+
+ /* FAILURE (logical)
+ */
+
+ errno = saved_errno;
+ return 0;
+
+err_same_dir:
+
+ /* FAILURE (probably syscall)
+ */
+
+ if (fd_a >= 0)
+ (void) close_on_eintr(fd_a);
+ if (fd_b >= 0)
+ (void) close_on_eintr(fd_b);
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}
+
+/* bypass_all_sticky_checks: if set,
+ disable stickiness checks (libc behaviour)
+ (if not set: leah behaviour)
+
+ allow_noworld_unsticky:
+ allow non-sticky files if not world-writeable
+ (still block non-sticky in standard TMPDIR)
+*/
+int
+world_writeable_and_sticky(
+ const char *s,
+ int allow_noworld_unsticky,
+ int bypass_all_sticky_checks)
+{
+ struct stat st;
+ int dirfd = -1;
+
+ int saved_errno = errno;
+
+ if (s == NULL || *s == '\0') {
+ errno = EINVAL;
+ goto sticky_hell;
+ }
+
+ /* mitigate symlink attacks*
+ */
+ dirfd = fs_open(s, O_RDONLY | O_DIRECTORY);
+ if (dirfd < 0)
+ goto sticky_hell;
+
+ if (fstat(dirfd, &st) < 0)
+ goto sticky_hell;
+
+ if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ goto sticky_hell;
+ }
+
+ /* must be fully executable
+ * by everyone, or openat2
+ * becomes unreliable**
+ */
+ if (!(st.st_mode & S_IXUSR) ||
+ !(st.st_mode & S_IXGRP) ||
+ !(st.st_mode & S_IXOTH)) {
+
+ errno = EACCES;
+ goto sticky_hell;
+ }
+
+ /* *normal-**ish mode (libc):
+ */
+
+ if (bypass_all_sticky_checks)
+ goto sticky_heaven; /* normal == no security */
+
+ /* unhinged leah mode:
+ */
+
+ if (st.st_mode & S_IWOTH) { /* world writeable */
+
+ /* if world-writeable, only
+ * allow sticky files
+ */
+ if (st.st_mode & S_ISVTX)
+ goto sticky_heaven; /* sticky */
+
+ errno = EPERM;
+ goto sticky_hell; /* not sticky */
+ }
+
+ /* non-world-writeable, so
+ * stickiness is do-not-care
+ */
+ if (allow_noworld_unsticky)
+ goto sticky_heaven; /* sticky! */
+
+ goto sticky_hell; /* heaven visa denied */
+
+sticky_heaven:
+/* i like the one in hamburg better */
+
+ if (dirfd >= 0)
+ (void) close_on_eintr(dirfd);
+
+ errno = saved_errno;
+
+ return 1;
+
+sticky_hell:
+
+ if (errno == saved_errno)
+ errno = EPERM;
+
+ saved_errno = errno;
+
+ if (dirfd >= 0)
+ (void) close_on_eintr(dirfd);
+
+ errno = saved_errno;
+
+ return 0;
+}
+
+/* mk(h)temp - hardened mktemp.
+ * like mkstemp, but (MUCH) harder.
+ *
+ * designed to resist TOCTOU attacsk
+ * e.g. directory race / symlink attack
+ *
+ * extremely strict and even implements
+ * some limited userspace-level sandboxing,
+ * similar to openbsd unveil (which you
+ * can also use with this in your program)
+ *
+ * supports both files and directories.
+ * file: type = MKHTEMP_FILE (0)
+ * dir: type = MKHTEMP_DIR (1)
+ *
+ * DESIGN NOTES:
+ *
+ * caller is expected to handle
+ * cleanup e.g. free(), on *st,
+ * *template, *fname (all of the
+ * pointers). ditto fd cleanup.
+ *
+ * some limited cleanup is
+ * performed here, e.g. directory/file
+ * cleanup on error in mkhtemp_try_create
+ *
+ * we only check if these are not NULL,
+ * and the caller is expected to take
+ * care; without too many conditions,
+ * these functions are more flexible,
+ * but some precauttions are taken:
+ *
+ * when used via the function new_tmpfile
+ * or new_tmpdir, thtis is extremely strict,
+ * much stricter than previous mktemp
+ * variants. for example, it is much
+ * stricter about stickiness on world
+ * writeable directories, and it enforces
+ * file ownership under hardened mode
+ * (only lets you touch your own files/dirs)
+ */
+int
+mkhtemp(int *fd,
+ struct stat *st,
+ char *template,
+ int dirfd,
+ const char *fname,
+ struct stat *st_dir_initial,
+ int type)
+{
+ size_t len = 0;
+ size_t xc = 0;
+ size_t fname_len = 0;
+
+ char *fname_copy = NULL;
+ char *p;
+
+ size_t retries;
+
+ int close_errno;
+ int saved_errno = errno;
+
+#if defined(PATH_LEN) && \
+ (PATH_LEN) >= 256
+ size_t max_len = PATH_LEN;
+#else
+ size_t max_len = 4096;
+#endif
+ int r;
+ char *end;
+
+ if (fd == NULL ||
+ template == NULL ||
+ fname == NULL ||
+ st_dir_initial == NULL) {
+
+ errno = EFAULT;
+ return -1;
+ }
+
+ /* we do not mess with an
+ open descriptor.
+ */
+ if (*fd >= 0) {
+ errno = EEXIST; /* leave their file alone */
+ return -1;
+ }
+
+ if (dirfd < 0) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (slen(template, max_len, &len) < 0)
+ return -1;
+
+ if (len >= max_len) {
+ errno = EMSGSIZE;
+ return -1;
+ }
+
+ if (slen(fname, max_len, &fname_len) < 0)
+ return -1;
+
+ if (fname_len == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (strrchr(fname, '/') != NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* count trailing X */
+ end = template + len;
+ while (end > template && *--end == 'X')
+ xc++;
+
+ if (xc < 12 || xc > len) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (fname_len > len) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ if (memcmp(fname,
+ template + len - fname_len,
+ fname_len) != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fname_copy = malloc(fname_len + 1);
+ if (fname_copy == NULL) {
+ errno = ENOMEM;
+ goto err;
+ }
+
+ /* fname_copy = suffix region only; p points to trailing XXXXXX */
+ memcpy(fname_copy,
+ template + len - fname_len,
+ fname_len + 1);
+ p = fname_copy + fname_len - xc;
+
+ for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) {
+
+ r = mkhtemp_try_create(
+ dirfd,
+ st_dir_initial,
+ fname_copy,
+ p,
+ xc,
+ fd,
+ st,
+ type);
+
+ if (r == 0) {
+ if (retries >= MKHTEMP_SPIN_THRESHOLD) {
+ /* usleep can return EINTR */
+ close_errno = errno;
+ usleep((useconds_t)rlong() & 0x3FF);
+ errno = close_errno;
+ }
+ continue;
+ }
+ if (r < 0)
+ goto err;
+
+ /* success: copy final name back */
+ memcpy(template + len - fname_len,
+ fname_copy, fname_len);
+
+ errno = saved_errno;
+ goto success;
+ }
+
+ errno = EEXIST;
+
+err:
+ if (*fd >= 0) {
+ close_errno = errno;
+ (void)close_on_eintr(*fd);
+ errno = close_errno;
+ *fd = -1;
+ }
+
+success:
+
+ if (fname_copy != NULL)
+ free(fname_copy);
+
+ return (*fd >= 0) ? *fd : -1;
+}
+
+static int
+mkhtemp_try_create(int dirfd,
+ struct stat *st_dir_initial,
+ char *fname_copy,
+ char *p,
+ size_t xc,
+ int *fd,
+ struct stat *st,
+ int type)
+{
+ struct stat st_open;
+ int saved_errno = errno;
+ int close_errno;
+ int rval = -1;
+
+ int file_created = 0;
+ int dir_created = 0;
+
+ if (fd == NULL || st == NULL || p == NULL || fname_copy == NULL ||
+ st_dir_initial == NULL) {
+ errno = EFAULT;
+ goto err;
+ } else if (*fd >= 0) { /* do not mess with someone else's file */
+ errno = EEXIST;
+ goto err;
+ }
+
+ if (mkhtemp_fill_random(p, xc) < 0)
+ goto err;
+
+ if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
+ goto err;
+
+ if (type == MKHTEMP_FILE) {
+ *fd = openat2p(dirfd, fname_copy,
+ O_RDWR | O_CREAT | O_EXCL |
+ O_NOFOLLOW | O_CLOEXEC | O_NOCTTY,
+ 0600);
+
+ /* O_CREAT and O_EXCL guarantees
+ * creation upon success
+ */
+ if (*fd >= 0)
+ file_created = 1;
+
+ } else { /* dir: MKHTEMP_DIR */
+
+ if (mkdirat_on_eintr(dirfd, fname_copy, 0700) < 0)
+ goto err;
+
+ /* ^ NOTE: opening the directory here
+ will never set errno=EEXIST,
+ since we're not creating it */
+
+ dir_created = 1;
+
+ /* do it again (mitigate directory race) */
+ if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
+ goto err;
+
+ *fd = openat2p(dirfd, fname_copy,
+ O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0);
+ if (*fd < 0)
+ goto err;
+
+ if (fstat(*fd, &st_open) < 0)
+ goto err;
+
+ if (!S_ISDIR(st_open.st_mode)) {
+ errno = ENOTDIR;
+ goto err;
+ }
+
+ /* NOTE: could check nlink count here,
+ * but it's not very reliable here. skipped.
+ */
+
+ if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
+ goto err;
+
+ }
+
+ /* NOTE: openat2p and mkdirat_on_eintr
+ * already handled EINTR/EAGAIN looping
+ */
+
+ if (*fd < 0) {
+ if (errno == EEXIST) {
+
+ rval = 0;
+ goto out;
+ }
+ goto err;
+ }
+
+ if (fstat(*fd, &st_open) < 0)
+ goto err;
+
+ if (type == MKHTEMP_FILE) {
+
+ if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0)
+ goto err;
+
+ if (secure_file(fd, st, &st_open,
+ O_APPEND, 1, 1, 0600) < 0) /* WARNING: only once */
+ goto err;
+
+ } else { /* dir: MKHTEMP_DIR */
+
+ if (fd_verify_identity(*fd, &st_open, st_dir_initial) < 0)
+ goto err;
+
+ if (!S_ISDIR(st_open.st_mode)) {
+ errno = ENOTDIR;
+ goto err;
+ }
+
+ if (is_owner(&st_open) < 0)
+ goto err;
+
+ /* group/world writeable */
+ if (st_open.st_mode & (S_IWGRP | S_IWOTH)) {
+ errno = EPERM;
+ goto err;
+ }
+ }
+
+ errno = saved_errno;
+ rval = 1;
+ goto out;
+
+err:
+ close_errno = errno;
+
+ if (fd != NULL && *fd >= 0) {
+ (void) close_on_eintr(*fd);
+ *fd = -1;
+ }
+
+ if (file_created)
+ (void) unlinkat(dirfd, fname_copy, 0);
+
+ if (dir_created)
+ (void) unlinkat(dirfd, fname_copy, AT_REMOVEDIR);
+
+ errno = close_errno;
+ rval = -1;
+out:
+ return rval;
+}
+
+int
+mkhtemp_fill_random(char *p, size_t xc)
+{
+ size_t chx = 0;
+ int rand_failures = 0;
+
+ size_t r;
+
+ int saved_rand_error = 0;
+ static char ch[] =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+ /* clamp rand to prevent modulo bias
+ * (reduced risk of entropy leak)
+ */
+ size_t limit = ((size_t)-1) - (((size_t)-1) % (sizeof(ch) - 1));
+
+ int saved_errno = errno;
+
+ if (p == NULL) {
+ errno = EFAULT;
+ goto err_mkhtemp_fill_random;
+ }
+
+ for (chx = 0; chx < xc; chx++) {
+
+ do {
+ saved_rand_error = errno;
+ rand_failures = 0;
+retry_rand:
+ errno = 0;
+
+ /* on bsd: uses arc4random
+ on linux: uses getrandom
+ on OLD linux: /dev/urandom
+ on old/other unix: /dev/urandom
+ */
+ r = rlong();
+
+ if (errno > 0) {
+ if (++rand_failures <= 8)
+ goto retry_rand;
+
+ goto err_mkhtemp_fill_random;
+ }
+
+ rand_failures = 0;
+ errno = saved_rand_error;
+
+ } while (r >= limit);
+
+ p[chx] = ch[r % (sizeof(ch) - 1)];
+ }
+
+ errno = saved_errno;
+ return 0;
+
+err_mkhtemp_fill_random:
+
+ if (errno == saved_errno)
+ errno = ECANCELED;
+
+ return -1;
+}
+
+/* WARNING: **ONCE** per file.
+ *
+ * !!! DO NOT RUN TWICE PER FILE. BEWARE OF THE DEMON !!!
+ * watch out for spikes!
+ */
+int secure_file(int *fd,
+ struct stat *st,
+ struct stat *expected,
+ int bad_flags,
+ int check_seek,
+ int do_lock,
+ mode_t mode)
+{
+ int flags;
+ struct stat st_now;
+ int saved_errno = errno;
+ /* you have been warned */
+ if (fd == NULL) {
+ errno = EFAULT;
+ goto err_demons;
+ }
+ if (*fd < 0) {
+ errno = EBADF;
+ goto err_demons;
+ }
+
+ if (st == NULL) {
+ errno = EFAULT;
+ goto err_demons;
+ }
+
+ flags = fcntl(*fd, F_GETFL);
+
+ if (flags == -1)
+ goto err_demons;
+
+ if (bad_flags > 0) {
+
+ /* e.g. O_APPEND breaks pwrite/pread
+ * by allowing offsets to be ignored */
+ if (flags & bad_flags) {
+ errno = EPERM;
+ goto err_demons;
+ }
+ }
+
+ if (expected != NULL) {
+ if (fd_verify_regular(*fd, expected, st) < 0)
+ goto err_demons;
+ } else {
+ if (fstat(*fd, &st_now) == -1)
+ goto err_demons;
+
+ if (!S_ISREG(st_now.st_mode)) {
+ errno = EBADF;
+ goto err_demons;
+ }
+
+ *st = st_now;
+ }
+
+ if (check_seek) {
+
+ /* check if it's seekable */
+ if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1)
+ goto err_demons;
+ }
+
+ /* don't release the demon
+ */
+ if (st->st_nlink != 1) { /***********/
+ /* ( >:3 ) */
+ errno = ELOOP; /* /| |\ */ /* don't let him out */
+ goto err_demons; /* / \ */
+ /***********/
+ }
+
+ if (st->st_uid != geteuid() && /* someone else's file */
+ geteuid() != 0) { /* override for root */
+
+ errno = EPERM;
+ goto err_demons;
+ }
+ if (is_owner(st) < 0)
+ goto err_demons;
+
+ /* world-writeable or group-ownership.
+ * if these are set, then others could
+ * modify the file (not secure)
+ */
+ if (st->st_mode & (S_IWGRP | S_IWOTH)) {
+ errno = EPERM;
+ goto err_demons;
+ }
+
+ if (do_lock) {
+ if (lock_file(*fd, flags) == -1)
+ goto err_demons;
+
+ if (expected != NULL) {
+ if (fd_verify_identity(*fd, expected, &st_now) < 0)
+ goto err_demons;
+ }
+ }
+
+ if (fchmod(*fd, mode) == -1)
+ goto err_demons;
+
+ errno = saved_errno;
+ return 0;
+
+err_demons:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}
+
+int
+fd_verify_regular(int fd,
+ const struct stat *expected,
+ struct stat *out)
+{
+ if (fd_verify_identity(fd, expected, out) < 0)
+ return -1;
+
+ if (!S_ISREG(out->st_mode)) {
+ errno = EBADF;
+ return -1;
+ }
+
+ return 0; /* regular file */
+}
+
+int
+fd_verify_identity(int fd,
+ const struct stat *expected,
+ struct stat *out)
+{
+ struct stat st_now;
+ int saved_errno = errno;
+
+ if (fd < 0 || expected == NULL) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (fstat(fd, &st_now) < 0)
+ return -1;
+
+ if (st_now.st_dev != expected->st_dev ||
+ st_now.st_ino != expected->st_ino) {
+ errno = ESTALE;
+ return -1;
+ }
+
+ if (out != NULL)
+ *out = st_now;
+
+ errno = saved_errno;
+ return 0;
+}
+
+int
+fd_verify_dir_identity(int fd,
+ const struct stat *expected)
+{
+ struct stat st_now;
+ int saved_errno = errno;
+
+ if (fd < 0 || expected == NULL) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ if (fstat(fd, &st_now) < 0)
+ return -1;
+
+ if (st_now.st_dev != expected->st_dev ||
+ st_now.st_ino != expected->st_ino) {
+ errno = ESTALE;
+ return -1;
+ }
+
+ if (!S_ISDIR(st_now.st_mode)) {
+ errno = ENOTDIR;
+ return -1;
+ }
+
+ errno = saved_errno;
+ return 0;
+}
+
+int
+is_owner(struct stat *st)
+{
+ if (st == NULL) {
+
+ errno = EFAULT;
+ return -1;
+ }
+
+#if ALLOW_ROOT_OVERRIDE
+ if (st->st_uid != geteuid() && /* someone else's file */
+ geteuid() != 0) { /* override for root */
+#else
+ if (st->st_uid != geteuid()) { /* someone else's file */
+#endif /* and no root override */
+ errno = EPERM;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+lock_file(int fd, int flags)
+{
+ struct flock fl;
+ int saved_errno = errno;
+
+ if (fd < 0) {
+ errno = EBADF;
+ goto err_lock_file;
+ }
+
+ if (flags < 0) {
+ errno = EINVAL;
+ goto err_lock_file;
+ }
+
+ memset(&fl, 0, sizeof(fl));
+
+ if ((flags & O_ACCMODE) == O_RDONLY)
+ fl.l_type = F_RDLCK;
+ else
+ fl.l_type = F_WRLCK;
+
+ fl.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLK, &fl) == -1)
+ goto err_lock_file;
+
+ saved_errno = errno;
+ return 0;
+
+err_lock_file:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}