core: resolve binary names immediately before execution

This has two advantages:
- we save a bit of IO in early boot because we don't look for executables
  which we might never call
- if the executable is in a different place and it was specified as a
  non-absolute path, it is OK if it moves to a different place. This should
  solve the case paths are different in the initramfs.

Since the executable path is only available quite late, the call to
mac_selinux_get_child_mls_label() which uses the path needs to be moved down
too.

Fixes #16076.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2020-09-16 10:27:51 +02:00
parent 92673045b5
commit 9f71ba8d95
2 changed files with 59 additions and 68 deletions

View file

@ -2873,12 +2873,11 @@ static int setup_credentials(
#if ENABLE_SMACK
static int setup_smack(
const ExecContext *context,
const ExecCommand *command) {
const char *executable) {
int r;
assert(context);
assert(command);
assert(executable);
if (context->smack_process_label) {
r = mac_smack_apply_pid(0, context->smack_process_label);
@ -2889,7 +2888,7 @@ static int setup_smack(
else {
_cleanup_free_ char *exec_label = NULL;
r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label);
r = mac_smack_read(executable, SMACK_ATTR_EXEC, &exec_label);
if (r < 0 && !IN_SET(r, -ENODATA, -EOPNOTSUPP))
return r;
@ -3084,7 +3083,7 @@ static bool insist_on_sandboxing(
static int apply_mount_namespace(
const Unit *u,
const ExecCommand *command,
ExecCommandFlags command_flags,
const ExecContext *context,
const ExecParameters *params,
const ExecRuntime *runtime,
@ -3113,7 +3112,7 @@ static int apply_mount_namespace(
if (r < 0)
return r;
needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command->flags & EXEC_COMMAND_FULLY_PRIVILEGED);
needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command_flags & EXEC_COMMAND_FULLY_PRIVILEGED);
if (needs_sandboxing) {
/* The runtime struct only contains the parent of the private /tmp,
* which is non-accessible to world users. Inside of it there's a /tmp
@ -4098,16 +4097,6 @@ static int exec_child(
}
if (needs_sandboxing) {
#if HAVE_SELINUX
if (use_selinux && params->selinux_context_net && socket_fd >= 0) {
r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net);
if (r < 0) {
*exit_status = EXIT_SELINUX_CONTEXT;
return log_unit_error_errno(unit, r, "Failed to determine SELinux context: %m");
}
}
#endif
/* If we're unprivileged, set up the user namespace first to enable use of the other namespaces.
* Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to
* set up the all of the other namespaces (i.e. network, mount, UTS) without a user namespace. */
@ -4144,7 +4133,7 @@ static int exec_child(
if (needs_mount_namespace) {
_cleanup_free_ char *error_path = NULL;
r = apply_mount_namespace(unit, command, context, params, runtime, &error_path);
r = apply_mount_namespace(unit, command->flags, context, params, runtime, &error_path);
if (r < 0) {
*exit_status = EXIT_NAMESPACE;
return log_unit_error_errno(unit, r, "Failed to set up mount namespacing%s%s: %m",
@ -4198,6 +4187,43 @@ static int exec_child(
}
}
/* Now that the mount namespace has been set up and privileges adjusted, let's look for the thing we
* shall execute. */
_cleanup_free_ char *executable = NULL;
r = find_executable_full(command->path, false, &executable);
if (r < 0) {
if (r != -ENOMEM && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) {
log_struct_errno(LOG_INFO, r,
"MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR,
LOG_UNIT_ID(unit),
LOG_UNIT_INVOCATION_ID(unit),
LOG_UNIT_MESSAGE(unit, "Executable %s missing, skipping: %m",
command->path),
"EXECUTABLE=%s", command->path);
return 0;
}
*exit_status = EXIT_EXEC;
return log_struct_errno(LOG_INFO, r,
"MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR,
LOG_UNIT_ID(unit),
LOG_UNIT_INVOCATION_ID(unit),
LOG_UNIT_MESSAGE(unit, "Failed to locate executable %s: %m",
command->path),
"EXECUTABLE=%s", command->path);
}
#if HAVE_SELINUX
if (needs_sandboxing && use_selinux && params->selinux_context_net && socket_fd >= 0) {
r = mac_selinux_get_child_mls_label(socket_fd, executable, context->selinux_context, &mac_selinux_context_net);
if (r < 0) {
*exit_status = EXIT_SELINUX_CONTEXT;
return log_unit_error_errno(unit, r, "Failed to determine SELinux context: %m");
}
}
#endif
/* We repeat the fd closing here, to make sure that nothing is leaked from the PAM modules. Note that we are
* more aggressive this time since socket_fd and the netns fds we don't need anymore. We do keep the exec_fd
* however if we have it as we want to keep it open until the final execve(). */
@ -4270,7 +4296,7 @@ static int exec_child(
/* LSM Smack needs the capability CAP_MAC_ADMIN to change the current execution security context of the
* process. This is the latest place before dropping capabilities. Other MAC context are set later. */
if (use_smack) {
r = setup_smack(context, command);
r = setup_smack(context, executable);
if (r < 0) {
*exit_status = EXIT_SMACK_PROCESS_LABEL;
return log_unit_error_errno(unit, r, "Failed to set SMACK process label: %m");
@ -4522,7 +4548,7 @@ static int exec_child(
line = exec_command_line(final_argv);
if (line)
log_struct(LOG_DEBUG,
"EXECUTABLE=%s", command->path,
"EXECUTABLE=%s", executable,
LOG_UNIT_MESSAGE(unit, "Executing: %s", line),
LOG_UNIT_ID(unit),
LOG_UNIT_INVOCATION_ID(unit));
@ -4540,7 +4566,7 @@ static int exec_child(
}
}
execve(command->path, final_argv, accum_env);
execve(executable, final_argv, accum_env);
r = -errno;
if (exec_fd >= 0) {
@ -4555,19 +4581,8 @@ static int exec_child(
}
}
if (r == -ENOENT && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) {
log_struct_errno(LOG_INFO, r,
"MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR,
LOG_UNIT_ID(unit),
LOG_UNIT_INVOCATION_ID(unit),
LOG_UNIT_MESSAGE(unit, "Executable %s missing, skipping: %m",
command->path),
"EXECUTABLE=%s", command->path);
return 0;
}
*exit_status = EXIT_EXEC;
return log_unit_error_errno(unit, r, "Failed to execute command: %m");
return log_unit_error_errno(unit, r, "Failed to execute %s: %m", executable);
}
static int exec_context_load_environment(const Unit *unit, const ExecContext *c, char ***l);
@ -4629,13 +4644,16 @@ int exec_spawn(Unit *unit,
if (!line)
return log_oom();
/* fork with up-to-date SELinux label database, so the child inherits the up-to-date db
and, until the next SELinux policy changes, we safe further reloads in future children */
/* Fork with up-to-date SELinux label database, so the child inherits the up-to-date db
and, until the next SELinux policy changes, we save further reloads in future children. */
mac_selinux_maybe_reload();
log_struct(LOG_DEBUG,
LOG_UNIT_MESSAGE(unit, "About to execute: %s", line),
"EXECUTABLE=%s", command->path,
LOG_UNIT_MESSAGE(unit, "About to execute %s", line),
"EXECUTABLE=%s", command->path, /* We won't know the real executable path until we create
the mount namespace in the child, but we want to log
from the parent, so we need to use the (possibly
inaccurate) path here. */
LOG_UNIT_ID(unit),
LOG_UNIT_INVOCATION_ID(unit));

View file

@ -789,38 +789,11 @@ int config_parse_exec(
return ignore ? 0 : -ENOEXEC;
}
if (!path_is_absolute(path)) {
const char *prefix;
bool found = false;
if (!filename_is_valid(path)) {
log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
"Neither a valid executable name nor an absolute path%s: %s",
ignore ? ", ignoring" : "", path);
return ignore ? 0 : -ENOEXEC;
}
/* Resolve a single-component name to a full path */
NULSTR_FOREACH(prefix, DEFAULT_PATH_NULSTR) {
_cleanup_free_ char *fullpath = NULL;
fullpath = path_join(prefix, path);
if (!fullpath)
return log_oom();
if (access(fullpath, X_OK) >= 0) {
free_and_replace(path, fullpath);
found = true;
break;
}
}
if (!found) {
log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
"Executable \"%s\" not found in path \"%s\"%s",
path, DEFAULT_PATH, ignore ? ", ignoring" : "");
return ignore ? 0 : -ENOEXEC;
}
if (!path_is_absolute(path) && !filename_is_valid(path)) {
log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0,
"Neither a valid executable name nor an absolute path%s: %s",
ignore ? ", ignoring" : "", path);
return ignore ? 0 : -ENOEXEC;
}
if (!separate_argv0) {