core/namespace: fix issue in PrivateTmp=disconnected vs var.mount (#37271)

Fixes #37258.
Replaces #37259.
This commit is contained in:
Yu Watanabe
2025-05-12 23:05:24 +09:00
committed by GitHub
22 changed files with 419 additions and 130 deletions
+100 -22
View File
@@ -61,13 +61,18 @@
paths. This is equivalent to having them listed explicitly in
<varname>RequiresMountsFor=</varname>.</para></listitem>
<listitem><para>Similarly, units with <varname>PrivateTmp=</varname> enabled automatically get mount
unit dependencies for all mounts required to access <filename>/tmp/</filename> and
<filename>/var/tmp/</filename>. They will also gain an automatic <varname>After=</varname> dependency
on
<listitem><para>Units with <varname>PrivateTmp=yes</varname> automatically gain dependencies of type
<varname>Wants=</varname> and <varname>After=</varname> on all mounts required to access
<filename>/tmp/</filename> and <filename>/var/tmp/</filename>. They will also gain an automatic
<varname>After=</varname> dependency on
<citerefentry><refentrytitle>systemd-tmpfiles-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
</para></listitem>
<listitem><para>Units with <varname>PrivateTmp=disconnected</varname> automatically gain dependencies
of type <varname>Wants=</varname> and <varname>After=</varname> on the mount required to access
<filename>/var/</filename>, unless <varname>DefaultDependencies=no</varname> and/or
<varname>RootDirectory=/RootImage=</varname> are specified.</para></listitem>
<listitem><para>Units whose standard output or error output is connected to <option>journal</option> or
<option>kmsg</option> (or their combinations with console output, see below) automatically acquire
dependencies of type <varname>After=</varname> on
@@ -1797,28 +1802,89 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting>
<listitem><para>Takes a boolean argument, or <literal>disconnected</literal>. If enabled, a new
file system namespace will be set up for the executed processes, and <filename>/tmp/</filename>
and <filename>/var/tmp/</filename> 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 <literal>true</literal>, the backing storage of the private temporary directories
will remain on the host's <filename>/tmp/</filename> and <filename>/var/tmp/</filename> directories.
If <literal>disconnected</literal>, 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.</para>
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 <varname>ReadOnlyPaths=</varname> and related calls, see above. This
setting is useful to secure access to temporary files of the process, but makes sharing between
processes via <filename>/tmp/</filename> or <filename>/var/tmp/</filename> impossible. If
<varname>DynamicUser=</varname> is enabled, <literal>disconnected</literal> is implied. Otherwise,
defaults to false.</para>
<para>This setting is useful to secure access to temporary files of the process, but makes sharing
between processes via <filename>/tmp/</filename> or <filename>/var/tmp/</filename> impossible.
If not set to <literal>disconnected</literal>, it is possible to run two or more units within
the same private <filename>/tmp/</filename> and <filename>/var/tmp/</filename> namespace by using
the <varname>JoinsNamespaceOf=</varname> directive, see
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. This setting is implied if <varname>DynamicUser=</varname> is set. For this setting,
the same restrictions regarding mount propagation and privileges apply as for
<varname>ReadOnlyPaths=</varname> and related calls, see above. If set to <literal>true</literal>
(as opposed to <literal>disconnected</literal>), this has the side effect of adding
<varname>Requires=</varname> and <varname>After=</varname> dependencies on all mount units necessary
to access <filename>/tmp/</filename> and <filename>/var/tmp/</filename> on the host. Moreover, an
implicitly <varname>After=</varname> ordering on
<para>If <literal>true</literal>, the backing storage of the private temporary directories will
remain on the host's <filename>/tmp/</filename> and <filename>/var/tmp/</filename> directories. It is
possible to run two or more units within the same private <filename>/tmp/</filename> and
<filename>/var/tmp/</filename> namespace by using the <varname>JoinsNamespaceOf=</varname> directive,
see <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. This has the side effect of adding <varname>Wants=</varname> and
<varname>After=</varname> dependencies on all mount units necessary to access
<filename>/tmp/</filename> and <filename>/var/tmp/</filename> on the host. Moreover, an implicit
<varname>After=</varname> ordering on
<citerefentry><refentrytitle>systemd-tmpfiles-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
is added.</para>
<para>If <literal>disconnected</literal>, 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 <varname>JoinsNamespaceOf=</varname> directive is used.
If <varname>DefaultDependencies=no</varname> is specified,
<varname>RequiresMountsFor=/WantsMountsFor=</varname> for <filename>/var/</filename> is not
specified, and <varname>RootDirectory=/RootImage=</varname> is not specified, then a new tmpfs is
mounted <emphasis>only</emphasis> on <filename>/tmp/</filename>, hence the host's
<filename>/var/tmp</filename> is still accessible from the unit. In that case,
<varname>$TMPDIR</varname> environment variable is set to <literal>/tmp</literal> to suggest the
processes in the unit to use <filename>/tmp/</filename>. This automatically adds
<varname>WantsMountsFor=/var/</varname> dependency, unless <varname>DefaultDependencies=no</varname>
and/or <varname>RootDirectory=/RootImage=</varname> are specified.</para>
<table>
<title>Summary for <varname>PrivateTmp=disconnected</varname></title>
<tgroup cols='4' align='left' colsep='1' rowsep='1'>
<colspec colname="other settings" />
<colspec colname="tmpfs instance on /var/tmp/" />
<colspec colname="$TMPVAR" />
<colspec colname="implied dependencies" />
<thead>
<row>
<entry>Other Settings</entry>
<entry>tmpfs on /var/tmp/</entry>
<entry>$TMPVAR</entry>
<entry>implied dependencies</entry>
</row>
</thead>
<tbody>
<row>
<entry>(none)</entry>
<entry>yes</entry>
<entry>(unset)</entry>
<entry><varname>WantsMountsFor=/var/</varname></entry>
</row>
<row>
<entry><varname>RootDirectory=/RootImage=</varname></entry>
<entry>yes</entry>
<entry>(unset)</entry>
<entry>(none)</entry>
</row>
<row>
<entry><varname>DefaultDependency=no</varname>, <varname>RequiresMountsFor=/var/</varname></entry>
<entry>yes</entry>
<entry>(unset)</entry>
<entry>(none)</entry>
</row>
<row>
<entry><varname>DefaultDependency=no</varname>, <varname>WantsMountsFor=/var/</varname></entry>
<entry>yes</entry>
<entry>(unset)</entry>
<entry>(none)</entry>
</row>
<row>
<entry><varname>DefaultDependency=no</varname></entry>
<entry>no</entry>
<entry><varname>$TMPDIR=/tmp</varname></entry>
<entry>(none)</entry>
</row>
</tbody>
</tgroup>
</table>
<para>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.</para>
@@ -3944,6 +4010,18 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
<xi:include href="version-info.xml" xpointer="v247"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>$TMPDIR</varname></term>
<listitem><para>The environment variable is set to <literal>/tmp</literal> when
<varname>PrivateTmp=disconnected</varname> specified with <varname>DefaultDependencies=no</varname>
and without <varname>RootDirectory=/RootImage=</varname> and
<varname>RequiresMountsFor=/WantsMountsFor=</varname> for <filename>/var/</filename>.
See explanation for <varname>PrivateTmp=</varname> above.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>$MAINPID</varname></term>
+17 -1
View File
@@ -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),
+10
View File
@@ -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)
+1
View File
@@ -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,
};
+2
View File
@@ -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;
+1
View File
@@ -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, &params, &runtime, &cgroup_context);
+7 -9
View File
@@ -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;
+107 -80
View File
@@ -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)
+1
View File
@@ -199,6 +199,7 @@ struct NamespaceParameters {
ProtectProc protect_proc;
ProcSubset proc_subset;
PrivateTmp private_tmp;
PrivateTmp private_var_tmp;
PrivatePIDs private_pids;
};
+5 -5
View File
@@ -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;
+66 -13
View File
@@ -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);
}
+2
View File
@@ -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))
+1
View File
@@ -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");
+1
View File
@@ -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");
+14
View File
@@ -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);
+1
View File
@@ -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");
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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