From 6838db4647b600bf5b356429f54850bf801e7ba4 Mon Sep 17 00:00:00 2001 From: Leah Rowe Date: Fri, 20 Mar 2026 04:02:51 +0000 Subject: WIP: hardened mktemp i'm pretty much nearly there. still no dir support, only files. i won't keep amending now - will do more, then squash later. Signed-off-by: Leah Rowe --- util/nvmutil/include/common.h | 176 ++-- util/nvmutil/lib/checksum.c | 16 +- util/nvmutil/lib/command.c | 171 ++-- util/nvmutil/lib/file.c | 1799 ++++++++++++++++++++++++++++++++++------- util/nvmutil/lib/io.c | 131 +-- util/nvmutil/lib/num.c | 235 ++++-- util/nvmutil/lib/state.c | 155 ++-- util/nvmutil/lib/string.c | 109 ++- util/nvmutil/lib/word.c | 20 +- util/nvmutil/nvmutil.c | 84 +- 10 files changed, 2222 insertions(+), 674 deletions(-) diff --git a/util/nvmutil/include/common.h b/util/nvmutil/include/common.h index 46fbcb38..6cdeba18 100644 --- a/util/nvmutil/include/common.h +++ b/util/nvmutil/include/common.h @@ -35,11 +35,20 @@ int fchmod(int fd, mode_t mode); -/* analog of SSIZE_MAX +/* if 1: on operations that + * check ownership, always + * permit root to access even + * if not the file/dir owner */ +#ifndef ALLOW_ROOT_OVERRIDE +#define ALLOW_ROOT_OVERRIDE 0 +#endif -#ifndef X_LONG_MAX -#define X_LONG_MAX ((long)(~((long)1 << (sizeof(long)*CHAR_BIT-1)))) +/* + */ + +#ifndef SSIZE_MAX +#define SSIZE_MAX ((ssize_t)(~((ssize_t)1 << (sizeof(ssize_t)*CHAR_BIT-1)))) #endif @@ -60,6 +69,13 @@ int fchmod(int fd, mode_t mode); #define OFF_RESET 1 #endif +/* by default: allow use + of openat in hardened mkstemp + */ +#ifndef DISABLE_OPENAT +#define DISABLE_OPENAT 0 /* change to 1 if you don't have openat (old unix) */ +#endif + #ifndef S_ISVTX #define S_ISVTX 01000 #endif @@ -72,8 +88,8 @@ int fchmod(int fd, mode_t mode); #define MAX_ZERO_RW_RETRY 5 #endif -#ifndef HAVE_REAL_PREAD_PWRITE -#define HAVE_REAL_PREAD_PWRITE 0 +#ifndef REAL_POS_IO +#define REAL_POS_IO 0 #endif #ifndef LOOP_EAGAIN @@ -95,6 +111,10 @@ int fchmod(int fd, mode_t mode); #define EXIT_SUCCESS 0 #endif +#ifndef O_NOCTTY +#define O_NOCTTY 0 +#endif + #ifndef O_ACCMODE #define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) #endif @@ -208,14 +228,14 @@ int fchmod(int fd, mode_t mode); */ struct commands { - unsigned long chk; + size_t chk; char *str; void (*run)(void); int argc; unsigned char arg_part; unsigned char chksum_read; unsigned char chksum_write; - unsigned long rw_size; /* within the 4KB GbE part */ + size_t rw_size; /* within the 4KB GbE part */ int flags; /* e.g. O_RDWR or O_RDONLY */ }; @@ -254,7 +274,7 @@ struct xfile { off_t gbe_file_size; off_t gbe_tmp_size; - unsigned long part; + size_t part; unsigned char part_modified[2]; unsigned char part_valid[2]; @@ -278,7 +298,7 @@ struct xstate { char *argv0; - unsigned long i; /* index to cmd[] for current command */ + size_t i; /* index to cmd[] for current command */ int no_cmd; /* Cat commands set this. @@ -286,28 +306,33 @@ struct xstate { int cat; }; +struct path_split { + int dirfd; + char *buf; + const char *base; +}; - -struct xstate *xstatus(int argc, char *argv[]); +struct xstate *xstart(int argc, char *argv[]); +struct xstate *xstatus(void); /* Sanitize command tables. */ void sanitize_command_list(void); -void sanitize_command_index(unsigned long c); +void sanitize_command_index(size_t c); /* Argument handling (user input) */ void set_cmd(int argc, char *argv[]); void set_cmd_args(int argc, char *argv[]); -unsigned long conv_argv_part_num(const char *part_str); -int xstrxcmp(const char *a, const char *b, unsigned long maxlen); +size_t conv_argv_part_num(const char *part_str); /* Prep files for reading */ void open_gbe_file(void); +int is_owner(struct stat *st); int lock_file(int fd, int flags); int same_file(int fd, struct stat *st_old, int check_size); void xopen(int *fd, const char *path, int flags, struct stat *st); @@ -318,25 +343,37 @@ void xopen(int *fd, const char *path, int flags, struct stat *st); void copy_gbe(void); void read_file(void); void read_checksums(void); -int good_checksum(unsigned long partnum); +int good_checksum(size_t partnum); /* validate commands */ -void check_command_num(unsigned long c); -unsigned char valid_command(unsigned long c); +void check_command_num(size_t c); +unsigned char valid_command(size_t c); /* Helper functions for command: setmac */ void cmd_helper_setmac(void); void parse_mac_string(void); -unsigned long xstrxlen(const char *scmp, unsigned long maxlen); -void set_mac_byte(unsigned long mac_byte_pos); -void set_mac_nib(unsigned long mac_str_pos, - unsigned long mac_byte_pos, unsigned long mac_nib_pos); +void set_mac_byte(size_t mac_byte_pos); +void set_mac_nib(size_t mac_str_pos, + size_t mac_byte_pos, size_t mac_nib_pos); +void write_mac_part(size_t partnum); + +/* string functions + */ + +int slen(const char *scmp, size_t maxlen, + size_t *rval); +int scmp(const char *a, const char *b, + size_t maxlen, int *rval); + +/* numerical functions + */ + unsigned short hextonum(char ch_s); -unsigned long rlong(void); +size_t rlong(void); #if !(defined(FALLBACK_RAND_1989) && \ ((FALLBACK_RAND_1989) > 0)) #if defined(__linux__) @@ -346,17 +383,16 @@ int fallback_rand_getrandom(void *buf, size_t len); #endif #endif #else -unsigned long fallback_rand_1989(void); -unsigned long entropy_jitter(void); +size_t fallback_rand_1989(void); +size_t entropy_jitter(void); #endif -void write_mac_part(unsigned long partnum); /* Helper functions for command: dump */ void cmd_helper_dump(void); -void print_mac_from_nvm(unsigned long partnum); -void hexdump(unsigned long partnum); +void print_mac_from_nvm(size_t partnum); +void hexdump(size_t partnum); /* Helper functions for command: swap */ @@ -375,7 +411,7 @@ void cmd_helper_copy(void); void cmd_helper_cat(void); void cmd_helper_cat16(void); void cmd_helper_cat128(void); -void cat(unsigned long nff); +void cat(size_t nff); void cat_buf(unsigned char *b); /* Command verification/control @@ -388,51 +424,51 @@ void cmd_helper_err(void); */ void write_gbe_file(void); -void set_checksum(unsigned long part); -unsigned short calculated_checksum(unsigned long p); +void set_checksum(size_t part); +unsigned short calculated_checksum(size_t p); /* NVM read/write */ -unsigned short nvm_word(unsigned long pos16, unsigned long part); -void set_nvm_word(unsigned long pos16, - unsigned long part, unsigned short val16); -void set_part_modified(unsigned long p); -void check_nvm_bound(unsigned long pos16, unsigned long part); -void check_bin(unsigned long a, const char *a_name); +unsigned short nvm_word(size_t pos16, size_t part); +void set_nvm_word(size_t pos16, + size_t part, unsigned short val16); +void set_part_modified(size_t p); +void check_nvm_bound(size_t pos16, size_t part); +void check_bin(size_t a, const char *a_name); /* GbE file read/write */ -void rw_gbe_file_part(unsigned long p, int rw_type, +void rw_gbe_file_part(size_t p, int rw_type, const char *rw_type_str); void write_to_gbe_bin(void); int gbe_mv(void); -void check_written_part(unsigned long p); +void check_written_part(size_t p); void report_io_err_rw(void); -unsigned char *gbe_mem_offset(unsigned long part, const char *f_op); -off_t gbe_file_offset(unsigned long part, const char *f_op); -off_t gbe_x_offset(unsigned long part, const char *f_op, +unsigned char *gbe_mem_offset(size_t part, const char *f_op); +off_t gbe_file_offset(size_t part, const char *f_op); +off_t gbe_x_offset(size_t part, const char *f_op, const char *d_type, off_t nsize, off_t ncmp); -long rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw, +ssize_t rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw, off_t off, int rw_type); /* Generic read/write */ int fsync_dir(const char *path); -long rw_file_exact(int fd, unsigned char *mem, unsigned long len, +ssize_t rw_file_exact(int fd, unsigned char *mem, size_t len, off_t off, int rw_type, int loop_eagain, int loop_eintr, - unsigned long max_retries, int off_reset); -long prw(int fd, void *mem, unsigned long nrw, + size_t max_retries, int off_reset); +ssize_t prw(int fd, void *mem, size_t nrw, off_t off, int rw_type, int loop_eagain, int loop_eintr, int off_reset); -int io_args(int fd, void *mem, unsigned long nrw, +int io_args(int fd, void *mem, size_t nrw, off_t off, int rw_type); int check_file(int fd, struct stat *st); -long rw_over_nrw(long r, unsigned long nrw); -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 +ssize_t rw_over_nrw(ssize_t r, size_t nrw); +#if !defined(REAL_POS_IO) || \ + REAL_POS_IO < 1 off_t lseek_on_eintr(int fd, off_t off, int whence, int loop_eagain, int loop_eintr); #endif @@ -442,16 +478,40 @@ int try_err(int loop_err, int errval); */ void usage(void); +void err_no_cleanup(int nvm_errval, const char *msg, ...); void err(int nvm_errval, const char *msg, ...); int exit_cleanup(void); const char *getnvmprogname(void); -/* Portable libc functions +/* libc hardening */ char *new_tmpfile(int *fd, int local, const char *path); -int mkstemp_n(char *template); -char *get_tmpdir(void); +char *new_tmplate(int *fd, int local, const char *path); +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation + in mkhtemp() + */ +int mkhtemp(int *fd, struct stat *st, + char *template, int dirfd, const char *fname, + struct stat *st_dir_initial); +#else +int mkhtemp(int *fd, struct stat *st, + char *template); +#endif +int world_writeable_and_sticky(const char *s, + int sticky_allowed, int always_sticky); +int same_dir(const char *a, const char *b); +int tmpdir_policy(const char *path, + int *allow_noworld_unsticky); +char *env_tmpdir(int always_sticky); +int split_path(const char *path, + struct path_split *ps); +int open_verified_dir(const char *path); +int check_dirfd(int dirfd, const char *path); +int secure_file(int *fd, struct stat *st, + int bad_flags, int check_seek, + int do_lock, mode_t mode); int close_on_eintr(int fd); int fsync_on_eintr(int fd); @@ -467,16 +527,16 @@ typedef char static_assert_unsigned_short_is_2[ typedef char static_assert_short_is_2[(sizeof(short) >= 2) ? 1 : -1]; typedef char static_assert_unsigned_int_is_4[ (sizeof(unsigned int) >= 4) ? 1 : -1]; -typedef char static_assert_unsigned_long_is_4[ - (sizeof(unsigned long) >= 4) ? 1 : -1]; -typedef char static_assert_long_ulong[ - (sizeof(unsigned long) == sizeof(long)) ? 1 : -1]; +typedef char static_assert_unsigned_ssize_t_is_4[ + (sizeof(size_t) >= 4) ? 1 : -1]; +typedef char static_assert_ssize_t_ussize_t[ + (sizeof(size_t) == sizeof(ssize_t)) ? 1 : -1]; typedef char static_assert_int_ge_32[(sizeof(int) >= 4) ? 1 : -1]; typedef char static_assert_twos_complement[ ((-1 & 3) == 3) ? 1 : -1 ]; -typedef char assert_unsigned_long_ptr[ - (sizeof(unsigned long) >= sizeof(void *)) ? 1 : -1 +typedef char assert_unsigned_ssize_t_ptr[ + (sizeof(size_t) >= sizeof(void *)) ? 1 : -1 ]; /* diff --git a/util/nvmutil/lib/checksum.c b/util/nvmutil/lib/checksum.c index 8565361b..b417dc7e 100644 --- a/util/nvmutil/lib/checksum.c +++ b/util/nvmutil/lib/checksum.c @@ -17,12 +17,12 @@ void read_checksums(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; - unsigned long _p; - unsigned long _skip_part; + size_t _p; + size_t _skip_part; unsigned char _num_invalid; unsigned char _max_invalid; @@ -60,7 +60,7 @@ read_checksums(void) if (_max_invalid == 1) err(ECANCELED, "%s: part %lu has a bad checksum", - f->fname, (unsigned long)f->part); + f->fname, (size_t)f->part); err(ECANCELED, "%s: No valid checksum found in file", f->fname); @@ -68,7 +68,7 @@ read_checksums(void) } int -good_checksum(unsigned long partnum) +good_checksum(size_t partnum) { unsigned short expected_checksum; unsigned short actual_checksum; @@ -87,16 +87,16 @@ good_checksum(unsigned long partnum) } void -set_checksum(unsigned long p) +set_checksum(size_t p) { check_bin(p, "part number"); set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p)); } unsigned short -calculated_checksum(unsigned long p) +calculated_checksum(size_t p) { - unsigned long c; + size_t c; unsigned int val16; val16 = 0; diff --git a/util/nvmutil/lib/command.c b/util/nvmutil/lib/command.c index 95e1b4f7..3a863d23 100644 --- a/util/nvmutil/lib/command.c +++ b/util/nvmutil/lib/command.c @@ -21,10 +21,10 @@ void sanitize_command_list(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); - unsigned long c; - unsigned long num_commands; + size_t c; + size_t num_commands; num_commands = items(x->cmd); @@ -33,37 +33,41 @@ sanitize_command_list(void) } void -sanitize_command_index(unsigned long c) +sanitize_command_index(size_t c) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[c]; int _flag; - unsigned long gbe_rw_size; + size_t gbe_rw_size; + + size_t rval; check_command_num(c); if (cmd->argc < 3) err(EINVAL, "cmd index %lu: argc below 3, %d", - (unsigned long)c, cmd->argc); + (size_t)c, cmd->argc); if (cmd->str == NULL) err(EINVAL, "cmd index %lu: NULL str", - (unsigned long)c); + (size_t)c); if (*cmd->str == '\0') err(EINVAL, "cmd index %lu: empty str", - (unsigned long)c); + (size_t)c); + + if (slen(cmd->str, MAX_CMD_LEN +1, &rval) < 0) + err(errno, "Could not get command length"); - if (xstrxlen(cmd->str, MAX_CMD_LEN + 1) > - MAX_CMD_LEN) { + if (rval > MAX_CMD_LEN) { err(EINVAL, "cmd index %lu: str too long: %s", - (unsigned long)c, cmd->str); + (size_t)c, cmd->str); } if (cmd->run == NULL) err(EINVAL, "cmd index %lu: cmd ptr null", - (unsigned long)c); + (size_t)c); check_bin(cmd->arg_part, "cmd.arg_part"); check_bin(cmd->chksum_read, "cmd.chksum_read"); @@ -77,12 +81,12 @@ sanitize_command_index(unsigned long c) break; default: err(EINVAL, "Unsupported rw_size: %lu", - (unsigned long)gbe_rw_size); + (size_t)gbe_rw_size); } if (gbe_rw_size > GBE_PART_SIZE) err(EINVAL, "rw_size larger than GbE part: %lu", - (unsigned long)gbe_rw_size); + (size_t)gbe_rw_size); _flag = (cmd->flags & O_ACCMODE); @@ -94,18 +98,22 @@ sanitize_command_index(unsigned long c) void set_cmd(int argc, char *argv[]) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); const char *cmd; - unsigned long c; + int rval; + + size_t c; for (c = 0; c < items(x->cmd); c++) { cmd = x->cmd[c].str; - /* not the right command */ - if (xstrxcmp(argv[2], cmd, MAX_CMD_LEN) != 0) - continue; + if (scmp(argv[2], cmd, MAX_CMD_LEN, &rval) < 0) + err_no_cleanup(EINVAL, + "could not compare command strings"); + if (rval != 0) + continue; /* not the right command */ /* valid command found */ if (argc >= x->cmd[c].argc) { @@ -115,7 +123,7 @@ set_cmd(int argc, char *argv[]) return; } - err(EINVAL, + err_no_cleanup(EINVAL, "Too few args on command '%s'", cmd); } @@ -125,8 +133,8 @@ set_cmd(int argc, char *argv[]) void set_cmd_args(int argc, char *argv[]) { - struct xstate *x = xstatus(0, NULL); - unsigned long i = x->i; + struct xstate *x = xstatus(); + size_t i = x->i; struct commands *cmd = &x->cmd[i]; struct xfile *f = &x->f; @@ -159,7 +167,7 @@ set_cmd_args(int argc, char *argv[]) } } -unsigned long +size_t conv_argv_part_num(const char *part_str) { unsigned char ch; @@ -173,21 +181,21 @@ conv_argv_part_num(const char *part_str) if (ch < '0' || ch > '1') err(EINVAL, "Bad part number (%c)", ch); - return (unsigned long)(ch - '0'); + return (size_t)(ch - '0'); } void -check_command_num(unsigned long c) +check_command_num(size_t c) { if (!valid_command(c)) err(EINVAL, "Invalid run_cmd arg: %lu", - (unsigned long)c); + (size_t)c); } unsigned char -valid_command(unsigned long c) +valid_command(size_t c) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd; if (c >= items(x->cmd)) @@ -206,10 +214,10 @@ valid_command(unsigned long c) void cmd_helper_setmac(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct macaddr *mac = &x->mac; - unsigned long partnum; + size_t partnum; check_cmd(cmd_helper_setmac, "setmac"); @@ -223,12 +231,17 @@ cmd_helper_setmac(void) void parse_mac_string(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct macaddr *mac = &x->mac; - unsigned long mac_byte; + size_t mac_byte; - if (xstrxlen(x->mac.str, 18) != 17) + size_t rval; + + if (slen(x->mac.str, 18, &rval) < 0) + err(EINVAL, "Could not determine MAC length"); + + if (rval != 17) err(EINVAL, "MAC address is the wrong length"); memset(mac->mac_buf, 0, sizeof(mac->mac_buf)); @@ -244,15 +257,15 @@ parse_mac_string(void) } void -set_mac_byte(unsigned long mac_byte_pos) +set_mac_byte(size_t mac_byte_pos) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct macaddr *mac = &x->mac; char separator; - unsigned long mac_str_pos; - unsigned long mac_nib_pos; + size_t mac_str_pos; + size_t mac_nib_pos; mac_str_pos = mac_byte_pos * 3; @@ -267,10 +280,10 @@ set_mac_byte(unsigned long mac_byte_pos) } void -set_mac_nib(unsigned long mac_str_pos, - unsigned long mac_byte_pos, unsigned long mac_nib_pos) +set_mac_nib(size_t mac_str_pos, + size_t mac_byte_pos, size_t mac_nib_pos) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct macaddr *mac = &x->mac; char mac_ch; @@ -278,9 +291,13 @@ set_mac_nib(unsigned long mac_str_pos, mac_ch = mac->str[mac_str_pos + mac_nib_pos]; - if ((hex_num = hextonum(mac_ch)) > 15) - err(EINVAL, "Invalid character '%c'", - mac->str[mac_str_pos + mac_nib_pos]); + if ((hex_num = hextonum(mac_ch)) > 15) { + if (hex_num >= 17) + err(EIO, "Randomisation failure"); + else + err(EINVAL, "Invalid character '%c'", + mac->str[mac_str_pos + mac_nib_pos]); + } /* If random, ensure that local/unicast bits are set. */ @@ -298,13 +315,13 @@ set_mac_nib(unsigned long mac_str_pos, } void -write_mac_part(unsigned long partnum) +write_mac_part(size_t partnum) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; struct macaddr *mac = &x->mac; - unsigned long w; + size_t w; check_bin(partnum, "part number"); if (!f->part_valid[partnum]) @@ -314,17 +331,17 @@ write_mac_part(unsigned long partnum) set_nvm_word(w, partnum, mac->mac_buf[w]); printf("Wrote MAC address to part %lu: ", - (unsigned long)partnum); + (size_t)partnum); print_mac_from_nvm(partnum); } void cmd_helper_dump(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long p; + size_t p; check_cmd(cmd_helper_dump, "dump"); @@ -338,12 +355,12 @@ cmd_helper_dump(void) fprintf(stderr, "BAD checksum %04x in part %lu (expected %04x)\n", nvm_word(NVM_CHECKSUM_WORD, p), - (unsigned long)p, + (size_t)p, calculated_checksum(p)); } printf("MAC (part %lu): ", - (unsigned long)p); + (size_t)p); print_mac_from_nvm(p); @@ -352,9 +369,9 @@ cmd_helper_dump(void) } void -print_mac_from_nvm(unsigned long partnum) +print_mac_from_nvm(size_t partnum) { - unsigned long c; + size_t c; unsigned short val16; for (c = 0; c < 3; c++) { @@ -373,16 +390,16 @@ print_mac_from_nvm(unsigned long partnum) } void -hexdump(unsigned long partnum) +hexdump(size_t partnum) { - unsigned long c; - unsigned long row; + size_t c; + size_t row; unsigned short val16; for (row = 0; row < 8; row++) { printf("%08lx ", - (unsigned long)((unsigned long)row << 4)); + (size_t)((size_t)row << 4)); for (c = 0; c < 8; c++) { @@ -404,24 +421,24 @@ hexdump(unsigned long partnum) void cmd_helper_swap(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; check_cmd(cmd_helper_swap, "swap"); memcpy( - f->buf + (unsigned long)GBE_WORK_SIZE, + f->buf + (size_t)GBE_WORK_SIZE, f->buf, GBE_PART_SIZE); memcpy( f->buf, - f->buf + (unsigned long)GBE_PART_SIZE, + f->buf + (size_t)GBE_PART_SIZE, GBE_PART_SIZE); memcpy( - f->buf + (unsigned long)GBE_PART_SIZE, - f->buf + (unsigned long)GBE_WORK_SIZE, + f->buf + (size_t)GBE_PART_SIZE, + f->buf + (size_t)GBE_WORK_SIZE, GBE_PART_SIZE); set_part_modified(0); @@ -431,14 +448,14 @@ cmd_helper_swap(void) void cmd_helper_copy(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; check_cmd(cmd_helper_copy, "copy"); memcpy( - f->buf + (unsigned long)((f->part ^ 1) * GBE_PART_SIZE), - f->buf + (unsigned long)(f->part * GBE_PART_SIZE), + f->buf + (size_t)((f->part ^ 1) * GBE_PART_SIZE), + f->buf + (size_t)(f->part * GBE_PART_SIZE), GBE_PART_SIZE); set_part_modified(f->part ^ 1); @@ -447,7 +464,7 @@ cmd_helper_copy(void) void cmd_helper_cat(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); check_cmd(cmd_helper_cat, "cat"); @@ -458,7 +475,7 @@ cmd_helper_cat(void) void cmd_helper_cat16(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); check_cmd(cmd_helper_cat16, "cat16"); @@ -469,7 +486,7 @@ cmd_helper_cat16(void) void cmd_helper_cat128(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); check_cmd(cmd_helper_cat128, "cat128"); @@ -478,18 +495,18 @@ cmd_helper_cat128(void) } void -cat(unsigned long nff) +cat(size_t nff) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long p; - unsigned long ff; + size_t p; + size_t ff; p = 0; ff = 0; - if ((unsigned long)x->cat != nff) { + if ((size_t)x->cat != nff) { err(ECANCELED, "erroneous call to cat"); } @@ -501,7 +518,7 @@ cat(unsigned long nff) for (p = 0; p < 2; p++) { cat_buf(f->bufcmp + - (unsigned long)(p * (f->gbe_file_size >> 1))); + (size_t)(p * (f->gbe_file_size >> 1))); for (ff = 0; ff < nff; ff++) { @@ -525,8 +542,8 @@ void check_cmd(void (*fn)(void), const char *name) { - struct xstate *x = xstatus(0, NULL); - unsigned long i = x->i; + struct xstate *x = xstatus(); + size_t i = x->i; if (x->cmd[i].run != fn) err(ECANCELED, "Running %s, but cmd %s is set", diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c index b4925ccd..e722cb6a 100644 --- a/util/nvmutil/lib/file.c +++ b/util/nvmutil/lib/file.c @@ -24,16 +24,29 @@ same_file(int fd, struct stat *st_old, struct stat st; int saved_errno = errno; - if (st_old == NULL || fd < 0) + /* TODO: null/-1 checks + * like this can be + * generalised + */ + if (st_old == NULL) { + errno = EFAULT; + goto err_same_file; + } + if (fd < 0) { + errno = EBADF; goto err_same_file; + } if (fstat(fd, &st) == -1) - return -1; + goto err_same_file; if (st.st_dev != st_old->st_dev || st.st_ino != st_old->st_ino || - !S_ISREG(st.st_mode)) + !S_ISREG(st.st_mode)) { + + errno = ESTALE; goto err_same_file; + } if (check_size && st.st_size != st_old->st_size) @@ -44,13 +57,24 @@ same_file(int fd, struct stat *st_old, err_same_file: - errno = EIO; + if (errno == saved_errno) + errno = ESTALE; + return -1; } /* open() but with abort traps */ +/* TODO: also support other things here than files. + and then use, throughout the program. + in particular, use of openat might help + (split the path) + (see: link attack mitigations throughout nvmutil) + + make it return, and handle the return value/errno + (this could return e.g. EINTR) + */ void xopen(int *fd_ptr, const char *path, int flags, struct stat *st) { @@ -76,30 +100,32 @@ fsync_dir(const char *path) { int saved_errno = errno; - unsigned long pathlen; - unsigned long maxlen; - - char *dirbuf; - int dirfd; + size_t pathlen = 0; + size_t maxlen = 0; - char *slash; + char *dirbuf = NULL; + int dirfd = -1; - struct stat st; + char *slash = NULL; + struct stat st = {0}; #if defined(PATH_LEN) && \ (PATH_LEN) >= 256 maxlen = PATH_LEN; #else - maxlen = 1024; + maxlen = 4096; #endif - dirbuf = NULL; - dirfd = -1; + if (path == NULL) { + errno = EFAULT; + goto err_fsync_dir; + } - pathlen = xstrxlen(path, maxlen); + if (slen(path, maxlen, &pathlen) < 0) + goto err_fsync_dir; - if (pathlen >= maxlen) { - fprintf(stderr, "Path too long for fsync_parent_dir\n"); + if (pathlen >= maxlen || pathlen < 0) { + errno = EMSGSIZE; goto err_fsync_dir; } @@ -110,8 +136,11 @@ fsync_dir(const char *path) } dirbuf = malloc(pathlen + 1); - if (dirbuf == NULL) + if (dirbuf == NULL) { + + errno = ENOMEM; goto err_fsync_dir; + } memcpy(dirbuf, path, pathlen + 1); slash = strrchr(dirbuf, '/'); @@ -127,7 +156,7 @@ fsync_dir(const char *path) dirbuf[1] = '\0'; } - dirfd = open(dirbuf, O_RDONLY + dirfd = open(dirbuf, O_RDONLY | O_CLOEXEC | O_NOCTTY #ifdef O_DIRECTORY | O_DIRECTORY #endif @@ -135,14 +164,26 @@ fsync_dir(const char *path) | O_NOFOLLOW #endif ); - if (dirfd == -1) + if (dirfd < 0) + goto err_fsync_dir; + + /* symlink/directory replacement + attack mitigation + */ + if (check_dirfd(dirfd, dirbuf) < 0) { + + (void) close_on_eintr(dirfd); + + dirfd = -1; goto err_fsync_dir; + } if (fstat(dirfd, &st) < 0) goto err_fsync_dir; if (!S_ISDIR(st.st_mode)) { - fprintf(stderr, "%s: not a directory\n", dirbuf); + + errno = ENOTDIR; goto err_fsync_dir; } @@ -150,316 +191,1420 @@ fsync_dir(const char *path) if (fsync_on_eintr(dirfd) == -1) goto err_fsync_dir; - if (close_on_eintr(dirfd) == -1) + if (close_on_eintr(dirfd) == -1) { + + dirfd = -1; goto err_fsync_dir; + } + + if (dirbuf != NULL) { - if (dirbuf != NULL) free(dirbuf); + dirbuf = NULL; + } + + dirbuf = NULL; errno = saved_errno; return 0; err_fsync_dir: - if (!errno) + + if (errno == saved_errno) errno = EIO; - if (errno != saved_errno) - fprintf(stderr, "%s: %s\n", path, strerror(errno)); + if (dirbuf != NULL) { - if (dirbuf != NULL) free(dirbuf); + dirbuf = NULL; + } - if (dirfd > -1) - close_on_eintr(dirfd); + if (dirfd >= 0) { - errno = saved_errno; + (void) close_on_eintr(dirfd); + dirfd = -1; + } return -1; } -/* returns ptr to path (string). if local>0: - * make tmpfile in the same directory as the - * file. if local==0, use TMPDIR +/* hardened tmpfile creation + * + * if not local, a standard world + * writeable directory (e.g. /tmp) + * will be used. otherwise, + * the path is simply suffixed for + * local tmp file (no world check) * - * if local==0, the 3rd argument is ignored + * sets a file descriptor by pointer + * fd, and returns the path as a + * string (for your new tmp file) + * + * on error, the descriptor will be + * set to -1 and errno will be set, + * to indicate the error, and then + * a NULL pointer will be returned. */ char * -new_tmpfile(int *fd, int local, const char *path) +new_tmpfile(int *fd, int local, + const char *path) { - unsigned long maxlen; +/* TODO: + * directory support (currently only files) + */ + size_t maxlen; struct stat st; - /* please do not modify the - * strings or I will get mad - */ - char tmp_none[] = ""; - char tmp_default[] = "/tmp"; - char default_tmpname[] = "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; - char *tmpname; + char suffix[] = + "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + char *tmpdir = NULL; + + size_t dirlen; + size_t pathlen; + size_t destlen; + size_t baselen; + + char *dest = NULL; /* final path */ + + int saved_errno = errno; + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation + in mkhtemp() + */ + int dirfd = -1; + const char *fname = NULL; + + /* open the directory early, + * check via fd throughout, + * for directory replacement + * attack mitigation + */ + struct stat st_dir_initial; +#endif + + struct path_split ps; + + if (fd == NULL) { + + errno = EFAULT; + goto err_new_tmpfile; + } + + /* block operating on + * someone elses file + */ + if (*fd >= 0) { + + errno = EEXIST; + goto err_new_tmpfile; + + /* they might + * want it later! + */ + } + /* but now it's *mine*: + */ + *fd = -1; + +#if defined(PATH_LEN) && \ + (PATH_LEN) >= 256 + + maxlen = PATH_LEN; +#else + maxlen = 4096; +#endif + + /* base dir e.g. /tmp + */ + if (!local) { + +#if defined(PERMIT_NON_STICKY_ALWAYS) && \ + ((PERMIT_NON_STICKY_ALWAYS) > 0) + + tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS); +#else + tmpdir = env_tmpdir(0); +#endif + if (tmpdir == NULL) + goto err_new_tmpfile; + } + + /* local means we want + * e.g. hello.txt to then + * have a tmpfile created + * for it. useful for + * atomic writes + */ + if (local) { + + if (path == NULL) { + errno = EFAULT; + goto err_new_tmpfile; + } + if (*path == '\0') { + errno = EINVAL; + goto err_new_tmpfile; + } + + if (slen(path, maxlen, &pathlen) < 0) + goto err_new_tmpfile; + + if (path[pathlen - 1] == '/') { + errno = EINVAL; + goto err_new_tmpfile; + } + + dirlen = 0; + + } else { + + if (slen(tmpdir, maxlen, &dirlen) < 0) + goto err_new_tmpfile; + + pathlen = 0; + } + + /* now we want the base dir, + * with the file appended, + * and the XXXXXXXXXX suffix + */ + + /* using sizeof (not slen) adds an extra byte, + * useful because we either want '.' or '/' + */ + destlen = dirlen + pathlen + sizeof(suffix); + + if (destlen > maxlen - 1) { /* -1 for NULL */ + + errno = EOVERFLOW; + goto err_new_tmpfile; + } + + dest = malloc(destlen + 1); /* +1 for NULL */ + if (dest == NULL) { + + errno = ENOMEM; + goto err_new_tmpfile; + } + + /* As you see above, we only allow + * either a base tmpdir and suffix, + * or a user-supplied file and we + * suffix that. + */ + if (dirlen > 0 && pathlen > 0) { + + errno = EINVAL; + goto err_new_tmpfile; /* pre-emptive fix */ + } + + if (local) { + + if (split_path(path, &ps) < 0) + goto err_new_tmpfile; + + if (slen(ps.base, maxlen, &baselen) < 0) + goto err_new_tmpfile; + + /* ALWAYS set this right after + * split path, to avoid leaking fd: + */ + dirfd = ps.dirfd; + + *(dest) = '.'; + + memcpy(dest + 1, ps.base, baselen); + + memcpy(dest + 1 + baselen, + suffix, sizeof(suffix) - 1); + +#if !(defined(DISABLE_OPENAT) && \ +((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation + * in mkhtemp( + */ + fname = dest + 1; +#endif + + if (ps.buf != NULL) { + free(ps.buf); + ps.buf = NULL; + } + + } else { + + memcpy(dest, tmpdir, dirlen); + + *(dest + dirlen) = '/'; + + memcpy(dest + dirlen + 1, suffix, + sizeof(suffix) - 1); + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation + in mkhtemp() + */ + dirfd = open_verified_dir(tmpdir); + if (dirfd < 0) + goto err_new_tmpfile; + + /* we will use this later, throughout, + * for detecting **directory replacement** + */ + if (fstat(dirfd, &st_dir_initial) < 0) + goto err_new_tmpfile; + + fname = dest + dirlen + 1; +#endif + } + *(dest + destlen) = '\0'; + + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation + in mkhtemp() + */ + *fd = mkhtemp(fd, &st, dest, dirfd, fname, &st_dir_initial); +#else + *fd = mkhtemp(fd, &st, dest); +#endif + + if (*fd < 0) + goto err_new_tmpfile; + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation + in mkhtemp() + */ + if (dirfd >= 0) { + (void) close_on_eintr(dirfd); + dirfd = -1; + } +#endif + + errno = saved_errno; + return dest; + +err_new_tmpfile: + + if (errno != saved_errno) + saved_errno = errno; + else + saved_errno = errno = EIO; + + if (dest != NULL) { + free(dest); + dest = NULL; + } + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation + in mkhtemp() + */ + if (dirfd >= 0) { + + (void) close_on_eintr(dirfd); + dirfd = -1; + } +#endif + + if (*fd >= 0) { + + (void) close_on_eintr(*fd); + *fd = -1; + } + + errno = saved_errno; + + return NULL; +} + +/* hardened TMPDIR parsing + */ + +char * +env_tmpdir(int bypass_all_sticky_checks) +{ + char *t; + int allow_noworld_unsticky; + int saved_errno = errno; + + t = getenv("TMPDIR"); + + if (t != NULL && *t != '\0') { + + if (tmpdir_policy(t, + &allow_noworld_unsticky) == 0) { + + if (world_writeable_and_sticky(t, + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + + errno = saved_errno; + return t; + } + } + } + + allow_noworld_unsticky = 0; + + if (world_writeable_and_sticky("/tmp", + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + + errno = saved_errno; + return "/tmp"; + } + + if (world_writeable_and_sticky("/var/tmp", + allow_noworld_unsticky, + bypass_all_sticky_checks)) { + + errno = saved_errno; + return "/var/tmp"; + } + + if (errno == saved_errno) + errno = EPERM; + + return NULL; +} + +int +tmpdir_policy(const char *path, + int *allow_noworld_unsticky) +{ + int saved_errno = errno; + int r; + + if (path == NULL || + allow_noworld_unsticky == NULL) { + + errno = EFAULT; + return -1; + } + + *allow_noworld_unsticky = 1; + + r = same_dir(path, "/tmp"); + if (r < 0) + goto err_tmpdir_policy; + if (r > 0) + *allow_noworld_unsticky = 0; + + r = same_dir(path, "/var/tmp"); + if (r < 0) + goto err_tmpdir_policy; + if (r > 0) + *allow_noworld_unsticky = 0; + + errno = saved_errno; + return 0; + +err_tmpdir_policy: + + if (errno == saved_errno) + errno = EIO; + + return -1; +} + +int +same_dir(const char *a, const char *b) +{ + int fd_a = -1; + int fd_b = -1; + + struct stat st_a; + struct stat st_b; + + int saved_errno = errno; + int rval_scmp; + +#if defined(PATH_LEN) && \ + (PATH_LEN) >= 256 + size_t maxlen = (PATH_LEN); +#else + size_t maxlen = 4096; +#endif + + /* optimisation: if both dirs + are the same, we don't need + to check anything. sehr schnell: + */ + if (scmp(a, b, maxlen, &rval_scmp) < 0) + goto err_same_dir; + /* bonus: scmp checks null for us */ + if (rval_scmp == 0) + goto success_same_dir; + + fd_a = open_verified_dir(a); + if (fd_a < 0) + goto err_same_dir; + + fd_b = open_verified_dir(b); + if (fd_b < 0) + goto err_same_dir; + + if (fstat(fd_a, &st_a) < 0) + goto err_same_dir; + + if (fstat(fd_b, &st_b) < 0) + goto err_same_dir; + + if (st_a.st_dev == st_b.st_dev && + st_a.st_ino == st_b.st_ino) { + + (void) close_on_eintr(fd_a); + (void) close_on_eintr(fd_b); + +success_same_dir: + + /* SUCCESS + */ + + errno = saved_errno; + return 1; + } + + (void) close_on_eintr(fd_a); + (void) close_on_eintr(fd_b); + + /* FAILURE (logical) + */ + + errno = saved_errno; + return 0; + +err_same_dir: + + /* FAILURE (probably syscall) + */ + + if (fd_a >= 0) + (void) close_on_eintr(fd_a); + if (fd_b >= 0) + (void) close_on_eintr(fd_b); + + if (errno == saved_errno) + errno = EIO; + + return -1; +} + +/* bypass_all_sticky_checks: if set, + disable stickiness checks (libc behaviour) + (if not set: leah behaviour) + + allow_noworld_unsticky: + allow non-sticky files if not world-writeable + (still block non-sticky in standard TMPDIR) +*/ +int +world_writeable_and_sticky( + const char *s, + int allow_noworld_unsticky, + int bypass_all_sticky_checks) +{ + struct stat st; + int dirfd = -1; + + int saved_errno = errno; + + if (s == NULL || *s == '\0') { + errno = EINVAL; + goto sticky_hell; + } + + /* mitigate symlink attacks* + */ + dirfd = open_verified_dir(s); + if (dirfd < 0) + goto sticky_hell; + + if (fstat(dirfd, &st) < 0) + goto sticky_hell; + + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + goto sticky_hell; + } + + /* must be fully executable + * by everyone, or openat(2) + * becomes unreliable** + */ + if (!(st.st_mode & S_IXUSR) || + !(st.st_mode & S_IXGRP) || + !(st.st_mode & S_IXOTH)) { + + errno = EACCES; + goto sticky_hell; + } + + /* *normal-**ish mode (libc): + */ + + if (bypass_all_sticky_checks) + goto sticky_heaven; /* normal == no security */ + + /* unhinged leah mode: + */ + + if (is_owner(&st) < 0) + goto sticky_hell; + + if (st.st_mode & S_IWOTH) { /* world writeable */ + + /* if world-writeable, only + * allow sticky files + */ + if (st.st_mode & S_ISVTX) + goto sticky_heaven; /* sticky */ + + errno = EPERM; + goto sticky_hell; /* not sticky */ + } + + /* non-world-writeable, so + * stickiness is do-not-care + */ + if (allow_noworld_unsticky) + goto sticky_heaven; /* sticky! */ + + goto sticky_hell; /* definitely not sticky */ + +sticky_heaven: +/* i like the one in hamburg better */ + + if (dirfd >= 0) + (void) close_on_eintr(dirfd); + + errno = saved_errno; + + return 1; + +sticky_hell: + + if (errno == saved_errno) + errno = EPERM; + + saved_errno = errno; + + if (dirfd >= 0) + (void) close_on_eintr(dirfd); + + errno = saved_errno; + + return 0; +} + +/* mk(h)temp - hardened mktemp. + * like mkstemp, but (MUCH) harder. + * TODO: + * directory support (currently only + * generates files) + */ + +#if defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0) +int +mkhtemp(int *fd, + struct stat *st, + char *template) +#else +int mkhtemp(int *fd, + struct stat *st, + char *template, + int dirfd, + const char *fname, + struct stat *st_dir_initial) +#endif +{ + /* NOTE: this function currently + * only supports *files*. + * it doesn't make tmp*dirs* + */ + + size_t retries = 0; +#if !(defined(MAX_MKHTEMP_RETRIES) && \ + (MAX_MKHTEMP_RETRIES) >= 128) + size_t max_retries = 200; +#else + size_t max_retries = 128; +#endif + + size_t chx = 0; + size_t len = 0; + char *p = NULL; + char *template_copy = NULL; + + size_t xc = 0; + + static char ch[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + size_t limit = + ((size_t)-1) - (((size_t)-1) % (sizeof(ch) - 1)); + + int rand_failures = 0; + + size_t r; +#if defined(PATH_LEN) && \ + (PATH_LEN) >= 256 + size_t max_len = PATH_LEN; +#else + size_t max_len = 4096; +#endif + + int file_created = 0; + + int saved_errno = errno; + struct stat st_tmp; + + mode_t old_umask; + + int saved_rand_error = 0; + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) + char *fname_copy = NULL; + size_t fname_len = 0; + + /* for ctrl char check + */ + unsigned char ctrl = 0; + size_t ctrl_pos = 0; + + /* in openat mode, we re-check + directory against previous + check done by the caller, + * mitigating symlink attacks + */ + struct stat st_dir_now; + + if (fname == NULL || + st_dir_initial == NULL) { + + errno = EFAULT; + goto err_mkhtemp; + } + + if (slen(fname, max_len, &fname_len) < 0) + goto err_mkhtemp; + + if (fname_len == 0) { + errno = EINVAL; + goto err_mkhtemp; + } + + if (dirfd < 0) { + + errno = EBADF; + goto err_mkhtemp; + } +#endif + + if (fd == NULL || + template == NULL) { + + errno = EFAULT; + goto err_mkhtemp; + } + + if (*fd >= 0) { + + errno = EEXIST; + goto err_mkhtemp; + } + + if (slen(template, max_len, &len) < 0) + goto err_mkhtemp; + + /* bounds check */ + if (len >= max_len) { + errno = EMSGSIZE; + goto err_mkhtemp; + } + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) + + if (strrchr(fname, '/') != NULL) { + + /* otherwise, a mangled + path could leave the + directory, defeating + the purpose of openat + */ + errno = EINVAL; + goto err_mkhtemp; + } + + /* reject dangerous basenames + */ + if (fname[0] == '\0' || + (fname[0] == '.' && fname[1] == '\0') || + (fname[0] == '.' && fname[1] == '.' && fname[2] == '\0')) { + + errno = EINVAL; + goto err_mkhtemp; + } + /* block control chars + */ + for (ctrl_pos = 0; + ctrl_pos < fname_len; + ctrl_pos++) { + + ctrl = (unsigned char)fname[ctrl_pos]; + + if (ctrl < 32 || ctrl == 127) { + + errno = EINVAL; + goto err_mkhtemp; + } + } + +#endif + + p = template + len; + + while (p > template && + *--p == 'X') + ++xc; + + if (xc < 6 || xc > len) { + + errno = EINVAL; + goto err_mkhtemp; + } + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) + + if (fname_len > len || + fname_len > (len - xc)) { + + /* prevent overflow + */ + errno = EOVERFLOW; + goto err_mkhtemp; + } + + if (memcmp(fname, + template + len - fname_len, + fname_len) != 0) { + + errno = EINVAL; + goto err_mkhtemp; + } + +#endif + + template_copy = malloc(len + 1); + if (template_copy == NULL) { + + errno = ENOMEM; + goto err_mkhtemp; + } + + /* we work on a cached copy first, + * to avoid partial writes of the + * input under fault conditions + */ + memcpy(template_copy, template, len + 1); + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) + + /* redundant copy, reduce chance of + * mangling (regression mitigation) + */ + + fname_copy = malloc(fname_len + 1); + + if (fname_copy == NULL) { + + errno = ENOMEM; + goto err_mkhtemp; + } + + memcpy(fname_copy, + template_copy + len - fname_len, + fname_len + 1); + + p = fname_copy + fname_len - xc; +#else + p = template_copy + len - xc; +#endif + + for (retries = 0; retries < max_retries; retries++) { + + for (chx = 0; chx < xc; chx++) { + + /* clamp rand to prevent modulo bias + * (reduced risk of entropy leak) + */ + + do { + saved_rand_error = errno; + + rand_failures = 0; +retry_rand: + errno = 0; + + /* on bsd: uses arc4random + on linux: uses getrandom + on OLD linux: /dev/urandom + on old/other unix: /dev/urandom + */ + r = rlong(); + + if (errno > 0) { + + if (++rand_failures <= 8) + goto retry_rand; + + goto err_mkhtemp; + } + + rand_failures = 0; + + errno = saved_rand_error; + + } while (r >= limit); + + p[chx] = ch[r % (sizeof(ch) - 1)]; + } + +#if defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0) /* openat(2) added to linux in 2006, BSDs later + */ + *fd = open(template_copy, + O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC | + O_NOCTTY, 0600); +#else + /* + * use the file descriptor instead. + * (danach prüfen wir die Sicherheit des Verzeichnisses) + */ + if (fstat(dirfd, &st_dir_now) < 0) + goto err_mkhtemp; + /* + * mitigate symlink attacks before open + */ + if (st_dir_now.st_dev != st_dir_initial->st_dev || + st_dir_now.st_ino != st_dir_initial->st_ino) { + errno = ESTALE; + goto err_mkhtemp; + } + + *fd = openat(dirfd, fname_copy, + O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC | + O_NOCTTY, 0600); +#endif + + if (*fd >= 0) { + + file_created = 1; + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) + + memcpy(template_copy + len - fname_len, + fname_copy, fname_len); +#endif + + if (secure_file(fd, st, O_APPEND, 1, 1, 0600) < 0) + goto err_mkhtemp; + + /* copy replaced characters + */ + memcpy( + template + len + xc, + template_copy + len + xc, + xc); + + if (template_copy != NULL) { + + free(template_copy); + template_copy = NULL; + } + + errno = saved_errno; + + /* thunder room secure + */ + return *fd; + } + + if (errno != EEXIST && + errno != EINTR && + errno != EAGAIN) + goto err_mkhtemp; + } + +err_mkhtemp: + + saved_errno = errno; + + if (*fd >= 0) { + + (void) close_on_eintr(*fd); + + *fd = -1; + /* ^^^^^ the caller gives us a dir, + for use and we write in it + but *we* create their file + + touch their *fd, not dirfd + as we only initialised *fd + */ + } + + if (template_copy != NULL) { + + /* we created it, so *we* nuke it + */ +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) + + if (file_created && + fname_copy != NULL) + (void) unlinkat(dirfd, fname_copy, 0); +#else + if (file_created) + (void) unlink(template_copy); + +#endif + + free(template_copy); + + template_copy = NULL; + } + +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) + + if (fname_copy != NULL) { + + free(fname_copy); + fname_copy = NULL; + } + +#endif + + errno = saved_errno; + + /* returning EINTR/EAGAIN + ourselves means that a + caller could implement + the same wait loop + */ + if (errno != EEXIST && + errno != EINTR && + errno != EAGAIN) { + + if (errno == saved_errno) + errno = ECANCELED; + } + + return -1; +} + +/* split up a given + * path into directory + * and file name. used + * for e.g. openat + */ +int +split_path(const char *path, + struct path_split *ps) +{ + size_t maxlen; + size_t len; + + char *slash; + + int saved_errno = errno; + + if (path == NULL || ps == NULL) + goto err_split_path; + +#if defined(PATH_LEN) && \ +(PATH_LEN) >= 256 + maxlen = PATH_LEN; +#else + maxlen = 4096; +#endif + + if (slen(path, maxlen, &len) < 0) + goto err_split_path; + + if (len == 0 || len >= maxlen) { + + errno = ERANGE; + goto err_split_path; + } + + ps->buf = malloc(len + 1); + if (ps->buf == NULL) { + + errno = ENOMEM; + goto err_split_path; + } + + memcpy(ps->buf, path, len + 1); - char *base = NULL; - char *dest = NULL; + for ( ; len > 1 && + ps->buf[len - 1] == '/'; len--) + ps->buf[len - 1] = '\0'; - unsigned long tmpdir_len = 0; - unsigned long tmpname_len = 0; - unsigned long tmppath_len = 0; + slash = strrchr(ps->buf, '/'); - int fd_tmp = -1; - int flags; + if (slash) { -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - maxlen = PATH_LEN; -#else - maxlen = 1024; -#endif + *slash = '\0'; + ps->base = slash + 1; - tmpname = default_tmpname; - if (local) { - if (path == NULL) - goto err_new_tmpfile; - if (*path == '\0') - goto err_new_tmpfile; + if (*ps->buf == '\0') { - if (stat(path, &st) == -1) - goto err_new_tmpfile; + ps->buf[0] = '/'; + ps->buf[1] = '\0'; + } + } else { - if (!S_ISREG(st.st_mode)) - goto err_new_tmpfile; + ps->base = ps->buf; - tmpname = (char *)path; + ps->buf[0] = '.'; + ps->buf[1] = '\0'; } - if (local) { - base = tmp_none; + ps->dirfd = open_verified_dir(ps->buf); + if (ps->dirfd < 0) + goto err_split_path; - /* appended to filename for tmp: - */ - tmpdir_len = xstrxlen(default_tmpname, maxlen); - } else { - base = get_tmpdir(); + errno = saved_errno; + + return 0; + +err_split_path: - if (base == NULL) - base = tmp_default; - if (*base == '\0') - base = tmp_default; + saved_errno = errno; - tmpdir_len = xstrxlen(base, maxlen); + if (ps->buf != NULL) { + free(ps->buf); + ps->buf = NULL; } - tmpname_len = xstrxlen(tmpname, maxlen); + if (ps->dirfd >= 0) { + (void) close_on_eintr(ps->dirfd); + ps->dirfd = -1; + } - tmppath_len = tmpdir_len + tmpname_len; - ++tmppath_len; /* for '/' or '.' */ + errno = saved_errno; - /* max length -1 of maxlen - * for termination - */ - if (tmpdir_len > maxlen - tmpname_len - 1) - goto err_new_tmpfile; + if (errno == saved_errno) + errno = EIO; /* likely open/check_dirfd */ - /* +1 for NULL */ - dest = malloc(tmppath_len + 1); - if (dest == NULL) - goto err_new_tmpfile; + return -1; +} - if (local) { +/* when we open a directory, + * we need to secure it each + * time against replacement + * attacks (e.g. symlinks) + */ +int +open_verified_dir(const char *path) +{ + int fd; + int saved_errno = errno; - *dest = '.'; /* hidden file */ + fd = open(path, O_RDONLY | O_DIRECTORY | + O_CLOEXEC | O_NOCTTY +#ifdef O_NOFOLLOW + | O_NOFOLLOW +#endif + ); - memcpy(dest + (unsigned long)1, tmpname, tmpname_len); + if (fd < 0) + return -1; - memcpy(dest + (unsigned long)1 + tmpname_len, - default_tmpname, tmpdir_len); - } else { + errno = saved_errno; - memcpy(dest, base, tmpdir_len); + if (check_dirfd(fd, path) < 0) { - dest[tmpdir_len] = '/'; + saved_errno = errno; + (void) close_on_eintr(fd); - memcpy(dest + tmpdir_len + 1, tmpname, tmpname_len); + errno = saved_errno; + return -1; } - dest[tmppath_len] = '\0'; + errno = saved_errno; + return fd; +} + +/* useful for mitigating directory + * replacement attacks; call this + * before e.g. stat(), right after + * calling open/openat(). compares + * the inode/device of a given path + * relative to the file descriptor, + * which if changed would indicate + * a possible attack / race condition + */ +int +check_dirfd(int dirfd, const char *path) +{ + struct stat st_fd; + struct stat st_path; - fd_tmp = mkstemp_n(dest); - if (fd_tmp == -1) - goto err_new_tmpfile; + int saved_errno = errno; - if (fchmod(fd_tmp, 0600) == -1) - goto err_new_tmpfile; + if (dirfd < 0) { + errno = EBADF; + goto err_check_dirfd; + } - flags = fcntl(fd_tmp, F_GETFL); + if (path == NULL) { + errno = EFAULT; + goto err_check_dirfd; + } - if (flags == -1) - goto err_new_tmpfile; + if (fstat(dirfd, &st_fd) < 0) + goto err_check_dirfd; +#if !(defined(DISABLE_OPENAT) && \ + ((DISABLE_OPENAT) > 0)) /* - * O_APPEND would permit offsets - * to be ignored, which breaks - * positional read/write + * mitigate symlink / directory replacement + * attacks (fstatat added to linux in 2006, + * and the BSDs added it later on) + * + * on older/weird unix, you'll see stat(2), + * and would therefore be vulnerable. */ - if (flags & O_APPEND) - goto err_new_tmpfile; + if (fstatat(AT_FDCWD, path, &st_path, + AT_SYMLINK_NOFOLLOW) != 0) + goto err_check_dirfd; +#else + if (stat(path, &st_path) != 0) + goto err_check_dirfd; +#endif - if (lock_file(fd_tmp, flags) == -1) - goto err_new_tmpfile; + if (st_fd.st_dev != st_path.st_dev || + st_fd.st_ino != st_path.st_ino) { - if (fstat(fd_tmp, &st) == -1) - goto err_new_tmpfile; + errno = ENOENT; + goto err_check_dirfd; + } - /* - * Extremely defensive - * likely pointless checks - */ + errno = saved_errno; - /* check if it's a file */ - if (!S_ISREG(st.st_mode)) - goto err_new_tmpfile; + return 0; - /* check if it's seekable */ - if (lseek(fd_tmp, 0, SEEK_CUR) == (off_t)-1) - goto err_new_tmpfile; +err_check_dirfd: - /* tmpfile has >1 hardlinks */ - if (st.st_nlink > 1) - goto err_new_tmpfile; + if (errno == saved_errno) + errno = EPERM; /* context: symlink attack */ - /* tmpfile unlinked while opened */ - if (st.st_nlink == 0) - goto err_new_tmpfile; + return -1; +} - *fd = fd_tmp; +/* why doesn't literally + every libc have this? + + TODO: consider set_flags, + complementing bad_flags, + for setting new flags; + with another option to + set it before the bad_flags + check, or after it (because + some callers may be setting + flags given to them with + theirs OR'd in, yet still + want to filter bad flags, + whereas others may not want + to modify flags on a file + that already contains + specific flags) + */ +int +secure_file(int *fd, + struct stat *st, int bad_flags, + int check_seek, int do_lock, + mode_t mode) +{ + int flags; + int saved_errno = errno; - return dest; + if (fd == NULL) { + errno = EFAULT; + goto err_secure_file; + } + if (*fd < 0) { + errno = EBADF; + goto err_secure_file; + } -err_new_tmpfile: + if (st == NULL) { + errno = EFAULT; + goto err_secure_file; + } - if (dest != NULL) - free(dest); + flags = fcntl(*fd, F_GETFL); - if (fd_tmp > -1) - close_on_eintr(fd_tmp); + if (flags == -1) + goto err_secure_file; - return NULL; -} + if (bad_flags > 0) { -int -lock_file(int fd, int flags) -{ - struct flock fl; + /* + * For example: + * O_APPEND would permit offsets + * to be ignored, which breaks + * positional read/write + * + * You might provide that as + * the mask to this function, + * before doing positional i/o + */ + if (flags & bad_flags) { + errno = EPERM; + goto err_secure_file; + } + } - memset(&fl, 0, sizeof(fl)); + if (fstat(*fd, st) == -1) + goto err_secure_file; - if ((flags & O_ACCMODE) == O_RDONLY) - fl.l_type = F_RDLCK; - else - fl.l_type = F_WRLCK; + /* + * Extremely defensive + * likely pointless checks + */ - fl.l_whence = SEEK_SET; + /* check if it's a file */ + if (!S_ISREG(st->st_mode)) { + errno = EBADF; + goto err_secure_file; + } - if (fcntl(fd, F_SETLK, &fl) == -1) - return -1; + if (check_seek) { - return 0; -} + /* check if it's seekable + */ + if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1) + goto err_secure_file; + } -/* return TMPDIR, or fall back - * to portable defaults - */ + /* tmpfile re-linked, or + unlinked, while opened + */ + if (st->st_nlink != 1) { + errno = ELOOP; + goto err_secure_file; + } -char * -get_tmpdir(void) -{ - char *t; - struct stat st; + /* block if we don't own the file + * (exception made for root) + */ + if (st->st_uid != geteuid() && /* someone else's file */ + geteuid() != 0) { /* override for root */ - t = getenv("TMPDIR"); + errno = EPERM; + goto err_secure_file; + } + if (is_owner(st) < 0) + goto err_secure_file; - if (t && *t) { + /* world-writeable or group-ownership. + * if these are set, then others could + * modify the file (not secure) + */ + if (st->st_mode & (S_IWGRP | S_IWOTH)) { + errno = EPERM; + goto err_secure_file; + } - if (stat(t, &st) == 0 && S_ISDIR(st.st_mode)) { + if (do_lock) { + if (lock_file(*fd, flags) == -1) + goto err_secure_file; + } - if ((st.st_mode & S_IWOTH) && !(st.st_mode & S_ISVTX)) - return NULL; + if (fchmod(*fd, mode) == -1) + goto err_secure_file; - return t; - } - } + errno = saved_errno; + return 0; - if (stat("/tmp", &st) == 0 && S_ISDIR(st.st_mode)) - return "/tmp"; +err_secure_file: - if (stat("/var/tmp", &st) == 0 && S_ISDIR(st.st_mode)) - return "/var/tmp"; + if (errno == saved_errno) + errno = EIO; - return "."; + return -1; } -/* portable mkstemp - */ - int -mkstemp_n(char *template) +is_owner(struct stat *st) { - int fd; - unsigned long i, j; - unsigned long len; - char *p; - - unsigned long xc = 0; + if (st == NULL) { - static char ch[] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + errno = EFAULT; + return -1; + } - unsigned long r; -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - unsigned long max_len = PATH_LEN; +#if ALLOW_ROOT_OVERRIDE + if (st->st_uid != geteuid() && /* someone else's file */ + geteuid() != 0) { /* override for root */ #else - unsigned long max_len = 4096; -#endif - - len = xstrxlen(template, max_len); - - if (len < 6) { - errno = EINVAL; + if (st->st_uid != geteuid()) { /* someone else's file */ +#endif /* and no root override */ + errno = EPERM; return -1; } - p = template + len; + return 0; +} - while (p > template && p[-1] == 'X') { - --p; - ++xc; +int +lock_file(int fd, int flags) +{ + struct flock fl; + int saved_errno = errno; + + if (fd < 0) { + errno = EBADF; + goto err_lock_file; } - if (xc < 6) { + if (flags < 0) { errno = EINVAL; - return -1; + goto err_lock_file; } - for (i = 0; i < 200; i++) { + memset(&fl, 0, sizeof(fl)); - for (j = 0; j < xc; j++) { + if ((flags & O_ACCMODE) == O_RDONLY) + fl.l_type = F_RDLCK; + else + fl.l_type = F_WRLCK; - r = rlong(); + fl.l_whence = SEEK_SET; - p[j] = ch[(unsigned long)(r >> 1) % (sizeof(ch) - 1)]; - } + if (fcntl(fd, F_SETLK, &fl) == -1) + goto err_lock_file; - fd = open(template, - O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600); + saved_errno = errno; + return 0; - if (fd >= 0) - return fd; +err_lock_file: - if (errno != EEXIST) - return -1; - } + if (errno == saved_errno) + errno = EIO; - errno = EEXIST; return -1; } @@ -493,21 +1638,23 @@ mkstemp_n(char *template) * otherwise it will return an error. */ -long -rw_file_exact(int fd, unsigned char *mem, unsigned long nrw, +ssize_t +rw_file_exact(int fd, unsigned char *mem, size_t nrw, off_t off, int rw_type, int loop_eagain, - int loop_eintr, unsigned long max_retries, + int loop_eintr, size_t max_retries, int off_reset) { - long rval; - long rc; + ssize_t rval; + ssize_t rc; - unsigned long nrw_cur; + size_t nrw_cur; off_t off_cur; void *mem_cur; - unsigned long retries_on_zero; + size_t retries_on_zero; + + int saved_errno = errno; rval = 0; @@ -515,22 +1662,28 @@ rw_file_exact(int fd, unsigned char *mem, unsigned long nrw, retries_on_zero = 0; if (io_args(fd, mem, nrw, off, rw_type) == -1) - return -1; + goto err_rw_file_exact; while (1) { /* Prevent theoretical overflow */ - if (rval >= 0 && (unsigned long)rval > (nrw - rc)) + if (rval >= 0 && (size_t)rval > (nrw - rc)) { + errno = EOVERFLOW; goto err_rw_file_exact; + } rc += rval; - if ((unsigned long)rc >= nrw) + if ((size_t)rc >= nrw) break; - mem_cur = (void *)(mem + (unsigned long)rc); - nrw_cur = (unsigned long)(nrw - (unsigned long)rc); - if (off < 0) + mem_cur = (void *)(mem + (size_t)rc); + nrw_cur = (size_t)(nrw - (size_t)rc); + + if (off < 0) { + errno = EOVERFLOW; goto err_rw_file_exact; + } + off_cur = off + (off_t)rc; rval = prw(fd, mem_cur, nrw_cur, off_cur, @@ -538,24 +1691,38 @@ rw_file_exact(int fd, unsigned char *mem, unsigned long nrw, off_reset); if (rval < 0) - return -1; + goto err_rw_file_exact; if (rval == 0) { if (retries_on_zero++ < max_retries) continue; + + errno = EIO; goto err_rw_file_exact; } retries_on_zero = 0; } - if ((unsigned long)rc != nrw) + if ((size_t)rc != nrw) { + + errno = EIO; + goto err_rw_file_exact; + } + + rval = rw_over_nrw(rc, nrw); + if (rval < 0) goto err_rw_file_exact; - return rw_over_nrw(rc, nrw); + errno = saved_errno; + + return rval; err_rw_file_exact: - errno = EIO; + + if (errno == saved_errno) + errno = EIO; + return -1; } @@ -569,7 +1736,7 @@ err_rw_file_exact: * mitigate race conditions on file descriptors * * If you need real pwrite/pread, just compile - * with flag: HAVE_REAL_PREAD_PWRITE=1 + * with flag: REAL_POS_IO=1 * * A fallback is provided for regular read/write. * rw_type can be IO_READ (read), IO_WRITE (write), @@ -587,27 +1754,27 @@ err_rw_file_exact: * off_reset 0: never reset if changed */ -long -prw(int fd, void *mem, unsigned long nrw, +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) { - long r; + ssize_t rval; + ssize_t r; int positional_rw; struct stat st; -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 - int saved_errno; +#if !defined(REAL_POS_IO) || \ + REAL_POS_IO < 1 off_t verified; off_t off_orig; off_t off_last; #endif + int saved_errno = errno; if (io_args(fd, mem, nrw, off, rw_type) - == -1) { - return -1; - } + == -1) + goto err_prw; r = -1; @@ -621,7 +1788,7 @@ prw(int fd, void *mem, unsigned long nrw, */ if (check_file(fd, &st) == -1) - return -1; + goto err_prw; } if (rw_type >= IO_PREAD) @@ -632,16 +1799,16 @@ prw(int fd, void *mem, unsigned long nrw, try_rw_again: if (!positional_rw) { -#if defined(HAVE_REAL_PREAD_PWRITE) && \ - HAVE_REAL_PREAD_PWRITE > 0 +#if defined(REAL_POS_IO) && \ + REAL_POS_IO > 0 real_pread_pwrite: #endif 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 +#if defined(REAL_POS_IO) && \ + REAL_POS_IO > 0 else if (rw_type == IO_PWRITE) r = pwrite(fd, mem, nrw, off); else if (rw_type == IO_PREAD) @@ -652,11 +1819,17 @@ real_pread_pwrite: || errno == try_err(loop_eagain, EAGAIN))) goto try_rw_again; - return rw_over_nrw(r, nrw); + rval = rw_over_nrw(r, nrw); + if (rval < 0) + goto err_prw; + + errno = saved_errno; + + return rval; } -#if defined(HAVE_REAL_PREAD_PWRITE) && \ - HAVE_REAL_PREAD_PWRITE > 0 +#if defined(REAL_POS_IO) && \ + REAL_POS_IO > 0 goto real_pread_pwrite; #else if ((off_orig = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, @@ -687,18 +1860,19 @@ real_pread_pwrite: verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, loop_eagain, loop_eintr); - if (off != verified) + if (off != verified) { + + errno = EBUSY; 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; + if (rw_over_nrw(r, nrw) == -1) break; - } } while (r == -1 && (errno == try_err(loop_eintr, EINTR) || @@ -717,65 +1891,110 @@ real_pread_pwrite: errno = saved_errno; - return rw_over_nrw(r, nrw); + rval = rw_over_nrw(r, nrw); + if (rval < 0) + goto err_prw; + + errno = saved_errno; + + return rval; + #endif err_prw: - errno = EIO; + + if (errno == saved_errno) + errno = EIO; + return -1; } int -io_args(int fd, void *mem, unsigned long nrw, +io_args(int fd, void *mem, size_t nrw, off_t off, int rw_type) { + int saved_errno = errno; + /* obviously */ - if (mem == NULL) + if (mem == NULL) { + + errno = EFAULT; goto err_io_args; + } /* uninitialised fd */ - if (fd < 0) + if (fd < 0) { + + errno = EBADF; goto err_io_args; + } /* negative offset */ - if (off < 0) + if (off < 0) { + + errno = ERANGE; goto err_io_args; + } /* prevent zero-byte rw */ if (!nrw) goto err_io_args; /* prevent overflow */ - if (nrw > (unsigned long)X_LONG_MAX) + if (nrw > (size_t)SSIZE_MAX) { + + errno = ERANGE; goto err_io_args; + } /* prevent overflow */ - if (((unsigned long)off + nrw) < (unsigned long)off) + if (((size_t)off + nrw) < (size_t)off) { + + errno = ERANGE; goto err_io_args; + } + + if (rw_type > IO_PWRITE) { - if (rw_type > IO_PWRITE) + errno = EINVAL; goto err_io_args; + } + + errno = saved_errno; return 0; err_io_args: - errno = EIO; + + if (errno == saved_errno) + errno = EINVAL; + return -1; } int check_file(int fd, struct stat *st) { + int saved_errno = errno; + if (fstat(fd, st) == -1) goto err_is_file; - if (!S_ISREG(st->st_mode)) + if (!S_ISREG(st->st_mode)) { + + errno = EBADF; goto err_is_file; + } + + errno = saved_errno; return 0; err_is_file: - errno = EIO; + + if (errno == saved_errno) + errno = EINVAL; + return -1; } @@ -783,9 +2002,11 @@ err_is_file: * specification != implementation */ -long -rw_over_nrw(long r, unsigned long nrw) +ssize_t +rw_over_nrw(ssize_t r, size_t nrw) { + int saved_errno = errno; + /* not a libc bug, but we * don't like the number zero */ @@ -795,8 +2016,8 @@ rw_over_nrw(long r, unsigned long nrw) if (r == -1) return r; - if ((unsigned long) - r > X_LONG_MAX) { + if ((size_t) + r > SSIZE_MAX) { /* Theoretical buggy libc * check. Extremely academic. @@ -810,10 +2031,11 @@ rw_over_nrw(long r, unsigned long nrw) * [p]read() or [p]write() * * NOTE: here, we assume - * long integers are the + * ssize_t integers are the * same size as SSIZE_T */ + errno = ERANGE; goto err_rw_over_nrw; } @@ -821,19 +2043,26 @@ rw_over_nrw(long r, unsigned long nrw) * Should never return a number of * bytes above the requested length. */ - if ((unsigned long)r > nrw) + if ((size_t)r > nrw) { + + errno = ERANGE; goto err_rw_over_nrw; + } + + errno = saved_errno; return r; err_rw_over_nrw: - errno = EIO; + if (errno == saved_errno) + errno = EIO; + return -1; } -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 +#if !defined(REAL_POS_IO) || \ + REAL_POS_IO < 1 off_t lseek_on_eintr(int fd, off_t off, int whence, int loop_eagain, int loop_eintr) @@ -869,9 +2098,9 @@ close_on_eintr(int fd) do { r = close(fd); - } while (r == -1 && errno == EINTR); + } while (r == -1 && (errno == EINTR || errno == EAGAIN)); - if (r > -1) + if (r >= 0) errno = saved_errno; return r; @@ -881,10 +2110,14 @@ int fsync_on_eintr(int fd) { int r; + int saved_errno = errno; do { r = fsync(fd); - } while (r == -1 && errno == EINTR); + } while (r == -1 && (errno == EINTR || errno == EAGAIN)); + + if (r >= 0) + errno = saved_errno; return r; } diff --git a/util/nvmutil/lib/io.c b/util/nvmutil/lib/io.c index 5769dd05..87163359 100644 --- a/util/nvmutil/lib/io.c +++ b/util/nvmutil/lib/io.c @@ -21,7 +21,7 @@ void open_gbe_file(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; @@ -29,12 +29,12 @@ open_gbe_file(void) xopen(&f->gbe_fd, f->fname, cmd->flags | O_BINARY | - O_NOFOLLOW | O_CLOEXEC, &f->gbe_st); + O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, &f->gbe_st); if (f->gbe_st.st_nlink > 1) err(EINVAL, "%s: warning: file has multiple (%lu) hard links\n", - f->fname, (unsigned long)f->gbe_st.st_nlink); + f->fname, (size_t)f->gbe_st.st_nlink); if (f->gbe_st.st_nlink == 0) err(EIO, "%s: file unlinked while open", f->fname); @@ -69,7 +69,7 @@ open_gbe_file(void) void copy_gbe(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; read_file(); @@ -77,19 +77,19 @@ copy_gbe(void) if (f->gbe_file_size == SIZE_8KB) return; - memcpy(f->buf + (unsigned long)GBE_PART_SIZE, - f->buf + (unsigned long)(f->gbe_file_size >> 1), - (unsigned long)GBE_PART_SIZE); + memcpy(f->buf + (size_t)GBE_PART_SIZE, + f->buf + (size_t)(f->gbe_file_size >> 1), + (size_t)GBE_PART_SIZE); } void read_file(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; struct stat _st; - long _r; + ssize_t _r; /* read main file */ @@ -141,11 +141,11 @@ read_file(void) void write_gbe_file(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; - unsigned long p; + size_t p; unsigned char update_checksum; if ((cmd->flags & O_ACCMODE) == O_RDONLY) @@ -171,25 +171,25 @@ write_gbe_file(void) } void -rw_gbe_file_part(unsigned long p, int rw_type, +rw_gbe_file_part(size_t p, int rw_type, const char *rw_type_str) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; - long rval; + ssize_t rval; off_t file_offset; - unsigned long gbe_rw_size; + size_t gbe_rw_size; unsigned char *mem_offset; gbe_rw_size = cmd->rw_size; if (rw_type < IO_PREAD || rw_type > IO_PWRITE) err(errno, "%s: %s: part %lu: invalid rw_type, %d", - f->fname, rw_type_str, (unsigned long)p, rw_type); + f->fname, rw_type_str, (size_t)p, rw_type); mem_offset = gbe_mem_offset(p, rw_type_str); file_offset = (off_t)gbe_file_offset(p, rw_type_str); @@ -199,17 +199,17 @@ rw_gbe_file_part(unsigned long p, int rw_type, if (rval == -1) err(errno, "%s: %s: part %lu", - f->fname, rw_type_str, (unsigned long)p); + f->fname, rw_type_str, (size_t)p); - if ((unsigned long)rval != gbe_rw_size) + if ((size_t)rval != gbe_rw_size) err(EIO, "%s: partial %s: part %lu", - f->fname, rw_type_str, (unsigned long)p); + f->fname, rw_type_str, (size_t)p); } void write_to_gbe_bin(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; @@ -240,14 +240,20 @@ write_to_gbe_bin(void) saved_errno = errno; if (close_on_eintr(f->tmp_fd) == -1) { + f->tmp_fd = -1; + fprintf(stderr, "FAIL: %s: close\n", f->tname); f->io_err_gbe_bin = 1; } + f->tmp_fd = -1; if (close_on_eintr(f->gbe_fd) == -1) { + f->gbe_fd = -1; + fprintf(stderr, "FAIL: %s: close\n", f->fname); f->io_err_gbe_bin = 1; } + f->gbe_fd = -1; errno = saved_errno; @@ -274,8 +280,10 @@ write_to_gbe_bin(void) /* removed by rename */ - if (f->tname != NULL) + if (f->tname != NULL) { free(f->tname); + f->tname = NULL; + } f->tname = NULL; } @@ -292,15 +300,15 @@ write_to_gbe_bin(void) } void -check_written_part(unsigned long p) +check_written_part(size_t p) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct commands *cmd = &x->cmd[x->i]; struct xfile *f = &x->f; - long rval; + ssize_t rval; - unsigned long gbe_rw_size; + size_t gbe_rw_size; off_t file_offset; unsigned char *mem_offset; @@ -328,7 +336,7 @@ check_written_part(unsigned long p) if (rval == -1) f->rw_check_err_read[p] = f->io_err_gbe = 1; - else if ((unsigned long)rval != gbe_rw_size) + else if ((size_t)rval != gbe_rw_size) f->rw_check_partial_read[p] = f->io_err_gbe = 1; else if (memcmp(mem_offset, f->pad, gbe_rw_size) != 0) f->rw_check_bad_part[p] = f->io_err_gbe = 1; @@ -359,10 +367,10 @@ check_written_part(unsigned long p) void report_io_err_rw(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long p; + size_t p; if (!f->io_err_gbe) return; @@ -374,22 +382,22 @@ report_io_err_rw(void) if (f->rw_check_err_read[p]) fprintf(stderr, "%s: pread: p%lu (post-verification)\n", - f->fname, (unsigned long)p); + f->fname, (size_t)p); if (f->rw_check_partial_read[p]) fprintf(stderr, "%s: partial pread: p%lu (post-verification)\n", - f->fname, (unsigned long)p); + f->fname, (size_t)p); if (f->rw_check_bad_part[p]) fprintf(stderr, "%s: pwrite: corrupt write on p%lu\n", - f->fname, (unsigned long)p); + f->fname, (size_t)p); if (f->rw_check_err_read[p] || f->rw_check_partial_read[p]) { fprintf(stderr, "%s: p%lu: skipped checksum verification " "(because read failed)\n", - f->fname, (unsigned long)p); + f->fname, (size_t)p); continue; } @@ -402,7 +410,7 @@ report_io_err_rw(void) fprintf(stderr, "BAD"); fprintf(stderr, " checksum in p%lu on-disk.\n", - (unsigned long)p); + (size_t)p); if (f->post_rw_checksum[p]) { fprintf(stderr, @@ -415,7 +423,7 @@ report_io_err_rw(void) int gbe_mv(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; int rval; @@ -490,8 +498,11 @@ gbe_mv(void) if (fsync_on_eintr(dest_fd) == -1) goto ret_gbe_mv; - if (close_on_eintr(dest_fd) == -1) + if (close_on_eintr(dest_fd) == -1) { + dest_fd = -1; goto ret_gbe_mv; + } + dest_fd = -1; if (rename(dest_tmp, f->fname) == -1) goto ret_gbe_mv; @@ -501,25 +512,37 @@ gbe_mv(void) goto ret_gbe_mv; } - free(dest_tmp); + if (dest_tmp != NULL) { + free(dest_tmp); + dest_tmp = NULL; + } + dest_tmp = NULL; ret_gbe_mv: + /* TODO: this whole section is bloat. + it can be generalised + */ + if (f->gbe_fd > -1) { - if (close_on_eintr(f->gbe_fd) < 0) + if (close_on_eintr(f->gbe_fd) < 0) { + f->gbe_fd = -1; rval = -1; + } + f->gbe_fd = -1; + if (fsync_dir(f->fname) < 0) { f->io_err_gbe_bin = 1; rval = -1; } - f->gbe_fd = -1; } if (f->tmp_fd > -1) { - if (close_on_eintr(f->tmp_fd) < 0) + if (close_on_eintr(f->tmp_fd) < 0) { + f->tmp_fd = -1; rval = -1; - + } f->tmp_fd = -1; } @@ -552,9 +575,9 @@ ret_gbe_mv: * and it is *also* used during file I/O. */ unsigned char * -gbe_mem_offset(unsigned long p, const char *f_op) +gbe_mem_offset(size_t p, const char *f_op) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; off_t gbe_off; @@ -563,7 +586,7 @@ gbe_mem_offset(unsigned long p, const char *f_op) GBE_PART_SIZE, GBE_WORK_SIZE); return (unsigned char *) - (f->buf + (unsigned long)gbe_off); + (f->buf + (size_t)gbe_off); } /* I/O operations filtered here. These operations must @@ -571,9 +594,9 @@ gbe_mem_offset(unsigned long p, const char *f_op) * within the GbE file, and write 4KB of data. */ off_t -gbe_file_offset(unsigned long p, const char *f_op) +gbe_file_offset(size_t p, const char *f_op) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; off_t gbe_file_half_size; @@ -585,10 +608,10 @@ gbe_file_offset(unsigned long p, const char *f_op) } off_t -gbe_x_offset(unsigned long p, const char *f_op, const char *d_type, +gbe_x_offset(size_t p, const char *f_op, const char *d_type, off_t nsize, off_t ncmp) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; off_t off; @@ -608,14 +631,14 @@ gbe_x_offset(unsigned long p, const char *f_op, const char *d_type, return off; } -long -rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw, +ssize_t +rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw, off_t off, int rw_type) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - long r; + ssize_t r; if (io_args(fd, mem, nrw, off, rw_type) == -1) return -1; @@ -624,17 +647,17 @@ rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw, if (mem < f->buf) goto err_rw_gbe_file_exact; - if ((unsigned long)(mem - f->buf) >= GBE_WORK_SIZE) + if ((size_t)(mem - f->buf) >= GBE_WORK_SIZE) goto err_rw_gbe_file_exact; } if (off < 0 || off >= f->gbe_file_size) goto err_rw_gbe_file_exact; - if (nrw > (unsigned long)(f->gbe_file_size - off)) + if (nrw > (size_t)(f->gbe_file_size - off)) goto err_rw_gbe_file_exact; - if (nrw > (unsigned long)GBE_PART_SIZE) + if (nrw > (size_t)GBE_PART_SIZE) goto err_rw_gbe_file_exact; r = rw_file_exact(fd, mem, nrw, off, rw_type, diff --git a/util/nvmutil/lib/num.c b/util/nvmutil/lib/num.c index bbb5a83e..343350b0 100644 --- a/util/nvmutil/lib/num.c +++ b/util/nvmutil/lib/num.c @@ -4,6 +4,10 @@ * Numerical functions. */ +/* +TODO: properly handle errno in this file + */ + #ifdef __OpenBSD__ #include #endif @@ -30,31 +34,108 @@ #include "../include/common.h" +/* TODO: + * make this and errno handling more + * flexible + + in particular: + hextonum could be modified to + write into a buffer instead, + with the converted numbers, + of an arbitrary length + */ unsigned short hextonum(char ch_s) { + int saved_errno = errno; + + /* rlong() can return error, + but preserves errno if no + error. we need to detect + this because it handles + /dev/urandom sometimes + + therefore, if it's zero + at start, we know if there + was an err at the end, by + return value zero, if errno + was set; this is technically + valid, since zero is also + a valid random number! + + it's an edge case that i had + to fix. i'll rewrite the code + better later. for now, it + should be ok. + */ + errno = 0; + unsigned char ch; + size_t rval; ch = (unsigned char)ch_s; - if ((unsigned int)(ch - '0') <= 9) - return ch - '0'; + if ((unsigned int)(ch - '0') <= 9) { + + rval = ch - '0'; + goto hextonum_success; + } ch |= 0x20; - if ((unsigned int)(ch - 'a') <= 5) - return ch - 'a' + 10; + if ((unsigned int)(ch - 'a') <= 5) { + + rval = ch - 'a' + 10; + goto hextonum_success; + } - if (ch == '?' || ch == 'x') - return (unsigned short)rlong() & 0xf; + if (ch == '?' || ch == 'x') { + + rval = rlong(); + if (errno > 0) + goto err_hextonum; + + goto hextonum_success; + } + + goto err_hextonum; + +hextonum_success: + + errno = saved_errno; + return (unsigned short)rval & 0xf; + +err_hextonum: + + if (errno == saved_errno) + errno = EINVAL; + else + return 17; /* 17 indicates getrandom/urandom fail */ return 16; /* invalid character */ + + /* caller just checks >15. */ } /* Random numbers */ -unsigned long +/* when calling this: save errno + * first, then set errno to zero. + * on error, this function will + * set errno and possibly return + * + * rlong also preserves errno + * and leaves it unchanged on + * success, so if you do it + * right, you can detect error. + * this is because it uses + * /dev/urandom which can err. + * ditto getrandom (EINTR), + * theoretically. + */ + +size_t rlong(void) { #if !(defined(FALLBACK_RAND_1989) && \ @@ -63,14 +144,17 @@ rlong(void) defined(__FreeBSD__) || \ defined(__NetBSD__) || defined(__APPLE__) - unsigned long rval; - arc4random_buf(&rval, sizeof(unsigned long)); + int saved_errno = errno; + size_t rval; + arc4random_buf(&rval, sizeof(size_t)); + + errno = saved_errno; return rval; #else static int fd = -1; - static long nr = -1; - static unsigned long off = 0; + static ssize_t nr = -1; + static size_t off = 0; #if defined (BUFSIZ) static char rbuf[BUFSIZ]; #else @@ -82,12 +166,14 @@ rlong(void) static char rbuf[4096]; /* typical 32-bit BUFSIZ */ #endif #endif - unsigned long rval; - long new_nr; + size_t rval; + ssize_t new_nr; int retries = 0; int max_retries = 100; + int saved_errno = errno; + #if defined(__linux__) #if defined(HAVE_GETRANDOM) || \ defined(HAVE_GETRANDOM_SYSCALL) @@ -98,17 +184,12 @@ rlong(void) * modern linux, but not on * every libc. better use the * official linux function - * - * similar benefits to arc4random - * e.g. works in chroot, blocks - * until it has enough entropy, - * and works even when /dev/urandom - * is available (doesn't use it); - * it's generally more reliable */ - if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0) + if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0) { + errno = saved_errno; return rval; + } /* * now fall back to urandom if getrandom failed: @@ -131,15 +212,15 @@ rlong(void) retry_urandom_read: if (++retries > max_retries) - goto rlong_next; + goto err_rlong; - if (nr < 0 || nr < (long)sizeof(unsigned long)) { + if (nr < 0 || nr < (ssize_t)sizeof(size_t)) { if (fd < 0) { fd = open("/dev/urandom", O_RDONLY | O_BINARY | O_NOFOLLOW | - O_CLOEXEC); + O_CLOEXEC | O_NOCTTY); #ifdef USE_OLD_DEV_RANDOM #if (USE_OLD_DEV_RANDOM) > 0 @@ -155,7 +236,7 @@ retry_urandom_read: if (fd < 0) fd = open("/dev/random", O_RDONLY | O_BINARY | O_NOFOLLOW | - O_CLOEXEC); + O_CLOEXEC | O_NOCTTY); #endif #endif @@ -169,7 +250,7 @@ retry_urandom_read: sizeof(rbuf), 0, IO_READ, LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY, OFF_ERR); - if (new_nr < 0 || new_nr < (long)sizeof(rbuf)) + if (new_nr < 0 || new_nr < (ssize_t)sizeof(rbuf)) goto retry_urandom_read; /* only reset buffer after successful refill */ @@ -187,37 +268,37 @@ retry_urandom_read: fd = -1; retries = 0; - memcpy(&rval, rbuf + off, sizeof(unsigned long)); + memcpy(&rval, rbuf + off, sizeof(size_t)); - nr -= (long)sizeof(unsigned long); - off += sizeof(unsigned long); + nr -= (ssize_t)sizeof(size_t); + off += sizeof(size_t); + + errno = saved_errno; return rval; -rlong_next: +err_rlong: - fd = -1; - off = 0; - nr = -1; + if (errno == saved_errno) + errno = EIO; - err(EIO, "Can't read from /dev/[ua]random"); return 0; #endif #else /* FALLBACK_RAND_1989 */ /* your computer is from a museum */ - unsigned long mix = 0; - int nr; + size_t mix = 0; + int nr = 0; + int saved_errno = errno; /* 100 times, for entropy */ for (nr = 0; nr < 100; nr++) mix ^= fallback_rand_1989(); - /* 101 times ;) - */ - return fallback_rand_1989(); + errno = saved_errno; + return mix; #endif } @@ -226,17 +307,22 @@ rlong_next: #if defined(__linux__) #if defined(HAVE_GETRANDOM) || \ defined(HAVE_GETRANDOM_SYSCALL) -int -fallback_rand_getrandom(void *buf, unsigned long len) +int /* yes, linux is a fallback */ +fallback_rand_getrandom(void *buf, size_t len) { - unsigned long off = 0; - long rval = -1; + size_t off = 0; + ssize_t rval = -1; + int saved_errno = errno; - if (!len) + if (!len) { + errno = EINVAL; return -1; + } - if (buf == NULL) + if (buf == NULL) { + errno = EFAULT; return -1; + } #if defined(HAVE_GETRANDOM) || \ defined(HAVE_GETRANDOM_SYSCALL) @@ -244,51 +330,60 @@ fallback_rand_getrandom(void *buf, unsigned long len) while (off < len) { #if defined(HAVE_GETRANDOM) - rval = (long)getrandom((char *)buf + off, len - off, 0); + rval = (ssize_t)getrandom((char *)buf + off, len - off, 0); #elif defined(HAVE_GETRANDOM_SYSCALL) - rval = (long)syscall(SYS_getrandom, + rval = (ssize_t)syscall(SYS_getrandom, (char *)buf + off, len - off, 0); #endif if (rval < 0) { - if (errno == EINTR) + if (errno == EINTR || errno == EAGAIN) continue; + errno = EIO; return -1; /* unsupported by kernel */ } - off += (unsigned long)rval; + if (rval == 0) { + errno = EIO; + return -1; + } + + off += (size_t)rval; } + errno = saved_errno; return 0; #else (void)buf; (void)len; + errno = EIO; return -1; #endif } #endif #endif #else -/* nobody should use this - * (not crypto-safe) - */ -unsigned long +size_t fallback_rand_1989(void) { - static unsigned long mix = 0; - static unsigned long counter = 0; + static size_t mix = 0; + static size_t counter = 0; struct timeval tv; + /* nobody should use this + * (not crypto-safe) + */ + gettimeofday(&tv, NULL); - mix ^= (unsigned long)tv.tv_sec - ^ (unsigned long)tv.tv_usec - ^ (unsigned long)getpid() - ^ (unsigned long)&mix + mix ^= (size_t)tv.tv_sec + ^ (size_t)tv.tv_usec + ^ (size_t)getpid() + ^ (size_t)&mix ^ counter++ ^ entropy_jitter(); @@ -296,20 +391,20 @@ fallback_rand_1989(void) * Stack addresses can vary between * calls, thus increasing entropy. */ - mix ^= (unsigned long)&mix; - mix ^= (unsigned long)&tv; - mix ^= (unsigned long)&counter; + mix ^= (size_t)&mix; + mix ^= (size_t)&tv; + mix ^= (size_t)&counter; return mix; } -unsigned long +size_t entropy_jitter(void) { - unsigned long mix; + size_t mix; struct timeval a, b; - long mix_diff; + ssize_t mix_diff; int c; @@ -326,13 +421,13 @@ entropy_jitter(void) * prevent negative numbers to prevent overflow, * which would bias rand to large numbers */ - mix_diff = (long)(b.tv_usec - a.tv_usec); + mix_diff = (ssize_t)(b.tv_usec - a.tv_usec); if (mix_diff < 0) mix_diff = -mix_diff; - mix ^= (unsigned long)(mix_diff); + mix ^= (size_t)(mix_diff); - mix ^= (unsigned long)&mix; + mix ^= (size_t)&mix; } @@ -341,9 +436,9 @@ entropy_jitter(void) #endif void -check_bin(unsigned long a, const char *a_name) +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, (size_t)a); } diff --git a/util/nvmutil/lib/state.c b/util/nvmutil/lib/state.c index 02a3e51c..9869bb14 100644 --- a/util/nvmutil/lib/state.c +++ b/util/nvmutil/lib/state.c @@ -23,8 +23,11 @@ #include "../include/common.h" struct xstate * -xstatus(int argc, char *argv[]) +xstart(int argc, char *argv[]) { + static int first_run = 1; + static int pre_init = 0; + static struct xstate us = { /* DO NOT MESS THIS UP, OR THERE WILL BE DEMONS */ { @@ -86,92 +89,52 @@ xstatus(int argc, char *argv[]) }; - static int first_run = 1; - - if (!first_run) - return &us; + struct xstate *x = &us; - us.f.buf = us.f.real_buf; + if (!first_run) { + if (pre_init) + err_no_cleanup(ECANCELED, + "Outside access to state during init"); - first_run = 0; - us.argv0 = argv[0]; + first_run = 0; - if (argc > 1) - us.f.fname = argv[1]; + return &us; + } if (argc < 3) - usage(); - -/* https://man.openbsd.org/pledge.2 - https://man.openbsd.org/unveil.2 */ -#if defined(__OpenBSD__) && defined(OpenBSD) -#if (OpenBSD) >= 604 - if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1) - err(errno, "pledge plus unveil"); -#elif (OpenBSD) >= 509 - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err(errno, "pledge"); -#endif -#endif + err_no_cleanup(EINVAL, "xstart: Too few arguments"); + if (argv == NULL) + err_no_cleanup(EINVAL, "xstart: NULL argv"); + + first_run = 0; + pre_init = 1; -#ifndef S_ISREG - err(ECANCELED, "Can't determine file types (S_ISREG undefined)"); -#endif + us.f.buf = us.f.real_buf; -#ifndef CHAR_BIT - err(ECANCELED, "Unknown char size"); -#else - if (CHAR_BIT != 8) - err(EINVAL, "Unsupported char size"); -#endif + us.argv0 = argv[0]; + us.f.fname = argv[1]; #if defined(__OpenBSD__) && defined(OpenBSD) && \ (OpenBSD) >= 604 /* can only use local tmp on openbsd, due to unveil */ us.f.tname = new_tmpfile(&us.f.tmp_fd, 1, NULL); #else - us.f.tname = new_tmpfile(&us.f.tmp_fd, 0, NULL); -#endif - if (us.f.tname == NULL) - err(errno, "Can't create tmpfile"); - if (*us.f.tname == '\0') - err(errno, "tmp dir is an empty string"); - -#if defined(__OpenBSD__) && defined(OpenBSD) && \ - OpenBSD >= 604 - if (unveil(us.f.tname, "rwc") == -1) - err(errno, "unveil rwc: %s", us.f.tname); + // TODO: new_tmplate (do for above too) + us.f.tname = new_tmpfile(&us.f.tmp_fd, 0, NULL); // TODO: NULL BAD! #endif - if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0) - err(errno, "%s: stat", us.f.tname); - - sanitize_command_list(); /* parse user command */ +// TODO: CHECK ACCESSES VIA xstatus() set_cmd(argc, argv); set_cmd_args(argc, argv); -#if defined(__OpenBSD__) && defined(OpenBSD) && \ - (OpenBSD) >= 604 - if ((us.cmd[i].flags & O_ACCMODE) == O_RDONLY) { - if (unveil(us.f.fname, "r") == -1) - err(errno, "%s: unveil r", us.f.fname); - } else { - if (unveil(us.f.fname, "rwc") == -1) - err(errno, "%s: unveil rw", us.f.fname); - } - - if (unveil(us.f.tname, "rwc") == -1) - err(errno, "%s: unveil rwc", us.f.tname); - - if (unveil(NULL, NULL) == -1) - err(errno, "unveil block (rw)"); - - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err(errno, "pledge (kill unveil)"); -#endif + if (us.f.tname == NULL) + err_no_cleanup(errno, "x->f.tname null"); + if (*us.f.tname == '\0') + err_no_cleanup(errno, "x->f.tname empty"); - open_gbe_file(); + if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0) + err_no_cleanup(errno, "%s: stat", us.f.tname); memset(us.f.real_buf, 0, sizeof(us.f.real_buf)); memset(us.f.bufcmp, 0, sizeof(us.f.bufcmp)); @@ -179,16 +142,55 @@ xstatus(int argc, char *argv[]) /* for good measure */ memset(us.f.pad, 0, sizeof(us.f.pad)); - copy_gbe(); - read_checksums(); + pre_init = 0; return &us; } +struct xstate * +xstatus(void) +{ + struct xstate *x = xstart(0, NULL); + + if (x == NULL) + err_no_cleanup(EACCES, "NULL pointer to xstate"); + + sanitize_command_list(); + + return x; +} + +/* early init functions that + should not access state + WARNING: + does not do cleanup. only + call this during pre-init + */ +void +err_no_cleanup(int nvm_errval, const char *msg, ...) +{ + va_list args; + + if (errno == 0) + errno = nvm_errval; + if (!errno) + errno = ECANCELED; + + fprintf(stderr, "nvmutil: "); + + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + + fprintf(stderr, ": %s\n", strerror(errno)); + + exit(EXIT_FAILURE); +} + void err(int nvm_errval, const char *msg, ...) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); va_list args; @@ -214,7 +216,7 @@ err(int nvm_errval, const char *msg, ...) const char * getnvmprogname(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); const char *p; static char fallback[] = "nvmutil"; @@ -239,7 +241,7 @@ getnvmprogname(void) int exit_cleanup(void) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f; int close_err; @@ -252,14 +254,19 @@ exit_cleanup(void) f = &x->f; if (f->gbe_fd > -1) { - if (close_on_eintr(f->gbe_fd) == -1) + if (close_on_eintr(f->gbe_fd) == -1) { + f->gbe_fd = -1; close_err = 1; + } f->gbe_fd = -1; } if (f->tmp_fd > -1) { - if (close_on_eintr(f->tmp_fd) == -1) + if (close_on_eintr(f->tmp_fd) == -1) { + f->tmp_fd = -1; close_err = 1; + } + f->tmp_fd = -1; } if (f->tname != NULL) { diff --git a/util/nvmutil/lib/string.c b/util/nvmutil/lib/string.c index b1a5c3e2..ca58fb1c 100644 --- a/util/nvmutil/lib/string.c +++ b/util/nvmutil/lib/string.c @@ -9,67 +9,106 @@ #include #include +#include #include #include "../include/common.h" -/* Portable strncmp() that blocks - * NULL/empty/unterminated strings +/* scmp() - strict string comparison + * + * strict string comparison + * similar to strncmp, but null and + * unterminated inputs do not produce + * a return value; on error, errno is + * set and -1 is returned. + * + * the real return value is stored in + * the 4th argument by pointer. + * + * the value at rval pointer is set, + * only upon success. callers should + * check the return value accordingly. */ int -xstrxcmp(const char *a, const char *b, unsigned long maxlen) +scmp(const char *a, + const char *b, + size_t maxlen, + int *rval) { - unsigned long i; + size_t ch; + unsigned char ac; + unsigned char bc; - if (a == NULL || b == NULL) - err(EINVAL, "NULL input to xstrxcmp"); + if (a == NULL || + b == NULL || + rval == NULL) { - if (*a == '\0' || *b == '\0') - err(EINVAL, "Empty string in xstrxcmp"); + errno = EFAULT; + return -1; + } - for (i = 0; i < maxlen; i++) { + for (ch = 0; ch < maxlen; ch++) { - unsigned char ac = (unsigned char)a[i]; - unsigned char bc = (unsigned char)b[i]; + ac = (unsigned char)a[ch]; + bc = (unsigned char)b[ch]; - if (ac == '\0' || bc == '\0') { - if (ac == bc) - return 0; - return ac - bc; + if (ac != bc) { + *rval = ac - bc; + return 0; } - if (ac != bc) - return ac - bc; + if (ac == '\0') { + *rval = 0; + return 0; + } } - err(EINVAL, "Unterminated string in xstrxcmp"); - - errno = EINVAL; + /* block unterminated strings */ + errno = EFAULT; return -1; } -/* Portable strncmp() that blocks - * NULL/empty/unterminated strings +/* slen() - strict strict length + * + * strict string length calculation + * similar to strnlen, but null and + * unterminated inputs do not produce + * a return value; on error, errno is + * set and -1 is returned. + * + * the real return value is stored in + * the 3rd argument by pointer. + * + * the value at rval pointer is set, + * only upon success. callers should + * check the return value accordingly. */ -unsigned long -xstrxlen(const char *scmp, unsigned long maxlen) +int +slen(const char *s, + size_t maxlen, + size_t *rval) { - unsigned long xstr_index; + size_t ch; - if (scmp == NULL) - err(EINVAL, "NULL input to xstrxlen"); + if (s == NULL || + rval == NULL) { - if (*scmp == '\0') - err(EINVAL, "Empty string in xstrxlen"); + errno = EFAULT; + return -1; + } - for (xstr_index = 0; - xstr_index < maxlen && scmp[xstr_index] != '\0'; - xstr_index++); + for (ch = 0; + ch < maxlen && s[ch] != '\0'; + ch++); - if (xstr_index == maxlen) - err(EINVAL, "Unterminated string in xstrxlen"); + if (ch == maxlen) { + /* unterminated */ + errno = EFAULT; + return -1; + } - return xstr_index; + *rval = ch; + return 0; } diff --git a/util/nvmutil/lib/word.c b/util/nvmutil/lib/word.c index 5d9220c7..f84dae6a 100644 --- a/util/nvmutil/lib/word.c +++ b/util/nvmutil/lib/word.c @@ -13,12 +13,12 @@ #include "../include/common.h" unsigned short -nvm_word(unsigned long pos16, unsigned long p) +nvm_word(size_t pos16, size_t p) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long pos; + size_t pos; check_nvm_bound(pos16, p); pos = (pos16 << 1) + (p * GBE_PART_SIZE); @@ -28,12 +28,12 @@ nvm_word(unsigned long pos16, unsigned long p) } void -set_nvm_word(unsigned long pos16, unsigned long p, unsigned short val16) +set_nvm_word(size_t pos16, size_t p, unsigned short val16) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; - unsigned long pos; + size_t pos; check_nvm_bound(pos16, p); pos = (pos16 << 1) + (p * GBE_PART_SIZE); @@ -45,9 +45,9 @@ set_nvm_word(unsigned long pos16, unsigned long p, unsigned short val16) } void -set_part_modified(unsigned long p) +set_part_modified(size_t p) { - struct xstate *x = xstatus(0, NULL); + struct xstate *x = xstatus(); struct xfile *f = &x->f; check_bin(p, "part number"); @@ -55,7 +55,7 @@ set_part_modified(unsigned long p) } void -check_nvm_bound(unsigned long c, unsigned long p) +check_nvm_bound(size_t c, size_t p) { /* Block out of bound NVM access */ @@ -64,5 +64,5 @@ check_nvm_bound(unsigned long c, unsigned long p) if (c >= NVM_WORDS) err(ECANCELED, "check_nvm_bound: out of bounds %lu", - (unsigned long)c); + (size_t)c); } diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index 670b7110..cb08ec43 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -6,6 +6,12 @@ * These images configure your Intel Gigabit Ethernet adapter. */ +#ifdef __OpenBSD__ +/* for pledge/unveil test: + */ +#include +#endif + #include #include @@ -13,22 +19,88 @@ #include #include #include +#include #include +#include #include "include/common.h" int main(int argc, char *argv[]) { - struct xstate *x = xstatus(argc, argv); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; + struct xstate *x; + + struct commands *cmd; + struct xfile *f; + + size_t c; + +/* https://man.openbsd.org/pledge.2 + https://man.openbsd.org/unveil.2 */ +#if defined(__OpenBSD__) && defined(OpenBSD) +#if (OpenBSD) >= 604 + if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1) + err_no_cleanup(errno, "pledge plus unveil, main"); + if (unveil("/dev/null", "r") == -1) + err_no_cleanup(errno, "unveil r: /dev/null"); +#elif (OpenBSD) >= 509 + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + err_no_cleanup(errno, "pledge, main"); +#endif +#endif + +#ifndef S_ISREG + err_no_cleanup(ECANCELED, + "Can't determine file types (S_ISREG undefined)"); +#endif +#if ((CHAR_BIT) != 8) + err_no_cleanup(ECANCELED, "Unsupported char size"); +#endif + + x = xstart(argc, argv); + + if (x == NULL) + err_no_cleanup(ECANCELED, "NULL state on init"); - unsigned long c; + cmd = &x->cmd[x->i]; + f = &x->f; + +/* https://man.openbsd.org/pledge.2 + https://man.openbsd.org/unveil.2 */ +#if defined(__OpenBSD__) && defined(OpenBSD) +#if (OpenBSD) >= 604 + + if ((us.cmd[i].flags & O_ACCMODE) == O_RDONLY) { + if (unveil(us.f.fname, "r") == -1) + err(errno, "%s: unveil r", us.f.fname); + } else { + if (unveil(us.f.fname, "rwc") == -1) + err(errno, "%s: unveil rw", us.f.fname); + } + + if (unveil(us.f.tname, "rwc") == -1) + err(errno, "unveil rwc: %s", us.f.tname); + + if (unveil(NULL, NULL) == -1) + err(errno, "unveil block (rw)"); + + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + err(errno, "pledge (kill unveil)"); + +#elif (OpenBSD) >= 509 + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + err(errno, "pledge"); +#endif +#endif if (cmd->run == NULL) err(errno, "Command not set"); + open_gbe_file(); + + copy_gbe(); + read_checksums(); + cmd->run(); for (c = 0; c < items(x->cmd); c++) @@ -43,8 +115,10 @@ main(int argc, char *argv[]) if (f->io_err_gbe_bin) err(EIO, "%s: error writing final file"); - if (f->tname != NULL) + if (f->tname != NULL) { free(f->tname); + f->tname = NULL; + } return EXIT_SUCCESS; } -- cgit v1.2.1