diff options
Diffstat (limited to 'util/libreboot-utils')
| -rw-r--r-- | util/libreboot-utils/include/common.h | 23 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/io.c | 82 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/mkhtemp.c | 170 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/state.c | 63 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/string.c | 66 | ||||
| -rw-r--r-- | util/libreboot-utils/mkhtemp.c | 52 | ||||
| -rw-r--r-- | util/libreboot-utils/nvmutil.c | 3 |
7 files changed, 332 insertions, 127 deletions
diff --git a/util/libreboot-utils/include/common.h b/util/libreboot-utils/include/common.h index b26448db..620e95b9 100644 --- a/util/libreboot-utils/include/common.h +++ b/util/libreboot-utils/include/common.h @@ -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 @@ -497,9 +502,9 @@ const char *getnvmprogname(void); /* libc hardening */ -int new_tmpfile(int *fd, char **path); -int new_tmpdir(int *fd, char **path); -int new_tmp_common(int *fd, char **path, int type); +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 mkhtemp_try_create(int dirfd, struct stat *st_dir_initial, char *fname_copy, @@ -508,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); @@ -517,7 +530,8 @@ int world_writeable_and_sticky(const char *s, int same_dir(const char *a, const char *b); int tmpdir_policy(const char *path, int *allow_noworld_unsticky); -char *env_tmpdir(int always_sticky, char **tmpdir); +char *env_tmpdir(int always_sticky, char **tmpdir, + char *override_tmpdir); int secure_file(int *fd, struct stat *st, struct stat *expected, @@ -547,6 +561,7 @@ int mkdirat_on_eintr(int dirfd, const char *pathname, mode_t mode); int if_err(int condition, int errval); int if_err_sys(int condition); +char *lbgetprogname(char *argv0); /* asserts */ diff --git a/util/libreboot-utils/lib/io.c b/util/libreboot-utils/lib/io.c index cc38e5c7..295e15c0 100644 --- a/util/libreboot-utils/lib/io.c +++ b/util/libreboot-utils/lib/io.c @@ -413,7 +413,15 @@ gbe_mv(void) int tmp_gbe_bin_exists; char *dest_tmp; - int dest_fd; + int dest_fd = -1; + + char *dir = NULL; + char *base = NULL; + char *dest_name = NULL; + + int dirfd = -1; + + struct stat st_dir; /* will be set 0 if it doesn't */ @@ -424,78 +432,12 @@ gbe_mv(void) saved_errno = errno; - 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_dir(f->fname) < 0) { - f->io_err_gbe_bin = 1; - rval = -1; - } - - goto ret_gbe_mv; - } - - if (errno != EXDEV) - goto ret_gbe_mv; - - /* - * OR, cross-filesystem rename: - */ - - if ((rval = f->tmp_fd = open(f->tname, - O_RDONLY | O_BINARY)) == -1) - goto ret_gbe_mv; - - /* create replacement temp in target directory - */ - if (new_tmpfile(&dest_fd, &f->fname) < 1) - goto ret_gbe_mv; - if (dest_tmp == NULL) - goto ret_gbe_mv; - - /* copy data - */ - rval = 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 (rval < 0) - goto ret_gbe_mv; - - rval = rw_file_exact(dest_fd, f->bufcmp, - f->gbe_file_size, 0, IO_PWRITE, - NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (rval < 0) - goto ret_gbe_mv; - - if (fsync_on_eintr(dest_fd) == -1) - goto ret_gbe_mv; - - if (close_on_eintr(dest_fd) == -1) { - dest_fd = -1; - goto ret_gbe_mv; - } - dest_fd = -1; - - if (rename(dest_tmp, f->fname) == -1) - goto ret_gbe_mv; - - if (fsync_dir(f->fname) < 0) { - f->io_err_gbe_bin = 1; - goto ret_gbe_mv; - } - - free_if_null(&dest_tmp); - ret_gbe_mv: /* TODO: this whole section is bloat. diff --git a/util/libreboot-utils/lib/mkhtemp.c b/util/libreboot-utils/lib/mkhtemp.c index b30f6587..cd4a9cde 100644 --- a/util/libreboot-utils/lib/mkhtemp.c +++ b/util/libreboot-utils/lib/mkhtemp.c @@ -19,26 +19,35 @@ #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) +new_tmpfile(int *fd, char **path, char *tmpdir) { - return new_tmp_common(fd, path, MKHTEMP_FILE); + return new_tmp_common(fd, path, MKHTEMP_FILE, tmpdir); } +/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */ int -new_tmpdir(int *fd, char **path) +new_tmpdir(int *fd, char **path, char *tmpdir) { - return new_tmp_common(fd, path, MKHTEMP_DIR); + return new_tmp_common(fd, path, MKHTEMP_DIR, tmpdir); } +/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */ /* WARNING: * on error, *path (at **path) may be NULL, or if the error pertains to @@ -55,7 +64,8 @@ new_tmpdir(int *fd, char **path) * default to /tmp or /var/tmp */ int -new_tmp_common(int *fd, char **path, int type) +new_tmp_common(int *fd, char **path, int type, + char *tmpdir) { #if defined(PATH_LEN) && \ (PATH_LEN) >= 256 @@ -66,7 +76,6 @@ new_tmp_common(int *fd, char **path, int type) struct stat st; char suffix[] = "tmp.XXXXXXXXXX"; - char *tmpdir = NULL; size_t dirlen; size_t destlen; @@ -100,15 +109,25 @@ new_tmp_common(int *fd, char **path, int type) * (on error, it will not be touched) */ - *fd = -1; + if (tmpdir == NULL) { /* no user override */ #if defined(PERMIT_NON_STICKY_ALWAYS) && \ ((PERMIT_NON_STICKY_ALWAYS) > 0) - tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS, &fail_dir); + tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS, &fail_dir, NULL); #else - tmpdir = env_tmpdir(0, &fail_dir); + tmpdir = env_tmpdir(0, &fail_dir, NULL); #endif + } else { + +#if defined(PERMIT_NON_STICKY_ALWAYS) && \ + ((PERMIT_NON_STICKY_ALWAYS) > 0) + tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS, &fail_dir, + tmpdir); +#else + tmpdir = env_tmpdir(0, &fail_dir, tmpdir); +#endif + } if (tmpdir == NULL) goto err; @@ -189,7 +208,8 @@ err: */ char * -env_tmpdir(int bypass_all_sticky_checks, char **tmpdir) +env_tmpdir(int bypass_all_sticky_checks, char **tmpdir, + char *override_tmpdir) { char *t; int allow_noworld_unsticky; @@ -198,7 +218,11 @@ env_tmpdir(int bypass_all_sticky_checks, char **tmpdir) char tmp[] = "/tmp"; char vartmp[] = "/var/tmp"; - t = getenv("TMPDIR"); + /* tmpdir is a user override, if set */ + if (override_tmpdir == NULL) + t = getenv("TMPDIR"); + else + t = override_tmpdir; if (t != NULL && *t != '\0') { @@ -409,11 +433,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; } @@ -439,6 +475,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 */ @@ -549,7 +591,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; @@ -637,6 +679,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); @@ -728,6 +782,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) { diff --git a/util/libreboot-utils/lib/state.c b/util/libreboot-utils/lib/state.c index c2040144..e3cb0890 100644 --- a/util/libreboot-utils/lib/state.c +++ b/util/libreboot-utils/lib/state.c @@ -26,6 +26,11 @@ struct xstate * xstart(int argc, char *argv[]) { static int first_run = 1; + static char *dir = NULL; + static char *base = NULL; + char *realdir = NULL; + char *tmpdir = NULL; + char *tmpbase_local = NULL; static struct xstate us = { { @@ -107,9 +112,35 @@ xstart(int argc, char *argv[]) us.f.tmp_fd = -1; us.f.tname = NULL; - if (new_tmpfile(&us.f.tmp_fd, &us.f.tname) < 0) + if ((realdir = realpath(us.f.fname, NULL)) == NULL) + err_no_cleanup(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", + us.f.fname); + + if ((us.f.base = strdup(base)) == NULL) + err_no_cleanup(errno, "strdup base"); + + us.f.dirfd = fs_open(dir, + O_RDONLY | O_DIRECTORY); + if (us.f.dirfd < 0) + err_no_cleanup(errno, "%s: open dir", dir); + + if (new_tmpfile(&us.f.tmp_fd, &us.f.tname, dir) < 0) err_no_cleanup(errno, "%s", us.f.tname); + if (fs_dirname_basename(us.f.tname, + &tmpdir, &tmpbase_local, 0) < 0) + err_no_cleanup(errno, "tmp basename"); + + us.f.tmpbase = strdup(tmpbase_local); + if (us.f.tmpbase == NULL) + err_no_cleanup(errno, "strdup tmpbase"); + + free_if_null(&tmpdir); + if (us.f.tname == NULL) err_no_cleanup(errno, "x->f.tname null"); if (*us.f.tname == '\0') @@ -164,32 +195,6 @@ err(int nvm_errval, const char *msg, ...) exit(EXIT_FAILURE); } -const char * -getnvmprogname(void) -{ - struct xstate *x = xstatus(); - - const char *p; - static char fallback[] = "nvmutil"; - - char *rval = fallback; - - if (x != NULL) { - - if (x->argv0 != NULL && *x->argv0 != '\0') - rval = x->argv0; - else - return fallback; - } - - p = strrchr(rval, '/'); - - if (p) - return p + 1; - else - return rval; -} - int exit_cleanup(void) { @@ -212,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) diff --git a/util/libreboot-utils/lib/string.c b/util/libreboot-utils/lib/string.c index cb37c1ba..2f2be5f3 100644 --- a/util/libreboot-utils/lib/string.c +++ b/util/libreboot-utils/lib/string.c @@ -122,6 +122,8 @@ void err_no_cleanup(int nvm_errval, const char *msg, ...) { va_list args; + int saved_errno = errno; + const char *p; #if defined(__OpenBSD__) && defined(OpenBSD) #if (OpenBSD) >= 509 @@ -129,11 +131,11 @@ err_no_cleanup(int nvm_errval, const char *msg, ...) fprintf(stderr, "pledge failure during exit"); #endif #endif - if (!errno) - errno = ECANCELED; + saved_errno = errno = ECANCELED; - fprintf(stderr, "nvmutil: "); + if ((p = getnvmprogname()) != NULL) + fprintf(stderr, "%s: ", p); va_start(args, msg); vfprintf(stderr, msg, args); @@ -144,3 +146,61 @@ err_no_cleanup(int nvm_errval, const char *msg, ...) exit(EXIT_FAILURE); } +const char * +getnvmprogname(void) +{ + static char *rval = NULL; + static char *p; + static int setname = 0; + + if (!setname) { + if ((rval = lbgetprogname(NULL)) == NULL) + return NULL; + + p = strrchr(rval, '/'); + if (p) + rval = p + 1; + + setname = 1; + } + + return rval; +} + +/* singleton. if string not null, + sets the string. after set, + will not set anymore. either + way, returns the string + */ +char * +lbgetprogname(char *argv0) +{ + static int setname = 0; + static char *progname = NULL; + size_t len; + + if (!setname) { + if (if_err(argv0 == NULL || *argv0 == '\0', EFAULT) || + slen(argv0, 4096, &len) < 0 || + (progname = malloc(len + 1)) == NULL) + return NULL; + + memcpy(progname, argv0, len + 1); + setname = 1; + } + + return progname; +} + + + + + + + + + + + + + diff --git a/util/libreboot-utils/mkhtemp.c b/util/libreboot-utils/mkhtemp.c index 3e148a4a..bcbc63a9 100644 --- a/util/libreboot-utils/mkhtemp.c +++ b/util/libreboot-utils/mkhtemp.c @@ -67,12 +67,19 @@ int main(int argc, char *argv[]) { + const char usage_str[] = "usage: %s [-d] [-p dir] [template]"; + + char *tmpdir = NULL; + char *template = NULL; + char *s = NULL; int fd = -1; char c; int type = MKHTEMP_FILE; size_t len; + char *rp; + #if defined (PATH_LEN) && \ (PATH_LEN) >= 256 size_t maxlen = PATH_LEN; @@ -80,30 +87,53 @@ main(int argc, char *argv[]) size_t maxlen = 4096; #endif + char resolved[maxlen]; + + if (lbgetprogname(argv[0]) == NULL) + err_no_cleanup(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) { + while ((c = + getopt(argc, argv, "dp:")) != -1) { - switch(c) { + switch (c) { case 'd': - type = MKHTEMP_DIR; break; - default: - err_no_cleanup(EINVAL, - "usage: mkhtemp [-d]\n"); + case 'p': + tmpdir = optarg; + break; + + default: + goto err_usage; } } - if (new_tmp_common(&fd, &s, type) < 0) + if (optind < argc) + template = argv[optind]; + if (optind + 1 < argc) + err_no_cleanup(EINVAL, + "usage: mkhtemp [-d] [-p dir] [template]\n"); + + + if (tmpdir != NULL) { + rp = realpath(tmpdir, resolved); + if (rp == NULL) + err_no_cleanup(errno, + "realpath: %s", tmpdir); + + tmpdir = resolved; + } + + if (new_tmp_common(&fd, &s, type, tmpdir) < 0) err_no_cleanup(errno, "%s", s); #if defined(__OpenBSD__) && defined(OpenBSD) @@ -125,6 +155,10 @@ main(int argc, char *argv[]) printf("%s\n", s); return EXIT_SUCCESS; + +err_usage: + err_no_cleanup(EINVAL, + "usage: %s [-d] [-p dir] [template]\n", getnvmprogname()); }/* diff --git a/util/libreboot-utils/nvmutil.c b/util/libreboot-utils/nvmutil.c index fb45a97c..da7d40ec 100644 --- a/util/libreboot-utils/nvmutil.c +++ b/util/libreboot-utils/nvmutil.c @@ -35,6 +35,9 @@ main(int argc, char *argv[]) size_t c; + if (lbgetprogname(argv[0]) == NULL) + err_no_cleanup(errno, "could not set progname"); + /* https://man.openbsd.org/pledge.2 https://man.openbsd.org/unveil.2 */ #if defined(__OpenBSD__) && defined(OpenBSD) |
