diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index b350c19dcb..62c59308ef 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -61,13 +61,18 @@
paths. This is equivalent to having them listed explicitly in
RequiresMountsFor= .
- Similarly, units with PrivateTmp= enabled automatically get mount
- unit dependencies for all mounts required to access /tmp/ and
- /var/tmp/ . They will also gain an automatic After= dependency
- on
+ Units with PrivateTmp=yes automatically gain dependencies of type
+ Wants= and After= on all mounts required to access
+ /tmp/ and /var/tmp/ . They will also gain an automatic
+ After= dependency on
systemd-tmpfiles-setup.service 8 .
+ Units with PrivateTmp=disconnected automatically gain dependencies
+ of type Wants= and After= on the mount required to access
+ /var/ , unless DefaultDependencies=no and/or
+ RootDirectory=/RootImage= are specified.
+
Units whose standard output or error output is connected to journal or
kmsg (or their combinations with console output, see below) automatically acquire
dependencies of type After= on
@@ -1797,28 +1802,89 @@ BindReadOnlyPaths=/var/lib/systemd
Takes a boolean argument, or disconnected . If enabled, a new
file system namespace will be set up for the executed processes, and /tmp/
and /var/tmp/ directories inside it are not shared with processes outside of
- the namespace, plus all temporary files created by a service in these directories will be removed after
- the service is stopped. If true , the backing storage of the private temporary directories
- will remain on the host's /tmp/ and /var/tmp/ directories.
- If disconnected , the directories will be backed by a completely new tmpfs instance,
- meaning that the storage is fully disconnected from the host namespace. Defaults to false.
+ the namespace, plus all temporary files created by a service in these directories will be removed
+ after the service is stopped. For this setting, the same restrictions regarding mount propagation
+ and privileges apply as for ReadOnlyPaths= and related calls, see above. This
+ setting is useful to secure access to temporary files of the process, but makes sharing between
+ processes via /tmp/ or /var/tmp/ impossible. If
+ DynamicUser= is enabled, disconnected is implied. Otherwise,
+ defaults to false.
- This setting is useful to secure access to temporary files of the process, but makes sharing
- between processes via /tmp/ or /var/tmp/ impossible.
- If not set to disconnected , it is possible to run two or more units within
- the same private /tmp/ and /var/tmp/ namespace by using
- the JoinsNamespaceOf= directive, see
- systemd.unit 5
- for details. This setting is implied if DynamicUser= is set. For this setting,
- the same restrictions regarding mount propagation and privileges apply as for
- ReadOnlyPaths= and related calls, see above. If set to true
- (as opposed to disconnected ), this has the side effect of adding
- Requires= and After= dependencies on all mount units necessary
- to access /tmp/ and /var/tmp/ on the host. Moreover, an
- implicitly After= ordering on
+ If true , the backing storage of the private temporary directories will
+ remain on the host's /tmp/ and /var/tmp/ directories. It is
+ possible to run two or more units within the same private /tmp/ and
+ /var/tmp/ namespace by using the JoinsNamespaceOf= directive,
+ see systemd.unit 5
+ for details. This has the side effect of adding Wants= and
+ After= dependencies on all mount units necessary to access
+ /tmp/ and /var/tmp/ on the host. Moreover, an implicit
+ After= ordering on
systemd-tmpfiles-setup.service 8
is added.
+ If disconnected , the directories will be backed by a completely new tmpfs
+ instance, meaning that the storage is fully disconnected from the host namespace. The tmpfs instance
+ does not shared with other units even if the JoinsNamespaceOf= directive is used.
+ If DefaultDependencies=no is specified,
+ RequiresMountsFor=/WantsMountsFor= for /var/ is not
+ specified, and RootDirectory=/RootImage= is not specified, then a new tmpfs is
+ mounted only on /tmp/ , hence the host's
+ /var/tmp is still accessible from the unit. In that case,
+ $TMPDIR environment variable is set to /tmp to suggest the
+ processes in the unit to use /tmp/ . This automatically adds
+ WantsMountsFor=/var/ dependency, unless DefaultDependencies=no
+ and/or RootDirectory=/RootImage= are specified.
+
+
+ Summary for PrivateTmp=disconnected
+
+
+
+
+
+
+
+ Other Settings
+ tmpfs on /var/tmp/
+ $TMPVAR
+ implied dependencies
+
+
+
+
+ (none)
+ yes
+ (unset)
+ WantsMountsFor=/var/
+
+
+ RootDirectory=/RootImage=
+ yes
+ (unset)
+ (none)
+
+
+ DefaultDependency=no , RequiresMountsFor=/var/
+ yes
+ (unset)
+ (none)
+
+
+ DefaultDependency=no , WantsMountsFor=/var/
+ yes
+ (unset)
+ (none)
+
+
+ DefaultDependency=no
+ no
+ $TMPDIR=/tmp
+ (none)
+
+
+
+
+
Note that the implementation of this setting might be impossible (for example if mount namespaces are not
available), and the unit should be written in a way that does not solely rely on this setting for
security.
@@ -3944,6 +4010,18 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
+
+ $TMPDIR
+
+ The environment variable is set to /tmp when
+ PrivateTmp=disconnected specified with DefaultDependencies=no
+ and without RootDirectory=/RootImage= and
+ RequiresMountsFor=/WantsMountsFor= for /var/ .
+ See explanation for PrivateTmp= above.
+
+
+
+
$MAINPID
diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c
index b0bf502576..77fc647f7b 100644
--- a/src/core/exec-invoke.c
+++ b/src/core/exec-invoke.c
@@ -1921,7 +1921,7 @@ static int build_environment(
assert(cgroup_context);
assert(ret);
-#define N_ENV_VARS 21
+#define N_ENV_VARS 22
our_env = new0(char*, N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
if (!our_env)
return -ENOMEM;
@@ -2181,6 +2181,21 @@ static int build_environment(
our_env[n_env++] = x;
}
+ assert(c->private_var_tmp >= 0 && c->private_var_tmp < _PRIVATE_TMP_MAX);
+ if (c->private_tmp != c->private_var_tmp) {
+ assert(c->private_tmp == PRIVATE_TMP_DISCONNECTED);
+ assert(c->private_var_tmp == PRIVATE_TMP_NO);
+
+ /* When private tmpfs is enabled only on /tmp/, then explicitly set $TMPDIR to suggest the
+ * service to use /tmp/. */
+
+ x = strdup("TMPDIR=/tmp");
+ if (!x)
+ return -ENOMEM;
+
+ our_env[n_env++] = x;
+ }
+
assert(n_env < N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
#undef N_ENV_VARS
@@ -3589,6 +3604,7 @@ static int apply_mount_namespace(
.private_ipc = needs_sandboxing && exec_needs_ipc_namespace(context),
.private_pids = needs_sandboxing && exec_needs_pid_namespace(context) ? context->private_pids : PRIVATE_PIDS_NO,
.private_tmp = needs_sandboxing ? context->private_tmp : PRIVATE_TMP_NO,
+ .private_var_tmp = needs_sandboxing ? context->private_var_tmp : PRIVATE_TMP_NO,
.mount_apivfs = needs_sandboxing && exec_context_get_effective_mount_apivfs(context),
.bind_log_sockets = needs_sandboxing && exec_context_get_effective_bind_log_sockets(context),
diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c
index 0b71512653..18ec06b6a4 100644
--- a/src/core/execute-serialize.c
+++ b/src/core/execute-serialize.c
@@ -1735,6 +1735,12 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
if (r < 0)
return r;
+ /* This must be set in unit_patch_contexts() before executing a command. */
+ assert(c->private_var_tmp >= 0 && c->private_var_tmp < _PRIVATE_TMP_MAX);
+ r = serialize_item(f, "exec-context-private-var-tmp", private_tmp_to_string(c->private_var_tmp));
+ if (r < 0)
+ return r;
+
r = serialize_bool_elide(f, "exec-context-private-devices", c->private_devices);
if (r < 0)
return r;
@@ -2628,6 +2634,10 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
c->private_tmp = private_tmp_from_string(val);
if (c->private_tmp < 0)
return c->private_tmp;
+ } else if ((val = startswith(l, "exec-context-private-var-tmp="))) {
+ c->private_var_tmp = private_tmp_from_string(val);
+ if (c->private_var_tmp < 0)
+ return c->private_var_tmp;
} else if ((val = startswith(l, "exec-context-private-devices="))) {
r = parse_boolean(val);
if (r < 0)
diff --git a/src/core/execute.c b/src/core/execute.c
index 4aef0ad819..417d0be575 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -635,6 +635,7 @@ void exec_context_init(ExecContext *c) {
.mount_apivfs = -1,
.bind_log_sockets = -1,
.memory_ksm = -1,
+ .private_var_tmp = _PRIVATE_TMP_INVALID,
.set_login_environment = -1,
};
diff --git a/src/core/execute.h b/src/core/execute.h
index c8e78bee24..756d78117c 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -330,6 +330,8 @@ struct ExecContext {
int bind_log_sockets;
int memory_ksm;
PrivateTmp private_tmp;
+ PrivateTmp private_var_tmp; /* This is not an independent parameter, but calculated from other
+ * parameters in unit_patch_contexts(). */
bool private_network;
bool private_devices;
PrivateUsers private_users;
diff --git a/src/core/fuzz-execute-serialize.c b/src/core/fuzz-execute-serialize.c
index 67437549ea..48ddea10d7 100644
--- a/src/core/fuzz-execute-serialize.c
+++ b/src/core/fuzz-execute-serialize.c
@@ -43,6 +43,7 @@ static void exec_fuzz_one(FILE *f, FDSet *fdset) {
};
exec_context_init(&exec_context);
+ exec_context.private_var_tmp = PRIVATE_TMP_DISCONNECTED;
cgroup_context_init(&cgroup_context);
(void) exec_deserialize_invocation(f, fdset, &exec_context, &command, ¶ms, &runtime, &cgroup_context);
diff --git a/src/core/mount.c b/src/core/mount.c
index c496375a21..d7211de024 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1795,10 +1795,6 @@ static int mount_setup_new_unit(
* attributes the deps to. */
mnt->from_proc_self_mountinfo = true;
- r = mount_add_non_exec_dependencies(mnt);
- if (r < 0)
- return r;
-
/* We have only allocated the stub now, let's enqueue this unit for loading now, so that everything
* else is loaded in now. */
unit_add_to_load_queue(u);
@@ -1855,16 +1851,18 @@ static int mount_setup_existing_unit(
if (UNIT_IS_LOAD_ERROR(u->load_state)) {
/* The unit was previously not found or otherwise not loaded. Now that the unit shows up in
- * /proc/self/mountinfo we should reconsider it this, hence set it to UNIT_LOADED. */
- u->load_state = UNIT_LOADED;
+ * /proc/self/mountinfo we should reconsider that. Hence, let's reset the load state and load
+ * error, and add the unit to load queue. */
+ u->load_state = UNIT_STUB;
u->load_error = 0;
+ unit_add_to_load_queue(u);
flags |= MOUNT_PROC_JUST_CHANGED;
}
- if (FLAGS_SET(flags, MOUNT_PROC_JUST_CHANGED)) {
- /* If things changed, then make sure that all deps are regenerated. Let's
- * first remove all automatic deps, and then add in the new ones. */
+ if (FLAGS_SET(flags, MOUNT_PROC_JUST_CHANGED) && u->load_state == UNIT_LOADED) {
+ /* If things changed, and we have successfully loaded the unit, then make sure that all deps
+ * are regenerated. Let's first remove all automatic deps, and then add in the new ones. */
r = mount_add_non_exec_dependencies(m);
if (r < 0)
return r;
diff --git a/src/core/namespace.c b/src/core/namespace.c
index 381de7528a..d80fe74afb 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -731,6 +731,102 @@ static int append_tmpfs_mounts(MountList *ml, const TemporaryFileSystem *tmpfs,
return 0;
}
+static int append_private_tmp(MountList *ml, const NamespaceParameters *p) {
+ MountEntry *me;
+
+ assert(ml);
+ assert(p);
+ assert(p->private_tmp == p->private_var_tmp ||
+ (p->private_tmp == PRIVATE_TMP_DISCONNECTED && p->private_var_tmp == PRIVATE_TMP_NO));
+
+ if (p->tmp_dir) {
+ assert(p->private_tmp == PRIVATE_TMP_CONNECTED);
+
+ me = mount_list_extend(ml);
+ if (!me)
+ return log_oom_debug();
+ *me = (MountEntry) {
+ .path_const = "/tmp/",
+ .mode = MOUNT_PRIVATE_TMP,
+ .read_only = streq(p->tmp_dir, RUN_SYSTEMD_EMPTY),
+ .source_const = p->tmp_dir,
+ };
+ }
+
+ if (p->var_tmp_dir) {
+ assert(p->private_var_tmp == PRIVATE_TMP_CONNECTED);
+
+ me = mount_list_extend(ml);
+ if (!me)
+ return log_oom_debug();
+ *me = (MountEntry) {
+ .path_const = "/var/tmp/",
+ .mode = MOUNT_PRIVATE_TMP,
+ .read_only = streq(p->var_tmp_dir, RUN_SYSTEMD_EMPTY),
+ .source_const = p->var_tmp_dir,
+ };
+ }
+
+ if (p->private_tmp != PRIVATE_TMP_DISCONNECTED)
+ return 0;
+
+ if (p->private_var_tmp == PRIVATE_TMP_NO) {
+ me = mount_list_extend(ml);
+ if (!me)
+ return log_oom_debug();
+ *me = (MountEntry) {
+ .path_const = "/tmp/",
+ .mode = MOUNT_PRIVATE_TMPFS,
+ .options_const = "mode=0700" NESTED_TMPFS_LIMITS,
+ .flags = MS_NODEV|MS_STRICTATIME,
+ };
+
+ return 0;
+ }
+
+ _cleanup_free_ char *tmpfs_dir = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL;
+ tmpfs_dir = path_join(p->private_namespace_dir, "unit-private-tmp");
+ tmp_dir = path_join(tmpfs_dir, "tmp");
+ var_tmp_dir = path_join(tmpfs_dir, "var-tmp");
+ if (!tmpfs_dir || !tmp_dir || !var_tmp_dir)
+ return log_oom_debug();
+
+ me = mount_list_extend(ml);
+ if (!me)
+ return log_oom_debug();
+ *me = (MountEntry) {
+ .path_malloc = TAKE_PTR(tmpfs_dir),
+ .mode = MOUNT_PRIVATE_TMPFS,
+ .options_const = "mode=0700" NESTED_TMPFS_LIMITS,
+ .flags = MS_NODEV|MS_STRICTATIME,
+ .has_prefix = true,
+ };
+
+ me = mount_list_extend(ml);
+ if (!me)
+ return log_oom_debug();
+ *me = (MountEntry) {
+ .source_malloc = TAKE_PTR(tmp_dir),
+ .path_const = "/tmp/",
+ .mode = MOUNT_BIND,
+ .source_dir_mode = 01777,
+ .create_source_dir = true,
+ };
+
+ me = mount_list_extend(ml);
+ if (!me)
+ return log_oom_debug();
+ *me = (MountEntry) {
+ .source_malloc = TAKE_PTR(var_tmp_dir),
+ .path_const = "/var/tmp/",
+ .mode = MOUNT_BIND,
+ .source_dir_mode = 01777,
+ .create_source_dir = true,
+ };
+
+ return 0;
+}
+
static int append_static_mounts(MountList *ml, const MountEntry *mounts, size_t n, bool ignore_protect) {
assert(ml);
assert(mounts || n == 0);
@@ -1907,6 +2003,11 @@ static int apply_one_mount(
return 1;
}
+static bool should_propagate_to_submounts(const MountEntry *m) {
+ assert(m);
+ return !IN_SET(m->mode, MOUNT_EMPTY_DIR, MOUNT_TMPFS, MOUNT_PRIVATE_TMPFS);
+}
+
static int make_read_only(const MountEntry *m, char **deny_list, FILE *proc_self_mountinfo) {
unsigned long new_flags = 0, flags_mask = 0;
bool submounts;
@@ -1935,9 +2036,7 @@ static int make_read_only(const MountEntry *m, char **deny_list, FILE *proc_self
* nothing further down. Set /dev readonly, but not submounts like /dev/shm. Also, we only set the
* per-mount read-only flag. We can't set it on the superblock, if we are inside a user namespace
* and running Linux <= 4.17. */
- submounts =
- mount_entry_read_only(m) &&
- !IN_SET(m->mode, MOUNT_EMPTY_DIR, MOUNT_TMPFS, MOUNT_PRIVATE_TMPFS);
+ submounts = mount_entry_read_only(m) && should_propagate_to_submounts(m);
if (submounts)
r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, deny_list, proc_self_mountinfo);
else
@@ -1977,8 +2076,7 @@ static int make_noexec(const MountEntry *m, char **deny_list, FILE *proc_self_mo
if (flags_mask == 0) /* No Change? */
return 0;
- submounts = !IN_SET(m->mode, MOUNT_EMPTY_DIR, MOUNT_TMPFS, MOUNT_PRIVATE_TMPFS);
-
+ submounts = should_propagate_to_submounts(m);
if (submounts)
r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, deny_list, proc_self_mountinfo);
else
@@ -2002,7 +2100,7 @@ static int make_nosuid(const MountEntry *m, FILE *proc_self_mountinfo) {
if (m->state != MOUNT_APPLIED)
return 0;
- submounts = !IN_SET(m->mode, MOUNT_EMPTY_DIR, MOUNT_TMPFS, MOUNT_PRIVATE_TMPFS);
+ submounts = should_propagate_to_submounts(m);
if (submounts)
r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), MS_NOSUID, MS_NOSUID, NULL, proc_self_mountinfo);
else
@@ -2450,80 +2548,9 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
if (r < 0)
return r;
- /* When DynamicUser=yes enforce that /tmp/ and /var/tmp/ are disconnected from the host's
- * directories, as they are world writable and ephemeral uid/gid will be used. */
- if (p->private_tmp == PRIVATE_TMP_DISCONNECTED) {
- _cleanup_free_ char *tmpfs_dir = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL;
- MountEntry *tmpfs_entry, *tmp_entry, *var_tmp_entry;
-
- tmpfs_dir = path_join(p->private_namespace_dir, "unit-private-tmp");
- tmp_dir = path_join(tmpfs_dir, "tmp");
- var_tmp_dir = path_join(tmpfs_dir, "var-tmp");
- if (!tmpfs_dir || !tmp_dir || !var_tmp_dir)
- return log_oom_debug();
-
- tmpfs_entry = mount_list_extend(&ml);
- if (!tmpfs_entry)
- return log_oom_debug();
- *tmpfs_entry = (MountEntry) {
- .path_malloc = TAKE_PTR(tmpfs_dir),
- .mode = MOUNT_PRIVATE_TMPFS,
- .options_const = "mode=0700" NESTED_TMPFS_LIMITS,
- .flags = MS_NODEV|MS_STRICTATIME,
- .has_prefix = true,
- };
-
- tmp_entry = mount_list_extend(&ml);
- if (!tmp_entry)
- return log_oom_debug();
- *tmp_entry = (MountEntry) {
- .source_malloc = TAKE_PTR(tmp_dir),
- .path_const = "/tmp",
- .mode = MOUNT_BIND,
- .source_dir_mode = 01777,
- .create_source_dir = true,
- };
-
- var_tmp_entry = mount_list_extend(&ml);
- if (!var_tmp_entry)
- return log_oom_debug();
- *var_tmp_entry = (MountEntry) {
- .source_malloc = TAKE_PTR(var_tmp_dir),
- .path_const = "/var/tmp",
- .mode = MOUNT_BIND,
- .source_dir_mode = 01777,
- .create_source_dir = true,
- };
-
- } else if (p->tmp_dir || p->var_tmp_dir) {
- assert(p->private_tmp == PRIVATE_TMP_CONNECTED);
-
- if (p->tmp_dir) {
- MountEntry *me = mount_list_extend(&ml);
- if (!me)
- return log_oom_debug();
-
- *me = (MountEntry) {
- .path_const = "/tmp",
- .mode = MOUNT_PRIVATE_TMP,
- .read_only = streq(p->tmp_dir, RUN_SYSTEMD_EMPTY),
- .source_const = p->tmp_dir,
- };
- }
-
- if (p->var_tmp_dir) {
- MountEntry *me = mount_list_extend(&ml);
- if (!me)
- return log_oom_debug();
-
- *me = (MountEntry) {
- .path_const = "/var/tmp",
- .mode = MOUNT_PRIVATE_TMP,
- .read_only = streq(p->var_tmp_dir, RUN_SYSTEMD_EMPTY),
- .source_const = p->var_tmp_dir,
- };
- }
- }
+ r = append_private_tmp(&ml, p);
+ if (r < 0)
+ return r;
r = append_mount_images(&ml, p->mount_images, p->n_mount_images);
if (r < 0)
diff --git a/src/core/namespace.h b/src/core/namespace.h
index 0ff9b4d730..f1076d0ee4 100644
--- a/src/core/namespace.h
+++ b/src/core/namespace.h
@@ -199,6 +199,7 @@ struct NamespaceParameters {
ProtectProc protect_proc;
ProcSubset proc_subset;
PrivateTmp private_tmp;
+ PrivateTmp private_var_tmp;
PrivatePIDs private_pids;
};
diff --git a/src/core/swap.c b/src/core/swap.c
index 6d505c2acf..668e1bcf6c 100644
--- a/src/core/swap.c
+++ b/src/core/swap.c
@@ -406,8 +406,6 @@ static int swap_setup_unit(
s->what = strdup(what);
if (!s->what)
return log_oom();
-
- unit_add_to_load_queue(u);
}
SwapParameters *p = &s->parameters_proc_swaps;
@@ -418,13 +416,15 @@ static int swap_setup_unit(
return log_oom();
}
- /* The unit is definitely around now, mark it as loaded if it was previously referenced but
- * could not be loaded. After all we can load it now, from the data in /proc/swaps. */
+ /* The unit is definitely around now. When we previously fail to load the unit, let's reload the unit
+ * by resetting the load state and load error, and adding the unit to the load queue. */
if (UNIT_IS_LOAD_ERROR(u->load_state)) {
- u->load_state = UNIT_LOADED;
+ u->load_state = UNIT_STUB;
u->load_error = 0;
}
+ unit_add_to_load_queue(u);
+
if (set_flags) {
s->is_active = true;
s->just_activated = !s->from_proc_swaps;
diff --git a/src/core/unit.c b/src/core/unit.c
index c706caae08..7e3eaa4c92 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -1270,18 +1270,30 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
return r;
}
+ /* This must be already set in unit_patch_contexts(). */
+ assert(c->private_var_tmp >= 0 && c->private_var_tmp < _PRIVATE_TMP_MAX);
+
if (c->private_tmp == PRIVATE_TMP_CONNECTED) {
- r = unit_add_mounts_for(u, "/tmp", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS);
+ assert(c->private_var_tmp == PRIVATE_TMP_CONNECTED);
+
+ r = unit_add_mounts_for(u, "/tmp/", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS);
if (r < 0)
return r;
- r = unit_add_mounts_for(u, "/var/tmp", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS);
+ r = unit_add_mounts_for(u, "/var/tmp/", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS);
if (r < 0)
return r;
r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_TMPFILES_SETUP_SERVICE, true, UNIT_DEPENDENCY_FILE);
if (r < 0)
return r;
+
+ } else if (c->private_var_tmp == PRIVATE_TMP_DISCONNECTED && !exec_context_with_rootfs(c)) {
+ /* Even if PrivateTmp=disconnected, we still require /var/tmp/ mountpoint to be present,
+ * i.e. /var/ needs to be mounted. See comments in unit_patch_contexts(). */
+ r = unit_add_mounts_for(u, "/var/", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS);
+ if (r < 0)
+ return r;
}
if (c->root_image) {
@@ -1293,18 +1305,8 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
return r;
}
- if (!IN_SET(c->std_output,
- EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE,
- EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE) &&
- !IN_SET(c->std_error,
- EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE,
- EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE) &&
- !c->log_namespace)
- return 0;
-
/* If syslog or kernel logging is requested (or log namespacing is), make sure our own logging daemon
* is run first. */
-
if (c->log_namespace) {
static const struct {
const char *template;
@@ -1326,7 +1328,11 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
if (r < 0)
return r;
}
- } else {
+ } else if (IN_SET(c->std_output, EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE,
+ EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE) ||
+ IN_SET(c->std_error, EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE,
+ EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE)) {
+
r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, true, UNIT_DEPENDENCY_FILE);
if (r < 0)
return r;
@@ -4256,6 +4262,51 @@ static int unit_verify_contexts(const Unit *u) {
return 0;
}
+static PrivateTmp unit_get_private_var_tmp(const Unit *u, const ExecContext *c) {
+ assert(u);
+ assert(c);
+ assert(c->private_tmp >= 0 && c->private_tmp < _PRIVATE_TMP_MAX);
+
+ /* Disable disconnected private tmpfs on /var/tmp/ when DefaultDependencies=no and
+ * RootImage=/RootDirectory= are not set, as /var/ may be a separated partition.
+ * See issue #37258. */
+
+ /* PrivateTmp=yes/no also enables/disables private tmpfs on /var/tmp/. */
+ if (c->private_tmp != PRIVATE_TMP_DISCONNECTED)
+ return c->private_tmp;
+
+ /* When DefaultDependencies=yes, disconnected tmpfs is also enabled on /var/tmp/, and an explicit
+ * dependency to the mount on /var/ will be added in unit_add_exec_dependencies(). */
+ if (u->default_dependencies)
+ return PRIVATE_TMP_DISCONNECTED;
+
+ /* When RootImage=/RootDirectory= is enabled, /var/ should be prepared by the image or directory,
+ * hence we can mount a disconnected tmpfs on /var/tmp/. */
+ if (exec_context_with_rootfs(c))
+ return PRIVATE_TMP_DISCONNECTED;
+
+ /* Even if DefaultDependencies=no, enable disconnected tmpfs when
+ * RequiresMountsFor=/WantsMountsFor=/var/ is explicitly set. */
+ for (UnitMountDependencyType t = 0; t < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX; t++)
+ if (hashmap_contains(u->mounts_for[t], "/var/"))
+ return PRIVATE_TMP_DISCONNECTED;
+
+ /* Check the same but for After= with Requires=/Requisite=/Wants= or friends. */
+ Unit *m = manager_get_unit(u->manager, "var.mount");
+ if (!m)
+ return PRIVATE_TMP_NO;
+
+ if (!unit_has_dependency(u, UNIT_ATOM_AFTER, m))
+ return PRIVATE_TMP_NO;
+
+ if (unit_has_dependency(u, UNIT_ATOM_PULL_IN_START, m) ||
+ unit_has_dependency(u, UNIT_ATOM_PULL_IN_VERIFY, m) ||
+ unit_has_dependency(u, UNIT_ATOM_PULL_IN_START_IGNORED, m))
+ return PRIVATE_TMP_DISCONNECTED;
+
+ return PRIVATE_TMP_NO;
+}
+
int unit_patch_contexts(Unit *u) {
CGroupContext *cc;
ExecContext *ec;
@@ -4332,6 +4383,8 @@ int unit_patch_contexts(Unit *u) {
ec->restrict_suid_sgid = true;
}
+ ec->private_var_tmp = unit_get_private_var_tmp(u, ec);
+
FOREACH_ARRAY(d, ec->directories, _EXEC_DIRECTORY_TYPE_MAX)
exec_directory_sort(d);
}
diff --git a/src/test/test-bpf-firewall.c b/src/test/test-bpf-firewall.c
index 8ba3a888ec..8f3c7b41d8 100644
--- a/src/test/test-bpf-firewall.c
+++ b/src/test/test-bpf-firewall.c
@@ -168,6 +168,7 @@ int main(int argc, char *argv[]) {
ASSERT_OK(r);
+ ASSERT_OK(unit_patch_contexts(u));
ASSERT_OK(unit_start(u, NULL));
while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED))
@@ -193,6 +194,7 @@ int main(int argc, char *argv[]) {
SERVICE(u)->type = SERVICE_ONESHOT;
u->load_state = UNIT_LOADED;
+ ASSERT_OK(unit_patch_contexts(u));
ASSERT_OK(unit_start(u, NULL));
while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED))
diff --git a/src/test/test-bpf-foreign-programs.c b/src/test/test-bpf-foreign-programs.c
index 337243972c..1b1f1762c9 100644
--- a/src/test/test-bpf-foreign-programs.c
+++ b/src/test/test-bpf-foreign-programs.c
@@ -246,6 +246,7 @@ static int test_bpf_cgroup_programs(Manager *m, const char *unit_name, const Tes
SERVICE(u)->type = SERVICE_ONESHOT;
u->load_state = UNIT_LOADED;
+ ASSERT_OK(unit_patch_contexts(u));
r = unit_start(u, NULL);
if (r < 0)
return log_error_errno(r, "Unit start failed %m");
diff --git a/src/test/test-bpf-restrict-fs.c b/src/test/test-bpf-restrict-fs.c
index 31fdd7f0e2..522bb010e7 100644
--- a/src/test/test-bpf-restrict-fs.c
+++ b/src/test/test-bpf-restrict-fs.c
@@ -39,6 +39,7 @@ static int test_restrict_filesystems(Manager *m, const char *unit_name, const ch
SERVICE(u)->type = SERVICE_ONESHOT;
u->load_state = UNIT_LOADED;
+ ASSERT_OK(unit_patch_contexts(u));
r = unit_start(u, NULL);
if (r < 0)
return log_error_errno(r, "Unit start failed %m");
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
index 5ba680d571..1eddf11dd1 100644
--- a/src/test/test-execute.c
+++ b/src/test/test-execute.c
@@ -491,6 +491,20 @@ static void test_exec_privatetmp(Manager *m) {
if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) {
test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
test(m, "exec-privatetmp-disabled-by-prefix.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
+
+ FOREACH_STRING(s,
+ "exec-privatetmp-disconnected.service",
+ "exec-privatetmp-disconnected-defaultdependencies-no.service",
+ "exec-privatetmp-disconnected-requires-mounts-for-var.service",
+ "exec-privatetmp-disconnected-wants-mounts-for-var.service",
+ "exec-privatetmp-disconnected-after-and-requires-for-var.service",
+ "exec-privatetmp-disconnected-after-and-wants-for-var.service") {
+ (void) unlink("/tmp/test-exec_privatetmp_disconnected");
+ (void) unlink("/var/tmp/test-exec_privatetmp_disconnected");
+ test(m, s, can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED);
+ ASSERT_FAIL(access("/tmp/test-exec_privatetmp_disconnected", F_OK));
+ ASSERT_FAIL(access("/var/tmp/test-exec_privatetmp_disconnected", F_OK));
+ }
}
test(m, "exec-privatetmp-no.service", 0, CLD_EXITED);
diff --git a/src/test/test-socket-bind.c b/src/test/test-socket-bind.c
index 63552013b2..d0e448daa1 100644
--- a/src/test/test-socket-bind.c
+++ b/src/test/test-socket-bind.c
@@ -76,6 +76,7 @@ static int test_socket_bind(
SERVICE(u)->type = SERVICE_ONESHOT;
u->load_state = UNIT_LOADED;
+ ASSERT_OK(unit_patch_contexts(u));
r = unit_start(u, NULL);
if (r < 0)
return log_error_errno(r, "Unit start failed %m");
diff --git a/test/test-execute/exec-privatetmp-disconnected-after-and-requires-for-var.service b/test/test-execute/exec-privatetmp-disconnected-after-and-requires-for-var.service
new file mode 100644
index 0000000000..39e49c698a
--- /dev/null
+++ b/test/test-execute/exec-privatetmp-disconnected-after-and-requires-for-var.service
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for PrivateTmp=disconnected with After=/Requires=var.mount
+DefaultDependencies=no
+After=var.mount
+Requires=var.mount
+
+[Service]
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=touch /tmp/test-exec_privatetmp_disconnected
+ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected
+ExecStart=sh -x -c 'test x$$TMPDIR = x'
+Type=oneshot
+ProtectSystem=strict
+PrivateTmp=disconnected
diff --git a/test/test-execute/exec-privatetmp-disconnected-after-and-wants-for-var.service b/test/test-execute/exec-privatetmp-disconnected-after-and-wants-for-var.service
new file mode 100644
index 0000000000..b11302f5f2
--- /dev/null
+++ b/test/test-execute/exec-privatetmp-disconnected-after-and-wants-for-var.service
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for PrivateTmp=disconnected with After=/Wants=var.mount
+DefaultDependencies=no
+After=var.mount
+Wants=var.mount
+
+[Service]
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=touch /tmp/test-exec_privatetmp_disconnected
+ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected
+ExecStart=sh -x -c 'test x$$TMPDIR = x'
+Type=oneshot
+ProtectSystem=strict
+PrivateTmp=disconnected
diff --git a/test/test-execute/exec-privatetmp-disconnected-defaultdependencies-no.service b/test/test-execute/exec-privatetmp-disconnected-defaultdependencies-no.service
new file mode 100644
index 0000000000..2208b8e1b5
--- /dev/null
+++ b/test/test-execute/exec-privatetmp-disconnected-defaultdependencies-no.service
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for PrivateTmp=disconnected with DefaultDependencies=no
+DefaultDependencies=no
+
+[Service]
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=touch /tmp/test-exec_privatetmp_disconnected
+ExecStart=sh -x -c '(! touch /var/tmp/test-exec_privatetmp_disconnected)'
+ExecStart=sh -x -c 'test $$TMPDIR = /tmp'
+Type=oneshot
+ProtectSystem=strict
+PrivateTmp=disconnected
diff --git a/test/test-execute/exec-privatetmp-disconnected-requires-mounts-for-var.service b/test/test-execute/exec-privatetmp-disconnected-requires-mounts-for-var.service
new file mode 100644
index 0000000000..349c137984
--- /dev/null
+++ b/test/test-execute/exec-privatetmp-disconnected-requires-mounts-for-var.service
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for PrivateTmp=disconnected with RequiresMountsFor=/var/
+DefaultDependencies=no
+RequiresMountsFor=/var/
+
+[Service]
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=touch /tmp/test-exec_privatetmp_disconnected
+ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected
+ExecStart=sh -x -c 'test x$$TMPDIR = x'
+Type=oneshot
+ProtectSystem=strict
+PrivateTmp=disconnected
diff --git a/test/test-execute/exec-privatetmp-disconnected-wants-mounts-for-var.service b/test/test-execute/exec-privatetmp-disconnected-wants-mounts-for-var.service
new file mode 100644
index 0000000000..09ac3495b8
--- /dev/null
+++ b/test/test-execute/exec-privatetmp-disconnected-wants-mounts-for-var.service
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for PrivateTmp=disconnected with WantsMountsFor=/var/
+DefaultDependencies=no
+WantsMountsFor=/var/
+
+[Service]
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=touch /tmp/test-exec_privatetmp_disconnected
+ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected
+ExecStart=sh -x -c 'test x$$TMPDIR = x'
+Type=oneshot
+ProtectSystem=strict
+PrivateTmp=disconnected
diff --git a/test/test-execute/exec-privatetmp-disconnected.service b/test/test-execute/exec-privatetmp-disconnected.service
new file mode 100644
index 0000000000..fbee568d74
--- /dev/null
+++ b/test/test-execute/exec-privatetmp-disconnected.service
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Test for PrivateTmp=disconnected
+
+[Service]
+ExecStart=sh -x -c 'test ! -f /tmp/test-exec_privatetmp'
+ExecStart=touch /tmp/test-exec_privatetmp_disconnected
+ExecStart=touch /var/tmp/test-exec_privatetmp_disconnected
+ExecStart=sh -x -c 'test x$$TMPDIR = x'
+Type=oneshot
+ProtectSystem=strict
+PrivateTmp=disconnected