]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
machined: do not allow unprivileged users to shell into the root namespace via varlink
authorLuca Boccassi <luca.boccassi@gmail.com>
Sat, 28 Feb 2026 00:46:21 +0000 (00:46 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Wed, 4 Mar 2026 13:03:44 +0000 (13:03 +0000)
Forbid non-root from shelling into a machine that is running in
the root user namespace.

Follow-up for adaff8eb35d9c471af81fddaa4403bc5843a256f

src/machine/machine-varlink.c
test/units/TEST-13-NSPAWN.unpriv.sh

index a499dbc3be2d30d35aa261d0174ae2e924d2f6d7..e8a8933251e380cb9ae48fd39fb26eb0f6217478 100644 (file)
@@ -552,6 +552,24 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met
                 return r;
 
         if (manager->runtime_scope != RUNTIME_SCOPE_USER) {
+                /* Ensure only root can shell into the root namespace, unless it's specifically the host machine,
+                 * which is owned by uid 0 anyway and cannot be self-registered. This is to avoid unprivileged
+                 * users registering a process they own in the root user namespace, and then shelling in as root
+                 * or another user. Note that the shell operation is privileged and requires 'auth_admin', so we
+                 * do not need to check the caller's uid, as that will be checked by polkit, and if they machine's
+                 * and the caller's do not match, authorization will be required. It's only the case where the
+                 * caller owns the machine that will be shortcut and needs to be checked here. */
+                if (machine->uid != 0 && machine->class != MACHINE_HOST) {
+                        r = pidref_in_same_namespace(&PIDREF_MAKE_FROM_PID(1), &machine->leader, NAMESPACE_USER);
+                        if (r < 0)
+                                return log_debug_errno(
+                                                r,
+                                                "Failed to check if machine '%s' is running in the root user namespace: %m",
+                                                machine->name);
+                        if (r > 0)
+                                return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL);
+                }
+
                 _cleanup_strv_free_ char **polkit_details = NULL;
 
                 polkit_details = machine_open_polkit_details(p.mode, machine->name, user, path, command_line);
index cbf332aa22093d787d927003f17585160ff2c0fe..25867de7071155ab9b6564b767ef15b5e73c117d 100755 (executable)
@@ -31,6 +31,7 @@ at_exit() {
     rm -f /usr/share/polkit-1/rules.d/registermachinetest.rules
     rm -rf /var/tmp/mangletest
     rm -f /var/tmp/mangletest.tar.gz
+    rm -f /shouldnotwork
 }
 
 trap at_exit EXIT
@@ -104,6 +105,24 @@ run0 -u testuser \
 (! run0 -u testuser machinectl shell 0@shouldnotwork2 /usr/bin/id -u)
 (! run0 -u testuser machinectl shell testuser@shouldnotwork2 /usr/bin/id -u)
 
+run0 -u testuser \
+    systemd-run --unit sleep.service --user sleep infinity
+sleep_pid="$(run0 -u testuser systemctl show --user -P MainPID sleep.service)"
+run0 -u testuser  \
+    varlinkctl \
+        call \
+        /run/systemd/machine/io.systemd.Machine \
+        io.systemd.Machine.Register \
+        "{\"name\":\"shouldnotwork3\", \"class\":\"container\", \"leader\": $sleep_pid}"
+(! run0 -u testuser \
+    varlinkctl \
+        call \
+        /run/systemd/machine/io.systemd.Machine \
+        io.systemd.Machine.Open \
+        '{"name":"shouldnotwork3", "mode": "shell", "user":"root","path":"/usr/bin/bash","args":["bash","-c","''touch /shouldnotwork; sleep 20''"]}')
+systemctl --user --machine testuser@ stop sleep.service
+test ! -f /shouldnotwork
+
 run0 -u testuser mkdir /var/tmp/image-tar
 run0 -u testuser importctl --user export-tar zurps /var/tmp/image-tar/kurps.tar.gz -m
 run0 -u testuser importctl --user import-tar /var/tmp/image-tar/kurps.tar.gz -m