diff options
| -rw-r--r-- | util/nvmutil/nvmutil.c | 248 | ||||
| -rw-r--r-- | util/spkmodem_recv/spkmodem-recv.c | 34 |
2 files changed, 255 insertions, 27 deletions
diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index e07efbb3..b718ac6e 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -15,6 +15,135 @@ * -Os -Wall -Wextra -Werror -pedantic -std=c90 */ +/* + * 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: uint32_t 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: clean up the do_rw function: make PSCHREIB and + * so on clearer, probably just define them inline and + * validate them inline (no define). + * 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: remove some clever code, e.g.: + * rw_type == PLESEN << 2 + * make stuff like that clearer. + * ditto the invert copy/swap trick + * 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 +*/ + #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 500 #endif @@ -164,6 +293,7 @@ static void set_mac_nib(size_t mac_str_pos, static uint16_t hextonum(char ch_s); static uint16_t rhex(void); static uint16_t fallback_rand(void); +static unsigned long entropy_jitter(void); static void write_mac_part(size_t partnum); /* @@ -214,6 +344,8 @@ static off_t gbe_x_offset(size_t part, const char *f_op, const char *d_type, off_t nsize, off_t ncmp); static ssize_t rw_file_exact(int fd, uint8_t *mem, size_t len, off_t off, int rw_type); +static ssize_t rw_file_once(int fd, uint8_t *mem, size_t len, + off_t off, int rw_type, size_t rc); static ssize_t do_rw(int fd, uint8_t *mem, size_t len, off_t off, int rw_type); static ssize_t prw(int fd, void *mem, size_t nrw, @@ -451,8 +583,8 @@ main(int argc, char *argv[]) #ifdef NVMUTIL_UNVEIL if (pledge("stdio rpath wpath unveil", NULL) == -1) err(errno, "pledge"); - if (unveil("/dev/null", "r") == -1) - err(errno, "unveil '/dev/null'"); + if (unveil("/dev/urandom", "r") == -1) + err(errno, "unveil /dev/urandom"); #else if (pledge("stdio rpath wpath", NULL) == -1) err(errno, "pledge"); @@ -1034,11 +1166,39 @@ fallback_rand(void) ^ (unsigned long)tv.tv_usec ^ (unsigned long)getpid() ^ (unsigned long)&mix - ^ counter++; + ^ counter++ + ^ entropy_jitter(); + + /* + * Stack addresses can vary between + * calls, thus increasing entropy. + */ + mix ^= (unsigned long)&mix; + mix ^= (unsigned long)&tv; + mix ^= (unsigned long)&counter; return (uint16_t)(mix & 0xf); } +static unsigned long +entropy_jitter(void) +{ + struct timeval a, b; + unsigned long mix = 0; + int i; + + for (i = 0; i < 8; i++) { + gettimeofday(&a, NULL); + getpid(); + gettimeofday(&b, NULL); + + mix ^= (unsigned long)(b.tv_usec - a.tv_usec); + mix ^= (unsigned long)&mix; + } + + return mix; +} + static void write_mac_part(size_t partnum) { @@ -1393,41 +1553,89 @@ gbe_x_offset(size_t p, const char *f_op, const char *d_type, * be used on sockets or pipes, because 0-byte * reads are treated like fatal errors. This * means that EOF is also considered fatal. + * + * WARNING: Do not use O_APPEND on open() when + * using this function. If you do, POSIX allows + * write() to ignore the current file offset and + * write at EOF, which means that our use of + * lseek in prw() does not guarantee writing at + * a specified offset. So if using PSCHREIB or + * PLESEN, make sure not to pass a file descriptor + * with the O_APPEND flag. Alternatively, modify + * do_rw() to directly use pwrite() and pread() + * instead of prw(). */ static ssize_t rw_file_exact(int fd, uint8_t *mem, size_t len, off_t off, int rw_type) { - ssize_t rval = 0; - size_t rc = 0; + ssize_t rv; + size_t rc; if (fd < 0 || !len || len > (size_t)SSIZE_MAX) { errno = EIO; return -1; } - while (rc < len) { - rval = do_rw(fd, mem + rc, len - rc, off + rc, rw_type); - - if (rval < 0 && errno == EINTR) { - continue; - } else if (rval < 0) { - errno = EIO; - return -1; - } - if ((size_t)rval > (len - rc) /* Prevent overflow */ - || rval == 0) { /* Prevent infinite 0-byte loop */ - errno = EIO; + for (rc = 0, rv = 0; rc < len; rc += (size_t)rv) { + if ((rv = rw_file_once(fd, mem, len, off, rw_type, rc)) == -1) return -1; - } - - rc += (size_t)rval; } return rc; } static ssize_t +rw_file_once(int fd, uint8_t *mem, size_t len, + off_t off, int rw_type, size_t rc) +{ + ssize_t rv; + size_t retries_on_zero = 0; + size_t max_retries = 10; + +read_again: + rv = do_rw(fd, mem + rc, len - rc, off + rc, rw_type); + + if (rv < 0 && errno == EINTR) { + goto read_again; + } else if (rv < 0) { + errno = EIO; + return -1; + } + + /* + * Theoretical bug: if a buggy libc returned + * a size larger than SSIZE_MAX, the cast may + * cause an overflow. Specifications guarantee + * this won't happen, but spec != implementation + */ + if ((size_t)rv > SSIZE_MAX) { + errno = EIO; + return -1; + /* we will not tolerate your buggy libc */ + } + + if ((size_t)rv > (len - rc) /* Prevent overflow */ + || rv == 0) { /* Prevent infinite 0-byte loop */ + if (rv == 0) { + /* + * Fault tolerance against infinite + * zero-byte loop: re-try a finite + * number of times. This mitigates + * otherwise OK but slow filesystems + * e.g. NFS or slow media. + */ + if (retries_on_zero++ < max_retries) + goto read_again; + } + errno = EIO; + return -1; + } + + return rv; +} + +static ssize_t do_rw(int fd, uint8_t *mem, size_t len, off_t off, int rw_type) { diff --git a/util/spkmodem_recv/spkmodem-recv.c b/util/spkmodem_recv/spkmodem-recv.c index 4ff1fb5f..02f627e5 100644 --- a/util/spkmodem_recv/spkmodem-recv.c +++ b/util/spkmodem_recv/spkmodem-recv.c @@ -7,7 +7,7 @@ * and decodes them. This is a special type of interface provided * by coreboot and GRUB, for computers that lack serial ports. * - * Usage example: + * Usage example (NOTE: little endian!): * parec --channels=1 --rate=48000 --format=s16le | ./spkmodem-recv * * Originally provided by GNU GRUB, this version is a heavily @@ -74,10 +74,12 @@ struct decoder_state { unsigned char ascii; int debug; + int swap_bytes; }; static const char *argv0; +static int host_is_big_endian(void); static void handle_audio(struct decoder_state *st); static int valid_signal(struct decoder_state *st); static void decode_pulse(struct decoder_state *st); @@ -97,10 +99,6 @@ extern int optind; extern int opterr; extern int optopt; -#ifndef errno -extern int errno; -#endif - int main(int argc, char **argv) { @@ -129,6 +127,9 @@ main(int argc, char **argv) break; } + if (host_is_big_endian()) + st.swap_bytes = 1; + setvbuf(stdout, NULL, _IONBF, 0); for (;;) @@ -137,6 +138,13 @@ main(int argc, char **argv) return EXIT_SUCCESS; } +static int +host_is_big_endian(void) +{ + unsigned int x = 1; + return (*(unsigned char *)&x == 0); +} + static void handle_audio(struct decoder_state *st) { @@ -205,6 +213,8 @@ static signed short read_sample(struct decoder_state *st) { size_t n; + signed short sample; + unsigned short u; while (st->inpos >= st->inlen) { @@ -212,16 +222,26 @@ read_sample(struct decoder_state *st) READ_BUF, stdin); if (n == 0) { + if (ferror(stdin)) + err(errno, "stdin read"); if (feof(stdin)) exit(EXIT_SUCCESS); - err(errno, "stdin read"); } st->inpos = 0; st->inlen = n; } - return st->inbuf[st->inpos++]; + sample = st->inbuf[st->inpos++]; + + if (st->swap_bytes) { + u = (unsigned short)sample; + u = (u >> 8) | (u << 8); + + sample = (signed short)u; + } + + return sample; } static int |
