/* 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