mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
process-util: add execvp_safe() and execvpe_safe()
When execve() fails with ENOEXEC, glibc's execvp() and execvpe() retry the file as a shell script via /bin/sh. Hence a truncated (e.g. empty) binary is run as an empty shell script and exits successfully, instead of failing with ENOEXEC. musl doesn't do this. Let's add variants that look up the file in $PATH via find_executable() and then call execve(), so that ENOEXEC is propagated to the caller. (cherry picked from commitb3ec1fec6d) (cherry picked from commit68fa520bf5)
This commit is contained in:
1 parent
9a7581eeed
commit
8e49974e6c
3 files changed
+80
No files matched your search
@@ -2029,6 +2029,30 @@ int make_reaper_process(bool b) {
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(posix_spawnattr_t*, posix_spawnattr_destroy, NULL);
|
||||
|
||||
int execvpe_safe(const char *file, char * const *argv, char * const *envp) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
int r;
|
||||
|
||||
assert(file);
|
||||
assert(argv);
|
||||
assert(envp);
|
||||
|
||||
/* Like execvpe(), but doesn't fall back to running the file via /bin/sh if execve() fails with
|
||||
* ENOEXEC. Without this a truncated or otherwise broken binary would "successfully" run as an empty
|
||||
* shell script instead of failing. Returns a negative errno on failure. */
|
||||
|
||||
r = find_executable(file, &resolved);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
execve(resolved, argv, envp);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
int execvp_safe(const char *file, char * const *argv) {
|
||||
return execvpe_safe(file, argv, environ);
|
||||
}
|
||||
|
||||
int posix_spawn_wrapper(
|
||||
const char *path,
|
||||
char * const *argv,
|
||||
|
||||
@@ -236,6 +236,9 @@ int get_process_threads(pid_t pid);
|
||||
int is_reaper_process(void);
|
||||
int make_reaper_process(bool b);
|
||||
|
||||
int execvpe_safe(const char *file, char * const *argv, char * const *envp);
|
||||
int execvp_safe(const char *file, char * const *argv);
|
||||
|
||||
int posix_spawn_wrapper(
|
||||
const char *path,
|
||||
char * const *argv,
|
||||
|
||||
@@ -24,21 +24,25 @@
|
||||
#include "errno-list.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "ioprio-util.h"
|
||||
#include "log.h"
|
||||
#include "namespace-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pidfd-util.h"
|
||||
#include "pidref.h"
|
||||
#include "process-util.h"
|
||||
#include "procfs-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "signal-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "tests.h"
|
||||
#include "time-util.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "user-util.h"
|
||||
#include "virt.h"
|
||||
|
||||
@@ -1140,6 +1144,55 @@ TEST(invoked_as) {
|
||||
ASSERT_EQ(unsetenv("SYSTEMD_INVOKED_AS"), 0);
|
||||
}
|
||||
|
||||
#define EXIT_EXPECTED_ERROR 42
|
||||
|
||||
static void test_execvp_safe_one(const char *name, const char *search_path, int expected_error) {
|
||||
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
|
||||
int r;
|
||||
|
||||
/* If expected_error is 0, the binary is expected to be executed and to exit successfully. Otherwise
|
||||
* execvp_safe() is expected to return expected_error. */
|
||||
|
||||
r = ASSERT_OK(pidref_safe_fork("(execvp-safe)", FORK_DEATHSIG_SIGKILL|FORK_LOG, &pidref));
|
||||
if (r == 0) {
|
||||
if (search_path && setenv("PATH", search_path, /* overwrite= */ true) < 0) {
|
||||
log_error_errno(errno, "Failed to set $PATH: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
r = execvp_safe(name, STRV_MAKE(name));
|
||||
if (r != expected_error) {
|
||||
log_error_errno(r, "execvp_safe(\"%s\") returned unexpected error: %m", name);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
_exit(EXIT_EXPECTED_ERROR);
|
||||
}
|
||||
|
||||
int expected_exit_status = expected_error == 0 ? EXIT_SUCCESS : EXIT_EXPECTED_ERROR;
|
||||
ASSERT_OK_EQ(pidref_wait_for_terminate_and_check("(execvp-safe)", &pidref, WAIT_LOG_ABNORMAL),
|
||||
expected_exit_status);
|
||||
}
|
||||
|
||||
TEST(execvp_safe) {
|
||||
_cleanup_(rm_rf_physical_and_freep) char *d = NULL;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
|
||||
/* An empty executable file must make execvp_safe() fail with ENOEXEC, and must not be run as an
|
||||
* empty shell script, which would exit successfully. */
|
||||
|
||||
ASSERT_OK(mkdtemp_malloc(NULL, &d));
|
||||
ASSERT_NOT_NULL(p = path_join(d, "test-process-util-empty"));
|
||||
ASSERT_OK(touch_file(p, /* parents= */ false, USEC_INFINITY, UID_INVALID, GID_INVALID, 0755));
|
||||
if (access(p, X_OK) < 0) /* e.g. /tmp/ mounted noexec */
|
||||
return (void) log_tests_skipped_errno(errno, "Cannot execute files in %s", d);
|
||||
|
||||
test_execvp_safe_one(p, /* search_path= */ NULL, -ENOEXEC);
|
||||
test_execvp_safe_one("test-process-util-empty", d, -ENOEXEC);
|
||||
test_execvp_safe_one("test-process-util-missing", d, -ENOENT);
|
||||
test_execvp_safe_one("true", /* search_path= */ NULL, 0);
|
||||
}
|
||||
|
||||
static int intro(void) {
|
||||
log_show_color(true);
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
Reference in new issue
Block a user