diff options
Diffstat (limited to 'src/core/execute.c')
-rw-r--r-- | src/core/execute.c | 548 |
1 files changed, 193 insertions, 355 deletions
diff --git a/src/core/execute.c b/src/core/execute.c index 4ff6f4ebd0..06a291fd39 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1259,6 +1259,41 @@ static void rename_process_from_path(const char *path) { rename_process(process_name); } +static bool context_has_address_families(const ExecContext *c) { + assert(c); + + return c->address_families_whitelist || + !set_isempty(c->address_families); +} + +static bool context_has_syscall_filters(const ExecContext *c) { + assert(c); + + return c->syscall_whitelist || + !set_isempty(c->syscall_filter); +} + +static bool context_has_no_new_privileges(const ExecContext *c) { + assert(c); + + if (c->no_new_privileges) + return true; + + if (have_effective_cap(CAP_SYS_ADMIN)) /* if we are privileged, we don't need NNP */ + return false; + + /* We need NNP if we have any form of seccomp and are unprivileged */ + return context_has_address_families(c) || + c->memory_deny_write_execute || + c->restrict_realtime || + exec_context_restrict_namespaces_set(c) || + c->protect_kernel_tunables || + c->protect_kernel_modules || + c->private_devices || + context_has_syscall_filters(c) || + !set_isempty(c->syscall_archs); +} + #ifdef HAVE_SECCOMP static bool skip_seccomp_unavailable(const Unit* u, const char* msg) { @@ -1272,344 +1307,131 @@ static bool skip_seccomp_unavailable(const Unit* u, const char* msg) { return true; } -static int apply_seccomp(const Unit* u, const ExecContext *c) { - uint32_t negative_action, action; - scmp_filter_ctx seccomp; - Iterator i; - void *id; - int r; +static int apply_syscall_filter(const Unit* u, const ExecContext *c) { + uint32_t negative_action, default_action, action; + assert(u); assert(c); - if (skip_seccomp_unavailable(u, "syscall filtering")) + if (!context_has_syscall_filters(c)) return 0; - negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno); - - seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW); - if (!seccomp) - return -ENOMEM; - - if (c->syscall_archs) { + if (skip_seccomp_unavailable(u, "SystemCallFilter=")) + return 0; - SET_FOREACH(id, c->syscall_archs, i) { - r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); - if (r == -EEXIST) - continue; - if (r < 0) - goto finish; - } + negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno); + if (c->syscall_whitelist) { + default_action = negative_action; + action = SCMP_ACT_ALLOW; } else { - r = seccomp_add_secondary_archs(seccomp); - if (r < 0) - goto finish; + default_action = SCMP_ACT_ALLOW; + action = negative_action; } - action = c->syscall_whitelist ? SCMP_ACT_ALLOW : negative_action; - SET_FOREACH(id, c->syscall_filter, i) { - r = seccomp_rule_add(seccomp, action, PTR_TO_INT(id) - 1, 0); - if (r < 0) - goto finish; - } - - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) - goto finish; - - r = seccomp_load(seccomp); - -finish: - seccomp_release(seccomp); - return r; + return seccomp_load_syscall_filter_set_raw(default_action, c->syscall_filter, action); } -static int apply_address_families(const Unit* u, const ExecContext *c) { - scmp_filter_ctx seccomp; - Iterator i; - int r; - +static int apply_syscall_archs(const Unit *u, const ExecContext *c) { + assert(u); assert(c); - if (skip_seccomp_unavailable(u, "RestrictAddressFamilies=")) + if (set_isempty(c->syscall_archs)) return 0; - r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); - if (r < 0) - return r; - - if (c->address_families_whitelist) { - int af, first = 0, last = 0; - void *afp; - - /* If this is a whitelist, we first block the address - * families that are out of range and then everything - * that is not in the set. First, we find the lowest - * and highest address family in the set. */ - - SET_FOREACH(afp, c->address_families, i) { - af = PTR_TO_INT(afp); - - if (af <= 0 || af >= af_max()) - continue; - - if (first == 0 || af < first) - first = af; - - if (last == 0 || af > last) - last = af; - } - - assert((first == 0) == (last == 0)); - - if (first == 0) { - - /* No entries in the valid range, block everything */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 0); - if (r < 0) - goto finish; - - } else { + if (skip_seccomp_unavailable(u, "SystemCallArchitectures=")) + return 0; - /* Block everything below the first entry */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_LT, first)); - if (r < 0) - goto finish; - - /* Block everything above the last entry */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_GT, last)); - if (r < 0) - goto finish; - - /* Block everything between the first and last - * entry */ - for (af = 1; af < af_max(); af++) { - - if (set_contains(c->address_families, INT_TO_PTR(af))) - continue; + return seccomp_restrict_archs(c->syscall_archs); +} - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_EQ, af)); - if (r < 0) - goto finish; - } - } +static int apply_address_families(const Unit* u, const ExecContext *c) { + assert(u); + assert(c); - } else { - void *af; - - /* If this is a blacklist, then generate one rule for - * each address family that are then combined in OR - * checks. */ - - SET_FOREACH(af, c->address_families, i) { - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_EQ, PTR_TO_INT(af))); - if (r < 0) - goto finish; - } - } + if (!context_has_address_families(c)) + return 0; - r = seccomp_load(seccomp); + if (skip_seccomp_unavailable(u, "RestrictAddressFamilies=")) + return 0; -finish: - seccomp_release(seccomp); - return r; + return seccomp_restrict_address_families(c->address_families, c->address_families_whitelist); } static int apply_memory_deny_write_execute(const Unit* u, const ExecContext *c) { - scmp_filter_ctx seccomp; - int r; - + assert(u); assert(c); - if (skip_seccomp_unavailable(u, "MemoryDenyWriteExecute=")) + if (!c->memory_deny_write_execute) return 0; - r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); - if (r < 0) - return r; - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPERM), - SCMP_SYS(mmap), - 1, - SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_EXEC|PROT_WRITE, PROT_EXEC|PROT_WRITE)); - if (r < 0) - goto finish; - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPERM), - SCMP_SYS(mprotect), - 1, - SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_EXEC, PROT_EXEC)); - if (r < 0) - goto finish; - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPERM), - SCMP_SYS(shmat), - 1, - SCMP_A2(SCMP_CMP_MASKED_EQ, SHM_EXEC, SHM_EXEC)); - if (r < 0) - goto finish; - - r = seccomp_load(seccomp); + if (skip_seccomp_unavailable(u, "MemoryDenyWriteExecute=")) + return 0; -finish: - seccomp_release(seccomp); - return r; + return seccomp_memory_deny_write_execute(); } static int apply_restrict_realtime(const Unit* u, const ExecContext *c) { - static const int permitted_policies[] = { - SCHED_OTHER, - SCHED_BATCH, - SCHED_IDLE, - }; - - scmp_filter_ctx seccomp; - unsigned i; - int r, p, max_policy = 0; - + assert(u); assert(c); - if (skip_seccomp_unavailable(u, "RestrictRealtime=")) + if (!c->restrict_realtime) return 0; - r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); - if (r < 0) - return r; - - /* Determine the highest policy constant we want to allow */ - for (i = 0; i < ELEMENTSOF(permitted_policies); i++) - if (permitted_policies[i] > max_policy) - max_policy = permitted_policies[i]; - - /* Go through all policies with lower values than that, and block them -- unless they appear in the - * whitelist. */ - for (p = 0; p < max_policy; p++) { - bool good = false; - - /* Check if this is in the whitelist. */ - for (i = 0; i < ELEMENTSOF(permitted_policies); i++) - if (permitted_policies[i] == p) { - good = true; - break; - } - - if (good) - continue; - - /* Deny this policy */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPERM), - SCMP_SYS(sched_setscheduler), - 1, - SCMP_A1(SCMP_CMP_EQ, p)); - if (r < 0) - goto finish; - } - - /* Blacklist all other policies, i.e. the ones with higher values. Note that all comparisons are unsigned here, - * hence no need no check for < 0 values. */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPERM), - SCMP_SYS(sched_setscheduler), - 1, - SCMP_A1(SCMP_CMP_GT, max_policy)); - if (r < 0) - goto finish; - - r = seccomp_load(seccomp); + if (skip_seccomp_unavailable(u, "RestrictRealtime=")) + return 0; -finish: - seccomp_release(seccomp); - return r; + return seccomp_restrict_realtime(); } static int apply_protect_sysctl(const Unit *u, const ExecContext *c) { - scmp_filter_ctx seccomp; - int r; - + assert(u); assert(c); /* Turn off the legacy sysctl() system call. Many distributions turn this off while building the kernel, but * let's protect even those systems where this is left on in the kernel. */ - if (skip_seccomp_unavailable(u, "ProtectKernelTunables=")) + if (!c->protect_kernel_tunables) return 0; - r = seccomp_init_conservative(&seccomp, SCMP_ACT_ALLOW); - if (r < 0) - return r; - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPERM), - SCMP_SYS(_sysctl), - 0); - if (r < 0) - goto finish; - - r = seccomp_load(seccomp); + if (skip_seccomp_unavailable(u, "ProtectKernelTunables=")) + return 0; -finish: - seccomp_release(seccomp); - return r; + return seccomp_protect_sysctl(); } static int apply_protect_kernel_modules(const Unit *u, const ExecContext *c) { + assert(u); assert(c); /* Turn off module syscalls on ProtectKernelModules=yes */ + if (!c->protect_kernel_modules) + return 0; + if (skip_seccomp_unavailable(u, "ProtectKernelModules=")) return 0; - return seccomp_load_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_MODULE, SCMP_ACT_ERRNO(EPERM)); + return seccomp_load_syscall_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_MODULE, SCMP_ACT_ERRNO(EPERM)); } static int apply_private_devices(const Unit *u, const ExecContext *c) { + assert(u); assert(c); /* If PrivateDevices= is set, also turn off iopl and all @raw-io syscalls. */ + if (!c->private_devices) + return 0; + if (skip_seccomp_unavailable(u, "PrivateDevices=")) return 0; - return seccomp_load_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_RAW_IO, SCMP_ACT_ERRNO(EPERM)); + return seccomp_load_syscall_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_RAW_IO, SCMP_ACT_ERRNO(EPERM)); } static int apply_restrict_namespaces(Unit *u, const ExecContext *c) { + assert(u); assert(c); if (!exec_context_restrict_namespaces_set(c)) @@ -2310,41 +2132,6 @@ static int close_remaining_fds( return close_all_fds(dont_close, n_dont_close); } -static bool context_has_address_families(const ExecContext *c) { - assert(c); - - return c->address_families_whitelist || - !set_isempty(c->address_families); -} - -static bool context_has_syscall_filters(const ExecContext *c) { - assert(c); - - return c->syscall_whitelist || - !set_isempty(c->syscall_filter) || - !set_isempty(c->syscall_archs); -} - -static bool context_has_no_new_privileges(const ExecContext *c) { - assert(c); - - if (c->no_new_privileges) - return true; - - if (have_effective_cap(CAP_SYS_ADMIN)) /* if we are privileged, we don't need NNP */ - return false; - - /* We need NNP if we have any form of seccomp and are unprivileged */ - return context_has_address_families(c) || - c->memory_deny_write_execute || - c->restrict_realtime || - exec_context_restrict_namespaces_set(c) || - c->protect_kernel_tunables || - c->protect_kernel_modules || - c->private_devices || - context_has_syscall_filters(c); -} - static int send_user_lookup( Unit *unit, int user_lookup_fd, @@ -2386,7 +2173,8 @@ static int exec_child( int *fds, unsigned n_fds, char **files_env, int user_lookup_fd, - int *exit_status) { + int *exit_status, + char **error_message) { _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL; _cleanup_free_ char *mac_selinux_context_net = NULL; @@ -2405,6 +2193,9 @@ static int exec_child( assert(context); assert(params); assert(exit_status); + assert(error_message); + /* We don't always set error_message, hence it must be initialized */ + assert(*error_message == NULL); rename_process_from_path(command->path); @@ -2422,6 +2213,8 @@ static int exec_child( r = reset_signal_mask(); if (r < 0) { *exit_status = EXIT_SIGNAL_MASK; + *error_message = strdup("Failed to reset signal mask"); + /* If strdup fails, here and below, we will just print the generic error message. */ return r; } @@ -2437,6 +2230,7 @@ static int exec_child( r = close_remaining_fds(params, runtime, dcreds, user_lookup_fd, socket_fd, fds, n_fds); if (r < 0) { *exit_status = EXIT_FDS; + *error_message = strdup("Failed to close remaining fds"); return r; } @@ -2465,6 +2259,7 @@ static int exec_child( return 0; } *exit_status = EXIT_CONFIRM; + *error_message = strdup("Execution cancelled"); return -ECANCELED; } } @@ -2474,17 +2269,27 @@ static int exec_child( /* Make sure we bypass our own NSS module for any NSS checks */ if (putenv((char*) "SYSTEMD_NSS_DYNAMIC_BYPASS=1") != 0) { *exit_status = EXIT_USER; + *error_message = strdup("Failed to update environment"); return -errno; } r = dynamic_creds_realize(dcreds, &uid, &gid); if (r < 0) { *exit_status = EXIT_USER; + *error_message = strdup("Failed to update dynamic user credentials"); return r; } - if (!uid_is_valid(uid) || !gid_is_valid(gid)) { + if (!uid_is_valid(uid)) { *exit_status = EXIT_USER; + (void) asprintf(error_message, "UID validation failed for \""UID_FMT"\"", uid); + /* If asprintf fails, here and below, we will just print the generic error message. */ + return -ESRCH; + } + + if (!gid_is_valid(gid)) { + *exit_status = EXIT_USER; + (void) asprintf(error_message, "GID validation failed for \""GID_FMT"\"", gid); return -ESRCH; } @@ -2495,12 +2300,14 @@ static int exec_child( r = get_fixed_user(context, &username, &uid, &gid, &home, &shell); if (r < 0) { *exit_status = EXIT_USER; + *error_message = strdup("Failed to determine user credentials"); return r; } r = get_fixed_group(context, &groupname, &gid); if (r < 0) { *exit_status = EXIT_GROUP; + *error_message = strdup("Failed to determine group credentials"); return r; } } @@ -2510,12 +2317,14 @@ static int exec_child( &supplementary_gids, &ngids); if (r < 0) { *exit_status = EXIT_GROUP; + *error_message = strdup("Failed to determine supplementary groups"); return r; } r = send_user_lookup(unit, user_lookup_fd, uid, gid); if (r < 0) { *exit_status = EXIT_USER; + *error_message = strdup("Failed to send user credentials to PID1"); return r; } @@ -2529,18 +2338,21 @@ static int exec_child( r = setup_input(context, params, socket_fd, named_iofds); if (r < 0) { *exit_status = EXIT_STDIN; + *error_message = strdup("Failed to set up stdin"); return r; } r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, named_iofds, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); if (r < 0) { *exit_status = EXIT_STDOUT; + *error_message = strdup("Failed to set up stdout"); return r; } r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, named_iofds, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); if (r < 0) { *exit_status = EXIT_STDERR; + *error_message = strdup("Failed to set up stderr"); return r; } @@ -2548,6 +2360,7 @@ static int exec_child( r = cg_attach_everywhere(params->cgroup_supported, params->cgroup_path, 0, NULL, NULL); if (r < 0) { *exit_status = EXIT_CGROUP; + (void) asprintf(error_message, "Failed to attach to cgroup %s", params->cgroup_path); return r; } } @@ -2568,6 +2381,7 @@ static int exec_child( log_close(); } else if (r < 0) { *exit_status = EXIT_OOM_ADJUST; + *error_message = strdup("Failed to write /proc/self/oom_score_adj"); return -errno; } } @@ -2812,6 +2626,7 @@ static int exec_child( r = capability_bounding_set_drop(context->capability_bounding_set, false); if (r < 0) { *exit_status = EXIT_CAPABILITIES; + *error_message = strdup("Failed to drop capabilities"); return r; } } @@ -2822,6 +2637,7 @@ static int exec_child( r = capability_ambient_set_apply(context->capability_ambient_set, true); if (r < 0) { *exit_status = EXIT_CAPABILITIES; + *error_message = strdup("Failed to apply ambient capabilities (before UID change)"); return r; } } @@ -2830,6 +2646,7 @@ static int exec_child( r = enforce_user(context, uid); if (r < 0) { *exit_status = EXIT_USER; + (void) asprintf(error_message, "Failed to change UID to "UID_FMT, uid); return r; } if (context->capability_ambient_set != 0) { @@ -2838,6 +2655,7 @@ static int exec_child( r = capability_ambient_set_apply(context->capability_ambient_set, false); if (r < 0) { *exit_status = EXIT_CAPABILITIES; + *error_message = strdup("Failed to apply ambient capabilities (after UID change)"); return r; } @@ -2865,6 +2683,7 @@ static int exec_child( r = setexeccon(exec_context); if (r < 0) { *exit_status = EXIT_SELINUX_CONTEXT; + (void) asprintf(error_message, "Failed to set SELinux context to %s", exec_context); return r; } } @@ -2874,6 +2693,7 @@ static int exec_child( r = setup_smack(context, command); if (r < 0) { *exit_status = EXIT_SMACK_PROCESS_LABEL; + *error_message = strdup("Failed to set SMACK process label"); return r; } @@ -2882,6 +2702,9 @@ static int exec_child( r = aa_change_onexec(context->apparmor_profile); if (r < 0 && !context->apparmor_profile_ignore) { *exit_status = EXIT_APPARMOR_PROFILE; + (void) asprintf(error_message, + "Failed to prepare AppArmor profile change to %s", + context->apparmor_profile); return -errno; } } @@ -2894,78 +2717,81 @@ static int exec_child( if (prctl(PR_GET_SECUREBITS) != secure_bits) if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) { *exit_status = EXIT_SECUREBITS; + *error_message = strdup("Failed to set secure bits"); return -errno; } if (context_has_no_new_privileges(context)) if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { *exit_status = EXIT_NO_NEW_PRIVILEGES; + *error_message = strdup("Failed to disable new privileges"); return -errno; } #ifdef HAVE_SECCOMP - if (context_has_address_families(context)) { - r = apply_address_families(unit, context); - if (r < 0) { - *exit_status = EXIT_ADDRESS_FAMILIES; - return r; - } + r = apply_address_families(unit, context); + if (r < 0) { + *exit_status = EXIT_ADDRESS_FAMILIES; + *error_message = strdup("Failed to restrict address families"); + return r; } - if (context->memory_deny_write_execute) { - r = apply_memory_deny_write_execute(unit, context); - if (r < 0) { - *exit_status = EXIT_SECCOMP; - return r; - } + r = apply_memory_deny_write_execute(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + *error_message = strdup("Failed to disable writing to executable memory"); + return r; } - if (context->restrict_realtime) { - r = apply_restrict_realtime(unit, context); - if (r < 0) { - *exit_status = EXIT_SECCOMP; - return r; - } + r = apply_restrict_realtime(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + *error_message = strdup("Failed to apply realtime restrictions"); + return r; } r = apply_restrict_namespaces(unit, context); if (r < 0) { *exit_status = EXIT_SECCOMP; + *error_message = strdup("Failed to apply namespace restrictions"); return r; } - if (context->protect_kernel_tunables) { - r = apply_protect_sysctl(unit, context); - if (r < 0) { - *exit_status = EXIT_SECCOMP; - return r; - } + r = apply_protect_sysctl(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + *error_message = strdup("Failed to apply sysctl restrictions"); + return r; } - if (context->protect_kernel_modules) { - r = apply_protect_kernel_modules(unit, context); - if (r < 0) { - *exit_status = EXIT_SECCOMP; - return r; - } + r = apply_protect_kernel_modules(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + *error_message = strdup("Failed to apply module loading restrictions"); + return r; } - if (context->private_devices) { - r = apply_private_devices(unit, context); - if (r < 0) { - *exit_status = EXIT_SECCOMP; - return r; - } + r = apply_private_devices(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + *error_message = strdup("Failed to set up private devices"); + return r; + } + + r = apply_syscall_archs(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + *error_message = strdup("Failed to apply syscall architecture restrictions"); + return r; } /* This really should remain the last step before the execve(), to make sure our own code is unaffected * by the filter as little as possible. */ - if (context_has_syscall_filters(context)) { - r = apply_seccomp(unit, context); - if (r < 0) { - *exit_status = EXIT_SECCOMP; - return r; - } + r = apply_syscall_filter(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + *error_message = strdup("Failed to apply syscall filters"); + return r; } #endif } @@ -2973,6 +2799,7 @@ static int exec_child( final_argv = replace_env_argv(argv, accum_env); if (!final_argv) { *exit_status = EXIT_MEMORY; + *error_message = strdup("Failed to prepare process arguments"); return -ENOMEM; } @@ -3059,6 +2886,7 @@ int exec_spawn(Unit *unit, if (pid == 0) { int exit_status; + _cleanup_free_ char *error_message = NULL; r = exec_child(unit, command, @@ -3072,17 +2900,27 @@ int exec_spawn(Unit *unit, fds, n_fds, files_env, unit->manager->user_lookup_fds[1], - &exit_status); + &exit_status, + &error_message); if (r < 0) { log_open(); - log_struct_errno(LOG_ERR, r, - LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED), - LOG_UNIT_ID(unit), - LOG_UNIT_MESSAGE(unit, "Failed at step %s spawning %s: %m", - exit_status_to_string(exit_status, EXIT_STATUS_SYSTEMD), - command->path), - "EXECUTABLE=%s", command->path, - NULL); + if (error_message) + log_struct_errno(LOG_ERR, r, + LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED), + LOG_UNIT_ID(unit), + LOG_UNIT_MESSAGE(unit, "%s: %m", + error_message), + "EXECUTABLE=%s", command->path, + NULL); + else + log_struct_errno(LOG_ERR, r, + LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED), + LOG_UNIT_ID(unit), + LOG_UNIT_MESSAGE(unit, "Failed at step %s spawning %s: %m", + exit_status_to_string(exit_status, EXIT_STATUS_SYSTEMD), + command->path), + "EXECUTABLE=%s", command->path, + NULL); } _exit(exit_status); |