]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
machined: track UID owner of machines
authorLennart Poettering <lennart@poettering.net>
Fri, 23 May 2025 13:30:22 +0000 (15:30 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 11 Jul 2025 16:15:12 +0000 (18:15 +0200)
Now that unpriv clients can register machines, let's register their UID
too. This allows us to do two things:

1. make sure the scope delegation is assigned to the right UID (so that
   the unpriv user can actually create cgroups below the delegated
   scope)

2. permit certain types of access (i.e. killing, or pty access) to the
   client without auth if it owns the machine.

man/org.freedesktop.machine1.xml
src/machine/machine-dbus.c
src/machine/machine-varlink.c
src/machine/machine.c
src/machine/machine.h
src/machine/machinectl.c
src/machine/machined-dbus.c
src/machine/machined-varlink.c
src/shared/varlink-io.systemd.Machine.c

index ea3a70621597bab9a1384be9f998cefcca004003..c52aed0dbc0f767939f50b53d92faec7338cdf3a 100644 (file)
@@ -525,6 +525,8 @@ node /org/freedesktop/machine1/machine/rawhide {
       readonly s SSHPrivateKeyPath = '...';
       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
       readonly s State = '...';
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly u UID = ...;
   };
   interface org.freedesktop.DBus.Peer { ... };
   interface org.freedesktop.DBus.Introspectable { ... };
@@ -620,6 +622,8 @@ node /org/freedesktop/machine1/machine/rawhide {
 
     <variablelist class="dbus-property" generated="True" extra-ref="State"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="UID"/>
+
     <!--End of Autogenerated section-->
 
     <refsect2>
@@ -683,6 +687,8 @@ node /org/freedesktop/machine1/machine/rawhide {
 
       <para><varname>Subgroup</varname> contains the sub-control-group path this machine's processes reside
       in, relative to the specified unit's control group.</para>
+
+      <para><varname>UID</varname> contains the numeric UNIX UID of the user who registered the machine.</para>
     </refsect2>
   </refsect1>
 
@@ -726,7 +732,8 @@ $ gdbus introspect --system \
       <function>CopyToWithFlags()</function> were added in version 252.</para>
       <para><function>GetSSHInfo()</function>, <varname>VSockCID</varname>, <varname>SSHAddress</varname>,
       and <varname>SSHPrivateKeyPath</varname> were added in version 256.</para>
-      <para><varname>LeaderPIDFDId</varname> and <varname>Subgroup</varname> were added in version 258.</para>
+      <para><varname>LeaderPIDFDId</varname>, <varname>Subgroup</varname>, and <varname>UID</varname> were
+      added in version 258.</para>
     </refsect2>
   </refsect1>
 
index 8935b594656008f17683d52a5d84ef3496ec6c73..321ddbfdccd505c5c59990701c3a6cbff839a9cb 100644 (file)
@@ -60,10 +60,12 @@ int bus_machine_method_unregister(sd_bus_message *message, void *userdata, sd_bu
                 NULL
         };
 
-        r = bus_verify_polkit_async(
+        r = bus_verify_polkit_async_full(
                         message,
                         "org.freedesktop.machine1.manage-machines",
                         details,
+                        m->uid,
+                        /* flags= */ 0,
                         &m->manager->polkit_registry,
                         error);
         if (r < 0)
@@ -90,10 +92,12 @@ int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus
                 NULL
         };
 
-        r = bus_verify_polkit_async(
+        r = bus_verify_polkit_async_full(
                         message,
                         "org.freedesktop.machine1.manage-machines",
                         details,
+                        m->uid,
+                        /* flags= */ 0,
                         &m->manager->polkit_registry,
                         error);
         if (r < 0)
@@ -138,10 +142,12 @@ int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_erro
                 NULL
         };
 
-        r = bus_verify_polkit_async(
+        r = bus_verify_polkit_async_full(
                         message,
                         "org.freedesktop.machine1.manage-machines",
                         details,
+                        m->uid,
+                        /* flags= */ 0,
                         &m->manager->polkit_registry,
                         error);
         if (r < 0)
@@ -258,10 +264,12 @@ int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_
                 NULL
         };
 
-        r = bus_verify_polkit_async(
+        r = bus_verify_polkit_async_full(
                         message,
                         m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty",
                         details,
+                        m->uid,
+                        /* flags= */ 0,
                         &m->manager->polkit_registry,
                         error);
         if (r < 0)
@@ -299,10 +307,12 @@ int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bu
                 NULL
         };
 
-        r = bus_verify_polkit_async(
+        r = bus_verify_polkit_async_full(
                         message,
                         m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login",
                         details,
+                        m->uid,
+                        /* flags= */ 0,
                         &m->manager->polkit_registry,
                         error);
         if (r < 0)
@@ -383,10 +393,12 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu
                 NULL
         };
 
-        r = bus_verify_polkit_async(
+        r = bus_verify_polkit_async_full(
                         message,
                         m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell",
                         details,
+                        m->uid,
+                        /* flags= */ 0,
                         &m->manager->polkit_registry,
                         error);
         if (r < 0)
@@ -446,6 +458,7 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
                 NULL
         };
 
+        /* NB: For now not opened up to owner of machine without auth */
         r = bus_verify_polkit_async(
                         message,
                         "org.freedesktop.machine1.manage-machines",
@@ -531,6 +544,7 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro
                 NULL
         };
 
+        /* NB: For now not opened up to owner of machine without auth */
         r = bus_verify_polkit_async(
                         message,
                         "org.freedesktop.machine1.manage-machines",
@@ -574,6 +588,7 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda
                 NULL
         };
 
+        /* NB: For now not opened up to owner of machine without auth */
         r = bus_verify_polkit_async(
                         message,
                         "org.freedesktop.machine1.manage-machines",
@@ -727,6 +742,7 @@ static const sd_bus_vtable machine_vtable[] = {
         SD_BUS_PROPERTY("SSHAddress", "s", NULL, offsetof(Machine, ssh_address), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("SSHPrivateKeyPath", "s", NULL, offsetof(Machine, ssh_private_key_path), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0),
+        SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Machine, uid), SD_BUS_VTABLE_PROPERTY_CONST),
 
         SD_BUS_METHOD("Terminate",
                       NULL,
index b5754e2c49fda063f1ed6b8f6ef84fd63773c288..8c437efc1759cdf82c3d54acdaad8e006f83841b 100644 (file)
@@ -168,6 +168,10 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink
                         return r;
         }
 
+        r = sd_varlink_get_peer_uid(link, &machine->uid);
+        if (r < 0)
+                return r;
+
         r = machine_link(manager, machine);
         if (r == -EEXIST)
                 return sd_varlink_error(link, VARLINK_ERROR_MACHINE_EXISTS, NULL);
@@ -278,12 +282,14 @@ int vl_method_unregister_internal(sd_varlink *link, sd_json_variant *parameters,
         Manager *manager = ASSERT_PTR(machine->manager);
         int r;
 
-        r = varlink_verify_polkit_async(
+        r = varlink_verify_polkit_async_full(
                         link,
                         manager->bus,
                         "org.freedesktop.machine1.manage-machines",
                         (const char**) STRV_MAKE("name", machine->name,
                                                  "verb", "unregister"),
+                        machine->uid,
+                        /* flags= */ 0,
                         &manager->polkit_registry);
         if (r <= 0)
                 return r;
@@ -300,12 +306,14 @@ int vl_method_terminate_internal(sd_varlink *link, sd_json_variant *parameters,
         Manager *manager = ASSERT_PTR(machine->manager);
         int r;
 
-        r = varlink_verify_polkit_async(
+        r = varlink_verify_polkit_async_full(
                         link,
                         manager->bus,
                         "org.freedesktop.machine1.manage-machines",
                         (const char**) STRV_MAKE("name", machine->name,
                                                  "verb", "terminate"),
+                        machine->uid,
+                        /* flags= */ 0,
                         &manager->polkit_registry);
         if (r <= 0)
                 return r;
@@ -368,12 +376,14 @@ int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met
                         return sd_varlink_error_invalid_parameter_name(link, "whom");
         }
 
-        r = varlink_verify_polkit_async(
+        r = varlink_verify_polkit_async_full(
                         link,
                         manager->bus,
                         "org.freedesktop.machine1.manage-machines",
                         (const char**) STRV_MAKE("name", machine->name,
                                                  "verb", "kill"),
+                        machine->uid,
+                        /* flags= */ 0,
                         &manager->polkit_registry);
         if (r <= 0)
                 return r;
@@ -510,11 +520,13 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met
                 return r;
 
         polkit_details = machine_open_polkit_details(p.mode, machine->name, user, path, command_line);
-        r = varlink_verify_polkit_async(
+        r = varlink_verify_polkit_async_full(
                         link,
                         manager->bus,
                         machine_open_polkit_action(p.mode, machine->class),
                         (const char**) polkit_details,
+                        machine->uid,
+                        /* flags= */ 0,
                         &manager->polkit_registry);
         if (r <= 0)
                 return r;
@@ -788,6 +800,7 @@ int vl_method_bind_mount(sd_varlink *link, sd_json_variant *parameters, sd_varli
         if (machine->class != MACHINE_CONTAINER)
                 return sd_varlink_error(link, VARLINK_ERROR_MACHINE_NOT_SUPPORTED, NULL);
 
+        /* NB: For now not opened up to owner of machine without auth */
         r = varlink_verify_polkit_async(
                         link,
                         manager->bus,
@@ -899,6 +912,7 @@ int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_va
         if (machine->class != MACHINE_CONTAINER)
                 return sd_varlink_error(link, VARLINK_ERROR_MACHINE_NOT_SUPPORTED, NULL);
 
+        /* NB: For now not opened up to owner of machine without auth */
         r = varlink_verify_polkit_async(
                         link,
                         manager->bus,
@@ -928,6 +942,7 @@ int vl_method_open_root_directory_internal(sd_varlink *link, sd_json_variant *pa
         Manager *manager = ASSERT_PTR(machine->manager);
         int r;
 
+        /* NB: For now not opened up to owner of machine without auth */
         r = varlink_verify_polkit_async(
                         link,
                         manager->bus,
index 7ce512dd62adc3f5edc5f0569d0cd9b43fb5b3b1..91c14501842a25a9c6749ce440c12d648e87cdd7 100644 (file)
@@ -183,8 +183,10 @@ int machine_save(Machine *m) {
 
         fprintf(f,
                 "# This is private data. Do not parse.\n"
-                "NAME=%s\n",
-                m->name);
+                "NAME=%s\n"
+                "UID=" UID_FMT "\n",
+                m->name,
+                m->uid);
 
         /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */
         env_file_fputs_assignment(f, "SCOPE=", m->unit);
@@ -261,7 +263,7 @@ static void machine_unlink(Machine *m) {
 
 int machine_load(Machine *m) {
         _cleanup_free_ char *name = NULL, *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *leader_pidfdid = NULL,
-                *class = NULL, *netif = NULL, *vsock_cid = NULL;
+                *class = NULL, *netif = NULL, *vsock_cid = NULL, *uid = NULL;
         int r;
 
         assert(m);
@@ -285,7 +287,8 @@ int machine_load(Machine *m) {
                            "NETIF",                &netif,
                            "VSOCK_CID",            &vsock_cid,
                            "SSH_ADDRESS",          &m->ssh_address,
-                           "SSH_PRIVATE_KEY_PATH", &m->ssh_private_key_path);
+                           "SSH_PRIVATE_KEY_PATH", &m->ssh_private_key_path,
+                           "UID",                  &uid);
         if (r == -ENOENT)
                 return 0;
         if (r < 0)
@@ -369,6 +372,10 @@ int machine_load(Machine *m) {
                         log_warning_errno(r, "Failed to parse AF_VSOCK CID, ignoring: %s", vsock_cid);
         }
 
+        r = parse_uid(uid, &m->uid);
+        if (r < 0)
+                log_warning_errno(r, "Failed to parse owning UID, ignoring: %s", uid);
+
         return r;
 }
 
@@ -426,14 +433,28 @@ static int machine_start_scope(
         if (r < 0)
                 return r;
 
-        r = sd_bus_message_append(m, "(sv)(sv)(sv)(sv)",
-                                  "Delegate", "b", 1,
-                                  "CollectMode", "s", "inactive-or-failed",
-                                  "AddRef", "b", 1,
-                                  "TasksMax", "t", UINT64_C(16384));
+        r = sd_bus_message_append(
+                        m, "(sv)(sv)(sv)(sv)",
+                        "Delegate", "b", 1,
+                        "CollectMode", "s", "inactive-or-failed",
+                        "AddRef", "b", 1,
+                        "TasksMax", "t", UINT64_C(16384));
         if (r < 0)
                 return r;
 
+        if (machine->uid != 0) {
+                _cleanup_free_ char *u = NULL;
+
+                if (asprintf(&u, UID_FMT, machine->uid) < 0)
+                        return -ENOMEM;
+
+                r = sd_bus_message_append(
+                                m, "(sv)",
+                                "User", "s", u);
+                if (r < 0)
+                        return r;
+        }
+
         if (more_properties) {
                 r = sd_bus_message_copy(m, more_properties, true);
                 if (r < 0)
index 8762263a8bf844dc9af4942309d38241a40e53a3..dddc0c8000cb823af94233ce35c4e307721b91fd 100644 (file)
@@ -38,6 +38,8 @@ typedef struct Machine {
         char *name;
         sd_id128_t id;
 
+        uid_t uid;
+
         MachineClass class;
 
         char *state_file;
index 7731fae70899c1c738e312c5c8b1de56ff0f1362..d31ef407f46e6c380f5177c25aee00f6696bde4b 100644 (file)
@@ -514,6 +514,7 @@ typedef struct MachineStatusInfo {
         struct dual_timestamp timestamp;
         int *netif;
         size_t n_netif;
+        uid_t uid;
 } MachineStatusInfo;
 
 static void machine_status_info_clear(MachineStatusInfo *info) {
@@ -567,6 +568,9 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
         } else if (i->class)
                 printf("\t   Class: %s\n", i->class);
 
+        if (i->uid != 0)
+                printf("\t     UID: " UID_FMT "\n", i->uid);
+
         if (i->root_directory)
                 printf("\t    Root: %s\n", i->root_directory);
 
@@ -662,6 +666,7 @@ static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bo
                 { "TimestampMonotonic", "t",  NULL,          offsetof(MachineStatusInfo, timestamp.monotonic) },
                 { "Id",                 "ay", bus_map_id128, offsetof(MachineStatusInfo, id)                  },
                 { "NetworkInterfaces",  "ai", map_netif,     0                                                },
+                { "UID",                "u",  NULL,          offsetof(MachineStatusInfo, uid)                 },
                 {}
         };
 
index 8aa00ba3edd53e1e101f06c0541bb9ce0e0a323b..30f722a4497d16e9e76605d5ae25ad4cb8a87e56 100644 (file)
@@ -300,6 +300,16 @@ static int method_create_or_register_machine(
         if (hashmap_get(manager->machines, name))
                 return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name);
 
+        _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+        r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
+        if (r < 0)
+                return r;
+
+        uid_t uid;
+        r = sd_bus_creds_get_euid(creds, &uid);
+        if (r < 0)
+                return r;
+
         const char *details[] = {
                 "name",  name,
                 "class", machine_class_to_string(c),
@@ -324,6 +334,7 @@ static int method_create_or_register_machine(
         m->leader = TAKE_PIDREF(pidref);
         m->class = c;
         m->id = id;
+        m->uid = uid;
 
         if (!isempty(service)) {
                 m->service = strdup(service);
index 8551d703e7b9a36fc8343b32efd8e81623b82227..7dac3cb0d20bdc8f151c37020305ce64d0ec2b81 100644 (file)
@@ -485,7 +485,8 @@ static int list_machine_one_and_maybe_read_metadata(sd_varlink *link, Machine *m
                         JSON_BUILD_PAIR_STRING_NON_EMPTY("sshPrivateKeyPath", m->ssh_private_key_path),
                         JSON_BUILD_PAIR_VARIANT_NON_NULL("addresses", addr_array),
                         JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY("OSRelease", os_release),
-                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("UIDShift", shift, UID_INVALID));
+                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("UIDShift", shift, UID_INVALID),
+                        SD_JSON_BUILD_PAIR_UNSIGNED("UID", m->uid));
         if (r < 0)
                 return r;
 
index 1bb7c00fa18ce0940610170ea7e4a57aa2997264..ad7dff228f16e1af0c967c33579aa5cace22d920 100644 (file)
@@ -98,7 +98,9 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
                 SD_VARLINK_FIELD_COMMENT("Return the base UID/GID of the machine"),
                 SD_VARLINK_DEFINE_OUTPUT(UIDShift, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Subcgroup path of the machine, relative to the unit's cgroup path"),
-                SD_VARLINK_DEFINE_OUTPUT(Subgroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
+                SD_VARLINK_DEFINE_OUTPUT(Subgroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user who registered the machine"),
+                SD_VARLINK_DEFINE_OUTPUT(UID, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
 
 static SD_VARLINK_DEFINE_ENUM_TYPE(
                 MachineOpenMode,