diff options
| -rw-r--r-- | include/inject.sh | 6 | ||||
| -rw-r--r-- | util/libreboot-utils/.gitignore (renamed from util/nvmutil/.gitignore) | 1 | ||||
| -rw-r--r-- | util/libreboot-utils/AUTHORS (renamed from util/nvmutil/AUTHORS) | 0 | ||||
| -rw-r--r-- | util/libreboot-utils/COPYING (renamed from util/nvmutil/COPYING) | 0 | ||||
| -rw-r--r-- | util/libreboot-utils/Makefile (renamed from util/nvmutil/Makefile) | 43 | ||||
| -rw-r--r-- | util/libreboot-utils/include/common.h (renamed from util/nvmutil/include/common.h) | 264 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/checksum.c (renamed from util/nvmutil/lib/checksum.c) | 20 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/command.c (renamed from util/nvmutil/lib/command.c) | 218 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/file.c | 1065 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/io.c (renamed from util/nvmutil/lib/io.c) | 252 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/mkhtemp.c | 1103 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/num.c | 119 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/rand.c | 114 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/state.c (renamed from util/nvmutil/lib/state.c) | 185 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/string.c | 284 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/usage.c (renamed from util/nvmutil/lib/usage.c) | 2 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/word.c (renamed from util/nvmutil/lib/word.c) | 22 | ||||
| -rw-r--r-- | util/libreboot-utils/mkhtemp.c | 211 | ||||
| -rw-r--r-- | util/libreboot-utils/nvmutil.c | 132 | ||||
| -rw-r--r-- | util/nvmutil/lib/file.c | 890 | ||||
| -rw-r--r-- | util/nvmutil/lib/num.c | 349 | ||||
| -rw-r--r-- | util/nvmutil/lib/string.c | 75 | ||||
| -rw-r--r-- | util/nvmutil/nvmutil.c | 50 |
23 files changed, 3553 insertions, 1852 deletions
diff --git a/include/inject.sh b/include/inject.sh index 783e06ed..b61ad9d5 100644 --- a/include/inject.sh +++ b/include/inject.sh @@ -6,7 +6,7 @@ cbcfgsdir="config/coreboot" tmpromdel="$XBMK_CACHE/DO_NOT_FLASH" -nvmutil="util/nvmutil/nvmutil" +nvmutil="util/libreboot-utils/nvmutil" ifdtool="elf/coreboot/default/ifdtool" checkvars="CONFIG_GBE_BIN_PATH" @@ -197,8 +197,8 @@ modify_mac() x_ cp "${CONFIG_GBE_BIN_PATH##*../}" "$xbtmp/gbe" if [ -n "$new_mac" ] && [ "$new_mac" != "restore" ]; then - x_ make -C util/nvmutil clean - x_ make -C util/nvmutil + x_ make -C util/libreboot-utils clean + x_ make -C util/libreboot-utils x_ "$nvmutil" "$xbtmp/gbe" setmac "$new_mac" fi diff --git a/util/nvmutil/.gitignore b/util/libreboot-utils/.gitignore index 9414c836..fbf110f9 100644 --- a/util/nvmutil/.gitignore +++ b/util/libreboot-utils/.gitignore @@ -1,5 +1,6 @@ /nvm /nvmutil +/mkhtemp *.bin *.o *.d diff --git a/util/nvmutil/AUTHORS b/util/libreboot-utils/AUTHORS index f38ea210..f38ea210 100644 --- a/util/nvmutil/AUTHORS +++ b/util/libreboot-utils/AUTHORS diff --git a/util/nvmutil/COPYING b/util/libreboot-utils/COPYING index 47c35a86..47c35a86 100644 --- a/util/nvmutil/COPYING +++ b/util/libreboot-utils/COPYING diff --git a/util/nvmutil/Makefile b/util/libreboot-utils/Makefile index 9d8548b9..692ebf0f 100644 --- a/util/nvmutil/Makefile +++ b/util/libreboot-utils/Makefile @@ -24,8 +24,9 @@ STRICT = $(WARN) -std=c90 -pedantic -Werror HELLFLAGS = $(STRICT) -Weverything PROG = nvmutil +PROGMKH = mkhtemp -OBJS = \ +OBJS_NVMUTIL = \ obj/nvmutil.o \ obj/lib/state.o \ obj/lib/file.o \ @@ -35,19 +36,33 @@ OBJS = \ obj/lib/num.o \ obj/lib/io.o \ obj/lib/checksum.o \ - obj/lib/word.o + obj/lib/word.o \ + obj/lib/mkhtemp.o \ + obj/lib/rand.o + +OBJS_MKHTEMP = \ + obj/mkhtemp.o \ + obj/lib/file.o \ + obj/lib/string.o \ + obj/lib/num.o \ + obj/lib/mkhtemp.o \ + obj/lib/rand.o # default mode CFLAGS_MODE = $(PORTABLE) CC_MODE = $(CC) -all: $(PROG) +all: $(PROG) $(PROGMKH) -$(PROG): $(OBJS) - $(CC_MODE) $(OBJS) -o $(PROG) $(LDFLAGS) +$(PROG): $(OBJS_NVMUTIL) + $(CC_MODE) $(OBJS_NVMUTIL) -o $(PROG) $(LDFLAGS) + +$(PROGMKH): $(OBJS_MKHTEMP) + $(CC_MODE) $(OBJS_MKHTEMP) -o $(PROGMKH) $(LDFLAGS) # ensure obj directory exists -$(OBJS): obj +$(OBJS_NVMUTIL): obj +$(OBJS_MKHTEMP): obj obj: mkdir obj || true @@ -58,6 +73,9 @@ obj: obj/nvmutil.o: nvmutil.c $(CC_MODE) $(CFLAGS_MODE) -c nvmutil.c -o obj/nvmutil.o +obj/mkhtemp.o: mkhtemp.c + $(CC_MODE) $(CFLAGS_MODE) -c mkhtemp.c -o obj/mkhtemp.o + # library/helper objects obj/lib/state.o: lib/state.c @@ -87,18 +105,27 @@ obj/lib/checksum.o: lib/checksum.c obj/lib/word.o: lib/word.c $(CC_MODE) $(CFLAGS_MODE) -c lib/word.c -o obj/lib/word.o +obj/lib/mkhtemp.o: lib/mkhtemp.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/mkhtemp.c -o obj/lib/mkhtemp.o + +obj/lib/rand.o: lib/rand.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/rand.c -o obj/lib/rand.o + # install -install: $(PROG) +install: $(PROG) $(PROGMKH) $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin $(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG) chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG) + $(INSTALL) $(PROGMKH) $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) + chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) + rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) clean: - rm -f $(PROG) $(OBJS) + rm -f $(PROG) $(PROGMKH) $(OBJS_NVMUTIL) $(OBJS_MKHTEMP) distclean: clean diff --git a/util/nvmutil/include/common.h b/util/libreboot-utils/include/common.h index 46fbcb38..fb3aa886 100644 --- a/util/nvmutil/include/common.h +++ b/util/libreboot-utils/include/common.h @@ -1,31 +1,25 @@ /* SPDX-License-Identifier: MIT * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> + + TODO: this file should be split, into headers for each + C source file specifically. it was originally just + for nvmutil, until i added mkhtemp to the mix */ + #ifndef COMMON_H #define COMMON_H #include <sys/types.h> #include <sys/stat.h> #include <limits.h> +#include <errno.h> /* for linux getrandom */ #if defined(__linux__) -#include <errno.h> -#if defined(__has_include) -#if __has_include(<sys/random.h>) #include <sys/random.h> -#define HAVE_GETRANDOM 1 -#endif -#endif -#if !defined(HAVE_GETRANDOM) #include <sys/syscall.h> -#if defined(SYS_getrandom) -#define HAVE_GETRANDOM_SYSCALL 1 -#endif -#endif - #endif #define items(x) (sizeof((x)) / sizeof((x)[0])) @@ -35,11 +29,27 @@ int fchmod(int fd, mode_t mode); -/* analog of SSIZE_MAX +#define MKHTEMP_RETRY_MAX 512 +#define MKHTEMP_SPIN_THRESHOLD 32 + +#define MKHTEMP_FILE 0 +#define MKHTEMP_DIR 1 + + +/* if 1: on operations that + * check ownership, always + * permit root to access even + * if not the file/dir owner + */ +#ifndef ALLOW_ROOT_OVERRIDE +#define ALLOW_ROOT_OVERRIDE 0 +#endif + +/* */ -#ifndef X_LONG_MAX -#define X_LONG_MAX ((long)(~((long)1 << (sizeof(long)*CHAR_BIT-1)))) +#ifndef SSIZE_MAX +#define SSIZE_MAX ((ssize_t)(~((ssize_t)1 << (sizeof(ssize_t)*CHAR_BIT-1)))) #endif @@ -72,8 +82,8 @@ int fchmod(int fd, mode_t mode); #define MAX_ZERO_RW_RETRY 5 #endif -#ifndef HAVE_REAL_PREAD_PWRITE -#define HAVE_REAL_PREAD_PWRITE 0 +#ifndef REAL_POS_IO +#define REAL_POS_IO 0 #endif #ifndef LOOP_EAGAIN @@ -95,6 +105,10 @@ int fchmod(int fd, mode_t mode); #define EXIT_SUCCESS 0 #endif +#ifndef O_NOCTTY +#define O_NOCTTY 0 +#endif + #ifndef O_ACCMODE #define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) #endif @@ -208,14 +222,14 @@ int fchmod(int fd, mode_t mode); */ struct commands { - unsigned long chk; + size_t chk; char *str; void (*run)(void); int argc; unsigned char arg_part; unsigned char chksum_read; unsigned char chksum_write; - unsigned long rw_size; /* within the 4KB GbE part */ + size_t rw_size; /* within the 4KB GbE part */ int flags; /* e.g. O_RDWR or O_RDONLY */ }; @@ -254,7 +268,7 @@ struct xfile { off_t gbe_file_size; off_t gbe_tmp_size; - unsigned long part; + size_t part; unsigned char part_modified[2]; unsigned char part_valid[2]; @@ -262,6 +276,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 @@ -278,7 +297,7 @@ struct xstate { char *argv0; - unsigned long i; /* index to cmd[] for current command */ + size_t i; /* index to cmd[] for current command */ int no_cmd; /* Cat commands set this. @@ -286,28 +305,39 @@ struct xstate { int cat; }; +struct filesystem { + int rootfd; +}; - -struct xstate *xstatus(int argc, char *argv[]); +struct xstate *xstart(int argc, char *argv[]); +struct xstate *xstatus(void); /* Sanitize command tables. */ void sanitize_command_list(void); -void sanitize_command_index(unsigned long c); +void sanitize_command_index(size_t c); /* Argument handling (user input) */ void set_cmd(int argc, char *argv[]); void set_cmd_args(int argc, char *argv[]); -unsigned long conv_argv_part_num(const char *part_str); -int xstrxcmp(const char *a, const char *b, unsigned long maxlen); +size_t conv_argv_part_num(const char *part_str); /* Prep files for reading */ void open_gbe_file(void); +int fd_verify_regular(int fd, + const struct stat *expected, + struct stat *out); +int fd_verify_identity(int fd, + const struct stat *expected, + struct stat *out); +int fd_verify_dir_identity(int fd, + const struct stat *expected); +int is_owner(struct stat *st); int lock_file(int fd, int flags); int same_file(int fd, struct stat *st_old, int check_size); void xopen(int *fd, const char *path, int flags, struct stat *st); @@ -318,45 +348,50 @@ void xopen(int *fd, const char *path, int flags, struct stat *st); void copy_gbe(void); void read_file(void); void read_checksums(void); -int good_checksum(unsigned long partnum); +int good_checksum(size_t partnum); /* validate commands */ -void check_command_num(unsigned long c); -unsigned char valid_command(unsigned long c); +void check_command_num(size_t c); +unsigned char valid_command(size_t c); /* Helper functions for command: setmac */ void cmd_helper_setmac(void); void parse_mac_string(void); -unsigned long xstrxlen(const char *scmp, unsigned long maxlen); -void set_mac_byte(unsigned long mac_byte_pos); -void set_mac_nib(unsigned long mac_str_pos, - unsigned long mac_byte_pos, unsigned long mac_nib_pos); +void set_mac_byte(size_t mac_byte_pos); +void set_mac_nib(size_t mac_str_pos, + size_t mac_byte_pos, size_t mac_nib_pos); +void write_mac_part(size_t partnum); + +/* string functions + */ + +int slen(const char *scmp, size_t maxlen, + size_t *rval); +int scmp(const char *a, const char *b, + size_t maxlen, int *rval); +int sdup(const char *s, + size_t n, char **dest); +int scat(const char *s1, const char *s2, + size_t n, char **dest); +int dcat(const char *s, size_t n, + size_t off, char **dest1, + char **dest2); +/* numerical functions + */ + unsigned short hextonum(char ch_s); -unsigned long rlong(void); -#if !(defined(FALLBACK_RAND_1989) && \ - ((FALLBACK_RAND_1989) > 0)) -#if defined(__linux__) -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) -int fallback_rand_getrandom(void *buf, size_t len); -#endif -#endif -#else -unsigned long fallback_rand_1989(void); -unsigned long entropy_jitter(void); -#endif -void write_mac_part(unsigned long partnum); +size_t rlong(void); /* Helper functions for command: dump */ void cmd_helper_dump(void); -void print_mac_from_nvm(unsigned long partnum); -void hexdump(unsigned long partnum); +void print_mac_from_nvm(size_t partnum); +void hexdump(size_t partnum); /* Helper functions for command: swap */ @@ -375,7 +410,7 @@ void cmd_helper_copy(void); void cmd_helper_cat(void); void cmd_helper_cat16(void); void cmd_helper_cat128(void); -void cat(unsigned long nff); +void cat(size_t nff); void cat_buf(unsigned char *b); /* Command verification/control @@ -388,51 +423,51 @@ void cmd_helper_err(void); */ void write_gbe_file(void); -void set_checksum(unsigned long part); -unsigned short calculated_checksum(unsigned long p); +void set_checksum(size_t part); +unsigned short calculated_checksum(size_t p); /* NVM read/write */ -unsigned short nvm_word(unsigned long pos16, unsigned long part); -void set_nvm_word(unsigned long pos16, - unsigned long part, unsigned short val16); -void set_part_modified(unsigned long p); -void check_nvm_bound(unsigned long pos16, unsigned long part); -void check_bin(unsigned long a, const char *a_name); +unsigned short nvm_word(size_t pos16, size_t part); +void set_nvm_word(size_t pos16, + size_t part, unsigned short val16); +void set_part_modified(size_t p); +void check_nvm_bound(size_t pos16, size_t part); +void check_bin(size_t a, const char *a_name); /* GbE file read/write */ -void rw_gbe_file_part(unsigned long p, int rw_type, +void rw_gbe_file_part(size_t p, int rw_type, const char *rw_type_str); void write_to_gbe_bin(void); int gbe_mv(void); -void check_written_part(unsigned long p); +void check_written_part(size_t p); void report_io_err_rw(void); -unsigned char *gbe_mem_offset(unsigned long part, const char *f_op); -off_t gbe_file_offset(unsigned long part, const char *f_op); -off_t gbe_x_offset(unsigned long part, const char *f_op, +unsigned char *gbe_mem_offset(size_t part, const char *f_op); +off_t gbe_file_offset(size_t part, const char *f_op); +off_t gbe_x_offset(size_t part, const char *f_op, const char *d_type, off_t nsize, off_t ncmp); -long rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw, +ssize_t rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw, off_t off, int rw_type); /* Generic read/write */ int fsync_dir(const char *path); -long rw_file_exact(int fd, unsigned char *mem, unsigned long len, +ssize_t rw_file_exact(int fd, unsigned char *mem, size_t len, off_t off, int rw_type, int loop_eagain, int loop_eintr, - unsigned long max_retries, int off_reset); -long prw(int fd, void *mem, unsigned long nrw, + size_t max_retries, int off_reset); +ssize_t prw(int fd, void *mem, size_t nrw, off_t off, int rw_type, int loop_eagain, int loop_eintr, int off_reset); -int io_args(int fd, void *mem, unsigned long nrw, +int io_args(int fd, void *mem, size_t nrw, off_t off, int rw_type); int check_file(int fd, struct stat *st); -long rw_over_nrw(long r, unsigned long nrw); -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 +ssize_t rw_over_nrw(ssize_t r, size_t nrw); +#if !defined(REAL_POS_IO) || \ + REAL_POS_IO < 1 off_t lseek_on_eintr(int fd, off_t off, int whence, int loop_eagain, int loop_eintr); #endif @@ -442,18 +477,79 @@ int try_err(int loop_err, int errval); */ void usage(void); -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); -/* Portable libc functions - */ - -char *new_tmpfile(int *fd, int local, const char *path); -int mkstemp_n(char *template); -char *get_tmpdir(void); +void err_mkhtemp(int stfu, int errval, const char *msg, ...); + +/* libc hardening + */ + +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, + char *p, + size_t xc, + 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); +int mkhtemp_fill_random(char *p, size_t xc); +int world_writeable_and_sticky(const char *s, + int sticky_allowed, int always_sticky); +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 *override_tmpdir); +int secure_file(int *fd, + struct stat *st, + struct stat *expected, + int bad_flags, + int check_seek, + int do_lock, + mode_t mode); int close_on_eintr(int fd); int fsync_on_eintr(int fd); +int fs_rename_at(int olddirfd, const char *old, + int newdirfd, const char *new); +int fs_open(const char *path, int flags); +void close_no_err(int *fd); +void free_if_null(char **buf); +int close_warn(int *fd, char *s); +struct filesystem *rootfs(void); +int fs_resolve_at(int dirfd, const char *path, int flags); +int fs_next_component(const char **p, + char *name, size_t namesz); +int fs_open_component(int dirfd, const char *name, + int flags, int is_last); +int fs_dirname_basename(const char *path, + char **dir, char **base, int allow_relative); +int openat2p(int dirfd, const char *path, + int flags, mode_t mode); +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 */ @@ -467,16 +563,16 @@ typedef char static_assert_unsigned_short_is_2[ typedef char static_assert_short_is_2[(sizeof(short) >= 2) ? 1 : -1]; typedef char static_assert_unsigned_int_is_4[ (sizeof(unsigned int) >= 4) ? 1 : -1]; -typedef char static_assert_unsigned_long_is_4[ - (sizeof(unsigned long) >= 4) ? 1 : -1]; -typedef char static_assert_long_ulong[ - (sizeof(unsigned long) == sizeof(long)) ? 1 : -1]; +typedef char static_assert_unsigned_ssize_t_is_4[ + (sizeof(size_t) >= 4) ? 1 : -1]; +typedef char static_assert_ssize_t_ussize_t[ + (sizeof(size_t) == sizeof(ssize_t)) ? 1 : -1]; typedef char static_assert_int_ge_32[(sizeof(int) >= 4) ? 1 : -1]; typedef char static_assert_twos_complement[ ((-1 & 3) == 3) ? 1 : -1 ]; -typedef char assert_unsigned_long_ptr[ - (sizeof(unsigned long) >= sizeof(void *)) ? 1 : -1 +typedef char assert_unsigned_ssize_t_ptr[ + (sizeof(size_t) >= sizeof(void *)) ? 1 : -1 ]; /* diff --git a/util/nvmutil/lib/checksum.c b/util/libreboot-utils/lib/checksum.c index 8565361b..9a041989 100644 --- a/util/nvmutil/lib/checksum.c +++ b/util/libreboot-utils/lib/checksum.c @@ -17,12 +17,12 @@ void read_checksums(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; - unsigned long _p; - unsigned long _skip_part; + size_t _p; + size_t _skip_part; unsigned char _num_invalid; unsigned char _max_invalid; @@ -59,16 +59,16 @@ read_checksums(void) if (_num_invalid >= _max_invalid) { if (_max_invalid == 1) - err(ECANCELED, "%s: part %lu has a bad checksum", - f->fname, (unsigned long)f->part); + 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); } } int -good_checksum(unsigned long partnum) +good_checksum(size_t partnum) { unsigned short expected_checksum; unsigned short actual_checksum; @@ -87,16 +87,16 @@ good_checksum(unsigned long partnum) } void -set_checksum(unsigned long p) +set_checksum(size_t p) { check_bin(p, "part number"); set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p)); } unsigned short -calculated_checksum(unsigned long p) +calculated_checksum(size_t p) { - unsigned long c; + size_t c; unsigned int val16; val16 = 0; diff --git a/util/nvmutil/lib/command.c b/util/libreboot-utils/lib/command.c index 95e1b4f7..c7048a23 100644 --- a/util/nvmutil/lib/command.c +++ b/util/libreboot-utils/lib/command.c @@ -21,10 +21,10 @@ void sanitize_command_list(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); - unsigned long c; - unsigned long num_commands; + size_t c; + size_t num_commands; num_commands = items(x->cmd); @@ -33,37 +33,41 @@ sanitize_command_list(void) } void -sanitize_command_index(unsigned long c) +sanitize_command_index(size_t c) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[c]; int _flag; - unsigned long gbe_rw_size; + size_t gbe_rw_size; + + size_t rval; check_command_num(c); if (cmd->argc < 3) - err(EINVAL, "cmd index %lu: argc below 3, %d", - (unsigned long)c, cmd->argc); + 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", - (unsigned long)c); + b0rk(EINVAL, "cmd index %lu: NULL str", + (size_t)c); if (*cmd->str == '\0') - err(EINVAL, "cmd index %lu: empty str", - (unsigned long)c); + b0rk(EINVAL, "cmd index %lu: empty str", + (size_t)c); + + if (slen(cmd->str, MAX_CMD_LEN +1, &rval) < 0) + b0rk(errno, "Could not get command length"); - if (xstrxlen(cmd->str, MAX_CMD_LEN + 1) > - MAX_CMD_LEN) { - err(EINVAL, "cmd index %lu: str too long: %s", - (unsigned long)c, cmd->str); + if (rval > MAX_CMD_LEN) { + 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", - (unsigned long)c); + b0rk(EINVAL, "cmd index %lu: cmd ptr null", + (size_t)c); check_bin(cmd->arg_part, "cmd.arg_part"); check_bin(cmd->chksum_read, "cmd.chksum_read"); @@ -76,36 +80,40 @@ sanitize_command_index(unsigned long c) case NVM_SIZE: break; default: - err(EINVAL, "Unsupported rw_size: %lu", - (unsigned long)gbe_rw_size); + 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", - (unsigned long)gbe_rw_size); + 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 set_cmd(int argc, char *argv[]) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); const char *cmd; - unsigned long c; + int rval; + + size_t c; for (c = 0; c < items(x->cmd); c++) { cmd = x->cmd[c].str; - /* not the right command */ - if (xstrxcmp(argv[2], cmd, MAX_CMD_LEN) != 0) - continue; + if (scmp(argv[2], cmd, MAX_CMD_LEN, &rval) < 0) + err_no_cleanup(0, EINVAL, + "could not compare command strings"); + if (rval != 0) + continue; /* not the right command */ /* valid command found */ if (argc >= x->cmd[c].argc) { @@ -115,18 +123,19 @@ set_cmd(int argc, char *argv[]) return; } - err(EINVAL, + err_no_cleanup(0, EINVAL, "Too few args on command '%s'", cmd); } + x->no_cmd = 1; } void set_cmd_args(int argc, char *argv[]) { - struct xstate *x = xstatus(0, NULL); - unsigned long i = x->i; + struct xstate *x = xstatus(); + size_t i = x->i; struct commands *cmd = &x->cmd[i]; struct xfile *f = &x->f; @@ -139,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) { @@ -159,35 +168,35 @@ set_cmd_args(int argc, char *argv[]) } } -unsigned long +size_t 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 (unsigned long)(ch - '0'); + return (size_t)(ch - '0'); } void -check_command_num(unsigned long c) +check_command_num(size_t c) { if (!valid_command(c)) - err(EINVAL, "Invalid run_cmd arg: %lu", - (unsigned long)c); + b0rk(EINVAL, "Invalid run_cmd arg: %lu", + (size_t)c); } unsigned char -valid_command(unsigned long c) +valid_command(size_t c) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd; if (c >= items(x->cmd)) @@ -196,7 +205,7 @@ valid_command(unsigned long c) cmd = &x->cmd[c]; if (c != cmd->chk) - err(EINVAL, + b0rk(EINVAL, "Invalid cmd chk value (%lu) vs arg: %lu", cmd->chk, c); @@ -206,10 +215,10 @@ valid_command(unsigned long c) void cmd_helper_setmac(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct macaddr *mac = &x->mac; - unsigned long partnum; + size_t partnum; check_cmd(cmd_helper_setmac, "setmac"); @@ -223,13 +232,18 @@ cmd_helper_setmac(void) void parse_mac_string(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct macaddr *mac = &x->mac; - unsigned long mac_byte; + size_t mac_byte; - if (xstrxlen(x->mac.str, 18) != 17) - err(EINVAL, "MAC address is the wrong length"); + size_t rval; + + if (slen(x->mac.str, 18, &rval) < 0) + b0rk(EINVAL, "Could not determine MAC length"); + + if (rval != 17) + b0rk(EINVAL, "MAC address is the wrong length"); memset(mac->mac_buf, 0, sizeof(mac->mac_buf)); @@ -237,28 +251,28 @@ 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 -set_mac_byte(unsigned long mac_byte_pos) +set_mac_byte(size_t mac_byte_pos) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct macaddr *mac = &x->mac; char separator; - unsigned long mac_str_pos; - unsigned long mac_nib_pos; + size_t mac_str_pos; + size_t mac_nib_pos; mac_str_pos = mac_byte_pos * 3; 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); } @@ -267,10 +281,10 @@ set_mac_byte(unsigned long mac_byte_pos) } void -set_mac_nib(unsigned long mac_str_pos, - unsigned long mac_byte_pos, unsigned long mac_nib_pos) +set_mac_nib(size_t mac_str_pos, + size_t mac_byte_pos, size_t mac_nib_pos) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct macaddr *mac = &x->mac; char mac_ch; @@ -278,9 +292,13 @@ set_mac_nib(unsigned long mac_str_pos, mac_ch = mac->str[mac_str_pos + mac_nib_pos]; - if ((hex_num = hextonum(mac_ch)) > 15) - err(EINVAL, "Invalid character '%c'", - mac->str[mac_str_pos + mac_nib_pos]); + if ((hex_num = hextonum(mac_ch)) > 15) { + if (hex_num >= 17) + b0rk(EIO, "Randomisation failure"); + else + b0rk(EINVAL, "Invalid character '%c'", + mac->str[mac_str_pos + mac_nib_pos]); + } /* If random, ensure that local/unicast bits are set. */ @@ -298,13 +316,13 @@ set_mac_nib(unsigned long mac_str_pos, } void -write_mac_part(unsigned long partnum) +write_mac_part(size_t partnum) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; struct macaddr *mac = &x->mac; - unsigned long w; + size_t w; check_bin(partnum, "part number"); if (!f->part_valid[partnum]) @@ -314,17 +332,17 @@ write_mac_part(unsigned long partnum) set_nvm_word(w, partnum, mac->mac_buf[w]); printf("Wrote MAC address to part %lu: ", - (unsigned long)partnum); + (size_t)partnum); print_mac_from_nvm(partnum); } void cmd_helper_dump(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long p; + size_t p; check_cmd(cmd_helper_dump, "dump"); @@ -338,12 +356,12 @@ cmd_helper_dump(void) fprintf(stderr, "BAD checksum %04x in part %lu (expected %04x)\n", nvm_word(NVM_CHECKSUM_WORD, p), - (unsigned long)p, + (size_t)p, calculated_checksum(p)); } printf("MAC (part %lu): ", - (unsigned long)p); + (size_t)p); print_mac_from_nvm(p); @@ -352,9 +370,9 @@ cmd_helper_dump(void) } void -print_mac_from_nvm(unsigned long partnum) +print_mac_from_nvm(size_t partnum) { - unsigned long c; + size_t c; unsigned short val16; for (c = 0; c < 3; c++) { @@ -373,16 +391,16 @@ print_mac_from_nvm(unsigned long partnum) } void -hexdump(unsigned long partnum) +hexdump(size_t partnum) { - unsigned long c; - unsigned long row; + size_t c; + size_t row; unsigned short val16; for (row = 0; row < 8; row++) { printf("%08lx ", - (unsigned long)((unsigned long)row << 4)); + (size_t)((size_t)row << 4)); for (c = 0; c < 8; c++) { @@ -404,24 +422,24 @@ hexdump(unsigned long partnum) void cmd_helper_swap(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; check_cmd(cmd_helper_swap, "swap"); memcpy( - f->buf + (unsigned long)GBE_WORK_SIZE, + f->buf + (size_t)GBE_WORK_SIZE, f->buf, GBE_PART_SIZE); memcpy( f->buf, - f->buf + (unsigned long)GBE_PART_SIZE, + f->buf + (size_t)GBE_PART_SIZE, GBE_PART_SIZE); memcpy( - f->buf + (unsigned long)GBE_PART_SIZE, - f->buf + (unsigned long)GBE_WORK_SIZE, + f->buf + (size_t)GBE_PART_SIZE, + f->buf + (size_t)GBE_WORK_SIZE, GBE_PART_SIZE); set_part_modified(0); @@ -431,14 +449,14 @@ cmd_helper_swap(void) void cmd_helper_copy(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; check_cmd(cmd_helper_copy, "copy"); memcpy( - f->buf + (unsigned long)((f->part ^ 1) * GBE_PART_SIZE), - f->buf + (unsigned long)(f->part * GBE_PART_SIZE), + f->buf + (size_t)((f->part ^ 1) * GBE_PART_SIZE), + f->buf + (size_t)(f->part * GBE_PART_SIZE), GBE_PART_SIZE); set_part_modified(f->part ^ 1); @@ -447,7 +465,7 @@ cmd_helper_copy(void) void cmd_helper_cat(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); check_cmd(cmd_helper_cat, "cat"); @@ -458,7 +476,7 @@ cmd_helper_cat(void) void cmd_helper_cat16(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); check_cmd(cmd_helper_cat16, "cat16"); @@ -469,7 +487,7 @@ cmd_helper_cat16(void) void cmd_helper_cat128(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); check_cmd(cmd_helper_cat128, "cat128"); @@ -478,20 +496,20 @@ cmd_helper_cat128(void) } void -cat(unsigned long nff) +cat(size_t nff) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long p; - unsigned long ff; + size_t p; + size_t ff; p = 0; ff = 0; - if ((unsigned long)x->cat != nff) { + if ((size_t)x->cat != nff) { - err(ECANCELED, "erroneous call to cat"); + b0rk(ECANCELED, "erroneous call to cat"); } fflush(NULL); @@ -501,7 +519,7 @@ cat(unsigned long nff) for (p = 0; p < 2; p++) { cat_buf(f->bufcmp + - (unsigned long)(p * (f->gbe_file_size >> 1))); + (size_t)(p * (f->gbe_file_size >> 1))); for (ff = 0; ff < nff; ff++) { @@ -514,22 +532,22 @@ 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), const char *name) { - struct xstate *x = xstatus(0, NULL); - unsigned long i = x->i; + struct xstate *x = xstatus(); + 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 @@ -541,6 +559,6 @@ check_cmd(void (*fn)(void), void cmd_helper_err(void) { - err(ECANCELED, + b0rk(ECANCELED, "Erroneously running command twice"); } diff --git a/util/libreboot-utils/lib/file.c b/util/libreboot-utils/lib/file.c new file mode 100644 index 00000000..552618d6 --- /dev/null +++ b/util/libreboot-utils/lib/file.c @@ -0,0 +1,1065 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> + * + * Pathless i/o, and some stuff you + * probably never saw in userspace. + * + * Be nice to the demon. + */ + +#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" + +/* check that a file changed + */ + +int +same_file(int fd, struct stat *st_old, + int check_size) +{ + struct stat st; + int saved_errno = errno; + + /* TODO: null/-1 checks + * like this can be + * generalised + */ + if (st_old == NULL) { + errno = EFAULT; + goto err_same_file; + } + if (fd < 0) { + errno = EBADF; + goto err_same_file; + } + + if (fstat(fd, &st) == -1) + goto err_same_file; + + if (fd_verify_regular(fd, st_old, &st) < 0) + goto err_same_file; + + if (check_size && + st.st_size != st_old->st_size) + goto err_same_file; + + errno = saved_errno; + return 0; + +err_same_file: + + if (errno == saved_errno) + errno = ESTALE; + + return -1; +} + +/* open() but with abort traps + */ +/* TODO: also support other things here than files. + and then use, throughout the program. + in particular, use of openat might help + (split the path) + (see: link attack mitigations throughout nvmutil) + + make it return, and handle the return value/errno + + (this could return e.g. EINTR) + + TODO: this function is not used by mkhtemp, nor will + it probably be, it's currently used by nvmutil, + for opening intel gbe nvm config files. i can + probably remove it though and unify witth some + of the verification code now used for mkhtemp + +TODO: and don't abort. return -1. and handle in the caller. + +minor obstacle: the mkhtemp code always requires absolute +paths, whereas the gbe editor takes relative paths. + */ +void +xopen(int *fd_ptr, const char *path, int flags, struct stat *st) +{ + if ((*fd_ptr = open(path, flags)) < 0) + err_no_cleanup(0, errno, "%s", path); + + if (fstat(*fd_ptr, st) < 0) + err_no_cleanup(0, errno, "%s: stat", path); + + if (!S_ISREG(st->st_mode)) + 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_no_cleanup(0, errno, "%s: file not seekable", path); +} + +/* fsync() the directory of a file, + * useful for atomic writes + */ + +int +fsync_dir(const char *path) +{ + int saved_errno = errno; + + size_t pathlen = 0; + size_t maxlen = 0; + + char *dirbuf = NULL; + int dirfd = -1; + + char *slash = NULL; + struct stat st = {0}; + + int close_errno; + +#if defined(PATH_LEN) && \ + (PATH_LEN) >= 256 + maxlen = PATH_LEN; +#else + maxlen = 4096; +#endif + + if (if_err(path == NULL, EFAULT) || + if_err_sys(slen(path, maxlen, &pathlen) < 0) || + if_err(pathlen >= maxlen || pathlen < 0, EMSGSIZE) || + if_err(pathlen == 0, EINVAL) + || + if_err_sys((dirbuf = malloc(pathlen + 1)) == NULL)) + goto err_fsync_dir; + + memcpy(dirbuf, path, pathlen + 1); + slash = strrchr(dirbuf, '/'); + + if (slash != NULL) { + *slash = '\0'; + if (*dirbuf == '\0') { + dirbuf[0] = '/'; + dirbuf[1] = '\0'; + } + } else { + dirbuf[0] = '.'; + dirbuf[1] = '\0'; + } + + dirfd = fs_open(dirbuf, + O_RDONLY | O_CLOEXEC | O_NOCTTY +#ifdef O_DIRECTORY + | O_DIRECTORY +#endif +#ifdef O_NOFOLLOW + | O_NOFOLLOW +#endif +); + + if (if_err_sys(dirfd < 0) || + if_err_sys(fstat(dirfd, &st) < 0) || + if_err(!S_ISDIR(st.st_mode), ENOTDIR) + || + if_err_sys(fsync_on_eintr(dirfd) == -1)) + goto err_fsync_dir; + + if (close_on_eintr(dirfd) == -1) { + + dirfd = -1; + goto err_fsync_dir; + } + + free_if_null(&dirbuf); + + errno = saved_errno; + return 0; + +err_fsync_dir: + + if (errno == saved_errno) + errno = EIO; + + free_if_null(&dirbuf); + close_no_err(&dirfd); + + return -1; +} + +/* + * Safe I/O functions wrapping around + * read(), write() and providing a portable + * analog of both pread() and pwrite(). + * These functions are designed for maximum + * robustness, checking NULL inputs, overflowed + * outputs, and all kinds of errors that the + * standard libc functions don't. + * + * Looping on EINTR and EAGAIN is supported. + * EINTR/EAGAIN looping is done indefinitely. + */ + +/* rw_file_exact() - Read perfectly or die + * + * Read/write, and absolutely insist on an + * absolute read; e.g. if 100 bytes are + * requested, this MUST return 100. + * + * This function will never return zero. + * It will only return below (error), + * or above (success). On error, -1 is + * returned and errno is set accordingly. + * + * Zero-byte returns are not allowed. + * It will re-spin a finite number of + * times upon zero-return, to recover, + * otherwise it will return an error. + */ + +ssize_t +rw_file_exact(int fd, unsigned char *mem, size_t nrw, + off_t off, int rw_type, int loop_eagain, + int loop_eintr, size_t max_retries, + int off_reset) +{ + ssize_t rval; + ssize_t rc; + + size_t nrw_cur; + + off_t off_cur; + void *mem_cur; + + size_t retries_on_zero; + + int saved_errno = errno; + + rval = 0; + + rc = 0; + retries_on_zero = 0; + + if (io_args(fd, mem, nrw, off, rw_type) == -1) + goto err_rw_file_exact; + + while (1) { + + /* Prevent theoretical overflow */ + if (rval >= 0 && (size_t)rval > (nrw - rc)) { + errno = EOVERFLOW; + goto err_rw_file_exact; + } + + rc += rval; + if ((size_t)rc >= nrw) + break; + + mem_cur = (void *)(mem + (size_t)rc); + nrw_cur = (size_t)(nrw - (size_t)rc); + + if (off < 0) { + errno = EOVERFLOW; + goto err_rw_file_exact; + } + + off_cur = off + (off_t)rc; + + rval = prw(fd, mem_cur, nrw_cur, off_cur, + rw_type, loop_eagain, loop_eintr, + off_reset); + + if (rval < 0) + goto err_rw_file_exact; + + if (rval == 0) { + if (retries_on_zero++ < max_retries) + continue; + + errno = EIO; + goto err_rw_file_exact; + } + + retries_on_zero = 0; + } + + if ((size_t)rc != nrw) { + + errno = EIO; + goto err_rw_file_exact; + } + + rval = rw_over_nrw(rc, nrw); + if (rval < 0) + goto err_rw_file_exact; + + errno = saved_errno; + + return rval; + +err_rw_file_exact: + + if (errno == saved_errno) + errno = EIO; + + return -1; +} + +/* prw() - portable read-write with more + * safety checks than barebones libc + * + * portable pwrite/pread on request, or real + * pwrite/pread libc functions can be used. + * the portable (non-libc) pread/pwrite is not + * thread-safe, because it does not prevent or + * mitigate race conditions on file descriptors + * + * If you need real pwrite/pread, just compile + * with flag: REAL_POS_IO=1 + * + * A fallback is provided for regular read/write. + * rw_type can be IO_READ (read), IO_WRITE (write), + * IO_PREAD (pread) or IO_PWRITE + * + * loop_eagain does a retry loop on EAGAIN if set + * loop_eintr does a retry loop on EINTR if set + * + * race conditions on non-libc pread/pwrite: + * if a file offset changes, abort, to mitage. + * + * off_reset 1: reset the file offset *once* if + * a change was detected, assuming + * nothing else is touching it now + * off_reset 0: never reset if changed + */ + +ssize_t +prw(int fd, void *mem, size_t nrw, + off_t off, int rw_type, + int loop_eagain, int loop_eintr, + int off_reset) +{ + ssize_t rval; + ssize_t r; + int positional_rw; + struct stat st; +#if !defined(REAL_POS_IO) || \ + REAL_POS_IO < 1 + off_t verified; + off_t off_orig; + off_t off_last; +#endif + int saved_errno = errno; + + if (io_args(fd, mem, nrw, off, rw_type) + == -1) + goto err_prw; + + r = -1; + + /* do not use loop_eagain on + * normal files + */ + + if (!loop_eagain) { + /* check whether the file + * changed + */ + + if (check_file(fd, &st) == -1) + goto err_prw; + } + + if (rw_type >= IO_PREAD) + positional_rw = 1; /* pread/pwrite */ + else + positional_rw = 0; /* read/write */ + +try_rw_again: + + if (!positional_rw) { +#if defined(REAL_POS_IO) && \ + REAL_POS_IO > 0 +real_pread_pwrite: +#endif + if (rw_type == IO_WRITE) + r = write(fd, mem, nrw); + else if (rw_type == IO_READ) + r = read(fd, mem, nrw); +#if defined(REAL_POS_IO) && \ + REAL_POS_IO > 0 + else if (rw_type == IO_PWRITE) + r = pwrite(fd, mem, nrw, off); + else if (rw_type == IO_PREAD) + r = pread(fd, mem, nrw, off); +#endif + + if (r == -1 && (errno == try_err(loop_eintr, EINTR) + || errno == try_err(loop_eagain, EAGAIN))) + goto try_rw_again; + + rval = rw_over_nrw(r, nrw); + if (rval < 0) + goto err_prw; + + errno = saved_errno; + + return rval; + } + +#if defined(REAL_POS_IO) && \ + REAL_POS_IO > 0 + goto real_pread_pwrite; +#else + if ((off_orig = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, + loop_eagain, loop_eintr)) == (off_t)-1) { + r = -1; + } else if (lseek_on_eintr(fd, off, SEEK_SET, + loop_eagain, loop_eintr) == (off_t)-1) { + r = -1; + } else { + verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, + loop_eagain, loop_eintr); + + /* abort if the offset changed, + * indicating race condition. if + * off_reset enabled, reset *ONCE* + */ + + if (off_reset && off != verified) + lseek_on_eintr(fd, off, SEEK_SET, + loop_eagain, loop_eintr); + + do { + /* check offset again, repeatedly. + * even if off_reset is set, this + * aborts if offsets change again + */ + + verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, + loop_eagain, loop_eintr); + + if (off != verified) { + + errno = EBUSY; + goto err_prw; + } + + if (rw_type == IO_PREAD) + r = read(fd, mem, nrw); + else if (rw_type == IO_PWRITE) + r = write(fd, mem, nrw); + + if (rw_over_nrw(r, nrw) == -1) + break; + + } while (r == -1 && + (errno == try_err(loop_eintr, EINTR) || + errno == try_err(loop_eagain, EAGAIN))); + } + + saved_errno = errno; + + off_last = lseek_on_eintr(fd, off_orig, SEEK_SET, + loop_eagain, loop_eintr); + + if (off_last != off_orig) { + errno = saved_errno; + goto err_prw; + } + + errno = saved_errno; + + rval = rw_over_nrw(r, nrw); + if (rval < 0) + goto err_prw; + + errno = saved_errno; + + return rval; + +#endif + +err_prw: + + if (errno == saved_errno) + errno = EIO; + + return -1; +} + +int +io_args(int fd, void *mem, size_t nrw, + off_t off, int rw_type) +{ + int saved_errno = errno; + + if (if_err(mem == NULL, EFAULT) || + if_err(fd < 0, EBADF) || + if_err(off < 0, ERANGE) || + if_err(!nrw, EPERM) || /* TODO: toggle zero-byte check */ + if_err(nrw > (size_t)SSIZE_MAX, ERANGE) || + if_err(((size_t)off + nrw) < (size_t)off, ERANGE) || + if_err(rw_type > IO_PWRITE, EINVAL)) + goto err_io_args; + + errno = saved_errno; + return 0; + +err_io_args: + if (errno == saved_errno) + errno = EINVAL; + + return -1; +} + +int +check_file(int fd, struct stat *st) +{ + int saved_errno = errno; + + if (if_err(fd < 0, EBADF) || + if_err(st == NULL, EFAULT) || + if_err(fstat(fd, st) == -1, 0) || + if_err(!S_ISREG(st->st_mode), EBADF)) + goto err_is_file; + + errno = saved_errno; + return 0; + +err_is_file: + if (errno == saved_errno) + errno = EINVAL; + + return -1; +} + +/* POSIX can say whatever it wants. + * specification != implementation + */ + +ssize_t +rw_over_nrw(ssize_t r, size_t nrw) +{ + int saved_errno = errno; + + if (if_err(!nrw, 0) || + if_err(r == -1, 0) || + if_err((size_t)r > SSIZE_MAX, ERANGE) || + if_err((size_t)r > nrw, ERANGE)) + goto err_rw_over_nrw; + + errno = saved_errno; + return r; + +err_rw_over_nrw: + if (errno == saved_errno) + errno = EIO; + + return -1; +} + +#if !defined(REAL_POS_IO) || \ + REAL_POS_IO < 1 +off_t +lseek_on_eintr(int fd, off_t off, int whence, + int loop_eagain, int loop_eintr) +{ + off_t old; + + old = -1; + + do { + old = lseek(fd, off, whence); + } while (old == (off_t)-1 && ( + errno == try_err(loop_eintr, EINTR) || + errno == try_err(loop_eintr, ETXTBSY) || + errno == try_err(loop_eagain, EAGAIN) || + errno == try_err(loop_eagain, EWOULDBLOCK))); + + return old; +} +#endif + +/* two functions that reduce sloccount by + * two hundred lines... no, now three. */ +int +if_err(int condition, int errval) +{ + if (!condition) + return 0; + + if (errval) + errno = errval; + + return 1; +} +/* technically pointless, but stylistically + * pleasing alongside if_err chains. + * use this one for syscalls that are + * expected to set errno + * also use it for non-system calls + * that act like them, e.g. prw() or + * rw_write_exact() */ +int +if_err_sys(int condition) +{ + if (!condition) + return 0; + return 1; +} +/* errno can never be -1, so you can + * use this to conditionally set an integer + * for comparison. see example in lseek_on_eintr + */ +int +try_err(int loop_err, int errval) +{ + if (loop_err) + return errval; + return -1; +} + +void +free_if_null(char **buf) +{ + if (buf == NULL || *buf == NULL) + return; + + free(*buf); + *buf = NULL; +} + +/* also returns error code */ +int +close_warn(int *fd, char *s) +{ + int saved_errno = errno; + + if (fd == NULL) { + if (s != NULL) + fprintf(stderr, "FAIL: %s: bad fd ptr\n", s); + return -1; + } + + if (*fd < 0 && s != NULL) { + fprintf(stderr, "WARN: %s: already closed\n", s); + } else if (close(*fd) < 0) { + if (s != NULL) + fprintf(stderr, "FAIL: %s: close\n", s); + return -1; + } + + *fd = -1; + errno = saved_errno; + + return 0; +} + +/* TODO: remove this. giant liability. + make close calls always err instead, + when they fail. otherwise we hide bugs! + */ +void +close_no_err(int *fd) +{ + int saved_errno = errno; + + if (fd == NULL || *fd < 0) + return; + + (void) close_on_eintr(*fd); + *fd = -1; + + errno = saved_errno; +} + +/* TODO: make fd a pointer insttead + and automatically reset -1 here */ +/* BUT DO NOT reset -1 on error */ +int +close_on_eintr(int fd) +{ + int r; + int saved_errno = errno; + + do { + r = close(fd); + } while (r == -1 && ( + errno == EINTR || errno == EAGAIN || + errno == EWOULDBLOCK || errno == ETXTBSY)); + + if (r >= 0) + errno = saved_errno; + + return r; +} + +int +fsync_on_eintr(int fd) +{ + int r; + int saved_errno = errno; + + do { + r = fsync(fd); + } while (r == -1 && (errno == EINTR || errno == EAGAIN || + errno == ETXTBSY || errno == EWOULDBLOCK)); + + if (r >= 0) + errno = saved_errno; + + return r; +} + +int +fs_rename_at(int olddirfd, const char *old, + int newdirfd, const char *new) +{ + if (if_err(new == NULL || old == NULL, EFAULT) || + if_err(olddirfd < 0 || newdirfd < 0, EBADF)) + return -1; + + return renameat(olddirfd, old, newdirfd, new); +} + +/* secure open, based on + * relative path to root + * + * always a fixed fd for / + * see: rootfs() + */ +int +fs_open(const char *path, int flags) +{ + struct filesystem *fs; + + if (if_err(path == NULL, EFAULT) || + if_err(path[0] != '/', EINVAL) || + if_err_sys((fs = rootfs()) == NULL)) + return -1; + + return fs_resolve_at(fs->rootfd, path + 1, flags); +} + +/* singleton function + * that returns a fixed + * descriptor of / + * + * used throughout, for + * repeated integrity checks + */ +struct filesystem * +rootfs(void) +{ + static struct filesystem global_fs; + static int fs_initialised = 0; + + if (!fs_initialised) { + + global_fs.rootfd = + open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + + if (global_fs.rootfd < 0) + return NULL; + + fs_initialised = 1; + } + + return &global_fs; +} + +/* filesystem sandboxing. + * (in userspace) + */ +int +fs_resolve_at(int dirfd, const char *path, int flags) +{ + int nextfd = -1; + int curfd; + const char *p; +#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; + + 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; + if (r == 0) + break; + + is_last = (*p == '\0'); + + nextfd = fs_open_component(curfd, name, flags, is_last); + if (nextfd < 0) + goto err; + + /* close previous fd IF it is not the original input */ + if (curfd != dirfd) { + (void) close_on_eintr(curfd); + } + + curfd = nextfd; + nextfd = -1; + } + + errno = saved_errno; + return curfd; + +err: + saved_errno = errno; + + 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; +} + +int +fs_next_component(const char **p, + char *name, size_t namesz) +{ + const char *s = *p; + size_t len = 0; +#if defined(PATH_LEN) && \ +(PATH_LEN) >= 256 + size_t maxlen = PATH_LEN; +#else + size_t maxlen = 4096; +#endif + + while (*s == '/') + s++; + + if (*s == '\0') { + *p = s; + return 0; + } + + while (s[len] != '/' && s[len] != '\0') + len++; + + if (len == 0 || len >= namesz || + len >= maxlen) { + errno = ENAMETOOLONG; + return -1; + } + + memcpy(name, s, len); + name[len] = '\0'; + + /* reject . and .. */ + if ((name[0] == '.' && name[1] == '\0') || + (name[0] == '.' && name[1] == '.' && name[2] == '\0')) { + errno = EPERM; + return -1; + } + + *p = s + len; + return 1; +} + +int +fs_open_component(int dirfd, const char *name, + int flags, int is_last) +{ + int fd; + struct stat st; + + fd = openat2p(dirfd, name, + (is_last ? flags : (O_RDONLY | O_DIRECTORY)) | + O_NOFOLLOW | O_CLOEXEC, (flags & O_CREAT) ? 0600 : 0); + + /* the patient always lies + */ + if (!is_last) { + + if (if_err(fd < 0, EBADF) || + if_err_sys(fstat(fd, &st) < 0)) + return -1; + + if (!S_ISDIR(st.st_mode)) { + + (void) close_on_eintr(fd); + errno = ENOTDIR; + return -1; + } + } + + return fd; +} + +int +fs_dirname_basename(const char *path, + char **dir, char **base, + int allow_relative) +{ + char *buf; + char *slash; + size_t len; + int rval; +#if defined(PATH_LEN) && \ +(PATH_LEN) >= 256 + size_t maxlen = PATH_LEN; +#else + size_t maxlen = 4096; +#endif + + if (path == NULL || dir == NULL || base == NULL || + if_err_sys(slen(path, maxlen, &len) < 0) || + if_err_sys((buf = malloc(len + 1)) == NULL)) + return -1; + + memcpy(buf, path, len + 1); + + /* strip trailing slashes */ + while (len > 1 && buf[len - 1] == '/') + buf[--len] = '\0'; + + slash = strrchr(buf, '/'); + + if (slash) { + + *slash = '\0'; + *dir = buf; + *base = slash + 1; + + if (**dir == '\0') { + (*dir)[0] = '/'; + (*dir)[1] = '\0'; + } + } else if (allow_relative) { + + *dir = strdup("."); + *base = buf; + } else { + errno = EINVAL; + free_if_null(&buf); + return -1; + } + + return 0; +} + +/* portable wrapper for use of openat2 on linux, + * with fallback for others e.g. openbsd + * + * BONUS: arg checks + * TODO: consider EINTR/EAGAIN retry loop + */ +int +openat2p(int dirfd, const char *path, + int flags, mode_t mode) +{ +#ifdef __linux__ + struct open_how how = { + .flags = flags, + .mode = mode, + .resolve = + RESOLVE_BENEATH | + RESOLVE_NO_SYMLINKS | + RESOLVE_NO_MAGICLINKS + }; + int saved_errno = errno; + int rval; +#endif + + if (if_err(dirfd < 0, EBADF) || + if_err(path == NULL, EFAULT)) + return -1; + +retry: + errno = 0; + +#ifdef __linux__ + /* more secure than regular openat, + * but linux-only at the time of writing + */ + rval = syscall(SYS_openat2, dirfd, path, &how, sizeof(how)); +#else + /* less secure, but e.g. openbsd + * doesn't have openat2 yet + */ + rval = openat(dirfd, path, flags, mode); +#endif + if (rval == -1 && ( + errno == EINTR || + errno == EAGAIN || + errno == EWOULDBLOCK || + errno == ETXTBSY)) + goto retry; + + if (rval >= 0) + errno = saved_errno; + + return rval; +} + +int +mkdirat_on_eintr( /* <-- say that 10 times to please the demon */ + int dirfd, + const char *path, mode_t mode) +{ + int saved_errno = errno; + int rval; + + if (if_err(dirfd < 0, EBADF) || + if_err(path == NULL, EFAULT)) + return -1; + +retry: + errno = 0; + rval = mkdirat(dirfd, path, mode); + + if (rval == -1 && ( + errno == EINTR || + errno == EAGAIN || + errno == EWOULDBLOCK || + errno == ETXTBSY)) + goto retry; + + if (rval >= 0) + errno = saved_errno; + + return rval; +} + + + + + + + + + + + diff --git a/util/nvmutil/lib/io.c b/util/libreboot-utils/lib/io.c index 5769dd05..d05adbcc 100644 --- a/util/nvmutil/lib/io.c +++ b/util/libreboot-utils/lib/io.c @@ -21,7 +21,7 @@ void open_gbe_file(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; @@ -29,19 +29,19 @@ open_gbe_file(void) xopen(&f->gbe_fd, f->fname, cmd->flags | O_BINARY | - O_NOFOLLOW | O_CLOEXEC, &f->gbe_st); + 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, (unsigned long)f->gbe_st.st_nlink); + 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,17 +59,17 @@ 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 copy_gbe(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; read_file(); @@ -77,19 +77,19 @@ copy_gbe(void) if (f->gbe_file_size == SIZE_8KB) return; - memcpy(f->buf + (unsigned long)GBE_PART_SIZE, - f->buf + (unsigned long)(f->gbe_file_size >> 1), - (unsigned long)GBE_PART_SIZE); + memcpy(f->buf + (size_t)GBE_PART_SIZE, + f->buf + (size_t)(f->gbe_file_size >> 1), + (size_t)GBE_PART_SIZE); } void read_file(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; struct stat _st; - long _r; + ssize_t _r; /* read main file */ @@ -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,55 +107,55 @@ 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); } void write_gbe_file(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; - unsigned long p; + size_t p; unsigned char update_checksum; if ((cmd->flags & O_ACCMODE) == O_RDONLY) 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; @@ -171,25 +171,25 @@ write_gbe_file(void) } void -rw_gbe_file_part(unsigned long p, int rw_type, +rw_gbe_file_part(size_t p, int rw_type, const char *rw_type_str) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; - long rval; + ssize_t rval; off_t file_offset; - unsigned long gbe_rw_size; + size_t gbe_rw_size; unsigned char *mem_offset; 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", - f->fname, rw_type_str, (unsigned long)p, rw_type); + 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); file_offset = (off_t)gbe_file_offset(p, rw_type_str); @@ -198,18 +198,18 @@ rw_gbe_file_part(unsigned long p, int rw_type, gbe_rw_size, file_offset, rw_type); if (rval == -1) - err(errno, "%s: %s: part %lu", - f->fname, rw_type_str, (unsigned long)p); + b0rk(errno, "%s: %s: part %lu", + f->fname, rw_type_str, (size_t)p); - if ((unsigned long)rval != gbe_rw_size) - err(EIO, "%s: partial %s: part %lu", - f->fname, rw_type_str, (unsigned long)p); + if ((size_t)rval != gbe_rw_size) + b0rk(EIO, "%s: partial %s: part %lu", + f->fname, rw_type_str, (size_t)p); } void write_to_gbe_bin(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; @@ -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,19 +235,12 @@ 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; - if (close_on_eintr(f->tmp_fd) == -1) { - fprintf(stderr, "FAIL: %s: close\n", f->tname); - f->io_err_gbe_bin = 1; - } - - if (close_on_eintr(f->gbe_fd) == -1) { - fprintf(stderr, "FAIL: %s: close\n", f->fname); - f->io_err_gbe_bin = 1; - } + f->io_err_gbe_bin |= -close_warn(&f->tmp_fd, f->tname); + f->io_err_gbe_bin |= -close_warn(&f->gbe_fd, f->fname); errno = saved_errno; @@ -273,11 +266,7 @@ write_to_gbe_bin(void) /* removed by rename */ - - if (f->tname != NULL) - free(f->tname); - - f->tname = NULL; + free_if_null(&f->tname); } } @@ -292,15 +281,15 @@ write_to_gbe_bin(void) } void -check_written_part(unsigned long p) +check_written_part(size_t p) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; - long rval; + ssize_t rval; - unsigned long gbe_rw_size; + size_t gbe_rw_size; off_t file_offset; unsigned char *mem_offset; @@ -318,17 +307,17 @@ check_written_part(unsigned long 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); if (rval == -1) f->rw_check_err_read[p] = f->io_err_gbe = 1; - else if ((unsigned long)rval != gbe_rw_size) + else if ((size_t)rval != gbe_rw_size) f->rw_check_partial_read[p] = f->io_err_gbe = 1; else if (memcmp(mem_offset, f->pad, gbe_rw_size) != 0) f->rw_check_bad_part[p] = f->io_err_gbe = 1; @@ -359,10 +348,10 @@ check_written_part(unsigned long p) void report_io_err_rw(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long p; + size_t p; if (!f->io_err_gbe) return; @@ -374,22 +363,22 @@ report_io_err_rw(void) if (f->rw_check_err_read[p]) fprintf(stderr, "%s: pread: p%lu (post-verification)\n", - f->fname, (unsigned long)p); + f->fname, (size_t)p); if (f->rw_check_partial_read[p]) fprintf(stderr, "%s: partial pread: p%lu (post-verification)\n", - f->fname, (unsigned long)p); + f->fname, (size_t)p); if (f->rw_check_bad_part[p]) fprintf(stderr, "%s: pwrite: corrupt write on p%lu\n", - f->fname, (unsigned long)p); + f->fname, (size_t)p); if (f->rw_check_err_read[p] || f->rw_check_partial_read[p]) { fprintf(stderr, "%s: p%lu: skipped checksum verification " "(because read failed)\n", - f->fname, (unsigned long)p); + f->fname, (size_t)p); continue; } @@ -402,7 +391,7 @@ report_io_err_rw(void) fprintf(stderr, "BAD"); fprintf(stderr, " checksum in p%lu on-disk.\n", - (unsigned long)p); + (size_t)p); if (f->post_rw_checksum[p]) { fprintf(stderr, @@ -415,7 +404,7 @@ report_io_err_rw(void) int gbe_mv(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; int rval; @@ -424,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 */ @@ -435,91 +432,36 @@ 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 - */ - dest_tmp = new_tmpfile(&dest_fd, 1, f->fname); - if (dest_tmp == NULL) - goto ret_gbe_mv; +ret_gbe_mv: - /* copy data + /* TODO: this whole section is bloat. + it can be generalised */ - 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) - goto ret_gbe_mv; - - 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(dest_tmp); - dest_tmp = NULL; - -ret_gbe_mv: if (f->gbe_fd > -1) { - if (close_on_eintr(f->gbe_fd) < 0) + if (close_on_eintr(f->gbe_fd) < 0) { + f->gbe_fd = -1; rval = -1; + } + f->gbe_fd = -1; + if (fsync_dir(f->fname) < 0) { f->io_err_gbe_bin = 1; rval = -1; } - f->gbe_fd = -1; } if (f->tmp_fd > -1) { - if (close_on_eintr(f->tmp_fd) < 0) + if (close_on_eintr(f->tmp_fd) < 0) { + f->tmp_fd = -1; rval = -1; - + } f->tmp_fd = -1; } @@ -552,9 +494,9 @@ ret_gbe_mv: * and it is *also* used during file I/O. */ unsigned char * -gbe_mem_offset(unsigned long p, const char *f_op) +gbe_mem_offset(size_t p, const char *f_op) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; off_t gbe_off; @@ -563,7 +505,7 @@ gbe_mem_offset(unsigned long p, const char *f_op) GBE_PART_SIZE, GBE_WORK_SIZE); return (unsigned char *) - (f->buf + (unsigned long)gbe_off); + (f->buf + (size_t)gbe_off); } /* I/O operations filtered here. These operations must @@ -571,9 +513,9 @@ gbe_mem_offset(unsigned long p, const char *f_op) * within the GbE file, and write 4KB of data. */ off_t -gbe_file_offset(unsigned long p, const char *f_op) +gbe_file_offset(size_t p, const char *f_op) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; off_t gbe_file_half_size; @@ -585,10 +527,10 @@ gbe_file_offset(unsigned long p, const char *f_op) } off_t -gbe_x_offset(unsigned long p, const char *f_op, const char *d_type, +gbe_x_offset(size_t p, const char *f_op, const char *d_type, off_t nsize, off_t ncmp) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; off_t off; @@ -598,24 +540,24 @@ gbe_x_offset(unsigned long 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; } -long -rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw, +ssize_t +rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw, off_t off, int rw_type) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - long r; + ssize_t r; if (io_args(fd, mem, nrw, off, rw_type) == -1) return -1; @@ -624,17 +566,17 @@ rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw, if (mem < f->buf) goto err_rw_gbe_file_exact; - if ((unsigned long)(mem - f->buf) >= GBE_WORK_SIZE) + if ((size_t)(mem - f->buf) >= GBE_WORK_SIZE) goto err_rw_gbe_file_exact; } if (off < 0 || off >= f->gbe_file_size) goto err_rw_gbe_file_exact; - if (nrw > (unsigned long)(f->gbe_file_size - off)) + if (nrw > (size_t)(f->gbe_file_size - off)) goto err_rw_gbe_file_exact; - if (nrw > (unsigned long)GBE_PART_SIZE) + if (nrw > (size_t)GBE_PART_SIZE) goto err_rw_gbe_file_exact; r = rw_file_exact(fd, mem, nrw, off, rw_type, diff --git a/util/libreboot-utils/lib/mkhtemp.c b/util/libreboot-utils/lib/mkhtemp.c new file mode 100644 index 00000000..191d657c --- /dev/null +++ b/util/libreboot-utils/lib/mkhtemp.c @@ -0,0 +1,1103 @@ +/* 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 / 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, + const char *template) +{ + 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, + const char *template) +{ + return new_tmp_common(fd, path, MKHTEMP_DIR, + tmpdir, template); +} + +/* 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 + an actual TMPDIR, set. if set, it + will be using *static* memory and + must not be freed. on success, + a pointer to heap memory is set + instead. + * see: + * env_tmpdir() + * this is for error reports if e.g. + * TMPDIR isn't found (but is set) + * if TMPDIR isn't set, it will + * default to /tmp or /var/tmp + */ +int +new_tmp_common(int *fd, char **path, int type, + char *tmpdir, const char *template) +{ +#if defined(PATH_LEN) && \ + (PATH_LEN) >= 256 + size_t maxlen = PATH_LEN; +#else + size_t maxlen = 4096; +#endif + struct stat st; + + const char *templatestr; + size_t templatestr_len; + + 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; + + char *fail_dir = NULL; + + 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 (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, NULL); +#else + 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; + + if (slen(tmpdir, maxlen, &dirlen) < 0) + goto err; + if (*tmpdir == '\0') + goto err; + if (*tmpdir != '/') + goto err; + + if (template != NULL) + templatestr = template; + else + templatestr = "tmp.XXXXXXXXXX"; + + if (slen(templatestr, maxlen, &templatestr_len) < 0) + goto err; + + /* sizeof adds an extra byte, useful + * because we also want '.' or '/' + */ + destlen = dirlen + 1 + templatestr_len; + 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, templatestr, templatestr_len); + *(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; + + close_no_err(&dirfd); + + errno = saved_errno; + *path = dest; + + return 0; + +err: + + if (errno != saved_errno) + saved_errno = errno; + else + saved_errno = errno = EIO; + + free_if_null(&dest); + + close_no_err(&dirfd); + close_no_err(fd); + + /* where a TMPDIR isn't found, and we err, + * we pass this back through for the + * error message + */ + if (fail_dir != NULL) + *path = fail_dir; + + errno = saved_errno; + return -1; +} + + +/* hardened TMPDIR parsing + */ + +char * +env_tmpdir(int bypass_all_sticky_checks, char **tmpdir, + char *override_tmpdir) +{ + char *t; + int allow_noworld_unsticky; + int saved_errno = errno; + + char tmp[] = "/tmp"; + char vartmp[] = "/var/tmp"; + + /* tmpdir is a user override, if set */ + if (override_tmpdir == NULL) + t = getenv("TMPDIR"); + else + t = override_tmpdir; + + if (t != NULL && *t != '\0') { + + if (tmpdir_policy(t, + &allow_noworld_unsticky) < 0) { + if (tmpdir != NULL) + *tmpdir = t; + return NULL; /* errno already set */ + } + + if (!world_writeable_and_sticky(t, + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + if (tmpdir != NULL) + *tmpdir = t; + return NULL; + } + + errno = saved_errno; + return t; + } + + allow_noworld_unsticky = 0; + + if (world_writeable_and_sticky("/tmp", + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + + if (tmpdir != NULL) + *tmpdir = tmp; + + errno = saved_errno; + return "/tmp"; + } + + if (world_writeable_and_sticky("/var/tmp", + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + + if (tmpdir != NULL) + *tmpdir = vartmp; + + errno = saved_errno; + return "/var/tmp"; + } + + 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) { + + close_no_err(&fd_a); + close_no_err(&fd_b); + +success_same_dir: + + /* SUCCESS + */ + + errno = saved_errno; + return 1; + } + + close_no_err(&fd_a); + close_no_err(&fd_b); + + /* FAILURE (logical) + */ + + errno = saved_errno; + return 0; + +err_same_dir: + + /* FAILURE (probably syscall) + */ + + close_no_err(&fd_a); + close_no_err(&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** + * + * 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; + } + + /* *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 */ + } + + /* 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 + */ + if (allow_noworld_unsticky) + goto sticky_heaven; /* sticky! */ + + goto sticky_hell; /* heaven visa denied */ + +sticky_heaven: +/* i like the one in hamburg better */ + + close_no_err(&dirfd); + errno = saved_errno; + + return 1; + +sticky_hell: + + if (errno == saved_errno) + errno = EPERM; + + saved_errno = errno; + + close_no_err(&dirfd); + + errno = saved_errno; + + return 0; +} + +/* mk(h)temp - hardened mktemp. + * like mkstemp, but (MUCH) harder. + * + * designed to resist TOCTOU attacks + * e.g. directory race / symlink attack + * + * extremely strict and even implements + * some limited userspace-level sandboxing, + * similar in spirit to openbsd unveil, + * though unveil is from kernel space. + * + * 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 (if_err(fd == NULL || template == NULL || fname == NULL || + st_dir_initial == NULL, EFAULT) || + if_err(*fd >= 0, EEXIST) || + if_err(dirfd < 0, EBADF) + || + if_err_sys(slen(template, max_len, &len) < 0) || + if_err(len >= max_len, EMSGSIZE) + || + if_err_sys(slen(fname, max_len, &fname_len) < 0) || + if_err(fname == NULL, EINVAL) || + if_err(strrchr(fname, '/') != NULL, EINVAL)) + return -1; + + for (end = template + len; /* count X */ + end > template && *--end == 'X'; xc++); + + if (if_err(xc < 3 || xc > len, EINVAL) || + if_err(fname_len > len, EOVERFLOW)) + return -1; + + if (if_err(memcmp(fname, template + len - fname_len, + fname_len) != 0, EINVAL)) + return -1; + + if((fname_copy = malloc(fname_len + 1)) == NULL) + goto err; + + /* fname_copy = templatestr 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: + close_no_err(fd); + +success: + free_if_null(&fname_copy); + + return (*fd >= 0) ? *fd : -1; +} + +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 rval = -1; + + int file_created = 0; + int dir_created = 0; + + if (if_err(fd == NULL || st == NULL || p ==NULL || fname_copy ==NULL || + st_dir_initial == NULL, EFAULT) || + if_err(*fd >= 0, EEXIST)) + goto err; + + if (if_err_sys(mkhtemp_fill_random(p, xc) < 0) || + if_err_sys(fd_verify_dir_identity(dirfd, st_dir_initial) < 0)) + 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); + + /* 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; + + if ((*fd = openat2p(dirfd, fname_copy, + O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0)) < 0) + goto err; + + if (if_err_sys(fstat(*fd, &st_open) < 0) || + if_err(!S_ISDIR(st_open.st_mode), ENOTDIR)) + goto err; + + /* NOTE: pointless to check nlink here (only just opened) */ + 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 (if_err(!S_ISDIR(st_open.st_mode), ENOTDIR) || + if_err_sys(is_owner(&st_open) < 0) || + if_err(st_open.st_mode & (S_IWGRP | S_IWOTH), EPERM)) + goto err; + } + + errno = saved_errno; + rval = 1; + goto out; + +err: + close_no_err(fd); + + if (file_created) + (void) unlinkat(dirfd, fname_copy, 0); + if (dir_created) + (void) unlinkat(dirfd, fname_copy, AT_REMOVEDIR); + + rval = -1; +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) +{ + static char ch[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + size_t chx = 0; + size_t r; + + /* clamp rand to prevent modulo bias + */ + size_t limit = ((size_t)-1) - (((size_t)-1) % (sizeof(ch) - 1)); + int saved_errno = errno; + + if (if_err(p == NULL, EFAULT)) + return -1; + + for (chx = 0; chx < xc; chx++) { + +retry_rand: + /* on bsd: uses arc4random + on linux: uses getrandom + *never returns error* + */ + r = rlong(); /* always returns successful */ + if (r >= limit) + goto retry_rand; + + p[chx] = ch[r % (sizeof(ch) - 1)]; + } + + errno = saved_errno; + return 0; + +} + +/* WARNING: **ONCE** per file. + * + * !!! DO NOT RUN TWICE PER FILE. BEWARE OF THE DEMON !!! + * watch out for spikes! + */ +/* TODO: bad_flags can be negative, and is + * ignored if it is. should we err instead? + */ +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; + + if (if_err(fd == NULL || st == NULL, EFAULT) || + if_err(*fd < 0, EBADF) || + if_err_sys((flags = fcntl(*fd, F_GETFL)) == -1) || + if_err(bad_flags > 0 && (flags & bad_flags), EPERM)) + goto err_demons; + + if (expected != NULL) { + if (fd_verify_regular(*fd, expected, st) < 0) + goto err_demons; + } else if (if_err_sys(fstat(*fd, &st_now) == -1) || + if_err(!S_ISREG(st_now.st_mode), EBADF)) { + goto err_demons; /***********/ + } else /* ( >:3 ) */ + *st = st_now; /* /| |\ */ /* don't let him out */ + /* / \ */ + if (check_seek) { /***********/ + if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1) + goto err_demons; + } /* don't release the demon */ + + if (if_err(st->st_nlink != 1, ELOOP) || + if_err(st->st_uid != geteuid() && geteuid() != 0, EPERM) || + if_err_sys(is_owner(st) < 0) || + if_err(st->st_mode & (S_IWGRP | S_IWOTH), 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 ( + if_err_sys(fd_verify_identity(fd, expected, out) < 0) || + if_err(!S_ISREG(out->st_mode), EBADF) + ) return -1; + else + 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( if_err(fd < 0 || expected == NULL, EFAULT) || + if_err_sys(fstat(fd, &st_now)) || + if_err(st_now.st_dev != expected->st_dev || + st_now.st_ino != expected->st_ino, 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 (if_err(fd < 0 || expected == NULL, EFAULT) || + if_err_sys(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 (if_err(fd < 0, EBADF) || + if_err(flags < 0, 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; +} diff --git a/util/libreboot-utils/lib/num.c b/util/libreboot-utils/lib/num.c new file mode 100644 index 00000000..41a08f0b --- /dev/null +++ b/util/libreboot-utils/lib/num.c @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> + * + * Numerical functions. + * NOTE: randomness was moved to rand.c + */ + +/* +TODO: properly handle errno in this file + */ + +#ifdef __OpenBSD__ +#include <sys/param.h> +#endif +#include <sys/types.h> + +#include <errno.h> +#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \ + defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__)) +#include <fcntl.h> /* if not arc4random: /dev/urandom */ +#endif +#include <limits.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> + +#include "../include/common.h" + +/* TODO: + * make this and errno handling more + * flexible + + in particular: + hextonum could be modified to + write into a buffer instead, + with the converted numbers, + of an arbitrary length + */ +unsigned short +hextonum(char ch_s) +{ + int saved_errno = errno; + + /* rlong() can return error, + but preserves errno if no + error. we need to detect + this because it handles + /dev/urandom sometimes + + therefore, if it's zero + at start, we know if there + was an err at the end, by + return value zero, if errno + was set; this is technically + valid, since zero is also + a valid random number! + + it's an edge case that i had + to fix. i'll rewrite the code + better later. for now, it + should be ok. + */ + errno = 0; + + unsigned char ch; + size_t rval; + + ch = (unsigned char)ch_s; + + if ((unsigned int)(ch - '0') <= 9) { + + rval = ch - '0'; + goto hextonum_success; + } + + ch |= 0x20; + + if ((unsigned int)(ch - 'a') <= 5) { + + rval = ch - 'a' + 10; + goto hextonum_success; + } + + if (ch == '?' || ch == 'x') { + + rval = rlong(); + if (errno > 0) + goto err_hextonum; + + goto hextonum_success; + } + + goto err_hextonum; + +hextonum_success: + + errno = saved_errno; + return (unsigned short)rval & 0xf; + +err_hextonum: + + if (errno == saved_errno) + errno = EINVAL; + else + return 17; /* 17 indicates getrandom/urandom fail */ + + return 16; /* invalid character */ + + /* caller just checks >15. */ +} + +void +check_bin(size_t a, const char *a_name) +{ + if (a > 1) + err_no_cleanup(0, EINVAL, "%s must be 0 or 1, but is %lu", + a_name, (size_t)a); +} diff --git a/util/libreboot-utils/lib/rand.c b/util/libreboot-utils/lib/rand.c new file mode 100644 index 00000000..c4cf008c --- /dev/null +++ b/util/libreboot-utils/lib/rand.c @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> + * + * Random number generation + */ + +#ifndef RAND_H +#define RAND_H + +#ifdef __OpenBSD__ +#include <sys/param.h> +#endif +#include <sys/types.h> + +#include <errno.h> +#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \ + defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__)) +#include <fcntl.h> /* if not arc4random: /dev/urandom */ +#endif +#include <limits.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +#include "../include/common.h" + +/* Random numbers + */ + +/* when calling this: save errno + * first, then set errno to zero. + * on error, this function will + * set errno and possibly return + * + * rlong also preserves errno + * and leaves it unchanged on + * success, so if you do it + * right, you can detect error. + * this is because it uses + * /dev/urandom which can err. + * ditto getrandom (EINTR), + * theoretically. + */ + +/* for the linux version: we use only the + * syscall, because we cannot trust /dev/urandom + * to be as robust, and some libc implementations + * may default to /dev/urandom under fault conditions. + * + * for general high reliability, we must abort on + * failure. in practise, it will likely never fail. + * the arc4random call on bsd never returns error. + */ + +size_t +rlong(void) +{ + size_t rval; + int saved_errno = errno; + errno = 0; + +#if (defined(__OpenBSD__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__) || \ + defined(__DragonFly__)) + + arc4random_buf(&rval, sizeof(size_t)); + goto out; + +#elif defined(__linux__) + + size_t off = 0; + size_t len = sizeof(rval); + ssize_t rc; + +retry_rand: + rc = (ssize_t)syscall(SYS_getrandom, + (char *)&rval + off, len - off, 0); + + if (rc < 0) { + if (errno == EINTR || errno == EAGAIN) { + usleep(100); + goto retry_rand; + } + + goto err; /* possibly unsupported by kernel */ + } + + if ((off += (size_t)rc) < len) + goto retry_rand; + + goto out; +err: + /* + * getrandom can return with error, but arc4random + * doesn't. generally, getrandom will be reliable, + * but we of course have to maintain parity with + * BSD. So a rand failure is to be interpreted as + * a major systems failure, and we act accordingly. + */ + err_no_cleanup(1, ECANCELED, + "Randomisation failure, possibly unsupported in your kernel."); + exit(EXIT_FAILURE); + +#else +#error Unsupported operating system (possibly unsecure randomisation) +#endif + +out: + errno = saved_errno; + return rval; +} +#endif diff --git a/util/nvmutil/lib/state.c b/util/libreboot-utils/lib/state.c index 02a3e51c..42d060b7 100644 --- a/util/nvmutil/lib/state.c +++ b/util/libreboot-utils/lib/state.c @@ -23,11 +23,19 @@ #include "../include/common.h" struct xstate * -xstatus(int argc, char *argv[]) +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 = { - /* DO NOT MESS THIS UP, OR THERE WILL BE DEMONS */ { + /* be careful when modifying xstate. you + * must set everything precisely */ { CMD_DUMP, "dump", cmd_helper_dump, ARGC_3, ARG_NOPART, @@ -86,92 +94,60 @@ xstatus(int argc, char *argv[]) }; - static int first_run = 1; - if (!first_run) return &us; + if (argc < 3) + err_no_cleanup(0, EINVAL, "xstart: Too few arguments"); + if (argv == NULL) + err_no_cleanup(0, EINVAL, "xstart: NULL argv"); + + first_run = 0; + us.f.buf = us.f.real_buf; - first_run = 0; us.argv0 = argv[0]; + us.f.fname = argv[1]; - if (argc > 1) - us.f.fname = argv[1]; + us.f.tmp_fd = -1; + us.f.tname = NULL; - if (argc < 3) - usage(); - -/* 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(errno, "pledge plus unveil"); -#elif (OpenBSD) >= 509 - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err(errno, "pledge"); -#endif -#endif + if ((realdir = realpath(us.f.fname, NULL)) == NULL) + err_no_cleanup(0, errno, "xstart: can't get realpath of %s", + us.f.fname); -#ifndef S_ISREG - err(ECANCELED, "Can't determine file types (S_ISREG undefined)"); -#endif + if (fs_dirname_basename(realdir, &dir, &base, 0) < 0) + err_no_cleanup(0, errno, "xstart: don't know CWD of %s", + us.f.fname); -#ifndef CHAR_BIT - err(ECANCELED, "Unknown char size"); -#else - if (CHAR_BIT != 8) - err(EINVAL, "Unsupported char size"); -#endif + if ((us.f.base = strdup(base)) == NULL) + err_no_cleanup(0, errno, "strdup base"); -#if defined(__OpenBSD__) && defined(OpenBSD) && \ - (OpenBSD) >= 604 - /* can only use local tmp on openbsd, due to unveil */ - us.f.tname = new_tmpfile(&us.f.tmp_fd, 1, NULL); -#else - us.f.tname = new_tmpfile(&us.f.tmp_fd, 0, NULL); -#endif - if (us.f.tname == NULL) - err(errno, "Can't create tmpfile"); - if (*us.f.tname == '\0') - err(errno, "tmp dir is an empty string"); + 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 defined(__OpenBSD__) && defined(OpenBSD) && \ - OpenBSD >= 604 - if (unveil(us.f.tname, "rwc") == -1) - err(errno, "unveil rwc: %s", us.f.tname); -#endif - if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0) - err(errno, "%s: stat", us.f.tname); - - sanitize_command_list(); - - /* parse user command */ - set_cmd(argc, argv); - set_cmd_args(argc, argv); - -#if defined(__OpenBSD__) && defined(OpenBSD) && \ - (OpenBSD) >= 604 - if ((us.cmd[i].flags & O_ACCMODE) == O_RDONLY) { - if (unveil(us.f.fname, "r") == -1) - err(errno, "%s: unveil r", us.f.fname); - } else { - if (unveil(us.f.fname, "rwc") == -1) - err(errno, "%s: unveil rw", us.f.fname); - } + if (new_tmpfile(&us.f.tmp_fd, &us.f.tname, dir, ".gbe.XXXXXXXXXX") < 0) + err_no_cleanup(0, errno, "%s", us.f.tname); - if (unveil(us.f.tname, "rwc") == -1) - err(errno, "%s: unveil rwc", us.f.tname); + if (fs_dirname_basename(us.f.tname, + &tmpdir, &tmpbase_local, 0) < 0) + err_no_cleanup(0, errno, "tmp basename"); - if (unveil(NULL, NULL) == -1) - err(errno, "unveil block (rw)"); + us.f.tmpbase = strdup(tmpbase_local); + if (us.f.tmpbase == NULL) + err_no_cleanup(0, errno, "strdup tmpbase"); - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err(errno, "pledge (kill unveil)"); -#endif + free_if_null(&tmpdir); - open_gbe_file(); + if (us.f.tname == NULL) + err_no_cleanup(0, errno, "x->f.tname null"); + if (*us.f.tname == '\0') + err_no_cleanup(0, errno, "x->f.tname empty"); + + if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0) + 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)); @@ -179,16 +155,24 @@ xstatus(int argc, char *argv[]) /* for good measure */ memset(us.f.pad, 0, sizeof(us.f.pad)); - copy_gbe(); - read_checksums(); - return &us; } +struct xstate * +xstatus(void) +{ + struct xstate *x = xstart(0, NULL); + + if (x == NULL) + 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(0, NULL); + struct xstate *x = xstatus(); va_list args; @@ -211,35 +195,10 @@ err(int nvm_errval, const char *msg, ...) exit(EXIT_FAILURE); } -const char * -getnvmprogname(void) -{ - struct xstate *x = xstatus(0, NULL); - - const char *p; - static char fallback[] = "nvmutil"; - - char *rval = fallback; - - if (x != NULL) { - if (x->argv0 == NULL || *x->argv0 == '\0') - return ""; - - rval = x->argv0; - } - - p = strrchr(rval, '/'); - - if (p) - return p + 1; - else - return rval; -} - int exit_cleanup(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f; int close_err; @@ -251,23 +210,17 @@ exit_cleanup(void) if (x != NULL) { f = &x->f; - if (f->gbe_fd > -1) { - if (close_on_eintr(f->gbe_fd) == -1) - close_err = 1; - f->gbe_fd = -1; - } - - if (f->tmp_fd > -1) { - if (close_on_eintr(f->tmp_fd) == -1) - close_err = 1; - } + close_no_err(&f->gbe_fd); + close_no_err(&f->tmp_fd); + close_no_err(&f->tmp_fd); - if (f->tname != NULL) { + if (f->tname != NULL) if (unlink(f->tname) == -1) close_err = 1; - } - f->tmp_fd = -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 new file mode 100644 index 00000000..0329c6c3 --- /dev/null +++ b/util/libreboot-utils/lib/string.c @@ -0,0 +1,284 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> + * + * String functions + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <errno.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <stdint.h> + +#include "../include/common.h" + +/* strict strcmp */ +int +scmp(const char *a, + const char *b, + size_t maxlen, + int *rval) +{ + size_t ch; + unsigned char ac; + unsigned char bc; + + if (a == NULL || + b == NULL || + rval == NULL) { + errno = EFAULT; + goto err; + } + + for (ch = 0; ch < maxlen; ch++) { + + ac = (unsigned char)a[ch]; + bc = (unsigned char)b[ch]; + + if (ac != bc) { + *rval = ac - bc; + return 0; + } + + if (ac == '\0') { + *rval = 0; + return 0; + } + } + +err: + errno = EFAULT; + if (rval != NULL) + *rval = -1; + return -1; +} + +/* strict strlen */ +int +slen(const char *s, + size_t maxlen, + size_t *rval) +{ + size_t ch; + + if (s == NULL || + rval == NULL) { + errno = EFAULT; + goto err; + } + + for (ch = 0; + ch < maxlen && s[ch] != '\0'; + ch++); + + if (ch == maxlen) { + /* unterminated */ + errno = EFAULT; + goto err; + } + + *rval = ch; + return 0; +err: + if (rval != NULL) + *rval = 0; + return -1; +} + +/* strict strdup */ +int +sdup(const char *s, + size_t n, char **dest) +{ + size_t size; + char *rval; + + if (dest == NULL || + slen(s, n, &size) < 0 || + if_err(size == SIZE_MAX, EOVERFLOW) || + (rval = malloc(size + 1)) == NULL) { + + if (dest != NULL) + *dest = NULL; + return -1; + } + + memcpy(rval, s, size); + *(rval + size) = '\0'; + + *dest = rval; + return 0; +} + +/* strict strcat */ +int +scat(const char *s1, const char *s2, + size_t n, char **dest) +{ + size_t size1; + size_t size2; + char *rval; + + if (dest == NULL || + slen(s1, n, &size1) < 0 || + slen(s2, n, &size2) < 0 || + if_err(size1 > SIZE_MAX - size2 - 1, EOVERFLOW) || + (rval = malloc(size1 + size2 + 1)) == NULL) { + + if (dest != NULL) + *dest = NULL; + return -1; + } + + memcpy(rval, s1, size1); + memcpy(rval + size1, s2, size2); + *(rval + size1 + size2) = '\0'; + + *dest = rval; + return 0; +} + +/* strict split/de-cat - off is where + 2nd buffer will start from */ +int +dcat(const char *s, size_t n, + size_t off, char **dest1, + char **dest2) +{ + size_t size; + char *rval1 = NULL; + char *rval2 = NULL; + + if (dest1 == NULL || dest2 == NULL || + slen(s, n, &size) < 0 || + if_err(size == SIZE_MAX, EOVERFLOW) || + if_err(off >= size, EOVERFLOW) || + (rval1 = malloc(off + 1)) == NULL || + (rval2 = malloc(size - off + 1)) == NULL) { + + goto err; + } + + memcpy(rval1, s, off); + *(rval1 + off) = '\0'; + + memcpy(rval2, s + off, size - off); + *(rval2 + size - off) = '\0'; + + *dest1 = rval1; + *dest2 = rval2; + + return 0; + +err: + if (rval1 != NULL) + free(rval1); + if (rval2 != NULL) + free(rval2); + + if (dest1 != NULL) + *dest1 = NULL; + if (dest2 != NULL) + *dest2 = NULL; + + return -1; +} + +/* the one for nvmutil state is in state.c */ +/* this one just exits */ +void +err_no_cleanup(int stfu, int nvm_errval, const char *msg, ...) +{ + va_list args; + int saved_errno = errno; + const char *p; + +#if defined(__OpenBSD__) && defined(OpenBSD) +#if (OpenBSD) >= 509 + if (pledge("stdio", NULL) == -1) + fprintf(stderr, "pledge failure during exit"); +#endif +#endif + if (!errno) + saved_errno = errno = ECANCELED; + + if ((p = getnvmprogname()) != NULL) + fprintf(stderr, "%s: ", p); + + va_start(args, 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); +} + +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/nvmutil/lib/usage.c b/util/libreboot-utils/lib/usage.c index 3b0614e8..2b5a93ca 100644 --- a/util/nvmutil/lib/usage.c +++ b/util/libreboot-utils/lib/usage.c @@ -26,5 +26,5 @@ usage(void) util, util, util, util, util, util, util); - err(EINVAL, "Too few arguments"); + b0rk(EINVAL, "Too few arguments"); } diff --git a/util/nvmutil/lib/word.c b/util/libreboot-utils/lib/word.c index 5d9220c7..6563e67a 100644 --- a/util/nvmutil/lib/word.c +++ b/util/libreboot-utils/lib/word.c @@ -13,12 +13,12 @@ #include "../include/common.h" unsigned short -nvm_word(unsigned long pos16, unsigned long p) +nvm_word(size_t pos16, size_t p) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long pos; + size_t pos; check_nvm_bound(pos16, p); pos = (pos16 << 1) + (p * GBE_PART_SIZE); @@ -28,12 +28,12 @@ nvm_word(unsigned long pos16, unsigned long p) } void -set_nvm_word(unsigned long pos16, unsigned long p, unsigned short val16) +set_nvm_word(size_t pos16, size_t p, unsigned short val16) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long pos; + size_t pos; check_nvm_bound(pos16, p); pos = (pos16 << 1) + (p * GBE_PART_SIZE); @@ -45,9 +45,9 @@ set_nvm_word(unsigned long pos16, unsigned long p, unsigned short val16) } void -set_part_modified(unsigned long p) +set_part_modified(size_t p) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; check_bin(p, "part number"); @@ -55,7 +55,7 @@ set_part_modified(unsigned long p) } void -check_nvm_bound(unsigned long c, unsigned long p) +check_nvm_bound(size_t c, size_t p) { /* Block out of bound NVM access */ @@ -63,6 +63,6 @@ check_nvm_bound(unsigned long c, unsigned long p) check_bin(p, "part number"); if (c >= NVM_WORDS) - err(ECANCELED, "check_nvm_bound: out of bounds %lu", - (unsigned long)c); + b0rk(ECANCELED, "check_nvm_bound: out of bounds %lu", + (size_t)c); } diff --git a/util/libreboot-utils/mkhtemp.c b/util/libreboot-utils/mkhtemp.c new file mode 100644 index 00000000..261227cb --- /dev/null +++ b/util/libreboot-utils/mkhtemp.c @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> + * + * WORK IN PROGRESS (proof of concept), or, v0.0000001 + * + * Mkhtemp - Hardened mktemp. Create files and directories + * randomly as determined by user's TMPDIR, or fallback. It + * attemps to provide mitigation against several TOCTOU-based + * attacks e.g. directory rename / symlink attacks, and it + * 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 + * level options e.g. OBSD pledge where available), constant + * identity/ownership checks on files, MUCH stricter ownership + * restrictions (e.g. enforce sticky bit policy on world- + * writeable tmpdirs), preventing operation on other people's + * files (only your own files) - even root is restricted, + * depending on how the code is compiled. Please read the code. + * + * This is the utility version, which makes use of the also- + * included library. No docs yet - source code are the docs, + * and the (ever evolving, and hardening) specification. + * + * This was written from scratch, for use in nvmutil, and + * it is designed to be portable (BSD, Linux). Patches + * very much welcome. + * + * WARNING: This is MUCH stricter than every other mktemp + * implementation, even more so than mkdtemp or + * the OpenBSD version of mkstemp. It *will* break, + * or more specifically, reveal the flaws in, almost + * every major critical infrastructure, because most + * people already use mktemp extremely insecurely. + * + * This tool is written by me, for me, and also Libreboot, but + * it will be summitted for review to various Linux distros + * and BSD projects once it has reached maturity. + */ + +#if defined(__linux__) && !defined(_GNU_SOURCE) +/* for openat2 on linux */ +#define _GNU_SOURCE 1 +#endif + +#ifdef __OpenBSD__ +#include <sys/param.h> /* pledge(2) */ +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "include/common.h" + +int +main(int argc, char *argv[]) +{ +#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(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) + goto err_usage; +#endif +#endif + + while ((c = + getopt(argc, argv, "qdp:")) != -1) { + + 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: + goto err_usage; + } + } + + 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 < 3) /* the gnu mktemp errs on less than 3 */ + err_no_cleanup(stfu, EINVAL, + "template must have 3 X or more on end (12+ advised"); + } + + /* 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(stfu, errno, "pledge, exit"); +#endif +#endif + + if (s == NULL) + err_no_cleanup(stfu, EFAULT, "bad string initialisation"); + if (*s == '\0') + err_no_cleanup(stfu, EFAULT, "empty string initialisation"); + if (slen(s, maxlen, &len) < 0) + 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()); +}/* + + + ( >:3 ) + /| |\ + / \ + + + + + + */ + + + + + + + + + + + + diff --git a/util/libreboot-utils/nvmutil.c b/util/libreboot-utils/nvmutil.c new file mode 100644 index 00000000..e02f60af --- /dev/null +++ b/util/libreboot-utils/nvmutil.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> + * + * This tool lets you modify Intel GbE NVM (Gigabit Ethernet + * Non-Volatile Memory) images, e.g. change the MAC address. + * These images configure your Intel Gigabit Ethernet adapter. + */ + +#ifdef __OpenBSD__ +/* for pledge/unveil test: + */ +#include <sys/param.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "include/common.h" + +int +main(int argc, char *argv[]) +{ + struct xstate *x; + + struct commands *cmd; + struct xfile *f; + + size_t c; + + if (lbgetprogname(argv[0]) == NULL) + 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(0, errno, "pledge plus unveil, main"); + if (unveil("/dev/null", "r") == -1) + err_no_cleanup(0, errno, "unveil r: /dev/null"); +#elif (OpenBSD) >= 509 + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + err_no_cleanup(0, errno, "pledge, main"); +#endif +#endif + +#ifndef S_ISREG + err_no_cleanup(0, ECANCELED, + "Can't determine file types (S_ISREG undefined)"); +#endif +#if ((CHAR_BIT) != 8) + err_no_cleanup(0, ECANCELED, "Unsupported char size"); +#endif + + x = xstart(argc, argv); + + if (x == NULL) + err_no_cleanup(0, ECANCELED, "NULL state on init"); + + /* parse user command */ +/* TODO: CHECK ACCESSES VIA xstatus() */ + set_cmd(argc, argv); + set_cmd_args(argc, argv); + + cmd = &x->cmd[x->i]; + f = &x->f; + +/* https://man.openbsd.org/pledge.2 + https://man.openbsd.org/unveil.2 */ +#if defined(__OpenBSD__) && defined(OpenBSD) +#if (OpenBSD) >= 604 + + if ((us.cmd[i].flags & O_ACCMODE) == O_RDONLY) { + if (unveil(us.f.fname, "r") == -1) + b0rk(errno, "%s: unveil r", us.f.fname); + } else { + if (unveil(us.f.fname, "rwc") == -1) + b0rk(errno, "%s: unveil rw", us.f.fname); + } + + if (unveil(us.f.tname, "rwc") == -1) + b0rk(errno, "unveil rwc: %s", us.f.tname); + + if (unveil(NULL, NULL) == -1) + b0rk(errno, "unveil block (rw)"); + + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + b0rk(errno, "pledge (kill unveil)"); + +#elif (OpenBSD) >= 509 + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + b0rk(errno, "pledge"); +#endif +#endif + + if (cmd->run == NULL) + b0rk(errno, "Command not set"); + + + sanitize_command_list(); + + open_gbe_file(); + + copy_gbe(); + read_checksums(); + + cmd->run(); + + for (c = 0; c < items(x->cmd); c++) + x->cmd[c].run = cmd_helper_err; + + if ((cmd->flags & O_ACCMODE) == O_RDWR) + write_to_gbe_bin(); + + if (exit_cleanup() == -1) + b0rk(EIO, "%s: close", f->fname); + + if (f->io_err_gbe_bin) + b0rk(EIO, "%s: error writing final file"); + + free_if_null(&f->tname); + + return EXIT_SUCCESS; +} diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c deleted file mode 100644 index b4925ccd..00000000 --- a/util/nvmutil/lib/file.c +++ /dev/null @@ -1,890 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - */ - -#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> - -#include "../include/common.h" - -/* check that a file changed - */ - -int -same_file(int fd, struct stat *st_old, - int check_size) -{ - struct stat st; - int saved_errno = errno; - - if (st_old == NULL || fd < 0) - goto err_same_file; - - if (fstat(fd, &st) == -1) - return -1; - - if (st.st_dev != st_old->st_dev || - st.st_ino != st_old->st_ino || - !S_ISREG(st.st_mode)) - goto err_same_file; - - if (check_size && - st.st_size != st_old->st_size) - goto err_same_file; - - errno = saved_errno; - return 0; - -err_same_file: - - errno = EIO; - return -1; -} - -/* open() but with abort traps - */ - -void -xopen(int *fd_ptr, const char *path, int flags, struct stat *st) -{ - if ((*fd_ptr = open(path, flags)) == -1) - err(errno, "%s", path); - - if (fstat(*fd_ptr, st) == -1) - err(errno, "%s: stat", path); - - if (!S_ISREG(st->st_mode)) - err(errno, "%s: not a regular file", path); - - if (lseek(*fd_ptr, 0, SEEK_CUR) == (off_t)-1) - err(errno, "%s: file not seekable", path); -} - -/* fsync() the directory of a file, - * useful for atomic writes - */ - -int -fsync_dir(const char *path) -{ - int saved_errno = errno; - - unsigned long pathlen; - unsigned long maxlen; - - char *dirbuf; - int dirfd; - - char *slash; - - struct stat st; - -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - maxlen = PATH_LEN; -#else - maxlen = 1024; -#endif - - dirbuf = NULL; - dirfd = -1; - - pathlen = xstrxlen(path, maxlen); - - if (pathlen >= maxlen) { - fprintf(stderr, "Path too long for fsync_parent_dir\n"); - goto err_fsync_dir; - } - - if (pathlen == 0) - { - errno = EINVAL; - goto err_fsync_dir; - } - - dirbuf = malloc(pathlen + 1); - if (dirbuf == NULL) - goto err_fsync_dir; - - memcpy(dirbuf, path, pathlen + 1); - slash = strrchr(dirbuf, '/'); - - if (slash != NULL) { - *slash = '\0'; - if (*dirbuf == '\0') { - dirbuf[0] = '/'; - dirbuf[1] = '\0'; - } - } else { - dirbuf[0] = '.'; - dirbuf[1] = '\0'; - } - - dirfd = open(dirbuf, O_RDONLY -#ifdef O_DIRECTORY - | O_DIRECTORY -#endif -#ifdef O_NOFOLLOW - | O_NOFOLLOW -#endif - ); - if (dirfd == -1) - goto err_fsync_dir; - - if (fstat(dirfd, &st) < 0) - goto err_fsync_dir; - - if (!S_ISDIR(st.st_mode)) { - fprintf(stderr, "%s: not a directory\n", dirbuf); - goto err_fsync_dir; - } - - /* sync file on disk */ - if (fsync_on_eintr(dirfd) == -1) - goto err_fsync_dir; - - if (close_on_eintr(dirfd) == -1) - goto err_fsync_dir; - - if (dirbuf != NULL) - free(dirbuf); - - errno = saved_errno; - return 0; - -err_fsync_dir: - if (!errno) - errno = EIO; - - if (errno != saved_errno) - fprintf(stderr, "%s: %s\n", path, strerror(errno)); - - if (dirbuf != NULL) - free(dirbuf); - - if (dirfd > -1) - close_on_eintr(dirfd); - - errno = saved_errno; - - return -1; -} - -/* returns ptr to path (string). if local>0: - * make tmpfile in the same directory as the - * file. if local==0, use TMPDIR - * - * if local==0, the 3rd argument is ignored - */ - -char * -new_tmpfile(int *fd, int local, const char *path) -{ - unsigned long maxlen; - struct stat st; - - /* please do not modify the - * strings or I will get mad - */ - char tmp_none[] = ""; - char tmp_default[] = "/tmp"; - char default_tmpname[] = "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; - char *tmpname; - - char *base = NULL; - char *dest = NULL; - - unsigned long tmpdir_len = 0; - unsigned long tmpname_len = 0; - unsigned long tmppath_len = 0; - - int fd_tmp = -1; - int flags; - -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - maxlen = PATH_LEN; -#else - maxlen = 1024; -#endif - - tmpname = default_tmpname; - if (local) { - if (path == NULL) - goto err_new_tmpfile; - if (*path == '\0') - goto err_new_tmpfile; - - if (stat(path, &st) == -1) - goto err_new_tmpfile; - - if (!S_ISREG(st.st_mode)) - goto err_new_tmpfile; - - tmpname = (char *)path; - } - - if (local) { - base = tmp_none; - - /* appended to filename for tmp: - */ - tmpdir_len = xstrxlen(default_tmpname, maxlen); - } else { - base = get_tmpdir(); - - if (base == NULL) - base = tmp_default; - if (*base == '\0') - base = tmp_default; - - tmpdir_len = xstrxlen(base, maxlen); - } - - tmpname_len = xstrxlen(tmpname, maxlen); - - tmppath_len = tmpdir_len + tmpname_len; - ++tmppath_len; /* for '/' or '.' */ - - /* max length -1 of maxlen - * for termination - */ - if (tmpdir_len > maxlen - tmpname_len - 1) - goto err_new_tmpfile; - - /* +1 for NULL */ - dest = malloc(tmppath_len + 1); - if (dest == NULL) - goto err_new_tmpfile; - - if (local) { - - *dest = '.'; /* hidden file */ - - memcpy(dest + (unsigned long)1, tmpname, tmpname_len); - - memcpy(dest + (unsigned long)1 + tmpname_len, - default_tmpname, tmpdir_len); - } else { - - memcpy(dest, base, tmpdir_len); - - dest[tmpdir_len] = '/'; - - memcpy(dest + tmpdir_len + 1, tmpname, tmpname_len); - } - - dest[tmppath_len] = '\0'; - - fd_tmp = mkstemp_n(dest); - if (fd_tmp == -1) - goto err_new_tmpfile; - - if (fchmod(fd_tmp, 0600) == -1) - goto err_new_tmpfile; - - flags = fcntl(fd_tmp, F_GETFL); - - if (flags == -1) - goto err_new_tmpfile; - - /* - * O_APPEND would permit offsets - * to be ignored, which breaks - * positional read/write - */ - if (flags & O_APPEND) - goto err_new_tmpfile; - - if (lock_file(fd_tmp, flags) == -1) - goto err_new_tmpfile; - - if (fstat(fd_tmp, &st) == -1) - goto err_new_tmpfile; - - /* - * Extremely defensive - * likely pointless checks - */ - - /* check if it's a file */ - if (!S_ISREG(st.st_mode)) - goto err_new_tmpfile; - - /* check if it's seekable */ - if (lseek(fd_tmp, 0, SEEK_CUR) == (off_t)-1) - goto err_new_tmpfile; - - /* tmpfile has >1 hardlinks */ - if (st.st_nlink > 1) - goto err_new_tmpfile; - - /* tmpfile unlinked while opened */ - if (st.st_nlink == 0) - goto err_new_tmpfile; - - *fd = fd_tmp; - - return dest; - -err_new_tmpfile: - - if (dest != NULL) - free(dest); - - if (fd_tmp > -1) - close_on_eintr(fd_tmp); - - return NULL; -} - -int -lock_file(int fd, int flags) -{ - struct flock fl; - - 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) - return -1; - - return 0; -} - -/* return TMPDIR, or fall back - * to portable defaults - */ - -char * -get_tmpdir(void) -{ - char *t; - struct stat st; - - t = getenv("TMPDIR"); - - if (t && *t) { - - if (stat(t, &st) == 0 && S_ISDIR(st.st_mode)) { - - if ((st.st_mode & S_IWOTH) && !(st.st_mode & S_ISVTX)) - return NULL; - - return t; - } - } - - if (stat("/tmp", &st) == 0 && S_ISDIR(st.st_mode)) - return "/tmp"; - - if (stat("/var/tmp", &st) == 0 && S_ISDIR(st.st_mode)) - return "/var/tmp"; - - return "."; -} - -/* portable mkstemp - */ - -int -mkstemp_n(char *template) -{ - int fd; - unsigned long i, j; - unsigned long len; - char *p; - - unsigned long xc = 0; - - static char ch[] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - - unsigned long r; -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - unsigned long max_len = PATH_LEN; -#else - unsigned long max_len = 4096; -#endif - - len = xstrxlen(template, max_len); - - if (len < 6) { - errno = EINVAL; - return -1; - } - - p = template + len; - - while (p > template && p[-1] == 'X') { - --p; - ++xc; - } - - if (xc < 6) { - errno = EINVAL; - return -1; - } - - for (i = 0; i < 200; i++) { - - for (j = 0; j < xc; j++) { - - r = rlong(); - - p[j] = ch[(unsigned long)(r >> 1) % (sizeof(ch) - 1)]; - } - - fd = open(template, - O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600); - - if (fd >= 0) - return fd; - - if (errno != EEXIST) - return -1; - } - - errno = EEXIST; - return -1; -} - -/* - * Safe I/O functions wrapping around - * read(), write() and providing a portable - * analog of both pread() and pwrite(). - * These functions are designed for maximum - * robustness, checking NULL inputs, overflowed - * outputs, and all kinds of errors that the - * standard libc functions don't. - * - * Looping on EINTR and EAGAIN is supported. - * EINTR/EAGAIN looping is done indefinitely. - */ - -/* rw_file_exact() - Read perfectly or die - * - * Read/write, and absolutely insist on an - * absolute read; e.g. if 100 bytes are - * requested, this MUST return 100. - * - * This function will never return zero. - * It will only return below (error), - * or above (success). On error, -1 is - * returned and errno is set accordingly. - * - * Zero-byte returns are not allowed. - * It will re-spin a finite number of - * times upon zero-return, to recover, - * otherwise it will return an error. - */ - -long -rw_file_exact(int fd, unsigned char *mem, unsigned long nrw, - off_t off, int rw_type, int loop_eagain, - int loop_eintr, unsigned long max_retries, - int off_reset) -{ - long rval; - long rc; - - unsigned long nrw_cur; - - off_t off_cur; - void *mem_cur; - - unsigned long retries_on_zero; - - rval = 0; - - rc = 0; - retries_on_zero = 0; - - if (io_args(fd, mem, nrw, off, rw_type) == -1) - return -1; - - while (1) { - - /* Prevent theoretical overflow */ - if (rval >= 0 && (unsigned long)rval > (nrw - rc)) - goto err_rw_file_exact; - - rc += rval; - if ((unsigned long)rc >= nrw) - break; - - mem_cur = (void *)(mem + (unsigned long)rc); - nrw_cur = (unsigned long)(nrw - (unsigned long)rc); - if (off < 0) - goto err_rw_file_exact; - off_cur = off + (off_t)rc; - - rval = prw(fd, mem_cur, nrw_cur, off_cur, - rw_type, loop_eagain, loop_eintr, - off_reset); - - if (rval < 0) - return -1; - - if (rval == 0) { - if (retries_on_zero++ < max_retries) - continue; - goto err_rw_file_exact; - } - - retries_on_zero = 0; - } - - if ((unsigned long)rc != nrw) - goto err_rw_file_exact; - - return rw_over_nrw(rc, nrw); - -err_rw_file_exact: - errno = EIO; - return -1; -} - -/* prw() - portable read-write with more - * safety checks than barebones libc - * - * portable pwrite/pread on request, or real - * pwrite/pread libc functions can be used. - * the portable (non-libc) pread/pwrite is not - * thread-safe, because it does not prevent or - * mitigate race conditions on file descriptors - * - * If you need real pwrite/pread, just compile - * with flag: HAVE_REAL_PREAD_PWRITE=1 - * - * A fallback is provided for regular read/write. - * rw_type can be IO_READ (read), IO_WRITE (write), - * IO_PREAD (pread) or IO_PWRITE - * - * loop_eagain does a retry loop on EAGAIN if set - * loop_eintr does a retry loop on EINTR if set - * - * race conditions on non-libc pread/pwrite: - * if a file offset changes, abort, to mitage. - * - * off_reset 1: reset the file offset *once* if - * a change was detected, assuming - * nothing else is touching it now - * off_reset 0: never reset if changed - */ - -long -prw(int fd, void *mem, unsigned long nrw, - off_t off, int rw_type, - int loop_eagain, int loop_eintr, - int off_reset) -{ - long r; - int positional_rw; - struct stat st; -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 - int saved_errno; - off_t verified; - off_t off_orig; - off_t off_last; -#endif - - if (io_args(fd, mem, nrw, off, rw_type) - == -1) { - return -1; - } - - r = -1; - - /* do not use loop_eagain on - * normal files - */ - - if (!loop_eagain) { - /* check whether the file - * changed - */ - - if (check_file(fd, &st) == -1) - return -1; - } - - if (rw_type >= IO_PREAD) - positional_rw = 1; /* pread/pwrite */ - else - positional_rw = 0; /* read/write */ - -try_rw_again: - - if (!positional_rw) { -#if defined(HAVE_REAL_PREAD_PWRITE) && \ - HAVE_REAL_PREAD_PWRITE > 0 -real_pread_pwrite: -#endif - if (rw_type == IO_WRITE) - r = write(fd, mem, nrw); - else if (rw_type == IO_READ) - r = read(fd, mem, nrw); -#if defined(HAVE_REAL_PREAD_PWRITE) && \ - HAVE_REAL_PREAD_PWRITE > 0 - else if (rw_type == IO_PWRITE) - r = pwrite(fd, mem, nrw, off); - else if (rw_type == IO_PREAD) - r = pread(fd, mem, nrw, off); -#endif - - if (r == -1 && (errno == try_err(loop_eintr, EINTR) - || errno == try_err(loop_eagain, EAGAIN))) - goto try_rw_again; - - return rw_over_nrw(r, nrw); - } - -#if defined(HAVE_REAL_PREAD_PWRITE) && \ - HAVE_REAL_PREAD_PWRITE > 0 - goto real_pread_pwrite; -#else - if ((off_orig = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, - loop_eagain, loop_eintr)) == (off_t)-1) { - r = -1; - } else if (lseek_on_eintr(fd, off, SEEK_SET, - loop_eagain, loop_eintr) == (off_t)-1) { - r = -1; - } else { - verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, - loop_eagain, loop_eintr); - - /* abort if the offset changed, - * indicating race condition. if - * off_reset enabled, reset *ONCE* - */ - - if (off_reset && off != verified) - lseek_on_eintr(fd, off, SEEK_SET, - loop_eagain, loop_eintr); - - do { - /* check offset again, repeatedly. - * even if off_reset is set, this - * aborts if offsets change again - */ - - verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, - loop_eagain, loop_eintr); - - if (off != verified) - goto err_prw; - - if (rw_type == IO_PREAD) - r = read(fd, mem, nrw); - else if (rw_type == IO_PWRITE) - r = write(fd, mem, nrw); - - if (rw_over_nrw(r, nrw) == -1) { - errno = EIO; - break; - } - - } while (r == -1 && - (errno == try_err(loop_eintr, EINTR) || - errno == try_err(loop_eagain, EAGAIN))); - } - - saved_errno = errno; - - off_last = lseek_on_eintr(fd, off_orig, SEEK_SET, - loop_eagain, loop_eintr); - - if (off_last != off_orig) { - errno = saved_errno; - goto err_prw; - } - - errno = saved_errno; - - return rw_over_nrw(r, nrw); -#endif - -err_prw: - errno = EIO; - return -1; -} - -int -io_args(int fd, void *mem, unsigned long nrw, - off_t off, int rw_type) -{ - /* obviously */ - if (mem == NULL) - goto err_io_args; - - /* uninitialised fd */ - if (fd < 0) - goto err_io_args; - - /* negative offset */ - if (off < 0) - goto err_io_args; - - /* prevent zero-byte rw */ - if (!nrw) - goto err_io_args; - - /* prevent overflow */ - if (nrw > (unsigned long)X_LONG_MAX) - goto err_io_args; - - /* prevent overflow */ - if (((unsigned long)off + nrw) < (unsigned long)off) - goto err_io_args; - - if (rw_type > IO_PWRITE) - goto err_io_args; - - return 0; - -err_io_args: - errno = EIO; - return -1; -} - -int -check_file(int fd, struct stat *st) -{ - if (fstat(fd, st) == -1) - goto err_is_file; - - if (!S_ISREG(st->st_mode)) - goto err_is_file; - - return 0; - -err_is_file: - errno = EIO; - return -1; -} - -/* POSIX can say whatever it wants. - * specification != implementation - */ - -long -rw_over_nrw(long r, unsigned long nrw) -{ - /* not a libc bug, but we - * don't like the number zero - */ - if (!nrw) - goto err_rw_over_nrw; - - if (r == -1) - return r; - - if ((unsigned long) - r > X_LONG_MAX) { - - /* Theoretical buggy libc - * check. Extremely academic. - * - * Specifications never - * allow this return value - * to exceed SSIZE_T, but - * spec != implementation - * - * Check this after using - * [p]read() or [p]write() - * - * NOTE: here, we assume - * long integers are the - * same size as SSIZE_T - */ - - goto err_rw_over_nrw; - } - - /* Theoretical buggy libc: - * Should never return a number of - * bytes above the requested length. - */ - if ((unsigned long)r > nrw) - goto err_rw_over_nrw; - - return r; - -err_rw_over_nrw: - - errno = EIO; - return -1; -} - -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 -off_t -lseek_on_eintr(int fd, off_t off, int whence, - int loop_eagain, int loop_eintr) -{ - off_t old; - - old = -1; - - do { - old = lseek(fd, off, whence); - } while (old == (off_t)-1 && ( - errno == try_err(loop_eintr, EINTR) || - errno == try_err(loop_eagain, EAGAIN))); - - return old; -} -#endif - -int -try_err(int loop_err, int errval) -{ - if (loop_err) - return errval; - - return -1; -} - -int -close_on_eintr(int fd) -{ - int r; - int saved_errno = errno; - - do { - r = close(fd); - } while (r == -1 && errno == EINTR); - - if (r > -1) - errno = saved_errno; - - return r; -} - -int -fsync_on_eintr(int fd) -{ - int r; - - do { - r = fsync(fd); - } while (r == -1 && errno == EINTR); - - return r; -} diff --git a/util/nvmutil/lib/num.c b/util/nvmutil/lib/num.c deleted file mode 100644 index bbb5a83e..00000000 --- a/util/nvmutil/lib/num.c +++ /dev/null @@ -1,349 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - * - * Numerical functions. - */ - -#ifdef __OpenBSD__ -#include <sys/param.h> -#endif -#include <sys/types.h> -#if defined(FALLBACK_RAND_1989) && \ - (FALLBACK_RAND_1989) > 0 -#include <sys/time.h> -#endif - -#include <errno.h> -#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \ - defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__APPLE__)) -#include <fcntl.h> /* if not arc4random: /dev/urandom */ -#endif -#include <limits.h> -#include <stddef.h> -#include <string.h> -#if defined(FALLBACK_RAND_1989) && \ - (FALLBACK_RAND_1989) > 0 -#include <time.h> -#endif -#include <unistd.h> - -#include "../include/common.h" - -unsigned short -hextonum(char ch_s) -{ - unsigned char ch; - - ch = (unsigned char)ch_s; - - if ((unsigned int)(ch - '0') <= 9) - return ch - '0'; - - ch |= 0x20; - - if ((unsigned int)(ch - 'a') <= 5) - return ch - 'a' + 10; - - if (ch == '?' || ch == 'x') - return (unsigned short)rlong() & 0xf; - - return 16; /* invalid character */ -} - -/* Random numbers - */ - -unsigned long -rlong(void) -{ -#if !(defined(FALLBACK_RAND_1989) && \ - ((FALLBACK_RAND_1989) > 0)) -#if (defined(__OpenBSD__) && (OpenBSD) >= 201) || \ - defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__APPLE__) - - unsigned long rval; - arc4random_buf(&rval, sizeof(unsigned long)); - - return rval; -#else - static int fd = -1; - static long nr = -1; - static unsigned long off = 0; -#if defined (BUFSIZ) - static char rbuf[BUFSIZ]; -#else -#ifndef PORTABLE - static char rbuf[4096]; -#elif ((PORTABLE) > 0) - static char rbuf[256]; /* scarce memory on old systems */ -#else - static char rbuf[4096]; /* typical 32-bit BUFSIZ */ -#endif -#endif - unsigned long rval; - long new_nr; - - int retries = 0; - int max_retries = 100; - -#if defined(__linux__) -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) - - /* linux getrandom() - * - * we *can* use arc4random on - * modern linux, but not on - * every libc. better use the - * official linux function - * - * similar benefits to arc4random - * e.g. works in chroot, blocks - * until it has enough entropy, - * and works even when /dev/urandom - * is available (doesn't use it); - * it's generally more reliable - */ - - if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0) - return rval; - - /* - * now fall back to urandom if getrandom failed: - */ -#endif -#endif - - /* reading from urandom is inherently - * unreliable on old systems, even if - * newer systems make it more reliable - * - * modern linux/bsd make it safe, but - * we have to assume that someone is - * compiling this on linux from 1999 - * - * this logic therefore applies various - * tricks to mitigate possible os bugs - */ - -retry_urandom_read: - - if (++retries > max_retries) - goto rlong_next; - - if (nr < 0 || nr < (long)sizeof(unsigned long)) { - - if (fd < 0) { - - fd = open("/dev/urandom", - O_RDONLY | O_BINARY | O_NOFOLLOW | - O_CLOEXEC); - -#ifdef USE_OLD_DEV_RANDOM -#if (USE_OLD_DEV_RANDOM) > 0 - /* WARNING: - * /dev/random may block - * forever and does **NOT** - * guarantee better entropy - * on old systems - * - * only use it if needed - */ - - if (fd < 0) - fd = open("/dev/random", - O_RDONLY | O_BINARY | O_NOFOLLOW | - O_CLOEXEC); -#endif -#endif - - if (fd < 0) - goto retry_urandom_read; - - retries = 0; - } - - new_nr = rw_file_exact(fd, (unsigned char *)rbuf, - sizeof(rbuf), 0, IO_READ, LOOP_EAGAIN, - LOOP_EINTR, MAX_ZERO_RW_RETRY, OFF_ERR); - - if (new_nr < 0 || new_nr < (long)sizeof(rbuf)) - goto retry_urandom_read; - - /* only reset buffer after successful refill */ - nr = new_nr; - off = 0; - - /* to mitigate file descriptor - * injection, we do not re-use - * the same descriptor each time - */ - (void) close_on_eintr(fd); - fd = -1; - } - - fd = -1; - retries = 0; - - memcpy(&rval, rbuf + off, sizeof(unsigned long)); - - nr -= (long)sizeof(unsigned long); - off += sizeof(unsigned long); - - return rval; - -rlong_next: - - fd = -1; - off = 0; - nr = -1; - - err(EIO, "Can't read from /dev/[ua]random"); - return 0; - -#endif -#else /* FALLBACK_RAND_1989 */ - /* your computer is from a museum - */ - unsigned long mix = 0; - int nr; - - /* 100 times, for entropy - */ - for (nr = 0; nr < 100; nr++) - mix ^= fallback_rand_1989(); - - /* 101 times ;) - */ - return fallback_rand_1989(); -#endif -} - -#if !(defined(FALLBACK_RAND_1989) && \ - ((FALLBACK_RAND_1989) > 0)) -#if defined(__linux__) -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) -int -fallback_rand_getrandom(void *buf, unsigned long len) -{ - unsigned long off = 0; - long rval = -1; - - if (!len) - return -1; - - if (buf == NULL) - return -1; - -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) - - while (off < len) { - -#if defined(HAVE_GETRANDOM) - rval = (long)getrandom((char *)buf + off, len - off, 0); -#elif defined(HAVE_GETRANDOM_SYSCALL) - rval = (long)syscall(SYS_getrandom, - (char *)buf + off, len - off, 0); -#endif - - if (rval < 0) { - if (errno == EINTR) - continue; - - return -1; /* unsupported by kernel */ - } - - off += (unsigned long)rval; - } - - return 0; - -#else - (void)buf; - (void)len; - - return -1; -#endif -} -#endif -#endif -#else -/* nobody should use this - * (not crypto-safe) - */ -unsigned long -fallback_rand_1989(void) -{ - static unsigned long mix = 0; - static unsigned long counter = 0; - - struct timeval tv; - - gettimeofday(&tv, NULL); - - mix ^= (unsigned long)tv.tv_sec - ^ (unsigned long)tv.tv_usec - ^ (unsigned long)getpid() - ^ (unsigned long)&mix - ^ counter++ - ^ entropy_jitter(); - - /* - * Stack addresses can vary between - * calls, thus increasing entropy. - */ - mix ^= (unsigned long)&mix; - mix ^= (unsigned long)&tv; - mix ^= (unsigned long)&counter; - - return mix; -} - -unsigned long -entropy_jitter(void) -{ - unsigned long mix; - - struct timeval a, b; - long mix_diff; - - int c; - - mix = 0; - - gettimeofday(&a, NULL); - - for (c = 0; c < 32; c++) { - - getpid(); - gettimeofday(&b, NULL); - - /* - * prevent negative numbers to prevent overflow, - * which would bias rand to large numbers - */ - mix_diff = (long)(b.tv_usec - a.tv_usec); - if (mix_diff < 0) - mix_diff = -mix_diff; - - mix ^= (unsigned long)(mix_diff); - - mix ^= (unsigned long)&mix; - - } - - return mix; -} -#endif - -void -check_bin(unsigned long a, const char *a_name) -{ - if (a > 1) - err(EINVAL, "%s must be 0 or 1, but is %lu", - a_name, (unsigned long)a); -} diff --git a/util/nvmutil/lib/string.c b/util/nvmutil/lib/string.c deleted file mode 100644 index b1a5c3e2..00000000 --- a/util/nvmutil/lib/string.c +++ /dev/null @@ -1,75 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - * - * String functions - */ - -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <stddef.h> -#include <unistd.h> - -#include "../include/common.h" - -/* Portable strncmp() that blocks - * NULL/empty/unterminated strings - */ - -int -xstrxcmp(const char *a, const char *b, unsigned long maxlen) -{ - unsigned long i; - - if (a == NULL || b == NULL) - err(EINVAL, "NULL input to xstrxcmp"); - - if (*a == '\0' || *b == '\0') - err(EINVAL, "Empty string in xstrxcmp"); - - for (i = 0; i < maxlen; i++) { - - unsigned char ac = (unsigned char)a[i]; - unsigned char bc = (unsigned char)b[i]; - - if (ac == '\0' || bc == '\0') { - if (ac == bc) - return 0; - return ac - bc; - } - - if (ac != bc) - return ac - bc; - } - - err(EINVAL, "Unterminated string in xstrxcmp"); - - errno = EINVAL; - return -1; -} - -/* Portable strncmp() that blocks - * NULL/empty/unterminated strings - */ - -unsigned long -xstrxlen(const char *scmp, unsigned long maxlen) -{ - unsigned long xstr_index; - - if (scmp == NULL) - err(EINVAL, "NULL input to xstrxlen"); - - if (*scmp == '\0') - err(EINVAL, "Empty string in xstrxlen"); - - for (xstr_index = 0; - xstr_index < maxlen && scmp[xstr_index] != '\0'; - xstr_index++); - - if (xstr_index == maxlen) - err(EINVAL, "Unterminated string in xstrxlen"); - - return xstr_index; -} diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c deleted file mode 100644 index 670b7110..00000000 --- a/util/nvmutil/nvmutil.c +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> - * - * This tool lets you modify Intel GbE NVM (Gigabit Ethernet - * Non-Volatile Memory) images, e.g. change the MAC address. - * These images configure your Intel Gigabit Ethernet adapter. - */ - -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <stddef.h> -#include <stdlib.h> - -#include "include/common.h" - -int -main(int argc, char *argv[]) -{ - struct xstate *x = xstatus(argc, argv); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - unsigned long c; - - if (cmd->run == NULL) - err(errno, "Command not set"); - - cmd->run(); - - for (c = 0; c < items(x->cmd); c++) - x->cmd[c].run = cmd_helper_err; - - if ((cmd->flags & O_ACCMODE) == O_RDWR) - write_to_gbe_bin(); - - if (exit_cleanup() == -1) - err(EIO, "%s: close", f->fname); - - if (f->io_err_gbe_bin) - err(EIO, "%s: error writing final file"); - - if (f->tname != NULL) - free(f->tname); - - return EXIT_SUCCESS; -} |
