diff options
Diffstat (limited to 'util/nvmutil')
| -rw-r--r-- | util/nvmutil/.gitignore | 3 | ||||
| -rw-r--r-- | util/nvmutil/ChangeLog.md | 8 | ||||
| -rw-r--r-- | util/nvmutil/Makefile | 32 | ||||
| -rw-r--r-- | util/nvmutil/README.md | 4 | ||||
| -rw-r--r-- | util/nvmutil/nvmutil.c | 2388 |
5 files changed, 1903 insertions, 532 deletions
diff --git a/util/nvmutil/.gitignore b/util/nvmutil/.gitignore new file mode 100644 index 00000000..802202a4 --- /dev/null +++ b/util/nvmutil/.gitignore @@ -0,0 +1,3 @@ +/nvm +/nvmutil +*.bin diff --git a/util/nvmutil/ChangeLog.md b/util/nvmutil/ChangeLog.md deleted file mode 100644 index e1ed5754..00000000 --- a/util/nvmutil/ChangeLog.md +++ /dev/null @@ -1,8 +0,0 @@ -This change log has moved. Please refer here for historical pre-osboot-merge -changes: - -<https://libreboot.org/docs/install/nvmutilimport.html> - -Osboot merged with Libreboot on November 17th, 2022. For nvmutil changes after -this date, please check regular Libreboot release announcements which shall -now specify any such changes. diff --git a/util/nvmutil/Makefile b/util/nvmutil/Makefile index b8ec2ad3..719e1c1e 100644 --- a/util/nvmutil/Makefile +++ b/util/nvmutil/Makefile @@ -1,24 +1,32 @@ # SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2022,2025 Leah Rowe <leah@libreboot.org> -# SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com> +# Copyright (c) 2022,2026 Leah Rowe <leah@libreboot.org> +# Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> CC?=cc -CFLAGS?=-Os -Wall -Wextra -Werror -pedantic +CFLAGS?=-Os -Wall -Wextra -Werror -pedantic -std=c90 +LDFLAGS?= DESTDIR?= PREFIX?=/usr/local INSTALL?=install -nvm: nvmutil.c - $(CC) $(CFLAGS) nvmutil.c -o nvm +PROG=nvmutil -install: - $(INSTALL) nvm $(DESTDIR)$(PREFIX)/bin/nvm +all: $(PROG) -uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/nvm +$(PROG): nvmutil.c + $(CC) $(CFLAGS) $(LDFLAGS) nvmutil.c -o $(PROG) + +install: $(PROG) + $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin + $(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG) + chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG) -distclean: - rm -f nvm +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) clean: - rm -f nvm + rm -f $(PROG) + +distclean: clean + +.PHONY: all install uninstall clean distclean diff --git a/util/nvmutil/README.md b/util/nvmutil/README.md deleted file mode 100644 index 03a25bc4..00000000 --- a/util/nvmutil/README.md +++ /dev/null @@ -1,4 +0,0 @@ - -This documentation has become part of lbwww. See: - -<https://libreboot.org/docs/install/nvmutil.html> diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index c609ca36..fe8364f7 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -1,77 +1,439 @@ -/* SPDX-License-Identifier: MIT */ -/* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> */ -/* Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> */ +/* SPDX-License-Identifier: MIT + * + * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> + * Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> + * + * 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. + * + * This code is designed to be portable, running on as many + * Unix and Unix-like systems as possible (mainly BSD/Linux). + * + * Recommended CFLAGS for Clang/GCC: + * + * -Os -Wall -Wextra -Werror -pedantic -std=c90 + */ + +#define OFF_ERR 0 +#ifndef OFF_RESET +#define OFF_RESET 1 +#endif + +/* + * NOTE: older Linux lacked arc4random. + * added in glibc 2.36. Just pass HAVE_ARC4RANDOM_BUF=0 + * at build time if you need old Linux / other libc. + */ +#if defined(__OpenBSD__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__) || \ + defined(__linux__) +#ifndef HAVE_ARC4RANDOM_BUF +#define HAVE_ARC4RANDOM_BUF 1 +#endif +#endif + +/* + * I/O config (build-time) + * + * Regarding: + * Retries on zero-return. + * + * 5 retries is generous, + * but also conservative. + * This is enough for e.g. + * slow USB flash drives, + * busy NFS servers, etc. + * Any more is too much + * and not of much benefit. + * + * 3-5 will tolerate buggy + * USB drives for example, + * but won't spin as long + * on really buggy and slow + * networks e.g. slow NFS. + * + * At least 3-5 recommended. + * Pass this at build time. + */ +#ifndef MAX_ZERO_RW_RETRY +#define MAX_ZERO_RW_RETRY 5 +#endif +/* + * 0: portable pread/pwrite + * 1: real pread/pwrite (thread-safe) + * Pass this at build-time + */ +#ifndef HAVE_REAL_PREAD_PWRITE +#define HAVE_REAL_PREAD_PWRITE 0 +#endif +/* + * Configure whether to wait on + * EINTR on files, or EAGAIN on + * cmd cat (stdout). + * + * Pass these at build time. + */ +#ifndef LOOP_EAGAIN +#define LOOP_EAGAIN 1 +#endif +#ifndef LOOP_EINTR +#define LOOP_EINTR 1 +#endif + +/* + * Major TODO: split this into multiple files. + * This program has become quite large now, mostly + * due to all the extra sanity checks / portability. + * Make most of nvmutil a *library* for re-use + * + * TODO: gettimeofday not posible - use portable functions. + * TODO: ux fallback: modify the program instead + * to run on 16-bit systems: smaller buffers, and do + * operations byte-based instead of word-based. + * + * TODO: _XOPEN_SOURCE 500 probably not needed anymore. + * the portable fallbacks alone are likely enough. + * e.g. i don't need stdint, and i don't use pwrite/pread + * anymore. + * + * TODO: version detection of various BSDs to detect + * arc4random, use that if available. but also work on + * older versions of those BSDs (also MacOS) that lack it. + * + * TODO: portability/testing on non-Unix systems: + * old DOS. all windows versions (probably irrelevant + * because you can use cygwin/wsl, whatever), classic MacOS, + * also test really old unix e.g. sunos and irix. Be/Haiku too! + * + * TODO: reliance on global variables for status. make + * functions use structs passed as args instead, make + * functions re-useable (including libraries), etc. + * + * TODO: bound checks for files per-command, e.g. only + * first 6 bytes for CMD_SETMAC + * + * TODO: in command sanitizer: verify that each given + * entry corresponds to the correct function, in the + * pointer (this check is currently missing) + * + * TODO: general modularisierung of the entire codebase. + * TODO: better explain copy/swap read inversion trick + * by improving existing comments + * TODO: lots of overwritten comments in code. tidy it up. + * + * TODO: use getopt for nvmutil args, so that multiple + * operations can be performed, and also on many + * files at once (noting limitations with cat) + * BONUS: implement own getopt(), for portability + * + * TODO: document fuzzing / static analysis methods + * for the code, and: + * TODO: implement rigorous unit tests (separate util) + * NOTE: this would *include* known good test files + * in various configurations, also invalid files. + * the tests would likely be portable posix shell + * scripts rather than a new C program, but a modularisiert + * codebase would allow me to write a separate C + * program to test some finer intricacies + * TODO: the unit tests would basically test regressions + * TODO: after writing back a gbe to file, close() and + * open() it again, read it again, and check that + * the contents were written correctly, providing + * a warning if they were. do this in the main + * program. + * TODO: the unit tests would include an aggressive set + * of fuzz tests, under controlled conditions + * + * TODO: also document the layout of Intel GbE files, so + * that wily individuals can easily expand the + * featureset of nvmutil. + * TODO: write a manpage + * TODO: simplify the command sanitization, implement more + * of it as build time checks, e.g. static asserts. + * generally remove cleverness from the code, instead + * prefyerring readibility + * TODO: also document nvmutil's coding style, which is + * its own style at this point! + * TODO: when all the above (and possibly more) is done, + * submit this tool to coreboot with a further change + * to their build system that lets users modify + * GbE images, especially set MAC addresses, when + * including GbE files in coreboot configs. + */ +/* + BONUS TODO: + CI/CD. woodpecker is good enough, sourcehut also has one. + tie this in with other things mentioned here, + e.g. fuzzer / unit tests +*/ + +/* Major TODO: reproducible builds +Test with and without these: + +CFLAGS += -fno-record-gcc-switches +CFLAGS += -ffile-prefix-map=$(PWD)=. +CFLAGS += -fdebug-prefix-map=$(PWD)=. + +I already avoid unique timestamps per-build, +by not using them, e.g. not reporting build +time in the program. + +When splitting the nvmutil.c file later, do e.g.: + +SRC = main.c io.c nvm.c cmd.c +OBJ = $(SRC:.c=.o) + +^ explicitly declare the order in which to build +*/ + +/* +TODO: +further note when fuzzing is implemented: +use deterministic randomisation, with a +guaranteed seed - so e.g. don't use /dev/urandom +in test builds. e.g. just use normal rand() +but with a static seed e.g. 1234 +*/ +/* +TODO: stricter build flags, e.g. +CFLAGS += -fstack-protector-strong +CFLAGS += -fno-common +CFLAGS += -D_FORTIFY_SOURCE=2 +CFLAGS += -fPIE + +also consider: +-fstack-clash-protection +-Wl,-z,relro +-Wl,-z,now +*/ + +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif +#ifdef __OpenBSD__ +#include <sys/param.h> +#endif +#include <sys/types.h> +#include <sys/time.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <stdarg.h> -#include <stdint.h> +#include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <time.h> #include <unistd.h> +typedef unsigned char u8; +typedef unsigned short ushort; +typedef unsigned int uint; +typedef unsigned long ulong; + +/* 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_u8_is_1[ + (sizeof(u8) == 1) ? 1 : -1]; +typedef char static_assert_ushort_is_2[ + (sizeof(ushort) >= 2) ? 1 : -1]; +typedef char static_assert_short_is_2[(sizeof(short) >= 2) ? 1 : -1]; +typedef char static_assert_uint_is_4[ + (sizeof(uint) >= 4) ? 1 : -1]; +typedef char static_assert_ulong_is_4[ + (sizeof(ulong) >= 4) ? 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_ulong_ptr[ + (sizeof(ulong) >= sizeof(void *)) ? 1 : -1 +]; +typedef char assert_size_t_ptr[ + (sizeof(size_t) >= sizeof(void *)) ? 1 : -1 +]; + /* - * On the platforms below, we will use arc4random - * for random MAC address generation. + * We set _FILE_OFFSET_BITS 64, but we only handle + * files that are 128KB in size at a maximum, so we + * realistically only need 32-bit at a minimum. * - * Later on, the code has fallbacks for other systems. + * We set 64 anyway, because there's no reason not + * to, but some systems may ignore _FILE_OFFSET_BITS */ -#if defined(__OpenBSD__) || defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__APPLE__) || \ - defined(__DragonFly__) -#ifndef HAVE_ARC4RANDOM_BUF -#define HAVE_ARC4RANDOM_BUF +typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1]; + +/* + * Older versions of BSD to the early 2000s + * could compile nvmutil, but pledge was + * added in the 2010s. Therefore, for extra + * portability, we will only pledge/unveil + * on OpenBSD versions that have it. + */ +#if defined(__OpenBSD__) && defined(OpenBSD) +#if OpenBSD >= 604 +#ifndef NVMUTIL_UNVEIL +#define NVMUTIL_UNVEIL 1 #endif #endif +#if OpenBSD >= 509 +#ifndef NVMUTIL_PLEDGE +#define NVMUTIL_PLEDGE 1 +#endif +#endif +#endif + +#ifndef EXIT_FAILURE +#define EXIT_FAILURE 1 +#endif + +#ifndef EXIT_SUCCESS +#define EXIT_SUCCESS 0 +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif +/* + * Sanitize command tables. + */ +static void sanitize_command_list(void); +static void sanitize_command_index(size_t c); + +/* + * Argument handling (user input) + */ static void set_cmd(int argc, char *argv[]); -static void check_cmd_args(int argc, char *argv[]); +static void set_cmd_args(int argc, char *argv[]); static size_t conv_argv_part_num(const char *part_str); -static void set_io_flags(int argc, char *argv[]); +static int xstrxcmp(const char *a, const char *b, size_t maxlen); + +/* + * Prep files for reading + */ static void open_gbe_file(void); -#ifndef HAVE_ARC4RANDOM_BUF -static void open_dev_urandom(void); -#endif +static void lock_gbe_file(void); static void xopen(int *fd, const char *path, int flags, struct stat *st); + +/* + * Read GbE file and verify + * checksums. + * + * After this, we can run commands. + */ static void read_gbe_file(void); -static void read_gbe_file_part(size_t part, uint8_t invert); -static void cmd_setmac(void); +static void read_checksums(void); +static int good_checksum(size_t partnum); + +/* + * Execute user command on GbE data. + * These are stubs that call helpers. + */ +static void run_cmd(size_t c); +static void check_command_num(size_t c); +static u8 valid_command(size_t c); + +/* + * Helper functions for command: setmac + */ +static void cmd_helper_setmac(void); static void parse_mac_string(void); +static size_t xstrxlen(const char *scmp, size_t maxlen); static void set_mac_byte(size_t mac_byte_pos); static void set_mac_nib(size_t mac_str_pos, size_t mac_byte_pos, size_t mac_nib_pos); -static uint8_t hextonum(char ch_s); -static uint8_t rhex(void); -static void read_file_exact(int fd, void *buf, size_t len, - off_t off, const char *path, const char *op); -static int write_mac_part(size_t partnum); -static void cmd_dump(void); -static void print_mac_address(size_t partnum); +static ushort hextonum(char ch_s); +static ushort rhex(void); +#if !defined(HAVE_ARC4RANDOM_BUF) || \ + (HAVE_ARC4RANDOM_BUF) < 1 +static ulong entropy_jitter(void); +#endif +static void write_mac_part(size_t partnum); + +/* + * Helper functions for command: dump + */ +static void cmd_helper_dump(void); +static void print_mac_from_nvm(size_t partnum); static void hexdump(size_t partnum); -static void cmd_setchecksum(void); + +/* + * Helper functions for commands: + * cat, cat16 and cat128 + */ +static void cmd_helper_cat(void); +static void gbe_cat_buf(u8 *b); + +/* + * After command processing, write + * the modified GbE file back. + * + * These are stub functions: check + * below for the actual functions. + */ +static void write_gbe_file(void); +static void override_part_modified(void); static void set_checksum(size_t part); -static void cmd_brick(void); -static void cmd_copy(void); -static void cmd_swap(void); -static int good_checksum(size_t partnum); -static uint16_t word(size_t pos16, size_t part); -static void set_word(size_t pos16, size_t part, uint16_t val16); +static ushort calculated_checksum(size_t p); + +/* + * Helper functions for accessing + * the NVM area during operation. + */ +static ushort nvm_word(size_t pos16, size_t part); +static void set_nvm_word(size_t pos16, size_t part, ushort val16); +static void set_part_modified(size_t p); static void check_nvm_bound(size_t pos16, size_t part); -static void write_gbe_file(void); -static void write_gbe_file_part(size_t part); +static void check_bin(size_t a, const char *a_name); + +/* + * Helper functions for stub functions + * that handle GbE file reads/writes. + */ +static void rw_gbe_file_part(size_t p, int rw_type, + const char *rw_type_str); +static void check_written_part(size_t p); +static void report_io_err_rw(void); +static u8 *gbe_mem_offset(size_t part, const char *f_op); static off_t gbe_file_offset(size_t part, const char *f_op); -static void *gbe_mem_offset(size_t part, const char *f_op); static off_t gbe_x_offset(size_t part, const char *f_op, const char *d_type, off_t nsize, off_t ncmp); -static void set_part_modified(size_t p); -static void check_part_num(size_t p); -static void usage(void); +static ssize_t rw_gbe_file_exact(int fd, u8 *mem, size_t nrw, + off_t off, int rw_type); +static ssize_t rw_file_exact(int fd, u8 *mem, size_t len, + off_t off, int rw_type, int loop_eagain, int loop_eintr, + size_t max_retries, int off_reset); +static 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); +static int io_args(int fd, void *mem, size_t nrw, + off_t off, int rw_type); +static int check_file(int fd, struct stat *st); +static ssize_t rw_over_nrw(ssize_t r, size_t nrw); +#if !defined(HAVE_REAL_PREAD_PWRITE) || \ + HAVE_REAL_PREAD_PWRITE < 1 +static off_t lseek_loop(int fd, off_t off, + int whence, int loop_eagain, int loop_eintr); +#endif +static int try_err(int loop_err, int errval); + +/* + * Error handling and cleanup + */ static void err(int nvm_errval, const char *msg, ...); +static int close_files(void); static const char *getnvmprogname(void); -static void set_err(int errval); +static void usage(int usage_exit); /* * Sizes in bytes: @@ -102,23 +464,11 @@ static void set_err(int errval); #define NVM_CHECKSUM_WORD (NVM_WORDS - 1) /* - * When reading files, we loop on error EINTR - * a maximum number of times as defined, thus: - */ -#define MAX_RETRY_READ 30 - -/* - * Portably macro based on BSD nitems. + * Portable macro based on BSD nitems. * Used to count the number of commands (see below). */ #define items(x) (sizeof((x)) / sizeof((x)[0])) -static const char newrandom[] = "/dev/urandom"; -static const char oldrandom[] = "/dev/random"; /* fallback on OLD unix */ -#ifndef HAVE_ARC4RANDOM_BUF -static const char *rname = NULL; -#endif - /* * GbE files can be 8KB, 16KB or 128KB, * but we only need the two 4KB parts @@ -128,269 +478,533 @@ static const char *rname = NULL; * * The code will handle this properly. */ -static uint8_t buf[GBE_FILE_SIZE]; +static u8 real_buf[GBE_FILE_SIZE]; +static u8 pad[GBE_FILE_SIZE]; /* the file that wouldn't die */ +static u8 *buf = real_buf; -static uint16_t mac_buf[3]; +static ushort mac_buf[3]; static off_t gbe_file_size; -static int gbe_flags; -#ifndef HAVE_ARC4RANDOM_BUF -static int urandom_fd = -1; -#endif static int gbe_fd = -1; static size_t part; -static uint8_t invert; -static uint8_t part_modified[2]; +static u8 part_modified[2]; +static u8 part_valid[2]; -static const char *mac_str; static const char rmac[] = "xx:xx:xx:xx:xx:xx"; +static const char *mac_str; static const char *fname; static const char *argv0; +#ifndef SSIZE_MAX +#define SSIZE_MAX ((ssize_t)(~((size_t)1 << (sizeof(ssize_t)*CHAR_BIT-1)))) +#endif + +/* + * Use these for .invert in command[]: + * If set to 1: read/write inverter (p0->p1, p1->p0) + */ +#define PART_INVERT 1 +#define NO_INVERT 0 + +/* + * Use these for .argc in command[]: + */ +#define ARGC_3 3 +#define ARGC_4 4 + +#define NO_LOOP_EAGAIN 0 +#define NO_LOOP_EINTR 0 + +enum { + IO_READ, + IO_WRITE, + IO_PREAD, + IO_PWRITE +}; + +/* + * Used as indices for command[] + * MUST be in the same order as entries in command[] + */ +enum { + CMD_DUMP, + CMD_SETMAC, + CMD_SWAP, + CMD_COPY, + CMD_CAT, + CMD_CAT16, + CMD_CAT128 +}; + +/* + * If set, a given part will always be written. + */ +enum { + SET_MOD_OFF, /* don't manually set part modified */ + SET_MOD_0, /* set part 0 modified */ + SET_MOD_1, /* set part 1 modified */ + SET_MOD_N, /* set user-specified part modified */ + /* affected by command[].invert */ + SET_MOD_BOTH /* set both parts modified */ +}; + +enum { + ARG_NOPART, + ARG_PART +}; + +enum { + SKIP_CHECKSUM_READ, + CHECKSUM_READ +}; + +enum { + SKIP_CHECKSUM_WRITE, + CHECKSUM_WRITE +}; + struct commands { + size_t chk; const char *str; - void (*cmd)(void); - int args; + void (*run)(void); + int argc; + u8 invert; + u8 set_modified; + u8 arg_part; + u8 chksum_read; + u8 chksum_write; + size_t rw_size; /* within the 4KB GbE part */ + int flags; /* e.g. O_RDWR or O_RDONLY */ }; + +/* + * Command table, for nvmutil commands + */ static const struct commands command[] = { - { "dump", cmd_dump, 3 }, - { "setmac", cmd_setmac, 3 }, - { "swap", cmd_swap, 3 }, - { "copy", cmd_copy, 4 }, - { "brick", cmd_brick, 4 }, - { "setchecksum", cmd_setchecksum, 4 }, + { CMD_DUMP, "dump", cmd_helper_dump, ARGC_3, + NO_INVERT, SET_MOD_OFF, + ARG_NOPART, + SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + NVM_SIZE, O_RDONLY }, + + { CMD_SETMAC, "setmac", cmd_helper_setmac, ARGC_3, + NO_INVERT, SET_MOD_OFF, + ARG_NOPART, + CHECKSUM_READ, CHECKSUM_WRITE, + NVM_SIZE, O_RDWR }, + + /* + * OPTIMISATION: Read inverted, so no copying is needed. + */ + { CMD_SWAP, "swap", NULL, ARGC_3, + PART_INVERT, SET_MOD_BOTH, + ARG_NOPART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDWR }, + + /* + * OPTIMISATION: Read inverted, so no copying is needed. + * The non-target part will not be read. + */ + { CMD_COPY, "copy", NULL, ARGC_4, + PART_INVERT, SET_MOD_N, + ARG_PART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDWR }, + + { CMD_CAT, "cat", cmd_helper_cat, ARGC_3, + NO_INVERT, SET_MOD_OFF, + ARG_NOPART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDONLY }, + + { CMD_CAT16, "cat16", cmd_helper_cat, ARGC_3, + NO_INVERT, SET_MOD_OFF, + ARG_NOPART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDONLY }, + + { CMD_CAT128, "cat128", cmd_helper_cat, ARGC_3, + NO_INVERT, SET_MOD_OFF, + ARG_NOPART, + CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE, O_RDONLY }, }; -static void (*cmd)(void) = NULL; +#define MAX_CMD_LEN 50 +#define N_COMMANDS items(command) +#define CMD_NULL N_COMMANDS + +/* + * Index in command[], will be set later + */ +static size_t cmd_index = CMD_NULL; + +/* + * 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]; +/* 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]; +/* mod_type */ +typedef char assert_mod_off[(SET_MOD_OFF==0)?1:-1]; +typedef char assert_mod_0[(SET_MOD_0==1)?1:-1]; +typedef char assert_mod_1[(SET_MOD_1==2)?1:-1]; +typedef char assert_mod_n[(SET_MOD_N==3)?1:-1]; +typedef char assert_mod_both[(SET_MOD_BOTH==4)?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_no_invert[(NO_INVERT==0)?1:-1]; +typedef char bool_part_invert[(PART_INVERT==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]; + +static int io_err_gbe = 0; +static int rw_check_err_read[] = {0, 0}; +static int rw_check_partial_read[] = {0, 0}; +static int rw_check_bad_part[] = {0, 0}; + +static int post_rw_checksum[] = {0, 0}; + +static dev_t gbe_dev; +static ino_t gbe_ino; + +#if defined(HAVE_ARC4RANDOM_BUF) && \ + (HAVE_ARC4RANDOM_BUF) > 0 +void arc4random_buf(void *buf, size_t n); +#endif int main(int argc, char *argv[]) { argv0 = argv[0]; - if (argc < 2) - usage(); + if (argc < 3) + usage(1); fname = argv[1]; -#ifdef __OpenBSD__ - if (pledge("stdio rpath wpath unveil", NULL) == -1) - err(ECANCELED, "pledge"); - - /* - * For restricted filesystem access on early error. - * - * Unveiling the random device early, regardless of - * whether we will use it, prevents operations on any - * GbE files until we permit it, while performing the - * prerequisite error checks. - * - * We don't actually use the random device on platforms - * that have arc4random, which includes OpenBSD. - */ - if (unveil("/dev/urandom", "r") == -1) - err(ECANCELED, "unveil '/dev/urandom'"); - if (unveil("/dev/random", "r") == -1) - err(ECANCELED, "unveil '/dev/random'"); +#ifdef NVMUTIL_PLEDGE +#ifdef NVMUTIL_UNVEIL + if (pledge("stdio flock rpath wpath unveil", NULL) == -1) + err(errno, "pledge"); + if (unveil("/dev/null", "r") == -1) + err(errno, "unveil /dev/null"); +#else + if (pledge("stdio flock rpath wpath", NULL) == -1) + err(errno, "pledge"); #endif +#endif + + sanitize_command_list(); set_cmd(argc, argv); - check_cmd_args(argc, argv); - set_io_flags(argc, argv); + set_cmd_args(argc, argv); -#ifdef __OpenBSD__ - if (gbe_flags == O_RDONLY) { +#ifdef NVMUTIL_PLEDGE +#ifdef NVMUTIL_UNVEIL + if (command[cmd_index].flags == O_RDONLY) { if (unveil(fname, "r") == -1) - err(ECANCELED, "unveil ro '%s'", fname); + err(errno, "%s: unveil ro", fname); if (unveil(NULL, NULL) == -1) - err(ECANCELED, "unveil block (ro)"); - if (pledge("stdio rpath", NULL) == -1) - err(ECANCELED, "pledge ro (kill unveil)"); + err(errno, "unveil block (ro)"); + if (pledge("stdio flock rpath", NULL) == -1) + err(errno, "pledge ro (kill unveil)"); } else { if (unveil(fname, "rw") == -1) - err(ECANCELED, "unveil rw '%s'", fname); + err(errno, "%s: unveil rw", fname); if (unveil(NULL, NULL) == -1) - err(ECANCELED, "unveil block (rw)"); - if (pledge("stdio rpath wpath", NULL) == -1) - err(ECANCELED, "pledge rw (kill unveil)"); + err(errno, "unveil block (rw)"); + if (pledge("stdio flock rpath wpath", NULL) == -1) + err(errno, "pledge rw (kill unveil)"); + } +#else + if (command[cmd_index].flags == O_RDONLY) { + if (pledge("stdio flock rpath", NULL) == -1) + err(errno, "pledge ro"); } #endif +#endif -#ifndef HAVE_ARC4RANDOM_BUF - open_dev_urandom(); +#if !defined(HAVE_ARC4RANDOM_BUF) || \ + (HAVE_ARC4RANDOM_BUF) < 1 + srand((uint)(time(NULL) ^ getpid())); #endif + open_gbe_file(); + lock_gbe_file(); -#ifdef __OpenBSD__ +#ifdef NVMUTIL_PLEDGE if (pledge("stdio", NULL) == -1) - err(ECANCELED, "pledge stdio (main)"); + err(errno, "pledge stdio (main)"); #endif + /* + * Used by CMD_CAT, for padding + */ + memset(pad, 0xff, sizeof(pad)); + read_gbe_file(); - (*cmd)(); - write_gbe_file(); + read_checksums(); - if (close(gbe_fd) == -1) - err(ECANCELED, "close '%s'", fname); -#ifndef HAVE_ARC4RANDOM_BUF - if (close(urandom_fd) == -1) - err(ECANCELED, "close '%s'", rname); -#endif + run_cmd(cmd_index); - /* - * We still exit with non-zero status if - * errno is set, but we don't need to print - * the error on dump commands, because they - * already print errors. - * - * If both parts have bad checksums, then - * cmd_dump will cause non-zero exit. If at - * least one part is valid, it resets errno. - * - * However, if we're not using cmd_dump, then - * we have a bug somewhere in the code. - */ - if (cmd != cmd_dump) { - if (errno) - err(ECANCELED, "Unhandled error on exit"); + if (command[cmd_index].flags == O_RDWR) { + + write_gbe_file(); + + /* + * We may otherwise read from + * cache, so we must sync. + */ + if (fsync(gbe_fd) == -1) + err(errno, "%s: fsync (pre-verification)", + fname); + + check_written_part(0); + check_written_part(1); + + report_io_err_rw(); + + if (io_err_gbe) + err(EIO, "%s: bad write", fname); } - if (errno) - return EXIT_FAILURE; - else - return EXIT_SUCCESS; + if (close_files() == -1) + err(EIO, "%s: close", fname); + + return EXIT_SUCCESS; } +/* + * Guard against regressions by maintainers (command table) + */ static void -set_cmd(int argc, char *argv[]) +sanitize_command_list(void) { - size_t i; + size_t c; - /* - * Example: ./nvmutil gbe.bin - * - * Here, we assume that the user - * wants a randomised MAC address. - */ - if (argc == 2) { - cmd = cmd_setmac; - return; + for (c = 0; c < N_COMMANDS; c++) + sanitize_command_index(c); +} + +/* + * TODO: specific config checks per command + */ +static void +sanitize_command_index(size_t c) +{ + u8 mod_type; + size_t gbe_rw_size; + + check_command_num(c); + + if (command[c].argc < 3) + err(EINVAL, "cmd index %lu: argc below 3, %d", + (ulong)c, command[c].argc); + + if (command[c].str == NULL) + err(EINVAL, "cmd index %lu: NULL str", + (ulong)c); + if (*command[c].str == '\0') + err(EINVAL, "cmd index %lu: empty str", + (ulong)c); + + if (xstrxlen(command[c].str, MAX_CMD_LEN + 1) > + MAX_CMD_LEN) { + err(EINVAL, "cmd index %lu: str too long: %s", + (ulong)c, command[c].str); } - for (i = 0; i < items(command); i++) { - if (strcmp(argv[2], command[i].str) != 0) - continue; - if (argc >= command[i].args) { - cmd = command[i].cmd; - break; - } + mod_type = command[c].set_modified; + switch (mod_type) { + case SET_MOD_0: + case SET_MOD_1: + case SET_MOD_N: + case SET_MOD_BOTH: + case SET_MOD_OFF: + break; + default: + err(EINVAL, "Unsupported set_mod type: %u", mod_type); + } + + check_bin(command[c].invert, "cmd.invert"); + check_bin(command[c].arg_part, "cmd.arg_part"); + check_bin(command[c].chksum_read, "cmd.chksum_read"); + check_bin(command[c].chksum_write, "cmd.chksum_write"); + + gbe_rw_size = command[c].rw_size; - err(EINVAL, "Too few args: command '%s'", command[i].str); + switch (gbe_rw_size) { + case GBE_PART_SIZE: + case NVM_SIZE: + break; + default: + err(EINVAL, "Unsupported rw_size: %lu", + (ulong)gbe_rw_size); } + + if (gbe_rw_size > GBE_PART_SIZE) + err(EINVAL, "rw_size larger than GbE part: %lu", + (ulong)gbe_rw_size); + + if (command[c].flags != O_RDONLY && + command[c].flags != O_RDWR) + err(EINVAL, "invalid cmd.flags setting"); } static void -check_cmd_args(int argc, char *argv[]) +set_cmd(int argc, char *argv[]) { - if (cmd == NULL && argc > 2) { - /* - * Here, no valid command was found, but a - * 3rd argument is available, which tells - * us that the 3rd argument is a MAC address - * supplied by the user, which could also - * contain one or more random characters. - * - * This is intentional, because a lot of - * users might run something like: - * - * ./nvmutil gbe.bin xx:1f:16:??:??:?? - * - * Instead of (more properly): - * - * ./nvmutil gbe.bin setmac xx:1f:16:??:??:?? - * - * This quirk makes the tool easier to use. - */ - mac_str = argv[2]; - cmd = cmd_setmac; - } else if (cmd == cmd_setmac) { - /* - * ./nvmutil gbe.bin setmac [MAC] - */ - mac_str = rmac; /* random MAC */ - if (argc > 3) - mac_str = argv[3]; - } else if (cmd != NULL && argc > 3) { /* user-supplied partnum */ - /* - * Example: ./nvmutil gbe.bin copy 0 - */ - part = conv_argv_part_num(argv[3]); + const char *cmd_str; + + for (cmd_index = 0; valid_command(cmd_index); cmd_index++) { + cmd_str = command[cmd_index].str; + + if (xstrxcmp(argv[2], cmd_str, MAX_CMD_LEN) != 0) + continue; + else if (argc >= command[cmd_index].argc) + return; + + err(EINVAL, "Too few args on command '%s'", cmd_str); } - if (cmd == NULL) - err(EINVAL, "Bad command"); + cmd_index = CMD_NULL; } -static size_t -conv_argv_part_num(const char *part_str) +static void +set_cmd_args(int argc, char *argv[]) { - unsigned char ch; + u8 arg_part; - /* - * Because char signedness is implementation-defined, - * we cast to unsigned char before arithmetic. - */ + if (!valid_command(cmd_index) || argc < 3) + usage(1); - if (part_str[0] == '\0' || part_str[1] != '\0') - err(EINVAL, "Partnum string '%s' wrong length", part_str); + arg_part = command[cmd_index].arg_part; - ch = (unsigned char)part_str[0] - '0'; + /* Maintainer bugs */ + if (arg_part && argc < 4) + err(EINVAL, + "arg_part set for command that needs argc4"); + if (arg_part && cmd_index == CMD_SETMAC) + err(EINVAL, + "arg_part set on CMD_SETMAC"); - check_part_num((size_t)ch); - return (size_t)ch; + if (cmd_index == CMD_SETMAC) + mac_str = argc >= 4 ? argv[3] : rmac; + else if (arg_part) + part = conv_argv_part_num(argv[3]); } -static void -set_io_flags(int argc, char *argv[]) +static size_t +conv_argv_part_num(const char *part_str) { - gbe_flags = O_RDWR; + u8 ch; - if (argc < 3) - return; + if (part_str[0] == '\0' || part_str[1] != '\0') + err(EINVAL, "Partnum string '%s' wrong length", part_str); + + /* char signedness is implementation-defined */ + ch = (u8)part_str[0]; + if (ch < '0' || ch > '1') + err(EINVAL, "Bad part number (%c)", ch); - if (strcmp(argv[2], "dump") == 0) - gbe_flags = O_RDONLY; + return (size_t)(ch - '0'); } -#ifndef HAVE_ARC4RANDOM_BUF -static void -open_dev_urandom(void) +/* + * Portable strcmp() but blocks NULL/empty/unterminated + * strings. Even stricter than strncmp(). + */ +static int +xstrxcmp(const char *a, const char *b, size_t maxlen) { - struct stat st_urandom_fd; + size_t i; - rname = newrandom; + if (a == NULL || b == NULL) + err(EINVAL, "NULL input to xstrxcmp"); - if ((urandom_fd = open(rname, O_RDONLY)) == -1) { - /* - * Fall back to /dev/random on old platforms - * where /dev/urandom does not exist. - * - * We must reset the error condition first, - * to prevent stale error status later. - */ - errno = 0; + if (*a == '\0' || *b == '\0') + err(EINVAL, "Empty string in xstrxcmp"); + + for (i = 0; i < maxlen; i++) { + u8 ac = (u8)a[i]; + u8 bc = (u8)b[i]; + + if (ac == '\0' || bc == '\0') { + if (ac == bc) + return 0; + return ac - bc; + } - rname = oldrandom; - xopen(&urandom_fd, rname, O_RDONLY, &st_urandom_fd); + if (ac != bc) + return ac - bc; } + + /* + * We reached maxlen, so assume unterminated string. + */ + err(EINVAL, "Unterminated string in xstrxcmp"); + + /* + * Should never reach here. This keeps compilers happy. + */ + errno = EINVAL; + return -1; } -#endif static void open_gbe_file(void) { struct stat gbe_st; + int flags; - xopen(&gbe_fd, fname, gbe_flags, &gbe_st); + xopen(&gbe_fd, fname, + command[cmd_index].flags | O_BINARY | O_NOFOLLOW, &gbe_st); + + /* inode will be checked later on write */ + gbe_dev = gbe_st.st_dev; + gbe_ino = gbe_st.st_ino; + + if (gbe_st.st_nlink > 1) + fprintf(stderr, + "%s: warning: file has %lu hard links\n", + fname, (ulong)gbe_st.st_nlink); + + if (gbe_st.st_nlink == 0) + err(EIO, "%s: file unlinked while open", fname); + + flags = fcntl(gbe_fd, F_GETFL); + if (flags == -1) + err(errno, "%s: fcntl(F_GETFL)", fname); + + /* + * O_APPEND must not be used, because this + * allows POSIX write() to ignore the + * current write offset and write at EOF, + * which would therefore break pread/pwrite + */ + if (flags & O_APPEND) + err(EIO, "%s: O_APPEND flag"); gbe_file_size = gbe_st.st_size; @@ -400,78 +1014,165 @@ open_gbe_file(void) case SIZE_128KB: break; default: - err(ECANCELED, "File size must be 8KB, 16KB or 128KB"); + err(EINVAL, "File size must be 8KB, 16KB or 128KB"); } } static void +lock_gbe_file(void) +{ + struct flock fl; + + memset(&fl, 0, sizeof(fl)); + + if (command[cmd_index].flags == O_RDONLY) + fl.l_type = F_RDLCK; + else + fl.l_type = F_WRLCK; + + fl.l_whence = SEEK_SET; + + if (fcntl(gbe_fd, F_SETLK, &fl) == -1) + err(errno, "file is locked by another process"); +} + +static void xopen(int *fd_ptr, const char *path, int flags, struct stat *st) { if ((*fd_ptr = open(path, flags)) == -1) - err(ECANCELED, "%s", path); + err(errno, "%s", path); + if (fstat(*fd_ptr, st) == -1) - err(ECANCELED, "%s", path); + err(errno, "%s", path); + + if (!S_ISREG(st->st_mode)) + err(errno, "%s: not a regular file", path); + + if (lseek(*fd_ptr, 0, SEEK_CUR) == (off_t)-1) + err(errno, "%s: file not seekable", path); } static void read_gbe_file(void) { size_t p; - uint8_t do_read[2] = {1, 1}; + u8 do_read[2] = {1, 1}; /* - * The copy, brick and setchecksum commands need - * only read data from the user-specified part. - * - * We can skip reading the other part, thus: + * Commands specifying a partnum only + * need the given GbE part to be read. */ - if (cmd == cmd_copy || - cmd == cmd_brick || - cmd == cmd_setchecksum) + if (command[cmd_index].arg_part) do_read[part ^ 1] = 0; + for (p = 0; p < 2; p++) { + if (do_read[p]) + rw_gbe_file_part(p, IO_PREAD, "pread"); + } +} + +static void +read_checksums(void) +{ + size_t p; + size_t skip_part; + u8 invert; + u8 arg_part; + u8 num_invalid; + u8 max_invalid; + + part_valid[0] = 0; + part_valid[1] = 0; + + if (!command[cmd_index].chksum_read) + return; + + num_invalid = 0; + max_invalid = 2; + + invert = command[cmd_index].invert; + arg_part = command[cmd_index].arg_part; + if (arg_part) + max_invalid = 1; + /* - * SPEED HACK: - * - * On copy/swap commands, flip where data gets written to memory, - * so that cmd_copy and cmd_swap don't have to work on every word - * - * NOTE: - * - * write_gbe_file() will not use this, but copy/setchecksum commands - * will directly manipulate part_modified[], telling write_gbe_file() - * to also write in reverse, as in read_gbe_file(). + * Skip verification on this part, + * but only when arg_part is set. */ - if (cmd == cmd_copy || cmd == cmd_swap) - invert = 1; + skip_part = part ^ 1 ^ invert; for (p = 0; p < 2; p++) { - if (do_read[p]) - read_gbe_file_part(p, invert); + /* + * Only verify a part if it was *read* + */ + if (arg_part && (p == skip_part)) + continue; + + part_valid[p] = good_checksum(p); + if (!part_valid[p]) + ++num_invalid; + } + + if (num_invalid >= max_invalid) { + if (max_invalid == 1) + err(ECANCELED, "%s: part %lu has a bad checksum", + fname, (ulong)part); + err(ECANCELED, "%s: No valid checksum found in file", + fname); } } +static int +good_checksum(size_t partnum) +{ + ushort expected_checksum = calculated_checksum(partnum); + ushort current_checksum = nvm_word(NVM_CHECKSUM_WORD, partnum); + + if (current_checksum == expected_checksum) + return 1; + + return 0; +} + static void -read_gbe_file_part(size_t p, uint8_t invert) +run_cmd(size_t c) { - read_file_exact(gbe_fd, gbe_mem_offset(p ^ invert, "pread"), - GBE_PART_SIZE, gbe_file_offset(p, "pread"), fname, "pread"); + check_command_num(c); + if (command[c].run != NULL) + command[c].run(); } static void -cmd_setmac(void) +check_command_num(size_t c) +{ + if (!valid_command(c)) + err(EINVAL, "Invalid run_cmd arg: %lu", + (ulong)c); +} + +static u8 +valid_command(size_t c) +{ + if (c >= N_COMMANDS) + return 0; + + if (c != command[c].chk) + err(EINVAL, "Invalid cmd chk value (%lu) vs arg: %lu", + (ulong)command[c].chk, (ulong)c); + + return 1; +} + +static void +cmd_helper_setmac(void) { size_t partnum; - uint8_t mac_updated = 0; - parse_mac_string(); printf("MAC address to be written: %s\n", mac_str); + parse_mac_string(); for (partnum = 0; partnum < 2; partnum++) - mac_updated |= write_mac_part(partnum); - - if (mac_updated) - errno = 0; + write_mac_part(partnum); } static void @@ -479,7 +1180,7 @@ parse_mac_string(void) { size_t mac_byte; - if (strlen(mac_str) != 17) + if (xstrxlen(mac_str, 18) != 17) err(EINVAL, "MAC address is the wrong length"); memset(mac_buf, 0, sizeof(mac_buf)); @@ -494,6 +1195,33 @@ parse_mac_string(void) err(EINVAL, "Must not specify multicast MAC address"); } +/* + * strnlen() but aborts on NULL input, and empty strings. + * Our version also prohibits unterminated strings. + * strnlen() was standardized in POSIX.1-2008 and is not + * available on some older systems, so we provide our own. + */ +static size_t +xstrxlen(const char *scmp, size_t maxlen) +{ + size_t xstr_index; + + if (scmp == NULL) + err(EINVAL, "NULL input to xstrxlen"); + + if (*scmp == '\0') + err(EINVAL, "Empty string in xstrxlen"); + + for (xstr_index = 0; + xstr_index < maxlen && scmp[xstr_index] != '\0'; + xstr_index++); + + if (xstr_index == maxlen) + err(EINVAL, "Unterminated string in xstrxlen"); + + return xstr_index; +} + static void set_mac_byte(size_t mac_byte_pos) { @@ -513,159 +1241,176 @@ set_mac_byte(size_t mac_byte_pos) static void set_mac_nib(size_t mac_str_pos, - size_t mac_byte_pos, size_t mac_nib_pos) + size_t mac_byte_pos, size_t mac_nib_pos) { char mac_ch; - uint16_t hex_num; + ushort hex_num; mac_ch = mac_str[mac_str_pos + mac_nib_pos]; - hex_num = hextonum(mac_ch); - if (hex_num > 15) + if ((hex_num = hextonum(mac_ch)) > 15) err(EINVAL, "Invalid character '%c'", mac_str[mac_str_pos + mac_nib_pos]); - /* If random, ensure that local/unicast bits are set */ + /* + * 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 */ /* - * Words other than the MAC address are stored little - * endian in the file, and we handle that when reading. - * However, MAC address words are stored big-endian - * in that file, so we write each 2-byte word logically - * in little-endian order, which on little-endian would - * be stored big-endian in memory, and vice versa. - * - * Later code using the MAC string will handle this. + * MAC words stored big endian in-file, little-endian + * logically, so we reverse the order. */ - 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? */ } -static uint8_t +static ushort hextonum(char ch_s) { - /* - * We assume char is signed, hence ch_s. - * We explicitly cast to unsigned: - */ - unsigned char ch = (unsigned char)ch_s; + u8 ch = (u8)ch_s; - if ((unsigned)(ch - '0') <= 9) + if ((uint)(ch - '0') <= 9) return ch - '0'; ch |= 0x20; - if ((unsigned)(ch - 'a') <= 5) + if ((uint)(ch - 'a') <= 5) return ch - 'a' + 10; - else if (ch == '?' || ch == 'x') + + if (ch == '?' || ch == 'x') return rhex(); /* random character */ - else - return 16; /* invalid character */ + + return 16; /* invalid character */ } -static uint8_t +#if defined(HAVE_ARC4RANDOM_BUF) && \ + (HAVE_ARC4RANDOM_BUF) > 0 +static ushort rhex(void) { + static u8 num[12]; static size_t n = 0; - static uint8_t rnum[12]; if (!n) { - n = sizeof(rnum); -#ifdef HAVE_ARC4RANDOM_BUF - arc4random_buf(rnum, n); -#else - read_file_exact(urandom_fd, rnum, n, 0, rname, NULL); -#endif + n = 12; + arc4random_buf(num, 12); } - return rnum[--n] & 0xf; + return num[--n] & 0xf; } - -static void -read_file_exact(int fd, void *buf, size_t len, - off_t off, const char *path, const char *op) +#else +static ushort +rhex(void) { - int retry; - ssize_t rval; + struct timeval tv; + ulong mix; + static ulong counter = 0; - for (retry = 0; retry < MAX_RETRY_READ; retry++) { - if (op) - rval = pread(fd, buf, len, off); - else - rval = read(fd, buf, len); + gettimeofday(&tv, NULL); - if (rval == (ssize_t)len) { - errno = 0; - return; - } + mix = (ulong)tv.tv_sec + ^ (ulong)tv.tv_usec + ^ (ulong)getpid() + ^ (ulong)&mix + ^ counter++ + ^ entropy_jitter(); + + /* + * Stack addresses can vary between + * calls, thus increasing entropy. + */ + mix ^= (ulong)&mix; + mix ^= (ulong)&tv; + mix ^= (ulong)&counter; + + return (ushort)(mix & 0xf); +} + +static ulong +entropy_jitter(void) +{ + struct timeval a, b; + ulong mix = 0; + long mix_diff; + int i; + + for (i = 0; i < 8; i++) { + gettimeofday(&a, NULL); + getpid(); + gettimeofday(&b, NULL); - if (rval != -1) - err(ECANCELED, - "Short %s, %zd bytes, on file: %s", - op ? op : "read", rval, path); + /* + * prevent negative numbers to prevent overflow, + * which would bias rand to large numbers + */ + mix_diff = (long)(b.tv_usec - a.tv_usec); + if (mix_diff < 0) + mix_diff = -mix_diff; - if (errno != EINTR) - err(ECANCELED, - "Could not %s file: '%s'", - op ? op : "read", path); + mix ^= (ulong)(mix_diff); + mix ^= (ulong)&mix; } - err(EINTR, "%s: max retries exceeded on file: %s", - op ? op : "read", path); + return mix; } +#endif -static int +static void write_mac_part(size_t partnum) { size_t w; - if (!good_checksum(partnum)) - return 0; + check_bin(partnum, "part number"); + if (!part_valid[partnum]) + return; for (w = 0; w < 3; w++) - set_word(w, partnum, mac_buf[w]); - - printf("Wrote MAC address to part %zu: ", partnum); - print_mac_address(partnum); - - set_checksum(partnum); + set_nvm_word(w, partnum, mac_buf[w]); - return 1; + printf("Wrote MAC address to part %lu: ", + (ulong)partnum); + print_mac_from_nvm(partnum); } static void -cmd_dump(void) +cmd_helper_dump(void) { size_t partnum; - int num_invalid = 0; - for (partnum = 0; partnum < 2; partnum++) { - if (!good_checksum(partnum)) - ++num_invalid; + part_valid[0] = good_checksum(0); + part_valid[1] = good_checksum(1); - printf("MAC (part %zu): ", partnum); - print_mac_address(partnum); + for (partnum = 0; partnum < 2; partnum++) { + if (!part_valid[partnum]) + fprintf(stderr, + "BAD checksum %04x in part %lu (expected %04x)\n", + nvm_word(NVM_CHECKSUM_WORD, partnum), + (ulong)partnum, + calculated_checksum(partnum)); + + printf("MAC (part %lu): ", + (ulong)partnum); + print_mac_from_nvm(partnum); hexdump(partnum); } - - if (num_invalid < 2) - errno = 0; } static void -print_mac_address(size_t partnum) +print_mac_from_nvm(size_t partnum) { size_t c; + ushort val16; for (c = 0; c < 3; c++) { - uint16_t val16 = word(c, partnum); - printf("%02x:%02x", val16 & 0xff, val16 >> 8); + val16 = nvm_word(c, partnum); + printf("%02x:%02x", + (uint)(val16 & 0xff), + (uint)(val16 >> 8)); if (c == 2) printf("\n"); else @@ -678,212 +1423,347 @@ hexdump(size_t partnum) { size_t c; size_t row; - uint16_t val16; + ushort val16; for (row = 0; row < 8; row++) { - printf("%08zx ", row << 4); + printf("%08lx ", (ulong)((size_t)row << 4)); for (c = 0; c < 8; c++) { - val16 = word((row << 3) + c, partnum); + val16 = nvm_word((row << 3) + c, partnum); if (c == 4) printf(" "); - printf(" %02x %02x", val16 & 0xff, val16 >> 8); + printf(" %02x %02x", + (uint)(val16 & 0xff), + (uint)(val16 >> 8)); } printf("\n"); } } static void -cmd_setchecksum(void) +cmd_helper_cat(void) { - set_checksum(part); -} + size_t p; + size_t ff; + size_t n = 0; -static void -set_checksum(size_t p) -{ - size_t c; - uint16_t val16 = 0; + if (cmd_index == CMD_CAT16) + n = 1; + else if (cmd_index == CMD_CAT128) + n = 15; + else if (cmd_index != CMD_CAT) + err(EINVAL, "cmd_helper_cat called erroneously"); - check_part_num(p); + fflush(NULL); - for (c = 0; c < NVM_CHECKSUM_WORD; c++) - val16 += word(c, p); + for (p = 0; p < 2; p++) { + gbe_cat_buf(buf + (size_t)(p * GBE_PART_SIZE)); - set_word(NVM_CHECKSUM_WORD, p, NVM_CHECKSUM - val16); + for (ff = 0; ff < n; ff++) + gbe_cat_buf(pad); + } } static void -cmd_brick(void) +gbe_cat_buf(u8 *b) { - uint16_t checksum_word; - - if (!good_checksum(part)) { - err(ECANCELED, - "Part %zu checksum already invalid in file '%s'", - part, fname); - } - - /* - * We know checksum_word is valid, so we need only - * flip one bit to invalidate it. - */ - checksum_word = word(NVM_CHECKSUM_WORD, part); - set_word(NVM_CHECKSUM_WORD, part, checksum_word ^ 1); + 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"); } static void -cmd_copy(void) +write_gbe_file(void) { - if (!good_checksum(part ^ 1)) - err(ECANCELED, "copy p%zu, file '%s'", part ^ 1, fname); + struct stat gbe_st; - /* - * SPEED HACK: - * - * read_gbe_file() already performed the copy, - * by virtue of inverted read. We need - * only set the other part as changed. - */ - set_part_modified(part ^ 1); + size_t p; + size_t partnum; + u8 update_checksum; + + if (command[cmd_index].flags == O_RDONLY) + return; + + update_checksum = command[cmd_index].chksum_write; + + override_part_modified(); + + if (fstat(gbe_fd, &gbe_st) == -1) + err(errno, "%s: re-check", fname); + + if (gbe_st.st_dev != gbe_dev || gbe_st.st_ino != gbe_ino) + err(EIO, "%s: file replaced while open", fname); + + if (gbe_st.st_size != gbe_file_size) + err(errno, "%s: file size changed before write", fname); + + if (!S_ISREG(gbe_st.st_mode)) + err(errno, "%s: file type changed before write", fname); + + for (p = 0; p < 2; p++) { + partnum = p ^ command[cmd_index].invert; + + if (!part_modified[partnum]) + continue; + + if (update_checksum) + set_checksum(partnum); + + rw_gbe_file_part(partnum, IO_PWRITE, "pwrite"); + } } static void -cmd_swap(void) +override_part_modified(void) { - if (!(good_checksum(0) || good_checksum(1))) - err(ECANCELED, "swap parts, file '%s'", fname); + u8 mod_type = command[cmd_index].set_modified; - /* - * good_checksum() can set errno, if one - * of the parts is bad. We will reset it. - */ - errno = 0; - - /* - * SPEED HACK: - * - * read_gbe_file() already performed the swap, - * by virtue of inverted read. We need - * only set both parts as changed. - */ - set_part_modified(0); - set_part_modified(1); + switch (mod_type) { + case SET_MOD_0: + set_part_modified(0); + break; + case SET_MOD_1: + set_part_modified(1); + break; + case SET_MOD_N: + set_part_modified(part ^ command[cmd_index].invert); + break; + case SET_MOD_BOTH: + set_part_modified(0); + set_part_modified(1); + break; + case SET_MOD_OFF: + break; + default: + err(EINVAL, "Unsupported set_mod type: %u", + mod_type); + } } -static int -good_checksum(size_t partnum) +static void +set_checksum(size_t p) { - size_t w; - uint16_t total = 0; - - for (w = 0; w <= NVM_CHECKSUM_WORD; w++) - total += word(w, partnum); + check_bin(p, "part number"); + set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p)); +} - if (total == NVM_CHECKSUM) - return 1; +static ushort +calculated_checksum(size_t p) +{ + size_t c; + uint val16 = 0; - fprintf(stderr, "WARNING: BAD checksum in part %zu\n", - partnum ^ invert); + for (c = 0; c < NVM_CHECKSUM_WORD; c++) + val16 += (uint)nvm_word(c, p); - set_err(ECANCELED); - return 0; + return (ushort)((NVM_CHECKSUM - val16) & 0xffff); } /* * GbE NVM files store 16-bit (2-byte) little-endian words. * We must therefore swap the order when reading or writing. + * + * NOTE: The MAC address words are stored big-endian in the + * file, but we assume otherwise and adapt accordingly. */ -static uint16_t -word(size_t pos16, size_t p) +static ushort +nvm_word(size_t pos16, size_t p) { size_t pos; check_nvm_bound(pos16, p); pos = (pos16 << 1) + (p * GBE_PART_SIZE); - return buf[pos] | (buf[pos + 1] << 8); + return (ushort)buf[pos] | + ((ushort)buf[pos + 1] << 8); } static void -set_word(size_t pos16, size_t p, uint16_t val16) +set_nvm_word(size_t pos16, size_t p, ushort val16) { size_t pos; check_nvm_bound(pos16, p); pos = (pos16 << 1) + (p * GBE_PART_SIZE); - buf[pos] = (uint8_t)(val16 & 0xff); - buf[pos + 1] = (uint8_t)(val16 >> 8); + buf[pos] = (u8)(val16 & 0xff); + buf[pos + 1] = (u8)(val16 >> 8); set_part_modified(p); } static void +set_part_modified(size_t p) +{ + check_bin(p, "part number"); + part_modified[p] = 1; +} + +static void check_nvm_bound(size_t c, size_t p) { /* - * NVM_SIZE assumed as the limit, because the + * NVM_SIZE assumed as the limit, because this * current design assumes that we will only * ever modified the NVM area. - * - * The only exception is copy/swap, but these - * do not use word/set_word and therefore do - * not cause check_nvm_bound() to be called. - * - * TODO: - * This should be adjusted in the future, if - * we ever wish to work on the extented area. */ - check_part_num(p); + check_bin(p, "part number"); if (c >= NVM_WORDS) - err(EINVAL, "check_nvm_bound: out of bounds %zu", c); + err(ECANCELED, "check_nvm_bound: out of bounds %lu", + (ulong)c); } static void -write_gbe_file(void) +check_bin(size_t a, const char *a_name) { - size_t p; + if (a > 1) + err(EINVAL, "%s must be 0 or 1, but is %lu", + a_name, (ulong)a); +} - if (gbe_flags == O_RDONLY) - return; +static void +rw_gbe_file_part(size_t p, int rw_type, + const char *rw_type_str) +{ + ssize_t r; + size_t gbe_rw_size = command[cmd_index].rw_size; + u8 invert = command[cmd_index].invert; - for (p = 0; p < 2; p++) { - if (part_modified[p]) - write_gbe_file_part(p); - } + u8 *mem_offset; + off_t file_offset; + + if (rw_type < IO_PREAD || rw_type > IO_PWRITE) + err(errno, "%s: %s: part %lu: invalid rw_type, %d", + fname, rw_type_str, (ulong)p, rw_type); + + if (rw_type == IO_PWRITE) + invert = 0; + + /* + * Inverted reads are used by copy/swap. + * E.g. read from p0 (file) to p1 (mem). + */ + mem_offset = gbe_mem_offset(p ^ invert, rw_type_str); + file_offset = (off_t)gbe_file_offset(p, rw_type_str); + + r = rw_gbe_file_exact(gbe_fd, mem_offset, + gbe_rw_size, file_offset, rw_type); + + if (r == -1) + err(errno, "%s: %s: part %lu", + fname, rw_type_str, (ulong)p); + + if ((size_t)r != gbe_rw_size) + err(EIO, "%s: partial %s: part %lu", + fname, rw_type_str, (ulong)p); } static void -write_gbe_file_part(size_t p) +check_written_part(size_t p) { - ssize_t rval = pwrite(gbe_fd, gbe_mem_offset(p, "pwrite"), - GBE_PART_SIZE, gbe_file_offset(p, "pwrite")); + ssize_t r; + size_t gbe_rw_size; + u8 *mem_offset; + off_t file_offset; + u8 *buf_restore; + struct stat st; + + if (!part_modified[p]) + return; + + gbe_rw_size = command[cmd_index].rw_size; - if (rval == -1) - err(ECANCELED, "Can't write %zu b to '%s' p%zu", - GBE_PART_SIZE, fname, p); + /* invert not needed for pwrite */ + mem_offset = gbe_mem_offset(p, "pwrite"); + file_offset = (off_t)gbe_file_offset(p, "pwrite"); - if (rval != GBE_PART_SIZE) - err(ECANCELED, "CORRUPTED WRITE (%zd b) to file '%s' p%zu", - rval, fname, p); + memset(pad, 0xff, sizeof(pad)); + + if (fstat(gbe_fd, &st) == -1) + err(errno, "%s: fstat (post-write)", fname); + + if (st.st_dev != gbe_dev || st.st_ino != gbe_ino) + err(EIO, "%s: file changed during write", fname); + + r = rw_gbe_file_exact(gbe_fd, pad, + gbe_rw_size, file_offset, IO_PREAD); + + if (r == -1) + rw_check_err_read[p] = io_err_gbe = 1; + else if ((size_t)r != gbe_rw_size) + rw_check_partial_read[p] = io_err_gbe = 1; + else if (memcmp(mem_offset, pad, gbe_rw_size) != 0) + rw_check_bad_part[p] = io_err_gbe = 1; + + if (rw_check_err_read[p] || + 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 = buf; + buf = pad; + post_rw_checksum[p] = good_checksum(0); + buf = buf_restore; } -/* - * Reads to GbE from write_gbe_file_part and read_gbe_file_part - * are filtered through here. These operations must - * only write from the 0th position or the half position - * within the GbE file, and write 4KB of data. - * - * This check is called, to ensure just that. - */ -static off_t -gbe_file_offset(size_t p, const char *f_op) +static void +report_io_err_rw(void) { - return gbe_x_offset(p, f_op, "file", - gbe_file_size >> 1, gbe_file_size); + size_t p; + + if (!io_err_gbe) + return; + + for (p = 0; p < 2; p++) { + if (!part_modified[p]) + continue; + + if (rw_check_err_read[p]) + fprintf(stderr, + "%s: pread: p%lu (post-verification)\n", + fname, (ulong)p); + if (rw_check_partial_read[p]) + fprintf(stderr, + "%s: partial pread: p%lu (post-verification)\n", + fname, (ulong)p); + if (rw_check_bad_part[p]) + fprintf(stderr, + "%s: pwrite: corrupt write on p%lu\n", + fname, (ulong)p); + + if (rw_check_err_read[p] || + rw_check_partial_read[p]) { + fprintf(stderr, + "%s: p%lu: skipped checksum verification " + "(because read failed)\n", + fname, (ulong)p); + + continue; + } + + fprintf(stderr, "%s: ", fname); + + if (post_rw_checksum[p]) + fprintf(stderr, "GOOD"); + else + fprintf(stderr, "BAD"); + + fprintf(stderr, " checksum in p%lu on-disk.\n", + (ulong)p); + + if (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"); + } + } } /* @@ -891,13 +1771,29 @@ gbe_file_offset(size_t p, const char *f_op) * but used to check Gbe bounds in memory, * and it is *also* used during file I/O. */ -static void * +static u8 * gbe_mem_offset(size_t p, const char *f_op) { off_t gbe_off = gbe_x_offset(p, f_op, "mem", GBE_PART_SIZE, GBE_FILE_SIZE); - return (void *)(buf + gbe_off); + return (u8 *)(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. + * + * This check is called, to ensure just that. + */ +static off_t +gbe_file_offset(size_t p, const char *f_op) +{ + off_t gbe_file_half_size = gbe_file_size >> 1; + + return gbe_x_offset(p, f_op, "file", + gbe_file_half_size, gbe_file_size); } static off_t @@ -906,57 +1802,493 @@ gbe_x_offset(size_t p, const char *f_op, const char *d_type, { off_t off; - check_part_num(p); + check_bin(p, "part number"); - off = (off_t)p * nsize; + off = ((off_t)p) * (off_t)nsize; - if (off + GBE_PART_SIZE > ncmp) - err(ECANCELED, "GbE %s %s out of bounds: %s", - d_type, f_op, fname); + if (off > ncmp - GBE_PART_SIZE) + err(ECANCELED, "%s: GbE %s %s out of bounds", + fname, d_type, f_op); if (off != 0 && off != ncmp >> 1) - err(ECANCELED, "GbE %s %s at bad offset: %s", - d_type, f_op, fname); + err(ECANCELED, "%s: GbE %s %s at bad offset", + fname, d_type, f_op); return off; } -static void -set_part_modified(size_t p) +static ssize_t +rw_gbe_file_exact(int fd, u8 *mem, size_t nrw, + off_t off, int rw_type) { - check_part_num(p); - part_modified[p] = 1; + size_t mem_addr; + size_t buf_addr; + ssize_t r; + + if (io_args(fd, mem, nrw, off, rw_type) == -1) + return -1; + + mem_addr = (size_t)(void *)mem; + buf_addr = (size_t)(void *)buf; + + if (mem != (void *)pad) { + if (mem_addr < buf_addr) + goto err_rw_gbe_file_exact; + + if ((mem_addr - buf_addr) >= (size_t)GBE_FILE_SIZE) + goto err_rw_gbe_file_exact; + } + + if (off < 0 || off >= gbe_file_size) + goto err_rw_gbe_file_exact; + + if (nrw > (size_t)(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; } -static void -check_part_num(size_t p) +/* + * 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. + */ +static ssize_t +rw_file_exact(int fd, u8 *mem, size_t nrw, + off_t off, int rw_type, int loop_eagain, + int loop_eintr, size_t max_retries, + int off_reset) { - if (p > 1) - err(EINVAL, "Bad part number (%zu)", p); + ssize_t rv = 0; + ssize_t rc = 0; + size_t retries_on_zero = 0; + off_t off_cur; + size_t nrw_cur; + void *mem_cur; + + if (io_args(fd, mem, nrw, off, rw_type) == -1) + return -1; + + while (1) { + + /* Prevent theoretical overflow */ + if (rv >= 0 && (size_t)rv > (nrw - rc)) + goto err_rw_file_exact; + + rc += rv; + if ((size_t)rc >= nrw) + break; + + mem_cur = (void *)(mem + (size_t)rc); + nrw_cur = (size_t)(nrw - (size_t)rc); + if (off < 0) + goto err_rw_file_exact; + off_cur = (off_t)((size_t)off + (size_t)rc); + + rv = prw(fd, mem_cur, nrw_cur, off_cur, + rw_type, loop_eagain, loop_eintr, + off_reset); + + if (rv < 0) + return -1; + + if (rv == 0) { + if (retries_on_zero++ < max_retries) + continue; + goto err_rw_file_exact; + } + + retries_on_zero = 0; + } + + if ((size_t)rc != nrw) + goto err_rw_file_exact; + + return rw_over_nrw(rc, nrw); + +err_rw_file_exact: + errno = EIO; + return -1; } -static void -usage(void) +/* + * prw() - portable read-write + * + * This implements a portable analog of pwrite() + * and pread() - note that this version is not + * thread-safe (race conditions are possible on + * shared file descriptors). + * + * This limitation is acceptable, since nvmutil is + * single-threaded. Portability is the main goal. + * + * If you need real pwrite/pread, just compile + * with flag: HAVE_REAL_PREAD_PWRITE=1 + * + * A fallback is provided for regular read/write. + * rw_type can be IO_READ, IO_WRITE, IO_PREAD + * or IO_PWRITE + * + * loop_eagain does a retry loop on EAGAIN if set + * loop_eintr does a retry loop on EINTR if set + * + * Unlike the bare syscalls, prw() does security + * checks e.g. checks NULL strings, checks bounds, + * also mitigates a few theoretical libc bugs. + * It is designed for extremely safe single-threaded + * I/O on applications that need it. + * + * NOTE: If you use loop_eagain (1), you enable wait + * loop on EAGAIN. Beware if using this on a non-blocking + * pipe (it could spin indefinitely). + * + * off_reset: if zero, and using fallback pwrite/pread + * analogs, we check if a file offset changed, + * which would indicate another thread changed + * it, and return error, without resetting the + * file - this would allow that thread to keep + * running, but we could then cause a whole + * program exit if we wanted to. + * if not zero: + * we reset and continue, and pray for the worst. + */ + +static 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) { - const char *util = getnvmprogname(); + ssize_t r; + int positional_rw; + struct stat st; +#if !defined(HAVE_REAL_PREAD_PWRITE) || \ + HAVE_REAL_PREAD_PWRITE < 1 + int saved_errno; + off_t verified; + off_t off_orig; + off_t off_last; +#endif -#ifdef __OpenBSD__ - if (pledge("stdio", NULL) == -1) - err(ECANCELED, "pledge"); + if (io_args(fd, mem, nrw, off, rw_type) == -1) + return -1; + + r = -1; + + /* Programs like cat can use this, + so we only check if it's a normal + file if not looping EAGAIN */ + if (!loop_eagain) { + /* + * Checking on every run of prw() + * is expensive if called many + * times, but is defensive in + * case the status changes. + */ + if (check_file(fd, &st) == -1) + return -1; + } + + if (rw_type >= IO_PREAD) + positional_rw = 1; /* pread/pwrite */ + else + positional_rw = 0; /* read/write */ + +try_rw_again: + + if (!positional_rw) { +#if defined(HAVE_REAL_PREAD_PWRITE) && \ + HAVE_REAL_PREAD_PWRITE > 0 +real_pread_pwrite: #endif - fprintf(stderr, - "Modify Intel GbE NVM images e.g. set MAC\n" - "USAGE:\n" - "\t%s FILE dump\n" - "\t%s FILE # same as setmac without [MAC]\n" - "\t%s FILE setmac [MAC]\n" - "\t%s FILE swap\n" - "\t%s FILE copy 0|1\n" - "\t%s FILE brick 0|1\n" - "\t%s FILE setchecksum 0|1\n", - util, util, util, util, util, util, util); + if (rw_type == IO_WRITE) + r = write(fd, mem, nrw); + else if (rw_type == IO_READ) + r = read(fd, mem, nrw); +#if defined(HAVE_REAL_PREAD_PWRITE) && \ + HAVE_REAL_PREAD_PWRITE > 0 + else if (rw_type == IO_PWRITE) + r = pwrite(fd, mem, nrw, off); + else if (rw_type == IO_PREAD) + r = pread(fd, mem, nrw, off); +#endif + + if (r == -1 && (errno == try_err(loop_eintr, EINTR) + || errno == try_err(loop_eagain, EAGAIN))) + goto try_rw_again; + + return rw_over_nrw(r, nrw); + } - err(ECANCELED, "Too few arguments"); +#if defined(HAVE_REAL_PREAD_PWRITE) && \ + HAVE_REAL_PREAD_PWRITE > 0 + goto real_pread_pwrite; +#else + if ((off_orig = lseek_loop(fd, (off_t)0, SEEK_CUR, + loop_eagain, loop_eintr)) == (off_t)-1) { + r = -1; + } else if (lseek_loop(fd, off, SEEK_SET, + loop_eagain, loop_eintr) == (off_t)-1) { + r = -1; + } else { + verified = lseek_loop(fd, (off_t)0, SEEK_CUR, + loop_eagain, loop_eintr); + + /* + * Partial thread-safety: detect + * if the offset changed to what + * we previously got. If it did, + * then another thread may have + * changed it. Enabled if + * off_reset is OFF_RESET. + * + * We do this *once*, on the theory + * that nothing is touching it now. + */ + if (off_reset && off != verified) + lseek_loop(fd, off, SEEK_SET, + loop_eagain, loop_eintr); + + do { + /* + * Verify again before I/O + * (even with OFF_ERR) + * + * This implements the first check + * even with OFF_ERR, but without + * the recovery. On ERR_RESET, if + * the check fails again, then we + * know something else is touching + * the file, so it's best that we + * probably leave it alone and err. + * + * In other words, ERR_RESET only + * tolerates one change. Any more + * will cause an exit, including + * per EINTR/EAGAIN re-spin. + */ + verified = lseek_loop(fd, (off_t)0, SEEK_CUR, + loop_eagain, loop_eintr); + + if (off != verified) + goto err_prw; + + if (rw_type == IO_PREAD) + r = read(fd, mem, nrw); + else if (rw_type == IO_PWRITE) + r = write(fd, mem, nrw); + + if (rw_over_nrw(r, nrw) == -1) { + errno = EIO; + break; + } + + } while (r == -1 && + (errno == try_err(loop_eintr, EINTR) + || errno == try_err(loop_eagain, EAGAIN))); + } + + saved_errno = errno; + + off_last = lseek_loop(fd, off_orig, SEEK_SET, + loop_eagain, loop_eintr); + + if (off_last != off_orig) { + errno = saved_errno; + goto err_prw; + } + + errno = saved_errno; + + return rw_over_nrw(r, nrw); +#endif + +err_prw: + errno = EIO; + return -1; +} + +static int +io_args(int fd, void *mem, size_t nrw, + off_t off, int rw_type) +{ + /* obviously */ + if (mem == NULL) + goto err_io_args; + + /* uninitialised fd */ + if (fd < 0) + goto err_io_args; + + /* negative offset */ + if (off < 0) + goto err_io_args; + + /* prevent zero-byte rw */ + if (!nrw) + goto err_io_args; + + /* prevent overflow */ + if (nrw > (size_t)SSIZE_MAX) + goto err_io_args; + + /* prevent overflow */ + if (((size_t)off + nrw) < (size_t)off) + goto err_io_args; + + if (rw_type > IO_PWRITE) + goto err_io_args; + + return 0; + +err_io_args: + errno = EIO; + return -1; +} + +static int +check_file(int fd, struct stat *st) +{ + if (fstat(fd, st) == -1) + goto err_is_file; + + if (!S_ISREG(st->st_mode)) + goto err_is_file; + + return 0; + +err_is_file: + errno = EIO; + return -1; +} + +/* + * Check overflows caused by buggy libc. + * + * POSIX can say whatever it wants. + * specification != implementation + */ +static ssize_t +rw_over_nrw(ssize_t r, size_t nrw) +{ + /* + * If a byte length of zero + * was requested, that is + * clearly a bug. No way. + */ + 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_MAX, but + * spec != implementation + * + * Check this after using + * [p]read() or [p]write() + */ + goto err_rw_over_nrw; + } + + /* + * Theoretical buggy libc: + * Should never return a number of + * bytes above the requested length. + */ + if ((size_t)r > nrw) + goto err_rw_over_nrw; + + return r; + +err_rw_over_nrw: + + errno = EIO; + return -1; +} + +#if !defined(HAVE_REAL_PREAD_PWRITE) || \ + HAVE_REAL_PREAD_PWRITE < 1 +/* + * lseek_loop() does lseek() but optionally + * on an EINTR/EAGAIN wait loop. Used by prw() + * for setting offsets for positional I/O. + */ +static off_t +lseek_loop(int fd, off_t off, int whence, + int loop_eagain, int loop_eintr) +{ + off_t old = -1; + + do { + old = lseek(fd, off, whence); + } while (old == (off_t)-1 && ( + errno == try_err(loop_eintr, EINTR) || + errno == try_err(loop_eagain, EAGAIN))); + + return old; +} +#endif + +/* + * If a given error loop is enabled, + * e.g. EINTR or EAGAIN, an I/O operation + * will loop until errno isn't -1 and one + * of these, e.g. -1 and EINTR + */ +static int +try_err(int loop_err, int errval) +{ + if (loop_err) + return errval; + + /* errno is never negative, + so functions checking it + can use it accordingly */ + return -1; } static void @@ -964,19 +2296,44 @@ err(int nvm_errval, const char *msg, ...) { va_list args; + if (errno == 0) + errno = nvm_errval; + + (void)close_files(); + fprintf(stderr, "%s: ", getnvmprogname()); va_start(args, msg); vfprintf(stderr, msg, args); va_end(args); - set_err(nvm_errval); fprintf(stderr, ": %s", strerror(errno)); fprintf(stderr, "\n"); exit(EXIT_FAILURE); } +static int +close_files(void) +{ + int close_err_gbe = 0; + int saved_errno = errno; + + if (gbe_fd > -1) { + if (close(gbe_fd) == -1) + close_err_gbe = errno; + gbe_fd = -1; + } + + if (saved_errno) + errno = saved_errno; + + if (close_err_gbe) + return -1; + + return 0; +} + static const char * getnvmprogname(void) { @@ -994,12 +2351,27 @@ getnvmprogname(void) } static void -set_err(int x) +usage(int usage_exit) { - if (errno) - return; - if (x) - errno = x; - else - errno = ECANCELED; + const char *util = getnvmprogname(); + +#ifdef NVMUTIL_PLEDGE + if (pledge("stdio", NULL) == -1) + err(errno, "pledge"); +#endif + 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); + + if (usage_exit) + err(EINVAL, "Too few arguments"); } |
