core: Make DelegateNamespaces= work for user managers with CAP_SYS_ADMIN (#36771)

Currently DelegateNamespaces= only works for services spawned by the
system manager. User managers will always unshare the user namespace
first even if they're running with CAP_SYS_ADMIN.

Let's add support for DelegateNamespaces= for user managers if they're
running with CAP_SYS_ADMIN. By default, we'll still delegate all
namespaces
for user managers, but this can now be overridden by explicitly passing
DelegateNamespaces=.

If a user manager is running without CAP_SYS_ADMIN, the user manager is
still always unshared first just like before.
This commit is contained in:
Yu Watanabe
2025-03-20 06:28:18 +09:00
committed by GitHub
7 changed files with 92 additions and 56 deletions
+38 -4
View File
@@ -9,6 +9,22 @@ set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
# IMPORTANT: For /proc/ to be remounted in pid namespace within an unprivileged user namespace, there needs to
# be at least 1 unmasked procfs mount in ANY directory. Otherwise, if /proc/ is masked (e.g. /proc/scsi is
# over-mounted with tmpfs), then mounting a new /proc/ will fail.
#
# Thus, to guarantee PrivatePIDs=yes tests for unprivileged users pass, we mount a new procfs on a temporary
# directory with no masking. This will guarantee an unprivileged user can mount a new /proc/ successfully.
mkdir -p /tmp/TEST-07-PID1-delegate-namespaces-proc
mount -t proc proc /tmp/TEST-07-PID1-delegate-namespaces-proc
at_exit() {
umount /tmp/TEST-07-PID1-delegate-namespaces-proc
rm -rf /tmp/TEST-07-PID1-delegate-namespaces-proc
}
trap at_exit EXIT
testcase_mount() {
(! systemd-run -p PrivateUsersEx=self -p PrivateMounts=yes --wait --pipe -- mount --bind /usr /home)
systemd-run -p PrivateUsersEx=self -p PrivateMounts=yes -p DelegateNamespaces=mnt --wait --pipe -- mount --bind /usr /home
@@ -16,7 +32,7 @@ testcase_mount() {
testcase_network() {
(! systemd-run -p PrivateUsersEx=self -p PrivateNetwork=yes --wait --pipe -- ip link add veth1 type veth peer name veth2)
systemd-run -p PrivateUsersEx=self -p PrivateMounts=yes -p DelegateNamespaces=mnt --wait --pipe -- ip link add veth1 type veth peer name veth2
systemd-run -p PrivateUsersEx=self -p PrivateNetwork=yes -p DelegateNamespaces=net --wait --pipe -- ip link add veth1 type veth peer name veth2
}
testcase_cgroup() {
@@ -25,8 +41,12 @@ testcase_cgroup() {
}
testcase_pid() {
(! systemd-run -p PrivateUsersEx=self -p PrivatePIDs=yes --wait --pipe -- sh -c 'echo 5 >/proc/sys/kernel/ns_last_pid')
systemd-run -p PrivateUsersEx=self -p PrivatePIDs=yes -p DelegateNamespaces=pid --wait --pipe -- sh -c 'echo 5 >/proc/sys/kernel/ns_last_pid'
# MountAPIVFS=yes always bind mounts child mounts of APIVFS filesystems, which means /proc/sys is always read-only
# so we can't write to it when running in a container.
if ! systemd-detect-virt --container; then
(! systemd-run -p PrivateUsersEx=self -p PrivatePIDs=yes -p MountAPIVFS=yes --wait --pipe -- sh -c 'echo 5 >/proc/sys/kernel/ns_last_pid')
systemd-run -p PrivateUsersEx=self -p PrivatePIDs=yes -p MountAPIVFS=yes -p DelegateNamespaces="mnt pid" --wait --pipe -- sh -c 'echo 5 >/proc/sys/kernel/ns_last_pid'
fi
}
testcase_uts() {
@@ -42,6 +62,18 @@ testcase_implied_private_users_self() {
systemd-run -p PrivateUsersEx=identity -p PrivateMounts=yes -p DelegateNamespaces=mnt --wait bash -c 'test "$(cat /proc/self/uid_map)" == " 0 0 65536"'
}
testcase_user_manager() {
systemctl start user@0
# DelegateNamespaces=yes is implied for user managers.
systemd-run --machine=testuser@.host --user -p PrivateMounts=yes -p AmbientCapabilities="~" --wait --pipe -- mount --bind /usr /home
# Even those with CAP_SYS_ADMIN.
SYSTEMD_LOG_LEVEL=debug systemd-run --machine=.host --user -p PrivateMounts=yes --wait --pipe -- mount --bind /usr /home
# But can be overridden for user managers that are running with CAP_SYS_ADMIN.
(! systemd-run --machine=.host --user -p PrivateMounts=yes -p DelegateNamespaces=no --wait --pipe -- mount --bind /usr /home)
# But not for those without CAP_SYS_ADMIN.
systemd-run --machine=testuser@.host --user -p PrivateMounts=yes -p DelegateNamespaces=no -p AmbientCapabilities="~" --wait --pipe -- mount --bind /usr /home
}
testcase_multiple_features() {
unsquashfs -no-xattrs -d /tmp/TEST-07-PID1-delegate-namespaces-root /usr/share/minimal_0.raw
@@ -52,7 +84,7 @@ testcase_multiple_features() {
-p BindReadOnlyPaths=/usr/share \
-p NoNewPrivileges=yes \
-p ProtectSystem=strict \
-p User=testuser\
-p User=testuser \
-p Group=testuser \
-p RuntimeDirectory=abc \
-p StateDirectory=qed \
@@ -78,3 +110,5 @@ testcase_multiple_features() {
rm -rf /tmp/TEST-07-PID1-delegate-namespaces-root
}
run_testcases
+4 -4
View File
@@ -95,7 +95,7 @@ testcase_multiple_features() {
-p BindReadOnlyPaths=/usr/share \
-p NoNewPrivileges=yes \
-p ProtectSystem=strict \
-p User=testuser\
-p User=testuser \
-p Group=testuser \
-p RuntimeDirectory=abc \
-p StateDirectory=qed \
@@ -142,8 +142,8 @@ testcase_unpriv() {
mount -t proc proc /tmp/TEST-07-PID1-private-pids-proc
# Verify running as unprivileged user can unshare PID namespace and mounts /proc properly.
assert_eq "$(runas testuser systemd-run --wait --user --pipe -p PrivatePIDs=yes readlink /proc/self)" "1"
assert_eq "$(runas testuser systemd-run --wait --user --pipe -p PrivatePIDs=yes ps aux --no-heading | wc -l)" "1"
assert_eq "$(systemd-run --machine=testuser@.host --wait --user --pipe -p PrivatePIDs=yes readlink /proc/self)" "1"
assert_eq "$(systemd-run --machine=testuser@.host --wait --user --pipe -p PrivatePIDs=yes ps aux --no-heading | wc -l)" "1"
umount /tmp/TEST-07-PID1-private-pids-proc
rm -rf /tmp/TEST-07-PID1-private-pids-proc
@@ -162,7 +162,7 @@ testcase_unpriv() {
mount -t tmpfs tmpfs /proc/scsi
fi
(! runas testuser systemd-run --wait --user --pipe -p PrivatePIDs=yes true)
(! systemd-run --machine=testuser@.host --wait --user --pipe -p PrivatePIDs=yes true)
if [[ "$HAS_EXISTING_SCSI_MOUNT" == "no" ]]; then
umount /proc/scsi