summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2025-06-15 09:57:45 -0400
committerTavian Barnes <tavianator@tavianator.com>2025-06-15 10:09:23 -0400
commitcb40f51e4e6375a10265484b6959c6b1b0591378 (patch)
tree14b181a135857e6b5ea956f9a408506f7c24c5fa
parentdef4a832425bfe94b96b8cb1146a83552b754fb4 (diff)
downloadbfs-cb40f51e4e6375a10265484b6959c6b1b0591378.tar.xz
parse: Allow relative $PATH entries with explicit executable paths
$ bfs -execdir /bin/exe {} \; is perfectly safe regardless of what's in $PATH.
-rw-r--r--src/parse.c69
-rw-r--r--tests/bfs/execdir_path_relative_slash.out19
-rw-r--r--tests/bfs/execdir_path_relative_slash.sh1
3 files changed, 68 insertions, 21 deletions
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;
diff --git a/tests/bfs/execdir_path_relative_slash.out b/tests/bfs/execdir_path_relative_slash.out
new file mode 100644
index 0000000..62b31f6
--- /dev/null
+++ b/tests/bfs/execdir_path_relative_slash.out
@@ -0,0 +1,19 @@
+./a
+./b
+./bar
+./bar
+./basic
+./baz
+./c
+./d
+./e
+./f
+./foo
+./foo
+./foo
+./g
+./h
+./i
+./j
+./k
+./l
diff --git a/tests/bfs/execdir_path_relative_slash.sh b/tests/bfs/execdir_path_relative_slash.sh
new file mode 100644
index 0000000..fb5a924
--- /dev/null
+++ b/tests/bfs/execdir_path_relative_slash.sh
@@ -0,0 +1 @@
+PATH="foo:$PATH" bfs_diff basic -execdir /bin/sh -c 'printf "%s\\n" "$@"' sh {} +