From 47dfe7fa37d4f5bdeb99b60d43c4646881fa489d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 30 Oct 2025 21:44:04 +0000 Subject: [PATCH] fs: improve path-in-executable location * Do not search `PATH` for fully- or partially-qualified filenames (eg, `foo/bar`) * Ensure that a file in the `PATH` is executable before returning it --- src/util/fs_path.c | 37 +++++++++++++++++++++++++++++++++---- src/util/fs_path.h | 6 ++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/util/fs_path.c b/src/util/fs_path.c index 9d5c99eab..37dcf0330 100644 --- a/src/util/fs_path.c +++ b/src/util/fs_path.c @@ -611,6 +611,18 @@ bool git_fs_path_isfile(const char *path) return S_ISREG(st.st_mode) != 0; } +bool git_fs_path_isexecutable(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + if (p_stat(path, &st) < 0) + return false; + + return S_ISREG(st.st_mode) != 0 && + ((st.st_mode & S_IXUSR) != 0); +} + bool git_fs_path_islink(const char *path) { struct stat st; @@ -2038,8 +2050,17 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable) #else git_str path = GIT_STR_INIT; const char *current_dir, *term; + size_t current_dirlen; bool found = false; + /* For qualified paths we do not look in PATH */ + if (strchr(executable, '/') != NULL) { + if (!git_fs_path_isexecutable(executable)) + return GIT_ENOTFOUND; + + return git_str_puts(fullpath, executable); + } + if (git__getenv(&path, "PATH") < 0) return -1; @@ -2049,20 +2070,28 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable) if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) term = strchr(current_dir, '\0'); + current_dirlen = term - current_dir; git_str_clear(fullpath); - if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || - git_str_putc(fullpath, '/') < 0 || + + /* An empty path segment is treated as '.' */ + if (current_dirlen == 0 && git_str_putc(fullpath, '.')) + return -1; + else if (current_dirlen != 0 && + git_str_put(fullpath, current_dir, current_dirlen) < 0) + return -1; + + if (git_str_putc(fullpath, '/') < 0 || git_str_puts(fullpath, executable) < 0) return -1; - if (git_fs_path_isfile(fullpath->ptr)) { + if (git_fs_path_isexecutable(fullpath->ptr)) { found = true; break; } current_dir = term; - while (*current_dir == GIT_PATH_LIST_SEPARATOR) + if (*current_dir == GIT_PATH_LIST_SEPARATOR) current_dir++; } diff --git a/src/util/fs_path.h b/src/util/fs_path.h index 43f7951ad..82baf9bfa 100644 --- a/src/util/fs_path.h +++ b/src/util/fs_path.h @@ -204,6 +204,12 @@ extern bool git_fs_path_isdir(const char *path); */ extern bool git_fs_path_isfile(const char *path); +/** + * Check if the given path points to an executable. + * @return true or false + */ +extern bool git_fs_path_isexecutable(const char *path); + /** * Check if the given path points to a symbolic link. * @return true or false