core: expose PrivateTmp=disconnected

As discussed in https://github.com/systemd/systemd/pull/32724#discussion_r1638963071

I don't find the opposite reasoning particularly convincing.
We have ProtectHome=tmpfs and friends, and those can be
pretty much trivially implemented through TemporaryFileSystem=
too. The new logic brings many benefits, and is completely generic,
hence I see no reason not to expose it. We can even get more tests
for the code path if we make it public.
This commit is contained in:
Mike Yuan 2024-06-18 16:18:56 +02:00
parent d7f24848ba
commit 9d50d053f3
No known key found for this signature in database
GPG Key ID: 417471C0A40F58B3
6 changed files with 78 additions and 48 deletions

View File

@ -3207,6 +3207,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateTmp = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PrivateTmpEx = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateDevices = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectClock = ...;
@ -3816,6 +3818,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property PrivateTmp is not documented!-->
<!--property PrivateTmpEx is not documented!-->
<!--property PrivateDevices is not documented!-->
<!--property ProtectClock is not documented!-->
@ -4504,6 +4508,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmpEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateDevices"/>
<variablelist class="dbus-property" generated="True" extra-ref="ProtectClock"/>
@ -5326,6 +5332,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateTmp = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PrivateTmpEx = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateDevices = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectClock = ...;
@ -5949,6 +5957,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property PrivateTmp is not documented!-->
<!--property PrivateTmpEx is not documented!-->
<!--property PrivateDevices is not documented!-->
<!--property ProtectClock is not documented!-->
@ -6613,6 +6623,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmpEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateDevices"/>
<variablelist class="dbus-property" generated="True" extra-ref="ProtectClock"/>
@ -7299,6 +7311,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateTmp = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PrivateTmpEx = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateDevices = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectClock = ...;
@ -7848,6 +7862,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property PrivateTmp is not documented!-->
<!--property PrivateTmpEx is not documented!-->
<!--property PrivateDevices is not documented!-->
<!--property ProtectClock is not documented!-->
@ -8424,6 +8440,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmpEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateDevices"/>
<variablelist class="dbus-property" generated="True" extra-ref="ProtectClock"/>
@ -9233,6 +9251,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateTmp = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PrivateTmpEx = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateDevices = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectClock = ...;
@ -9768,6 +9788,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property PrivateTmp is not documented!-->
<!--property PrivateTmpEx is not documented!-->
<!--property PrivateDevices is not documented!-->
<!--property ProtectClock is not documented!-->
@ -10330,6 +10352,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmpEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateDevices"/>
<variablelist class="dbus-property" generated="True" extra-ref="ProtectClock"/>
@ -12074,8 +12098,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>MemoryZSwapWriteback</varname>,
<varname>ExecMainHandoffTimestampMonotonic</varname>, and
<varname>ExecMainHandoffTimestamp</varname> were added in version 256.</para>
<para><varname>StatusBusError</varname> and
<varname>StatusVarlinkError</varname> were added in version 257.</para>
<para><varname>StatusBusError</varname>,
<varname>StatusVarlinkError</varname>, and
<varname>PrivateTmpEx</varname> were added in version 257.</para>
</refsect2>
<refsect2>
<title>Socket Unit Objects</title>
@ -12112,6 +12137,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>EffectiveTasksMax</varname>,
<varname>MemoryZSwapWriteback</varname>, and
<varname>PassFileDescriptorsToExec</varname> were added in version 256.</para>
<para><varname>PrivateTmpEx</varname> was added in version 257.</para>
</refsect2>
<refsect2>
<title>Mount Unit Objects</title>
@ -12145,6 +12171,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>EffectiveMemoryMax</varname>,
<varname>EffectiveTasksMax</varname>, and
<varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
<para><varname>PrivateTmpEx</varname> was added in version 257.</para>
</refsect2>
<refsect2>
<title>Swap Unit Objects</title>
@ -12178,6 +12205,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>EffectiveMemoryMax</varname>,
<varname>EffectiveTasksMax</varname>, and
<varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
<para><varname>PrivateTmpEx</varname> was added in version 257.</para>
</refsect2>
<refsect2>
<title>Slice Unit Objects</title>

View File

@ -675,8 +675,8 @@
of IPC objects and temporary files created by the executed processes is bound to the runtime of the
service, and hence the lifetime of the dynamic user/group. Since <filename>/tmp/</filename> and
<filename>/var/tmp/</filename> are usually the only world-writable directories on a system, unless
<varname>PrivateTmp=</varname> is manually enabled, those directories will be placed on a private
tmpfs filesystem, as this ensures that a unit making use of dynamic user/group allocation cannot
<varname>PrivateTmp=</varname> is manually set to <literal>true</literal>, <literal>disconnected</literal>
would be implied. This ensures that a unit making use of dynamic user/group allocation cannot
leave files around after unit termination. Furthermore
<varname>NoNewPrivileges=</varname> and <varname>RestrictSUIDSGID=</varname> are implicitly enabled
(and cannot be disabled), to ensure that processes invoked cannot take benefit or create SUID/SGID
@ -1748,20 +1748,27 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting>
<varlistentry>
<term><varname>PrivateTmp=</varname></term>
<listitem><para>Takes a boolean argument. If true, sets up a new file system namespace for the
executed processes and mounts private <filename>/tmp/</filename> and <filename>/var/tmp/</filename>
directories inside it that are not shared by processes outside of the namespace. This 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 true, all temporary files
created by a service in these directories will be removed after the service is stopped. Defaults to
false. 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. Enabling this setting 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>. Moreover an
<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>
<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
<citerefentry><refentrytitle>systemd-tmpfiles-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
is added.</para>

View File

@ -58,6 +58,7 @@ static BUS_DEFINE_PROPERTY_GET(property_get_mount_apivfs, "b", ExecContext, exec
static BUS_DEFINE_PROPERTY_GET2(property_get_ioprio_class, "i", ExecContext, exec_context_get_effective_ioprio, ioprio_prio_class);
static BUS_DEFINE_PROPERTY_GET2(property_get_ioprio_priority, "i", ExecContext, exec_context_get_effective_ioprio, ioprio_prio_data);
static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_string, "s", NULL);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_tmp_ex, "s", PrivateTmp, private_tmp_to_string);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_level, "i", int, LOG_PRI);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_facility, "i", int, LOG_FAC);
static BUS_DEFINE_PROPERTY_GET(property_get_cpu_affinity_from_numa, "b", ExecContext, exec_context_get_cpu_affinity_from_numa);
@ -1078,6 +1079,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("ExecSearchPath", "as", NULL, offsetof(ExecContext, exec_search_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_propagation_flag), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateTmp", "b", property_get_private_tmp, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateTmpEx", "s", property_get_private_tmp_ex, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProtectClock", "b", bus_property_get_bool, offsetof(ExecContext, protect_clock), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProtectKernelTunables", "b", bus_property_get_bool, offsetof(ExecContext, protect_kernel_tunables), SD_BUS_VTABLE_PROPERTY_CONST),
@ -1769,6 +1771,26 @@ int bus_exec_context_set_transient_property(
(void) unit_write_settingf(u, flags, name, "%s=%s", name, yes_no(v));
}
return 1;
} else if (streq(name, "PrivateTmpEx")) {
const char *s;
PrivateTmp t;
r = sd_bus_message_read(message, "s", &s);
if (r < 0)
return r;
t = private_tmp_from_string(s);
if (t < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s setting: %s", name, s);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c->private_tmp = t;
(void) unit_write_settingf(u, flags, name, "PrivateTmp=%s",
private_tmp_to_string(c->private_tmp));
}
return 1;
}

View File

@ -119,7 +119,7 @@
{{type}}.BindPaths, config_parse_bind_paths, 0, offsetof({{type}}, exec_context)
{{type}}.BindReadOnlyPaths, config_parse_bind_paths, 0, offsetof({{type}}, exec_context)
{{type}}.TemporaryFileSystem, config_parse_temporary_filesystems, 0, offsetof({{type}}, exec_context)
{{type}}.PrivateTmp, config_parse_private_tmp, 0, offsetof({{type}}, exec_context)
{{type}}.PrivateTmp, config_parse_private_tmp, 0, offsetof({{type}}, exec_context.private_tmp)
{{type}}.PrivateDevices, config_parse_bool, 0, offsetof({{type}}, exec_context.private_devices)
{{type}}.ProtectKernelTunables, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_kernel_tunables)
{{type}}.ProtectKernelModules, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_kernel_modules)

View File

@ -133,6 +133,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGrou
DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode, "Failed to parse keyring mode");
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_proc, protect_proc, ProtectProc, "Failed to parse /proc/ protection mode");
DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset, "Failed to parse /proc/ subset mode");
DEFINE_CONFIG_PARSE_ENUM(config_parse_private_tmp, private_tmp, PrivateTmp, "Failed to parse private tmp value");
DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode, "Failed to parse utmp mode");
DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to parse job mode");
DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier");
@ -5199,34 +5200,6 @@ int config_parse_temporary_filesystems(
}
}
int config_parse_private_tmp(
const char* unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
ExecContext *c = ASSERT_PTR(data);
int r;
assert(filename);
assert(rvalue);
r = parse_boolean(rvalue);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse boolean value: %s ignoring", rvalue);
return 0;
}
c->private_tmp = r ? PRIVATE_TMP_CONNECTED : PRIVATE_TMP_OFF;
return 0;
}
int config_parse_bind_paths(
const char *unit,
const char *filename,

View File

@ -3157,4 +3157,4 @@ static const char* const private_tmp_table[_PRIVATE_TMP_MAX] = {
[PRIVATE_TMP_DISCONNECTED] = "disconnected",
};
DEFINE_STRING_TABLE_LOOKUP(private_tmp, PrivateTmp);
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(private_tmp, PrivateTmp, PRIVATE_TMP_CONNECTED);