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.
This commit is contained in:
Lennart Poettering
2025-05-23 15:30:22 +02:00
parent d5feeb373c
commit 276d200186
9 changed files with 102 additions and 22 deletions

View 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>

View 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,

View 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,

View 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)

View File

@@ -38,6 +38,8 @@ typedef struct Machine {
char *name;
sd_id128_t id;
uid_t uid;
MachineClass class;
char *state_file;

View 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) },
{}
};

View 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);

View 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;

View 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,