core/namespace: fix issue in PrivateTmp=disconnected vs var.mount (#37271)
Fixes #37258. Replaces #37259.
This commit is contained in:
+100
-22
@@ -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
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+7
-9
@@ -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
@@ -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)
|
||||
|
||||
@@ -199,6 +199,7 @@ struct NamespaceParameters {
|
||||
ProtectProc protect_proc;
|
||||
ProcSubset proc_subset;
|
||||
PrivateTmp private_tmp;
|
||||
PrivateTmp private_var_tmp;
|
||||
PrivatePIDs private_pids;
|
||||
};
|
||||
|
||||
|
||||
+5
-5
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user