diff options
Diffstat (limited to 'util')
| -rw-r--r-- | util/nvmutil/nvmutil.c | 797 |
1 files changed, 548 insertions, 249 deletions
diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index da6336aa..62eaa6c5 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -22,7 +22,7 @@ * Make most of nvmutil a *library* for re-use * * TODO: gettimeofday not posible - use portable functions. - * TODO: uint32_t fallback: modify the program instead + * TODO: ux fallback: modify the program instead * to run on 16-bit systems: smaller buffers, and do * operations byte-based instead of word-based. * @@ -135,11 +135,12 @@ CFLAGS += -fstack-protector-strong CFLAGS += -fno-common CFLAGS += -D_FORTIFY_SOURCE=2 CFLAGS += -fPIE -*/ -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 500 -#endif +also consider: +-fstack-clash-protection +-Wl,-z,relro +-Wl,-z,now +*/ #ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 64 @@ -156,31 +157,29 @@ CFLAGS += -fPIE #include <fcntl.h> #include <limits.h> #include <stdarg.h> -#if defined(__has_include) -#if __has_include(<stdint.h>) -#include <stdint.h> -#else -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -#endif -#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -#include <stdint.h> -#else -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -#endif #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_uint8_is_1[(sizeof(uint8_t) == 1) ? 1 : -1]; -typedef char static_assert_uint16_is_2[(sizeof(uint16_t) == 2) ? 1 : -1]; -typedef char static_assert_uint32_is_4[(sizeof(uint32_t) == 4) ? 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 @@ -190,6 +189,9 @@ typedef char static_assert_twos_complement[ * 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. + * + * We set 64 anyway, because there's no reason not + * to, but some systems may ignore _FILE_OFFSET_BITS */ typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1]; @@ -225,8 +227,8 @@ typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1]; #define O_BINARY 0 #endif -#ifndef O_NONBLOCK -#define O_NONBLOCK 0 +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 #endif /* @@ -234,8 +236,6 @@ typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1]; */ 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); /* * Argument handling (user input) @@ -254,6 +254,7 @@ static int xstrxcmp(const char *a, const char *b, size_t maxlen); */ static void open_dev_urandom(void); static void open_gbe_file(void); +static void lock_gbe_file(void); static void xopen(int *fd, const char *path, int flags, struct stat *st); /* @@ -272,7 +273,7 @@ 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 u8 valid_command(size_t c); /* * Helper functions for command: setmac @@ -283,10 +284,10 @@ 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 uint16_t fallback_rand(void); -static unsigned long entropy_jitter(void); +static ushort hextonum(char ch_s); +static ushort rhex(void); +static ushort fallback_rand(void); +static ulong entropy_jitter(void); static void write_mac_part(size_t partnum); /* @@ -301,7 +302,7 @@ static void hexdump(size_t partnum); * cat, cat16 and cat128 */ static void cmd_helper_cat(void); -static void gbe_cat_buf(uint8_t *b); +static void gbe_cat_buf(u8 *b); /* * After command processing, write @@ -313,14 +314,14 @@ static void gbe_cat_buf(uint8_t *b); static void write_gbe_file(void); static void override_part_modified(void); static void set_checksum(size_t part); -static uint16_t calculated_checksum(size_t p); +static ushort calculated_checksum(size_t p); /* * Helper functions for accessing * the NVM area during operation. */ -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 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 check_bin(size_t a, const char *a_name); @@ -331,19 +332,25 @@ static void check_bin(size_t a, const char *a_name); */ static void rw_gbe_file_part(size_t p, int rw_type, const char *rw_type_str); -static uint8_t *gbe_mem_offset(size_t part, const char *f_op); +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 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, +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_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 rw_file_exact(int fd, u8 *mem, size_t len, + off_t off, int rw_type, int loop_eagain, int loop_eintr); +static ssize_t rw_file_once(int fd, u8 *mem, size_t len, + off_t off, int rw_type, size_t rc, int loop_eagain, + int loop_eintr); static ssize_t prw(int fd, void *mem, size_t nrw, - off_t off, int rw_type); -static off_t lseek_eintr(int fd, off_t off, int whence); + off_t off, int rw_type, int loop_eagain, int loop_eintr); +static int rw_over_nrw(ssize_t r, size_t nrw); +static off_t lseek_loop(int fd, off_t off, + int whence, int loop_eagain, int loop_eintr); +static int try_err(int loop_err, int errval); /* * Error handling and cleanup @@ -351,7 +358,7 @@ static off_t lseek_eintr(int fd, off_t off, int whence); static void err(int nvm_errval, const char *msg, ...); static void close_files(void); static const char *getnvmprogname(void); -static void usage(uint8_t usage_exit); +static void usage(int usage_exit); /* * Sizes in bytes: @@ -381,6 +388,9 @@ static void usage(uint8_t usage_exit); #define NVM_WORDS (NVM_SIZE >> 1) #define NVM_CHECKSUM_WORD (NVM_WORDS - 1) +#define NUM_RANDOM_BYTES 12 +static u8 rnum[NUM_RANDOM_BYTES]; + /* * Portable macro based on BSD nitems. * Used to count the number of commands (see below). @@ -399,17 +409,18 @@ static const char *rname = NULL; * * The code will handle this properly. */ -static uint8_t buf[GBE_FILE_SIZE]; -static uint8_t pad[GBE_PART_SIZE]; /* the file that wouldn't die */ +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 urandom_fd = -1; static int gbe_fd = -1; static size_t part; -static uint8_t part_modified[2]; -static uint8_t part_valid[2]; +static u8 part_modified[2]; +static u8 part_valid[2]; static const char rmac[] = "xx:xx:xx:xx:xx:xx"; static const char *mac_str; @@ -486,11 +497,11 @@ struct commands { const char *str; void (*run)(void); int argc; - uint8_t invert; - uint8_t set_modified; - uint8_t arg_part; - uint8_t chksum_read; - uint8_t chksum_write; + 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 */ }; @@ -558,15 +569,50 @@ static const struct commands command[] = { */ 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]; +typedef char assert_rand_byte[(NUM_RANDOM_BYTES>0)?1:-1]; +typedef char assert_rand_len[(NUM_RANDOM_BYTES<NVM_SIZE)?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]; static int use_prng = 0; +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}; + int main(int argc, char *argv[]) { @@ -621,6 +667,7 @@ main(int argc, char *argv[]) open_dev_urandom(); open_gbe_file(); + lock_gbe_file(); #ifdef NVMUTIL_PLEDGE if (pledge("stdio", NULL) == -1) @@ -637,9 +684,27 @@ main(int argc, char *argv[]) run_cmd(cmd_index); - if (command[cmd_index].flags == O_RDWR) + 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); + } + close_files(); return EXIT_SUCCESS; @@ -657,40 +722,34 @@ sanitize_command_list(void) sanitize_command_index(c); } +/* + * TODO: specific config checks per command + */ static void sanitize_command_index(size_t c) { - uint8_t mod_type; + 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", - (unsigned long)c, command[c].argc); + (ulong)c, command[c].argc); if (command[c].str == NULL) err(EINVAL, "cmd index %lu: NULL str", - (unsigned long)c); + (ulong)c); if (*command[c].str == '\0') err(EINVAL, "cmd index %lu: empty str", - (unsigned long)c); + (ulong)c); if (xstrxlen(command[c].str, MAX_CMD_LEN + 1) > MAX_CMD_LEN) { err(EINVAL, "cmd index %lu: str too long: %s", - (unsigned long)c, command[c].str); + (ulong)c, command[c].str); } - if (!((CMD_SETMAC > CMD_DUMP) && (CMD_SWAP > CMD_SETMAC) && - (CMD_COPY > CMD_SWAP) && (CMD_CAT > CMD_COPY) && - (CMD_CAT16 > CMD_CAT) && (CMD_CAT128 > CMD_CAT16))) - err(EINVAL, "Some command integers are the same"); - - if (!((SET_MOD_0 > SET_MOD_OFF) && (SET_MOD_1 > SET_MOD_0) && - (SET_MOD_N > SET_MOD_1) && (SET_MOD_BOTH > SET_MOD_N))) - err(EINVAL, "Some modtype integers are the same"); - mod_type = command[c].set_modified; switch (mod_type) { case SET_MOD_0: @@ -708,13 +767,6 @@ sanitize_command_index(size_t c) 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"); - check_enum_bin(NO_INVERT, "NO_INVERT", PART_INVERT, "PART_INVERT"); - gbe_rw_size = command[c].rw_size; switch (gbe_rw_size) { @@ -723,12 +775,12 @@ sanitize_command_index(size_t c) break; default: err(EINVAL, "Unsupported rw_size: %lu", - (unsigned long)gbe_rw_size); + (ulong)gbe_rw_size); } if (gbe_rw_size > GBE_PART_SIZE) err(EINVAL, "rw_size larger than GbE part: %lu", - (unsigned long)gbe_rw_size); + (ulong)gbe_rw_size); if (command[c].flags != O_RDONLY && command[c].flags != O_RDWR) @@ -736,17 +788,6 @@ 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) -{ - if (a) - err(EINVAL, "%s is non-zero", a_name); - - if (b != 1) - err(EINVAL, "%s is a value other than 1", b_name); -} - -static void set_cmd(int argc, char *argv[]) { const char *cmd_str; @@ -768,7 +809,7 @@ set_cmd(int argc, char *argv[]) static void set_cmd_args(int argc, char *argv[]) { - uint8_t arg_part; + u8 arg_part; if (!valid_command(cmd_index) || argc < 3) usage(1); @@ -792,13 +833,13 @@ set_cmd_args(int argc, char *argv[]) static size_t conv_argv_part_num(const char *part_str) { - unsigned char ch; + u8 ch; if (part_str[0] == '\0' || part_str[1] != '\0') err(EINVAL, "Partnum string '%s' wrong length", part_str); /* char signedness is implementation-defined */ - ch = (unsigned char)part_str[0]; + ch = (u8)part_str[0]; if (ch < '0' || ch > '1') err(EINVAL, "Bad part number (%c)", ch); @@ -822,7 +863,7 @@ xstrxcmp(const char *a, const char *b, size_t maxlen) for (i = 0; i < maxlen; i++) { if (a[i] != b[i]) - return (unsigned char)a[i] - (unsigned char)b[i]; + return (u8)a[i] - (u8)b[i]; if (a[i] == '\0') return 0; @@ -850,7 +891,7 @@ open_dev_urandom(void) /* fallback on VERY VERY VERY old unix */ use_prng = 1; - srand((unsigned)(time(NULL) ^ getpid())); + srand((uint)(time(NULL) ^ getpid())); } static void @@ -858,7 +899,8 @@ open_gbe_file(void) { struct stat gbe_st; - xopen(&gbe_fd, fname, command[cmd_index].flags | O_BINARY, &gbe_st); + xopen(&gbe_fd, fname, + command[cmd_index].flags | O_BINARY | O_NOFOLLOW, &gbe_st); gbe_file_size = gbe_st.st_size; @@ -873,6 +915,24 @@ open_gbe_file(void) } 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) @@ -889,7 +949,7 @@ static void read_gbe_file(void) { size_t p; - uint8_t do_read[2] = {1, 1}; + u8 do_read[2] = {1, 1}; /* * Commands specifying a partnum only @@ -909,10 +969,10 @@ 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; + u8 invert; + u8 arg_part; + u8 num_invalid; + u8 max_invalid; part_valid[0] = 0; part_valid[1] = 0; @@ -949,7 +1009,7 @@ read_checksums(void) if (num_invalid >= max_invalid) { if (max_invalid == 1) err(ECANCELED, "%s: part %lu has a bad checksum", - fname, (unsigned long)part); + fname, (ulong)part); err(ECANCELED, "%s: No valid checksum found in file", fname); } @@ -958,8 +1018,8 @@ read_checksums(void) static int good_checksum(size_t partnum) { - uint16_t expected_checksum = calculated_checksum(partnum); - uint16_t current_checksum = nvm_word(NVM_CHECKSUM_WORD, partnum); + ushort expected_checksum = calculated_checksum(partnum); + ushort current_checksum = nvm_word(NVM_CHECKSUM_WORD, partnum); if (current_checksum == expected_checksum) return 1; @@ -980,10 +1040,10 @@ check_command_num(size_t c) { if (!valid_command(c)) err(EINVAL, "Invalid run_cmd arg: %lu", - (unsigned long)c); + (ulong)c); } -static uint8_t +static u8 valid_command(size_t c) { if (c >= N_COMMANDS) @@ -991,7 +1051,7 @@ valid_command(size_t c) if (c != command[c].chk) err(EINVAL, "Invalid cmd chk value (%lu) vs arg: %lu", - (unsigned long)command[c].chk, (unsigned long)c); + (ulong)command[c].chk, (ulong)c); return 1; } @@ -1077,7 +1137,7 @@ set_mac_nib(size_t mac_str_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]; @@ -1102,17 +1162,17 @@ set_mac_nib(size_t mac_str_pos, | ((mac_nib_pos ^ 1) << 2)); /* left or right nib? */ } -static uint16_t +static ushort hextonum(char ch_s) { - 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; if (ch == '?' || ch == 'x') @@ -1121,37 +1181,36 @@ hextonum(char ch_s) return 16; /* invalid character */ } -static uint16_t +static ushort rhex(void) { static size_t n = 0; - static uint8_t rnum[12]; if (use_prng) return fallback_rand(); if (!n) { n = sizeof(rnum); - if (rw_file_exact(urandom_fd, rnum, n, 0, IO_READ) == -1) + if (rw_file_exact(urandom_fd, rnum, n, 0, IO_READ, 0, 1) == -1) err(errno, "Randomisation failed"); } - return (uint16_t)(rnum[--n] & 0xf); + return (ushort)(rnum[--n] & 0xf); } -static uint16_t +static ushort fallback_rand(void) { struct timeval tv; - unsigned long mix; - static unsigned long counter = 0; + ulong mix; + static ulong counter = 0; gettimeofday(&tv, NULL); - mix = (unsigned long)tv.tv_sec - ^ (unsigned long)tv.tv_usec - ^ (unsigned long)getpid() - ^ (unsigned long)&mix + mix = (ulong)tv.tv_sec + ^ (ulong)tv.tv_usec + ^ (ulong)getpid() + ^ (ulong)&mix ^ counter++ ^ entropy_jitter(); @@ -1159,18 +1218,19 @@ fallback_rand(void) * Stack addresses can vary between * calls, thus increasing entropy. */ - mix ^= (unsigned long)&mix; - mix ^= (unsigned long)&tv; - mix ^= (unsigned long)&counter; + mix ^= (ulong)&mix; + mix ^= (ulong)&tv; + mix ^= (ulong)&counter; - return (uint16_t)(mix & 0xf); + return (ushort)(mix & 0xf); } -static unsigned long +static ulong entropy_jitter(void) { struct timeval a, b; - unsigned long mix = 0; + ulong mix = 0; + long mix_diff; int i; for (i = 0; i < 8; i++) { @@ -1178,8 +1238,16 @@ entropy_jitter(void) getpid(); gettimeofday(&b, NULL); - mix ^= (unsigned long)(b.tv_usec - a.tv_usec); - mix ^= (unsigned long)&mix; + /* + * 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; + + mix ^= (ulong)(mix_diff); + mix ^= (ulong)&mix; } return mix; @@ -1198,7 +1266,7 @@ write_mac_part(size_t partnum) set_nvm_word(w, partnum, mac_buf[w]); printf("Wrote MAC address to part %lu: ", - (unsigned long)partnum); + (ulong)partnum); print_mac_from_nvm(partnum); } @@ -1215,11 +1283,11 @@ cmd_helper_dump(void) fprintf(stderr, "BAD checksum %04x in part %lu (expected %04x)\n", nvm_word(NVM_CHECKSUM_WORD, partnum), - (unsigned long)partnum, + (ulong)partnum, calculated_checksum(partnum)); printf("MAC (part %lu): ", - (unsigned long)partnum); + (ulong)partnum); print_mac_from_nvm(partnum); hexdump(partnum); } @@ -1229,11 +1297,13 @@ static void print_mac_from_nvm(size_t partnum) { size_t c; - uint16_t val16; + ushort val16; for (c = 0; c < 3; c++) { val16 = nvm_word(c, partnum); - printf("%02x:%02x", val16 & 0xff, val16 >> 8); + printf("%02x:%02x", + (uint)(val16 & 0xff), + (uint)(val16 >> 8)); if (c == 2) printf("\n"); else @@ -1246,15 +1316,17 @@ hexdump(size_t partnum) { size_t c; size_t row; - uint16_t val16; + ushort val16; for (row = 0; row < 8; row++) { - printf("%08lx ", (unsigned long)((size_t)row << 4)); + printf("%08lx ", (ulong)((size_t)row << 4)); for (c = 0; c < 8; c++) { 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"); } @@ -1285,36 +1357,21 @@ cmd_helper_cat(void) } static void -gbe_cat_buf(uint8_t *b) +gbe_cat_buf(u8 *b) { - ssize_t rval; - - while (1) { - rval = rw_file_exact(STDOUT_FILENO, b, - GBE_PART_SIZE, 0, IO_WRITE); - - if (rval >= 0) { - /* - * A partial write is especially - * fatal, as it should already be - * prevented in rw_file_exact(). - */ - if ((size_t)rval != GBE_PART_SIZE) - err(EIO, "stdout: cat: Partial write"); - break; - } - - if (errno != EAGAIN) - err(errno, "stdout: cat"); - } + if (rw_file_exact(STDOUT_FILENO, b, + GBE_PART_SIZE, 0, IO_WRITE, 1, 1) < 0) + err(errno, "stdout: cat"); } static void write_gbe_file(void) { + struct stat gbe_st; + size_t p; size_t partnum; - uint8_t update_checksum; + u8 update_checksum; if (command[cmd_index].flags == O_RDONLY) return; @@ -1322,6 +1379,15 @@ write_gbe_file(void) 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_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; @@ -1339,7 +1405,7 @@ write_gbe_file(void) static void override_part_modified(void) { - uint8_t mod_type = command[cmd_index].set_modified; + u8 mod_type = command[cmd_index].set_modified; switch (mod_type) { case SET_MOD_0: @@ -1370,16 +1436,16 @@ set_checksum(size_t p) set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p)); } -static uint16_t +static ushort calculated_checksum(size_t p) { size_t c; - uint32_t val16 = 0; + uint val16 = 0; for (c = 0; c < NVM_CHECKSUM_WORD; c++) - val16 += (uint32_t)nvm_word(c, p); + val16 += (uint)nvm_word(c, p); - return (uint16_t)((NVM_CHECKSUM - val16) & 0xffff); + return (ushort)((NVM_CHECKSUM - val16) & 0xffff); } /* @@ -1390,7 +1456,7 @@ calculated_checksum(size_t p) * file, but we assume otherwise and adapt accordingly. */ -static uint16_t +static ushort nvm_word(size_t pos16, size_t p) { size_t pos; @@ -1398,20 +1464,20 @@ nvm_word(size_t pos16, size_t p) check_nvm_bound(pos16, p); pos = (pos16 << 1) + (p * GBE_PART_SIZE); - return (uint16_t)buf[pos] | - ((uint16_t)buf[pos + 1] << 8); + return (ushort)buf[pos] | + ((ushort)buf[pos + 1] << 8); } static void -set_nvm_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); } @@ -1436,7 +1502,7 @@ check_nvm_bound(size_t c, size_t p) if (c >= NVM_WORDS) err(ECANCELED, "check_nvm_bound: out of bounds %lu", - (unsigned long)c); + (ulong)c); } static void @@ -1444,19 +1510,25 @@ check_bin(size_t a, const char *a_name) { if (a > 1) err(EINVAL, "%s must be 0 or 1, but is %lu", - a_name, (unsigned long)a); + a_name, (ulong)a); } 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; - uint8_t invert = command[cmd_index].invert; + u8 invert = command[cmd_index].invert; - uint8_t *mem_offset; + u8 *mem_offset; + off_t file_offset; - if (rw_type == IO_WRITE || rw_type == IO_PWRITE) + 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; /* @@ -1464,12 +1536,100 @@ rw_gbe_file_part(size_t p, int rw_type, * 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 (rw_file_exact(gbe_fd, mem_offset, - gbe_rw_size, gbe_file_offset(p, rw_type_str), - rw_type) == -1) + if (r == -1) err(errno, "%s: %s: part %lu", - fname, rw_type_str, (unsigned long)p); + 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 +check_written_part(size_t p) +{ + ssize_t r; + size_t gbe_rw_size; + u8 *mem_offset; + off_t file_offset; + u8 *buf_restore; + + if (!part_modified[p]) + return; + + gbe_rw_size = command[cmd_index].rw_size; + + /* invert not needed for pwrite */ + mem_offset = gbe_mem_offset(p, "pwrite"); + file_offset = (off_t)gbe_file_offset(p, "pwrite"); + + 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[0] = io_err_gbe = 1; + + buf_restore = buf; + buf = pad; + post_rw_checksum[p] = good_checksum(0); + buf = buf_restore; +} + +static void +report_io_err_rw(void) +{ + 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); + + /* + * so that we can re-use main checksumming features + * correct part to read always part 0 + */ + + 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"); + } + } } /* @@ -1477,13 +1637,13 @@ rw_gbe_file_part(size_t p, int rw_type, * but used to check Gbe bounds in memory, * and it is *also* used during file I/O. */ -static uint8_t * +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 (uint8_t *)(buf + gbe_off); + return (u8 *)(buf + gbe_off); } /* @@ -1523,10 +1683,38 @@ gbe_x_offset(size_t p, const char *f_op, const char *d_type, return off; } +static ssize_t +rw_gbe_file_exact(int fd, u8 *mem, size_t nrw, + off_t off, int rw_type) +{ + if (mem == NULL) + goto err_rw_gbe_file_exact; + + if (mem != (void *)pad + && mem != (void *)rnum + && (mem < buf || mem >= (buf + 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 > GBE_PART_SIZE) + goto err_rw_gbe_file_exact; + + return rw_file_exact(fd, mem, nrw, off, rw_type, 0, 1); + +err_rw_gbe_file_exact: + errno = EIO; + return -1; +} + /* * Read or write the exact contents of a file, * along with a buffer, (if applicable) offset, - * and number of bytes to be read. It unified + * and number of bytes to be read. It unifies * the functionality of read(), pread(), write() * and pwrite(), with retry-on-EINTR and also * prevents infinite loop on zero-reads. @@ -1539,34 +1727,28 @@ 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 IO_PWRITE or - * IO_PREAD, 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) +rw_file_exact(int fd, u8 *mem, size_t nrw, + off_t off, int rw_type, int loop_eagain, + int loop_eintr) { ssize_t rv; size_t rc; - if (fd < 0 || !len || len > (size_t)SSIZE_MAX - || (unsigned int)rw_type > IO_PWRITE) { - errno = EIO; - return -1; - } - - for (rc = 0, rv = 0; rc < len; ) { - if ((rv = rw_file_once(fd, mem, len, off, rw_type, rc)) <= 0) + for (rc = 0, rv = 0; rc < nrw; ) { + if ((rv = rw_file_once(fd, mem, nrw, off, rw_type, rc, + loop_eagain, loop_eintr)) < 0) + return -1; + + /* rw_file_once never returns + zero, but it's still logically + incorrect not to handle it here */ + + if (rv == 0) { + errno = EIO; return -1; + } rc += (size_t)rv; } @@ -1575,31 +1757,38 @@ rw_file_exact(int fd, uint8_t *mem, size_t len, } /* - * May not return all requested bytes (len). + * Helper function for rw_file_exact, that + * also does extra error handling pertaining + * to GbE file offsets. + * + * May not return all requested bytes (nrw). * Use rw_file_exact for guaranteed length. + * + * 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. */ static ssize_t -rw_file_once(int fd, uint8_t *mem, size_t len, - off_t off, int rw_type, size_t rc) +rw_file_once(int fd, u8 *mem, size_t nrw, + off_t off, int rw_type, size_t rc, + int loop_eagain, int loop_eintr) { ssize_t rv; size_t retries_on_zero = 0; size_t max_retries = 10; -read_again: - if ((unsigned int)rw_type > IO_PWRITE) + if (mem == NULL) goto err_rw_file_once; - rv = do_rw(fd, mem + rc, len - rc, off + rc, rw_type); - - if (rv < 0 && errno == EINTR) - goto read_again; +read_again: + rv = prw(fd, mem + rc, nrw - rc, off + rc, rw_type, + loop_eagain, loop_eintr); if (rv < 0) return -1; - if ((size_t)rv > SSIZE_MAX /* theoretical buggy libc */ - || (size_t)rv > (len - rc))/* don't overflow */ + if ((size_t)rv > (nrw - rc))/* don't overflow */ goto err_rw_file_once; if (rv != 0) @@ -1613,24 +1802,9 @@ err_rw_file_once: return -1; } -static ssize_t -do_rw(int fd, uint8_t *mem, - size_t len, off_t off, int rw_type) -{ - if (rw_type == IO_READ) - return read(fd, mem, len); - - if (rw_type == IO_WRITE) - return write(fd, mem, len); - - if (rw_type == IO_PREAD || rw_type == IO_PWRITE) - return prw(fd, mem, len, off, rw_type); - - errno = EIO; - return -1; -} - /* + * 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 @@ -1638,55 +1812,180 @@ do_rw(int fd, uint8_t *mem, * * This limitation is acceptable, since nvmutil is * single-threaded. Portability is the main goal. + * + * 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. */ + static ssize_t prw(int fd, void *mem, size_t nrw, - off_t off, int rw_type) + off_t off, int rw_type, + int loop_eagain, int loop_eintr) { off_t off_orig; ssize_t r; int saved_errno; - int prw_type; + int flags; + int positional_rw; - prw_type = rw_type ^ IO_PREAD; + if (mem == NULL) + goto err_prw; - if ((unsigned int)prw_type > IO_WRITE) { - errno = EIO; - return -1; + if (fd < 0 + || off < 0 + || !nrw /* prevent zero read request */ + || nrw > (size_t)SSIZE_MAX /* prevent overflow */ + || (uint)rw_type > IO_PWRITE) + goto err_prw; + + r = -1; + + if (rw_type >= IO_PREAD) + positional_rw = 1; /* pread/pwrite */ + else + positional_rw = 0; /* read/write */ + +try_rw_again: + + if (!positional_rw) { + if (rw_type == IO_WRITE) + r = write(fd, mem, nrw); + else if (rw_type == IO_READ) + r = read(fd, mem, nrw); + + 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); } - if ((off_orig = lseek_eintr(fd, (off_t)0, SEEK_CUR)) == (off_t)-1) - return -1; - if (lseek_eintr(fd, off, SEEK_SET) == (off_t)-1) + flags = fcntl(fd, F_GETFL); + if (flags == -1) return -1; + /* + * 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) + goto err_prw; + + 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; + do { - r = do_rw(fd, mem, nrw, off, prw_type); - } while (r < 0 && errno == EINTR); + if (rw_type == IO_PREAD) + r = read(fd, mem, nrw); + else if (rw_type == IO_PWRITE) + r = write(fd, mem, nrw); + + r = rw_over_nrw(r, nrw); + } while (r == -1 && + (errno == try_err(loop_eintr, EINTR) + || errno == try_err(loop_eagain, EAGAIN))); saved_errno = errno; - if (lseek_eintr(fd, off_orig, SEEK_SET) == (off_t)-1) { + if (lseek_loop(fd, off_orig, SEEK_SET, + loop_eagain, loop_eintr) == (off_t)-1) { if (r < 0) errno = saved_errno; return -1; } errno = saved_errno; + return rw_over_nrw(r, nrw); + +err_prw: + errno = EIO; + return -1; +} + +/* + * POSIX can say whatever it wants. + * specification != implementation + */ +static int +rw_over_nrw(ssize_t r, size_t 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; } static off_t -lseek_eintr(int fd, off_t off, int whence) +lseek_loop(int fd, off_t off, int whence, + int loop_eagain, int loop_eintr) { - off_t old; + off_t old = -1; do { old = lseek(fd, off, whence); - } while (old == (off_t)-1 && errno == EINTR); + } while (old == (off_t)-1 && ( + errno == try_err(loop_eintr, EINTR) || + errno == try_err(loop_eagain, EAGAIN))); return old; } +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 err(int nvm_errval, const char *msg, ...) { @@ -1744,7 +2043,7 @@ getnvmprogname(void) } static void -usage(uint8_t usage_exit) +usage(int usage_exit) { const char *util = getnvmprogname(); |
