1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-19 22:50:17 +03:00

login,udev: avoid race between systemd-logind and systemd-udevd in setting ACLs

Previously, both udevd and logind modifies ACLs of a device node. Hence,
there exists a race something like the following:
1. udevd reads an old state file,
2. logind updates the state file, and apply new ACLs,
3. udevd applies ACLs based on the old state file.

This makes logind not update ACLs but trigger uevents for relevant
devices to make ACLs updated by udevd.
This commit is contained in:
Yu Watanabe 2025-02-27 10:04:01 +09:00
parent 31bca25517
commit b900e558f0
3 changed files with 112 additions and 33 deletions

View File

@ -8,12 +8,13 @@
#include "sd-messages.h"
#include "alloc-util.h"
#include "devnode-acl.h"
#include "device-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
#include "id128-util.h"
#include "logind-seat-dbus.h"
#include "logind-seat.h"
#include "logind-session-dbus.h"
@ -73,6 +74,7 @@ Seat* seat_free(Seat *s) {
hashmap_remove(s->manager->seats, s->id);
set_free(s->uevents);
free(s->positions);
free(s->state_file);
free(s->id);
@ -202,19 +204,107 @@ int seat_preallocate_vts(Seat *s) {
return r;
}
int seat_apply_acls(Seat *s, Session *old_active) {
static void seat_triggered_uevents_done(Seat *s) {
assert(s);
if (!set_isempty(s->uevents))
return;
Session *session = s->active;
if (session) {
session_save(session);
user_save(session->user);
}
if (session && session->started) {
session_send_changed(session, "Active", NULL);
session_device_resume_all(session);
}
if (!session || session->started)
seat_send_changed(s, "ActiveSession", NULL);
}
int manager_process_device_triggered_by_seat(Manager *m, sd_device *dev) {
assert(m);
assert(dev);
sd_id128_t uuid;
if (sd_device_get_trigger_uuid(dev, &uuid) < 0)
return 0;
Seat *s;
HASHMAP_FOREACH(s, m->seats)
if (set_contains(s->uevents, &uuid))
break;
if (!s)
return 0;
free(ASSERT_PTR(set_remove(s->uevents, &uuid)));
seat_triggered_uevents_done(s);
const char *id;
if (sd_device_get_property_value(dev, "ID_SEAT", &id) < 0 || isempty(id))
id = "seat0";
if (!streq(id, s->id)) {
log_device_debug(dev, "ID_SEAT is changed in the triggered uevent: \"%s\" -> \"%s\"", s->id, id);
return 0;
}
return 1; /* The uevent is triggered by the relevant seat. */
}
static int seat_trigger_devices(Seat *s) {
int r;
assert(s);
r = devnode_acl_all(s->id,
false,
!!old_active, old_active ? old_active->user->user_record->uid : 0,
!!s->active, s->active ? s->active->user->user_record->uid : 0);
set_clear(s->uevents);
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
r = sd_device_enumerator_new(&e);
if (r < 0)
return log_error_errno(r, "Failed to apply ACLs: %m");
return r;
r = sd_device_enumerator_add_match_tag(e, "uaccess");
if (r < 0)
return r;
FOREACH_DEVICE(e, d) {
/* Verify that the tag is still in place. */
if (sd_device_has_current_tag(d, "uaccess") <= 0)
continue;
/* In case people mistag devices with nodes, we need to ignore this. */
if (sd_device_get_devname(d, NULL) < 0)
continue;
const char *id;
if (sd_device_get_property_value(d, "ID_SEAT", &id) < 0 || isempty(id))
id = "seat0";
if (!streq(id, s->id))
continue;
sd_id128_t uuid;
r = sd_device_trigger_with_uuid(d, SD_DEVICE_CHANGE, &uuid);
if (r < 0) {
log_device_debug_errno(d, r, "Failed to trigger 'change' event, ignoring: %m");
continue;
}
_cleanup_free_ sd_id128_t *copy = newdup(sd_id128_t, &uuid, 1);
if (!copy)
return -ENOMEM;
r = set_ensure_consume(&s->uevents, &id128_hash_ops_free, TAKE_PTR(copy));
if (r < 0)
return r;
}
seat_triggered_uevents_done(s);
return 0;
}
@ -232,7 +322,7 @@ int seat_set_active(Seat *s, Session *session) {
* Therefore, if the active session has executed session_leave_vt ,
* A resume is required here. */
if (session == s->active) {
if (session) {
if (session && set_isempty(s->uevents)) {
log_debug("Active session remains unchanged, resuming session devices.");
session_device_resume_all(session);
}
@ -245,32 +335,13 @@ int seat_set_active(Seat *s, Session *session) {
seat_save(s);
if (old_active) {
user_save(old_active->user);
session_save(old_active);
session_device_pause_all(old_active);
session_send_changed(old_active, "Active", NULL);
}
(void) seat_apply_acls(s, old_active);
if (session && session->started) {
session_send_changed(session, "Active", NULL);
session_device_resume_all(session);
}
if (!session || session->started)
seat_send_changed(s, "ActiveSession", NULL);
if (session) {
session_save(session);
user_save(session->user);
}
if (old_active) {
session_save(old_active);
if (!session || session->user != old_active->user)
user_save(old_active->user);
}
return 0;
return seat_trigger_devices(s);
}
static Session* seat_get_position(Seat *s, unsigned pos) {

View File

@ -3,6 +3,8 @@
typedef struct Seat Seat;
#include "sd-device.h"
#include "list.h"
#include "logind-session.h"
@ -14,6 +16,8 @@ struct Seat {
LIST_HEAD(Device, devices);
Set *uevents;
Session *active;
Session *pending_switch;
LIST_HEAD(Session, sessions);
@ -34,7 +38,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Seat*, seat_free);
int seat_save(Seat *s);
int seat_load(Seat *s);
int seat_apply_acls(Seat *s, Session *old_active);
int manager_process_device_triggered_by_seat(Manager *m, sd_device *dev);
int seat_set_active(Seat *s, Session *session);
int seat_switch_to(Seat *s, unsigned num);
int seat_switch_to_next(Seat *s);

View File

@ -804,8 +804,11 @@ static int manager_dispatch_device_udev(sd_device_monitor *monitor, sd_device *d
assert(device);
if (device_in_subsystems(device, STRV_MAKE("input", "graphics", "drm")) ||
sd_device_has_current_tag(device, "master-of-seat") > 0)
/* If the event is triggered by us, do not try to start the relevant seat again. Otherwise, starting
* the seat may trigger uevents again again again... */
if (manager_process_device_triggered_by_seat(m, device) <= 0 &&
(device_in_subsystems(device, STRV_MAKE("input", "graphics", "drm")) ||
sd_device_has_current_tag(device, "master-of-seat") > 0))
(void) manager_process_seat_device(m, device);
if (!manager_all_buttons_ignored(m) &&