core/execute: fall back to execve() for scripts

fexecve() fails with ENOENT and we need a fallback. Add appropriate test.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2020-09-18 14:28:08 +02:00
parent b83d505087
commit a6d9111c67
6 changed files with 38 additions and 4 deletions

View 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;

View 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;
}

View 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[]);

View 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) {

View 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',

6
test/test-path-util/script.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
echo "$0 $@"
test "$(basename $0)" = "script.sh" || exit 1
test "$1" = "--version" || exit 2
echo "Life is good"