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 | 33 | ||||
| -rw-r--r-- | util/nvmutil/README.md | 4 | ||||
| -rw-r--r-- | util/nvmutil/nvmutil.c | 1185 |
5 files changed, 801 insertions, 432 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 91b5ba1c..22376c70 100644 --- a/util/nvmutil/Makefile +++ b/util/nvmutil/Makefile @@ -3,33 +3,28 @@ # SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com> CC?=cc -CFLAGS?=-Os -Wall -Wextra -Werror -pedantic +CFLAGS?=-Os -Wall -Wextra -Werror -pedantic -std=c99 DESTDIR?= PREFIX?=/usr/local INSTALL?=install -# nvm is the old binary name, -# but it was renamed to nvmutil -# to avoid conflict with a certain -# package manager by that name! +PROG=nvmutil -nvm: nvmutil.c - rm -f nvm - $(CC) $(CFLAGS) nvmutil.c -o nvmutil +all: $(PROG) -install: - $(INSTALL) nvmutil $(DESTDIR)$(PREFIX)/bin/nvmutil +$(PROG): nvmutil.c + $(CC) $(CFLAGS) nvmutil.c -o $(PROG) -# do not delete *bin/nvm because -# there is a package manager by -# that name. this makefile now -# treats nvmutil as the binary +install: $(PROG) + mkdir -p $(DESTDIR)$(PREFIX)/bin/ + install $(PROG) $(DESTDIR)$(PREFIX)/bin/ uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/nvmutil - -distclean: - rm -f nvm nvmutil + rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) clean: - rm -f nvm nvmutil + 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 a03af5e4..966f7a81 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -1,7 +1,32 @@ -/* 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=c99 + */ + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 500 +#endif + +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif +#ifdef __OpenBSD__ +#include <sys/param.h> +#endif +#include <sys/types.h> #include <sys/stat.h> #include <errno.h> @@ -13,66 +38,108 @@ #include <string.h> #include <unistd.h> +#if __STDC_VERSION__ >= 201112L +_Static_assert(sizeof(uint16_t) == 2, "uint16_t must be 16 bits"); +#else +typedef char static_assert_uint16_t_is_2[(sizeof(uint16_t) == 2) ? 1 : -1]; +#endif + /* - * On the platforms below, we will use arc4random - * for random MAC address generation. + * The BSD versions that could realistically build + * nvmutil almost certainly have arc4random (first + * introduced in 1990s to early 2000s). * - * Later on, the code has fallbacks for other systems. + * If you want it on another platform, e.g. Linux, + * just patch this accordingly. Or patch it to remove + * arc4random on old/weird Unix systems. */ #if defined(__OpenBSD__) || defined(__FreeBSD__) || \ defined(__NetBSD__) || defined(__APPLE__) || \ defined(__DragonFly__) -#ifndef HAVE_ARC4RANDOM_BUF -#define HAVE_ARC4RANDOM_BUF +#ifndef NVMUTIL_ARC4RANDOM_BUF +#define NVMUTIL_ARC4RANDOM_BUF 1 +#endif +#endif + +/* + * 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 +static void sanitize_command_list(void); +static void sanitize_command_index(size_t c); +static void check_enum_bin(size_t a, const char *a_name, + size_t b, const char *b_name); 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 run_cmd(ssize_t c); static void set_io_flags(int argc, char *argv[]); -static void open_gbe_file(void); -#ifndef HAVE_ARC4RANDOM_BUF +static int xstrxcmp(const char *a, const char *b, size_t maxlen); +#ifndef NVMUTIL_ARC4RANDOM_BUF static void open_dev_urandom(void); #endif +static void open_gbe_file(void); static void xopen(int *fd, const char *path, int flags, struct stat *st); static void read_gbe_file(void); static void read_gbe_file_part(size_t part); -static void cmd_setmac(void); +static ssize_t read_gbe_file_exact(int fd, void *buf, size_t len, + off_t off); +static void read_checksums(void); +static int good_checksum(size_t partnum); +static void run_cmd(size_t c); +static void check_command_num(size_t c); +static uint8_t valid_command(size_t c); +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 uint16_t hextonum(char ch_s); static uint16_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); +#ifndef NVMUTIL_ARC4RANDOM_BUF +static ssize_t read_dev_urandom(int fd, void *buf, + size_t len); +#endif +static void write_mac_part(size_t partnum); +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); +static void cmd_helper_out(void); +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 uint16_t calculated_checksum(size_t p); +static uint16_t nvm_word(size_t pos16, size_t part); +static void set_nvm_word(size_t pos16, size_t part, uint16_t 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 check_bin(size_t a, const char *a_name); static void write_gbe_file_part(size_t part); 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 void err(int nvm_errval, const char *msg, ...); +static void close_files(void); static const char *getnvmprogname(void); static void set_err(int errval); +static void usage(uint8_t usage_exit); /* * Sizes in bytes: @@ -106,7 +173,7 @@ static void set_err(int errval); * When reading files, we loop on error EINTR * a maximum number of times as defined, thus: */ -#define MAX_RETRY_READ 30 +#define MAX_RETRY_RW 30 /* * Portable macro based on BSD nitems. @@ -114,7 +181,7 @@ static void set_err(int errval); */ #define items(x) (sizeof((x)) / sizeof((x)[0])) -#ifndef HAVE_ARC4RANDOM_BUF +#ifndef NVMUTIL_ARC4RANDOM_BUF static const char newrandom[] = "/dev/urandom"; static const char oldrandom[] = "/dev/random"; /* fallback on OLD unix */ static const char *rname = NULL; @@ -135,213 +202,351 @@ static uint16_t mac_buf[3]; static off_t gbe_file_size; static int gbe_flags; -#ifndef HAVE_ARC4RANDOM_BUF +#ifndef NVMUTIL_ARC4RANDOM_BUF static int urandom_fd = -1; #endif static int gbe_fd = -1; static size_t part; static uint8_t part_modified[2]; +static uint8_t 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; /* + * 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 + +/* * 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_BRICK, - CMD_SETCHECKSUM + CMD_CAT }; -#define CMD_NULL -1 /* - * Those enum values are used for .chk in - * the command struct. Then, even if an index - * is valid, if it doesn't match .chk, run_cmd() - * will fail. This mitigates against the possibility - * of a maintainer (read: you) screwing up the enum, - * which otherwise enables easier understanding. - * - * In other words: the order of the CMD enum must - * precisely match the order of the command struct. + * 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; /* use by in later check on run_cmd, - against cmd index, to verify correct enum order */ + size_t chk; const char *str; void (*run)(void); - int args; + int argc; uint8_t invert; + uint8_t set_modified; + uint8_t arg_part; + uint8_t chksum_read; + uint8_t chksum_write; + size_t rw_size; /* within the 4KB GbE part */ }; + +/* + * Command table, for nvmutil commands + */ static const struct commands command[] = { - { CMD_DUMP, "dump", cmd_dump, 3, 0 }, - { CMD_SETMAC, "setmac", cmd_setmac, 3, 0 }, - { CMD_SWAP, "swap", cmd_swap, 3, 1 }, - { CMD_COPY, "copy", cmd_copy, 4, 1 }, - { CMD_BRICK, "brick", cmd_brick, 4, 0 }, - { CMD_SETCHECKSUM, "setchecksum", cmd_setchecksum, 4, 0 }, + { CMD_DUMP, "dump", cmd_helper_dump, ARGC_3, + NO_INVERT, SET_MOD_OFF, + ARG_NOPART, + SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + NVM_SIZE }, + + { CMD_SETMAC, "setmac", cmd_helper_setmac, ARGC_3, + NO_INVERT, SET_MOD_OFF, + ARG_NOPART, + CHECKSUM_READ, CHECKSUM_WRITE, + NVM_SIZE }, + + /* + * 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 }, + + /* + * 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 }, + + { CMD_CAT, "out", cmd_helper_out, ARGC_3, + NO_INVERT, SET_MOD_OFF, + ARG_NOPART, + SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE, + GBE_PART_SIZE }, }; -static ssize_t cmd = CMD_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; int main(int argc, char *argv[]) { argv0 = argv[0]; - if (argc < 2) - usage(); + if (argc < 3) + usage(1); fname = argv[1]; -#ifdef __OpenBSD__ +#ifdef NVMUTIL_PLEDGE +#ifdef NVMUTIL_UNVEIL 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'"); + if (unveil("/dev/null", "r") == -1) + err(ECANCELED, "unveil '/dev/null'"); +#else + if (pledge("stdio rpath wpath", NULL) == -1) + err(ECANCELED, "pledge"); +#endif #endif + sanitize_command_list(); + set_cmd(argc, argv); - check_cmd_args(argc, argv); + set_cmd_args(argc, argv); set_io_flags(argc, argv); -#ifdef __OpenBSD__ +#ifdef NVMUTIL_PLEDGE +#ifdef NVMUTIL_UNVEIL if (gbe_flags == O_RDONLY) { if (unveil(fname, "r") == -1) - err(ECANCELED, "unveil ro '%s'", fname); + err(ECANCELED, "%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)"); } else { if (unveil(fname, "rw") == -1) - err(ECANCELED, "unveil rw '%s'", fname); + err(ECANCELED, "%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)"); } +#else + if (gbe_flags == O_RDONLY) { + if (pledge("stdio rpath", NULL) == -1) + err(ECANCELED, "pledge ro"); + } +#endif #endif -#ifndef HAVE_ARC4RANDOM_BUF +#ifndef NVMUTIL_ARC4RANDOM_BUF +#if defined(__OpenBSD__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__APPLE__) || \ + defined(__DragonFly__) + err(ECANCELED, "Maintainer error: arc4random disabled on BSD/MacOS"); +#endif open_dev_urandom(); #endif + open_gbe_file(); -#ifdef __OpenBSD__ +#ifdef NVMUTIL_PLEDGE if (pledge("stdio", NULL) == -1) err(ECANCELED, "pledge stdio (main)"); #endif read_gbe_file(); - run_cmd(cmd); - write_gbe_file(); - - 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 + read_checksums(); - /* - * 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"); - } + run_cmd(cmd_index); if (errno) - return EXIT_FAILURE; - else - return EXIT_SUCCESS; + err(errno, "%s: Unhandled error (WRITE SKIPPED)", fname); + else if (gbe_flags != O_RDONLY) + write_gbe_file(); + + close_files(); + + if (errno) + err(ECANCELED, "Unhandled error on exit"); + + return EXIT_SUCCESS; } +/* + * Guard against regressions by maintainers (command table) + */ static void -set_cmd(int argc, char *argv[]) +sanitize_command_list(void) { - /* - * No extra args: ./nvmutil gbe.bin - * Equivalent: ./nvmutil gbe.bin setmac xx:xx:xx:xx:xx:xx - */ - if (argc == 2) { - cmd = CMD_SETMAC; - return; + size_t c; + + for (c = 0; valid_command(c); c++) + sanitize_command_index(c); +} + +static void +sanitize_command_index(size_t c) +{ + uint8_t mod_type; + size_t gbe_rw_size; + + check_command_num(c); + + if (ARGC_3 != 3) + err(ECANCELED, "ARGC_3 is not equal to 3"); + if (ARGC_4 != 4) + err(ECANCELED, "ARGC_4 is not equal to 4"); + + if (command[c].argc < 3) + err(ECANCELED, "cmd index %zu: argc below 3, %d", + c, command[c].argc); + + if (command[c].str == NULL) + err(ECANCELED, "cmd index %zu: NULL str", c); + if (*command[c].str == '\0') + err(ECANCELED, "cmd index %zu: empty str", c); + + if (xstrxlen(command[c].str, MAX_CMD_LEN + 1) > + MAX_CMD_LEN) { + err(ECANCELED, "cmd index %zu: str too long: %s", + c, command[c].str); } - /* - * Three or more args. - * Example: ./nvmutil gbe.bin copy 0 - */ - for (cmd = 0; cmd < (ssize_t)items(command); cmd++) { - if (strcmp(argv[2], command[cmd].str) != 0) + 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"); + + check_enum_bin(ARG_NOPART, "ARG_NOPART", ARG_PART, "ARG_PART"); + check_enum_bin(SKIP_CHECKSUM_READ, "SKIP_CHECKSUM_READ", + CHECKSUM_READ, "CHECKSUM_READ"); + check_enum_bin(SKIP_CHECKSUM_WRITE, "SKIP_CHECKSUM_WRITE", + CHECKSUM_WRITE, "CHECKSUM_WRITE"); + + gbe_rw_size = command[c].rw_size; + + switch (gbe_rw_size) { + case GBE_PART_SIZE: + case NVM_SIZE: + break; + default: + err(EINVAL, "Unsupported rw_size: %zu", gbe_rw_size); + } + + if (gbe_rw_size > GBE_PART_SIZE) + err(EINVAL, "rw_size larger than GbE part: %zu", + gbe_rw_size); +} + +static void +check_enum_bin(size_t a, const char *a_name, + size_t b, const char *b_name) +{ + if (a) + err(ECANCELED, "%s is non-zero", a_name); + + if (b != 1) + err(ECANCELED, "%s is a value other than 1", b_name); +} + +static void +set_cmd(int argc, char *argv[]) +{ + 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; - if (argc >= command[cmd].args) { + else if (argc >= command[cmd_index].argc) return; - } - err(EINVAL, "Too few args: command '%s'", command[cmd].str); + err(EINVAL, "Too few args on command '%s'", cmd_str); } - cmd = CMD_NULL; + cmd_index = CMD_NULL; } static void -check_cmd_args(int argc, char *argv[]) +set_cmd_args(int argc, char *argv[]) { - if (cmd == CMD_NULL && argc > 2) { - /* - * Example: ./nvmutil gbe.bin xx:1f:16:xx:xx:xx - * Equivalent ./nvmutil gbe.bin setmac xx:1f:16:xx:xx:xx - */ - mac_str = argv[2]; - cmd = CMD_SETMAC; - } else if (cmd == CMD_SETMAC) { /* 1 is setmac */ - /* - * Example: ./nvmutil gbe.bin setmac xx:1f:16:xx:xx:xx - */ - mac_str = rmac; /* random MAC */ - if (argc > 3) - mac_str = argv[3]; - } else if (cmd != CMD_NULL && argc > 3) { /* user-supplied partnum */ - /* - * Example: ./nvmutil gbe.bin copy 0 - */ - part = conv_argv_part_num(argv[3]); - } + uint8_t arg_part; + + if (!valid_command(cmd_index) || argc < 3) + usage(1); - if (cmd == CMD_NULL) - err(EINVAL, "Bad command"); + arg_part = command[cmd_index].arg_part; + + /* Maintainer bugs */ + if (arg_part && argc < 4) + err(ECANCELED, + "arg_part set for command that needs argc4"); + if (arg_part && cmd_index == CMD_SETMAC) + err(ECANCELED, + "arg_part set on CMD_SETMAC"); + + if (cmd_index == CMD_SETMAC) + mac_str = argc >= 4 ? argv[3] : rmac; + else if (arg_part) + part = conv_argv_part_num(argv[3]); } static size_t @@ -349,16 +554,11 @@ conv_argv_part_num(const char *part_str) { unsigned char ch; - /* - * Because char signedness is implementation-defined, - * we cast to unsigned char before arithmetic. - */ - 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); @@ -366,21 +566,6 @@ conv_argv_part_num(const char *part_str) } static void -run_cmd(ssize_t c) -{ - size_t d = (size_t)c; - - if (d >= items(command)) - err(ECANCELED, "run_cmd: Invalid run_cmd arg: %zd", c); - - if (d != command[d].chk) - err(ECANCELED, "run_cmd: Invalid chk value (%zu) vs arg: %zd", - command[d].chk, c); - - command[d].run(); -} - -static void set_io_flags(int argc, char *argv[]) { gbe_flags = O_RDWR; @@ -388,30 +573,62 @@ set_io_flags(int argc, char *argv[]) if (argc < 3) return; - if (strcmp(argv[2], "dump") == 0) + if (xstrxcmp(argv[2], "dump", MAX_CMD_LEN) == 0) gbe_flags = O_RDONLY; } -#ifndef HAVE_ARC4RANDOM_BUF +/* + * Portable strcmp() but blocks NULL/empty/unterminated + * strings. Even stricter than strncmp(). + */ +static int +xstrxcmp(const char *a, const char *b, size_t maxlen) +{ + size_t i; + + if (a == NULL || b == NULL) + err(EINVAL, "NULL input to xstrxcmp"); + + if (*a == '\0' || *b == '\0') + err(EINVAL, "Empty string in xstrxcmp"); + + for (i = 0; i < maxlen; i++) { + if (a[i] != b[i]) + return (unsigned char)a[i] - (unsigned char)b[i]; + + if (a[i] == '\0') + return 0; + } + + /* + * 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; +} + +#ifndef NVMUTIL_ARC4RANDOM_BUF static void open_dev_urandom(void) { struct stat st_urandom_fd; - /* - * Try /dev/urandom first - */ rname = newrandom; if ((urandom_fd = open(rname, O_RDONLY)) != -1) return; /* - * 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. + * Fall back to /dev/random on very old Unix. */ + + fprintf(stderr, "Can't open %s (will use %s instead)\n", + newrandom, oldrandom); + errno = 0; rname = oldrandom; @@ -443,6 +660,7 @@ xopen(int *fd_ptr, const char *path, int flags, struct stat *st) { if ((*fd_ptr = open(path, flags)) == -1) err(ECANCELED, "%s", path); + if (fstat(*fd_ptr, st) == -1) err(ECANCELED, "%s", path); } @@ -454,14 +672,10 @@ read_gbe_file(void) uint8_t 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++) { @@ -473,26 +687,167 @@ read_gbe_file(void) static void read_gbe_file_part(size_t p) { - void *mem_offset = gbe_mem_offset(p ^ command[cmd].invert, "pread"); + ssize_t rc; + + size_t gbe_rw_size = command[cmd_index].rw_size; + void *mem_offset = + gbe_mem_offset(p ^ command[cmd_index].invert, "pread"); + + rc = read_gbe_file_exact(gbe_fd, mem_offset, + gbe_rw_size, gbe_file_offset(p, "pread")); + + if (rc != (ssize_t)gbe_rw_size) + err(ECANCELED, "%s: Partial read from p%zu", fname, p); +} + +static ssize_t +read_gbe_file_exact(int fd, + void *buf, size_t len, off_t off) +{ + int retry; + ssize_t rval; + + if (fd == -1) + err(ECANCELED, "Trying to open bad fd: %s", fname); + + for (retry = 0; retry < MAX_RETRY_RW; retry++) { + rval = pread(fd, buf, len, off); + + if (rval == (ssize_t)len) { + errno = 0; + return rval; + } else if (rval != -1) { + err(ECANCELED, + "%s: Short pread of %zd bytes", + fname, rval); + } else if (errno != EINTR) { + err(ECANCELED, + "%s: Could not pread", fname); + } + } + + err(EINTR, "%s: pread: max retries exceeded", fname); + + return -1; +} + +static void +read_checksums(void) +{ + size_t p; + size_t skip_part; + uint8_t invert; + uint8_t arg_part; + uint8_t num_invalid; + uint8_t max_invalid; + + 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; + + /* + * Skip verification on this part, + * but only when arg_part is set. + */ + skip_part = part ^ 1 ^ invert; + + for (p = 0; p < 2; p++) { + /* + * Only verify a part if it was *read* + */ + if (arg_part && (p == skip_part)) + continue; + + if (good_checksum(p)) + part_valid[p] = 1; + else + ++num_invalid; + } + + if (num_invalid < max_invalid) + errno = 0; + + if (num_invalid >= max_invalid) + err(ECANCELED, "%s: No valid checksum found in file", + fname); +} + +static int +good_checksum(size_t partnum) +{ + uint16_t expected_checksum = calculated_checksum(partnum); + uint16_t current_checksum = nvm_word(NVM_CHECKSUM_WORD, partnum); + + size_t real_partnum = partnum ^ command[cmd_index].invert; + + if (current_checksum == expected_checksum) + return 1; + + fprintf(stderr, + "WARNING: BAD checksum in part %zu\n" + "EXPECTED checksum in part %zu: %04x\n" + "CURRENT checksum in part %zu: %04x\n", + real_partnum, + real_partnum, + expected_checksum, + real_partnum, + current_checksum); + + set_err(ECANCELED); + return 0; +} - read_file_exact(gbe_fd, mem_offset, - GBE_PART_SIZE, gbe_file_offset(p, "pread"), fname, "pread"); +static void +run_cmd(size_t c) +{ + check_command_num(c); + if (command[c].run) + command[c].run(); } static void -cmd_setmac(void) +check_command_num(size_t c) +{ + if (!valid_command(c)) + err(ECANCELED, "Invalid run_cmd arg: %zu", c); +} + +static uint8_t +valid_command(size_t c) +{ + if (c >= N_COMMANDS) + return 0; + + if (c != command[c].chk) + err(ECANCELED, "Invalid cmd chk value (%zu) vs arg: %zu", + command[c].chk, c); + + return 1; +} + +static void +cmd_helper_setmac(void) { size_t partnum; - uint8_t mac_updated = 0; + +#ifdef NVMUTIL_ARC4RANDOM_BUF + printf("Randomisation method: arc4random_buf\n"); +#else + printf("Randomisation method: %s\n", rname); +#endif 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 @@ -500,7 +855,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)); @@ -515,6 +870,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) { @@ -545,23 +927,18 @@ set_mac_nib(size_t mac_str_pos, 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? */ @@ -570,10 +947,6 @@ set_mac_nib(size_t mac_str_pos, static uint16_t hextonum(char ch_s) { - /* - * We assume char is signed, hence ch_s. - * We explicitly cast to unsigned: - */ unsigned char ch = (unsigned char)ch_s; if ((unsigned)(ch - '0') <= 9) @@ -597,95 +970,93 @@ rhex(void) static uint8_t rnum[12]; if (!n) { +#ifdef NVMUTIL_ARC4RANDOM_BUF n = sizeof(rnum); -#ifdef HAVE_ARC4RANDOM_BUF arc4random_buf(rnum, n); #else - read_file_exact(urandom_fd, rnum, n, 0, rname, NULL); + n = (size_t)read_dev_urandom( + urandom_fd, rnum, sizeof(rnum)); + + if (!n || n > sizeof(rnum)) + err(ECANCELED, "Randomisation failure"); #endif } return (uint16_t)(rnum[--n] & 0xf); } -static void -read_file_exact(int fd, void *buf, size_t len, - off_t off, const char *path, const char *op) +#ifndef NVMUTIL_ARC4RANDOM_BUF +static ssize_t +read_dev_urandom(int fd, void *buf, size_t len) { int retry; ssize_t rval; - for (retry = 0; retry < MAX_RETRY_READ; retry++) { - if (op) - rval = pread(fd, buf, len, off); - else - rval = read(fd, buf, len); + if (fd == -1) + err(ECANCELED, "Trying to open bad fd: %s", rname); - if (rval == (ssize_t)len) { - errno = 0; - return; + for (retry = 0; retry < MAX_RETRY_RW; retry++) { + rval = read(fd, buf, len); + + if (rval == -1) { + if (errno == EINTR) + continue; + err(errno, "%s", rname); } - if (rval != -1) - err(ECANCELED, - "Short %s, %zd bytes, on file: %s", - op ? op : "read", rval, path); + if (!rval || (size_t)rval > len) + continue; - if (errno != EINTR) - err(ECANCELED, - "Could not %s file: '%s'", - op ? op : "read", path); + errno = 0; + return rval; } - err(EINTR, "%s: max retries exceeded on file: %s", - op ? op : "read", path); + err(EINTR, "%s: read: max retries exceeded: %s", rname); + + return -1; } +#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]); + set_nvm_word(w, partnum, mac_buf[w]); printf("Wrote MAC address to part %zu: ", partnum); - print_mac_address(partnum); - - set_checksum(partnum); - - return 1; + 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; + int errval = good_checksum(0); + errval |= good_checksum(1); + if (errval) + errno = 0; + for (partnum = 0; partnum < 2; partnum++) { printf("MAC (part %zu): ", partnum); - print_mac_address(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; for (c = 0; c < 3; c++) { - uint16_t val16 = word(c, partnum); + uint16_t val16 = nvm_word(c, partnum); printf("%02x:%02x", val16 & 0xff, val16 >> 8); if (c == 2) printf("\n"); @@ -702,9 +1073,9 @@ hexdump(size_t partnum) uint16_t val16; for (row = 0; row < 8; row++) { - printf("%08zx ", row << 4); + printf("%08zx ", (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); @@ -714,120 +1085,113 @@ hexdump(size_t partnum) } static void -cmd_setchecksum(void) +cmd_helper_out(void) { - set_checksum(part); + size_t wc = 0; + ssize_t w; + + fflush(NULL); + + for (wc = 0; wc < sizeof(buf); wc += w) + if ((w = write(STDOUT_FILENO, buf + wc, sizeof(buf) - wc)) < 1) + err(EIO, "%s: stdout", fname); } static void -set_checksum(size_t p) +write_gbe_file(void) { - size_t c; - uint16_t val16 = 0; + size_t p; + size_t partnum; + uint8_t update_checksum; - check_part_num(p); + if (gbe_flags == O_RDONLY) + return; - for (c = 0; c < NVM_CHECKSUM_WORD; c++) - val16 += word(c, p); + update_checksum = command[cmd_index].chksum_write; - set_word(NVM_CHECKSUM_WORD, p, NVM_CHECKSUM - val16); -} + override_part_modified(); -static void -cmd_brick(void) -{ - uint16_t checksum_word; + for (p = 0; p < 2; p++) { + partnum = p ^ command[cmd_index].invert; - if (!good_checksum(part)) { - err(ECANCELED, - "Part %zu checksum already invalid in file '%s'", - part, fname); - } + if (!part_modified[partnum]) + continue; - /* - * 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 (update_checksum) + set_checksum(partnum); + + write_gbe_file_part(partnum); + } } static void -cmd_copy(void) +override_part_modified(void) { - if (!good_checksum(part ^ 1)) - err(ECANCELED, "copy p%zu, file '%s'", part ^ 1, fname); + uint8_t mod_type = command[cmd_index].set_modified; - /* - * 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); + 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 void -cmd_swap(void) +set_checksum(size_t p) { - if (!(good_checksum(0) || good_checksum(1))) - err(ECANCELED, "swap parts, file '%s'", fname); - - /* - * 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); + check_bin(p, "part number"); + set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p)); } -static int -good_checksum(size_t partnum) +static uint16_t +calculated_checksum(size_t p) { - size_t w; - uint16_t total = 0; - - for (w = 0; w <= NVM_CHECKSUM_WORD; w++) - total += word(w, partnum); - - if (total == NVM_CHECKSUM) - return 1; + size_t c; + uint32_t val16 = 0; - fprintf(stderr, "WARNING: BAD checksum in part %zu\n", - partnum ^ command[cmd].invert); + for (c = 0; c < NVM_CHECKSUM_WORD; c++) + val16 += (uint32_t)nvm_word(c, p); - set_err(ECANCELED); - return 0; + return (uint16_t)((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) +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 (uint16_t)buf[pos] | + ((uint16_t)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, uint16_t val16) { size_t pos; @@ -841,55 +1205,68 @@ set_word(size_t pos16, size_t p, uint16_t val16) } 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); } static void -write_gbe_file(void) +check_bin(size_t a, const char *a_name) { - size_t p; - - if (gbe_flags == O_RDONLY) - return; - - for (p = 0; p < 2; p++) { - if (part_modified[p]) - write_gbe_file_part(p); - } + if (a > 1) + err(ECANCELED, "%s must be 0 or 1, but is %zu", a_name, a); } static void write_gbe_file_part(size_t p) { - ssize_t rval = pwrite(gbe_fd, gbe_mem_offset(p, "pwrite"), - GBE_PART_SIZE, gbe_file_offset(p, "pwrite")); + int retry; + ssize_t rval; + size_t gbe_rw_size; - if (rval == -1) - err(ECANCELED, "Can't write %zu b to '%s' p%zu", - GBE_PART_SIZE, fname, p); + if (gbe_fd == -1) + err(ECANCELED, "%s: Trying to write bad gbe_fd", fname); + + gbe_rw_size = command[cmd_index].rw_size; + + for (retry = 0; retry < MAX_RETRY_RW; retry++) { + rval = pwrite(gbe_fd, gbe_mem_offset(p, "pwrite"), + gbe_rw_size, gbe_file_offset(p, "pwrite")); + + if (rval == (ssize_t)gbe_rw_size) { + errno = 0; + printf("%s: Wrote %zu bytes to part %zu\n", + fname, gbe_rw_size, p); + return; + } + + if (rval != -1) + err(ECANCELED, + "%s: Short pwrite of %zd bytes", + fname, rval); + + if (errno != EINTR) + err(ECANCELED, + "%s: pwrite failed on p%zu", fname, p); + } - if (rval != GBE_PART_SIZE) - err(ECANCELED, "CORRUPTED WRITE (%zd b) to file '%s' p%zu", - rval, fname, p); + err(EINTR, "%s: pwrite: max retries exceeded on p%zu", fname, p); } /* @@ -929,62 +1306,27 @@ 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; if (off + GBE_PART_SIZE > ncmp) - err(ECANCELED, "GbE %s %s out of bounds: %s", - d_type, f_op, fname); + 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) -{ - check_part_num(p); - part_modified[p] = 1; -} - -static void -check_part_num(size_t p) -{ - if (p > 1) - err(EINVAL, "Bad part number (%zu)", p); -} - -static void -usage(void) -{ - const char *util = getnvmprogname(); - -#ifdef __OpenBSD__ - if (pledge("stdio", NULL) == -1) - err(ECANCELED, "pledge"); -#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); - - err(ECANCELED, "Too few arguments"); -} - -static void err(int nvm_errval, const char *msg, ...) { + if (nvm_errval != -1) + close_files(); + va_list args; fprintf(stderr, "%s: ", getnvmprogname()); @@ -1000,6 +1342,24 @@ err(int nvm_errval, const char *msg, ...) exit(EXIT_FAILURE); } +static void +close_files(void) +{ + if (gbe_fd > -1) { + if (close(gbe_fd) == -1) + err(-1, "%s: close failed", fname); + gbe_fd = -1; + } + +#ifndef NVMUTIL_ARC4RANDOM_BUF + if (urandom_fd > -1) { + if (close(urandom_fd) == -1) + err(-1, "%s: close failed", rname); + urandom_fd = -1; + } +#endif +} + static const char * getnvmprogname(void) { @@ -1021,8 +1381,31 @@ set_err(int x) { if (errno) return; - if (x) + if (x > 0) errno = x; else errno = ECANCELED; } + +static void +usage(uint8_t usage_exit) +{ + const char *util = getnvmprogname(); + +#ifdef NVMUTIL_PLEDGE + if (pledge("stdio", NULL) == -1) + err(ECANCELED, "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 out\n", + util, util, util, util, util); + + if (usage_exit) + err(EINVAL, "Too few arguments"); +} |
