1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

core: Add support for PrivateUsers=identity

This configures an indentity mapping similar to
systemd-nspawn --private-users=identity.
This commit is contained in:
Daan De Meyer 2024-09-09 12:25:28 +02:00
parent d8b4be38dd
commit fa693fdc7e
14 changed files with 191 additions and 48 deletions

View File

@ -3244,6 +3244,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateUsers = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PrivateUsersEx = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateMounts = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateIPC = ...;
@ -3861,6 +3863,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property PrivateUsers is not documented!-->
<!--property PrivateUsersEx is not documented!-->
<!--property PrivateMounts is not documented!-->
<!--property PrivateIPC is not documented!-->
@ -4557,6 +4561,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="PrivateUsers"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateUsersEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateMounts"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateIPC"/>
@ -5385,6 +5391,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateUsers = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PrivateUsersEx = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateMounts = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateIPC = ...;
@ -6014,6 +6022,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property PrivateUsers is not documented!-->
<!--property PrivateUsersEx is not documented!-->
<!--property PrivateMounts is not documented!-->
<!--property PrivateIPC is not documented!-->
@ -6684,6 +6694,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="PrivateUsers"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateUsersEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateMounts"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateIPC"/>
@ -7376,6 +7388,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateUsers = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PrivateUsersEx = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateMounts = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateIPC = ...;
@ -7931,6 +7945,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property PrivateUsers is not documented!-->
<!--property PrivateUsersEx is not documented!-->
<!--property PrivateMounts is not documented!-->
<!--property PrivateIPC is not documented!-->
@ -8513,6 +8529,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="PrivateUsers"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateUsersEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateMounts"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateIPC"/>
@ -9328,6 +9346,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateUsers = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s PrivateUsersEx = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateMounts = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateIPC = ...;
@ -9869,6 +9889,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property PrivateUsers is not documented!-->
<!--property PrivateUsersEx is not documented!-->
<!--property PrivateMounts is not documented!-->
<!--property PrivateIPC is not documented!-->
@ -10437,6 +10459,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="PrivateUsers"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateUsersEx"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateMounts"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateIPC"/>
@ -12173,8 +12197,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>StatusVarlinkError</varname>,
<varname>LiveMountResult</varname>,
<varname>PrivateTmpEx</varname>,
<varname>ImportCredentialEx</varname>, and
<varname>BindLogSockets</varname> were added in version 257.</para>
<varname>ImportCredentialEx</varname>,
<varname>BindLogSockets</varname>, and
<varname>PrivateUsersEx</varname> were added in version 257.</para>
</refsect2>
<refsect2>
<title>Socket Unit Objects</title>
@ -12212,8 +12237,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>MemoryZSwapWriteback</varname>, and
<varname>PassFileDescriptorsToExec</varname> were added in version 256.</para>
<para><varname>PrivateTmpEx</varname>,
<varname>ImportCredentialEx</varname>, and
<varname>BindLogSockets</varname> were added in version 257.</para>
<varname>ImportCredentialEx</varname>,
<varname>BindLogSockets</varname>, and
<varname>PrivateUsersEx</varname> were added in version 257.</para>
</refsect2>
<refsect2>
<title>Mount Unit Objects</title>
@ -12248,8 +12274,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>EffectiveTasksMax</varname>, and
<varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
<para><varname>PrivateTmpEx</varname>,
<varname>ImportCredentialEx</varname>, and
<varname>BindLogSockets</varname> were added in version 257.</para>
<varname>ImportCredentialEx</varname>,
<varname>BindLogSockets</varname>, and
<varname>PrivateUsersEx</varname> were added in version 257.</para>
</refsect2>
<refsect2>
<title>Swap Unit Objects</title>
@ -12284,8 +12311,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>EffectiveTasksMax</varname>, and
<varname>MemoryZSwapWriteback</varname> were added in version 256.</para>
<para><varname>PrivateTmpEx</varname>,
<varname>ImportCredentialEx</varname>, and
<varname>BindLogSockets</varname> were added in version 257.</para>
<varname>ImportCredentialEx</varname>,
<varname>BindLogSockets</varname>, and
<varname>PrivateUsersEx</varname> were added in version 257.</para>
</refsect2>
<refsect2>
<title>Slice Unit Objects</title>

View File

@ -1966,18 +1966,29 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting>
<varlistentry>
<term><varname>PrivateUsers=</varname></term>
<listitem><para>Takes a boolean argument. If true, sets up a new user namespace for the executed processes and
configures a minimal user and group mapping, that maps the <literal>root</literal> user and group as well as
the unit's own user and group to themselves and everything else to the <literal>nobody</literal> user and
group. This is useful to securely detach the user and group databases used by the unit from the rest of the
system, and thus to create an effective sandbox environment. All files, directories, processes, IPC objects and
other resources owned by users/groups not equaling <literal>root</literal> or the unit's own will stay visible
from within the unit but appear owned by the <literal>nobody</literal> user and group. If this mode is enabled,
all unit processes are run without privileges in the host user namespace (regardless if the unit's own
user/group is <literal>root</literal> or not). Specifically this means that the process will have zero process
capabilities on the host's user namespace, but full capabilities within the service's user namespace. Settings
such as <varname>CapabilityBoundingSet=</varname> will affect only the latter, and there's no way to acquire
additional capabilities in the host's user namespace. Defaults to off.</para>
<listitem><para>Takes a boolean argument or one of <literal>self</literal> or
<literal>identity</literal>. Defaults to off. If enabled, sets up a new user namespace for the
executed processes and configures a user and group mapping. If set to a true value or
<literal>self</literal>, a minimal user and group mapping is configured that maps the
<literal>root</literal> user and group as well as the unit's own user and group to themselves and
everything else to the <literal>nobody</literal> user and group. This is useful to securely detach
the user and group databases used by the unit from the rest of the system, and thus to create an
effective sandbox environment. All files, directories, processes, IPC objects and other resources
owned by users/groups not equaling <literal>root</literal> or the unit's own will stay visible from
within the unit but appear owned by the <literal>nobody</literal> user and group. </para>
<para>If the parameter is <literal>identity</literal>, user namespacing is set up with an identity
mapping for the first 65536 UIDs/GIDs. Any UIDs/GIDs above 65536 will be mapped to the
<literal>nobody</literal> user and group, respectively. While this does not provide UID/GID isolation,
since all UIDs/GIDs are chosen identically it does provide process capability isolation, and hence is
often a good choice if proper user namespacing with distinct UID maps is not appropriate.</para>
<para>If this mode is enabled, all unit processes are run without privileges in the host user
namespace (regardless if the unit's own user/group is <literal>root</literal> or not). Specifically
this means that the process will have zero process capabilities on the host's user namespace, but
full capabilities within the service's user namespace. Settings such as
<varname>CapabilityBoundingSet=</varname> will affect only the latter, and there's no way to acquire
additional capabilities in the host's user namespace.</para>
<para>When this setting is set up by a per-user instance of the service manager, the mapping of the
<literal>root</literal> user and group to itself is omitted (unless the user manager is root).

View File

@ -60,6 +60,7 @@ static BUS_DEFINE_PROPERTY_GET2(property_get_ioprio_class, "i", ExecContext, exe
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_private_users_ex, "s", PrivateUsers, private_users_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);
@ -1027,6 +1028,21 @@ static int property_get_private_tmp(
return sd_bus_message_append_basic(reply, 'b', &b);
}
static int property_get_private_users(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
PrivateUsers *p = ASSERT_PTR(userdata);
int b = *p != PRIVATE_USERS_OFF;
return sd_bus_message_append_basic(reply, 'b', &b);
}
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@ -1149,7 +1165,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("ProtectKernelLogs", "b", bus_property_get_bool, offsetof(ExecContext, protect_kernel_logs), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProtectControlGroups", "b", bus_property_get_bool, offsetof(ExecContext, protect_control_groups), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateUsers", "b", bus_property_get_bool, offsetof(ExecContext, private_users), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateUsers", "b", property_get_private_users, offsetof(ExecContext, private_users), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateUsersEx", "s", property_get_private_users_ex, offsetof(ExecContext, private_users), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateMounts", "b", bus_property_get_tristate, offsetof(ExecContext, private_mounts), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateIPC", "b", bus_property_get_bool, offsetof(ExecContext, private_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProtectHome", "s", property_get_protect_home, offsetof(ExecContext, protect_home), SD_BUS_VTABLE_PROPERTY_CONST),
@ -1857,6 +1874,41 @@ int bus_exec_context_set_transient_property(
return 1;
}
if (streq(name, "PrivateUsers")) {
int v;
r = sd_bus_message_read(message, "b", &v);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c->private_users = v ? PRIVATE_USERS_SELF : PRIVATE_USERS_OFF;
(void) unit_write_settingf(u, flags, name, "%s=%s", name, yes_no(v));
}
return 1;
} else if (streq(name, "PrivateUsersEx")) {
const char *s;
PrivateUsers t;
r = sd_bus_message_read(message, "s", &s);
if (r < 0)
return r;
t = private_users_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_users = t;
(void) unit_write_settingf(u, flags, name, "PrivateUsers=%s",
private_users_to_string(c->private_users));
}
return 1;
}
if (streq(name, "PrivateDevices"))
return bus_set_transient_bool(u, name, &c->private_devices, message, flags, error);
@ -1875,9 +1927,6 @@ int bus_exec_context_set_transient_property(
if (streq(name, "PrivateIPC"))
return bus_set_transient_bool(u, name, &c->private_ipc, message, flags, error);
if (streq(name, "PrivateUsers"))
return bus_set_transient_bool(u, name, &c->private_users, message, flags, error);
if (streq(name, "NoNewPrivileges"))
return bus_set_transient_bool(u, name, &c->no_new_privileges, message, flags, error);

View File

@ -2077,7 +2077,7 @@ static int build_pass_environment(const ExecContext *c, char ***ret) {
return 0;
}
static int setup_private_users(uid_t ouid, gid_t ogid, uid_t uid, gid_t gid) {
static int setup_private_users(PrivateUsers private_users, uid_t ouid, gid_t ogid, uid_t uid, gid_t gid) {
_cleanup_free_ char *uid_map = NULL, *gid_map = NULL;
_cleanup_close_pair_ int errno_pipe[2] = EBADF_PAIR;
_cleanup_close_ int unshare_ready_fd = -EBADF;
@ -2096,31 +2096,48 @@ static int setup_private_users(uid_t ouid, gid_t ogid, uid_t uid, gid_t gid) {
* For unprivileged users (i.e. without capabilities), the root to root mapping is excluded. As such, it
* does not need CAP_SETUID to write the single line mapping to itself. */
if (private_users == PRIVATE_USERS_OFF)
return 0;
if (private_users == PRIVATE_USERS_IDENTITY) {
uid_map = strdup("0 0 65536\n");
if (!uid_map)
return -ENOMEM;
/* Can only set up multiple mappings with CAP_SETUID. */
if (have_effective_cap(CAP_SETUID) > 0 && uid != ouid && uid_is_valid(uid))
} else if (have_effective_cap(CAP_SETUID) > 0 && uid != ouid && uid_is_valid(uid)) {
r = asprintf(&uid_map,
UID_FMT " " UID_FMT " 1\n" /* Map $OUID → $OUID */
UID_FMT " " UID_FMT " 1\n", /* Map $UID → $UID */
ouid, ouid, uid, uid);
else
if (r < 0)
return -ENOMEM;
} else {
r = asprintf(&uid_map,
UID_FMT " " UID_FMT " 1\n", /* Map $OUID → $OUID */
ouid, ouid);
if (r < 0)
return -ENOMEM;
if (r < 0)
return -ENOMEM;
}
if (private_users == PRIVATE_USERS_IDENTITY) {
gid_map = strdup("0 0 65536\n");
if (!gid_map)
return -ENOMEM;
/* Can only set up multiple mappings with CAP_SETGID. */
if (have_effective_cap(CAP_SETGID) > 0 && gid != ogid && gid_is_valid(gid))
} else if (have_effective_cap(CAP_SETGID) > 0 && gid != ogid && gid_is_valid(gid)) {
r = asprintf(&gid_map,
GID_FMT " " GID_FMT " 1\n" /* Map $OGID → $OGID */
GID_FMT " " GID_FMT " 1\n", /* Map $GID → $GID */
ogid, ogid, gid, gid);
else
if (r < 0)
return -ENOMEM;
} else {
r = asprintf(&gid_map,
GID_FMT " " GID_FMT " 1\n", /* Map $OGID -> $OGID */
ogid, ogid);
if (r < 0)
return -ENOMEM;
if (r < 0)
return -ENOMEM;
}
/* Create a communication channel so that the parent can tell the child when it finished creating the user
* namespace. */
@ -2231,7 +2248,7 @@ static int setup_private_users(uid_t ouid, gid_t ogid, uid_t uid, gid_t gid) {
if (r != EXIT_SUCCESS) /* If something strange happened with the child, let's consider this fatal, too */
return -EIO;
return 0;
return 1;
}
static int create_many_symlinks(const char *root, const char *source, char **symlinks) {
@ -3834,7 +3851,7 @@ static bool exec_context_need_unprivileged_private_users(
if (params->runtime_scope != RUNTIME_SCOPE_USER)
return false;
return context->private_users ||
return context->private_users != PRIVATE_USERS_OFF ||
context->private_tmp != PRIVATE_TMP_OFF ||
context->private_devices ||
context->private_network ||
@ -4743,18 +4760,23 @@ int exec_invoke(
/* If we're unprivileged, set up the user namespace first to enable use of the other namespaces.
* Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to
* set up all of the other namespaces (i.e. network, mount, UTS) without a user namespace. */
PrivateUsers pu = context->private_users;
if (pu == PRIVATE_USERS_OFF)
pu = PRIVATE_USERS_SELF;
r = setup_private_users(saved_uid, saved_gid, uid, gid);
r = setup_private_users(pu, saved_uid, saved_gid, uid, gid);
/* If it was requested explicitly and we can't set it up, fail early. Otherwise, continue and let
* the actual requested operations fail (or silently continue). */
if (r < 0 && context->private_users) {
if (r < 0 && context->private_users != PRIVATE_USERS_OFF) {
*exit_status = EXIT_USER;
return log_exec_error_errno(context, params, r, "Failed to set up user namespacing for unprivileged user: %m");
}
if (r < 0)
log_exec_info_errno(context, params, r, "Failed to set up user namespacing for unprivileged user, ignoring: %m");
else
else {
assert(r > 0);
userns_set_up = true;
}
}
if (exec_needs_network_namespace(context) && runtime && runtime->shared && runtime->shared->netns_storage_socket[0] >= 0) {
@ -4867,8 +4889,8 @@ int exec_invoke(
* case of mount namespaces being less privileged when the mount point list is copied from a
* different user namespace). */
if (needs_sandboxing && context->private_users && !userns_set_up) {
r = setup_private_users(saved_uid, saved_gid, uid, gid);
if (needs_sandboxing && !userns_set_up) {
r = setup_private_users(context->private_users, saved_uid, saved_gid, uid, gid);
if (r < 0) {
*exit_status = EXIT_USER;
return log_exec_error_errno(context, params, r, "Failed to set up user namespacing: %m");

View File

@ -1894,7 +1894,7 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
if (r < 0)
return r;
r = serialize_bool_elide(f, "exec-context-private-users", c->private_users);
r = serialize_item(f, "exec-context-private-users", private_users_to_string(c->private_users));
if (r < 0)
return r;
@ -2778,10 +2778,9 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
return r;
c->private_network = r;
} else if ((val = startswith(l, "exec-context-private-users="))) {
r = parse_boolean(val);
if (r < 0)
return r;
c->private_users = r;
c->private_users = private_users_from_string(val);
if (c->private_users < 0)
return -EINVAL;
} else if ((val = startswith(l, "exec-context-private-ipc="))) {
r = parse_boolean(val);
if (r < 0)

View File

@ -1002,7 +1002,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
prefix, yes_no(c->protect_clock),
prefix, yes_no(c->protect_control_groups),
prefix, yes_no(c->private_network),
prefix, yes_no(c->private_users),
prefix, private_users_to_string(c->private_users),
prefix, protect_home_to_string(c->protect_home),
prefix, protect_system_to_string(c->protect_system),
prefix, yes_no(exec_context_get_effective_mount_apivfs(c)),

View File

@ -318,7 +318,7 @@ struct ExecContext {
PrivateTmp private_tmp;
bool private_network;
bool private_devices;
bool private_users;
PrivateUsers private_users;
bool private_ipc;
bool protect_kernel_tunables;
bool protect_kernel_modules;

View File

@ -130,7 +130,7 @@
{{type}}.IPCNamespacePath, config_parse_unit_path_printf, 0, offsetof({{type}}, exec_context.ipc_namespace_path)
{{type}}.LogNamespace, config_parse_log_namespace, 0, offsetof({{type}}, exec_context)
{{type}}.PrivateNetwork, config_parse_bool, 0, offsetof({{type}}, exec_context.private_network)
{{type}}.PrivateUsers, config_parse_bool, 0, offsetof({{type}}, exec_context.private_users)
{{type}}.PrivateUsers, config_parse_private_users, 0, offsetof({{type}}, exec_context.private_users)
{{type}}.PrivateMounts, config_parse_tristate, 0, offsetof({{type}}, exec_context.private_mounts)
{{type}}.PrivateIPC, config_parse_bool, 0, offsetof({{type}}, exec_context.private_ipc)
{{type}}.ProtectSystem, config_parse_protect_system, 0, offsetof({{type}}, exec_context.protect_system)

View File

@ -134,6 +134,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_keyring_mode, exec_keyring_mode, Exec
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_proc, protect_proc, ProtectProc);
DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset);
DEFINE_CONFIG_PARSE_ENUM(config_parse_private_tmp, private_tmp, PrivateTmp);
DEFINE_CONFIG_PARSE_ENUM(config_parse_private_users, private_users, PrivateUsers);
DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess);

View File

@ -112,6 +112,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);
CONFIG_PARSER_PROTOTYPE(config_parse_private_tmp);
CONFIG_PARSER_PROTOTYPE(config_parse_private_users);
CONFIG_PARSER_PROTOTYPE(config_parse_cpu_quota);
CONFIG_PARSER_PROTOTYPE(config_parse_allowed_cpuset);
CONFIG_PARSER_PROTOTYPE(config_parse_protect_home);

View File

@ -3227,3 +3227,11 @@ static const char* const private_tmp_table[_PRIVATE_TMP_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(private_tmp, PrivateTmp, PRIVATE_TMP_CONNECTED);
static const char* const private_users_table[_PRIVATE_USERS_MAX] = {
[PRIVATE_USERS_OFF] = "off",
[PRIVATE_USERS_SELF] = "self",
[PRIVATE_USERS_IDENTITY] = "identity",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(private_users, PrivateUsers, PRIVATE_USERS_SELF);

View File

@ -61,6 +61,14 @@ typedef enum PrivateTmp {
_PRIVATE_TMP_INVALID = -EINVAL,
} PrivateTmp;
typedef enum PrivateUsers {
PRIVATE_USERS_OFF,
PRIVATE_USERS_SELF,
PRIVATE_USERS_IDENTITY,
_PRIVATE_USERS_MAX,
_PRIVATE_USERS_INVALID = -EINVAL,
} PrivateUsers;
struct BindMount {
char *source;
char *destination;
@ -199,6 +207,9 @@ ProcSubset proc_subset_from_string(const char *s) _pure_;
const char* private_tmp_to_string(PrivateTmp i) _const_;
PrivateTmp private_tmp_from_string(const char *s) _pure_;
const char* private_users_to_string(PrivateUsers i) _const_;
PrivateUsers private_users_from_string(const char *s) _pure_;
void bind_mount_free_many(BindMount *b, size_t n);
int bind_mount_add(BindMount **b, size_t *n, const BindMount *item);

View File

@ -1038,6 +1038,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ProtectSystem",
"ProtectHome",
"PrivateTmpEx",
"PrivateUsersEx",
"SELinuxContext",
"RootImage",
"RootVerity",

View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail
systemd-run -p PrivateUsers=yes --wait bash -c 'test "$(cat /proc/self/uid_map)" == " 0 0 1"'
systemd-run -p PrivateUsers=yes --wait bash -c 'test "$(cat /proc/self/gid_map)" == " 0 0 1"'
systemd-run -p PrivateUsersEx=self --wait bash -c 'test "$(cat /proc/self/uid_map)" == " 0 0 1"'
systemd-run -p PrivateUsersEx=self --wait bash -c 'test "$(cat /proc/self/gid_map)" == " 0 0 1"'
systemd-run -p PrivateUsersEx=identity --wait bash -c 'test "$(cat /proc/self/uid_map)" == " 0 0 65536"'
systemd-run -p PrivateUsersEx=identity --wait bash -c 'test "$(cat /proc/self/gid_map)" == " 0 0 65536"'