diff options
| author | Leah Rowe <leah@libreboot.org> | 2026-03-20 04:02:51 +0000 |
|---|---|---|
| committer | Leah Rowe <leah@libreboot.org> | 2026-03-25 12:32:57 +0000 |
| commit | 210922bc9174bcce3444f9bc2782b033622b4c70 (patch) | |
| tree | 7153f7b848bf1ebc60baae09f4384c7a43d83d0a | |
| parent | f50ffd6bb13c04cb185fb6311f8875582bf18388 (diff) | |
This will also be used in lbmk itself at some point,
which currently just uses regular mktemp, for tmpdir
handling during the build process.
Renamed util/nvmutil to util/libreboot-utils, which
now contains two tools. The new tool, mkhtemp, is a
hardened implementation of mktemp, which nvmutil
also uses now. Still experimental, but good enough
for nvmutil.
Mkhtemp attempts to provide TOCTOU resistance on
Linux, by using modern features in Linux such as
Openat2 (syscall) with O_EXCL and O_TMPFILE,
and many various security checks e.g.
inode/dev during creation. Checks are done constantly,
to try to detect race conditions. The code is very
strict about things like sticky bits in world writeable
directories, also ownership (it can be made to bar even
root access on files and directories it doesn't own).
It's a security-first implementation of mktemp, likely
even more secure than the OpenBSD mkstemp, but more
auditing and testing is needed - more features are
also planned, including a compatibility mode to make
it also work like traditional mktemp/mkstemp. The
intention, once this becomes stable, is that it will
become a modern drop-in replacement for mkstemp on
Linux and BSD systems.
Some legacy code has been removed, and in general
cleaned up. I wrote mkhtemp for nvmutil, as part of
its atomic write behaviour, but mktemp was the last
remaining liability, so I rewrote that too!
Docs/manpage/website will be made for mkhtemp once
the code is mature.
Other changes have also been made. This is from another
experimental branch of Libreboot, that I'm pushing
early. For example, nvmutil's state machine has been
tidied up, moving more logic back into main.
Mktemp is historically prone to race conditions,
e.g. symlink attacks, directory replacement, remounting
during operation, all sorts of things. Mkhtemp has
been written to solve, or otherwise mitigate, that
problem. Mkhtemp is currently experimental and will
require a major cleanup at some point, but it
already works well enough, and you can in fact use
it; at this time, the -d, -p and -q flags are
supported, and you can add a custom template at
the end, e.g.
mkhtemp -p test -d
Eventually, I will make this have complete parity
with the GNU and BSD implementations, so that it is
fully useable on existing setups, while optionally
providing the hardening as well.
A lot of code has also been tidied up. I didn't
track the changes I made with this one, because
it was a major re-write of nvmutil; it is now
libreboot-utils, and I will continue to write
more programs in here over time. It's basically
now a bunch of hardened wrappers around various
libc functions, e.g. there is also a secure I/O
wrapper for read/write.
There is a custom randomisation function, rlong,
which simply uses arc4random or getrandom, on
BSD and Linux respectively. Efforts are made to
make it as reliable as possible, to the extent
that it never returns with failure; in the unlikely
event that it fails, it aborts. It also sleeps
between failure, to mitigate certain DoS attacks.
You can just go in util/libreboot-utils and
type make, then you will have the nvmutil and
mkhtemp binaries, which you can just use. It
all works. Everything was massively rewritten.
Signed-off-by: Leah Rowe <leah@libreboot.org>
| -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; -} |
