diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 5775e6c641..cbb2983d02 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -3366,7 +3366,9 @@ SystemCallErrorNumber=EPERM
assignments made through Environment= or EnvironmentFile=, inherited from
the system manager's global set of environment variables, inherited via PassEnvironment=,
set by the service manager itself (such as $NOTIFY_SOCKET and such), or set by a PAM module
- (in case PAMName= is used).
+ (in case PAMName= is used). Note that variable expansion in the command lines of
+ ExecStart= and friends is not affected by this option, as it happens before the
+ unsetting is applied.
See "Environment Variables in Spawned Processes" below for a description of how those
settings combine to form the inherited environment. See If the same environment variable is set by multiple of these sources, the later source — according
to the order of the list above — wins. Note that as the final step all variables listed in
UnsetEnvironment= are removed from the compiled environment variable list, immediately
- before it is passed to the executed process.
+ before it is passed to the executed process. This unsetting happens after variable expansion in
+ the command lines of ExecStart= and friends is performed, hence command line
+ expansion is not affected by UnsetEnvironment=.
The general philosophy is to expose a small curated list of environment variables to processes.
Services started by the system manager (PID 1) will be started, without additional service-specific
diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c
index 74a556fa3b..2fabfe3410 100644
--- a/src/core/exec-invoke.c
+++ b/src/core/exec-invoke.c
@@ -6665,18 +6665,6 @@ int exec_invoke(
#endif
}
- if (!strv_isempty(context->unset_environment)) {
- char **ee = NULL;
-
- ee = strv_env_delete(accum_env, 1, context->unset_environment);
- if (!ee) {
- *exit_status = EXIT_MEMORY;
- return log_oom();
- }
-
- strv_free_and_replace(accum_env, ee);
- }
-
_cleanup_strv_free_ char **replaced_argv = NULL, **argv_via_shell = NULL;
char **final_argv = FLAGS_SET(command->flags, EXEC_COMMAND_VIA_SHELL) ? strv_skip(command->argv, 1) : command->argv;
@@ -6701,6 +6689,21 @@ int exec_invoke(
}
}
+ /* Apply UnsetEnvironment= after the command line has been expanded, so that the variables it
+ * removes are still available for expansion. Only the environment block passed to the executed
+ * process is affected, not the expansion. */
+ if (!strv_isempty(context->unset_environment)) {
+ char **ee = NULL;
+
+ ee = strv_env_delete(accum_env, 1, context->unset_environment);
+ if (!ee) {
+ *exit_status = EXIT_MEMORY;
+ return log_oom();
+ }
+
+ strv_free_and_replace(accum_env, ee);
+ }
+
if (FLAGS_SET(command->flags, EXEC_COMMAND_VIA_SHELL)) {
r = strv_extendf(&argv_via_shell, "%s%s", command->argv[0][0] == '-' ? "-" : "", path);
if (r < 0) {
diff --git a/test/test-execute/exec-unsetenvironment.service b/test/test-execute/exec-unsetenvironment.service
index e64e261072..16a5559bfb 100644
--- a/test/test-execute/exec-unsetenvironment.service
+++ b/test/test-execute/exec-unsetenvironment.service
@@ -3,7 +3,9 @@
Description=Test for UnsetEnvironment
[Service]
-ExecStart=bash -x -c 'test "$$FOO" = "bar" && test "$${QUUX-X}" = "X" && test "$$VAR3" = "value3" && test "$${VAR4-X}" = "X" && test "$$VAR5" = "value5" && test "$${X%b-X}" = "X"'
+# Variable expansion in the command line is done by the service manager before UnsetEnvironment= is
+# applied, so $VAR6 is expanded, but the variable is not part of the environment of the executed bash.
+ExecStart=bash -x -c 'test "$$FOO" = "bar" && test "$${QUUX-X}" = "X" && test "$$VAR3" = "value3" && test "$${VAR4-X}" = "X" && test "$$VAR5" = "value5" && test "$${X%b-X}" = "X" && test "$$1" = "value6" && test "$${VAR6-X}" = "X"' x $VAR6
Type=oneshot
-Environment=FOO=bar QUUX=waldo VAR3=value3 VAR4=value4 VAR5=value5 X%b=%U
-UnsetEnvironment=QUUX=waldo VAR3=somethingelse VAR4 X%b=%U
+Environment=FOO=bar QUUX=waldo VAR3=value3 VAR4=value4 VAR5=value5 X%b=%U VAR6=value6
+UnsetEnvironment=QUUX=waldo VAR3=somethingelse VAR4 X%b=%U VAR6