]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core/execute: fall back to execve() for scripts
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 18 Sep 2020 12:28:08 +0000 (14:28 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 6 Nov 2020 14:14:13 +0000 (15:14 +0100)
fexecve() fails with ENOENT and we need a fallback. Add appropriate test.

src/core/execute.c
src/shared/exec-util.c
src/shared/exec-util.h
src/test/test-path-util.c
test/meson.build
test/test-path-util/script.sh [new file with mode: 0755]

index f1d6f87755e8af379766a21baf60249b200f53e0..11e172f61b4d1602ff978f825b2edff806df3ed5 100644 (file)
@@ -4578,8 +4578,7 @@ static int exec_child(
                 }
         }
 
-        fexecve(executable_fd, final_argv, accum_env);
-        r = -errno;
+        r = fexecve_or_execve(executable_fd, executable, final_argv, accum_env);
 
         if (exec_fd >= 0) {
                 uint8_t hot = 0;
index a93c206d9a3791c281658ca893f944ad6d35bf1d..a0bb1567ced1aafc15f8bb65f786f986732e97a6 100644 (file)
@@ -443,3 +443,23 @@ ExecCommandFlags exec_command_flags_from_string(const char *s) {
         else
                 return 1 << idx;
 }
+
+int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]) {
+        fexecve(executable_fd, argv, envp);
+        if (errno == ENOENT)
+                /* A script? Let's fall back to execve().
+                 *
+                 * fexecve(3): "If fd refers to a script (i.e., it is an executable text file that names a
+                 * script interpreter with a first line that begins with the characters #!) and the
+                 * close-on-exec flag has been set for fd, then fexecve() fails with the error ENOENT. This
+                 * error occurs because, by the time the script interpreter is executed, fd has already been
+                 * closed because of the close-on-exec flag. Thus, the close-on-exec flag can't be set on fd
+                 * if it refers to a script."
+                 *
+                 * Unfortunately, if we unset close-on-exec, the script will be executed just fine, but (at
+                 * least in case of bash) the script name, $0, will be shown as /dev/fd/nnn, which breaks
+                 * scripts which make use of $0. Thus, let's fall back to execve() in this case.
+                 */
+                execve(executable, argv, envp);
+        return -errno;
+}
index 9fe9012516cbb752e18123dbc5b7222cf14358f1..65556249c1a75c537faaea00693fe8e3ab39225a 100644 (file)
@@ -45,3 +45,5 @@ extern const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX];
 
 const char* exec_command_flags_to_string(ExecCommandFlags i);
 ExecCommandFlags exec_command_flags_from_string(const char *s);
+
+int fexecve_or_execve(int executable_fd, const char *executable, char *const argv[], char *const envp[]);
index 3ee3795b486ab9cea65ba432783f2690853ce41f..c05a5d5b4a9cd755e099ddeb13cfc86c8373801b 100644 (file)
@@ -4,6 +4,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "exec-util.h"
 #include "fd-util.h"
 #include "macro.h"
 #include "mountpoint-util.h"
@@ -255,8 +256,8 @@ static void test_find_executable_exec_one(const char *path) {
         pid = fork();
         assert_se(pid >= 0);
         if (pid == 0) {
-                fexecve(fd, STRV_MAKE(t, "--version"), STRV_MAKE(NULL));
-                log_error_errno(errno, "fexecve: %m");
+                r = fexecve_or_execve(fd, t, STRV_MAKE(t, "--version"), STRV_MAKE(NULL));
+                log_error_errno(r, "[f]execve: %m");
                 _exit(EXIT_FAILURE);
         }
 
@@ -268,6 +269,10 @@ static void test_find_executable_exec(void) {
 
         test_find_executable_exec_one("touch");
         test_find_executable_exec_one("/bin/touch");
+
+        _cleanup_free_ char *script = NULL;
+        assert_se(get_testdata_dir("test-path-util/script.sh", &script) >= 0);
+        test_find_executable_exec_one(script);
 }
 
 static void test_prefixes(void) {
index 5656abdf72a97df0277d351c9313aba86eb67789..d9ff25f829c8d1ce09bf493b4839dc75f79ff773 100644 (file)
@@ -11,6 +11,8 @@ if install_tests
                        install_dir : testdata_dir)
         install_subdir('test-path',
                        install_dir : testdata_dir)
+        install_subdir('test-path-util',
+                       install_dir : testdata_dir)
         install_subdir('test-umount',
                        install_dir : testdata_dir)
         install_subdir('test-network-generator-conversion',
diff --git a/test/test-path-util/script.sh b/test/test-path-util/script.sh
new file mode 100755 (executable)
index 0000000..57c93e7
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+echo "$0 $@"
+test "$(basename $0)" = "script.sh" || exit 1
+test "$1" = "--version" || exit 2
+echo "Life is good"