diff --git a/man/systemd.service.xml b/man/systemd.service.xml index f0f9aee1546..b8f3bcad606 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -338,6 +338,11 @@ If the executable path is prefixed with -, an exit code of the command normally considered a failure (i.e. non-zero exit status or abnormal exit due to signal) is recorded, but has no further effect and is considered equivalent to success. + + : + If the executable path is prefixed with :, environment variable substitution (as described by the "Command Lines" section below) is not applied. + + + If the executable path is prefixed with + then the process is executed with full privileges. In this mode privilege restrictions configured with User=, Group=, CapabilityBoundingSet= or the various file system namespacing options (such as PrivateDevices=, PrivateTmp=) are not applied to the invoked command line (but still affect any other ExecStart=, ExecStop=, … lines). @@ -358,7 +363,7 @@ - @, -, and one of + @, -, :, and one of +/!/!! may be used together and they can appear in any order. However, only one of +, !, !! may be used at a time. Note that these prefixes are also supported for the other command line settings, diff --git a/src/core/execute.c b/src/core/execute.c index 42a09333b15..39d9f07518b 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2846,12 +2846,13 @@ static int exec_child( int user_lookup_fd, int *exit_status) { - _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL; + _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **replaced_argv = NULL; int *fds_with_exec_fd, n_fds_with_exec_fd, r, ngids = 0, exec_fd = -1; _cleanup_free_ gid_t *supplementary_gids = NULL; const char *username = NULL, *groupname = NULL; _cleanup_free_ char *home_buffer = NULL; const char *home = NULL, *shell = NULL; + char **final_argv = NULL; dev_t journal_stream_dev = 0; ino_t journal_stream_ino = 0; bool needs_sandboxing, /* Do we need to set up full sandboxing? (i.e. all namespacing, all MAC stuff, caps, yadda yadda */ @@ -3604,11 +3605,15 @@ static int exec_child( strv_free_and_replace(accum_env, ee); } - final_argv = replace_env_argv(command->argv, accum_env); - if (!final_argv) { - *exit_status = EXIT_MEMORY; - return log_oom(); - } + if (!FLAGS_SET(command->flags, EXEC_COMMAND_NO_ENV_EXPAND)) { + replaced_argv = replace_env_argv(command->argv, accum_env); + if (!replaced_argv) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } + final_argv = replaced_argv; + } else + final_argv = command->argv; if (DEBUG_LOGGING) { _cleanup_free_ char *line; diff --git a/src/core/execute.h b/src/core/execute.h index 12a6e92b659..4b5b2d98cef 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -91,6 +91,7 @@ typedef enum ExecCommandFlags { EXEC_COMMAND_FULLY_PRIVILEGED = 1 << 1, EXEC_COMMAND_NO_SETUID = 1 << 2, EXEC_COMMAND_AMBIENT_MAGIC = 1 << 3, + EXEC_COMMAND_NO_ENV_EXPAND = 1 << 4, } ExecCommandFlags; /* Stores information about commands we execute. Covers both configuration settings as well as runtime data. */ diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 1d4a1bd3435..a4efd309003 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -561,7 +561,8 @@ int config_parse_exec( for (;;) { /* We accept an absolute path as first argument. If it's prefixed with - and the path doesn't * exist, we ignore it instead of erroring out; if it's prefixed with @, we allow overriding of - * argv[0]; if it's prefixed with +, it will be run with full privileges and no sandboxing; if + * argv[0]; if it's prefixed with :, we will not do environment variable substitution; + * if it's prefixed with +, it will be run with full privileges and no sandboxing; if * it's prefixed with '!' we apply sandboxing, but do not change user/group credentials; if * it's prefixed with '!!', then we apply user/group credentials if the kernel supports ambient * capabilities -- if it doesn't we don't apply the credentials themselves, but do apply most @@ -576,6 +577,8 @@ int config_parse_exec( ignore = true; } else if (*f == '@' && !separate_argv0) separate_argv0 = true; + else if (*f == ':' && !(flags & EXEC_COMMAND_NO_ENV_EXPAND)) + flags |= EXEC_COMMAND_NO_ENV_EXPAND; else if (*f == '+' && !(flags & (EXEC_COMMAND_FULLY_PRIVILEGED|EXEC_COMMAND_NO_SETUID|EXEC_COMMAND_AMBIENT_MAGIC))) flags |= EXEC_COMMAND_FULLY_PRIVILEGED; else if (*f == '!' && !(flags & (EXEC_COMMAND_FULLY_PRIVILEGED|EXEC_COMMAND_NO_SETUID|EXEC_COMMAND_AMBIENT_MAGIC))) diff --git a/src/test/test-execute.c b/src/test/test-execute.c index eb8f7c4effc..19571121593 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -494,6 +494,7 @@ static void test_exec_dynamicuser(Manager *m) { } static void test_exec_environment(Manager *m) { + test(m, "exec-environment-no-substitute.service", 0, CLD_EXITED); test(m, "exec-environment.service", 0, CLD_EXITED); test(m, "exec-environment-multiple.service", 0, CLD_EXITED); test(m, "exec-environment-empty.service", 0, CLD_EXITED); diff --git a/test/meson.build b/test/meson.build index 94903934a5c..796ad063c02 100644 --- a/test/meson.build +++ b/test/meson.build @@ -55,6 +55,7 @@ test_data_files = ''' test-execute/exec-dynamicuser-statedir-migrate-step2.service test-execute/exec-dynamicuser-statedir.service test-execute/exec-dynamicuser-supplementarygroups.service + test-execute/exec-environment-no-substitute.service test-execute/exec-environment-empty.service test-execute/exec-environment-multiple.service test-execute/exec-environment.service diff --git a/test/test-execute/exec-environment-no-substitute.service b/test/test-execute/exec-environment-no-substitute.service new file mode 100644 index 00000000000..6a2e60ec24f --- /dev/null +++ b/test/test-execute/exec-environment-no-substitute.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for No Environment Variable Substitution + +[Service] +ExecStart=/bin/sh -x -c 'test "$${VAR1-unset}" = "unset" && test "$${VAR2}" = "word3" && test "$${VAR3-unset}" = \'$word 5 6\'' +ExecStart=:/bin/sh -x -c 'test "$${VAR1-unset}" != "unset" && test "$${VAR2}" != "word3" && test "$${VAR3-unset}" != \'$word 5 6\'' +Type=oneshot +Environment="VAR2=word3" "VAR3=$word 5 6"