diff options
| -rw-r--r-- | util/nvmutil/include/common.h | 2 | ||||
| -rw-r--r-- | util/nvmutil/lib/file.c | 175 | ||||
| -rw-r--r-- | util/nvmutil/nvmutil.c | 10 |
3 files changed, 117 insertions, 70 deletions
diff --git a/util/nvmutil/include/common.h b/util/nvmutil/include/common.h index a546e464..4aca1772 100644 --- a/util/nvmutil/include/common.h +++ b/util/nvmutil/include/common.h @@ -494,6 +494,8 @@ const char *getnvmprogname(void); */ int new_tmpfile(int *fd, char **path); +int new_tmpdir(int *fd, char **path); +static int new_tmp_common(int *fd, char **path, int type); static int mkhtemp_try_create(int dirfd, struct stat *st_dir_initial, char *fname_copy, diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c index b605d488..786c51fb 100644 --- a/util/nvmutil/lib/file.c +++ b/util/nvmutil/lib/file.c @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: MIT * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> * - * Pathless i/o + * Pathless i/o, and some stuff you probably never saw. + * Be nice to the demon. */ #if defined(__linux__) && !defined(_GNU_SOURCE) @@ -241,17 +242,62 @@ err_fsync_dir: return -1; } -/* hardened tmpfile creation +/* 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) { -/* TODO: - * directory support (currently only files) - */ + 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; @@ -267,7 +313,7 @@ new_tmpfile(int *fd, char **path) int close_errno; size_t dirlen; size_t destlen; - char *dest = NULL; /* final path */ + char *dest = NULL; /* final path (will be written into "path") */ int saved_errno = errno; int dirfd = -1; const char *fname = NULL; @@ -276,26 +322,25 @@ new_tmpfile(int *fd, char **path) if (path == NULL || fd == NULL) { errno = EFAULT; - goto err_new_tmpfile; + goto err; } - /* don't mess with someone's file - * if already opened - */ + /* don't mess with someone elses file */ if (*fd >= 0) { errno = EEXIST; - goto err_new_tmpfile; + 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) - */ + * 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; @@ -306,30 +351,28 @@ new_tmpfile(int *fd, char **path) tmpdir = env_tmpdir(0); #endif if (tmpdir == NULL) - goto err_new_tmpfile; + goto err; if (slen(tmpdir, maxlen, &dirlen) < 0) - goto err_new_tmpfile; + goto err; if (*tmpdir == '\0') - goto err_new_tmpfile; + goto err; if (*tmpdir != '/') - goto err_new_tmpfile; + goto err; - /* using sizeof (not slen) adds an extra byte, - * useful because we either want '.' or '/' + /* sizeof adds an extra byte, useful + * because we also want '.' or '/' */ destlen = dirlen + sizeof(suffix); - if (destlen > maxlen - 1) { /* -1 for NULL */ - + if (destlen > maxlen - 1) { errno = EOVERFLOW; - goto err_new_tmpfile; + goto err; } - dest = malloc(destlen + 1); /* +1 for NULL */ + dest = malloc(destlen + 1); if (dest == NULL) { - errno = ENOMEM; - goto err_new_tmpfile; + goto err; } memcpy(dest, tmpdir, dirlen); @@ -337,21 +380,20 @@ new_tmpfile(int *fd, char **path) memcpy(dest + dirlen + 1, suffix, sizeof(suffix) - 1); *(dest + destlen) = '\0'; - /* new tmpfile name */ fname = dest + dirlen + 1; dirfd = fs_open(tmpdir, O_RDONLY | O_DIRECTORY); if (dirfd < 0) - goto err_new_tmpfile; + goto err; if (fstat(dirfd, &st_dir_initial) < 0) - goto err_new_tmpfile; + goto err; *fd = mkhtemp(fd, &st, dest, dirfd, - fname, &st_dir_initial, MKHTEMP_FILE); + fname, &st_dir_initial, type); if (*fd < 0) - goto err_new_tmpfile; + goto err; if (dirfd >= 0) { close_errno = errno; @@ -361,12 +403,11 @@ new_tmpfile(int *fd, char **path) } errno = saved_errno; - *path = dest; return 0; - -err_new_tmpfile: + +err: if (errno != saved_errno) saved_errno = errno; @@ -393,10 +434,10 @@ err_new_tmpfile: } errno = saved_errno; - return -1; } + /* hardened TMPDIR parsing */ @@ -629,9 +670,6 @@ world_writeable_and_sticky( /* unhinged leah mode: */ - if (is_owner(&st) < 0) - goto sticky_hell; - if (st.st_mode & S_IWOTH) { /* world writeable */ /* if world-writeable, only @@ -801,8 +839,7 @@ mkhtemp(int *fd, return -1; } - if (fname_len > len || - fname_len > (len - xc)) { + if (fname_len > len) { errno = EOVERFLOW; return -1; } @@ -927,8 +964,8 @@ mkhtemp_try_create(int dirfd, goto err; /* ^ NOTE: opening the directory here - will never set errno=EEXIST, - since we're not creating it */ + will never set errno=EEXIST, + since we're not creating it */ dir_created = 1; @@ -936,6 +973,11 @@ mkhtemp_try_create(int dirfd, 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; @@ -944,11 +986,6 @@ mkhtemp_try_create(int dirfd, goto err; } - *fd = openat2p(dirfd, fname_copy, - O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0); - if (*fd < 0) - goto err; - /* NOTE: could check nlink count here, * but it's not very reliable here. skipped. */ @@ -1960,25 +1997,22 @@ int fs_resolve_at(int dirfd, const char *path, int flags) { int nextfd = -1; + int curfd; const char *p; - char name[256]; /* TODO: make configurable */ + char name[256]; int saved_errno = errno; - int saved_close_errno; int r; int is_last; - if (dirfd < 0 || - path == NULL || - *path == '\0') { - + if (dirfd < 0 || path == NULL || *path == '\0') { errno = EINVAL; return -1; } p = path; + curfd = dirfd; /* start here */ for (;;) { - r = fs_next_component(&p, name, sizeof(name)); if (r < 0) goto err; @@ -1987,30 +2021,32 @@ fs_resolve_at(int dirfd, const char *path, int flags) is_last = (*p == '\0'); - nextfd = fs_open_component(dirfd, - name, flags, is_last); + nextfd = fs_open_component(curfd, name, flags, is_last); if (nextfd < 0) goto err; - saved_close_errno = errno; - (void) close_on_eintr(dirfd); - errno = saved_close_errno; + /* close previous fd IF it is not the original input */ + if (curfd != dirfd) { + (void) close_on_eintr(curfd); + } - dirfd = nextfd; + curfd = nextfd; nextfd = -1; } errno = saved_errno; - return dirfd; + return curfd; err: saved_errno = errno; - if (dirfd >= 0) - (void) close_on_eintr(dirfd); if (nextfd >= 0) (void) close_on_eintr(nextfd); + /* close curfd only if it's not the original */ + if (curfd != dirfd && curfd >= 0) + (void) close_on_eintr(curfd); + errno = saved_errno; return -1; } @@ -2167,8 +2203,7 @@ openat2p(int dirfd, const char *path, .resolve = RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS | - RESOLVE_NO_MAGICLINKS | - RESOLVE_NO_XDEV + RESOLVE_NO_MAGICLINKS }; int saved_errno = errno; int rval; diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index cb08ec43..266654e8 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -35,6 +35,16 @@ main(int argc, char *argv[]) size_t c; + int rval; + char *test = NULL; + int fd = -1; + rval = new_tmpdir(&fd, &test); + if (rval < 0) + err_no_cleanup(errno, "TESTERR: "); + + printf("TEST: %s\n", test); + exit(1); + /* https://man.openbsd.org/pledge.2 https://man.openbsd.org/unveil.2 */ #if defined(__OpenBSD__) && defined(OpenBSD) |
