这是我的 /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
组的一部分,但无根 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 key不会在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;
}
以下是 ~/.local/share/xorg/Xorg.0.log 文件中的权限错误,当keybit_limit = KEY_CNT
传递给程序的 uinput 设备名称为“MYDEVICE”时:
[ 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 场景中设备以用户身份读取。
这是为什么?哪个进程正在这样做?
Udev 不会“授予 X11 输入驱动程序权限”。Udev 已经创建了您显示的设备节点,一旦完成,它关于输入设备的工作就基本完成了。系统的其余部分将必须处理它指定的设备权限。
startx
/usr/bin/xinit
通常是用于完成其工作的脚本。事实证明,在现代 Xorg X 服务器中,setuid root 权限是作为一个小的独立包装器实现的,
/usr/lib/xorg/Xorg.wrap
. 手册Xwrapper.config(5)
页说:这是在 Debian 11 上;您的发行版可能使用了不同的路径。
运行
ls -l /usr/bin/xinit /usr/bin/Xorg /usr/lib/xorg/Xorg.wrap
。如果它报告的权限包含 ans
而不是通常的x
,那么您的答案是:如果 thes
位于第一个x
位置 ( ),则可执行文件将以文件所有者的-rws......
权限运行(在类似情况下通常是 root这)。这通常称为可执行文件是setuid root或suid root。如果在第二个位置,进程将在运行时获得拥有可执行文件的组
s
的成员身份 :这被称为可执行文件是该组的setgid或sgid 。(请注意,在目录或不可执行文件上,setgid 权限位可能有其他含义,具体取决于所使用的文件系统类型。)请注意,复制文件时通常不会复制 setuid 和 setgid 权限位:即使复制了,当您复制 root 拥有的文件时,新副本将归您所有,而不是 root。它可能允许其他用户像您一样运行复制的可执行文件,但它不会帮助您成为 root。要在完全保留权限的同时复制setuid root文件,您必须自己成为 root。
Xorg 不直接打开设备节点——它使 D-Bus IPC 调用systemd-logind服务,它代表调用者打开设备节点(在检查诸如哪个用户在前台 tty 上“登录”之类的事情之后)并使用 D-Bus 的 fd 传递功能(基于 Unix 套接字中的 SCM_CREDENTIALS 功能)将文件描述符转发到 Xorg。
有关相关的D-Bus API,请参阅org.freedesktop.login1(5)
TakeDevice()
。当前台 tty 切换到另一个用户的会话时,此方法允许 systemd-logind 主动撤销对输入设备的访问(相反,设置和删除 ACL 将允许一个用户的程序简单地保持文件描述符打开并继续读取输入,即使另一个用户的会话在前台)。
对于非 systemd 发行版,seated 提供了功能相似的 API (libseat_open_device),但 Xorg 尚不支持它,而是依赖于 setuid 包装器
Xorg.wrap
。(撤销机制是 ioctl(EVIOCREVOKE),特定于输入设备。对于 DRM 设备有类似的东西,但到目前为止还没有类似的音频或相机设备;声音服务器如 pipewire 监听 logind 的“会话切换”信号和合作关闭和重新打开设备。)