From 276d20018623ef14956ce87975be48da5de63f29 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 23 May 2025 15:30:22 +0200 Subject: [PATCH] machined: track UID owner of machines 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 | 9 +++++- src/machine/machine-dbus.c | 28 ++++++++++++++---- src/machine/machine-varlink.c | 23 ++++++++++++--- src/machine/machine.c | 39 +++++++++++++++++++------ src/machine/machine.h | 2 ++ src/machine/machinectl.c | 5 ++++ src/machine/machined-dbus.c | 11 +++++++ src/machine/machined-varlink.c | 3 +- src/shared/varlink-io.systemd.Machine.c | 4 ++- 9 files changed, 102 insertions(+), 22 deletions(-) diff --git a/man/org.freedesktop.machine1.xml b/man/org.freedesktop.machine1.xml index ea3a706215..c52aed0dbc 100644 --- a/man/org.freedesktop.machine1.xml +++ b/man/org.freedesktop.machine1.xml @@ -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 { + + @@ -683,6 +687,8 @@ node /org/freedesktop/machine1/machine/rawhide { Subgroup contains the sub-control-group path this machine's processes reside in, relative to the specified unit's control group. + + UID contains the numeric UNIX UID of the user who registered the machine. @@ -726,7 +732,8 @@ $ gdbus introspect --system \ CopyToWithFlags() were added in version 252. GetSSHInfo(), VSockCID, SSHAddress, and SSHPrivateKeyPath were added in version 256. - LeaderPIDFDId and Subgroup were added in version 258. + LeaderPIDFDId, Subgroup, and UID were + added in version 258. diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 8935b59465..321ddbfdcc 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -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, diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index b5754e2c49..8c437efc17 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -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, diff --git a/src/machine/machine.c b/src/machine/machine.c index 7ce512dd62..91c1450184 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -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) diff --git a/src/machine/machine.h b/src/machine/machine.h index 8762263a8b..dddc0c8000 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -38,6 +38,8 @@ typedef struct Machine { char *name; sd_id128_t id; + uid_t uid; + MachineClass class; char *state_file; diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 7731fae708..d31ef407f4 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -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) }, {} }; diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 8aa00ba3ed..30f722a449 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -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); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 8551d703e7..7dac3cb0d2 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -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; diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 1bb7c00fa1..ad7dff228f 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -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,