0

This is the permission of my /dev/input/event* files:

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
...

As you can see there's no + after the permissions which means no special ACL permissions. I also confirmed it with getfacl. I'm not part of the input group either and am not running X11 as root. I just manually type startx in the console after logging in as the user to start xorg.

So my question is how and where does udev give permission to X11 input drivers (e.g. xf86-input-libinput) to open the those files without ACL?

If I want to open the /dev/input/event files I have to use sudo or be part of input group but rootless X11 seems to be able to do that with no problem!


Here's a minimal c program to demonstrate the permission issue. If keybit_limit is set to anything below 578, X11 drivers will have the permission to read the corresponding /dev/input/event and a device with your given name will show up in xinput output. Anything higher than that up to KEY_CNT will cause permission errors in Xorg log and xinput won't show the new device. Although you still can see the device with sudo evtest. The permission and group of /dev/input/event in both situations are exactly the same but in the KEY_CNT scenario X11 can't read it and the 1 2 3 keys will not be registered in 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;
}

Here are the permission errors in ~/.local/share/xorg/Xorg.0.log file when keybit_limit = KEY_CNT and uinput device name passed to the program is "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"

I have tested both evdev and libinput drivers for X11 with a xorg.conf.d file and both will behave the same. If I put myself in input group or use uaccess tag in a udev rule for the device then X11 drivers can read it. This suggests that in <578 scenario the device is read as root but in the KEY_CNT scenario the device is read as user.

Why is that? And which process is doing that?

2 Answers 2

2

Xorg does not open the device nodes directly – it makes D-Bus IPC calls to the systemd-logind service, which opens the device nodes on behalf of the caller (after checking things like which user is "logged in" at the foreground tty) and forwards the file descriptors to Xorg, using the fd-passing capability of D-Bus (built on the SCM_CREDENTIALS feature in Unix sockets).

See org.freedesktop.login1(5) for the relevant TakeDevice() D-Bus API.

This method allows systemd-logind to proactively revoke access to input devices when the foreground tty is switched away to another user's session (in contrast, setting and removing ACLs would allow one user's programs to simply hold a file descriptor open and continue reading input even if another user's session is foreground).

For non-systemd distributions, seatd provides a functionally similar API (libseat_open_device) but Xorg does not support it yet, relying instead on the setuid Xorg.wrap wrapper.

(The revocation mechanism is ioctl(EVIOCREVOKE), specific to input devices. There is something similar for DRM devices but so far there is no equivalent for e.g. audio or camera devices; sound servers such as pipewire listen to logind's "session switch" signals and cooperate in closing and reopening the devices.)

1
  • Interesting! Why do you think in the example I have given, this line appears in the xorg log: systemd-logind: failed to take device /dev/input/event24: No such device. Is the /dev/input/event file somehow being created after systemd-logind wants to open it on behalf of X? Then what is creating the event file later? Commented Mar 21, 2023 at 18:01
1

Udev does not "give permission to X11 input drivers". Udev has created the device nodes you displayed, and once it's done that, its job regarding input devices is essentially done. The rest of the system will have to deal with the device permissions as specified by it.

startx is typically a script that uses /usr/bin/xinit to do its job.

It turns out that in a modern Xorg X server, the setuid root privileges are implemented as a small separate wrapper, /usr/lib/xorg/Xorg.wrap. The Xwrapper.config(5) man page says:

DESCRIPTION

The Xorg X server may need root rights to function properly. To start the Xorg X server with these rights your system is using a suid root wrapper installed as /usr/lib/xorg/Xorg.wrap which will execute the real X server which is installed as /usr/lib/xorg/Xorg.

By default Xorg.wrap will autodetect if root rights are necessary, and if not it will drop its elevated rights before starting the real X server. By default Xorg.wrap will only allow executing the real X server from login sessions on a physical console.

This is on Debian 11; your distribution may be using somewhat different paths.

Run ls -l /usr/bin/xinit /usr/bin/Xorg /usr/lib/xorg/Xorg.wrap. If it reports permissions that include an s in place of an usual x, there is your answer: if the s is in the first x position (-rws......), the executable will be run with the privileges of the owner of the file (which is usually root in cases like this). This is usually referred to as the executable being setuid root or suid root.

If the s is in the second position, the process would gain the membership of the group that owns the executable when run: this is referred as the executable being setgid or sgid to that group. (Note that on directories or non-executable files, the setgid permission bit can have other meanings, depending on the filesystem type used.)

Note that the setuid and setgid permission bits are typically not replicated when you copy a file: even if they did, when you make a copy of a file that is owned by root, the new copy will be owned by you, not root. It might allow other users to run the copied executable as you, but it would not help you become root. To copy a setuid root file while fully preserving permissions, you would have to be root yourself.

8
  • None of those files have setgid or setuid permssions and ps -o user= -C Xorg also shows me as the user not root. I know the problem is udev related because if I create a uinput device and set too many UI_SET_KEYBITs for EV_KEY, the corresponding /dev/input/event file will still have the same permissions and group as the other ones but X11 drivers do not have the permission to read them. The Xorg log will show permission denied errors when opening the event file. If I put myself in input group or use less UI_SET_KEYBITs when creating uinput device, X11 can open the device. Commented Mar 21, 2023 at 7:34
  • I'm pretty sure the udev rules for wierd devices like a device that has the capability to emulate all keys doesn't exist and that is why X cannot read them. This has to be udev related. It must not recognize the device as a valid one and not give permissions to the drivers to read them. The device is fine by the way. sudo evtest can read the events just fine and linux console can interpret them correctly as well. X on the other hand gives permission denied errors. Commented Mar 21, 2023 at 7:39
  • 1
    Udev will not grant extra privileges to any processes that are not its descendants. Modern versions of udev will heavily discourage you from starting long-term processes from udev. Udev is not even aware that you are starting an X server, so what you are thinking seems impossible to me. It turns out that modern Xorg X server uses a dedicated Xorg.wrap wrapper binary, located in /usr/lib/xorg/Xorg.wrap, at least on my Debian 11. See my edit above. Commented Mar 21, 2023 at 10:06
  • On Arch its in /usr/lib/Xorg.wrap and it is setuid! So that is how it has the privileges! But It doesn't explain why Xorg drivers show permission denied errors if the /dev/input/event device is weird like I explained in the above comments. How can it be run as root but not be able to read a device that has the same privileges as the others? It seems like something else determines if the device should be opened with root privileges or user privileges. That's why I suspected udev. Can this be related to systemd somehow? I'm really lost here. Commented Mar 21, 2023 at 10:35
  • I have added a minimal example c program that demonstrate it. Commented Mar 21, 2023 at 14:09

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.