From 10e421f70889a672f984785393051e0471c0aeb2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 9 Sep 2024 12:25:28 +0200 Subject: [PATCH] core: Add support for PrivateUsers=identity This configures an indentity mapping similar to systemd-nspawn --private-users=identity. (cherry picked from commit fa693fdc7e17618958c505af4b2f39ecd1c3363e) --- man/org.freedesktop.systemd1.xml | 24 ++++++++++ man/systemd.exec.xml | 35 ++++++++++----- src/core/dbus-execute.c | 57 ++++++++++++++++++++++-- src/core/exec-invoke.c | 56 +++++++++++++++-------- src/core/execute-serialize.c | 9 ++-- src/core/execute.c | 2 +- src/core/execute.h | 2 +- src/core/load-fragment-gperf.gperf.in | 2 +- src/core/load-fragment.c | 1 + src/core/load-fragment.h | 1 + src/core/namespace.c | 8 ++++ src/core/namespace.h | 11 +++++ src/shared/bus-unit-util.c | 1 + test/units/TEST-07-PID1.private-users.sh | 12 +++++ 14 files changed, 179 insertions(+), 42 deletions(-) create mode 100755 test/units/TEST-07-PID1.private-users.sh diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index b0b45097e30a3..ce0757cf9cfa2 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -3221,6 +3221,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 = ...; @@ -3832,6 +3834,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4516,6 +4520,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -5338,6 +5344,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 = ...; @@ -5961,6 +5969,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6625,6 +6635,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -7311,6 +7323,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 = ...; @@ -7860,6 +7874,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8436,6 +8452,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -9245,6 +9263,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 = ...; @@ -9780,6 +9800,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -10342,6 +10364,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 21527f756d669..3cdb7ff70a09b 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -1941,18 +1941,29 @@ BindReadOnlyPaths=/var/lib/systemd PrivateUsers= - 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 root user and group as well as - the unit's own user and group to themselves and everything else to the nobody 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 root or the unit's own will stay visible - from within the unit but appear owned by the nobody 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 root 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 CapabilityBoundingSet= will affect only the latter, and there's no way to acquire - additional capabilities in the host's user namespace. Defaults to off. + Takes a boolean argument or one of self or + identity. 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 + self, a minimal user and group mapping is configured that maps the + root user and group as well as the unit's own user and group to themselves and + everything else to the nobody 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 root or the unit's own will stay visible from + within the unit but appear owned by the nobody user and group. + + If the parameter is identity, 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 + nobody 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. + + 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 root 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 + CapabilityBoundingSet= will affect only the latter, and there's no way to acquire + additional capabilities in the host's user namespace. When this setting is set up by a per-user instance of the service manager, the mapping of the root user and group to itself is omitted (unless the user manager is root). diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index b0d9402e53ca7..1aeba38df60a6 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -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_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); @@ -943,6 +944,21 @@ static int property_get_image_policy( return sd_bus_message_append(reply, "s", s); } +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), @@ -1063,7 +1079,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), @@ -1738,6 +1755,41 @@ int bus_exec_context_set_transient_property( if (streq(name, "PrivateTmp")) return bus_set_transient_bool(u, name, &c->private_tmp, message, flags, error); + 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); @@ -1753,9 +1805,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); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 5850a595f0475..1f4a9923df7e0 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2080,7 +2080,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; @@ -2099,33 +2099,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. */ @@ -2236,7 +2251,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) { @@ -3865,7 +3880,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 || context->private_devices || context->private_network || @@ -4720,18 +4735,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) { @@ -4844,8 +4864,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"); diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 41b31e9ae3f21..867a74419da98 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -1880,7 +1880,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; @@ -2772,10 +2772,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) diff --git a/src/core/execute.c b/src/core/execute.c index f74665fcd7b9a..c2241803b763a 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -973,7 +973,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)), diff --git a/src/core/execute.h b/src/core/execute.h index 107ae2524387c..603feac0d6027 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -317,7 +317,7 @@ struct ExecContext { bool 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; diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index df219d86dfeaa..213b36ec05eee 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -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) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index a1a116a439774..23b5a66742da7 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -132,6 +132,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_collect_mode, collect_mode, CollectMode, " DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy, "Failed to parse device policy"); 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_private_users, private_users, PrivateUsers, "Failed to parse private users mode"); DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset, "Failed to parse /proc/ subset mode"); 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"); diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 005b91510608f..b6741c9207ab0 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -113,6 +113,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_import_credential); 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_users); CONFIG_PARSER_PROTOTYPE(config_parse_cpu_quota); CONFIG_PARSER_PROTOTYPE(config_parse_allowed_cpuset); CONFIG_PARSER_PROTOTYPE(config_parse_protect_home); diff --git a/src/core/namespace.c b/src/core/namespace.c index b92bb012eee31..052a4452c12b7 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -3080,3 +3080,11 @@ static const char* const proc_subset_table[_PROC_SUBSET_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(proc_subset, ProcSubset); + +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); diff --git a/src/core/namespace.h b/src/core/namespace.h index 921716bf3ec93..d10d7f440ad8b 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -53,6 +53,14 @@ typedef enum ProcSubset { _PROC_SUBSET_INVALID = -EINVAL, } ProcSubset; +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; @@ -184,6 +192,9 @@ ProtectProc protect_proc_from_string(const char *s) _pure_; const char* proc_subset_to_string(ProcSubset i) _const_; ProcSubset proc_subset_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); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index da83422524fa4..ff24ac5dd40dc 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1037,6 +1037,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con "SyslogIdentifier", "ProtectSystem", "ProtectHome", + "PrivateUsersEx", "SELinuxContext", "RootImage", "RootVerity", diff --git a/test/units/TEST-07-PID1.private-users.sh b/test/units/TEST-07-PID1.private-users.sh new file mode 100755 index 0000000000000..2475b5d365d59 --- /dev/null +++ b/test/units/TEST-07-PID1.private-users.sh @@ -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"'