d0811f
From 3335de91437bc983c95cfab86489ceb3a0b0a6aa Mon Sep 17 00:00:00 2001
d0811f
From: Chris Down <chris@chrisdown.name>
d0811f
Date: Tue, 25 Aug 2020 21:59:11 +0100
d0811f
Subject: [PATCH 1/2] path: Skip directories when finalising $PATH search
d0811f
d0811f
Imagine $PATH /a:/b. There is an echo command at /b/echo. Under this
d0811f
configuration, this works fine:
d0811f
d0811f
    % systemd-run --user --scope echo .
d0811f
    Running scope as unit: run-rfe98e0574b424d63a641644af511ff30.scope
d0811f
    .
d0811f
d0811f
However, if I do `mkdir /a/echo`, this happens:
d0811f
d0811f
    % systemd-run --user --scope echo .
d0811f
    Running scope as unit: run-rcbe9369537ed47f282ee12ce9f692046.scope
d0811f
    Failed to execute: Permission denied
d0811f
d0811f
We check whether the resulting file is executable for the performing
d0811f
user, but of course, most directories are anyway, since that's needed to
d0811f
list within it. As such, another is_dir() check is needed prior to
d0811f
considering the search result final.
d0811f
d0811f
Another approach might be to check S_ISREG, but there may be more gnarly
d0811f
edge cases there than just eliminating this obviously pathological
d0811f
example, so let's just do this for now.
d0811f
---
d0811f
 src/basic/path-util.c | 3 +++
d0811f
 1 file changed, 3 insertions(+)
d0811f
d0811f
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
d0811f
index c4e022b3a1..d3b4978239 100644
d0811f
--- a/src/basic/path-util.c
d0811f
+++ b/src/basic/path-util.c
d0811f
@@ -637,6 +637,9 @@ int find_binary(const char *name, char **ret) {
d0811f
                 if (!j)
d0811f
                         return -ENOMEM;
d0811f
 
d0811f
+                if (is_dir(j, true))
d0811f
+                        continue;
d0811f
+
d0811f
                 if (access(j, X_OK) >= 0) {
d0811f
                         /* Found it! */
d0811f
 
d0811f
-- 
d0811f
2.26.2
d0811f
d0811f
d0811f
From 2f94890f37c13dcd680a63876ed6d34f8e66d0a3 Mon Sep 17 00:00:00 2001
d0811f
From: Chris Down <chris@chrisdown.name>
d0811f
Date: Wed, 26 Aug 2020 18:49:27 +0100
d0811f
Subject: [PATCH 2/2] path: Improve $PATH search directory case
d0811f
d0811f
Previously:
d0811f
d0811f
1. last_error wouldn't be updated with errors from is_dir;
d0811f
2. We'd always issue a stat(), even for binaries without execute;
d0811f
3. We used stat() instead of access(), which is cheaper.
d0811f
d0811f
This change avoids all of those, by only checking inside X_OK-positive
d0811f
case whether access() works on the path with an extra slash appended.
d0811f
Thanks to Lennart for the suggestion.
d0811f
---
d0811f
 src/basic/path-util.c | 25 ++++++++++++++++++-------
d0811f
 1 file changed, 18 insertions(+), 7 deletions(-)
d0811f
d0811f
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
d0811f
index d3b4978239..7b0863f749 100644
d0811f
--- a/src/basic/path-util.c
d0811f
+++ b/src/basic/path-util.c
d0811f
@@ -637,16 +637,27 @@ int find_binary(const char *name, char **ret) {
d0811f
                 if (!j)
d0811f
                         return -ENOMEM;
d0811f
 
d0811f
-                if (is_dir(j, true))
d0811f
-                        continue;
d0811f
-
d0811f
                 if (access(j, X_OK) >= 0) {
d0811f
-                        /* Found it! */
d0811f
+                        _cleanup_free_ char *with_dash;
d0811f
 
d0811f
-                        if (ret)
d0811f
-                                *ret = path_simplify(TAKE_PTR(j), false);
d0811f
+                        with_dash = strjoin(j, "/");
d0811f
+                        if (!with_dash)
d0811f
+                                return -ENOMEM;
d0811f
 
d0811f
-                        return 0;
d0811f
+                        /* If this passes, it must be a directory, and so should be skipped. */
d0811f
+                        if (access(with_dash, X_OK) >= 0)
d0811f
+                                continue;
d0811f
+
d0811f
+                        /**
d0811f
+                         * We can't just `continue` inverting this case, since we need to update last_error.
d0811f
+                         */
d0811f
+                        if (errno == ENOTDIR) {
d0811f
+                                /* Found it! */
d0811f
+                                if (ret)
d0811f
+                                        *ret = path_simplify(TAKE_PTR(j), false);
d0811f
+
d0811f
+                                return 0;
d0811f
+                        }
d0811f
                 }
d0811f
 
d0811f
                 /* PATH entries which we don't have access to are ignored, as per tradition. */
d0811f
-- 
d0811f
2.26.2
d0811f