From f2544d094ba88e1cfbb7993ad67444852cfd5efd Mon Sep 17 00:00:00 2001 From: Leah Rowe Date: Tue, 24 Mar 2026 00:28:15 +0000 Subject: util/mkhtemp: new utility (hardened mktemp) part of the same code library as nvmutil. as part of this, i renamed util/nvmutil to util/libreboot-utils/ because it is now a multi-utility codebase. this is more efficient, since i also wish to use mkhtemp (function) in nvmutil. Signed-off-by: Leah Rowe --- include/inject.sh | 6 +- util/libreboot-utils/.gitignore | 6 + util/libreboot-utils/AUTHORS | 2 + util/libreboot-utils/COPYING | 21 + util/libreboot-utils/Makefile | 136 ++++ util/libreboot-utils/include/common.h | 610 ++++++++++++++++++ util/libreboot-utils/lib/checksum.c | 108 ++++ util/libreboot-utils/lib/command.c | 563 ++++++++++++++++ util/libreboot-utils/lib/file.c | 1136 +++++++++++++++++++++++++++++++++ util/libreboot-utils/lib/io.c | 673 +++++++++++++++++++ util/libreboot-utils/lib/mkhtemp.c | 1133 ++++++++++++++++++++++++++++++++ util/libreboot-utils/lib/num.c | 444 +++++++++++++ util/libreboot-utils/lib/state.c | 252 ++++++++ util/libreboot-utils/lib/string.c | 146 +++++ util/libreboot-utils/lib/usage.c | 30 + util/libreboot-utils/lib/word.c | 68 ++ util/libreboot-utils/mkhtemp.c | 139 ++++ util/libreboot-utils/nvmutil.c | 124 ++++ util/nvmutil/.gitignore | 5 - util/nvmutil/AUTHORS | 2 - util/nvmutil/COPYING | 21 - util/nvmutil/Makefile | 118 ---- util/nvmutil/include/common.h | 606 ------------------ util/nvmutil/lib/checksum.c | 108 ---- util/nvmutil/lib/command.c | 563 ---------------- util/nvmutil/lib/file.c | 1136 --------------------------------- util/nvmutil/lib/io.c | 673 ------------------- util/nvmutil/lib/mkhtemp.c | 1133 -------------------------------- util/nvmutil/lib/num.c | 444 ------------- util/nvmutil/lib/state.c | 279 -------- util/nvmutil/lib/string.c | 114 ---- util/nvmutil/lib/usage.c | 30 - util/nvmutil/lib/word.c | 68 -- util/nvmutil/nvmutil.c | 134 ---- 34 files changed, 5594 insertions(+), 5437 deletions(-) create mode 100644 util/libreboot-utils/.gitignore create mode 100644 util/libreboot-utils/AUTHORS create mode 100644 util/libreboot-utils/COPYING create mode 100644 util/libreboot-utils/Makefile create mode 100644 util/libreboot-utils/include/common.h create mode 100644 util/libreboot-utils/lib/checksum.c create mode 100644 util/libreboot-utils/lib/command.c create mode 100644 util/libreboot-utils/lib/file.c create mode 100644 util/libreboot-utils/lib/io.c create mode 100644 util/libreboot-utils/lib/mkhtemp.c create mode 100644 util/libreboot-utils/lib/num.c create mode 100644 util/libreboot-utils/lib/state.c create mode 100644 util/libreboot-utils/lib/string.c create mode 100644 util/libreboot-utils/lib/usage.c create mode 100644 util/libreboot-utils/lib/word.c create mode 100644 util/libreboot-utils/mkhtemp.c create mode 100644 util/libreboot-utils/nvmutil.c delete mode 100644 util/nvmutil/.gitignore delete mode 100644 util/nvmutil/AUTHORS delete mode 100644 util/nvmutil/COPYING delete mode 100644 util/nvmutil/Makefile delete mode 100644 util/nvmutil/include/common.h delete mode 100644 util/nvmutil/lib/checksum.c delete mode 100644 util/nvmutil/lib/command.c delete mode 100644 util/nvmutil/lib/file.c delete mode 100644 util/nvmutil/lib/io.c delete mode 100644 util/nvmutil/lib/mkhtemp.c delete mode 100644 util/nvmutil/lib/num.c delete mode 100644 util/nvmutil/lib/state.c delete mode 100644 util/nvmutil/lib/string.c delete mode 100644 util/nvmutil/lib/usage.c delete mode 100644 util/nvmutil/lib/word.c delete mode 100644 util/nvmutil/nvmutil.c 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/libreboot-utils/.gitignore b/util/libreboot-utils/.gitignore new file mode 100644 index 00000000..fbf110f9 --- /dev/null +++ b/util/libreboot-utils/.gitignore @@ -0,0 +1,6 @@ +/nvm +/nvmutil +/mkhtemp +*.bin +*.o +*.d diff --git a/util/libreboot-utils/AUTHORS b/util/libreboot-utils/AUTHORS new file mode 100644 index 00000000..f38ea210 --- /dev/null +++ b/util/libreboot-utils/AUTHORS @@ -0,0 +1,2 @@ +Leah Rowe +Riku Viitanen diff --git a/util/libreboot-utils/COPYING b/util/libreboot-utils/COPYING new file mode 100644 index 00000000..47c35a86 --- /dev/null +++ b/util/libreboot-utils/COPYING @@ -0,0 +1,21 @@ +Copyright (C) 2022-2026 Leah Rowe +Copyright (c) 2023 Riku Viitanen + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/util/libreboot-utils/Makefile b/util/libreboot-utils/Makefile new file mode 100644 index 00000000..f741d3f5 --- /dev/null +++ b/util/libreboot-utils/Makefile @@ -0,0 +1,136 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2022,2026 Leah Rowe +# Copyright (c) 2023 Riku Viitanen + +# Makefile for nvmutil, which is an application +# that modifies Intel GbE NVM configurations. + +CC = cc +HELLCC = clang + +CFLAGS = +LDFLAGS = +DESTDIR = +PREFIX = /usr/local +INSTALL = install + +.SUFFIXES: .c .o + +LDIR = + +PORTABLE = $(LDIR) $(CFLAGS) +WARN = $(PORTABLE) -Wall -Wextra +STRICT = $(WARN) -std=c90 -pedantic -Werror +HELLFLAGS = $(STRICT) -Weverything + +PROG = nvmutil +PROGMKH = mkhtemp + +OBJS_NVMUTIL = \ + obj/nvmutil.o \ + obj/lib/state.o \ + obj/lib/file.o \ + obj/lib/string.o \ + obj/lib/usage.o \ + obj/lib/command.o \ + obj/lib/num.o \ + obj/lib/io.o \ + obj/lib/checksum.o \ + obj/lib/word.o \ + obj/lib/mkhtemp.o + +OBJS_MKHTEMP = \ + obj/mkhtemp.o \ + obj/lib/file.o \ + obj/lib/string.o \ + obj/lib/num.o \ + obj/lib/mkhtemp.o + +# default mode +CFLAGS_MODE = $(PORTABLE) +CC_MODE = $(CC) + +all: $(PROG) $(PROGMKH) + +$(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_NVMUTIL): obj +$(OBJS_MKHTEMP): obj + +obj: + mkdir obj || true + mkdir obj/lib || true + +# main program object + +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 + $(CC_MODE) $(CFLAGS_MODE) -c lib/state.c -o obj/lib/state.o + +obj/lib/file.o: lib/file.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/file.c -o obj/lib/file.o + +obj/lib/string.o: lib/string.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/string.c -o obj/lib/string.o + +obj/lib/usage.o: lib/usage.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/usage.c -o obj/lib/usage.o + +obj/lib/command.o: lib/command.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/command.c -o obj/lib/command.o + +obj/lib/num.o: lib/num.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/num.c -o obj/lib/num.o + +obj/lib/io.o: lib/io.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/io.c -o obj/lib/io.o + +obj/lib/checksum.o: lib/checksum.c + $(CC_MODE) $(CFLAGS_MODE) -c lib/checksum.c -o obj/lib/checksum.o + +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 + +# install + +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) $(PROGMKH) $(OBJS_NVMUTIL) $(OBJS_MKHTEMP) + +distclean: clean + +# mode targets (portable replacement for ifeq) + +warn: + $(MAKE) CFLAGS_MODE="$(WARN)" + +strict: + $(MAKE) CFLAGS_MODE="$(STRICT)" + +hell: + $(MAKE) CFLAGS_MODE="$(HELLFLAGS)" CC_MODE="$(HELLCC)" diff --git a/util/libreboot-utils/include/common.h b/util/libreboot-utils/include/common.h new file mode 100644 index 00000000..5d6405bc --- /dev/null +++ b/util/libreboot-utils/include/common.h @@ -0,0 +1,610 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2022-2026 Leah Rowe + + 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 +#include +#include + +/* for linux getrandom + */ +#if defined(__linux__) +#include +#if defined(__has_include) +#if __has_include() +#include +#define HAVE_GETRANDOM 1 +#endif +#endif +#if !defined(HAVE_GETRANDOM) +#include +#if defined(SYS_getrandom) +#define HAVE_GETRANDOM_SYSCALL 1 +#endif +#endif + +#endif + +#define items(x) (sizeof((x)) / sizeof((x)[0])) + +/* system prototypes + */ + +int fchmod(int fd, mode_t mode); + +#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 SSIZE_MAX +#define SSIZE_MAX ((ssize_t)(~((ssize_t)1 << (sizeof(ssize_t)*CHAR_BIT-1)))) +#endif + + +/* build config + */ + +#ifndef NVMUTIL_H +#define NVMUTIL_H + +#define MAX_CMD_LEN 50 + +#ifndef PATH_LEN +#define PATH_LEN 4096 +#endif + +#define OFF_ERR 0 +#ifndef OFF_RESET +#define OFF_RESET 1 +#endif + +#ifndef S_ISVTX +#define S_ISVTX 01000 +#endif + +#if defined(S_IFMT) && ((S_ISVTX & S_IFMT) != 0) +#error "Unexpected bit layout" +#endif + +#ifndef MAX_ZERO_RW_RETRY +#define MAX_ZERO_RW_RETRY 5 +#endif + +#ifndef REAL_POS_IO +#define REAL_POS_IO 0 +#endif + +#ifndef LOOP_EAGAIN +#define LOOP_EAGAIN 1 +#endif +#ifndef LOOP_EINTR +#define LOOP_EINTR 1 +#endif + +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif + +#ifndef EXIT_FAILURE +#define EXIT_FAILURE 1 +#endif + +#ifndef EXIT_SUCCESS +#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 + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef O_EXCL +#define O_EXCL 0 +#endif + +#ifndef O_CREAT +#define O_CREAT 0 +#endif + +#ifndef O_NONBLOCK +#define O_NONBLOCK 0 +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif + +#ifndef FD_CLOEXEC +#define FD_CLOEXEC 0 +#endif + +/* Sizes in bytes: + */ + +#define SIZE_1KB 1024 +#define SIZE_4KB (4 * SIZE_1KB) +#define SIZE_8KB (8 * SIZE_1KB) +#define SIZE_16KB (16 * SIZE_1KB) +#define SIZE_128KB (128 * SIZE_1KB) + +#define GBE_BUF_SIZE (SIZE_128KB) + +/* First 128 bytes of gbe.bin is NVM. + * Then extended area. All of NVM must + * add up to BABA, truncated (LE) + * + * First 4KB of each half of the file + * contains NVM+extended. + */ + +#define GBE_WORK_SIZE (SIZE_8KB) +#define GBE_PART_SIZE (GBE_WORK_SIZE >> 1) +#define NVM_CHECKSUM 0xBABA +#define NVM_SIZE 128 +#define NVM_WORDS (NVM_SIZE >> 1) +#define NVM_CHECKSUM_WORD (NVM_WORDS - 1) + +/* argc minimum (dispatch) + */ + +#define ARGC_3 3 +#define ARGC_4 4 + +#define NO_LOOP_EAGAIN 0 +#define NO_LOOP_EINTR 0 + +/* For checking if an fd is a normal file. + * Portable for old Unix e.g. v7 (S_IFREG), + * 4.2BSD (S_IFMT), POSIX (S_ISREG). + * + * IFREG: assumed 0100000 (classic bitmask) + */ + +#ifndef S_ISREG +#if defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#elif defined(S_IFREG) +#define S_ISREG(m) (((m) & S_IFREG) != 0) +#else +#error "can't determine types with stat()" +#endif +#endif + +#define IO_READ 0 +#define IO_WRITE 1 +#define IO_PREAD 2 +#define IO_PWRITE 3 + +/* for nvmutil commands + */ + +#define CMD_DUMP 0 +#define CMD_SETMAC 1 +#define CMD_SWAP 2 +#define CMD_COPY 3 +#define CMD_CAT 4 +#define CMD_CAT16 5 +#define CMD_CAT128 6 + +#define ARG_NOPART 0 +#define ARG_PART 1 + +#define SKIP_CHECKSUM_READ 0 +#define CHECKSUM_READ 1 + +#define SKIP_CHECKSUM_WRITE 0 +#define CHECKSUM_WRITE 1 + +/* command table + */ + +struct commands { + size_t chk; + char *str; + void (*run)(void); + int argc; + unsigned char arg_part; + unsigned char chksum_read; + unsigned char chksum_write; + size_t rw_size; /* within the 4KB GbE part */ + int flags; /* e.g. O_RDWR or O_RDONLY */ +}; + +/* mac address + */ + +struct macaddr { + char *str; /* set to rmac, or argv string */ + char rmac[18]; /* xx:xx:xx:xx:xx:xx */ + unsigned short mac_buf[3]; +}; + +/* gbe.bin and tmpfile + */ + +struct xfile { + int gbe_fd; + struct stat gbe_st; + + int tmp_fd; + struct stat tmp_st; + + char *tname; /* path of tmp file */ + char *fname; /* path of gbe file */ + + unsigned char *buf; /* work memory for files */ + + int io_err_gbe; /* intermediary write (verification) */ + int io_err_gbe_bin; /* final write (real file) */ + int rw_check_err_read[2]; + int rw_check_partial_read[2]; + int rw_check_bad_part[2]; + + int post_rw_checksum[2]; + + off_t gbe_file_size; + off_t gbe_tmp_size; + + size_t part; + unsigned char part_modified[2]; + unsigned char part_valid[2]; + + unsigned char real_buf[GBE_BUF_SIZE]; + unsigned char bufcmp[GBE_BUF_SIZE]; /* compare gbe/tmp/reads */ + + unsigned char pad[GBE_WORK_SIZE]; /* the file that wouldn't die */ +}; + +/* Command table, MAC address, files + * + * BE CAREFUL when editing this + * to ensure that you also update + * the tables in xstatus() + */ + +struct xstate { + struct commands cmd[7]; + struct macaddr mac; + struct xfile f; + + char *argv0; + + size_t i; /* index to cmd[] for current command */ + int no_cmd; + + /* Cat commands set this. + the cat cmd helpers check it */ + int cat; +}; + +struct filesystem { + int rootfd; +}; + +struct xstate *xstart(int argc, char *argv[]); +struct xstate *xstatus(void); + +/* Sanitize command tables. + */ + +void sanitize_command_list(void); +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[]); +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); + +/* Read GbE file and verify checksums + */ + +void copy_gbe(void); +void read_file(void); +void read_checksums(void); +int good_checksum(size_t partnum); + +/* validate commands + */ + +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); +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); + +/* numerical functions + */ + +unsigned short hextonum(char ch_s); +size_t 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 +size_t fallback_rand_1989(void); +size_t entropy_jitter(void); +#endif + +/* Helper functions for command: dump + */ + +void cmd_helper_dump(void); +void print_mac_from_nvm(size_t partnum); +void hexdump(size_t partnum); + +/* Helper functions for command: swap + */ + +void cmd_helper_swap(void); + +/* Helper functions for command: copy + */ + +void cmd_helper_copy(void); + +/* Helper functions for commands: + * cat, cat16 and cat128 + */ + +void cmd_helper_cat(void); +void cmd_helper_cat16(void); +void cmd_helper_cat128(void); +void cat(size_t nff); +void cat_buf(unsigned char *b); + +/* Command verification/control + */ + +void check_cmd(void (*fn)(void), const char *name); +void cmd_helper_err(void); + +/* Write GbE files to disk + */ + +void write_gbe_file(void); +void set_checksum(size_t part); +unsigned short calculated_checksum(size_t p); + +/* NVM read/write + */ + +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(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(size_t p); +void report_io_err_rw(void); +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); +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); +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, + 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, size_t nrw, + off_t off, int rw_type); +int check_file(int fd, struct stat *st); +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 +int try_err(int loop_err, int errval); + +/* Error handling and cleanup + */ + +void usage(void); +void err_no_cleanup(int nvm_errval, const char *msg, ...); +void err(int nvm_errval, const char *msg, ...); +int exit_cleanup(void); +const char *getnvmprogname(void); + +/* libc hardening + */ + +int new_tmpfile(int *fd, char **path); +int new_tmpdir(int *fd, char **path); +int new_tmp_common(int *fd, char **path, int type); +int 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(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); +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); +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); + +/* asserts */ + +/* type asserts */ +typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1]; +typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1]; +typedef char static_assert_unsigned_char_is_1[ + (sizeof(unsigned char) == 1) ? 1 : -1]; +typedef char static_assert_unsigned_short_is_2[ + (sizeof(unsigned short) >= 2) ? 1 : -1]; +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_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_ssize_t_ptr[ + (sizeof(size_t) >= sizeof(void *)) ? 1 : -1 +]; + +/* + * We set _FILE_OFFSET_BITS 64, but we only handle + * but we only need smaller files, so require 4-bytes. + * Some operating systems ignore the define, hence assert: + */ +typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1]; + +/* + * asserts (variables/defines sanity check) + */ +typedef char assert_argc3[(ARGC_3==3)?1:-1]; +typedef char assert_argc4[(ARGC_4==4)?1:-1]; +typedef char assert_read[(IO_READ==0)?1:-1]; +typedef char assert_write[(IO_WRITE==1)?1:-1]; +typedef char assert_pread[(IO_PREAD==2)?1:-1]; +typedef char assert_pwrite[(IO_PWRITE==3)?1:-1]; +typedef char assert_pathlen[(PATH_LEN>=256)?1:-1]; +/* commands */ +typedef char assert_cmd_dump[(CMD_DUMP==0)?1:-1]; +typedef char assert_cmd_setmac[(CMD_SETMAC==1)?1:-1]; +typedef char assert_cmd_swap[(CMD_SWAP==2)?1:-1]; +typedef char assert_cmd_copy[(CMD_COPY==3)?1:-1]; +typedef char assert_cmd_cat[(CMD_CAT==4)?1:-1]; +typedef char assert_cmd_cat16[(CMD_CAT16==5)?1:-1]; +typedef char assert_cmd_cat128[(CMD_CAT128==6)?1:-1]; +/* bool */ +typedef char bool_arg_nopart[(ARG_NOPART==0)?1:-1]; +typedef char bool_arg_part[(ARG_PART==1)?1:-1]; +typedef char bool_skip_checksum_read[(SKIP_CHECKSUM_READ==0)?1:-1]; +typedef char bool_checksum_read[(CHECKSUM_READ==1)?1:-1]; +typedef char bool_skip_checksum_write[(SKIP_CHECKSUM_WRITE==0)?1:-1]; +typedef char bool_checksum_write[(CHECKSUM_WRITE==1)?1:-1]; +typedef char bool_loop_eintr[(LOOP_EINTR==1||LOOP_EINTR==0)?1:-1]; +typedef char bool_loop_eagain[(LOOP_EAGAIN==1||LOOP_EAGAIN==0)?1:-1]; +typedef char bool_no_loop_eintr[(NO_LOOP_EINTR==0)?1:-1]; +typedef char bool_no_loop_eagain[(NO_LOOP_EAGAIN==0)?1:-1]; +typedef char bool_off_err[(OFF_ERR==0)?1:-1]; +typedef char bool_off_reset[(OFF_RESET==0||OFF_RESET==1)?1:-1]; + +#endif +#endif diff --git a/util/libreboot-utils/lib/checksum.c b/util/libreboot-utils/lib/checksum.c new file mode 100644 index 00000000..b417dc7e --- /dev/null +++ b/util/libreboot-utils/lib/checksum.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2022-2026 Leah Rowe + * + * Functions related to GbE NVM checksums. + */ + +#include +#include + +#include +#include +#include +#include + +#include "../include/common.h" + +void +read_checksums(void) +{ + struct xstate *x = xstatus(); + struct commands *cmd = &x->cmd[x->i]; + struct xfile *f = &x->f; + + size_t _p; + size_t _skip_part; + + unsigned char _num_invalid; + unsigned char _max_invalid; + + f->part_valid[0] = 0; + f->part_valid[1] = 0; + + if (!cmd->chksum_read) + return; + + _num_invalid = 0; + _max_invalid = 2; + + if (cmd->arg_part) + _max_invalid = 1; + + /* Skip verification on this part, + * but only when arg_part is set. + */ + _skip_part = f->part ^ 1; + + for (_p = 0; _p < 2; _p++) { + + /* Only verify a part if it was *read* + */ + if (cmd->arg_part && (_p == _skip_part)) + continue; + + f->part_valid[_p] = good_checksum(_p); + if (!f->part_valid[_p]) + ++_num_invalid; + } + + if (_num_invalid >= _max_invalid) { + + if (_max_invalid == 1) + err(ECANCELED, "%s: part %lu has a bad checksum", + f->fname, (size_t)f->part); + + err(ECANCELED, "%s: No valid checksum found in file", + f->fname); + } +} + +int +good_checksum(size_t partnum) +{ + unsigned short expected_checksum; + unsigned short actual_checksum; + + expected_checksum = + calculated_checksum(partnum); + + actual_checksum = + nvm_word(NVM_CHECKSUM_WORD, partnum); + + if (expected_checksum == actual_checksum) { + return 1; + } else { + return 0; + } +} + +void +set_checksum(size_t p) +{ + check_bin(p, "part number"); + set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p)); +} + +unsigned short +calculated_checksum(size_t p) +{ + size_t c; + unsigned int val16; + + val16 = 0; + + for (c = 0; c < NVM_CHECKSUM_WORD; c++) + val16 += (unsigned int)nvm_word(c, p); + + return (unsigned short)((NVM_CHECKSUM - val16) & 0xffff); +} diff --git a/util/libreboot-utils/lib/command.c b/util/libreboot-utils/lib/command.c new file mode 100644 index 00000000..3a863d23 --- /dev/null +++ b/util/libreboot-utils/lib/command.c @@ -0,0 +1,563 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2022-2026 Leah Rowe + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../include/common.h" + +/* Guard against regressions by maintainers (command table) + */ + +void +sanitize_command_list(void) +{ + struct xstate *x = xstatus(); + + size_t c; + size_t num_commands; + + num_commands = items(x->cmd); + + for (c = 0; c < num_commands; c++) + sanitize_command_index(c); +} + +void +sanitize_command_index(size_t c) +{ + struct xstate *x = xstatus(); + struct commands *cmd = &x->cmd[c]; + + int _flag; + 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", + (size_t)c, cmd->argc); + + if (cmd->str == NULL) + err(EINVAL, "cmd index %lu: NULL str", + (size_t)c); + + if (*cmd->str == '\0') + err(EINVAL, "cmd index %lu: empty str", + (size_t)c); + + if (slen(cmd->str, MAX_CMD_LEN +1, &rval) < 0) + err(errno, "Could not get command length"); + + if (rval > MAX_CMD_LEN) { + err(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", + (size_t)c); + + check_bin(cmd->arg_part, "cmd.arg_part"); + check_bin(cmd->chksum_read, "cmd.chksum_read"); + check_bin(cmd->chksum_write, "cmd.chksum_write"); + + gbe_rw_size = cmd->rw_size; + + switch (gbe_rw_size) { + case GBE_PART_SIZE: + case NVM_SIZE: + break; + default: + err(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", + (size_t)gbe_rw_size); + + _flag = (cmd->flags & O_ACCMODE); + + if (_flag != O_RDONLY && + _flag != O_RDWR) + err(EINVAL, "invalid cmd.flags setting"); +} + +void +set_cmd(int argc, char *argv[]) +{ + struct xstate *x = xstatus(); + const char *cmd; + + int rval; + + size_t c; + + for (c = 0; c < items(x->cmd); c++) { + + cmd = x->cmd[c].str; + + if (scmp(argv[2], cmd, MAX_CMD_LEN, &rval) < 0) + err_no_cleanup(EINVAL, + "could not compare command strings"); + if (rval != 0) + continue; /* not the right command */ + + /* valid command found */ + if (argc >= x->cmd[c].argc) { + x->no_cmd = 0; + x->i = c; /* set command */ + + return; + } + + err_no_cleanup(EINVAL, + "Too few args on command '%s'", cmd); + } + + x->no_cmd = 1; +} + +void +set_cmd_args(int argc, char *argv[]) +{ + struct xstate *x = xstatus(); + size_t i = x->i; + struct commands *cmd = &x->cmd[i]; + struct xfile *f = &x->f; + + if (!valid_command(i) || argc < 3) + usage(); + + if (x->no_cmd) + usage(); + + /* Maintainer bug + */ + if (cmd->arg_part && argc < 4) + err(EINVAL, + "arg_part set for command that needs argc4"); + + if (cmd->arg_part && i == CMD_SETMAC) + err(EINVAL, + "arg_part set on CMD_SETMAC"); + + if (i == CMD_SETMAC) { + + if (argc >= 4) + x->mac.str = argv[3]; + else + x->mac.str = x->mac.rmac; + + } else if (cmd->arg_part) { + + f->part = conv_argv_part_num(argv[3]); + } +} + +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); + + /* char signedness is implementation-defined + */ + ch = (unsigned char)part_str[0]; + if (ch < '0' || ch > '1') + err(EINVAL, "Bad part number (%c)", ch); + + return (size_t)(ch - '0'); +} + +void +check_command_num(size_t c) +{ + if (!valid_command(c)) + err(EINVAL, "Invalid run_cmd arg: %lu", + (size_t)c); +} + +unsigned char +valid_command(size_t c) +{ + struct xstate *x = xstatus(); + struct commands *cmd; + + if (c >= items(x->cmd)) + return 0; + + cmd = &x->cmd[c]; + + if (c != cmd->chk) + err(EINVAL, + "Invalid cmd chk value (%lu) vs arg: %lu", + cmd->chk, c); + + return 1; +} + +void +cmd_helper_setmac(void) +{ + struct xstate *x = xstatus(); + struct macaddr *mac = &x->mac; + + size_t partnum; + + check_cmd(cmd_helper_setmac, "setmac"); + + printf("MAC address to be written: %s\n", mac->str); + parse_mac_string(); + + for (partnum = 0; partnum < 2; partnum++) + write_mac_part(partnum); +} + +void +parse_mac_string(void) +{ + struct xstate *x = xstatus(); + struct macaddr *mac = &x->mac; + + size_t mac_byte; + + size_t rval; + + if (slen(x->mac.str, 18, &rval) < 0) + err(EINVAL, "Could not determine MAC length"); + + if (rval != 17) + err(EINVAL, "MAC address is the wrong length"); + + memset(mac->mac_buf, 0, sizeof(mac->mac_buf)); + + for (mac_byte = 0; mac_byte < 6; mac_byte++) + 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"); + + if (mac->mac_buf[0] & 1) + err(EINVAL, "Must not specify multicast MAC address"); +} + +void +set_mac_byte(size_t mac_byte_pos) +{ + struct xstate *x = xstatus(); + struct macaddr *mac = &x->mac; + + char separator; + + 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'", + separator); + } + + for (mac_nib_pos = 0; mac_nib_pos < 2; mac_nib_pos++) + set_mac_nib(mac_str_pos, mac_byte_pos, mac_nib_pos); +} + +void +set_mac_nib(size_t mac_str_pos, + size_t mac_byte_pos, size_t mac_nib_pos) +{ + struct xstate *x = xstatus(); + struct macaddr *mac = &x->mac; + + char mac_ch; + unsigned short hex_num; + + mac_ch = mac->str[mac_str_pos + mac_nib_pos]; + + if ((hex_num = hextonum(mac_ch)) > 15) { + if (hex_num >= 17) + err(EIO, "Randomisation failure"); + else + err(EINVAL, "Invalid character '%c'", + mac->str[mac_str_pos + mac_nib_pos]); + } + + /* If random, ensure that local/unicast bits are set. + */ + if ((mac_byte_pos == 0) && (mac_nib_pos == 1) && + ((mac_ch | 0x20) == 'x' || + (mac_ch == '?'))) + hex_num = (hex_num & 0xE) | 2; /* local, unicast */ + + /* MAC words stored big endian in-file, little-endian + * logically, so we reverse the order. + */ + mac->mac_buf[mac_byte_pos >> 1] |= hex_num << + (((mac_byte_pos & 1) << 3) /* left or right byte? */ + | ((mac_nib_pos ^ 1) << 2)); /* left or right nib? */ +} + +void +write_mac_part(size_t partnum) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + struct macaddr *mac = &x->mac; + + size_t w; + + check_bin(partnum, "part number"); + if (!f->part_valid[partnum]) + return; + + for (w = 0; w < 3; w++) + set_nvm_word(w, partnum, mac->mac_buf[w]); + + printf("Wrote MAC address to part %lu: ", + (size_t)partnum); + print_mac_from_nvm(partnum); +} + +void +cmd_helper_dump(void) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + size_t p; + + check_cmd(cmd_helper_dump, "dump"); + + f->part_valid[0] = good_checksum(0); + f->part_valid[1] = good_checksum(1); + + for (p = 0; p < 2; p++) { + + if (!f->part_valid[p]) { + + fprintf(stderr, + "BAD checksum %04x in part %lu (expected %04x)\n", + nvm_word(NVM_CHECKSUM_WORD, p), + (size_t)p, + calculated_checksum(p)); + } + + printf("MAC (part %lu): ", + (size_t)p); + + print_mac_from_nvm(p); + + hexdump(p); + } +} + +void +print_mac_from_nvm(size_t partnum) +{ + size_t c; + unsigned short val16; + + for (c = 0; c < 3; c++) { + + val16 = nvm_word(c, partnum); + + printf("%02x:%02x", + (unsigned int)(val16 & 0xff), + (unsigned int)(val16 >> 8)); + + if (c == 2) + printf("\n"); + else + printf(":"); + } +} + +void +hexdump(size_t partnum) +{ + size_t c; + size_t row; + unsigned short val16; + + for (row = 0; row < 8; row++) { + + printf("%08lx ", + (size_t)((size_t)row << 4)); + + for (c = 0; c < 8; c++) { + + val16 = nvm_word((row << 3) + c, partnum); + + if (c == 4) + printf(" "); + + printf(" %02x %02x", + (unsigned int)(val16 & 0xff), + (unsigned int)(val16 >> 8)); + + } + + printf("\n"); + } +} + +void +cmd_helper_swap(void) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + check_cmd(cmd_helper_swap, "swap"); + + memcpy( + f->buf + (size_t)GBE_WORK_SIZE, + f->buf, + GBE_PART_SIZE); + + memcpy( + f->buf, + f->buf + (size_t)GBE_PART_SIZE, + GBE_PART_SIZE); + + memcpy( + f->buf + (size_t)GBE_PART_SIZE, + f->buf + (size_t)GBE_WORK_SIZE, + GBE_PART_SIZE); + + set_part_modified(0); + set_part_modified(1); +} + +void +cmd_helper_copy(void) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + check_cmd(cmd_helper_copy, "copy"); + + memcpy( + 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); +} + +void +cmd_helper_cat(void) +{ + struct xstate *x = xstatus(); + + check_cmd(cmd_helper_cat, "cat"); + + x->cat = 0; + cat(0); +} + +void +cmd_helper_cat16(void) +{ + struct xstate *x = xstatus(); + + check_cmd(cmd_helper_cat16, "cat16"); + + x->cat = 1; + cat(1); +} + +void +cmd_helper_cat128(void) +{ + struct xstate *x = xstatus(); + + check_cmd(cmd_helper_cat128, "cat128"); + + x->cat = 15; + cat(15); +} + +void +cat(size_t nff) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + size_t p; + size_t ff; + + p = 0; + ff = 0; + + if ((size_t)x->cat != nff) { + + err(ECANCELED, "erroneous call to cat"); + } + + fflush(NULL); + + memset(f->pad, 0xff, GBE_PART_SIZE); + + for (p = 0; p < 2; p++) { + + cat_buf(f->bufcmp + + (size_t)(p * (f->gbe_file_size >> 1))); + + for (ff = 0; ff < nff; ff++) { + + cat_buf(f->pad); + } + } +} + +void +cat_buf(unsigned char *b) +{ + if (b == NULL) + err(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"); +} +void +check_cmd(void (*fn)(void), + const char *name) +{ + struct xstate *x = xstatus(); + size_t i = x->i; + + if (x->cmd[i].run != fn) + err(ECANCELED, "Running %s, but cmd %s is set", + name, x->cmd[i].str); + + /* prevent second command + */ + for (i = 0; i < items(x->cmd); i++) + x->cmd[i].run = cmd_helper_err; +} + +void +cmd_helper_err(void) +{ + err(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..ea2bcd0b --- /dev/null +++ b/util/libreboot-utils/lib/file.c @@ -0,0 +1,1136 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe + * + * Pathless i/o, and some stuff you probably never saw. + * Be nice to the demon. + */ + + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* for openat2: */ +#ifdef __linux__ +#include +#include +#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(errno, "%s", path); + + if (fstat(*fd_ptr, st) < 0) + err(errno, "%s: stat", path); + + if (!S_ISREG(st->st_mode)) + err(errno, "%s: not a regular file", path); + + if (lseek_on_eintr(*fd_ptr, 0, SEEK_CUR, 1, 1) == (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; + + 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 (path == NULL) { + errno = EFAULT; + goto err_fsync_dir; + } + + if (slen(path, maxlen, &pathlen) < 0) + goto err_fsync_dir; + + if (pathlen >= maxlen || pathlen < 0) { + errno = EMSGSIZE; + goto err_fsync_dir; + } + + if (pathlen == 0) + { + errno = EINVAL; + goto err_fsync_dir; + } + + dirbuf = malloc(pathlen + 1); + if (dirbuf == NULL) { + + errno = ENOMEM; + 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 (dirfd < 0) + goto err_fsync_dir; + + if (fstat(dirfd, &st) < 0) + goto err_fsync_dir; + + if (!S_ISDIR(st.st_mode)) { + + errno = ENOTDIR; + goto err_fsync_dir; + } + + /* sync file on disk */ + if (fsync_on_eintr(dirfd) == -1) + goto err_fsync_dir; + + if (close_on_eintr(dirfd) == -1) { + + dirfd = -1; + goto err_fsync_dir; + } + + if (dirbuf != NULL) { + + free(dirbuf); + dirbuf = NULL; + } + + dirbuf = NULL; + + errno = saved_errno; + return 0; + +err_fsync_dir: + + if (errno == saved_errno) + errno = EIO; + + if (dirbuf != NULL) { + + free(dirbuf); + dirbuf = NULL; + } + + if (dirfd >= 0) { + + close_errno = errno; + (void) close_on_eintr(dirfd); + errno = close_errno; + dirfd = -1; + } + + 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; + + /* obviously */ + if (mem == NULL) { + + errno = EFAULT; + goto err_io_args; + } + + /* uninitialised fd */ + if (fd < 0) { + + errno = EBADF; + goto err_io_args; + } + + /* negative offset */ + if (off < 0) { + + errno = ERANGE; + goto err_io_args; + } + + /* prevent zero-byte rw */ + if (!nrw) + goto err_io_args; + + /* prevent overflow */ + if (nrw > (size_t)SSIZE_MAX) { + + errno = ERANGE; + goto err_io_args; + } + + /* prevent overflow */ + if (((size_t)off + nrw) < (size_t)off) { + + errno = ERANGE; + goto err_io_args; + } + + if (rw_type > IO_PWRITE) { + + errno = 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 (fd < 0) { + errno = EBADF; + goto err_is_file; + } + + if (st == NULL) { + errno = EFAULT; + goto err_is_file; + } + + if (fstat(fd, st) == -1) + goto err_is_file; + + if (!S_ISREG(st->st_mode)) { + + errno = 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; + + /* 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 ((size_t) + r > SSIZE_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 + * ssize_t integers are the + * same size as SSIZE_T + */ + + errno = ERANGE; + goto err_rw_over_nrw; + } + + /* Theoretical buggy libc: + * Should never return a number of + * bytes above the requested length. + */ + if ((size_t)r > nrw) { + + errno = 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 + +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 || 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 (new == NULL || old == NULL) { + + errno = EFAULT; + return -1; + } + + if (olddirfd < 0 || newdirfd < 0) { + + errno = 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; + const char *rel; + + if (path == NULL) { + errno = EFAULT; + return -1; + } + + if (path[0] != '/') { + errno = EINVAL; + return -1; + } + + fs = rootfs(); + if (fs == NULL) + return -1; + + rel = path + 1; + + return fs_resolve_at(fs->rootfd, rel, 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; + char name[256]; + 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 (fd < 0) { + errno = EBADF; + return -1; + } + + if (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) + return -1; + + if (slen(path, maxlen, &len) < 0) + return -1; + + buf = malloc(len + 1); + if (buf == 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(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 (dirfd < 0) { + errno = EBADF; + return -1; + } + + if (path == NULL) { + errno = 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 (dirfd < 0) { + errno = EBADF; + return -1; + } + + if (path == NULL) { + errno = 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/libreboot-utils/lib/io.c b/util/libreboot-utils/lib/io.c new file mode 100644 index 00000000..94bde87e --- /dev/null +++ b/util/libreboot-utils/lib/io.c @@ -0,0 +1,673 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe + * + * I/O functions specific to nvmutil. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../include/common.h" + +void +open_gbe_file(void) +{ + struct xstate *x = xstatus(); + struct commands *cmd = &x->cmd[x->i]; + struct xfile *f = &x->f; + + int _flags; + + xopen(&f->gbe_fd, f->fname, + cmd->flags | O_BINARY | + O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, &f->gbe_st); + + if (f->gbe_st.st_nlink > 1) + err(EINVAL, + "%s: warning: file has multiple (%lu) hard links\n", + 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); + + _flags = fcntl(f->gbe_fd, F_GETFL); + if (_flags == -1) + err(errno, "%s: fcntl(F_GETFL)", f->fname); + + /* O_APPEND allows POSIX write() to ignore + * the current write offset and write at EOF, + * which would break positional read/write + */ + + if (_flags & O_APPEND) + err(EIO, "%s: O_APPEND flag", f->fname); + + f->gbe_file_size = f->gbe_st.st_size; + + switch (f->gbe_file_size) { + case SIZE_8KB: + case SIZE_16KB: + case SIZE_128KB: + break; + default: + err(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); +} + +void +copy_gbe(void) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + read_file(); + + if (f->gbe_file_size == SIZE_8KB) + return; + + 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(); + struct xfile *f = &x->f; + + struct stat _st; + ssize_t _r; + + /* read main file + */ + _r = rw_file_exact(f->gbe_fd, f->buf, 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", f->fname); + + /* copy to tmpfile + */ + _r = rw_file_exact(f->tmp_fd, f->buf, f->gbe_file_size, + 0, IO_PWRITE, NO_LOOP_EAGAIN, LOOP_EINTR, + MAX_ZERO_RW_RETRY, OFF_ERR); + + if (_r < 0) + err(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); + + f->gbe_tmp_size = _st.st_size; + + if (f->gbe_tmp_size != f->gbe_file_size) + err(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); + + _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); + + if (memcmp(f->buf, f->bufcmp, f->gbe_file_size) != 0) + err(errno, "%s: %s: read contents differ (pre-test)", + f->fname, f->tname); +} + +void +write_gbe_file(void) +{ + struct xstate *x = xstatus(); + struct commands *cmd = &x->cmd[x->i]; + struct xfile *f = &x->f; + + 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); + + if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0) + err(errno, "%s: file has changed", f->fname); + + update_checksum = cmd->chksum_write; + + for (p = 0; p < 2; p++) { + if (!f->part_modified[p]) + continue; + + if (update_checksum) + set_checksum(p); + + rw_gbe_file_part(p, IO_PWRITE, "pwrite"); + } +} + +void +rw_gbe_file_part(size_t p, int rw_type, + const char *rw_type_str) +{ + struct xstate *x = xstatus(); + struct commands *cmd = &x->cmd[x->i]; + struct xfile *f = &x->f; + + ssize_t rval; + + off_t file_offset; + + 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, (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); + + rval = rw_gbe_file_exact(f->tmp_fd, mem_offset, + gbe_rw_size, file_offset, rw_type); + + if (rval == -1) + err(errno, "%s: %s: part %lu", + f->fname, rw_type_str, (size_t)p); + + if ((size_t)rval != gbe_rw_size) + err(EIO, "%s: partial %s: part %lu", + f->fname, rw_type_str, (size_t)p); +} + +void +write_to_gbe_bin(void) +{ + struct xstate *x = xstatus(); + struct commands *cmd = &x->cmd[x->i]; + struct xfile *f = &x->f; + + int saved_errno; + int mv; + + if ((cmd->flags & O_ACCMODE) != O_RDWR) + return; + + write_gbe_file(); + + /* We may otherwise read from + * cache, so we must sync. + */ + + if (fsync_on_eintr(f->tmp_fd) == -1) + err(errno, "%s: fsync (pre-verification)", + f->tname); + + check_written_part(0); + check_written_part(1); + + report_io_err_rw(); + + if (f->io_err_gbe) + err(EIO, "%s: bad write", f->fname); + + saved_errno = errno; + + if (close_on_eintr(f->tmp_fd) == -1) { + f->tmp_fd = -1; + + fprintf(stderr, "FAIL: %s: close\n", f->tname); + f->io_err_gbe_bin = 1; + } + f->tmp_fd = -1; + + if (close_on_eintr(f->gbe_fd) == -1) { + f->gbe_fd = -1; + + fprintf(stderr, "FAIL: %s: close\n", f->fname); + f->io_err_gbe_bin = 1; + } + f->gbe_fd = -1; + + errno = saved_errno; + + /* tmpfile written, now we + * rename it back to the main file + * (we do atomic writes) + */ + + f->tmp_fd = -1; + f->gbe_fd = -1; + + if (!f->io_err_gbe_bin) { + + mv = gbe_mv(); + + if (mv < 0) { + + f->io_err_gbe_bin = 1; + + fprintf(stderr, "%s: %s\n", + f->fname, strerror(errno)); + } else { + + /* removed by rename + */ + + if (f->tname != NULL) { + free(f->tname); + f->tname = NULL; + } + + f->tname = NULL; + } + } + + if (!f->io_err_gbe_bin) + return; + + fprintf(stderr, "FAIL (rename): %s: skipping fsync\n", + f->fname); + if (errno) + fprintf(stderr, + "errno %d: %s\n", errno, strerror(errno)); +} + +void +check_written_part(size_t p) +{ + struct xstate *x = xstatus(); + struct commands *cmd = &x->cmd[x->i]; + struct xfile *f = &x->f; + + ssize_t rval; + + size_t gbe_rw_size; + + off_t file_offset; + unsigned char *mem_offset; + + unsigned char *buf_restore; + + if (!f->part_modified[p]) + return; + + gbe_rw_size = cmd->rw_size; + + mem_offset = gbe_mem_offset(p, "pwrite"); + file_offset = (off_t)gbe_file_offset(p, "pwrite"); + + 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); + + if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0) + err(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 ((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; + + if (f->rw_check_err_read[p] || + f->rw_check_partial_read[p]) + return; + + /* We only load one part on-file, into memory but + * always at offset zero, for post-write checks. + * That's why we hardcode good_checksum(0) + */ + + buf_restore = f->buf; + + /* good_checksum works on f->buf + * so let's change f->buf for now + */ + + f->buf = f->pad; + + if (good_checksum(0)) + f->post_rw_checksum[p] = 1; + + f->buf = buf_restore; +} + +void +report_io_err_rw(void) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + size_t p; + + if (!f->io_err_gbe) + return; + + for (p = 0; p < 2; p++) { + if (!f->part_modified[p]) + continue; + + if (f->rw_check_err_read[p]) + fprintf(stderr, + "%s: pread: p%lu (post-verification)\n", + f->fname, (size_t)p); + if (f->rw_check_partial_read[p]) + fprintf(stderr, + "%s: partial pread: p%lu (post-verification)\n", + f->fname, (size_t)p); + if (f->rw_check_bad_part[p]) + fprintf(stderr, + "%s: pwrite: corrupt write on p%lu\n", + 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, (size_t)p); + + continue; + } + + fprintf(stderr, "%s: ", f->fname); + + if (f->post_rw_checksum[p]) + fprintf(stderr, "GOOD"); + else + fprintf(stderr, "BAD"); + + fprintf(stderr, " checksum in p%lu on-disk.\n", + (size_t)p); + + if (f->post_rw_checksum[p]) { + fprintf(stderr, + " This does NOT mean it's safe. it may be\n" + " salvageable if you use the cat feature.\n"); + } + } +} + +int +gbe_mv(void) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + int rval; + + int saved_errno; + int tmp_gbe_bin_exists; + + char *dest_tmp; + int dest_fd; + + /* will be set 0 if it doesn't + */ + tmp_gbe_bin_exists = 1; + + dest_tmp = NULL; + dest_fd = -1; + + saved_errno = errno; + + rval = rename(f->tname, f->fname); + + if (rval > -1) { + + /* rename on same filesystem + */ + + tmp_gbe_bin_exists = 0; + + if (fsync_dir(f->fname) < 0) { + f->io_err_gbe_bin = 1; + rval = -1; + } + + goto ret_gbe_mv; + } + + if (errno != EXDEV) + goto ret_gbe_mv; + + /* + * OR, cross-filesystem rename: + */ + + if ((rval = f->tmp_fd = open(f->tname, + O_RDONLY | O_BINARY)) == -1) + goto ret_gbe_mv; + + /* create replacement temp in target directory + */ + if (new_tmpfile(&dest_fd, &f->fname) < 1) + goto ret_gbe_mv; + if (dest_tmp == NULL) + goto ret_gbe_mv; + + /* copy data + */ + rval = rw_file_exact(f->tmp_fd, f->bufcmp, + f->gbe_file_size, 0, IO_PREAD, + NO_LOOP_EAGAIN, LOOP_EINTR, + MAX_ZERO_RW_RETRY, OFF_ERR); + + if (rval < 0) + goto ret_gbe_mv; + + rval = rw_file_exact(dest_fd, f->bufcmp, + f->gbe_file_size, 0, IO_PWRITE, + NO_LOOP_EAGAIN, LOOP_EINTR, + MAX_ZERO_RW_RETRY, OFF_ERR); + + if (rval < 0) + goto ret_gbe_mv; + + if (fsync_on_eintr(dest_fd) == -1) + goto ret_gbe_mv; + + if (close_on_eintr(dest_fd) == -1) { + dest_fd = -1; + goto ret_gbe_mv; + } + dest_fd = -1; + + if (rename(dest_tmp, f->fname) == -1) + goto ret_gbe_mv; + + if (fsync_dir(f->fname) < 0) { + f->io_err_gbe_bin = 1; + goto ret_gbe_mv; + } + + if (dest_tmp != NULL) { + free(dest_tmp); + dest_tmp = NULL; + } + + dest_tmp = NULL; + +ret_gbe_mv: + + /* TODO: this whole section is bloat. + it can be generalised + */ + + if (f->gbe_fd > -1) { + 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; + } + } + + if (f->tmp_fd > -1) { + if (close_on_eintr(f->tmp_fd) < 0) { + f->tmp_fd = -1; + rval = -1; + } + f->tmp_fd = -1; + } + + /* before this function is called, + * tmp_fd may have been moved + */ + if (tmp_gbe_bin_exists) { + if (unlink(f->tname) < 0) + rval = -1; + else + tmp_gbe_bin_exists = 0; + } + + if (rval < 0) { + /* if nothing set errno, + * we assume EIO, or we + * use what was set + */ + if (errno == saved_errno) + errno = EIO; + } else { + errno = saved_errno; + } + + return rval; +} + +/* This one is similar to gbe_file_offset, + * but used to check Gbe bounds in memory, + * and it is *also* used during file I/O. + */ +unsigned char * +gbe_mem_offset(size_t p, const char *f_op) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + off_t gbe_off; + + gbe_off = gbe_x_offset(p, f_op, "mem", + GBE_PART_SIZE, GBE_WORK_SIZE); + + return (unsigned char *) + (f->buf + (size_t)gbe_off); +} + +/* I/O operations filtered here. These operations must + * only write from the 0th position or the half position + * within the GbE file, and write 4KB of data. + */ +off_t +gbe_file_offset(size_t p, const char *f_op) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + off_t gbe_file_half_size; + + gbe_file_half_size = f->gbe_file_size >> 1; + + return gbe_x_offset(p, f_op, "file", + gbe_file_half_size, f->gbe_file_size); +} + +off_t +gbe_x_offset(size_t p, const char *f_op, const char *d_type, + off_t nsize, off_t ncmp) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + off_t off; + + check_bin(p, "part number"); + + off = ((off_t)p) * (off_t)nsize; + + if (off > ncmp - GBE_PART_SIZE) + err(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", + f->fname, d_type, f_op); + + return off; +} + +ssize_t +rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw, + off_t off, int rw_type) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + ssize_t r; + + if (io_args(fd, mem, nrw, off, rw_type) == -1) + return -1; + + if (mem != (void *)f->pad) { + if (mem < f->buf) + goto err_rw_gbe_file_exact; + + 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 > (size_t)(f->gbe_file_size - off)) + goto err_rw_gbe_file_exact; + + if (nrw > (size_t)GBE_PART_SIZE) + goto err_rw_gbe_file_exact; + + r = rw_file_exact(fd, mem, nrw, off, rw_type, + NO_LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY, + OFF_ERR); + + return rw_over_nrw(r, nrw); + +err_rw_gbe_file_exact: + errno = EIO; + return -1; +} diff --git a/util/libreboot-utils/lib/mkhtemp.c b/util/libreboot-utils/lib/mkhtemp.c new file mode 100644 index 00000000..7c2f1fde --- /dev/null +++ b/util/libreboot-utils/lib/mkhtemp.c @@ -0,0 +1,1133 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe + * + * Hardened mktemp (be nice to the demon). + */ + +#if defined(__linux__) && !defined(_GNU_SOURCE) +/* for openat2 syscall on linux */ +#define _GNU_SOURCE 1 +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* for openat2: */ +#ifdef __linux__ +#include +#include +#endif + +#include "../include/common.h" + +int +new_tmpfile(int *fd, char **path) +{ + return new_tmp_common(fd, path, MKHTEMP_FILE); +} + +int +new_tmpdir(int *fd, char **path) +{ + return new_tmp_common(fd, path, MKHTEMP_DIR); +} + +int +new_tmp_common(int *fd, char **path, int type) +{ +#if defined(PATH_LEN) && \ + (PATH_LEN) >= 256 + size_t maxlen = PATH_LEN; +#else + size_t maxlen = 4096; +#endif + struct stat st; + + char suffix[] = + "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + char *tmpdir = NULL; + + int close_errno; + size_t dirlen; + size_t destlen; + char *dest = NULL; /* final path (will be written into "path") */ + int saved_errno = errno; + int dirfd = -1; + const char *fname = NULL; + + struct stat st_dir_initial; + + if (path == NULL || fd == NULL) { + errno = EFAULT; + goto err; + } + + /* don't mess with someone elses file */ + if (*fd >= 0) { + errno = EEXIST; + goto err; + } + + /* regarding **path: + * the pointer (to the pointer) + * must nott be null, but we don't + * care about the pointer it points + * to. you should expect it to be + * replaced upon successful return + * + * (on error, it will not be touched) + */ + + + *fd = -1; + +#if defined(PERMIT_NON_STICKY_ALWAYS) && \ + ((PERMIT_NON_STICKY_ALWAYS) > 0) + tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS); +#else + tmpdir = env_tmpdir(0); +#endif + if (tmpdir == NULL) + goto err; + + if (slen(tmpdir, maxlen, &dirlen) < 0) + goto err; + if (*tmpdir == '\0') + goto err; + if (*tmpdir != '/') + goto err; + + /* sizeof adds an extra byte, useful + * because we also want '.' or '/' + */ + destlen = dirlen + sizeof(suffix); + if (destlen > maxlen - 1) { + errno = EOVERFLOW; + goto err; + } + + dest = malloc(destlen + 1); + if (dest == NULL) { + errno = ENOMEM; + goto err; + } + + memcpy(dest, tmpdir, dirlen); + *(dest + dirlen) = '/'; + memcpy(dest + dirlen + 1, suffix, sizeof(suffix) - 1); + *(dest + destlen) = '\0'; + + fname = dest + dirlen + 1; + + dirfd = fs_open(tmpdir, + O_RDONLY | O_DIRECTORY); + if (dirfd < 0) + goto err; + + if (fstat(dirfd, &st_dir_initial) < 0) + goto err; + + *fd = mkhtemp(fd, &st, dest, dirfd, + fname, &st_dir_initial, type); + if (*fd < 0) + goto err; + + if (dirfd >= 0) { + close_errno = errno; + (void) close_on_eintr(dirfd); + errno = close_errno; + dirfd = -1; + } + + errno = saved_errno; + *path = dest; + + return 0; + +err: + + if (errno != saved_errno) + saved_errno = errno; + else + saved_errno = errno = EIO; + + if (dest != NULL) { + free(dest); + dest = NULL; + } + + if (dirfd >= 0) { + close_errno = errno; + (void) close_on_eintr(dirfd); + errno = close_errno; + dirfd = -1; + } + + if (*fd >= 0) { + close_errno = errno; + (void) close_on_eintr(*fd); + errno = close_errno; + *fd = -1; + } + + errno = saved_errno; + return -1; +} + + +/* hardened TMPDIR parsing + */ + +char * +env_tmpdir(int bypass_all_sticky_checks) +{ + char *t; + int allow_noworld_unsticky; + int saved_errno = errno; + + t = getenv("TMPDIR"); + + if (t != NULL && *t != '\0') { + + if (tmpdir_policy(t, + &allow_noworld_unsticky) < 0) { + errno = EPERM; + return NULL; /* errno already set */ + } + + if (!world_writeable_and_sticky(t, + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + errno = EPERM; + return NULL; + } + + errno = saved_errno; + return t; + } + + allow_noworld_unsticky = 0; + + if (world_writeable_and_sticky("/tmp", + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + + errno = saved_errno; + return "/tmp"; + } + + if (world_writeable_and_sticky("/var/tmp", + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + + errno = saved_errno; + return "/var/tmp"; + } + + if (errno == saved_errno) + errno = EPERM; + + return NULL; +} + +int +tmpdir_policy(const char *path, + int *allow_noworld_unsticky) +{ + int saved_errno = errno; + int r; + + if (path == NULL || + allow_noworld_unsticky == NULL) { + + errno = EFAULT; + return -1; + } + + *allow_noworld_unsticky = 1; + + r = same_dir(path, "/tmp"); + if (r < 0) + goto err_tmpdir_policy; + if (r > 0) + *allow_noworld_unsticky = 0; + + r = same_dir(path, "/var/tmp"); + if (r < 0) + goto err_tmpdir_policy; + if (r > 0) + *allow_noworld_unsticky = 0; + + errno = saved_errno; + return 0; + +err_tmpdir_policy: + + if (errno == saved_errno) + errno = EIO; + + return -1; +} + +int +same_dir(const char *a, const char *b) +{ + int fd_a = -1; + int fd_b = -1; + + struct stat st_a; + struct stat st_b; + + int saved_errno = errno; + int rval_scmp; + +#if defined(PATH_LEN) && \ + (PATH_LEN) >= 256 + size_t maxlen = (PATH_LEN); +#else + size_t maxlen = 4096; +#endif + + /* optimisation: if both dirs + are the same, we don't need + to check anything. sehr schnell: + */ + if (scmp(a, b, maxlen, &rval_scmp) < 0) + goto err_same_dir; + /* bonus: scmp checks null for us */ + if (rval_scmp == 0) + goto success_same_dir; + + fd_a = fs_open(a, O_RDONLY | O_DIRECTORY); + if (fd_a < 0) + goto err_same_dir; + + fd_b = fs_open(b, O_RDONLY | O_DIRECTORY); + if (fd_b < 0) + goto err_same_dir; + + if (fstat(fd_a, &st_a) < 0) + goto err_same_dir; + + if (fstat(fd_b, &st_b) < 0) + goto err_same_dir; + + if (st_a.st_dev == st_b.st_dev && + st_a.st_ino == st_b.st_ino) { + + (void) close_on_eintr(fd_a); + (void) close_on_eintr(fd_b); + +success_same_dir: + + /* SUCCESS + */ + + errno = saved_errno; + return 1; + } + + (void) close_on_eintr(fd_a); + (void) close_on_eintr(fd_b); + + /* FAILURE (logical) + */ + + errno = saved_errno; + return 0; + +err_same_dir: + + /* FAILURE (probably syscall) + */ + + if (fd_a >= 0) + (void) close_on_eintr(fd_a); + if (fd_b >= 0) + (void) close_on_eintr(fd_b); + + if (errno == saved_errno) + errno = EIO; + + return -1; +} + +/* bypass_all_sticky_checks: if set, + disable stickiness checks (libc behaviour) + (if not set: leah behaviour) + + allow_noworld_unsticky: + allow non-sticky files if not world-writeable + (still block non-sticky in standard TMPDIR) +*/ +int +world_writeable_and_sticky( + const char *s, + int allow_noworld_unsticky, + int bypass_all_sticky_checks) +{ + struct stat st; + int dirfd = -1; + + int saved_errno = errno; + + if (s == NULL || *s == '\0') { + errno = EINVAL; + goto sticky_hell; + } + + /* mitigate symlink attacks* + */ + dirfd = fs_open(s, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) + goto sticky_hell; + + if (fstat(dirfd, &st) < 0) + goto sticky_hell; + + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + goto sticky_hell; + } + + /* must be fully executable + * by everyone, or openat2 + * becomes unreliable** + */ + if (!(st.st_mode & S_IXUSR) || + !(st.st_mode & S_IXGRP) || + !(st.st_mode & S_IXOTH)) { + + errno = EACCES; + goto sticky_hell; + } + + /* *normal-**ish mode (libc): + */ + + if (bypass_all_sticky_checks) + goto sticky_heaven; /* normal == no security */ + + /* unhinged leah mode: + */ + + if (st.st_mode & S_IWOTH) { /* world writeable */ + + /* if world-writeable, only + * allow sticky files + */ + if (st.st_mode & S_ISVTX) + goto sticky_heaven; /* sticky */ + + errno = EPERM; + goto sticky_hell; /* not sticky */ + } + + /* non-world-writeable, so + * stickiness is do-not-care + */ + if (allow_noworld_unsticky) + goto sticky_heaven; /* sticky! */ + + goto sticky_hell; /* heaven visa denied */ + +sticky_heaven: +/* i like the one in hamburg better */ + + if (dirfd >= 0) + (void) close_on_eintr(dirfd); + + errno = saved_errno; + + return 1; + +sticky_hell: + + if (errno == saved_errno) + errno = EPERM; + + saved_errno = errno; + + if (dirfd >= 0) + (void) close_on_eintr(dirfd); + + errno = saved_errno; + + return 0; +} + +/* mk(h)temp - hardened mktemp. + * like mkstemp, but (MUCH) harder. + * + * designed to resist TOCTOU 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 (fd == NULL || + template == NULL || + fname == NULL || + st_dir_initial == NULL) { + + errno = EFAULT; + return -1; + } + + /* we do not mess with an + open descriptor. + */ + if (*fd >= 0) { + errno = EEXIST; /* leave their file alone */ + return -1; + } + + if (dirfd < 0) { + errno = EBADF; + return -1; + } + + if (slen(template, max_len, &len) < 0) + return -1; + + if (len >= max_len) { + errno = EMSGSIZE; + return -1; + } + + if (slen(fname, max_len, &fname_len) < 0) + return -1; + + if (fname_len == 0) { + errno = EINVAL; + return -1; + } + + if (strrchr(fname, '/') != NULL) { + errno = EINVAL; + return -1; + } + + /* count trailing X */ + end = template + len; + while (end > template && *--end == 'X') + xc++; + + if (xc < 12 || xc > len) { + errno = EINVAL; + return -1; + } + + if (fname_len > len) { + errno = EOVERFLOW; + return -1; + } + + if (memcmp(fname, + template + len - fname_len, + fname_len) != 0) { + errno = EINVAL; + return -1; + } + + fname_copy = malloc(fname_len + 1); + if (fname_copy == NULL) { + errno = ENOMEM; + goto err; + } + + /* fname_copy = suffix region only; p points to trailing XXXXXX */ + memcpy(fname_copy, + template + len - fname_len, + fname_len + 1); + p = fname_copy + fname_len - xc; + + for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) { + + r = mkhtemp_try_create( + dirfd, + st_dir_initial, + fname_copy, + p, + xc, + fd, + st, + type); + + if (r == 0) { + if (retries >= MKHTEMP_SPIN_THRESHOLD) { + /* usleep can return EINTR */ + close_errno = errno; + usleep((useconds_t)rlong() & 0x3FF); + errno = close_errno; + } + continue; + } + if (r < 0) + goto err; + + /* success: copy final name back */ + memcpy(template + len - fname_len, + fname_copy, fname_len); + + errno = saved_errno; + goto success; + } + + errno = EEXIST; + +err: + if (*fd >= 0) { + close_errno = errno; + (void)close_on_eintr(*fd); + errno = close_errno; + *fd = -1; + } + +success: + + if (fname_copy != NULL) + free(fname_copy); + + return (*fd >= 0) ? *fd : -1; +} + +int +mkhtemp_try_create(int dirfd, + struct stat *st_dir_initial, + char *fname_copy, + char *p, + size_t xc, + int *fd, + struct stat *st, + int type) +{ + struct stat st_open; + int saved_errno = errno; + int close_errno; + int rval = -1; + + int file_created = 0; + int dir_created = 0; + + if (fd == NULL || st == NULL || p == NULL || fname_copy == NULL || + st_dir_initial == NULL) { + errno = EFAULT; + goto err; + } else if (*fd >= 0) { /* do not mess with someone else's file */ + errno = EEXIST; + goto err; + } + + if (mkhtemp_fill_random(p, xc) < 0) + goto err; + + if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0) + goto err; + + if (type == MKHTEMP_FILE) { + *fd = openat2p(dirfd, fname_copy, + O_RDWR | O_CREAT | O_EXCL | + O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, + 0600); + + /* O_CREAT and O_EXCL guarantees + * creation upon success + */ + if (*fd >= 0) + file_created = 1; + + } else { /* dir: MKHTEMP_DIR */ + + if (mkdirat_on_eintr(dirfd, fname_copy, 0700) < 0) + goto err; + + /* ^ NOTE: opening the directory here + will never set errno=EEXIST, + since we're not creating it */ + + dir_created = 1; + + /* do it again (mitigate directory race) */ + if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0) + goto err; + + *fd = openat2p(dirfd, fname_copy, + O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0); + if (*fd < 0) + goto err; + + if (fstat(*fd, &st_open) < 0) + goto err; + + if (!S_ISDIR(st_open.st_mode)) { + errno = ENOTDIR; + goto err; + } + + /* NOTE: could check nlink count here, + * but it's not very reliable here. skipped. + */ + + if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0) + goto err; + + } + + /* NOTE: openat2p and mkdirat_on_eintr + * already handled EINTR/EAGAIN looping + */ + + if (*fd < 0) { + if (errno == EEXIST) { + + rval = 0; + goto out; + } + goto err; + } + + if (fstat(*fd, &st_open) < 0) + goto err; + + if (type == MKHTEMP_FILE) { + + if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0) + goto err; + + if (secure_file(fd, st, &st_open, + O_APPEND, 1, 1, 0600) < 0) /* WARNING: only once */ + goto err; + + } else { /* dir: MKHTEMP_DIR */ + + if (fd_verify_identity(*fd, &st_open, st_dir_initial) < 0) + goto err; + + if (!S_ISDIR(st_open.st_mode)) { + errno = ENOTDIR; + goto err; + } + + if (is_owner(&st_open) < 0) + goto err; + + /* group/world writeable */ + if (st_open.st_mode & (S_IWGRP | S_IWOTH)) { + errno = EPERM; + goto err; + } + } + + errno = saved_errno; + rval = 1; + goto out; + +err: + close_errno = errno; + + if (fd != NULL && *fd >= 0) { + (void) close_on_eintr(*fd); + *fd = -1; + } + + if (file_created) + (void) unlinkat(dirfd, fname_copy, 0); + + if (dir_created) + (void) unlinkat(dirfd, fname_copy, AT_REMOVEDIR); + + errno = close_errno; + rval = -1; +out: + return rval; +} + +int +mkhtemp_fill_random(char *p, size_t xc) +{ + size_t chx = 0; + int rand_failures = 0; + + size_t r; + + int saved_rand_error = 0; + static char ch[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + /* clamp rand to prevent modulo bias + * (reduced risk of entropy leak) + */ + size_t limit = ((size_t)-1) - (((size_t)-1) % (sizeof(ch) - 1)); + + int saved_errno = errno; + + if (p == NULL) { + errno = EFAULT; + goto err_mkhtemp_fill_random; + } + + for (chx = 0; chx < xc; chx++) { + + do { + saved_rand_error = errno; + rand_failures = 0; +retry_rand: + errno = 0; + + /* on bsd: uses arc4random + on linux: uses getrandom + on OLD linux: /dev/urandom + on old/other unix: /dev/urandom + */ + r = rlong(); + + if (errno > 0) { + if (++rand_failures <= 8) + goto retry_rand; + + goto err_mkhtemp_fill_random; + } + + rand_failures = 0; + errno = saved_rand_error; + + } while (r >= limit); + + p[chx] = ch[r % (sizeof(ch) - 1)]; + } + + errno = saved_errno; + return 0; + +err_mkhtemp_fill_random: + + if (errno == saved_errno) + errno = ECANCELED; + + return -1; +} + +/* WARNING: **ONCE** per file. + * + * !!! DO NOT RUN TWICE PER FILE. BEWARE OF THE DEMON !!! + * watch out for spikes! + */ +int secure_file(int *fd, + struct stat *st, + struct stat *expected, + int bad_flags, + int check_seek, + int do_lock, + mode_t mode) +{ + int flags; + struct stat st_now; + int saved_errno = errno; + /* you have been warned */ + if (fd == NULL) { + errno = EFAULT; + goto err_demons; + } + if (*fd < 0) { + errno = EBADF; + goto err_demons; + } + + if (st == NULL) { + errno = EFAULT; + goto err_demons; + } + + flags = fcntl(*fd, F_GETFL); + + if (flags == -1) + goto err_demons; + + if (bad_flags > 0) { + + /* e.g. O_APPEND breaks pwrite/pread + * by allowing offsets to be ignored */ + if (flags & bad_flags) { + errno = EPERM; + goto err_demons; + } + } + + if (expected != NULL) { + if (fd_verify_regular(*fd, expected, st) < 0) + goto err_demons; + } else { + if (fstat(*fd, &st_now) == -1) + goto err_demons; + + if (!S_ISREG(st_now.st_mode)) { + errno = EBADF; + goto err_demons; + } + + *st = st_now; + } + + if (check_seek) { + + /* check if it's seekable */ + if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1) + goto err_demons; + } + + /* don't release the demon + */ + if (st->st_nlink != 1) { /***********/ + /* ( >:3 ) */ + errno = ELOOP; /* /| |\ */ /* don't let him out */ + goto err_demons; /* / \ */ + /***********/ + } + + if (st->st_uid != geteuid() && /* someone else's file */ + geteuid() != 0) { /* override for root */ + + errno = EPERM; + goto err_demons; + } + if (is_owner(st) < 0) + goto err_demons; + + /* world-writeable or group-ownership. + * if these are set, then others could + * modify the file (not secure) + */ + if (st->st_mode & (S_IWGRP | S_IWOTH)) { + errno = EPERM; + goto err_demons; + } + + if (do_lock) { + if (lock_file(*fd, flags) == -1) + goto err_demons; + + if (expected != NULL) { + if (fd_verify_identity(*fd, expected, &st_now) < 0) + goto err_demons; + } + } + + if (fchmod(*fd, mode) == -1) + goto err_demons; + + errno = saved_errno; + return 0; + +err_demons: + + if (errno == saved_errno) + errno = EIO; + + return -1; +} + +int +fd_verify_regular(int fd, + const struct stat *expected, + struct stat *out) +{ + if (fd_verify_identity(fd, expected, out) < 0) + return -1; + + if (!S_ISREG(out->st_mode)) { + errno = EBADF; + return -1; + } + + return 0; /* regular file */ +} + +int +fd_verify_identity(int fd, + const struct stat *expected, + struct stat *out) +{ + struct stat st_now; + int saved_errno = errno; + + if (fd < 0 || expected == NULL) { + errno = EFAULT; + return -1; + } + + if (fstat(fd, &st_now) < 0) + return -1; + + if (st_now.st_dev != expected->st_dev || + st_now.st_ino != expected->st_ino) { + errno = ESTALE; + return -1; + } + + if (out != NULL) + *out = st_now; + + errno = saved_errno; + return 0; +} + +int +fd_verify_dir_identity(int fd, + const struct stat *expected) +{ + struct stat st_now; + int saved_errno = errno; + + if (fd < 0 || expected == NULL) { + errno = EFAULT; + return -1; + } + + if (fstat(fd, &st_now) < 0) + return -1; + + if (st_now.st_dev != expected->st_dev || + st_now.st_ino != expected->st_ino) { + errno = ESTALE; + return -1; + } + + if (!S_ISDIR(st_now.st_mode)) { + errno = ENOTDIR; + return -1; + } + + errno = saved_errno; + return 0; +} + +int +is_owner(struct stat *st) +{ + if (st == NULL) { + + errno = EFAULT; + return -1; + } + +#if ALLOW_ROOT_OVERRIDE + if (st->st_uid != geteuid() && /* someone else's file */ + geteuid() != 0) { /* override for root */ +#else + if (st->st_uid != geteuid()) { /* someone else's file */ +#endif /* and no root override */ + errno = EPERM; + return -1; + } + + return 0; +} + +int +lock_file(int fd, int flags) +{ + struct flock fl; + int saved_errno = errno; + + if (fd < 0) { + errno = EBADF; + goto err_lock_file; + } + + if (flags < 0) { + errno = EINVAL; + goto err_lock_file; + } + + memset(&fl, 0, sizeof(fl)); + + if ((flags & O_ACCMODE) == O_RDONLY) + fl.l_type = F_RDLCK; + else + fl.l_type = F_WRLCK; + + fl.l_whence = SEEK_SET; + + if (fcntl(fd, F_SETLK, &fl) == -1) + goto err_lock_file; + + saved_errno = errno; + return 0; + +err_lock_file: + + if (errno == saved_errno) + errno = EIO; + + return -1; +} diff --git a/util/libreboot-utils/lib/num.c b/util/libreboot-utils/lib/num.c new file mode 100644 index 00000000..343350b0 --- /dev/null +++ b/util/libreboot-utils/lib/num.c @@ -0,0 +1,444 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe + * + * Numerical functions. + */ + +/* +TODO: properly handle errno in this file + */ + +#ifdef __OpenBSD__ +#include +#endif +#include +#if defined(FALLBACK_RAND_1989) && \ + (FALLBACK_RAND_1989) > 0 +#include +#endif + +#include +#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \ + defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__)) +#include /* if not arc4random: /dev/urandom */ +#endif +#include +#include +#include +#if defined(FALLBACK_RAND_1989) && \ + (FALLBACK_RAND_1989) > 0 +#include +#endif +#include + +#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. */ +} + +/* 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. + */ + +size_t +rlong(void) +{ +#if !(defined(FALLBACK_RAND_1989) && \ + ((FALLBACK_RAND_1989) > 0)) +#if (defined(__OpenBSD__) && (OpenBSD) >= 201) || \ + defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__) + + int saved_errno = errno; + size_t rval; + + arc4random_buf(&rval, sizeof(size_t)); + + errno = saved_errno; + return rval; +#else + static int fd = -1; + static ssize_t nr = -1; + static size_t 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 + size_t rval; + ssize_t new_nr; + + int retries = 0; + int max_retries = 100; + + int saved_errno = errno; + +#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 + */ + + if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0) { + errno = saved_errno; + 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 err_rlong; + + if (nr < 0 || nr < (ssize_t)sizeof(size_t)) { + + if (fd < 0) { + + fd = open("/dev/urandom", + O_RDONLY | O_BINARY | O_NOFOLLOW | + O_CLOEXEC | O_NOCTTY); + +#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 | O_NOCTTY); +#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 < (ssize_t)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(size_t)); + + nr -= (ssize_t)sizeof(size_t); + off += sizeof(size_t); + + errno = saved_errno; + + return rval; + +err_rlong: + + if (errno == saved_errno) + errno = EIO; + + return 0; + +#endif +#else /* FALLBACK_RAND_1989 */ + /* your computer is from a museum + */ + size_t mix = 0; + int nr = 0; + int saved_errno = errno; + + /* 100 times, for entropy + */ + for (nr = 0; nr < 100; nr++) + mix ^= fallback_rand_1989(); + + errno = saved_errno; + return mix; +#endif +} + +#if !(defined(FALLBACK_RAND_1989) && \ + ((FALLBACK_RAND_1989) > 0)) +#if defined(__linux__) +#if defined(HAVE_GETRANDOM) || \ + defined(HAVE_GETRANDOM_SYSCALL) +int /* yes, linux is a fallback */ +fallback_rand_getrandom(void *buf, size_t len) +{ + size_t off = 0; + ssize_t rval = -1; + int saved_errno = errno; + + if (!len) { + errno = EINVAL; + return -1; + } + + if (buf == NULL) { + errno = EFAULT; + return -1; + } + +#if defined(HAVE_GETRANDOM) || \ + defined(HAVE_GETRANDOM_SYSCALL) + + while (off < len) { + +#if defined(HAVE_GETRANDOM) + rval = (ssize_t)getrandom((char *)buf + off, len - off, 0); +#elif defined(HAVE_GETRANDOM_SYSCALL) + rval = (ssize_t)syscall(SYS_getrandom, + (char *)buf + off, len - off, 0); +#endif + + if (rval < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + errno = EIO; + return -1; /* unsupported by kernel */ + } + + if (rval == 0) { + errno = EIO; + return -1; + } + + off += (size_t)rval; + } + + errno = saved_errno; + return 0; + +#else + (void)buf; + (void)len; + + errno = EIO; + return -1; +#endif +} +#endif +#endif +#else +size_t +fallback_rand_1989(void) +{ + static size_t mix = 0; + static size_t counter = 0; + + struct timeval tv; + + /* nobody should use this + * (not crypto-safe) + */ + + gettimeofday(&tv, NULL); + + mix ^= (size_t)tv.tv_sec + ^ (size_t)tv.tv_usec + ^ (size_t)getpid() + ^ (size_t)&mix + ^ counter++ + ^ entropy_jitter(); + + /* + * Stack addresses can vary between + * calls, thus increasing entropy. + */ + mix ^= (size_t)&mix; + mix ^= (size_t)&tv; + mix ^= (size_t)&counter; + + return mix; +} + +size_t +entropy_jitter(void) +{ + size_t mix; + + struct timeval a, b; + ssize_t 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 = (ssize_t)(b.tv_usec - a.tv_usec); + if (mix_diff < 0) + mix_diff = -mix_diff; + + mix ^= (size_t)(mix_diff); + + mix ^= (size_t)&mix; + + } + + return mix; +} +#endif + +void +check_bin(size_t a, const char *a_name) +{ + if (a > 1) + err(EINVAL, "%s must be 0 or 1, but is %lu", + a_name, (size_t)a); +} diff --git a/util/libreboot-utils/lib/state.c b/util/libreboot-utils/lib/state.c new file mode 100644 index 00000000..d06a8869 --- /dev/null +++ b/util/libreboot-utils/lib/state.c @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2022-2026 Leah Rowe + * + * State machine (singleton) for nvmutil data. + */ + +#ifdef __OpenBSD__ +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../include/common.h" + +struct xstate * +xstart(int argc, char *argv[]) +{ + static int first_run = 1; + static int pre_init = 0; + + static struct xstate us = { + /* DO NOT MESS THIS UP, OR THERE WILL BE DEMONS */ + { + { + CMD_DUMP, "dump", cmd_helper_dump, ARGC_3, + ARG_NOPART, + SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + NVM_SIZE, O_RDONLY + }, { + CMD_SETMAC, "setmac", cmd_helper_setmac, ARGC_3, + ARG_NOPART, + CHECKSUM_READ, CHECKSUM_WRITE, + NVM_SIZE, O_RDWR + }, { + CMD_SWAP, "swap", cmd_helper_swap, ARGC_3, + ARG_NOPART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDWR + }, { + CMD_COPY, "copy", cmd_helper_copy, ARGC_4, + ARG_PART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDWR + }, { + CMD_CAT, "cat", cmd_helper_cat, ARGC_3, + ARG_NOPART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDONLY + }, { + CMD_CAT16, "cat16", cmd_helper_cat16, ARGC_3, + ARG_NOPART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDONLY + }, { + CMD_CAT128, "cat128", cmd_helper_cat128, ARGC_3, + ARG_NOPART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDONLY + } + }, + + /* ->mac */ + {NULL, "xx:xx:xx:xx:xx:xx", {0, 0, 0}}, /* .str, .rmac, .mac_buf */ + + /* .f */ + {0}, + + /* .argv0 (for our getprogname implementation) */ + NULL, + + /* ->i (index to cmd[]) */ + 0, + + /* .no_cmd (set 0 when a command is found) */ + 1, + + /* .cat (cat helpers set this) */ + -1 + + }; + + if (!first_run) { + if (pre_init) + err_no_cleanup(ECANCELED, + "Outside access to state during init"); + + first_run = 0; + + return &us; + } + + if (argc < 3) + err_no_cleanup(EINVAL, "xstart: Too few arguments"); + if (argv == NULL) + err_no_cleanup(EINVAL, "xstart: NULL argv"); + + first_run = 0; + pre_init = 1; + + us.f.buf = us.f.real_buf; + + us.argv0 = argv[0]; + us.f.fname = argv[1]; + + if (new_tmpfile(&us.f.tmp_fd, &us.f.tname) < 0) + err_no_cleanup(errno, "xstart: cannot create tmpfile"); + + /* parse user command */ +/* TODO: CHECK ACCESSES VIA xstatus() */ + set_cmd(argc, argv); + set_cmd_args(argc, argv); + + if (us.f.tname == NULL) + err_no_cleanup(errno, "x->f.tname null"); + if (*us.f.tname == '\0') + err_no_cleanup(errno, "x->f.tname empty"); + + if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0) + err_no_cleanup(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)); + + /* for good measure */ + memset(us.f.pad, 0, sizeof(us.f.pad)); + + pre_init = 0; + + return &us; +} + +struct xstate * +xstatus(void) +{ + struct xstate *x = xstart(0, NULL); + + if (x == NULL) + err_no_cleanup(EACCES, "NULL pointer to xstate"); + + sanitize_command_list(); + + return x; +} + +void +err(int nvm_errval, const char *msg, ...) +{ + struct xstate *x = xstatus(); + + va_list args; + + if (errno == 0) + errno = nvm_errval; + if (!errno) + errno = ECANCELED; + + (void)exit_cleanup(); + + if (x != NULL) + fprintf(stderr, "%s: ", getnvmprogname()); + + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + + fprintf(stderr, ": %s\n", strerror(errno)); + + exit(EXIT_FAILURE); +} + +const char * +getnvmprogname(void) +{ + struct xstate *x = xstatus(); + + const char *p; + static char fallback[] = "nvmutil"; + + char *rval = fallback; + + if (x != NULL) { + if (x->argv0 == NULL || *x->argv0 == '\0') + return ""; + + rval = x->argv0; + } + + p = strrchr(rval, '/'); + + if (p) + return p + 1; + else + return rval; +} + +int +exit_cleanup(void) +{ + struct xstate *x = xstatus(); + struct xfile *f; + + int close_err; + int saved_errno; + + close_err = 0; + saved_errno = errno; + + if (x != NULL) { + f = &x->f; + + if (f->gbe_fd > -1) { + if (close_on_eintr(f->gbe_fd) == -1) { + f->gbe_fd = -1; + close_err = 1; + } + f->gbe_fd = -1; + } + + if (f->tmp_fd > -1) { + if (close_on_eintr(f->tmp_fd) == -1) { + f->tmp_fd = -1; + close_err = 1; + } + f->tmp_fd = -1; + } + + if (f->tname != NULL) { + if (unlink(f->tname) == -1) + close_err = 1; + } + + f->tmp_fd = -1; + } + + if (saved_errno) + errno = saved_errno; + + if (close_err) + return -1; + + return 0; +} diff --git a/util/libreboot-utils/lib/string.c b/util/libreboot-utils/lib/string.c new file mode 100644 index 00000000..cb37c1ba --- /dev/null +++ b/util/libreboot-utils/lib/string.c @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe + * + * String functions + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../include/common.h" + +/* scmp() - strict string comparison + * + * strict string comparison + * similar to strncmp, but null and + * unterminated inputs do not produce + * a return value; on error, errno is + * set and -1 is returned. + * + * the real return value is stored in + * the 4th argument by pointer. + * + * the value at rval pointer is set, + * only upon success. callers should + * check the return value accordingly. + */ + +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; + return -1; + } + + 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; + } + } + + /* block unterminated strings */ + errno = EFAULT; + return -1; +} + +/* slen() - strict strict length + * + * strict string length calculation + * similar to strnlen, but null and + * unterminated inputs do not produce + * a return value; on error, errno is + * set and -1 is returned. + * + * the real return value is stored in + * the 3rd argument by pointer. + * + * the value at rval pointer is set, + * only upon success. callers should + * check the return value accordingly. + */ + +int +slen(const char *s, + size_t maxlen, + size_t *rval) +{ + size_t ch; + + if (s == NULL || + rval == NULL) { + + errno = EFAULT; + return -1; + } + + for (ch = 0; + ch < maxlen && s[ch] != '\0'; + ch++); + + if (ch == maxlen) { + /* unterminated */ + errno = EFAULT; + return -1; + } + + *rval = ch; + return 0; +} + +/* the one for nvmutil state is in state.c */ +/* this one just exits */ +void +err_no_cleanup(int nvm_errval, const char *msg, ...) +{ + va_list args; + +#if defined(__OpenBSD__) && defined(OpenBSD) +#if (OpenBSD) >= 509 + if (pledge("stdio", NULL) == -1) + fprintf(stderr, "pledge failure during exit"); +#endif +#endif + + if (!errno) + errno = ECANCELED; + + fprintf(stderr, "nvmutil: "); + + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + + fprintf(stderr, ": %s\n", strerror(errno)); + + exit(EXIT_FAILURE); +} + diff --git a/util/libreboot-utils/lib/usage.c b/util/libreboot-utils/lib/usage.c new file mode 100644 index 00000000..3b0614e8 --- /dev/null +++ b/util/libreboot-utils/lib/usage.c @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2023 Riku Viitanen + * Copyright (c) 2026 Leah Rowe + */ + +#include +#include + +#include "../include/common.h" + +void +usage(void) +{ + const char *util = getnvmprogname(); + + fprintf(stderr, + "Modify Intel GbE NVM images e.g. set MAC\n" + "USAGE:\n" + "\t%s FILE dump\n" + "\t%s FILE setmac [MAC]\n" + "\t%s FILE swap\n" + "\t%s FILE copy 0|1\n" + "\t%s FILE cat\n" + "\t%s FILE cat16\n" + "\t%s FILE cat128\n", + util, util, util, util, + util, util, util); + + err(EINVAL, "Too few arguments"); +} diff --git a/util/libreboot-utils/lib/word.c b/util/libreboot-utils/lib/word.c new file mode 100644 index 00000000..f84dae6a --- /dev/null +++ b/util/libreboot-utils/lib/word.c @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2022-2026 Leah Rowe + * + * Manipulate Intel GbE NVM words, which are 16-bit little + * endian in the files (MAC address words are big endian). + */ + +#include + +#include +#include + +#include "../include/common.h" + +unsigned short +nvm_word(size_t pos16, size_t p) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + size_t pos; + + check_nvm_bound(pos16, p); + pos = (pos16 << 1) + (p * GBE_PART_SIZE); + + return (unsigned short)f->buf[pos] | + ((unsigned short)f->buf[pos + 1] << 8); +} + +void +set_nvm_word(size_t pos16, size_t p, unsigned short val16) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + size_t pos; + + check_nvm_bound(pos16, p); + pos = (pos16 << 1) + (p * GBE_PART_SIZE); + + f->buf[pos] = (unsigned char)(val16 & 0xff); + f->buf[pos + 1] = (unsigned char)(val16 >> 8); + + set_part_modified(p); +} + +void +set_part_modified(size_t p) +{ + struct xstate *x = xstatus(); + struct xfile *f = &x->f; + + check_bin(p, "part number"); + f->part_modified[p] = 1; +} + +void +check_nvm_bound(size_t c, size_t p) +{ + /* Block out of bound NVM access + */ + + check_bin(p, "part number"); + + if (c >= NVM_WORDS) + err(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..5e7fcc0f --- /dev/null +++ b/util/libreboot-utils/mkhtemp.c @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe + * + * 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. + * + * 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 /* pledge(2) */ +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/common.h" + +int +main(int argc, char *argv[]) +{ + char *s = NULL; + int fd = -1; + char c; + int type = MKHTEMP_FILE; + size_t len; + +#if defined (PATH_LEN) && \ + (PATH_LEN) >= 256 + size_t maxlen = PATH_LEN; +#else + size_t maxlen = 4096; +#endif + +/* https://man.openbsd.org/pledge.2 */ +#if defined(__OpenBSD__) && defined(OpenBSD) +#if (OpenBSD) >= 509 + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + err_no_cleanup(errno, "pledge, main"); +#endif +#endif + + while ((c = + getopt(argc, argv, "d")) != -1) { + + switch(c) { + case 'd': + + type = MKHTEMP_DIR; + break; + default: + + err_no_cleanup(EINVAL, + "usage: mkhtemp [-d]\n"); + } + } + + if (new_tmp_common(&fd, &s, type) < 0) + err_no_cleanup(errno, NULL); + +#if defined(__OpenBSD__) && defined(OpenBSD) +#if (OpenBSD) >= 509 + if (pledge("stdio", NULL) == -1) + err_no_cleanup(errno, "pledge, exit"); +#endif +#endif + + if (s == NULL) + err_no_cleanup(EFAULT, "bad string initialisation"); + + if (*s == '\0') + err_no_cleanup(EFAULT, "empty string initialisation"); + + if (slen(s, maxlen, &len) < 0) + err_no_cleanup(EFAULT, "unterminated string initialisation"); + + printf("%s\n", s); + + return EXIT_SUCCESS; +}/* + + + ( >:3 ) + /| |\ + / \ + + + + + + */ diff --git a/util/libreboot-utils/nvmutil.c b/util/libreboot-utils/nvmutil.c new file mode 100644 index 00000000..cb08ec43 --- /dev/null +++ b/util/libreboot-utils/nvmutil.c @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2022-2026 Leah Rowe + * + * 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 +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "include/common.h" + +int +main(int argc, char *argv[]) +{ + struct xstate *x; + + struct commands *cmd; + struct xfile *f; + + size_t c; + +/* 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(errno, "pledge plus unveil, main"); + if (unveil("/dev/null", "r") == -1) + err_no_cleanup(errno, "unveil r: /dev/null"); +#elif (OpenBSD) >= 509 + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + err_no_cleanup(errno, "pledge, main"); +#endif +#endif + +#ifndef S_ISREG + err_no_cleanup(ECANCELED, + "Can't determine file types (S_ISREG undefined)"); +#endif +#if ((CHAR_BIT) != 8) + err_no_cleanup(ECANCELED, "Unsupported char size"); +#endif + + x = xstart(argc, argv); + + if (x == NULL) + err_no_cleanup(ECANCELED, "NULL state on init"); + + 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) + 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 (unveil(us.f.tname, "rwc") == -1) + err(errno, "unveil rwc: %s", us.f.tname); + + if (unveil(NULL, NULL) == -1) + err(errno, "unveil block (rw)"); + + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + err(errno, "pledge (kill unveil)"); + +#elif (OpenBSD) >= 509 + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + err(errno, "pledge"); +#endif +#endif + + if (cmd->run == NULL) + err(errno, "Command not set"); + + 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) + 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); + f->tname = NULL; + } + + return EXIT_SUCCESS; +} diff --git a/util/nvmutil/.gitignore b/util/nvmutil/.gitignore deleted file mode 100644 index 9414c836..00000000 --- a/util/nvmutil/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/nvm -/nvmutil -*.bin -*.o -*.d diff --git a/util/nvmutil/AUTHORS b/util/nvmutil/AUTHORS deleted file mode 100644 index f38ea210..00000000 --- a/util/nvmutil/AUTHORS +++ /dev/null @@ -1,2 +0,0 @@ -Leah Rowe -Riku Viitanen diff --git a/util/nvmutil/COPYING b/util/nvmutil/COPYING deleted file mode 100644 index 47c35a86..00000000 --- a/util/nvmutil/COPYING +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (C) 2022-2026 Leah Rowe -Copyright (c) 2023 Riku Viitanen - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/util/nvmutil/Makefile b/util/nvmutil/Makefile deleted file mode 100644 index a2b65aeb..00000000 --- a/util/nvmutil/Makefile +++ /dev/null @@ -1,118 +0,0 @@ -# SPDX-License-Identifier: MIT -# Copyright (c) 2022,2026 Leah Rowe -# Copyright (c) 2023 Riku Viitanen - -# Makefile for nvmutil, which is an application -# that modifies Intel GbE NVM configurations. - -CC = cc -HELLCC = clang - -CFLAGS = -LDFLAGS = -DESTDIR = -PREFIX = /usr/local -INSTALL = install - -.SUFFIXES: .c .o - -LDIR = - -PORTABLE = $(LDIR) $(CFLAGS) -WARN = $(PORTABLE) -Wall -Wextra -STRICT = $(WARN) -std=c90 -pedantic -Werror -HELLFLAGS = $(STRICT) -Weverything - -PROG = nvmutil - -OBJS = \ - obj/nvmutil.o \ - obj/lib/state.o \ - obj/lib/file.o \ - obj/lib/string.o \ - obj/lib/usage.o \ - obj/lib/command.o \ - obj/lib/num.o \ - obj/lib/io.o \ - obj/lib/checksum.o \ - obj/lib/word.o \ - obj/lib/mkhtemp.o - -# default mode -CFLAGS_MODE = $(PORTABLE) -CC_MODE = $(CC) - -all: $(PROG) - -$(PROG): $(OBJS) - $(CC_MODE) $(OBJS) -o $(PROG) $(LDFLAGS) - -# ensure obj directory exists -$(OBJS): obj - -obj: - mkdir obj || true - mkdir obj/lib || true - -# main program object - -obj/nvmutil.o: nvmutil.c - $(CC_MODE) $(CFLAGS_MODE) -c nvmutil.c -o obj/nvmutil.o - -# library/helper objects - -obj/lib/state.o: lib/state.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/state.c -o obj/lib/state.o - -obj/lib/file.o: lib/file.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/file.c -o obj/lib/file.o - -obj/lib/string.o: lib/string.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/string.c -o obj/lib/string.o - -obj/lib/usage.o: lib/usage.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/usage.c -o obj/lib/usage.o - -obj/lib/command.o: lib/command.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/command.c -o obj/lib/command.o - -obj/lib/num.o: lib/num.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/num.c -o obj/lib/num.o - -obj/lib/io.o: lib/io.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/io.c -o obj/lib/io.o - -obj/lib/checksum.o: lib/checksum.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/checksum.c -o obj/lib/checksum.o - -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 - -# install - -install: $(PROG) - $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG) - chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG) - -uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) - -clean: - rm -f $(PROG) $(OBJS) - -distclean: clean - -# mode targets (portable replacement for ifeq) - -warn: - $(MAKE) CFLAGS_MODE="$(WARN)" - -strict: - $(MAKE) CFLAGS_MODE="$(STRICT)" - -hell: - $(MAKE) CFLAGS_MODE="$(HELLFLAGS)" CC_MODE="$(HELLCC)" diff --git a/util/nvmutil/include/common.h b/util/nvmutil/include/common.h deleted file mode 100644 index 4aca1772..00000000 --- a/util/nvmutil/include/common.h +++ /dev/null @@ -1,606 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe - */ - - -#ifndef COMMON_H -#define COMMON_H - -#include -#include -#include - -/* for linux getrandom - */ -#if defined(__linux__) -#include -#if defined(__has_include) -#if __has_include() -#include -#define HAVE_GETRANDOM 1 -#endif -#endif -#if !defined(HAVE_GETRANDOM) -#include -#if defined(SYS_getrandom) -#define HAVE_GETRANDOM_SYSCALL 1 -#endif -#endif - -#endif - -#define items(x) (sizeof((x)) / sizeof((x)[0])) - -/* system prototypes - */ - -int fchmod(int fd, mode_t mode); - -#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 SSIZE_MAX -#define SSIZE_MAX ((ssize_t)(~((ssize_t)1 << (sizeof(ssize_t)*CHAR_BIT-1)))) -#endif - - -/* build config - */ - -#ifndef NVMUTIL_H -#define NVMUTIL_H - -#define MAX_CMD_LEN 50 - -#ifndef PATH_LEN -#define PATH_LEN 4096 -#endif - -#define OFF_ERR 0 -#ifndef OFF_RESET -#define OFF_RESET 1 -#endif - -#ifndef S_ISVTX -#define S_ISVTX 01000 -#endif - -#if defined(S_IFMT) && ((S_ISVTX & S_IFMT) != 0) -#error "Unexpected bit layout" -#endif - -#ifndef MAX_ZERO_RW_RETRY -#define MAX_ZERO_RW_RETRY 5 -#endif - -#ifndef REAL_POS_IO -#define REAL_POS_IO 0 -#endif - -#ifndef LOOP_EAGAIN -#define LOOP_EAGAIN 1 -#endif -#ifndef LOOP_EINTR -#define LOOP_EINTR 1 -#endif - -#ifndef _FILE_OFFSET_BITS -#define _FILE_OFFSET_BITS 64 -#endif - -#ifndef EXIT_FAILURE -#define EXIT_FAILURE 1 -#endif - -#ifndef EXIT_SUCCESS -#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 - -#ifndef O_BINARY -#define O_BINARY 0 -#endif - -#ifndef O_EXCL -#define O_EXCL 0 -#endif - -#ifndef O_CREAT -#define O_CREAT 0 -#endif - -#ifndef O_NONBLOCK -#define O_NONBLOCK 0 -#endif - -#ifndef O_CLOEXEC -#define O_CLOEXEC 0 -#endif - -#ifndef O_NOFOLLOW -#define O_NOFOLLOW 0 -#endif - -#ifndef FD_CLOEXEC -#define FD_CLOEXEC 0 -#endif - -/* Sizes in bytes: - */ - -#define SIZE_1KB 1024 -#define SIZE_4KB (4 * SIZE_1KB) -#define SIZE_8KB (8 * SIZE_1KB) -#define SIZE_16KB (16 * SIZE_1KB) -#define SIZE_128KB (128 * SIZE_1KB) - -#define GBE_BUF_SIZE (SIZE_128KB) - -/* First 128 bytes of gbe.bin is NVM. - * Then extended area. All of NVM must - * add up to BABA, truncated (LE) - * - * First 4KB of each half of the file - * contains NVM+extended. - */ - -#define GBE_WORK_SIZE (SIZE_8KB) -#define GBE_PART_SIZE (GBE_WORK_SIZE >> 1) -#define NVM_CHECKSUM 0xBABA -#define NVM_SIZE 128 -#define NVM_WORDS (NVM_SIZE >> 1) -#define NVM_CHECKSUM_WORD (NVM_WORDS - 1) - -/* argc minimum (dispatch) - */ - -#define ARGC_3 3 -#define ARGC_4 4 - -#define NO_LOOP_EAGAIN 0 -#define NO_LOOP_EINTR 0 - -/* For checking if an fd is a normal file. - * Portable for old Unix e.g. v7 (S_IFREG), - * 4.2BSD (S_IFMT), POSIX (S_ISREG). - * - * IFREG: assumed 0100000 (classic bitmask) - */ - -#ifndef S_ISREG -#if defined(S_IFMT) && defined(S_IFREG) -#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) -#elif defined(S_IFREG) -#define S_ISREG(m) (((m) & S_IFREG) != 0) -#else -#error "can't determine types with stat()" -#endif -#endif - -#define IO_READ 0 -#define IO_WRITE 1 -#define IO_PREAD 2 -#define IO_PWRITE 3 - -/* for nvmutil commands - */ - -#define CMD_DUMP 0 -#define CMD_SETMAC 1 -#define CMD_SWAP 2 -#define CMD_COPY 3 -#define CMD_CAT 4 -#define CMD_CAT16 5 -#define CMD_CAT128 6 - -#define ARG_NOPART 0 -#define ARG_PART 1 - -#define SKIP_CHECKSUM_READ 0 -#define CHECKSUM_READ 1 - -#define SKIP_CHECKSUM_WRITE 0 -#define CHECKSUM_WRITE 1 - -/* command table - */ - -struct commands { - size_t chk; - char *str; - void (*run)(void); - int argc; - unsigned char arg_part; - unsigned char chksum_read; - unsigned char chksum_write; - size_t rw_size; /* within the 4KB GbE part */ - int flags; /* e.g. O_RDWR or O_RDONLY */ -}; - -/* mac address - */ - -struct macaddr { - char *str; /* set to rmac, or argv string */ - char rmac[18]; /* xx:xx:xx:xx:xx:xx */ - unsigned short mac_buf[3]; -}; - -/* gbe.bin and tmpfile - */ - -struct xfile { - int gbe_fd; - struct stat gbe_st; - - int tmp_fd; - struct stat tmp_st; - - char *tname; /* path of tmp file */ - char *fname; /* path of gbe file */ - - unsigned char *buf; /* work memory for files */ - - int io_err_gbe; /* intermediary write (verification) */ - int io_err_gbe_bin; /* final write (real file) */ - int rw_check_err_read[2]; - int rw_check_partial_read[2]; - int rw_check_bad_part[2]; - - int post_rw_checksum[2]; - - off_t gbe_file_size; - off_t gbe_tmp_size; - - size_t part; - unsigned char part_modified[2]; - unsigned char part_valid[2]; - - unsigned char real_buf[GBE_BUF_SIZE]; - unsigned char bufcmp[GBE_BUF_SIZE]; /* compare gbe/tmp/reads */ - - unsigned char pad[GBE_WORK_SIZE]; /* the file that wouldn't die */ -}; - -/* Command table, MAC address, files - * - * BE CAREFUL when editing this - * to ensure that you also update - * the tables in xstatus() - */ - -struct xstate { - struct commands cmd[7]; - struct macaddr mac; - struct xfile f; - - char *argv0; - - size_t i; /* index to cmd[] for current command */ - int no_cmd; - - /* Cat commands set this. - the cat cmd helpers check it */ - int cat; -}; - -struct filesystem { - int rootfd; -}; - -struct xstate *xstart(int argc, char *argv[]); -struct xstate *xstatus(void); - -/* Sanitize command tables. - */ - -void sanitize_command_list(void); -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[]); -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); - -/* Read GbE file and verify checksums - */ - -void copy_gbe(void); -void read_file(void); -void read_checksums(void); -int good_checksum(size_t partnum); - -/* validate commands - */ - -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); -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); - -/* numerical functions - */ - -unsigned short hextonum(char ch_s); -size_t 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 -size_t fallback_rand_1989(void); -size_t entropy_jitter(void); -#endif - -/* Helper functions for command: dump - */ - -void cmd_helper_dump(void); -void print_mac_from_nvm(size_t partnum); -void hexdump(size_t partnum); - -/* Helper functions for command: swap - */ - -void cmd_helper_swap(void); - -/* Helper functions for command: copy - */ - -void cmd_helper_copy(void); - -/* Helper functions for commands: - * cat, cat16 and cat128 - */ - -void cmd_helper_cat(void); -void cmd_helper_cat16(void); -void cmd_helper_cat128(void); -void cat(size_t nff); -void cat_buf(unsigned char *b); - -/* Command verification/control - */ - -void check_cmd(void (*fn)(void), const char *name); -void cmd_helper_err(void); - -/* Write GbE files to disk - */ - -void write_gbe_file(void); -void set_checksum(size_t part); -unsigned short calculated_checksum(size_t p); - -/* NVM read/write - */ - -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(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(size_t p); -void report_io_err_rw(void); -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); -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); -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, - 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, size_t nrw, - off_t off, int rw_type); -int check_file(int fd, struct stat *st); -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 -int try_err(int loop_err, int errval); - -/* Error handling and cleanup - */ - -void usage(void); -void err_no_cleanup(int nvm_errval, const char *msg, ...); -void err(int nvm_errval, const char *msg, ...); -int exit_cleanup(void); -const char *getnvmprogname(void); - -/* libc hardening - */ - -int new_tmpfile(int *fd, char **path); -int new_tmpdir(int *fd, char **path); -static int new_tmp_common(int *fd, char **path, int type); -static int mkhtemp_try_create(int dirfd, - struct stat *st_dir_initial, - char *fname_copy, - char *p, - size_t xc, - int *fd, - struct stat *st, - int type); -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); -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); -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); - -/* asserts */ - -/* type asserts */ -typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1]; -typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1]; -typedef char static_assert_unsigned_char_is_1[ - (sizeof(unsigned char) == 1) ? 1 : -1]; -typedef char static_assert_unsigned_short_is_2[ - (sizeof(unsigned short) >= 2) ? 1 : -1]; -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_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_ssize_t_ptr[ - (sizeof(size_t) >= sizeof(void *)) ? 1 : -1 -]; - -/* - * We set _FILE_OFFSET_BITS 64, but we only handle - * but we only need smaller files, so require 4-bytes. - * Some operating systems ignore the define, hence assert: - */ -typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1]; - -/* - * asserts (variables/defines sanity check) - */ -typedef char assert_argc3[(ARGC_3==3)?1:-1]; -typedef char assert_argc4[(ARGC_4==4)?1:-1]; -typedef char assert_read[(IO_READ==0)?1:-1]; -typedef char assert_write[(IO_WRITE==1)?1:-1]; -typedef char assert_pread[(IO_PREAD==2)?1:-1]; -typedef char assert_pwrite[(IO_PWRITE==3)?1:-1]; -typedef char assert_pathlen[(PATH_LEN>=256)?1:-1]; -/* commands */ -typedef char assert_cmd_dump[(CMD_DUMP==0)?1:-1]; -typedef char assert_cmd_setmac[(CMD_SETMAC==1)?1:-1]; -typedef char assert_cmd_swap[(CMD_SWAP==2)?1:-1]; -typedef char assert_cmd_copy[(CMD_COPY==3)?1:-1]; -typedef char assert_cmd_cat[(CMD_CAT==4)?1:-1]; -typedef char assert_cmd_cat16[(CMD_CAT16==5)?1:-1]; -typedef char assert_cmd_cat128[(CMD_CAT128==6)?1:-1]; -/* bool */ -typedef char bool_arg_nopart[(ARG_NOPART==0)?1:-1]; -typedef char bool_arg_part[(ARG_PART==1)?1:-1]; -typedef char bool_skip_checksum_read[(SKIP_CHECKSUM_READ==0)?1:-1]; -typedef char bool_checksum_read[(CHECKSUM_READ==1)?1:-1]; -typedef char bool_skip_checksum_write[(SKIP_CHECKSUM_WRITE==0)?1:-1]; -typedef char bool_checksum_write[(CHECKSUM_WRITE==1)?1:-1]; -typedef char bool_loop_eintr[(LOOP_EINTR==1||LOOP_EINTR==0)?1:-1]; -typedef char bool_loop_eagain[(LOOP_EAGAIN==1||LOOP_EAGAIN==0)?1:-1]; -typedef char bool_no_loop_eintr[(NO_LOOP_EINTR==0)?1:-1]; -typedef char bool_no_loop_eagain[(NO_LOOP_EAGAIN==0)?1:-1]; -typedef char bool_off_err[(OFF_ERR==0)?1:-1]; -typedef char bool_off_reset[(OFF_RESET==0||OFF_RESET==1)?1:-1]; - -#endif -#endif diff --git a/util/nvmutil/lib/checksum.c b/util/nvmutil/lib/checksum.c deleted file mode 100644 index b417dc7e..00000000 --- a/util/nvmutil/lib/checksum.c +++ /dev/null @@ -1,108 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe - * - * Functions related to GbE NVM checksums. - */ - -#include -#include - -#include -#include -#include -#include - -#include "../include/common.h" - -void -read_checksums(void) -{ - struct xstate *x = xstatus(); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - size_t _p; - size_t _skip_part; - - unsigned char _num_invalid; - unsigned char _max_invalid; - - f->part_valid[0] = 0; - f->part_valid[1] = 0; - - if (!cmd->chksum_read) - return; - - _num_invalid = 0; - _max_invalid = 2; - - if (cmd->arg_part) - _max_invalid = 1; - - /* Skip verification on this part, - * but only when arg_part is set. - */ - _skip_part = f->part ^ 1; - - for (_p = 0; _p < 2; _p++) { - - /* Only verify a part if it was *read* - */ - if (cmd->arg_part && (_p == _skip_part)) - continue; - - f->part_valid[_p] = good_checksum(_p); - if (!f->part_valid[_p]) - ++_num_invalid; - } - - if (_num_invalid >= _max_invalid) { - - if (_max_invalid == 1) - err(ECANCELED, "%s: part %lu has a bad checksum", - f->fname, (size_t)f->part); - - err(ECANCELED, "%s: No valid checksum found in file", - f->fname); - } -} - -int -good_checksum(size_t partnum) -{ - unsigned short expected_checksum; - unsigned short actual_checksum; - - expected_checksum = - calculated_checksum(partnum); - - actual_checksum = - nvm_word(NVM_CHECKSUM_WORD, partnum); - - if (expected_checksum == actual_checksum) { - return 1; - } else { - return 0; - } -} - -void -set_checksum(size_t p) -{ - check_bin(p, "part number"); - set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p)); -} - -unsigned short -calculated_checksum(size_t p) -{ - size_t c; - unsigned int val16; - - val16 = 0; - - for (c = 0; c < NVM_CHECKSUM_WORD; c++) - val16 += (unsigned int)nvm_word(c, p); - - return (unsigned short)((NVM_CHECKSUM - val16) & 0xffff); -} diff --git a/util/nvmutil/lib/command.c b/util/nvmutil/lib/command.c deleted file mode 100644 index 3a863d23..00000000 --- a/util/nvmutil/lib/command.c +++ /dev/null @@ -1,563 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../include/common.h" - -/* Guard against regressions by maintainers (command table) - */ - -void -sanitize_command_list(void) -{ - struct xstate *x = xstatus(); - - size_t c; - size_t num_commands; - - num_commands = items(x->cmd); - - for (c = 0; c < num_commands; c++) - sanitize_command_index(c); -} - -void -sanitize_command_index(size_t c) -{ - struct xstate *x = xstatus(); - struct commands *cmd = &x->cmd[c]; - - int _flag; - 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", - (size_t)c, cmd->argc); - - if (cmd->str == NULL) - err(EINVAL, "cmd index %lu: NULL str", - (size_t)c); - - if (*cmd->str == '\0') - err(EINVAL, "cmd index %lu: empty str", - (size_t)c); - - if (slen(cmd->str, MAX_CMD_LEN +1, &rval) < 0) - err(errno, "Could not get command length"); - - if (rval > MAX_CMD_LEN) { - err(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", - (size_t)c); - - check_bin(cmd->arg_part, "cmd.arg_part"); - check_bin(cmd->chksum_read, "cmd.chksum_read"); - check_bin(cmd->chksum_write, "cmd.chksum_write"); - - gbe_rw_size = cmd->rw_size; - - switch (gbe_rw_size) { - case GBE_PART_SIZE: - case NVM_SIZE: - break; - default: - err(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", - (size_t)gbe_rw_size); - - _flag = (cmd->flags & O_ACCMODE); - - if (_flag != O_RDONLY && - _flag != O_RDWR) - err(EINVAL, "invalid cmd.flags setting"); -} - -void -set_cmd(int argc, char *argv[]) -{ - struct xstate *x = xstatus(); - const char *cmd; - - int rval; - - size_t c; - - for (c = 0; c < items(x->cmd); c++) { - - cmd = x->cmd[c].str; - - if (scmp(argv[2], cmd, MAX_CMD_LEN, &rval) < 0) - err_no_cleanup(EINVAL, - "could not compare command strings"); - if (rval != 0) - continue; /* not the right command */ - - /* valid command found */ - if (argc >= x->cmd[c].argc) { - x->no_cmd = 0; - x->i = c; /* set command */ - - return; - } - - err_no_cleanup(EINVAL, - "Too few args on command '%s'", cmd); - } - - x->no_cmd = 1; -} - -void -set_cmd_args(int argc, char *argv[]) -{ - struct xstate *x = xstatus(); - size_t i = x->i; - struct commands *cmd = &x->cmd[i]; - struct xfile *f = &x->f; - - if (!valid_command(i) || argc < 3) - usage(); - - if (x->no_cmd) - usage(); - - /* Maintainer bug - */ - if (cmd->arg_part && argc < 4) - err(EINVAL, - "arg_part set for command that needs argc4"); - - if (cmd->arg_part && i == CMD_SETMAC) - err(EINVAL, - "arg_part set on CMD_SETMAC"); - - if (i == CMD_SETMAC) { - - if (argc >= 4) - x->mac.str = argv[3]; - else - x->mac.str = x->mac.rmac; - - } else if (cmd->arg_part) { - - f->part = conv_argv_part_num(argv[3]); - } -} - -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); - - /* char signedness is implementation-defined - */ - ch = (unsigned char)part_str[0]; - if (ch < '0' || ch > '1') - err(EINVAL, "Bad part number (%c)", ch); - - return (size_t)(ch - '0'); -} - -void -check_command_num(size_t c) -{ - if (!valid_command(c)) - err(EINVAL, "Invalid run_cmd arg: %lu", - (size_t)c); -} - -unsigned char -valid_command(size_t c) -{ - struct xstate *x = xstatus(); - struct commands *cmd; - - if (c >= items(x->cmd)) - return 0; - - cmd = &x->cmd[c]; - - if (c != cmd->chk) - err(EINVAL, - "Invalid cmd chk value (%lu) vs arg: %lu", - cmd->chk, c); - - return 1; -} - -void -cmd_helper_setmac(void) -{ - struct xstate *x = xstatus(); - struct macaddr *mac = &x->mac; - - size_t partnum; - - check_cmd(cmd_helper_setmac, "setmac"); - - printf("MAC address to be written: %s\n", mac->str); - parse_mac_string(); - - for (partnum = 0; partnum < 2; partnum++) - write_mac_part(partnum); -} - -void -parse_mac_string(void) -{ - struct xstate *x = xstatus(); - struct macaddr *mac = &x->mac; - - size_t mac_byte; - - size_t rval; - - if (slen(x->mac.str, 18, &rval) < 0) - err(EINVAL, "Could not determine MAC length"); - - if (rval != 17) - err(EINVAL, "MAC address is the wrong length"); - - memset(mac->mac_buf, 0, sizeof(mac->mac_buf)); - - for (mac_byte = 0; mac_byte < 6; mac_byte++) - 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"); - - if (mac->mac_buf[0] & 1) - err(EINVAL, "Must not specify multicast MAC address"); -} - -void -set_mac_byte(size_t mac_byte_pos) -{ - struct xstate *x = xstatus(); - struct macaddr *mac = &x->mac; - - char separator; - - 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'", - separator); - } - - for (mac_nib_pos = 0; mac_nib_pos < 2; mac_nib_pos++) - set_mac_nib(mac_str_pos, mac_byte_pos, mac_nib_pos); -} - -void -set_mac_nib(size_t mac_str_pos, - size_t mac_byte_pos, size_t mac_nib_pos) -{ - struct xstate *x = xstatus(); - struct macaddr *mac = &x->mac; - - char mac_ch; - unsigned short hex_num; - - mac_ch = mac->str[mac_str_pos + mac_nib_pos]; - - if ((hex_num = hextonum(mac_ch)) > 15) { - if (hex_num >= 17) - err(EIO, "Randomisation failure"); - else - err(EINVAL, "Invalid character '%c'", - mac->str[mac_str_pos + mac_nib_pos]); - } - - /* If random, ensure that local/unicast bits are set. - */ - if ((mac_byte_pos == 0) && (mac_nib_pos == 1) && - ((mac_ch | 0x20) == 'x' || - (mac_ch == '?'))) - hex_num = (hex_num & 0xE) | 2; /* local, unicast */ - - /* MAC words stored big endian in-file, little-endian - * logically, so we reverse the order. - */ - mac->mac_buf[mac_byte_pos >> 1] |= hex_num << - (((mac_byte_pos & 1) << 3) /* left or right byte? */ - | ((mac_nib_pos ^ 1) << 2)); /* left or right nib? */ -} - -void -write_mac_part(size_t partnum) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - struct macaddr *mac = &x->mac; - - size_t w; - - check_bin(partnum, "part number"); - if (!f->part_valid[partnum]) - return; - - for (w = 0; w < 3; w++) - set_nvm_word(w, partnum, mac->mac_buf[w]); - - printf("Wrote MAC address to part %lu: ", - (size_t)partnum); - print_mac_from_nvm(partnum); -} - -void -cmd_helper_dump(void) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - size_t p; - - check_cmd(cmd_helper_dump, "dump"); - - f->part_valid[0] = good_checksum(0); - f->part_valid[1] = good_checksum(1); - - for (p = 0; p < 2; p++) { - - if (!f->part_valid[p]) { - - fprintf(stderr, - "BAD checksum %04x in part %lu (expected %04x)\n", - nvm_word(NVM_CHECKSUM_WORD, p), - (size_t)p, - calculated_checksum(p)); - } - - printf("MAC (part %lu): ", - (size_t)p); - - print_mac_from_nvm(p); - - hexdump(p); - } -} - -void -print_mac_from_nvm(size_t partnum) -{ - size_t c; - unsigned short val16; - - for (c = 0; c < 3; c++) { - - val16 = nvm_word(c, partnum); - - printf("%02x:%02x", - (unsigned int)(val16 & 0xff), - (unsigned int)(val16 >> 8)); - - if (c == 2) - printf("\n"); - else - printf(":"); - } -} - -void -hexdump(size_t partnum) -{ - size_t c; - size_t row; - unsigned short val16; - - for (row = 0; row < 8; row++) { - - printf("%08lx ", - (size_t)((size_t)row << 4)); - - for (c = 0; c < 8; c++) { - - val16 = nvm_word((row << 3) + c, partnum); - - if (c == 4) - printf(" "); - - printf(" %02x %02x", - (unsigned int)(val16 & 0xff), - (unsigned int)(val16 >> 8)); - - } - - printf("\n"); - } -} - -void -cmd_helper_swap(void) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - check_cmd(cmd_helper_swap, "swap"); - - memcpy( - f->buf + (size_t)GBE_WORK_SIZE, - f->buf, - GBE_PART_SIZE); - - memcpy( - f->buf, - f->buf + (size_t)GBE_PART_SIZE, - GBE_PART_SIZE); - - memcpy( - f->buf + (size_t)GBE_PART_SIZE, - f->buf + (size_t)GBE_WORK_SIZE, - GBE_PART_SIZE); - - set_part_modified(0); - set_part_modified(1); -} - -void -cmd_helper_copy(void) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - check_cmd(cmd_helper_copy, "copy"); - - memcpy( - 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); -} - -void -cmd_helper_cat(void) -{ - struct xstate *x = xstatus(); - - check_cmd(cmd_helper_cat, "cat"); - - x->cat = 0; - cat(0); -} - -void -cmd_helper_cat16(void) -{ - struct xstate *x = xstatus(); - - check_cmd(cmd_helper_cat16, "cat16"); - - x->cat = 1; - cat(1); -} - -void -cmd_helper_cat128(void) -{ - struct xstate *x = xstatus(); - - check_cmd(cmd_helper_cat128, "cat128"); - - x->cat = 15; - cat(15); -} - -void -cat(size_t nff) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - size_t p; - size_t ff; - - p = 0; - ff = 0; - - if ((size_t)x->cat != nff) { - - err(ECANCELED, "erroneous call to cat"); - } - - fflush(NULL); - - memset(f->pad, 0xff, GBE_PART_SIZE); - - for (p = 0; p < 2; p++) { - - cat_buf(f->bufcmp + - (size_t)(p * (f->gbe_file_size >> 1))); - - for (ff = 0; ff < nff; ff++) { - - cat_buf(f->pad); - } - } -} - -void -cat_buf(unsigned char *b) -{ - if (b == NULL) - err(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"); -} -void -check_cmd(void (*fn)(void), - const char *name) -{ - struct xstate *x = xstatus(); - size_t i = x->i; - - if (x->cmd[i].run != fn) - err(ECANCELED, "Running %s, but cmd %s is set", - name, x->cmd[i].str); - - /* prevent second command - */ - for (i = 0; i < items(x->cmd); i++) - x->cmd[i].run = cmd_helper_err; -} - -void -cmd_helper_err(void) -{ - err(ECANCELED, - "Erroneously running command twice"); -} diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c deleted file mode 100644 index ea2bcd0b..00000000 --- a/util/nvmutil/lib/file.c +++ /dev/null @@ -1,1136 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe - * - * Pathless i/o, and some stuff you probably never saw. - * Be nice to the demon. - */ - - -#include -#include - -#include -#include -#include -#include -#include -#include - -/* for openat2: */ -#ifdef __linux__ -#include -#include -#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(errno, "%s", path); - - if (fstat(*fd_ptr, st) < 0) - err(errno, "%s: stat", path); - - if (!S_ISREG(st->st_mode)) - err(errno, "%s: not a regular file", path); - - if (lseek_on_eintr(*fd_ptr, 0, SEEK_CUR, 1, 1) == (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; - - 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 (path == NULL) { - errno = EFAULT; - goto err_fsync_dir; - } - - if (slen(path, maxlen, &pathlen) < 0) - goto err_fsync_dir; - - if (pathlen >= maxlen || pathlen < 0) { - errno = EMSGSIZE; - goto err_fsync_dir; - } - - if (pathlen == 0) - { - errno = EINVAL; - goto err_fsync_dir; - } - - dirbuf = malloc(pathlen + 1); - if (dirbuf == NULL) { - - errno = ENOMEM; - 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 (dirfd < 0) - goto err_fsync_dir; - - if (fstat(dirfd, &st) < 0) - goto err_fsync_dir; - - if (!S_ISDIR(st.st_mode)) { - - errno = ENOTDIR; - goto err_fsync_dir; - } - - /* sync file on disk */ - if (fsync_on_eintr(dirfd) == -1) - goto err_fsync_dir; - - if (close_on_eintr(dirfd) == -1) { - - dirfd = -1; - goto err_fsync_dir; - } - - if (dirbuf != NULL) { - - free(dirbuf); - dirbuf = NULL; - } - - dirbuf = NULL; - - errno = saved_errno; - return 0; - -err_fsync_dir: - - if (errno == saved_errno) - errno = EIO; - - if (dirbuf != NULL) { - - free(dirbuf); - dirbuf = NULL; - } - - if (dirfd >= 0) { - - close_errno = errno; - (void) close_on_eintr(dirfd); - errno = close_errno; - dirfd = -1; - } - - 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; - - /* obviously */ - if (mem == NULL) { - - errno = EFAULT; - goto err_io_args; - } - - /* uninitialised fd */ - if (fd < 0) { - - errno = EBADF; - goto err_io_args; - } - - /* negative offset */ - if (off < 0) { - - errno = ERANGE; - goto err_io_args; - } - - /* prevent zero-byte rw */ - if (!nrw) - goto err_io_args; - - /* prevent overflow */ - if (nrw > (size_t)SSIZE_MAX) { - - errno = ERANGE; - goto err_io_args; - } - - /* prevent overflow */ - if (((size_t)off + nrw) < (size_t)off) { - - errno = ERANGE; - goto err_io_args; - } - - if (rw_type > IO_PWRITE) { - - errno = 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 (fd < 0) { - errno = EBADF; - goto err_is_file; - } - - if (st == NULL) { - errno = EFAULT; - goto err_is_file; - } - - if (fstat(fd, st) == -1) - goto err_is_file; - - if (!S_ISREG(st->st_mode)) { - - errno = 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; - - /* 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 ((size_t) - r > SSIZE_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 - * ssize_t integers are the - * same size as SSIZE_T - */ - - errno = ERANGE; - goto err_rw_over_nrw; - } - - /* Theoretical buggy libc: - * Should never return a number of - * bytes above the requested length. - */ - if ((size_t)r > nrw) { - - errno = 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 - -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 || 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 (new == NULL || old == NULL) { - - errno = EFAULT; - return -1; - } - - if (olddirfd < 0 || newdirfd < 0) { - - errno = 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; - const char *rel; - - if (path == NULL) { - errno = EFAULT; - return -1; - } - - if (path[0] != '/') { - errno = EINVAL; - return -1; - } - - fs = rootfs(); - if (fs == NULL) - return -1; - - rel = path + 1; - - return fs_resolve_at(fs->rootfd, rel, 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; - char name[256]; - 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 (fd < 0) { - errno = EBADF; - return -1; - } - - if (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) - return -1; - - if (slen(path, maxlen, &len) < 0) - return -1; - - buf = malloc(len + 1); - if (buf == 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(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 (dirfd < 0) { - errno = EBADF; - return -1; - } - - if (path == NULL) { - errno = 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 (dirfd < 0) { - errno = EBADF; - return -1; - } - - if (path == NULL) { - errno = 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/nvmutil/lib/io.c deleted file mode 100644 index 94bde87e..00000000 --- a/util/nvmutil/lib/io.c +++ /dev/null @@ -1,673 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe - * - * I/O functions specific to nvmutil. - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../include/common.h" - -void -open_gbe_file(void) -{ - struct xstate *x = xstatus(); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - int _flags; - - xopen(&f->gbe_fd, f->fname, - cmd->flags | O_BINARY | - O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, &f->gbe_st); - - if (f->gbe_st.st_nlink > 1) - err(EINVAL, - "%s: warning: file has multiple (%lu) hard links\n", - 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); - - _flags = fcntl(f->gbe_fd, F_GETFL); - if (_flags == -1) - err(errno, "%s: fcntl(F_GETFL)", f->fname); - - /* O_APPEND allows POSIX write() to ignore - * the current write offset and write at EOF, - * which would break positional read/write - */ - - if (_flags & O_APPEND) - err(EIO, "%s: O_APPEND flag", f->fname); - - f->gbe_file_size = f->gbe_st.st_size; - - switch (f->gbe_file_size) { - case SIZE_8KB: - case SIZE_16KB: - case SIZE_128KB: - break; - default: - err(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); -} - -void -copy_gbe(void) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - read_file(); - - if (f->gbe_file_size == SIZE_8KB) - return; - - 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(); - struct xfile *f = &x->f; - - struct stat _st; - ssize_t _r; - - /* read main file - */ - _r = rw_file_exact(f->gbe_fd, f->buf, 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", f->fname); - - /* copy to tmpfile - */ - _r = rw_file_exact(f->tmp_fd, f->buf, f->gbe_file_size, - 0, IO_PWRITE, NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (_r < 0) - err(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); - - f->gbe_tmp_size = _st.st_size; - - if (f->gbe_tmp_size != f->gbe_file_size) - err(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); - - _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); - - if (memcmp(f->buf, f->bufcmp, f->gbe_file_size) != 0) - err(errno, "%s: %s: read contents differ (pre-test)", - f->fname, f->tname); -} - -void -write_gbe_file(void) -{ - struct xstate *x = xstatus(); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - 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); - - if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0) - err(errno, "%s: file has changed", f->fname); - - update_checksum = cmd->chksum_write; - - for (p = 0; p < 2; p++) { - if (!f->part_modified[p]) - continue; - - if (update_checksum) - set_checksum(p); - - rw_gbe_file_part(p, IO_PWRITE, "pwrite"); - } -} - -void -rw_gbe_file_part(size_t p, int rw_type, - const char *rw_type_str) -{ - struct xstate *x = xstatus(); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - ssize_t rval; - - off_t file_offset; - - 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, (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); - - rval = rw_gbe_file_exact(f->tmp_fd, mem_offset, - gbe_rw_size, file_offset, rw_type); - - if (rval == -1) - err(errno, "%s: %s: part %lu", - f->fname, rw_type_str, (size_t)p); - - if ((size_t)rval != gbe_rw_size) - err(EIO, "%s: partial %s: part %lu", - f->fname, rw_type_str, (size_t)p); -} - -void -write_to_gbe_bin(void) -{ - struct xstate *x = xstatus(); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - int saved_errno; - int mv; - - if ((cmd->flags & O_ACCMODE) != O_RDWR) - return; - - write_gbe_file(); - - /* We may otherwise read from - * cache, so we must sync. - */ - - if (fsync_on_eintr(f->tmp_fd) == -1) - err(errno, "%s: fsync (pre-verification)", - f->tname); - - check_written_part(0); - check_written_part(1); - - report_io_err_rw(); - - if (f->io_err_gbe) - err(EIO, "%s: bad write", f->fname); - - saved_errno = errno; - - if (close_on_eintr(f->tmp_fd) == -1) { - f->tmp_fd = -1; - - fprintf(stderr, "FAIL: %s: close\n", f->tname); - f->io_err_gbe_bin = 1; - } - f->tmp_fd = -1; - - if (close_on_eintr(f->gbe_fd) == -1) { - f->gbe_fd = -1; - - fprintf(stderr, "FAIL: %s: close\n", f->fname); - f->io_err_gbe_bin = 1; - } - f->gbe_fd = -1; - - errno = saved_errno; - - /* tmpfile written, now we - * rename it back to the main file - * (we do atomic writes) - */ - - f->tmp_fd = -1; - f->gbe_fd = -1; - - if (!f->io_err_gbe_bin) { - - mv = gbe_mv(); - - if (mv < 0) { - - f->io_err_gbe_bin = 1; - - fprintf(stderr, "%s: %s\n", - f->fname, strerror(errno)); - } else { - - /* removed by rename - */ - - if (f->tname != NULL) { - free(f->tname); - f->tname = NULL; - } - - f->tname = NULL; - } - } - - if (!f->io_err_gbe_bin) - return; - - fprintf(stderr, "FAIL (rename): %s: skipping fsync\n", - f->fname); - if (errno) - fprintf(stderr, - "errno %d: %s\n", errno, strerror(errno)); -} - -void -check_written_part(size_t p) -{ - struct xstate *x = xstatus(); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - ssize_t rval; - - size_t gbe_rw_size; - - off_t file_offset; - unsigned char *mem_offset; - - unsigned char *buf_restore; - - if (!f->part_modified[p]) - return; - - gbe_rw_size = cmd->rw_size; - - mem_offset = gbe_mem_offset(p, "pwrite"); - file_offset = (off_t)gbe_file_offset(p, "pwrite"); - - 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); - - if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0) - err(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 ((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; - - if (f->rw_check_err_read[p] || - f->rw_check_partial_read[p]) - return; - - /* We only load one part on-file, into memory but - * always at offset zero, for post-write checks. - * That's why we hardcode good_checksum(0) - */ - - buf_restore = f->buf; - - /* good_checksum works on f->buf - * so let's change f->buf for now - */ - - f->buf = f->pad; - - if (good_checksum(0)) - f->post_rw_checksum[p] = 1; - - f->buf = buf_restore; -} - -void -report_io_err_rw(void) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - size_t p; - - if (!f->io_err_gbe) - return; - - for (p = 0; p < 2; p++) { - if (!f->part_modified[p]) - continue; - - if (f->rw_check_err_read[p]) - fprintf(stderr, - "%s: pread: p%lu (post-verification)\n", - f->fname, (size_t)p); - if (f->rw_check_partial_read[p]) - fprintf(stderr, - "%s: partial pread: p%lu (post-verification)\n", - f->fname, (size_t)p); - if (f->rw_check_bad_part[p]) - fprintf(stderr, - "%s: pwrite: corrupt write on p%lu\n", - 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, (size_t)p); - - continue; - } - - fprintf(stderr, "%s: ", f->fname); - - if (f->post_rw_checksum[p]) - fprintf(stderr, "GOOD"); - else - fprintf(stderr, "BAD"); - - fprintf(stderr, " checksum in p%lu on-disk.\n", - (size_t)p); - - if (f->post_rw_checksum[p]) { - fprintf(stderr, - " This does NOT mean it's safe. it may be\n" - " salvageable if you use the cat feature.\n"); - } - } -} - -int -gbe_mv(void) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - int rval; - - int saved_errno; - int tmp_gbe_bin_exists; - - char *dest_tmp; - int dest_fd; - - /* will be set 0 if it doesn't - */ - tmp_gbe_bin_exists = 1; - - dest_tmp = NULL; - dest_fd = -1; - - saved_errno = errno; - - rval = rename(f->tname, f->fname); - - if (rval > -1) { - - /* rename on same filesystem - */ - - tmp_gbe_bin_exists = 0; - - if (fsync_dir(f->fname) < 0) { - f->io_err_gbe_bin = 1; - rval = -1; - } - - goto ret_gbe_mv; - } - - if (errno != EXDEV) - goto ret_gbe_mv; - - /* - * OR, cross-filesystem rename: - */ - - if ((rval = f->tmp_fd = open(f->tname, - O_RDONLY | O_BINARY)) == -1) - goto ret_gbe_mv; - - /* create replacement temp in target directory - */ - if (new_tmpfile(&dest_fd, &f->fname) < 1) - goto ret_gbe_mv; - if (dest_tmp == NULL) - goto ret_gbe_mv; - - /* copy data - */ - rval = rw_file_exact(f->tmp_fd, f->bufcmp, - f->gbe_file_size, 0, IO_PREAD, - NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (rval < 0) - goto ret_gbe_mv; - - rval = rw_file_exact(dest_fd, f->bufcmp, - f->gbe_file_size, 0, IO_PWRITE, - NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (rval < 0) - goto ret_gbe_mv; - - if (fsync_on_eintr(dest_fd) == -1) - goto ret_gbe_mv; - - if (close_on_eintr(dest_fd) == -1) { - dest_fd = -1; - goto ret_gbe_mv; - } - dest_fd = -1; - - if (rename(dest_tmp, f->fname) == -1) - goto ret_gbe_mv; - - if (fsync_dir(f->fname) < 0) { - f->io_err_gbe_bin = 1; - goto ret_gbe_mv; - } - - if (dest_tmp != NULL) { - free(dest_tmp); - dest_tmp = NULL; - } - - dest_tmp = NULL; - -ret_gbe_mv: - - /* TODO: this whole section is bloat. - it can be generalised - */ - - if (f->gbe_fd > -1) { - 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; - } - } - - if (f->tmp_fd > -1) { - if (close_on_eintr(f->tmp_fd) < 0) { - f->tmp_fd = -1; - rval = -1; - } - f->tmp_fd = -1; - } - - /* before this function is called, - * tmp_fd may have been moved - */ - if (tmp_gbe_bin_exists) { - if (unlink(f->tname) < 0) - rval = -1; - else - tmp_gbe_bin_exists = 0; - } - - if (rval < 0) { - /* if nothing set errno, - * we assume EIO, or we - * use what was set - */ - if (errno == saved_errno) - errno = EIO; - } else { - errno = saved_errno; - } - - return rval; -} - -/* This one is similar to gbe_file_offset, - * but used to check Gbe bounds in memory, - * and it is *also* used during file I/O. - */ -unsigned char * -gbe_mem_offset(size_t p, const char *f_op) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - off_t gbe_off; - - gbe_off = gbe_x_offset(p, f_op, "mem", - GBE_PART_SIZE, GBE_WORK_SIZE); - - return (unsigned char *) - (f->buf + (size_t)gbe_off); -} - -/* I/O operations filtered here. These operations must - * only write from the 0th position or the half position - * within the GbE file, and write 4KB of data. - */ -off_t -gbe_file_offset(size_t p, const char *f_op) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - off_t gbe_file_half_size; - - gbe_file_half_size = f->gbe_file_size >> 1; - - return gbe_x_offset(p, f_op, "file", - gbe_file_half_size, f->gbe_file_size); -} - -off_t -gbe_x_offset(size_t p, const char *f_op, const char *d_type, - off_t nsize, off_t ncmp) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - off_t off; - - check_bin(p, "part number"); - - off = ((off_t)p) * (off_t)nsize; - - if (off > ncmp - GBE_PART_SIZE) - err(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", - f->fname, d_type, f_op); - - return off; -} - -ssize_t -rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw, - off_t off, int rw_type) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - ssize_t r; - - if (io_args(fd, mem, nrw, off, rw_type) == -1) - return -1; - - if (mem != (void *)f->pad) { - if (mem < f->buf) - goto err_rw_gbe_file_exact; - - 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 > (size_t)(f->gbe_file_size - off)) - goto err_rw_gbe_file_exact; - - if (nrw > (size_t)GBE_PART_SIZE) - goto err_rw_gbe_file_exact; - - r = rw_file_exact(fd, mem, nrw, off, rw_type, - NO_LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY, - OFF_ERR); - - return rw_over_nrw(r, nrw); - -err_rw_gbe_file_exact: - errno = EIO; - return -1; -} diff --git a/util/nvmutil/lib/mkhtemp.c b/util/nvmutil/lib/mkhtemp.c deleted file mode 100644 index 2fcb894e..00000000 --- a/util/nvmutil/lib/mkhtemp.c +++ /dev/null @@ -1,1133 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe - * - * Hardened mktemp (be nice to the demon). - */ - -#if defined(__linux__) && !defined(_GNU_SOURCE) -/* for openat2 syscall on linux */ -#define _GNU_SOURCE 1 -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include - -/* for openat2: */ -#ifdef __linux__ -#include -#include -#endif - -#include "../include/common.h" - -int -new_tmpfile(int *fd, char **path) -{ - return new_tmp_common(fd, path, MKHTEMP_FILE); -} - -int -new_tmpdir(int *fd, char **path) -{ - return new_tmp_common(fd, path, MKHTEMP_DIR); -} - -static int -new_tmp_common(int *fd, char **path, int type) -{ -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - size_t maxlen = PATH_LEN; -#else - size_t maxlen = 4096; -#endif - struct stat st; - - char suffix[] = - "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; - char *tmpdir = NULL; - - int close_errno; - size_t dirlen; - size_t destlen; - char *dest = NULL; /* final path (will be written into "path") */ - int saved_errno = errno; - int dirfd = -1; - const char *fname = NULL; - - struct stat st_dir_initial; - - if (path == NULL || fd == NULL) { - errno = EFAULT; - goto err; - } - - /* don't mess with someone elses file */ - if (*fd >= 0) { - errno = EEXIST; - goto err; - } - - /* regarding **path: - * the pointer (to the pointer) - * must nott be null, but we don't - * care about the pointer it points - * to. you should expect it to be - * replaced upon successful return - * - * (on error, it will not be touched) - */ - - - *fd = -1; - -#if defined(PERMIT_NON_STICKY_ALWAYS) && \ - ((PERMIT_NON_STICKY_ALWAYS) > 0) - tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS); -#else - tmpdir = env_tmpdir(0); -#endif - if (tmpdir == NULL) - goto err; - - if (slen(tmpdir, maxlen, &dirlen) < 0) - goto err; - if (*tmpdir == '\0') - goto err; - if (*tmpdir != '/') - goto err; - - /* sizeof adds an extra byte, useful - * because we also want '.' or '/' - */ - destlen = dirlen + sizeof(suffix); - if (destlen > maxlen - 1) { - errno = EOVERFLOW; - goto err; - } - - dest = malloc(destlen + 1); - if (dest == NULL) { - errno = ENOMEM; - goto err; - } - - memcpy(dest, tmpdir, dirlen); - *(dest + dirlen) = '/'; - memcpy(dest + dirlen + 1, suffix, sizeof(suffix) - 1); - *(dest + destlen) = '\0'; - - fname = dest + dirlen + 1; - - dirfd = fs_open(tmpdir, - O_RDONLY | O_DIRECTORY); - if (dirfd < 0) - goto err; - - if (fstat(dirfd, &st_dir_initial) < 0) - goto err; - - *fd = mkhtemp(fd, &st, dest, dirfd, - fname, &st_dir_initial, type); - if (*fd < 0) - goto err; - - if (dirfd >= 0) { - close_errno = errno; - (void) close_on_eintr(dirfd); - errno = close_errno; - dirfd = -1; - } - - errno = saved_errno; - *path = dest; - - return 0; - -err: - - if (errno != saved_errno) - saved_errno = errno; - else - saved_errno = errno = EIO; - - if (dest != NULL) { - free(dest); - dest = NULL; - } - - if (dirfd >= 0) { - close_errno = errno; - (void) close_on_eintr(dirfd); - errno = close_errno; - dirfd = -1; - } - - if (*fd >= 0) { - close_errno = errno; - (void) close_on_eintr(*fd); - errno = close_errno; - *fd = -1; - } - - errno = saved_errno; - return -1; -} - - -/* hardened TMPDIR parsing - */ - -char * -env_tmpdir(int bypass_all_sticky_checks) -{ - char *t; - int allow_noworld_unsticky; - int saved_errno = errno; - - t = getenv("TMPDIR"); - - if (t != NULL && *t != '\0') { - - if (tmpdir_policy(t, - &allow_noworld_unsticky) < 0) { - errno = EPERM; - return NULL; /* errno already set */ - } - - if (!world_writeable_and_sticky(t, - allow_noworld_unsticky, - bypass_all_sticky_checks)) { - errno = EPERM; - return NULL; - } - - errno = saved_errno; - return t; - } - - allow_noworld_unsticky = 0; - - if (world_writeable_and_sticky("/tmp", - allow_noworld_unsticky, - bypass_all_sticky_checks)) { - - errno = saved_errno; - return "/tmp"; - } - - if (world_writeable_and_sticky("/var/tmp", - allow_noworld_unsticky, - bypass_all_sticky_checks)) { - - errno = saved_errno; - return "/var/tmp"; - } - - if (errno == saved_errno) - errno = EPERM; - - return NULL; -} - -int -tmpdir_policy(const char *path, - int *allow_noworld_unsticky) -{ - int saved_errno = errno; - int r; - - if (path == NULL || - allow_noworld_unsticky == NULL) { - - errno = EFAULT; - return -1; - } - - *allow_noworld_unsticky = 1; - - r = same_dir(path, "/tmp"); - if (r < 0) - goto err_tmpdir_policy; - if (r > 0) - *allow_noworld_unsticky = 0; - - r = same_dir(path, "/var/tmp"); - if (r < 0) - goto err_tmpdir_policy; - if (r > 0) - *allow_noworld_unsticky = 0; - - errno = saved_errno; - return 0; - -err_tmpdir_policy: - - if (errno == saved_errno) - errno = EIO; - - return -1; -} - -int -same_dir(const char *a, const char *b) -{ - int fd_a = -1; - int fd_b = -1; - - struct stat st_a; - struct stat st_b; - - int saved_errno = errno; - int rval_scmp; - -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - size_t maxlen = (PATH_LEN); -#else - size_t maxlen = 4096; -#endif - - /* optimisation: if both dirs - are the same, we don't need - to check anything. sehr schnell: - */ - if (scmp(a, b, maxlen, &rval_scmp) < 0) - goto err_same_dir; - /* bonus: scmp checks null for us */ - if (rval_scmp == 0) - goto success_same_dir; - - fd_a = fs_open(a, O_RDONLY | O_DIRECTORY); - if (fd_a < 0) - goto err_same_dir; - - fd_b = fs_open(b, O_RDONLY | O_DIRECTORY); - if (fd_b < 0) - goto err_same_dir; - - if (fstat(fd_a, &st_a) < 0) - goto err_same_dir; - - if (fstat(fd_b, &st_b) < 0) - goto err_same_dir; - - if (st_a.st_dev == st_b.st_dev && - st_a.st_ino == st_b.st_ino) { - - (void) close_on_eintr(fd_a); - (void) close_on_eintr(fd_b); - -success_same_dir: - - /* SUCCESS - */ - - errno = saved_errno; - return 1; - } - - (void) close_on_eintr(fd_a); - (void) close_on_eintr(fd_b); - - /* FAILURE (logical) - */ - - errno = saved_errno; - return 0; - -err_same_dir: - - /* FAILURE (probably syscall) - */ - - if (fd_a >= 0) - (void) close_on_eintr(fd_a); - if (fd_b >= 0) - (void) close_on_eintr(fd_b); - - if (errno == saved_errno) - errno = EIO; - - return -1; -} - -/* bypass_all_sticky_checks: if set, - disable stickiness checks (libc behaviour) - (if not set: leah behaviour) - - allow_noworld_unsticky: - allow non-sticky files if not world-writeable - (still block non-sticky in standard TMPDIR) -*/ -int -world_writeable_and_sticky( - const char *s, - int allow_noworld_unsticky, - int bypass_all_sticky_checks) -{ - struct stat st; - int dirfd = -1; - - int saved_errno = errno; - - if (s == NULL || *s == '\0') { - errno = EINVAL; - goto sticky_hell; - } - - /* mitigate symlink attacks* - */ - dirfd = fs_open(s, O_RDONLY | O_DIRECTORY); - if (dirfd < 0) - goto sticky_hell; - - if (fstat(dirfd, &st) < 0) - goto sticky_hell; - - if (!S_ISDIR(st.st_mode)) { - errno = ENOTDIR; - goto sticky_hell; - } - - /* must be fully executable - * by everyone, or openat2 - * becomes unreliable** - */ - if (!(st.st_mode & S_IXUSR) || - !(st.st_mode & S_IXGRP) || - !(st.st_mode & S_IXOTH)) { - - errno = EACCES; - goto sticky_hell; - } - - /* *normal-**ish mode (libc): - */ - - if (bypass_all_sticky_checks) - goto sticky_heaven; /* normal == no security */ - - /* unhinged leah mode: - */ - - if (st.st_mode & S_IWOTH) { /* world writeable */ - - /* if world-writeable, only - * allow sticky files - */ - if (st.st_mode & S_ISVTX) - goto sticky_heaven; /* sticky */ - - errno = EPERM; - goto sticky_hell; /* not sticky */ - } - - /* non-world-writeable, so - * stickiness is do-not-care - */ - if (allow_noworld_unsticky) - goto sticky_heaven; /* sticky! */ - - goto sticky_hell; /* heaven visa denied */ - -sticky_heaven: -/* i like the one in hamburg better */ - - if (dirfd >= 0) - (void) close_on_eintr(dirfd); - - errno = saved_errno; - - return 1; - -sticky_hell: - - if (errno == saved_errno) - errno = EPERM; - - saved_errno = errno; - - if (dirfd >= 0) - (void) close_on_eintr(dirfd); - - errno = saved_errno; - - return 0; -} - -/* mk(h)temp - hardened mktemp. - * like mkstemp, but (MUCH) harder. - * - * designed to resist TOCTOU attacsk - * e.g. directory race / symlink attack - * - * extremely strict and even implements - * some limited userspace-level sandboxing, - * similar to openbsd unveil (which you - * can also use with this in your program) - * - * supports both files and directories. - * file: type = MKHTEMP_FILE (0) - * dir: type = MKHTEMP_DIR (1) - * - * DESIGN NOTES: - * - * caller is expected to handle - * cleanup e.g. free(), on *st, - * *template, *fname (all of the - * pointers). ditto fd cleanup. - * - * some limited cleanup is - * performed here, e.g. directory/file - * cleanup on error in mkhtemp_try_create - * - * we only check if these are not NULL, - * and the caller is expected to take - * care; without too many conditions, - * these functions are more flexible, - * but some precauttions are taken: - * - * when used via the function new_tmpfile - * or new_tmpdir, thtis is extremely strict, - * much stricter than previous mktemp - * variants. for example, it is much - * stricter about stickiness on world - * writeable directories, and it enforces - * file ownership under hardened mode - * (only lets you touch your own files/dirs) - */ -int -mkhtemp(int *fd, - struct stat *st, - char *template, - int dirfd, - const char *fname, - struct stat *st_dir_initial, - int type) -{ - size_t len = 0; - size_t xc = 0; - size_t fname_len = 0; - - char *fname_copy = NULL; - char *p; - - size_t retries; - - int close_errno; - int saved_errno = errno; - -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - size_t max_len = PATH_LEN; -#else - size_t max_len = 4096; -#endif - int r; - char *end; - - if (fd == NULL || - template == NULL || - fname == NULL || - st_dir_initial == NULL) { - - errno = EFAULT; - return -1; - } - - /* we do not mess with an - open descriptor. - */ - if (*fd >= 0) { - errno = EEXIST; /* leave their file alone */ - return -1; - } - - if (dirfd < 0) { - errno = EBADF; - return -1; - } - - if (slen(template, max_len, &len) < 0) - return -1; - - if (len >= max_len) { - errno = EMSGSIZE; - return -1; - } - - if (slen(fname, max_len, &fname_len) < 0) - return -1; - - if (fname_len == 0) { - errno = EINVAL; - return -1; - } - - if (strrchr(fname, '/') != NULL) { - errno = EINVAL; - return -1; - } - - /* count trailing X */ - end = template + len; - while (end > template && *--end == 'X') - xc++; - - if (xc < 12 || xc > len) { - errno = EINVAL; - return -1; - } - - if (fname_len > len) { - errno = EOVERFLOW; - return -1; - } - - if (memcmp(fname, - template + len - fname_len, - fname_len) != 0) { - errno = EINVAL; - return -1; - } - - fname_copy = malloc(fname_len + 1); - if (fname_copy == NULL) { - errno = ENOMEM; - goto err; - } - - /* fname_copy = suffix region only; p points to trailing XXXXXX */ - memcpy(fname_copy, - template + len - fname_len, - fname_len + 1); - p = fname_copy + fname_len - xc; - - for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) { - - r = mkhtemp_try_create( - dirfd, - st_dir_initial, - fname_copy, - p, - xc, - fd, - st, - type); - - if (r == 0) { - if (retries >= MKHTEMP_SPIN_THRESHOLD) { - /* usleep can return EINTR */ - close_errno = errno; - usleep((useconds_t)rlong() & 0x3FF); - errno = close_errno; - } - continue; - } - if (r < 0) - goto err; - - /* success: copy final name back */ - memcpy(template + len - fname_len, - fname_copy, fname_len); - - errno = saved_errno; - goto success; - } - - errno = EEXIST; - -err: - if (*fd >= 0) { - close_errno = errno; - (void)close_on_eintr(*fd); - errno = close_errno; - *fd = -1; - } - -success: - - if (fname_copy != NULL) - free(fname_copy); - - return (*fd >= 0) ? *fd : -1; -} - -static int -mkhtemp_try_create(int dirfd, - struct stat *st_dir_initial, - char *fname_copy, - char *p, - size_t xc, - int *fd, - struct stat *st, - int type) -{ - struct stat st_open; - int saved_errno = errno; - int close_errno; - int rval = -1; - - int file_created = 0; - int dir_created = 0; - - if (fd == NULL || st == NULL || p == NULL || fname_copy == NULL || - st_dir_initial == NULL) { - errno = EFAULT; - goto err; - } else if (*fd >= 0) { /* do not mess with someone else's file */ - errno = EEXIST; - goto err; - } - - if (mkhtemp_fill_random(p, xc) < 0) - goto err; - - if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0) - goto err; - - if (type == MKHTEMP_FILE) { - *fd = openat2p(dirfd, fname_copy, - O_RDWR | O_CREAT | O_EXCL | - O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, - 0600); - - /* O_CREAT and O_EXCL guarantees - * creation upon success - */ - if (*fd >= 0) - file_created = 1; - - } else { /* dir: MKHTEMP_DIR */ - - if (mkdirat_on_eintr(dirfd, fname_copy, 0700) < 0) - goto err; - - /* ^ NOTE: opening the directory here - will never set errno=EEXIST, - since we're not creating it */ - - dir_created = 1; - - /* do it again (mitigate directory race) */ - if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0) - goto err; - - *fd = openat2p(dirfd, fname_copy, - O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0); - if (*fd < 0) - goto err; - - if (fstat(*fd, &st_open) < 0) - goto err; - - if (!S_ISDIR(st_open.st_mode)) { - errno = ENOTDIR; - goto err; - } - - /* NOTE: could check nlink count here, - * but it's not very reliable here. skipped. - */ - - if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0) - goto err; - - } - - /* NOTE: openat2p and mkdirat_on_eintr - * already handled EINTR/EAGAIN looping - */ - - if (*fd < 0) { - if (errno == EEXIST) { - - rval = 0; - goto out; - } - goto err; - } - - if (fstat(*fd, &st_open) < 0) - goto err; - - if (type == MKHTEMP_FILE) { - - if (fd_verify_dir_identity(dirfd, st_dir_initial) < 0) - goto err; - - if (secure_file(fd, st, &st_open, - O_APPEND, 1, 1, 0600) < 0) /* WARNING: only once */ - goto err; - - } else { /* dir: MKHTEMP_DIR */ - - if (fd_verify_identity(*fd, &st_open, st_dir_initial) < 0) - goto err; - - if (!S_ISDIR(st_open.st_mode)) { - errno = ENOTDIR; - goto err; - } - - if (is_owner(&st_open) < 0) - goto err; - - /* group/world writeable */ - if (st_open.st_mode & (S_IWGRP | S_IWOTH)) { - errno = EPERM; - goto err; - } - } - - errno = saved_errno; - rval = 1; - goto out; - -err: - close_errno = errno; - - if (fd != NULL && *fd >= 0) { - (void) close_on_eintr(*fd); - *fd = -1; - } - - if (file_created) - (void) unlinkat(dirfd, fname_copy, 0); - - if (dir_created) - (void) unlinkat(dirfd, fname_copy, AT_REMOVEDIR); - - errno = close_errno; - rval = -1; -out: - return rval; -} - -int -mkhtemp_fill_random(char *p, size_t xc) -{ - size_t chx = 0; - int rand_failures = 0; - - size_t r; - - int saved_rand_error = 0; - static char ch[] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - - /* clamp rand to prevent modulo bias - * (reduced risk of entropy leak) - */ - size_t limit = ((size_t)-1) - (((size_t)-1) % (sizeof(ch) - 1)); - - int saved_errno = errno; - - if (p == NULL) { - errno = EFAULT; - goto err_mkhtemp_fill_random; - } - - for (chx = 0; chx < xc; chx++) { - - do { - saved_rand_error = errno; - rand_failures = 0; -retry_rand: - errno = 0; - - /* on bsd: uses arc4random - on linux: uses getrandom - on OLD linux: /dev/urandom - on old/other unix: /dev/urandom - */ - r = rlong(); - - if (errno > 0) { - if (++rand_failures <= 8) - goto retry_rand; - - goto err_mkhtemp_fill_random; - } - - rand_failures = 0; - errno = saved_rand_error; - - } while (r >= limit); - - p[chx] = ch[r % (sizeof(ch) - 1)]; - } - - errno = saved_errno; - return 0; - -err_mkhtemp_fill_random: - - if (errno == saved_errno) - errno = ECANCELED; - - return -1; -} - -/* WARNING: **ONCE** per file. - * - * !!! DO NOT RUN TWICE PER FILE. BEWARE OF THE DEMON !!! - * watch out for spikes! - */ -int secure_file(int *fd, - struct stat *st, - struct stat *expected, - int bad_flags, - int check_seek, - int do_lock, - mode_t mode) -{ - int flags; - struct stat st_now; - int saved_errno = errno; - /* you have been warned */ - if (fd == NULL) { - errno = EFAULT; - goto err_demons; - } - if (*fd < 0) { - errno = EBADF; - goto err_demons; - } - - if (st == NULL) { - errno = EFAULT; - goto err_demons; - } - - flags = fcntl(*fd, F_GETFL); - - if (flags == -1) - goto err_demons; - - if (bad_flags > 0) { - - /* e.g. O_APPEND breaks pwrite/pread - * by allowing offsets to be ignored */ - if (flags & bad_flags) { - errno = EPERM; - goto err_demons; - } - } - - if (expected != NULL) { - if (fd_verify_regular(*fd, expected, st) < 0) - goto err_demons; - } else { - if (fstat(*fd, &st_now) == -1) - goto err_demons; - - if (!S_ISREG(st_now.st_mode)) { - errno = EBADF; - goto err_demons; - } - - *st = st_now; - } - - if (check_seek) { - - /* check if it's seekable */ - if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1) - goto err_demons; - } - - /* don't release the demon - */ - if (st->st_nlink != 1) { /***********/ - /* ( >:3 ) */ - errno = ELOOP; /* /| |\ */ /* don't let him out */ - goto err_demons; /* / \ */ - /***********/ - } - - if (st->st_uid != geteuid() && /* someone else's file */ - geteuid() != 0) { /* override for root */ - - errno = EPERM; - goto err_demons; - } - if (is_owner(st) < 0) - goto err_demons; - - /* world-writeable or group-ownership. - * if these are set, then others could - * modify the file (not secure) - */ - if (st->st_mode & (S_IWGRP | S_IWOTH)) { - errno = EPERM; - goto err_demons; - } - - if (do_lock) { - if (lock_file(*fd, flags) == -1) - goto err_demons; - - if (expected != NULL) { - if (fd_verify_identity(*fd, expected, &st_now) < 0) - goto err_demons; - } - } - - if (fchmod(*fd, mode) == -1) - goto err_demons; - - errno = saved_errno; - return 0; - -err_demons: - - if (errno == saved_errno) - errno = EIO; - - return -1; -} - -int -fd_verify_regular(int fd, - const struct stat *expected, - struct stat *out) -{ - if (fd_verify_identity(fd, expected, out) < 0) - return -1; - - if (!S_ISREG(out->st_mode)) { - errno = EBADF; - return -1; - } - - return 0; /* regular file */ -} - -int -fd_verify_identity(int fd, - const struct stat *expected, - struct stat *out) -{ - struct stat st_now; - int saved_errno = errno; - - if (fd < 0 || expected == NULL) { - errno = EFAULT; - return -1; - } - - if (fstat(fd, &st_now) < 0) - return -1; - - if (st_now.st_dev != expected->st_dev || - st_now.st_ino != expected->st_ino) { - errno = ESTALE; - return -1; - } - - if (out != NULL) - *out = st_now; - - errno = saved_errno; - return 0; -} - -int -fd_verify_dir_identity(int fd, - const struct stat *expected) -{ - struct stat st_now; - int saved_errno = errno; - - if (fd < 0 || expected == NULL) { - errno = EFAULT; - return -1; - } - - if (fstat(fd, &st_now) < 0) - return -1; - - if (st_now.st_dev != expected->st_dev || - st_now.st_ino != expected->st_ino) { - errno = ESTALE; - return -1; - } - - if (!S_ISDIR(st_now.st_mode)) { - errno = ENOTDIR; - return -1; - } - - errno = saved_errno; - return 0; -} - -int -is_owner(struct stat *st) -{ - if (st == NULL) { - - errno = EFAULT; - return -1; - } - -#if ALLOW_ROOT_OVERRIDE - if (st->st_uid != geteuid() && /* someone else's file */ - geteuid() != 0) { /* override for root */ -#else - if (st->st_uid != geteuid()) { /* someone else's file */ -#endif /* and no root override */ - errno = EPERM; - return -1; - } - - return 0; -} - -int -lock_file(int fd, int flags) -{ - struct flock fl; - int saved_errno = errno; - - if (fd < 0) { - errno = EBADF; - goto err_lock_file; - } - - if (flags < 0) { - errno = EINVAL; - goto err_lock_file; - } - - memset(&fl, 0, sizeof(fl)); - - if ((flags & O_ACCMODE) == O_RDONLY) - fl.l_type = F_RDLCK; - else - fl.l_type = F_WRLCK; - - fl.l_whence = SEEK_SET; - - if (fcntl(fd, F_SETLK, &fl) == -1) - goto err_lock_file; - - saved_errno = errno; - return 0; - -err_lock_file: - - if (errno == saved_errno) - errno = EIO; - - return -1; -} diff --git a/util/nvmutil/lib/num.c b/util/nvmutil/lib/num.c deleted file mode 100644 index 343350b0..00000000 --- a/util/nvmutil/lib/num.c +++ /dev/null @@ -1,444 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe - * - * Numerical functions. - */ - -/* -TODO: properly handle errno in this file - */ - -#ifdef __OpenBSD__ -#include -#endif -#include -#if defined(FALLBACK_RAND_1989) && \ - (FALLBACK_RAND_1989) > 0 -#include -#endif - -#include -#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \ - defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__APPLE__)) -#include /* if not arc4random: /dev/urandom */ -#endif -#include -#include -#include -#if defined(FALLBACK_RAND_1989) && \ - (FALLBACK_RAND_1989) > 0 -#include -#endif -#include - -#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. */ -} - -/* 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. - */ - -size_t -rlong(void) -{ -#if !(defined(FALLBACK_RAND_1989) && \ - ((FALLBACK_RAND_1989) > 0)) -#if (defined(__OpenBSD__) && (OpenBSD) >= 201) || \ - defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__APPLE__) - - int saved_errno = errno; - size_t rval; - - arc4random_buf(&rval, sizeof(size_t)); - - errno = saved_errno; - return rval; -#else - static int fd = -1; - static ssize_t nr = -1; - static size_t 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 - size_t rval; - ssize_t new_nr; - - int retries = 0; - int max_retries = 100; - - int saved_errno = errno; - -#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 - */ - - if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0) { - errno = saved_errno; - 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 err_rlong; - - if (nr < 0 || nr < (ssize_t)sizeof(size_t)) { - - if (fd < 0) { - - fd = open("/dev/urandom", - O_RDONLY | O_BINARY | O_NOFOLLOW | - O_CLOEXEC | O_NOCTTY); - -#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 | O_NOCTTY); -#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 < (ssize_t)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(size_t)); - - nr -= (ssize_t)sizeof(size_t); - off += sizeof(size_t); - - errno = saved_errno; - - return rval; - -err_rlong: - - if (errno == saved_errno) - errno = EIO; - - return 0; - -#endif -#else /* FALLBACK_RAND_1989 */ - /* your computer is from a museum - */ - size_t mix = 0; - int nr = 0; - int saved_errno = errno; - - /* 100 times, for entropy - */ - for (nr = 0; nr < 100; nr++) - mix ^= fallback_rand_1989(); - - errno = saved_errno; - return mix; -#endif -} - -#if !(defined(FALLBACK_RAND_1989) && \ - ((FALLBACK_RAND_1989) > 0)) -#if defined(__linux__) -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) -int /* yes, linux is a fallback */ -fallback_rand_getrandom(void *buf, size_t len) -{ - size_t off = 0; - ssize_t rval = -1; - int saved_errno = errno; - - if (!len) { - errno = EINVAL; - return -1; - } - - if (buf == NULL) { - errno = EFAULT; - return -1; - } - -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) - - while (off < len) { - -#if defined(HAVE_GETRANDOM) - rval = (ssize_t)getrandom((char *)buf + off, len - off, 0); -#elif defined(HAVE_GETRANDOM_SYSCALL) - rval = (ssize_t)syscall(SYS_getrandom, - (char *)buf + off, len - off, 0); -#endif - - if (rval < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - - errno = EIO; - return -1; /* unsupported by kernel */ - } - - if (rval == 0) { - errno = EIO; - return -1; - } - - off += (size_t)rval; - } - - errno = saved_errno; - return 0; - -#else - (void)buf; - (void)len; - - errno = EIO; - return -1; -#endif -} -#endif -#endif -#else -size_t -fallback_rand_1989(void) -{ - static size_t mix = 0; - static size_t counter = 0; - - struct timeval tv; - - /* nobody should use this - * (not crypto-safe) - */ - - gettimeofday(&tv, NULL); - - mix ^= (size_t)tv.tv_sec - ^ (size_t)tv.tv_usec - ^ (size_t)getpid() - ^ (size_t)&mix - ^ counter++ - ^ entropy_jitter(); - - /* - * Stack addresses can vary between - * calls, thus increasing entropy. - */ - mix ^= (size_t)&mix; - mix ^= (size_t)&tv; - mix ^= (size_t)&counter; - - return mix; -} - -size_t -entropy_jitter(void) -{ - size_t mix; - - struct timeval a, b; - ssize_t 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 = (ssize_t)(b.tv_usec - a.tv_usec); - if (mix_diff < 0) - mix_diff = -mix_diff; - - mix ^= (size_t)(mix_diff); - - mix ^= (size_t)&mix; - - } - - return mix; -} -#endif - -void -check_bin(size_t a, const char *a_name) -{ - if (a > 1) - err(EINVAL, "%s must be 0 or 1, but is %lu", - a_name, (size_t)a); -} diff --git a/util/nvmutil/lib/state.c b/util/nvmutil/lib/state.c deleted file mode 100644 index 19d5cd8c..00000000 --- a/util/nvmutil/lib/state.c +++ /dev/null @@ -1,279 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe - * - * State machine (singleton) for nvmutil data. - */ - -#ifdef __OpenBSD__ -#include -#endif -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../include/common.h" - -struct xstate * -xstart(int argc, char *argv[]) -{ - static int first_run = 1; - static int pre_init = 0; - - static struct xstate us = { - /* DO NOT MESS THIS UP, OR THERE WILL BE DEMONS */ - { - { - CMD_DUMP, "dump", cmd_helper_dump, ARGC_3, - ARG_NOPART, - SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - NVM_SIZE, O_RDONLY - }, { - CMD_SETMAC, "setmac", cmd_helper_setmac, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, CHECKSUM_WRITE, - NVM_SIZE, O_RDWR - }, { - CMD_SWAP, "swap", cmd_helper_swap, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDWR - }, { - CMD_COPY, "copy", cmd_helper_copy, ARGC_4, - ARG_PART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDWR - }, { - CMD_CAT, "cat", cmd_helper_cat, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDONLY - }, { - CMD_CAT16, "cat16", cmd_helper_cat16, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDONLY - }, { - CMD_CAT128, "cat128", cmd_helper_cat128, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDONLY - } - }, - - /* ->mac */ - {NULL, "xx:xx:xx:xx:xx:xx", {0, 0, 0}}, /* .str, .rmac, .mac_buf */ - - /* .f */ - {0}, - - /* .argv0 (for our getprogname implementation) */ - NULL, - - /* ->i (index to cmd[]) */ - 0, - - /* .no_cmd (set 0 when a command is found) */ - 1, - - /* .cat (cat helpers set this) */ - -1 - - }; - - if (!first_run) { - if (pre_init) - err_no_cleanup(ECANCELED, - "Outside access to state during init"); - - first_run = 0; - - return &us; - } - - if (argc < 3) - err_no_cleanup(EINVAL, "xstart: Too few arguments"); - if (argv == NULL) - err_no_cleanup(EINVAL, "xstart: NULL argv"); - - first_run = 0; - pre_init = 1; - - us.f.buf = us.f.real_buf; - - us.argv0 = argv[0]; - us.f.fname = argv[1]; - - if (new_tmpfile(&us.f.tmp_fd, &us.f.tname) < 0) - err_no_cleanup(errno, "xstart: cannot create tmpfile"); - - /* parse user command */ -/* TODO: CHECK ACCESSES VIA xstatus() */ - set_cmd(argc, argv); - set_cmd_args(argc, argv); - - if (us.f.tname == NULL) - err_no_cleanup(errno, "x->f.tname null"); - if (*us.f.tname == '\0') - err_no_cleanup(errno, "x->f.tname empty"); - - if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0) - err_no_cleanup(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)); - - /* for good measure */ - memset(us.f.pad, 0, sizeof(us.f.pad)); - - pre_init = 0; - - return &us; -} - -struct xstate * -xstatus(void) -{ - struct xstate *x = xstart(0, NULL); - - if (x == NULL) - err_no_cleanup(EACCES, "NULL pointer to xstate"); - - sanitize_command_list(); - - return x; -} - -/* early init functions that - should not access state - WARNING: - does not do cleanup. only - call this during pre-init - */ -void -err_no_cleanup(int nvm_errval, const char *msg, ...) -{ - va_list args; - - if (errno == 0) - errno = nvm_errval; - if (!errno) - errno = ECANCELED; - - fprintf(stderr, "nvmutil: "); - - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); - - fprintf(stderr, ": %s\n", strerror(errno)); - - exit(EXIT_FAILURE); -} - -void -err(int nvm_errval, const char *msg, ...) -{ - struct xstate *x = xstatus(); - - va_list args; - - if (errno == 0) - errno = nvm_errval; - if (!errno) - errno = ECANCELED; - - (void)exit_cleanup(); - - if (x != NULL) - fprintf(stderr, "%s: ", getnvmprogname()); - - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); - - fprintf(stderr, ": %s\n", strerror(errno)); - - exit(EXIT_FAILURE); -} - -const char * -getnvmprogname(void) -{ - struct xstate *x = xstatus(); - - const char *p; - static char fallback[] = "nvmutil"; - - char *rval = fallback; - - if (x != NULL) { - if (x->argv0 == NULL || *x->argv0 == '\0') - return ""; - - rval = x->argv0; - } - - p = strrchr(rval, '/'); - - if (p) - return p + 1; - else - return rval; -} - -int -exit_cleanup(void) -{ - struct xstate *x = xstatus(); - struct xfile *f; - - int close_err; - int saved_errno; - - close_err = 0; - saved_errno = errno; - - if (x != NULL) { - f = &x->f; - - if (f->gbe_fd > -1) { - if (close_on_eintr(f->gbe_fd) == -1) { - f->gbe_fd = -1; - close_err = 1; - } - f->gbe_fd = -1; - } - - if (f->tmp_fd > -1) { - if (close_on_eintr(f->tmp_fd) == -1) { - f->tmp_fd = -1; - close_err = 1; - } - f->tmp_fd = -1; - } - - if (f->tname != NULL) { - if (unlink(f->tname) == -1) - close_err = 1; - } - - f->tmp_fd = -1; - } - - if (saved_errno) - errno = saved_errno; - - if (close_err) - return -1; - - return 0; -} diff --git a/util/nvmutil/lib/string.c b/util/nvmutil/lib/string.c deleted file mode 100644 index ca58fb1c..00000000 --- a/util/nvmutil/lib/string.c +++ /dev/null @@ -1,114 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe - * - * String functions - */ - -#include -#include - -#include -#include -#include -#include - -#include "../include/common.h" - -/* scmp() - strict string comparison - * - * strict string comparison - * similar to strncmp, but null and - * unterminated inputs do not produce - * a return value; on error, errno is - * set and -1 is returned. - * - * the real return value is stored in - * the 4th argument by pointer. - * - * the value at rval pointer is set, - * only upon success. callers should - * check the return value accordingly. - */ - -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; - return -1; - } - - 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; - } - } - - /* block unterminated strings */ - errno = EFAULT; - return -1; -} - -/* slen() - strict strict length - * - * strict string length calculation - * similar to strnlen, but null and - * unterminated inputs do not produce - * a return value; on error, errno is - * set and -1 is returned. - * - * the real return value is stored in - * the 3rd argument by pointer. - * - * the value at rval pointer is set, - * only upon success. callers should - * check the return value accordingly. - */ - -int -slen(const char *s, - size_t maxlen, - size_t *rval) -{ - size_t ch; - - if (s == NULL || - rval == NULL) { - - errno = EFAULT; - return -1; - } - - for (ch = 0; - ch < maxlen && s[ch] != '\0'; - ch++); - - if (ch == maxlen) { - /* unterminated */ - errno = EFAULT; - return -1; - } - - *rval = ch; - return 0; -} diff --git a/util/nvmutil/lib/usage.c b/util/nvmutil/lib/usage.c deleted file mode 100644 index 3b0614e8..00000000 --- a/util/nvmutil/lib/usage.c +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2023 Riku Viitanen - * Copyright (c) 2026 Leah Rowe - */ - -#include -#include - -#include "../include/common.h" - -void -usage(void) -{ - const char *util = getnvmprogname(); - - fprintf(stderr, - "Modify Intel GbE NVM images e.g. set MAC\n" - "USAGE:\n" - "\t%s FILE dump\n" - "\t%s FILE setmac [MAC]\n" - "\t%s FILE swap\n" - "\t%s FILE copy 0|1\n" - "\t%s FILE cat\n" - "\t%s FILE cat16\n" - "\t%s FILE cat128\n", - util, util, util, util, - util, util, util); - - err(EINVAL, "Too few arguments"); -} diff --git a/util/nvmutil/lib/word.c b/util/nvmutil/lib/word.c deleted file mode 100644 index f84dae6a..00000000 --- a/util/nvmutil/lib/word.c +++ /dev/null @@ -1,68 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe - * - * Manipulate Intel GbE NVM words, which are 16-bit little - * endian in the files (MAC address words are big endian). - */ - -#include - -#include -#include - -#include "../include/common.h" - -unsigned short -nvm_word(size_t pos16, size_t p) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - size_t pos; - - check_nvm_bound(pos16, p); - pos = (pos16 << 1) + (p * GBE_PART_SIZE); - - return (unsigned short)f->buf[pos] | - ((unsigned short)f->buf[pos + 1] << 8); -} - -void -set_nvm_word(size_t pos16, size_t p, unsigned short val16) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - size_t pos; - - check_nvm_bound(pos16, p); - pos = (pos16 << 1) + (p * GBE_PART_SIZE); - - f->buf[pos] = (unsigned char)(val16 & 0xff); - f->buf[pos + 1] = (unsigned char)(val16 >> 8); - - set_part_modified(p); -} - -void -set_part_modified(size_t p) -{ - struct xstate *x = xstatus(); - struct xfile *f = &x->f; - - check_bin(p, "part number"); - f->part_modified[p] = 1; -} - -void -check_nvm_bound(size_t c, size_t p) -{ - /* Block out of bound NVM access - */ - - check_bin(p, "part number"); - - if (c >= NVM_WORDS) - err(ECANCELED, "check_nvm_bound: out of bounds %lu", - (size_t)c); -} diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c deleted file mode 100644 index 266654e8..00000000 --- a/util/nvmutil/nvmutil.c +++ /dev/null @@ -1,134 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe - * - * 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 -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "include/common.h" - -int -main(int argc, char *argv[]) -{ - struct xstate *x; - - struct commands *cmd; - struct xfile *f; - - size_t c; - - int rval; - char *test = NULL; - int fd = -1; - rval = new_tmpdir(&fd, &test); - if (rval < 0) - err_no_cleanup(errno, "TESTERR: "); - - printf("TEST: %s\n", test); - exit(1); - -/* https://man.openbsd.org/pledge.2 - https://man.openbsd.org/unveil.2 */ -#if defined(__OpenBSD__) && defined(OpenBSD) -#if (OpenBSD) >= 604 - if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1) - err_no_cleanup(errno, "pledge plus unveil, main"); - if (unveil("/dev/null", "r") == -1) - err_no_cleanup(errno, "unveil r: /dev/null"); -#elif (OpenBSD) >= 509 - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err_no_cleanup(errno, "pledge, main"); -#endif -#endif - -#ifndef S_ISREG - err_no_cleanup(ECANCELED, - "Can't determine file types (S_ISREG undefined)"); -#endif -#if ((CHAR_BIT) != 8) - err_no_cleanup(ECANCELED, "Unsupported char size"); -#endif - - x = xstart(argc, argv); - - if (x == NULL) - err_no_cleanup(ECANCELED, "NULL state on init"); - - 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) - 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 (unveil(us.f.tname, "rwc") == -1) - err(errno, "unveil rwc: %s", us.f.tname); - - if (unveil(NULL, NULL) == -1) - err(errno, "unveil block (rw)"); - - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err(errno, "pledge (kill unveil)"); - -#elif (OpenBSD) >= 509 - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err(errno, "pledge"); -#endif -#endif - - if (cmd->run == NULL) - err(errno, "Command not set"); - - 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) - 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); - f->tname = NULL; - } - - return EXIT_SUCCESS; -} -- cgit v1.2.1