@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s UserNamespacePath = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NetworkNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
<!--property MemoryKSM is not documented!-->
+ <!--property UserNamespacePath is not documented!-->
+
<!--property NetworkNamespacePath is not documented!-->
<!--property IPCNamespacePath is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryKSM"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="UserNamespacePath"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="NetworkNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s UserNamespacePath = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NetworkNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
<!--property MemoryKSM is not documented!-->
+ <!--property UserNamespacePath is not documented!-->
+
<!--property NetworkNamespacePath is not documented!-->
<!--property IPCNamespacePath is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryKSM"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="UserNamespacePath"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="NetworkNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s UserNamespacePath = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NetworkNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
<!--property MemoryKSM is not documented!-->
+ <!--property UserNamespacePath is not documented!-->
+
<!--property NetworkNamespacePath is not documented!-->
<!--property IPCNamespacePath is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryKSM"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="UserNamespacePath"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="NetworkNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly s UserNamespacePath = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s NetworkNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
<!--property MemoryKSM is not documented!-->
+ <!--property UserNamespacePath is not documented!-->
+
<!--property NetworkNamespacePath is not documented!-->
<!--property IPCNamespacePath is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="MemoryKSM"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="UserNamespacePath"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="NetworkNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
- <para><varname>OOMKills</varname>, and
+ <para><varname>UserNamespacePath</varname>,
+ <varname>OOMKills</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
</refsect2>
<refsect2>
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
<para><varname>OOMKills</varname>, and
+ <varname>UserNamespacePath</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
</refsect2>
<refsect2>
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
- <para><varname>OOMKills</varname>, and
+ <para><varname>UserNamespacePath</varname>,
+ <varname>OOMKills</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
</refsect2>
<refsect2>
<varname>LogsDirectoryQuotaUsage</varname>,
<varname>LogsDirectoryAccounting</varname>, and
<function>KillSubgroup()</function> were added in version 258.</para>
- <para><varname>OOMKills</varname>, and
+ <para><varname>UserNamespacePath</varname>,
+ <varname>OOMKills</varname>, and
<varname>ManagedOOMKills</varname> were added in 259.</para>
</refsect2>
<refsect2>
<xi:include href="system-or-user-ns.xml" xpointer="singular"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>UserNamespacePath=</varname></term>
+
+ <listitem><para>Takes an absolute file system path referring to a Linux user namespace
+ pseudo-file (i.e. a file like <filename>/proc/$PID/ns/user</filename> or a bind mount or symlink to
+ one). When set the invoked processes are added to the user namespace referenced by that path. The
+ path has to point to a valid namespace file at the moment the processes are forked off. If this
+ option is used <varname>PrivateUsers=</varname> has no effect.</para>
+
+ <para>This option is only available for system services.</para>
+
+ <xi:include href="version-info.xml" xpointer="v259"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>NetworkNamespacePath=</varname></term>
SD_BUS_PROPERTY("BPFDelegatePrograms", "s", property_get_bpf_delegate_programs, offsetof(ExecContext, bpf_delegate_programs), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("BPFDelegateAttachments", "s", property_get_bpf_delegate_attachments, offsetof(ExecContext, bpf_delegate_attachments), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MemoryKSM", "b", bus_property_get_tristate, offsetof(ExecContext, memory_ksm), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("UserNamespacePath", "s", NULL, offsetof(ExecContext, user_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, root_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
if (streq(name, "NetworkNamespacePath"))
return bus_set_transient_path(u, name, &c->network_namespace_path, message, flags, error);
+ if (streq(name, "UserNamespacePath"))
+ return bus_set_transient_path(u, name, &c->user_namespace_path, message, flags, error);
+
if (streq(name, "IPCNamespacePath"))
return bus_set_transient_path(u, name, &c->ipc_namespace_path, message, flags, error);
append_socket_pair(dont_close, &n_dont_close, runtime->ephemeral_storage_socket);
if (runtime->shared) {
+ append_socket_pair(dont_close, &n_dont_close, runtime->shared->userns_storage_socket);
append_socket_pair(dont_close, &n_dont_close, runtime->shared->netns_storage_socket);
append_socket_pair(dont_close, &n_dont_close, runtime->shared->ipcns_storage_socket);
}
context->private_tmp != PRIVATE_TMP_NO ||
context->private_devices ||
context->private_network ||
+ context->user_namespace_path ||
context->network_namespace_path ||
context->private_ipc ||
context->ipc_namespace_path ||
if (!shared)
return;
+ safe_close_pair(shared->userns_storage_socket);
safe_close_pair(shared->netns_storage_socket);
safe_close_pair(shared->ipcns_storage_socket);
}
}
}
+ if (context->user_namespace_path && runtime->shared && runtime->shared->userns_storage_socket[0] >= 0) {
+ r = open_shareable_ns_path(runtime->shared->userns_storage_socket, context->user_namespace_path, CLONE_NEWUSER);
+ if (r < 0) {
+ *exit_status = EXIT_NAMESPACE;
+ return log_error_errno(r, "Failed to open user namespace path %s: %m", context->user_namespace_path);
+ }
+ }
+
if (context->network_namespace_path && runtime->shared && runtime->shared->netns_storage_socket[0] >= 0) {
r = open_shareable_ns_path(runtime->shared->netns_storage_socket, context->network_namespace_path, CLONE_NEWNET);
if (r < 0) {
/* 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. */
+
+ if (context->user_namespace_path && runtime->shared->userns_storage_socket[0] >= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM), "UserNamespacePath= is configured, but user namespace setup not permitted");
+
PrivateUsers pu = exec_context_get_effective_private_users(context, params);
if (pu == PRIVATE_USERS_NO)
pu = PRIVATE_USERS_SELF;
* restricted by rules pertaining to combining user namespaces with other namespaces (e.g. in the
* case of mount namespaces being less privileged when the mount point list is copied from a
* different user namespace). */
+ if (needs_sandboxing && context->user_namespace_path && runtime->shared && runtime->shared->userns_storage_socket[0] >= 0) {
+ if (!namespace_type_supported(NAMESPACE_USER))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "UserNamespacePath= is not supported, refusing.");
+
+ r = setup_shareable_ns(runtime->shared->userns_storage_socket, CLONE_NEWUSER);
+ if (ERRNO_IS_NEG_PRIVILEGE(r))
+ return log_notice_errno(r, "PrivateUsers= is configured, but user namespace setup not permitted, refusing.");
+ if (r < 0) {
+ *exit_status = EXIT_USER;
+ return log_error_errno(r, "Failed to set up user namespacing: %m");
+ }
- if (needs_sandboxing && !userns_set_up) {
+ log_debug("Set up existing user namespace");
+ } else if (needs_sandboxing && !userns_set_up) {
PrivateUsers pu = exec_context_get_effective_private_users(context, params);
r = setup_private_users(pu, saved_uid, saved_gid, uid, gid,
if (r < 0)
return r;
+ if (rt->shared->userns_storage_socket[0] >= 0 && rt->shared->userns_storage_socket[1] >= 0) {
+ r = serialize_fd_many(f, fds, "exec-runtime-userns-storage-socket", rt->shared->userns_storage_socket, 2);
+ if (r < 0)
+ return r;
+ }
+
if (rt->shared->netns_storage_socket[0] >= 0 && rt->shared->netns_storage_socket[1] >= 0) {
r = serialize_fd_many(f, fds, "exec-runtime-netns-storage-socket", rt->shared->netns_storage_socket, 2);
if (r < 0)
r = free_and_strdup(&rt->shared->var_tmp_dir, val);
if (r < 0)
return r;
+ } else if ((val = startswith(l, "exec-runtime-userns-storage-socket="))) {
+
+ r = deserialize_fd_many(fds, val, 2, rt->shared->userns_storage_socket);
+ if (r < 0)
+ continue;
+
} else if ((val = startswith(l, "exec-runtime-netns-storage-socket="))) {
r = deserialize_fd_many(fds, val, 2, rt->shared->netns_storage_socket);
if (r < 0)
return r;
+ r = serialize_item(f, "exec-context-user-namespace-path", c->user_namespace_path);
+ if (r < 0)
+ return r;
+
r = serialize_item(f, "exec-context-network-namespace-path", c->network_namespace_path);
if (r < 0)
return r;
r = free_and_strdup(&c->network_namespace_path, val);
if (r < 0)
return r;
+ } else if ((val = startswith(l, "exec-context-user-namespace-path="))) {
+ r = free_and_strdup(&c->user_namespace_path, val);
+ if (r < 0)
+ return r;
} else if ((val = startswith(l, "exec-context-ipc-namespace-path="))) {
r = free_and_strdup(&c->ipc_namespace_path, val);
if (r < 0)
c->stdin_data = mfree(c->stdin_data);
c->stdin_data_size = 0;
+ c->user_namespace_path = mfree(c->user_namespace_path);
c->network_namespace_path = mfree(c->network_namespace_path);
c->ipc_namespace_path = mfree(c->ipc_namespace_path);
}
#endif
+ if (c->user_namespace_path)
+ fprintf(f,
+ "%sUserNamespacePath: %s\n",
+ prefix, c->user_namespace_path);
+
if (c->network_namespace_path)
fprintf(f,
"%sNetworkNamespacePath: %s\n",
rt->id = mfree(rt->id);
rt->tmp_dir = mfree(rt->tmp_dir);
rt->var_tmp_dir = mfree(rt->var_tmp_dir);
+ safe_close_pair(rt->userns_storage_socket);
safe_close_pair(rt->netns_storage_socket);
safe_close_pair(rt->ipcns_storage_socket);
}
*n = (ExecSharedRuntime) {
.id = TAKE_PTR(id_copy),
+ .userns_storage_socket = EBADF_PAIR,
.netns_storage_socket = EBADF_PAIR,
.ipcns_storage_socket = EBADF_PAIR,
};
const char *id,
char **tmp_dir,
char **var_tmp_dir,
+ int userns_storage_socket[2],
int netns_storage_socket[2],
int ipcns_storage_socket[2],
ExecSharedRuntime **ret) {
rt->tmp_dir = TAKE_PTR(*tmp_dir);
rt->var_tmp_dir = TAKE_PTR(*var_tmp_dir);
+ if (userns_storage_socket) {
+ rt->userns_storage_socket[0] = TAKE_FD(userns_storage_socket[0]);
+ rt->userns_storage_socket[1] = TAKE_FD(userns_storage_socket[1]);
+ }
+
if (netns_storage_socket) {
rt->netns_storage_socket[0] = TAKE_FD(netns_storage_socket[0]);
rt->netns_storage_socket[1] = TAKE_FD(netns_storage_socket[1]);
ExecSharedRuntime **ret) {
_cleanup_(namespace_cleanup_tmpdirp) char *tmp_dir = NULL, *var_tmp_dir = NULL;
- _cleanup_close_pair_ int netns_storage_socket[2] = EBADF_PAIR, ipcns_storage_socket[2] = EBADF_PAIR;
+ _cleanup_close_pair_ int userns_storage_socket[2] = EBADF_PAIR, netns_storage_socket[2] = EBADF_PAIR, ipcns_storage_socket[2] = EBADF_PAIR;
int r;
assert(m);
return r;
}
+ if (c->user_namespace_path)
+ if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, userns_storage_socket) < 0)
+ return -errno;
+
if (exec_needs_network_namespace(c))
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, netns_storage_socket) < 0)
return -errno;
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, ipcns_storage_socket) < 0)
return -errno;
- r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ipcns_storage_socket, ret);
+ r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, userns_storage_socket, netns_storage_socket, ipcns_storage_socket, ret);
if (r < 0)
return r;
if (rt->var_tmp_dir)
fprintf(f, " var-tmp-dir=%s", rt->var_tmp_dir);
+ if (rt->userns_storage_socket[0] >= 0) {
+ int copy;
+
+ copy = fdset_put_dup(fds, rt->userns_storage_socket[0]);
+ if (copy < 0)
+ return copy;
+
+ fprintf(f, " userns-socket-0=%i", copy);
+ }
+
+ if (rt->userns_storage_socket[1] >= 0) {
+ int copy;
+
+ copy = fdset_put_dup(fds, rt->userns_storage_socket[1]);
+ if (copy < 0)
+ return copy;
+
+ fprintf(f, " userns-socket-1=%i", copy);
+ }
+
if (rt->netns_storage_socket[0] >= 0) {
int copy;
int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) {
_cleanup_free_ char *tmp_dir = NULL, *var_tmp_dir = NULL;
char *id = NULL;
- int r, netns_fdpair[] = {-1, -1}, ipcns_fdpair[] = {-1, -1};
+ int r, userns_fdpair[] = {-1, -1}, netns_fdpair[] = {-1, -1}, ipcns_fdpair[] = {-1, -1};
const char *p, *v = ASSERT_PTR(value);
size_t n;
p = v + n + 1;
}
+ v = startswith(p, "userns-socket-0=");
+ if (v) {
+ char *buf;
+
+ n = strcspn(v, " ");
+ buf = strndupa_safe(v, n);
+
+ userns_fdpair[0] = deserialize_fd(fds, buf);
+ if (userns_fdpair[0] < 0)
+ return userns_fdpair[0];
+ if (v[n] != ' ')
+ goto finalize;
+ p = v + n + 1;
+ }
+
+ v = startswith(p, "userns-socket-1=");
+ if (v) {
+ char *buf;
+
+ n = strcspn(v, " ");
+ buf = strndupa_safe(v, n);
+
+ userns_fdpair[1] = deserialize_fd(fds, buf);
+ if (userns_fdpair[1] < 0)
+ return userns_fdpair[1];
+ if (v[n] != ' ')
+ goto finalize;
+ p = v + n + 1;
+ }
+
v = startswith(p, "netns-socket-0=");
if (v) {
char *buf;
}
finalize:
- r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_fdpair, ipcns_fdpair, NULL);
+ r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, userns_fdpair, netns_fdpair, ipcns_fdpair, NULL);
if (r < 0)
return log_debug_errno(r, "Failed to add exec-runtime: %m");
return 0;
/* Like netns_storage_socket, but the file descriptor is referring to the IPC namespace. */
int ipcns_storage_socket[2];
+
+ /* Like netns_storage_socket, but the file descriptor is referring to the user namespace. */
+ int userns_storage_socket[2];
} ExecSharedRuntime;
typedef struct ExecRuntime {
bool address_families_allow_list:1;
Set *address_families;
+ char *user_namespace_path;
char *network_namespace_path;
char *ipc_namespace_path;
_cleanup_(exec_command_done) ExecCommand command = {};
_cleanup_(exec_params_deep_clear) ExecParameters params = EXEC_PARAMETERS_INIT(/* flags= */ 0);
_cleanup_(exec_shared_runtime_done) ExecSharedRuntime shared = {
+ .userns_storage_socket = EBADF_PAIR,
.netns_storage_socket = EBADF_PAIR,
.ipcns_storage_socket = EBADF_PAIR,
};
DynamicCreds dynamic_creds = {};
ExecCommand command = {};
ExecSharedRuntime shared = {
+ .userns_storage_socket = EBADF_PAIR,
.netns_storage_socket = EBADF_PAIR,
.ipcns_storage_socket = EBADF_PAIR,
};
{{type}}.ProtectKernelLogs, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_kernel_logs)
{{type}}.ProtectClock, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_clock)
{{type}}.ProtectControlGroups, config_parse_protect_control_groups, 0, offsetof({{type}}, exec_context.protect_control_groups)
+{{type}}.UserNamespacePath, config_parse_unit_path_printf, 0, offsetof({{type}}, exec_context.user_namespace_path)
{{type}}.NetworkNamespacePath, config_parse_unit_path_printf, 0, offsetof({{type}}, exec_context.network_namespace_path)
{{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)
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Failed to acquire runtime: %m");
+ if (s->exec_context.user_namespace_path &&
+ s->exec_runtime &&
+ s->exec_runtime->shared &&
+ s->exec_runtime->shared->userns_storage_socket[0] >= 0) {
+ r = open_shareable_ns_path(s->exec_runtime->shared->userns_storage_socket, s->exec_context.user_namespace_path, CLONE_NEWUSER);
+ if (r < 0)
+ return log_unit_error_errno(UNIT(s), r, "Failed to open user namespace path %s: %m", s->exec_context.user_namespace_path);
+ }
+
if (s->exec_context.network_namespace_path &&
s->exec_runtime &&
s->exec_runtime->shared &&
JSON_BUILD_PAIR_TRISTATE_NON_NULL("MemoryKSM", c->memory_ksm),
SD_JSON_BUILD_PAIR_STRING("PrivatePIDs", private_pids_to_string(c->private_pids)),
SD_JSON_BUILD_PAIR_STRING("PrivateUsers", private_users_to_string(c->private_users)),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("UserNamespacePath", c->user_namespace_path),
SD_JSON_BUILD_PAIR_STRING("ProtectHostname", protect_hostname_to_string(c->protect_hostname)),
JSON_BUILD_PAIR_YES_NO("ProtectClock", c->protect_clock),
JSON_BUILD_PAIR_YES_NO("ProtectKernelTunables", c->protect_kernel_tunables),
{ "ProtectProc", bus_append_string },
{ "ProcSubset", bus_append_string },
{ "NetworkNamespacePath", bus_append_string },
+ { "UserNamespacePath", bus_append_string },
{ "IPCNamespacePath", bus_append_string },
{ "LogNamespace", bus_append_string },
{ "RootImagePolicy", bus_append_string },
SD_VARLINK_DEFINE_FIELD(PrivatePIDs, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateUsers="),
SD_VARLINK_DEFINE_FIELD(PrivateUsers, SD_VARLINK_STRING, 0),
+ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UserNamespacePath="),
+ SD_VARLINK_DEFINE_FIELD(UserNamespacePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectHostname="),
SD_VARLINK_DEFINE_FIELD(ProtectHostname, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectClock="),
UpheldBy=
Upholds=
User=
+UserNamespacePath=
WakeSystem=
WantedBy=
Wants=
--- /dev/null
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
+# When sanitizers are used, export LD_PRELOAD with the sanitizers path,
+# lsns doesn't work otherwise.
+if [ -f /usr/lib/systemd/systemd-asan-env ]; then
+ # shellcheck source=/dev/null
+ . /usr/lib/systemd/systemd-asan-env
+ export LD_PRELOAD
+ export ASAN_OPTIONS
+fi
+
+# Only reuse the user namespace
+systemd-run --unit=oldservice --property=PrivateUsers=true sleep 3600
+sleep .2
+OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}')
+
+systemd-run --unit=newservice --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=PrivateNetwork=true sleep 3600
+sleep .2
+NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}')
+
+assert_neq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)"
+assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)"
+
+systemctl stop oldservice newservice
+
+# Reuse the user and network namespaces
+systemd-run --unit=oldservice --property=PrivateUsers=true --property=PrivateNetwork=true sleep 3600
+sleep .2
+OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}')
+
+systemd-run --unit=newservice --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=NetworkNamespacePath=/proc/"$OLD_PID"/ns/net sleep 3600
+sleep .2
+NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}')
+
+assert_eq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)"
+assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)"
+
+systemctl stop oldservice newservice
+
+# Delegate the network namespace
+systemd-run --unit=oldservice --property=PrivateUsers=true sleep 3600
+sleep .2
+OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}')
+
+systemd-run --unit=newservice --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=DelegateNamespaces=net --property=PrivateNetwork=true sleep 3600
+sleep .2
+NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}')
+
+assert_neq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)"
+assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)"
+
+systemctl stop oldservice newservice