这是我的 /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。