diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bar.c | 16 | ||||
-rw-r--r-- | src/bfs.h | 9 | ||||
-rw-r--r-- | src/bfstd.c | 132 | ||||
-rw-r--r-- | src/bfstd.h | 63 | ||||
-rw-r--r-- | src/bftw.c | 5 | ||||
-rw-r--r-- | src/color.c | 4 | ||||
-rw-r--r-- | src/ctx.c | 1 | ||||
-rw-r--r-- | src/dstring.c | 2 | ||||
-rw-r--r-- | src/ioq.c | 4 | ||||
-rw-r--r-- | src/opt.c | 19 | ||||
-rw-r--r-- | src/parse.c | 69 | ||||
-rw-r--r-- | src/trie.c | 2 | ||||
-rw-r--r-- | src/trie.h | 2 | ||||
-rw-r--r-- | src/xspawn.c | 78 |
14 files changed, 319 insertions, 87 deletions
@@ -18,7 +18,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/ioctl.h> #include <termios.h> #include <unistd.h> @@ -33,25 +32,14 @@ struct bfs_bar { /** Get the terminal size, if possible. */ static int bfs_bar_getsize(struct bfs_bar *bar) { -#if BFS_HAS_TCGETWINSIZE || defined(TIOCGWINSZ) struct winsize ws; - -# if BFS_HAS_TCGETWINSIZE - int ret = tcgetwinsize(bar->fd, &ws); -# else - int ret = ioctl(bar->fd, TIOCGWINSZ, &ws); -# endif - if (ret != 0) { - return ret; + if (xtcgetwinsize(bar->fd, &ws) != 0) { + return -1; } store(&bar->width, ws.ws_col, relaxed); store(&bar->height, ws.ws_row, relaxed); return 0; -#else - errno = ENOTSUP; - return -1; -#endif } /** Write a string to the status bar (async-signal-safe). */ @@ -219,6 +219,15 @@ extern const char bfs_ldlibs[]; #endif /** + * Mark the size of a flexible array member. + */ +#if __has_attribute(counted_by) +# define _counted_by(...) __attribute__((counted_by(__VA_ARGS__))) +#else +# define _counted_by(...) +#endif + +/** * Optimization hint to not unroll a loop. */ #if BFS_HAS_PRAGMA_NOUNROLL diff --git a/src/bfstd.c b/src/bfstd.c index 82663eb..b78af7a 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -23,10 +23,12 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/ioctl.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> +#include <termios.h> #include <unistd.h> #include <wchar.h> @@ -187,16 +189,6 @@ char *xgetdelim(FILE *file, char delim) { } } -int open_cterm(int flags) { - char path[L_ctermid]; - if (ctermid(path) == NULL || strlen(path) == 0) { - errno = ENOTTY; - return -1; - } - - return open(path, flags); -} - const char *xgetprogname(void) { const char *cmd = NULL; #if BFS_HAS_GETPROGNAME @@ -243,6 +235,36 @@ static int xstrtox_epilogue(const char *str, char **end, char *endp) { return 0; } +int xstrtos(const char *str, char **end, int base, short *value) { + long n; + if (xstrtol(str, end, base, &n) != 0) { + return -1; + } + + if (n < SHRT_MIN || n > SHRT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +int xstrtoi(const char *str, char **end, int base, int *value) { + long n; + if (xstrtol(str, end, base, &n) != 0) { + return -1; + } + + if (n < INT_MIN || n > INT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + int xstrtol(const char *str, char **end, int base, long *value) { if (xstrtox_prologue(str) != 0) { return -1; @@ -283,6 +305,70 @@ int xstrtod(const char *str, char **end, double *value) { return xstrtox_epilogue(str, end, endp); } +int xstrtous(const char *str, char **end, int base, unsigned short *value) { + unsigned long n; + if (xstrtoul(str, end, base, &n) != 0) { + return -1; + } + + if (n > USHRT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +int xstrtoui(const char *str, char **end, int base, unsigned int *value) { + unsigned long n; + if (xstrtoul(str, end, base, &n) != 0) { + return -1; + } + + if (n > UINT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +/** Common epilogue for xstrtou*() wrappers. */ +static int xstrtoux_epilogue(const char *str, char **end, char *endp) { + if (xstrtox_epilogue(str, end, endp) != 0) { + return -1; + } + + if (str[0] == '-') { + errno = ERANGE; + return -1; + } + + return 0; +} + +int xstrtoul(const char *str, char **end, int base, unsigned long *value) { + if (xstrtox_prologue(str) != 0) { + return -1; + } + + char *endp; + *value = strtoul(str, &endp, base); + return xstrtoux_epilogue(str, end, endp); +} + +int xstrtoull(const char *str, char **end, int base, unsigned long long *value) { + if (xstrtox_prologue(str) != 0) { + return -1; + } + + char *endp; + *value = strtoull(str, &endp, base); + return xstrtoux_epilogue(str, end, endp); +} + /** Compile and execute a regular expression for xrpmatch(). */ static int xrpregex(nl_item item, const char *response) { const char *pattern = nl_langinfo(item); @@ -558,6 +644,32 @@ pid_t xwaitpid(pid_t pid, int *status, int flags) { return ret; } +int open_cterm(int flags) { + char path[L_ctermid]; + if (ctermid(path) == NULL || strlen(path) == 0) { + errno = ENOTTY; + return -1; + } + + return open(path, flags); +} + +int xtcgetwinsize(int fd, struct winsize *ws) { +#if BFS_HAS_TCGETWINSIZE + return tcgetwinsize(fd, ws); +#else + return ioctl(fd, TIOCGWINSZ, ws); +#endif +} + +int xtcsetwinsize(int fd, const struct winsize *ws) { +#if BFS_HAS_TCSETWINSIZE + return tcsetwinsize(fd, ws); +#else + return ioctl(fd, TIOCSWINSZ, ws); +#endif +} + int dup_cloexec(int fd) { #ifdef F_DUPFD_CLOEXEC return fcntl(fd, F_DUPFD_CLOEXEC, 0); diff --git a/src/bfstd.h b/src/bfstd.h index 28f473e..15dd949 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -158,16 +158,6 @@ FILE *xfopen(const char *path, int flags); */ char *xgetdelim(FILE *file, char delim); -/** - * Open the controlling terminal. - * - * @flags - * The open() flags. - * @return - * An open file descriptor, or -1 on failure. - */ -int open_cterm(int flags); - // #include <stdlib.h> /** @@ -179,6 +169,16 @@ int open_cterm(int flags); const char *xgetprogname(void); /** + * Like xstrtol(), but for short. + */ +int xstrtos(const char *str, char **end, int base, short *value); + +/** + * Like xstrtol(), but for int. + */ +int xstrtoi(const char *str, char **end, int base, int *value); + +/** * Wrapper for strtol() that forbids leading spaces. */ int xstrtol(const char *str, char **end, int base, long *value); @@ -189,6 +189,26 @@ int xstrtol(const char *str, char **end, int base, long *value); int xstrtoll(const char *str, char **end, int base, long long *value); /** + * Like xstrtoul(), but for unsigned short. + */ +int xstrtous(const char *str, char **end, int base, unsigned short *value); + +/** + * Like xstrtoul(), but for unsigned int. + */ +int xstrtoui(const char *str, char **end, int base, unsigned int *value); + +/** + * Wrapper for strtoul() that forbids leading spaces, negatives. + */ +int xstrtoul(const char *str, char **end, int base, unsigned long *value); + +/** + * Wrapper for strtoull() that forbids leading spaces, negatives. + */ +int xstrtoull(const char *str, char **end, int base, unsigned long long *value); + +/** * Wrapper for strtof() that forbids leading spaces. */ int xstrtof(const char *str, char **end, float *value); @@ -342,6 +362,29 @@ int xminor(dev_t dev); */ pid_t xwaitpid(pid_t pid, int *status, int flags); +#include <sys/ioctl.h> // May be necessary for struct winsize +#include <termios.h> + +/** + * Open the controlling terminal. + * + * @flags + * The open() flags. + * @return + * An open file descriptor, or -1 on failure. + */ +int open_cterm(int flags); + +/** + * tcgetwinsize()/ioctl(TIOCGWINSZ) wrapper. + */ +int xtcgetwinsize(int fd, struct winsize *ws); + +/** + * tcsetwinsize()/ioctl(TIOCSWINSZ) wrapper. + */ +int xtcsetwinsize(int fd, const struct winsize *ws); + // #include <unistd.h> /** @@ -253,7 +253,7 @@ struct bftw_file { /** The length of the file's name. */ size_t namelen; /** The file's name. */ - char name[]; + char name[]; // _counted_by(namelen + 1) }; /** @@ -1485,7 +1485,8 @@ fail: /** Check if we should stat() a file asynchronously. */ static bool bftw_should_ioq_stat(struct bftw_state *state, struct bftw_file *file) { - // To avoid surprising users too much, process the roots in order + // POSIX wants the root paths to be processed in order + // See https://www.austingroupbugs.net/view.php?id=1859 if (file->depth == 0) { return false; } diff --git a/src/color.c b/src/color.c index 588dbac..a026831 100644 --- a/src/color.c +++ b/src/color.c @@ -32,7 +32,7 @@ struct esc_seq { /** The length of the escape sequence. */ size_t len; /** The escape sequence itself, without a terminating NUL. */ - char seq[]; + char seq[] _counted_by(len); }; /** @@ -48,7 +48,7 @@ struct ext_color { /** Whether the comparison should be case-sensitive. */ bool case_sensitive; /** The extension to match (NUL-terminated). */ - char ext[]; + char ext[]; // _counted_by(len + 1); }; struct colors { @@ -286,6 +286,7 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { } free(ctx->paths); + free(ctx->kinds); free(ctx->argv); free(ctx); } diff --git a/src/dstring.c b/src/dstring.c index 0f08679..678d685 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -23,7 +23,7 @@ struct dstring { /** Length of the string, *excluding* the terminating NUL. */ size_t len; /** The string itself. */ - alignas(dchar) char str[]; + alignas(dchar) char str[] _counted_by(cap); }; #define DSTR_OFFSET offsetof(struct dstring, str) @@ -203,7 +203,7 @@ struct ioqq { cache_align atomic size_t tail; /** The circular buffer itself. */ - cache_align ioq_slot slots[]; + cache_align ioq_slot slots[]; // _counted_by(slot_mask + 1) }; /** Destroy an I/O command queue. */ @@ -593,7 +593,7 @@ struct ioq { /** The number of background threads. */ size_t nthreads; /** The background threads themselves. */ - struct ioq_thread threads[]; + struct ioq_thread threads[] _counted_by(nthreads); }; /** Cancel a request if we need to. */ @@ -1623,14 +1623,19 @@ static void data_flow_icmp(struct bfs_opt *opt, const struct bfs_expr *expr, enu /** Transfer function for -{execut,read,writ}able. */ static struct bfs_expr *data_flow_access(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { - if (expr->num & R_OK) { + switch (expr->num) { + case R_OK: data_flow_pred(opt, READABLE_PRED, true); - } - if (expr->num & W_OK) { + break; + case W_OK: data_flow_pred(opt, WRITABLE_PRED, true); - } - if (expr->num & X_OK) { + break; + case X_OK: data_flow_pred(opt, EXECUTABLE_PRED, true); + break; + default: + bfs_bug("Unknown access() mode %lld", expr->num); + break; } return expr; @@ -1655,7 +1660,7 @@ static struct bfs_expr *data_flow_gid(struct bfs_opt *opt, struct bfs_expr *expr gid_t gid = range->min; bool nogroup = !bfs_getgrgid(opt->ctx->groups, gid); if (errno == 0) { - data_flow_pred(opt, NOGROUP_PRED, nogroup); + constrain_pred(&opt->after_true.preds[NOGROUP_PRED], nogroup); } } @@ -1729,7 +1734,7 @@ static struct bfs_expr *data_flow_uid(struct bfs_opt *opt, struct bfs_expr *expr uid_t uid = range->min; bool nouser = !bfs_getpwuid(opt->ctx->users, uid); if (errno == 0) { - data_flow_pred(opt, NOUSER_PRED, nouser); + constrain_pred(&opt->after_true.preds[NOUSER_PRED], nouser); } } diff --git a/src/parse.c b/src/parse.c index 9c39d6b..5ec4c0e 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1247,6 +1247,41 @@ static struct bfs_expr *parse_empty(struct bfs_parser *parser, int arg1, int arg return expr; } +/** Check for unsafe relative paths in $PATH. */ +static const char *unsafe_path(const struct bfs_exec *execbuf) { + if (!(execbuf->flags & BFS_EXEC_CHDIR)) { + // Not -execdir or -okdir + return NULL; + } + + const char *exe = execbuf->tmpl_argv[0]; + if (strchr(exe, '/')) { + // No $PATH lookups for /foo or foo/bar + return NULL; + } + + if (strstr(exe, "{}")) { + // Substituted paths always contain a / + return NULL; + } + + const char *path = getenv("PATH"); + while (path) { + if (path[0] != '/') { + // Relative $PATH component! + return path; + } + + path = strchr(path, ':'); + if (path) { + ++path; + } + } + + // No relative components in $PATH + return NULL; +} + /** * Parse -exec(dir)?/-ok(dir)?. */ @@ -1269,29 +1304,21 @@ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg // For pipe() in bfs_spawn() expr->ephemeral_fds = 2; - if (execbuf->flags & BFS_EXEC_CHDIR) { - // Check for relative paths in $PATH - const char *path = getenv("PATH"); - while (path) { - if (*path != '/') { - size_t len = strcspn(path, ":"); - char *comp = strndup(path, len); - if (comp) { - parse_expr_error(parser, expr, - "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); - free(comp); - } else { - parse_perror(parser, "strndup()"); - } - return NULL; - } - - path = strchr(path, ':'); - if (path) { - ++path; - } + const char *unsafe = unsafe_path(execbuf); + if (unsafe) { + size_t len = strcspn(unsafe, ":"); + char *comp = strndup(unsafe, len); + if (comp) { + parse_expr_error(parser, expr, + "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); + free(comp); + } else { + parse_perror(parser, "strndup()"); } + return NULL; + } + if (execbuf->flags & BFS_EXEC_CHDIR) { // To dup() the parent directory if (execbuf->flags & BFS_EXEC_MULTI) { ++expr->persistent_fds; @@ -129,7 +129,7 @@ struct trie_node { * tag to distinguish internal nodes from leaves. This is safe as long * as all dynamic allocations are aligned to more than a single byte. */ - uintptr_t children[]; + uintptr_t children[]; // _counted_by(count_ones(bitmap)) }; /** Check if an encoded pointer is to an internal node. */ @@ -21,7 +21,7 @@ struct trie_leaf { /** The length of the key in bytes. */ size_t length; /** The key itself, stored inline. */ - char key[]; + char key[] _counted_by(length); }; /** diff --git a/src/xspawn.c b/src/xspawn.c index 3fa4e60..ee62c05 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -232,12 +232,28 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { */ #define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD__) +/** + * NetBSD even resolves the executable before file actions with posix_spawn()! + */ +#define BFS_POSIX_SPAWN_AFTER_FCHDIR !__NetBSD__ + int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR); if (!action) { return -1; } +#if __APPLE__ + // macOS has a bug that causes EBADF when an fchdir() action refers to a + // file opened by the file actions + for_slist (struct bfs_spawn_action, prev, ctx) { + if (fd == prev->out_fd) { + bfs_spawn_clear_posix(ctx); + break; + } + } +#endif + #if BFS_HAS_POSIX_SPAWN_ADDFCHDIR # define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir #elif BFS_HAS_POSIX_SPAWN_ADDFCHDIR_NP @@ -401,18 +417,40 @@ static bool bfs_resolve_relative(const struct bfs_resolver *res) { return false; } +/** Check if the actions include fchdir(). */ +static bool bfs_spawn_will_chdir(const struct bfs_spawn *ctx) { + if (ctx) { + for_slist (const struct bfs_spawn_action, action, ctx) { + if (action->op == BFS_SPAWN_FCHDIR) { + return true; + } + } + } + + return false; +} + +/** Check if we can call xfaccessat() before file actions. */ +static bool bfs_can_access_early(const struct bfs_resolver *res, const struct bfs_spawn *ctx) { + if (res->exe[0] == '/') { + return true; + } + + if (bfs_spawn_will_chdir(ctx)) { + return false; + } + + return true; +} + /** Check if we can resolve the executable before file actions. */ static bool bfs_can_resolve_early(const struct bfs_resolver *res, const struct bfs_spawn *ctx) { if (!bfs_resolve_relative(res)) { return true; } - if (ctx) { - for_slist (const struct bfs_spawn_action, action, ctx) { - if (action->op == BFS_SPAWN_FCHDIR) { - return false; - } - } + if (bfs_spawn_will_chdir(ctx)) { + return false; } return true; @@ -442,17 +480,19 @@ static int bfs_resolve_early(struct bfs_resolver *res, const char *exe, const st }; if (bfs_can_skip_resolve(res, ctx)) { - // Do this check eagerly, even though posix_spawn()/execv() also - // would, because: - // - // - faccessat() is faster than fork()/clone() + execv() - // - posix_spawn() is not guaranteed to report ENOENT - if (xfaccessat(AT_FDCWD, exe, X_OK) == 0) { - res->done = true; - return 0; - } else { - return -1; + if (bfs_can_access_early(res, ctx)) { + // Do this check eagerly, even though posix_spawn()/execv() also + // would, because: + // + // - faccessat() is faster than fork()/clone() + execv() + // - posix_spawn() is not guaranteed to report ENOENT + if (xfaccessat(AT_FDCWD, exe, X_OK) != 0) { + return -1; + } } + + res->done = true; + return 0; } res->path = getenv("PATH"); @@ -529,6 +569,12 @@ static bool bfs_use_posix_spawn(const struct bfs_resolver *res, const struct bfs } #endif +#if !BFS_POSIX_SPAWN_AFTER_FCHDIR + if (res->exe[0] != '/' && bfs_spawn_will_chdir(ctx)) { + return false; + } +#endif + return true; } |