1
0
mirror of https://github.com/systemd/systemd synced 2026-03-13 00:24:48 +01:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Lennart Poettering
5276ef1548
udev: Introduce uaccess for remote graphical sessions (#38516)
When systemd is compiled with group-render-mode=0660, only the active
seat gets access to the render devices through uaccess. Remote desktop
sessions like gnome-remote-desktop would be left with no hardware
rendering, because those sessions are not associated with a seat.

We solve the issue by granting uaccess to specifically tagged devices on
session start, if the session is marked with
XDG_SESSION_EXTRA_DEVICE_ACCESS.

udev-builtin-uaccess is refactored to grant multiple users access to a
device, taking into account the device's seat and all the active
EXTRA_DEVICE_ACCESS sessions.
2026-02-08 21:41:21 +01:00
Alessandro Astone
30a1a29d4e man/sd-login: Document the uaccess and xaccess udev tags
Fixes: #4288
2026-02-06 17:42:17 +01:00
Alessandro Astone
563b5dedb7 man: Update docs for ExtraDeviceAccess 2026-02-06 16:20:18 +01:00
Alessandro Astone
753341a221 rules: Tag DRM render nodes with xaccess
When systemd is compiled with group-render-mode=0660, only the active seat
gets access to the render devices through uaccess. Remote desktop sessions
like gnome-remote-desktop would be left with no hardware rendering, because
those sessions are not associated with a seat.

Tag the render nodes with "xaccess" so that access is also granted to remote
sessions created with XDG_SESSION_EXTRA_DEVICE_ACCESS=1
2026-02-06 16:20:18 +01:00
Alessandro Astone
5a198ad6f8 udev: Grant sessions access to devices tagged with xaccess
Grant access to devices tagged with "xaccess" on session start, if the session
was created with XDG_SESSION_EXTRA_DEVICE_ACCESS=1.

udev-builtin-uaccess is refactored to grant multiple users access to a device,
taking into account the device's seat and all the active EXTRA_DEVICE_ACCESS
sessions.
2026-02-06 16:20:18 +01:00
Alessandro Astone
87840e144b login: Add XDG_SESSION_EXTRA_DEVICE_ACCESS variable for additional access
A session created with XDG_SESSION_EXTRA_DEVICE_ACCESS will be granted
additional powers.
Exactly which powers are granted is going to be defined by udevd.
2026-02-06 16:20:18 +01:00
22 changed files with 298 additions and 76 deletions

View File

@ -1246,6 +1246,8 @@ node /org/freedesktop/login1/session/1 {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const") @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RemoteUser = '...'; readonly s RemoteUser = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const") @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ExtraDeviceAccess = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s Service = '...'; readonly s Service = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const") @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s Desktop = '...'; readonly s Desktop = '...';
@ -1348,6 +1350,8 @@ node /org/freedesktop/login1/session/1 {
<variablelist class="dbus-property" generated="True" extra-ref="RemoteUser"/> <variablelist class="dbus-property" generated="True" extra-ref="RemoteUser"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExtraDeviceAccess"/>
<variablelist class="dbus-property" generated="True" extra-ref="Service"/> <variablelist class="dbus-property" generated="True" extra-ref="Service"/>
<variablelist class="dbus-property" generated="True" extra-ref="Desktop"/> <variablelist class="dbus-property" generated="True" extra-ref="Desktop"/>
@ -1537,6 +1541,10 @@ node /org/freedesktop/login1/session/1 {
<para><varname>RemoteHost</varname> and <varname>RemoteUser</varname> encode the remote host and user <para><varname>RemoteHost</varname> and <varname>RemoteUser</varname> encode the remote host and user
if this is a remote session, or an empty string otherwise.</para> if this is a remote session, or an empty string otherwise.</para>
<para><varname>ExtraDeviceAccess</varname> encodes whether the session is granted access to additional
hardware devices, typically useful for for graphical, remote session. If true, the session is granted
access to all devices tagged with <literal>xaccess</literal> in udev.</para>
<para><varname>Service</varname> encodes the PAM service name that registered the session.</para> <para><varname>Service</varname> encodes the PAM service name that registered the session.</para>
<para><varname>Desktop</varname> describes the desktop environment running in the session (if <para><varname>Desktop</varname> describes the desktop environment running in the session (if
@ -1671,6 +1679,7 @@ node /org/freedesktop/login1/session/1 {
<para><function>SetTTY()</function> was added in version 254.</para> <para><function>SetTTY()</function> was added in version 254.</para>
<para><function>SetClass()</function> was added in version 256.</para> <para><function>SetClass()</function> was added in version 256.</para>
<para><varname>LeaderPIDFDId</varname> was added in version 258.</para> <para><varname>LeaderPIDFDId</varname> was added in version 258.</para>
<para><varname>ExtraDeviceAccess</varname> was added in version 260.</para>
</refsect2> </refsect2>
</refsect1> </refsect1>

View File

@ -404,6 +404,17 @@
<xi:include href="version-info.xml" xpointer="v258"/></listitem> <xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>$XDG_SESSION_EXTRA_DEVICE_ACCESS</varname></term>
<listitem><para>Whether or not the session shall be granted additional hardware device access,
typically useful for graphical, remote session. If true, the session is granted access to all
devices tagged with <literal>xaccess</literal> in udev. Typically, rendering device nodes of
the GPU are tagged like this.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
</variablelist> </variablelist>
<para>If not set, <command>pam_systemd</command> will initialize <para>If not set, <command>pam_systemd</command> will initialize

View File

@ -182,6 +182,25 @@
<xi:include href="version-info.xml" xpointer="v235"/></listitem> <xi:include href="version-info.xml" xpointer="v235"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>Tag <literal>uaccess</literal></term>
<listitem><para>When set, access to this device is tied to an active seat.
As the session on the seat becomes active or inactive, access to the device
is updated accordingly.</para></listitem>
</varlistentry>
<varlistentry>
<term>Tag <literal>xaccess</literal></term>
<listitem><para>When set, access to this device is granted to sessions
created with <literal>ExtraDeviceAccess</literal>. This is typically useful
for graphical, remote sessions. As the <literal>ExtraDeviceAccess</literal>
sessions open and close, access to the device is updated accordingly.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term>Property <varname>ID_SEAT</varname></term> <term>Property <varname>ID_SEAT</varname></term>

View File

@ -34,6 +34,7 @@
<refname>sd_session_get_remote_host</refname> <refname>sd_session_get_remote_host</refname>
<refname>sd_session_get_remote_user</refname> <refname>sd_session_get_remote_user</refname>
<refname>sd_session_get_leader</refname> <refname>sd_session_get_leader</refname>
<refname>sd_session_has_extra_device_access</refname>
<refpurpose>Determine state of a specific session</refpurpose> <refpurpose>Determine state of a specific session</refpurpose>
</refnamediv> </refnamediv>
@ -140,6 +141,11 @@
<paramdef>const char *<parameter>session</parameter></paramdef> <paramdef>const char *<parameter>session</parameter></paramdef>
<paramdef>unsigned int *<parameter>vt</parameter></paramdef> <paramdef>unsigned int *<parameter>vt</parameter></paramdef>
</funcprototype> </funcprototype>
<funcprototype>
<funcdef>int <function>sd_session_has_extra_device_access</function></funcdef>
<paramdef>const char *<parameter>session</parameter></paramdef>
</funcprototype>
</funcsynopsis> </funcsynopsis>
</refsynopsisdiv> </refsynopsisdiv>
@ -274,6 +280,12 @@
session identifier. This function will return an error if the seat session identifier. This function will return an error if the seat
does not support VTs.</para> does not support VTs.</para>
<para><function>sd_session_has_extra_device_access()</function> may
be used to determine whether the session is granted access to
additional hardware devices, typically useful for for graphical,
remote session. If true, the session is granted access to all
devices tagged with <literal>xaccess</literal> in udev.</para>
<para>If the <varname>session</varname> parameter of any of these <para>If the <varname>session</varname> parameter of any of these
functions is passed as <constant>NULL</constant>, the operation is functions is passed as <constant>NULL</constant>, the operation is
executed for the session the calling process is a member of, if executed for the session the calling process is a member of, if
@ -284,9 +296,10 @@
<title>Return Value</title> <title>Return Value</title>
<para>If the test succeeds, <para>If the test succeeds,
<function>sd_session_is_active()</function> and <function>sd_session_is_active()</function>,
<function>sd_session_is_remote()</function> return a <function>sd_session_is_remote()</function>, and
positive integer; if it fails, 0. On success, <function>sd_session_has_extra_device_access()</function>
return a positive integer; if it fails, 0. On success,
<function>sd_session_get_state()</function>, <function>sd_session_get_state()</function>,
<function>sd_session_get_uid()</function>, <function>sd_session_get_uid()</function>,
<function>sd_session_get_username()</function>, <function>sd_session_get_username()</function>,
@ -353,6 +366,7 @@
<para><function>sd_session_get_username()</function>, <para><function>sd_session_get_username()</function>,
<function>sd_session_get_start_time()</function>, and <function>sd_session_get_start_time()</function>, and
<function>sd_session_get_leader()</function> were added in version 254.</para> <function>sd_session_get_leader()</function> were added in version 254.</para>
<para><function>sd_session_has_extra_device_access()</function> was added in version 260.</para>
</refsect1> </refsect1>
<refsect1> <refsect1>

View File

@ -46,7 +46,7 @@ SUBSYSTEM=="firewire", TEST=="units", ENV{IEEE1394_UNIT_FUNCTION_VIDEO}=="1", TA
SUBSYSTEM=="drm", KERNEL=="card*", TAG+="uaccess" SUBSYSTEM=="drm", KERNEL=="card*", TAG+="uaccess"
{% if GROUP_RENDER_UACCESS %} {% if GROUP_RENDER_UACCESS %}
# DRI render nodes # DRI render nodes
SUBSYSTEM=="drm", KERNEL=="renderD*", TAG+="uaccess" SUBSYSTEM=="drm", KERNEL=="renderD*", TAG+="uaccess", TAG+="xaccess"
{% endif %} {% endif %}
{% if DEV_KVM_UACCESS %} {% if DEV_KVM_UACCESS %}
# KVM # KVM

View File

@ -14,7 +14,7 @@ ENV{ID_SEAT}=="", IMPORT{parent}="ID_SEAT"
ENV{ID_SEAT}!="", TAG+="$env{ID_SEAT}" ENV{ID_SEAT}!="", TAG+="$env{ID_SEAT}"
{% if HAVE_ACL %} {% if HAVE_ACL %}
TAG=="uaccess", ENV{MAJOR}!="", RUN{builtin}+="uaccess" TAG=="uaccess|xaccess", ENV{MAJOR}!="", RUN{builtin}+="uaccess"
{% endif %} {% endif %}
LABEL="seat_late_end" LABEL="seat_late_end"

View File

@ -697,6 +697,7 @@ int manager_default_environment(Manager *m) {
"XDG_SESSION_CLASS", "XDG_SESSION_CLASS",
"XDG_SESSION_TYPE", "XDG_SESSION_TYPE",
"XDG_SESSION_DESKTOP", "XDG_SESSION_DESKTOP",
"XDG_SESSION_EXTRA_DEVICE_ACCESS",
"XDG_SEAT", "XDG_SEAT",
"XDG_VTNR"); "XDG_VTNR");
} }

View File

@ -1085,3 +1085,8 @@ global:
sd_event_get_exit_on_idle; sd_event_get_exit_on_idle;
sd_varlink_is_connected; sd_varlink_is_connected;
} LIBSYSTEMD_258; } LIBSYSTEMD_258;
LIBSYSTEMD_260 {
global:
sd_session_has_extra_device_access;
} LIBSYSTEMD_259;

View File

@ -677,6 +677,25 @@ _public_ int sd_session_is_remote(const char *session) {
return parse_boolean(s); return parse_boolean(s);
} }
_public_ int sd_session_has_extra_device_access(const char *session) {
_cleanup_free_ char *p = NULL, *s = NULL;
int r;
r = file_of_session(session, &p);
if (r < 0)
return r;
r = parse_env_file(/* f= */ NULL, p, "EXTRA_DEVICE_ACCESS", &s);
if (r == -ENOENT)
return -ENXIO;
if (r < 0)
return r;
if (isempty(s))
return -ENODATA;
return parse_boolean(s);
}
_public_ int sd_session_get_state(const char *session, char **ret_state) { _public_ int sd_session_get_state(const char *session, char **ret_state) {
_cleanup_free_ char *p = NULL, *s = NULL; _cleanup_free_ char *p = NULL, *s = NULL;
int r; int r;

View File

@ -898,6 +898,7 @@ int manager_create_session(
bool remote, bool remote,
const char *remote_user, const char *remote_user,
const char *remote_host, const char *remote_host,
bool extra_device_access,
Session **ret_session) { Session **ret_session) {
bool mangle_class = false; bool mangle_class = false;
@ -1004,6 +1005,7 @@ int manager_create_session(
session->original_type = session->type = type; session->original_type = session->type = type;
session->remote = remote; session->remote = remote;
session->extra_device_access = extra_device_access;
session->vtnr = vtnr; session->vtnr = vtnr;
session->class = class; session->class = class;
@ -1227,6 +1229,7 @@ static int manager_create_session_by_bus(
remote, remote,
remote_user, remote_user,
remote_host, remote_host,
/* extra_device_access= */ false,
&session); &session);
if (r == -EBUSY) if (r == -EBUSY)
return sd_bus_error_set(error, BUS_ERROR_SESSION_BUSY, "Already running in a session or user slice"); return sd_bus_error_set(error, BUS_ERROR_SESSION_BUSY, "Already running in a session or user slice");

View File

@ -57,6 +57,7 @@ int manager_create_session(
bool remote, bool remote,
const char *remote_user, const char *remote_user,
const char *remote_host, const char *remote_host,
bool extra_device_access,
Session **ret_session); Session **ret_session);
extern const BusObjectImplementation manager_object; extern const BusObjectImplementation manager_object;

View File

@ -35,6 +35,7 @@
#include "tmpfile-util.h" #include "tmpfile-util.h"
#include "udev-util.h" #include "udev-util.h"
#include "user-record.h" #include "user-record.h"
#include "user-util.h"
int seat_new(Manager *m, const char *id, Seat **ret) { int seat_new(Manager *m, const char *id, Seat **ret) {
_cleanup_(seat_freep) Seat *s = NULL; _cleanup_(seat_freep) Seat *s = NULL;
@ -330,14 +331,15 @@ static int seat_trigger_devices(Seat *s) {
static int static_node_acl(Seat *s) { static int static_node_acl(Seat *s) {
#if HAVE_ACL #if HAVE_ACL
int r, ret = 0; int r, ret = 0;
uid_t uid; _cleanup_set_free_ Set *uids = NULL;
assert(s); assert(s);
if (s->active) if (s->active) {
uid = s->active->user->user_record->uid; r = set_ensure_put(&uids, NULL, UID_TO_PTR(s->active->user->user_record->uid));
else if (r < 0)
uid = 0; return log_oom();
}
_cleanup_closedir_ DIR *dir = opendir("/run/udev/static_node-tags/uaccess/"); _cleanup_closedir_ DIR *dir = opendir("/run/udev/static_node-tags/uaccess/");
if (!dir) { if (!dir) {
@ -377,7 +379,7 @@ static int static_node_acl(Seat *s) {
if (!ERRNO_IS_NEG_DEVICE_ABSENT_OR_EMPTY(r)) if (!ERRNO_IS_NEG_DEVICE_ABSENT_OR_EMPTY(r))
log_debug_errno(r, "Failed to check if '/run/udev/static_node-tags/uaccess/%s' points to a static device node, ignoring: %m", de->d_name); log_debug_errno(r, "Failed to check if '/run/udev/static_node-tags/uaccess/%s' points to a static device node, ignoring: %m", de->d_name);
r = devnode_acl(fd, uid); r = devnode_acl(fd, uids);
if (r >= 0 || r == -ENOENT) if (r >= 0 || r == -ENOENT)
continue; continue;
@ -385,11 +387,11 @@ static int static_node_acl(Seat *s) {
_cleanup_free_ char *node = NULL; _cleanup_free_ char *node = NULL;
(void) fd_get_path(fd, &node); (void) fd_get_path(fd, &node);
if (uid != 0) { if (!set_isempty(uids)) {
RET_GATHER(ret, log_debug_errno(r, "Failed to apply ACL on '%s': %m", node ?: de->d_name)); RET_GATHER(ret, log_debug_errno(r, "Failed to apply ACL on '%s': %m", node ?: de->d_name));
/* Better be safe than sorry and reset ACL */ /* Better be safe than sorry and reset ACL */
r = devnode_acl(fd, /* uid= */ 0); r = devnode_acl(fd, /* uids= */ NULL);
if (r >= 0 || r == -ENOENT) if (r >= 0 || r == -ENOENT)
continue; continue;
} }

View File

@ -985,6 +985,7 @@ static const sd_bus_vtable session_vtable[] = {
SD_BUS_PROPERTY("Remote", "b", bus_property_get_bool, offsetof(Session, remote), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Remote", "b", bus_property_get_bool, offsetof(Session, remote), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RemoteHost", "s", NULL, offsetof(Session, remote_host), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RemoteHost", "s", NULL, offsetof(Session, remote_host), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RemoteUser", "s", NULL, offsetof(Session, remote_user), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RemoteUser", "s", NULL, offsetof(Session, remote_user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ExtraDeviceAccess", "b", bus_property_get_bool, offsetof(Session, extra_device_access), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Session, service), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Session, service), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Desktop", "s", NULL, offsetof(Session, desktop), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Desktop", "s", NULL, offsetof(Session, desktop), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Session, scope), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Session, scope), SD_BUS_VTABLE_PROPERTY_CONST),

View File

@ -9,6 +9,7 @@
#include <unistd.h> #include <unistd.h>
#include "sd-bus.h" #include "sd-bus.h"
#include "sd-device.h"
#include "sd-event.h" #include "sd-event.h"
#include "sd-messages.h" #include "sd-messages.h"
#include "sd-varlink.h" #include "sd-varlink.h"
@ -18,6 +19,7 @@
#include "bus-error.h" #include "bus-error.h"
#include "bus-util.h" #include "bus-util.h"
#include "daemon-util.h" #include "daemon-util.h"
#include "device-util.h"
#include "devnum-util.h" #include "devnum-util.h"
#include "env-file.h" #include "env-file.h"
#include "errno-util.h" #include "errno-util.h"
@ -276,6 +278,46 @@ static void session_save_devices(Session *s, FILE *f) {
} }
} }
static int trigger_xaccess(void) {
int r;
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
r = sd_device_enumerator_new(&e);
if (r < 0)
return r;
r = sd_device_enumerator_add_match_tag(e, "xaccess");
if (r < 0)
return r;
FOREACH_DEVICE(e, d) {
/* Verify that the tag is still in place. */
r = sd_device_has_current_tag(d, "xaccess");
if (r < 0)
return r;
if (r == 0)
continue;
/* In case people mistag devices without nodes, we need to ignore this. */
r = sd_device_get_devname(d, NULL);
if (r == -ENOENT)
continue;
if (r < 0)
return r;
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;
}
log_device_debug(d, "Triggered synthetic event (ACTION=change, UUID=%s).", SD_ID128_TO_UUID_STRING(uuid));
}
return 0;
}
int session_save(Session *s) { int session_save(Session *s) {
int r; int r;
@ -307,12 +349,14 @@ int session_save(Session *s) {
"IS_DISPLAY=%s\n" "IS_DISPLAY=%s\n"
"STATE=%s\n" "STATE=%s\n"
"REMOTE=%s\n" "REMOTE=%s\n"
"EXTRA_DEVICE_ACCESS=%s\n"
"LEADER_FD_SAVED=%s\n", "LEADER_FD_SAVED=%s\n",
s->user->user_record->uid, s->user->user_record->uid,
one_zero(session_is_active(s)), one_zero(session_is_active(s)),
one_zero(s->user->display == s), one_zero(s->user->display == s),
session_state_to_string(session_get_state(s)), session_state_to_string(session_get_state(s)),
one_zero(s->remote), one_zero(s->remote),
one_zero(s->extra_device_access),
one_zero(s->leader_fd_saved)); one_zero(s->leader_fd_saved));
env_file_fputs_assignment(f, "USER=", s->user->user_record->user_name); env_file_fputs_assignment(f, "USER=", s->user->user_record->user_name);
@ -453,6 +497,7 @@ static int session_load_leader(Session *s, uint64_t pidfdid) {
int session_load(Session *s) { int session_load(Session *s) {
_cleanup_free_ char *remote = NULL, _cleanup_free_ char *remote = NULL,
*extra_device_access = NULL,
*seat = NULL, *seat = NULL,
*tty_validity = NULL, *tty_validity = NULL,
*vtnr = NULL, *vtnr = NULL,
@ -478,34 +523,35 @@ int session_load(Session *s) {
assert(s); assert(s);
r = parse_env_file(NULL, s->state_file, r = parse_env_file(NULL, s->state_file,
"REMOTE", &remote, "REMOTE", &remote,
"SCOPE", &s->scope, "EXTRA_DEVICE_ACCESS", &extra_device_access,
"SCOPE_JOB", &s->scope_job, "SCOPE", &s->scope,
"FIFO", &fifo_path, "SCOPE_JOB", &s->scope_job,
"SEAT", &seat, "FIFO", &fifo_path,
"TTY", &s->tty, "SEAT", &seat,
"TTY_VALIDITY", &tty_validity, "TTY", &s->tty,
"DISPLAY", &s->display, "TTY_VALIDITY", &tty_validity,
"REMOTE_HOST", &s->remote_host, "DISPLAY", &s->display,
"REMOTE_USER", &s->remote_user, "REMOTE_HOST", &s->remote_host,
"SERVICE", &s->service, "REMOTE_USER", &s->remote_user,
"DESKTOP", &s->desktop, "SERVICE", &s->service,
"VTNR", &vtnr, "DESKTOP", &s->desktop,
"STATE", &state, "VTNR", &vtnr,
"POSITION", &position, "STATE", &state,
"LEADER", &leader_pid, "POSITION", &position,
"LEADER_FD_SAVED", &leader_fd_saved, "LEADER", &leader_pid,
"LEADER_PIDFDID", &leader_pidfdid, "LEADER_FD_SAVED", &leader_fd_saved,
"TYPE", &type, "LEADER_PIDFDID", &leader_pidfdid,
"ORIGINAL_TYPE", &original_type, "TYPE", &type,
"CLASS", &class, "ORIGINAL_TYPE", &original_type,
"UID", &uid, "CLASS", &class,
"REALTIME", &realtime, "UID", &uid,
"MONOTONIC", &monotonic, "REALTIME", &realtime,
"CONTROLLER", &controller, "MONOTONIC", &monotonic,
"ACTIVE", &active, "CONTROLLER", &controller,
"DEVICES", &devices, "ACTIVE", &active,
"IS_DISPLAY", &is_display); "DEVICES", &devices,
"IS_DISPLAY", &is_display);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to read %s: %m", s->state_file); return log_error_errno(r, "Failed to read %s: %m", s->state_file);
@ -539,6 +585,12 @@ int session_load(Session *s) {
s->remote = k; s->remote = k;
} }
if (extra_device_access) {
k = parse_boolean(extra_device_access);
if (k >= 0)
s->extra_device_access = k;
}
if (vtnr) if (vtnr)
(void) safe_atou(vtnr, &s->vtnr); (void) safe_atou(vtnr, &s->vtnr);
@ -863,6 +915,9 @@ int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
if (s->seat) if (s->seat)
(void) seat_save(s->seat); (void) seat_save(s->seat);
if (s->extra_device_access)
(void) trigger_xaccess();
/* Send signals */ /* Send signals */
(void) session_send_signal(s, true); (void) session_send_signal(s, true);
(void) user_send_changed(s->user, "Display"); (void) user_send_changed(s->user, "Display");
@ -953,6 +1008,9 @@ int session_stop(Session *s, bool force) {
(void) session_save(s); (void) session_save(s);
(void) user_save(s->user); (void) user_save(s->user);
if (s->extra_device_access)
(void) trigger_xaccess();
return r; return r;
} }

View File

@ -121,6 +121,7 @@ typedef struct Session {
char *remote_host; char *remote_host;
char *service; char *service;
char *desktop; char *desktop;
bool extra_device_access;
char *scope; char *scope;
char *scope_job; char *scope_job;

View File

@ -145,6 +145,7 @@ typedef struct CreateSessionParameters {
int remote; int remote;
const char *remote_user; const char *remote_user;
const char *remote_host; const char *remote_host;
bool extra_device_access;
} CreateSessionParameters; } CreateSessionParameters;
static void create_session_parameters_done(CreateSessionParameters *p) { static void create_session_parameters_done(CreateSessionParameters *p) {
@ -156,19 +157,20 @@ static int vl_method_create_session(sd_varlink *link, sd_json_variant *parameter
int r; int r;
static const sd_json_dispatch_field dispatch_table[] = { static const sd_json_dispatch_field dispatch_table[] = {
{ "UID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(CreateSessionParameters, uid), SD_JSON_MANDATORY }, { "UID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(CreateSessionParameters, uid), SD_JSON_MANDATORY },
{ "PID", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(CreateSessionParameters, pid), SD_JSON_STRICT }, { "PID", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(CreateSessionParameters, pid), SD_JSON_STRICT },
{ "Service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, service), 0 }, { "Service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, service), 0 },
{ "Type", SD_JSON_VARIANT_STRING, json_dispatch_session_type, offsetof(CreateSessionParameters, type), SD_JSON_MANDATORY }, { "Type", SD_JSON_VARIANT_STRING, json_dispatch_session_type, offsetof(CreateSessionParameters, type), SD_JSON_MANDATORY },
{ "Class", SD_JSON_VARIANT_STRING, json_dispatch_session_class, offsetof(CreateSessionParameters, class), SD_JSON_MANDATORY }, { "Class", SD_JSON_VARIANT_STRING, json_dispatch_session_class, offsetof(CreateSessionParameters, class), SD_JSON_MANDATORY },
{ "Desktop", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, desktop), SD_JSON_STRICT }, { "Desktop", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, desktop), SD_JSON_STRICT },
{ "Seat", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, seat), 0 }, { "Seat", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, seat), 0 },
{ "VTNr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(CreateSessionParameters, vtnr), 0 }, { "VTNr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(CreateSessionParameters, vtnr), 0 },
{ "TTY", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, tty), 0 }, { "TTY", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, tty), 0 },
{ "Display", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, display), 0 }, { "Display", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, display), 0 },
{ "Remote", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(CreateSessionParameters, remote), 0 }, { "Remote", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(CreateSessionParameters, remote), 0 },
{ "RemoteUser", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, remote_user), 0 }, { "RemoteUser", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, remote_user), 0 },
{ "RemoteHost", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, remote_host), 0 }, { "RemoteHost", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, remote_host), 0 },
{ "ExtraDeviceAccess", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(CreateSessionParameters, extra_device_access), 0 },
{} {}
}; };
@ -178,6 +180,7 @@ static int vl_method_create_session(sd_varlink *link, sd_json_variant *parameter
.class = _SESSION_CLASS_INVALID, .class = _SESSION_CLASS_INVALID,
.type = _SESSION_TYPE_INVALID, .type = _SESSION_TYPE_INVALID,
.remote = -1, .remote = -1,
.extra_device_access = false,
}; };
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
@ -264,6 +267,7 @@ static int vl_method_create_session(sd_varlink *link, sd_json_variant *parameter
p.remote, p.remote,
p.remote_user, p.remote_user,
p.remote_host, p.remote_host,
p.extra_device_access,
&session); &session);
if (r == -EBUSY) if (r == -EBUSY)
return sd_varlink_error(link, "io.systemd.Login.AlreadySessionMember", /* parameters= */ NULL); return sd_varlink_error(link, "io.systemd.Login.AlreadySessionMember", /* parameters= */ NULL);

View File

@ -814,6 +814,7 @@ typedef struct SessionContext {
uint32_t vtnr; uint32_t vtnr;
const char *tty; const char *tty;
const char *display; const char *display;
bool extra_device_access;
bool remote; bool remote;
const char *remote_user; const char *remote_user;
const char *remote_host; const char *remote_host;
@ -1148,7 +1149,8 @@ static int register_session(
JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", c->display), JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", c->display),
SD_JSON_BUILD_PAIR_BOOLEAN("Remote", c->remote), SD_JSON_BUILD_PAIR_BOOLEAN("Remote", c->remote),
JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteUser", c->remote_user), JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteUser", c->remote_user),
JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteHost", c->remote_host)); JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteHost", c->remote_host),
JSON_BUILD_PAIR_CONDITION_BOOLEAN(c->extra_device_access, "ExtraDeviceAccess", c->extra_device_access));
if (r < 0) if (r < 0)
return pam_syslog_errno(pamh, LOG_ERR, r, return pam_syslog_errno(pamh, LOG_ERR, r,
"Failed to issue io.systemd.Login.CreateSession varlink call: %m"); "Failed to issue io.systemd.Login.CreateSession varlink call: %m");
@ -1315,6 +1317,10 @@ static int register_session(
if (r != PAM_SUCCESS) if (r != PAM_SUCCESS)
return r; return r;
r = update_environment(pamh, "XDG_SESSION_EXTRA_DEVICE_ACCESS", one_zero(c->extra_device_access));
if (r != PAM_SUCCESS)
return r;
r = update_environment(pamh, "XDG_SEAT", real_seat); r = update_environment(pamh, "XDG_SEAT", real_seat);
if (r != PAM_SUCCESS) if (r != PAM_SUCCESS)
return r; return r;
@ -1781,6 +1787,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
c.desktop = getenv_harder(pamh, "XDG_SESSION_DESKTOP", desktop_pam); c.desktop = getenv_harder(pamh, "XDG_SESSION_DESKTOP", desktop_pam);
c.area = getenv_harder(pamh, "XDG_AREA", area_pam); c.area = getenv_harder(pamh, "XDG_AREA", area_pam);
c.incomplete = getenv_harder_bool(pamh, "XDG_SESSION_INCOMPLETE", false); c.incomplete = getenv_harder_bool(pamh, "XDG_SESSION_INCOMPLETE", false);
c.extra_device_access = getenv_harder_bool(pamh, "XDG_SESSION_EXTRA_DEVICE_ACCESS", false);
r = pam_get_data_many( r = pam_get_data_many(
pamh, pamh,

View File

@ -8,6 +8,7 @@
#include "errno-util.h" #include "errno-util.h"
#include "extract-word.h" #include "extract-word.h"
#include "fd-util.h" #include "fd-util.h"
#include "set.h"
#include "string-util.h" #include "string-util.h"
#include "strv.h" #include "strv.h"
#include "user-util.h" #include "user-util.h"
@ -80,8 +81,9 @@ int dlopen_libacl(void) {
DLSYM_ARG(acl_to_any_text)); DLSYM_ARG(acl_to_any_text));
} }
int devnode_acl(int fd, uid_t uid) { int devnode_acl(int fd, const Set *uids) {
bool changed = false, found = false; _cleanup_set_free_ Set *found = NULL;
bool changed = false;
int r; int r;
assert(fd >= 0); assert(fd >= 0);
@ -107,12 +109,12 @@ int devnode_acl(int fd, uid_t uid) {
if (tag != ACL_USER) if (tag != ACL_USER)
continue; continue;
if (uid > 0) { if (!set_isempty(uids)) {
uid_t *u = sym_acl_get_qualifier(entry); uid_t *u = sym_acl_get_qualifier(entry);
if (!u) if (!u)
return -errno; return -errno;
if (*u == uid) { if (set_contains(uids, UID_TO_PTR(*u))) {
acl_permset_t permset; acl_permset_t permset;
if (sym_acl_get_permset(entry, &permset) < 0) if (sym_acl_get_permset(entry, &permset) < 0)
return -errno; return -errno;
@ -132,7 +134,10 @@ int devnode_acl(int fd, uid_t uid) {
changed = true; changed = true;
} }
found = true; r = set_ensure_put(&found, NULL, UID_TO_PTR(*u));
if (r < 0)
return r;
continue; continue;
} }
} }
@ -145,7 +150,16 @@ int devnode_acl(int fd, uid_t uid) {
if (r < 0) if (r < 0)
return -errno; return -errno;
if (!found && uid > 0) { void *p;
SET_FOREACH(p, uids) {
uid_t uid = PTR_TO_UID(p);
if (uid == 0)
continue;
if (set_contains(found, UID_TO_PTR(uid)))
continue;
if (sym_acl_create_entry(&acl, &entry) < 0) if (sym_acl_create_entry(&acl, &entry) < 0)
return -errno; return -errno;

View File

@ -38,7 +38,7 @@ extern DLSYM_PROTOTYPE(acl_to_any_text);
int dlopen_libacl(void); int dlopen_libacl(void);
int devnode_acl(int fd, uid_t uid); int devnode_acl(int fd, const Set *uids);
int calc_acl_mask_if_needed(acl_t *acl_p); int calc_acl_mask_if_needed(acl_t *acl_p);
int add_base_acls_if_needed(acl_t *acl_p, const char *path); int add_base_acls_if_needed(acl_t *acl_p, const char *path);
@ -89,7 +89,7 @@ static inline int dlopen_libacl(void) {
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
static inline int devnode_acl(int fd, uid_t uid) { static inline int devnode_acl(int fd, const Set *uids) {
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }

View File

@ -65,6 +65,10 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_DEFINE_INPUT(RemoteUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(RemoteUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Host name of the remote host"), SD_VARLINK_FIELD_COMMENT("Host name of the remote host"),
SD_VARLINK_DEFINE_INPUT(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("If true this session is granted access to additional hardware devices, "
"typically useful for remote, graphical sessions. "
"This adds access for all devices tagged with \"xaccess\" in udev."),
SD_VARLINK_DEFINE_INPUT(ExtraDeviceAccess, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The identifier string of the session of the user."), SD_VARLINK_FIELD_COMMENT("The identifier string of the session of the user."),
SD_VARLINK_DEFINE_OUTPUT(Id, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_OUTPUT(Id, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The runtime path ($XDG_RUNTIME_DIR) of the user."), SD_VARLINK_FIELD_COMMENT("The runtime path ($XDG_RUNTIME_DIR) of the user."),

View File

@ -150,6 +150,9 @@ int sd_session_is_active(const char *session);
/* Return 1 if the session is remote. */ /* Return 1 if the session is remote. */
int sd_session_is_remote(const char *session); int sd_session_is_remote(const char *session);
/* Return 1 if the session is granted extra device access. */
int sd_session_has_extra_device_access(const char *session);
/* Get state from session. Possible states: online, active, closing. /* Get state from session. Possible states: online, active, closing.
* This function is a more generic version of sd_session_is_active(). */ * This function is a more generic version of sd_session_is_active(). */
int sd_session_get_state(const char *session, char **ret_state); int sd_session_get_state(const char *session, char **ret_state);

View File

@ -3,15 +3,23 @@
#include "sd-login.h" #include "sd-login.h"
#include "acl-util.h" #include "acl-util.h"
#include "alloc-util.h"
#include "device-util.h" #include "device-util.h"
#include "errno-util.h" #include "errno-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "login-util.h" #include "login-util.h"
#include "set.h"
#include "string-util.h"
#include "strv.h"
#include "udev-builtin.h" #include "udev-builtin.h"
#include "user-util.h"
static int builtin_uaccess(UdevEvent *event, int argc, char *argv[]) { static int builtin_uaccess(UdevEvent *event, int argc, char *argv[]) {
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
int r, k; _cleanup_strv_free_ char **sessions = NULL;
_cleanup_set_free_ Set *uids = NULL;
uid_t uid;
int r = 0, k;
if (event->event_mode != EVENT_UDEV_WORKER) { if (event->event_mode != EVENT_UDEV_WORKER) {
log_device_debug(dev, "Running in test mode, skipping execution of 'uaccess' builtin command."); log_device_debug(dev, "Running in test mode, skipping execution of 'uaccess' builtin command.");
@ -33,24 +41,62 @@ static int builtin_uaccess(UdevEvent *event, int argc, char *argv[]) {
return ignore ? 0 : fd; return ignore ? 0 : fd;
} }
const char *seat; r = sd_device_has_tag(dev, "uaccess");
r = device_get_seat(dev, &seat);
if (r < 0) if (r < 0)
return log_device_error_errno(dev, r, "Failed to get seat: %m"); return log_device_error_errno(dev, r, "Failed to query uaccess tag: %m");
uid_t uid; if (r > 0) {
r = sd_seat_get_active(seat, /* ret_session= */ NULL, &uid); const char *seat;
if (r < 0) { r = device_get_seat(dev, &seat);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to get seat: %m");
r = sd_seat_get_active(seat, /* ret_session= */ NULL, &uid);
if (IN_SET(r, -ENXIO, -ENODATA)) if (IN_SET(r, -ENXIO, -ENODATA))
/* No active session on this seat */ /* No active session on this seat */
r = 0; r = 0;
else else if (r < 0)
log_device_error_errno(dev, r, "Failed to determine active user on seat %s: %m", seat); log_device_error_errno(dev, r, "Failed to determine active user on seat %s, ignoring: %m", seat);
else {
goto reset; if (set_ensure_put(&uids, NULL, UID_TO_PTR(uid)) < 0)
return log_oom();
}
} }
r = devnode_acl(fd, uid); r = sd_device_has_tag(dev, "xaccess");
if (r < 0)
return log_device_error_errno(dev, r, "Failed to query device xaccess tag: %m");
if (r > 0) {
r = sd_get_sessions(&sessions);
if (r < 0)
return log_device_error_errno(dev, r, "Failed to list sessions: %m");
STRV_FOREACH(s, sessions) {
_cleanup_free_ char *state = NULL;
if (sd_session_get_state(*s, &state) < 0) {
log_device_debug_errno(dev, r, "Failed to query state for session %s, ignoring: %m", *s);
continue;
}
if (streq(state, "closing"))
continue;
r = sd_session_has_extra_device_access(*s);
if (r < 0) {
log_device_debug_errno(dev, r, "Failed to query extra device access for session %s, ignoring: %m", *s);
continue;
}
if (r == 0)
continue;
if (sd_session_get_uid(*s, &uid) < 0) {
log_device_debug_errno(dev, r, "Failed to query uid for session %s, ignoring: %m", *s);
continue;
}
if (set_ensure_put(&uids, NULL, UID_TO_PTR(uid)) < 0)
return log_oom();
}
}
r = devnode_acl(fd, uids);
if (r < 0) { if (r < 0) {
log_device_full_errno(dev, r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to apply ACL: %m"); log_device_full_errno(dev, r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to apply ACL: %m");
goto reset; goto reset;
@ -60,7 +106,7 @@ static int builtin_uaccess(UdevEvent *event, int argc, char *argv[]) {
reset: reset:
/* Better be safe than sorry and reset ACL */ /* Better be safe than sorry and reset ACL */
k = devnode_acl(fd, /* uid= */ 0); k = devnode_acl(fd, /* uids= */ NULL);
if (k < 0) if (k < 0)
RET_GATHER(r, log_device_full_errno(dev, k == -ENOENT ? LOG_DEBUG : LOG_ERR, k, "Failed to flush ACLs: %m")); RET_GATHER(r, log_device_full_errno(dev, k == -ENOENT ? LOG_DEBUG : LOG_ERR, k, "Failed to flush ACLs: %m"));