udev 如何/在哪里授予 X11 输入驱动程序打开 /dev/input/event* 文件的权限,而不同时授予登录用户访问权限?

udev 如何/在哪里授予 X11 输入驱动程序打开 /dev/input/event* 文件的权限,而不同时授予登录用户访问权限?

这是我的 /dev/input/event* 文件的权限:

crw-rw---- 1 root input 13, 64 Mar 21 09:02 /dev/input/event0
crw-rw---- 1 root input 13, 65 Mar 21 09:02 /dev/input/event1
crw-rw---- 1 root input 13, 66 Mar 21 09:02 /dev/input/event2
crw-rw---- 1 root input 13, 67 Mar 21 09:02 /dev/input/event3
crw-rw---- 1 root input 13, 68 Mar 21 09:02 /dev/input/event4
...

+正如您所看到的,权限后面没有,这意味着没有特殊的 ACL 权限。我也用 确认过getfacl。我也不是该input组的成员,也没有以 root 身份运行 X11。我只是startx以用户身份登录后在控制台手动输入来启动 xorg。

所以我的问题是如何在哪里udev 是否​​授予 X11 输入驱动程序(例如 xf86-input-libinput)在没有 ACL 的情况下打开这些文件的权限?

如果我想打开 /dev/input/event 文件,我必须使用sudo或成为组的一部分input,但 rootless X11 似乎能够毫无问题地做到这一点!


这是一个最小的 C 程序来演示权限问题。如果 keybit_limit 设置为低于 578 的值,X11 驱动程序将有权读取相应的 /dev/input/event,并且具有您给定名称的设备将显示在 xinput 输出中。任何高于 KEY_CNT 的值都会导致 Xorg 日志中出现权限错误,并且 xinput 将不会显示新设备。尽管您仍然可以看到带有 的设备sudo evtest。两种情况下 /dev/input/event 的权限和组完全相同,但在 KEY_CNT 场景下 X11 无法读取它,并且 1 2 3 键不会在 Xorg 中注册。

#include <stdio.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/uinput.h>

#define ERROR(format, ...) {                                             \
    fprintf(stderr, "\x1b[31merror: " format "\x1b[0m\n", ##__VA_ARGS__); \
    return 1;                                                            \
}

#define SEND_EVENT(ev_type, ev_code, ev_value) { \
    uev.type = ev_type;                          \
    uev.code = ev_code;                          \
    uev.value = ev_value;                        \
    write(ufd, &uev, sizeof(uev));               \
}

int main(int argc, char *argv[]) {

    if (argc < 2) ERROR("needs uinput device name!");

    int ufd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
    if (ufd < 0) ERROR("could not open '/dev/uinput'");
    ioctl(ufd, UI_SET_EVBIT, EV_KEY);

    int keybit_limit;

    /* X11 will recognize this device for me */
    // keybit_limit = 577;

    /* but anything above that will cause permission denied errors in xorg log and xinput will not show the device */
    keybit_limit = KEY_CNT;

    for (int i = 0; i < keybit_limit; i++) {
        if (ioctl(ufd, UI_SET_KEYBIT, i) < 0) ERROR("cannot set uinput keybit: %d", i);
    }

    struct uinput_setup usetup;
    memset(&usetup, 0, sizeof(usetup));
    usetup.id.bustype = BUS_USB;
    strcpy(usetup.name, argv[1]);
    if (ioctl(ufd, UI_DEV_SETUP, &usetup) < 0) ERROR("cannot set up uinput device");
    if (ioctl(ufd, UI_DEV_CREATE) < 0) ERROR("cannot create uinput device");
    struct input_event uev;
    uev.time.tv_sec = 0;
    uev.time.tv_usec = 0;

    sleep(1);

    /* press 1 2 3 */
    SEND_EVENT(EV_KEY, KEY_1, 1);
    SEND_EVENT(EV_KEY, KEY_2, 1);
    SEND_EVENT(EV_KEY, KEY_3, 1);
    SEND_EVENT(EV_SYN, SYN_REPORT, 0);

    /* release 1 2 3 */
    SEND_EVENT(EV_KEY, KEY_1, 0);
    SEND_EVENT(EV_KEY, KEY_2, 0);
    SEND_EVENT(EV_KEY, KEY_3, 0);
    SEND_EVENT(EV_SYN, SYN_REPORT, 0);

    /* give you time to check xinput */
    sleep(300);

    ioctl(ufd, UI_DEV_DESTROY);
    close(ufd);
    return 0;
}

keybit_limit = KEY_CNT以下是当传递给程序的 uinput 设备名称为“MYDEVICE”时 ~/.local/share/xorg/Xorg.0.log 文件中的权限错误:

[ 28717.931] (II) config/udev: Adding input device MYDEVICE (/dev/input/event24)
[ 28717.931] (**) MYDEVICE: Applying InputClass "libinput pointer catchall"
[ 28717.931] (**) MYDEVICE: Applying InputClass "libinput keyboard catchall"
[ 28717.931] (**) MYDEVICE: Applying InputClass "system-keyboard"
[ 28717.931] (II) Using input driver 'libinput' for 'MYDEVICE'
[ 28717.933] (EE) systemd-logind: failed to take device /dev/input/event24: No such device
[ 28717.933] (**) MYDEVICE: always reports core events
[ 28717.933] (**) Option "Device" "/dev/input/event24"
[ 28717.933] (EE) xf86OpenSerial: Cannot open device /dev/input/event24
    Permission denied.
[ 28717.933] (II) event24: opening input device '/dev/input/event24' failed (Permission denied).
[ 28717.933] (II) event24 - failed to create input device '/dev/input/event24'.
[ 28717.933] (EE) libinput: MYDEVICE: Failed to create a device for /dev/input/event24
[ 28717.933] (EE) PreInit returned 2 for "MYDEVICE"
[ 28717.933] (II) UnloadModule: "libinput"

我已经使用 xorg.conf.d 文件测试了 X11 的 evdev 和 libinput 驱动程序,两者的行为相同。如果我将自己放入input组中或在设备的 udev 规则中使用 uaccess 标记,则 X11 驱动程序可以读取它。这表明在 <578 场景中设备被读取为 root,但在 KEY_CNT 场景中设备被读取为用户。

这是为什么?哪个进程正在这样做?

答案1

Xorg 不直接打开设备节点 – 它对设备进行 D-Bus IPC 调用系统登录服务,它代表调用者打开设备节点(在检查诸如哪个用户在前台 tty 上“登录”之类的事情之后)并使用 D-Bus 的 fd 传递功能将文件描述符转发到 Xorg(基于Unix 套接字中的 SCM_CREDENTIALS 功能)。

org.freedesktop.login1(5)对于相关的TakeDevice()D-Bus API。

当前台 tty 切换到另一个用户的会话时,此方法允许 systemd-logind 主动撤销对输入设备的访问(相反,设置和删除 ACL 将允许一个用户的程序简单地保持文件描述符打开并继续读取输入,即使另一个用户的会话是前台)。

对于非 systemd 发行版,seatd 提供了功能类似的 API (libseat_open_device),但 Xorg 尚不支持它,而是依赖于 setuidXorg.wrap包装器。

(撤销机制是 ioctl(EVIOCREVOKE),特定于输入设备。DRM 设备有类似的机制,但到目前为止,音频或摄像头设备还没有等效的机制;诸如 pipeline 之类的声音服务器监听登录的“会话切换”信号,并且配合关闭和重新打开设备。)

答案2

Udev 不“授予 X11 输入驱动程序权限”。 Udev 已经创建了您显示的设备节点,一旦完成,它有关输入设备的工作就基本上完成了。系统的其余部分必须处理它指定的设备权限。

startx/usr/bin/xinit通常是用于完成其工作的脚本。

事实证明,在现代 Xorg X 服务器中,setuid root 权限是作为一个小的独立包装器/usr/lib/xorg/Xorg.wrap.手册Xwrapper.config(5)页说:

描述

Xorg X 服务器可能需要 root 权限才能正常运行。要使用这些权限启动 Xorg X 服务器,您的系统将使用安装为 /usr/lib/xorg/Xorg.wrap 的 suid root 包装器,它将执行安装为 的真实 X 服务器/usr/lib/xorg/Xorg

默认情况下,Xorg.wrap 将自动检测是否需要 root 权限,如果不需要,它将在启动真正的 X 服务器之前放弃其提升的权限。默认情况下,Xorg.wrap 只允许从物理控制台上的登录会话执行真正的 X 服务器。

这是在 Debian 11 上;您的发行版可能使用有些不同的路径。

跑步ls -l /usr/bin/xinit /usr/bin/Xorg /usr/lib/xorg/Xorg.wrap。如果它报告的权限包括 代替s通常的x,那么您的答案是:如果s位于第一个x位置 ( -rws......),则可执行文件将以该权限的权限运行文件的所有者(这通常是此类情况的根源)。这通常被称为可执行文件setuid 根或者水根

如果s处于第二位置,则该进程将获得以下成员资格:拥有可执行文件的组 运行时:这被称为可执行文件设置gid或者sgid到那个小组。 (请注意,在目录或不可执行文件上,setgid 权限位可以具有其他含义,具体取决于所使用的文件系统类型。)

请注意,复制文件时,通常不会复制 setuid 和 setgid 权限位:即使复制,当您复制 root 拥有的文件时,新副本将归您所有,而不是 root 拥有。它可能允许其他用户运行复制的可执行文件,但它不会帮助您成为 root。要复制setuid 根文件同时完全保留权限,您必须自己成为 root。

相关内容