Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

drmDropMaster requires root privileges?

Pardon for the long introduction, but I haven't seen any other questions for this on SO.

I'm playing with DRM (Direct Rendering Manager, a wrapper for Linux kernel mode setting) and I'm having difficulty understanding a part of its design.

Basically, I can open a graphic card device in my virtual terminal, set up frame buffers, change connector and its CRTC just fine. This results in me being able to render to VT in a lightweight graphic mode without need for X server (that's what kms is about, and in fact X server uses it underneath).

Then I wanted to implement graceful VT switching, so when I hit ctrl+alt+f3 etc., I can see my other consoles. Turns out it's easy to do with calling ioctl() with stuff from linux/vt.h and handling some user signals.

But then I tried to switch from my graphic program to a running X server. Bzzt! didn't work at all. X server didn't draw anything at all. After some digging I found that in Linux kernel, only one program can do kernel mode setting. So what happens is this:

  1. I switch from X to a virtual terminal
  2. I run my program
  3. This program enters graphic mode with drmOpen, drmModeSetCRTC etc.
  4. I switch back to X
  5. X has no longer privileges to restore its own mode.

Then I found this in wayland source code: drmDropMaster() and drmSetMaster(). These functions are supposed to release and regain privileges to set modes so that X server can continue to work, and after switching back to my program, it can take it from there.


Finally the real question. These functions require root privileges. This is the part I don't understand. I can mess with kernel modes, but I can't say "okay X11, I'm done playing, I'm giving you the access now"? Why? Or should this work in theory, and I'm just doing something wrong in my code? (e.g. work with wrong file descriptors, or whatever.)

If I try to run my program as a normal user, I get "permission denied". If I run it as root, it works fine - I can switch from X to my program and vice versa.

Why?

like image 573
rr- Avatar asked Apr 17 '15 20:04

rr-


3 Answers

Yes, drmSetMaster and drmDropMaster require root privileges because they allow you to do mode setting. Otherwise, any random application could display whatever it wanted to your screen. weston handles this through a setuid launcher program. The systemd people also added functionality to systemd-logind (which runs as root) to do the drm{Set,Drop}Master calls for you. This is what enables recent X servers to run without root privileges. You could look into this if you don't mind depending on systemd.

Your post seems to suggest that you can successfully call drmModeSetCRTC without root privileges. This doesn't make sense to me. Are you sure?

It is up to display servers like X, weston, and whatever you're working on to call drmDropMaster before it invokes the VT_RELDISP ioctl, so that the next session can call drmSetMaster successfully.

like image 169
user2669812 Avatar answered Sep 30 '22 22:09

user2669812


Before digging into why it doesn't work, I had to understand how it works.

So, calling drmModeSetCRTC and drmSetMaster in libdrm in reality just calls ioctl:

include/xf86drm.c

int drmSetMaster(int fd)
{
    return ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
}

This is handled by the kernel. In my program the most important function that controls the display is drmModeSetCRTC and drmModeAddFB, the rest is just diagnostics really. So let's see how they're handled by the kernel. Turns out there is a big table that maps ioctl events to their handlers:

drivers/gpu/drm/drm_ioctl.c

static const struct drm_ioctl_desc drm_ioctls[] = {
        ...
        DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCRTC, drm_mode_getcrtc, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
        DRM_IOCTL_DEF(DRM_IOCTL_MODE_SETCRTC, drm_mode_setcrtc, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),
        ...,
        DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
        DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB2, drm_mode_addfb2, DRM_CONTROL_ALLOW|DRM_UNLOCKED),
        ...,
},

This is used by the drm_ioctl, out of which the most interesting part is drm_ioctl_permit.

drivers/gpu/drm/drm_ioctl.c

long drm_ioctl(struct file *filp,
               unsigned int cmd, unsigned long arg)
{
        ...
        retcode = drm_ioctl_permit(ioctl->flags, file_priv);
        if (unlikely(retcode))
               goto err_i1;
        ...
}

static int drm_ioctl_permit(u32 flags, struct drm_file *file_priv)
{
        /* ROOT_ONLY is only for CAP_SYS_ADMIN */
        if (unlikely((flags & DRM_ROOT_ONLY) && !capable(CAP_SYS_ADMIN)))
                return -EACCES;

        /* AUTH is only for authenticated or render client */
        if (unlikely((flags & DRM_AUTH) && !drm_is_render_client(file_priv) &&
                     !file_priv->authenticated))
                return -EACCES;

        /* MASTER is only for master or control clients */
        if (unlikely((flags & DRM_MASTER) && !file_priv->is_master &&
                     !drm_is_control_client(file_priv)))
                return -EACCES;

        /* Control clients must be explicitly allowed */
        if (unlikely(!(flags & DRM_CONTROL_ALLOW) &&
                     drm_is_control_client(file_priv)))
                return -EACCES;

        /* Render clients must be explicitly allowed */
        if (unlikely(!(flags & DRM_RENDER_ALLOW) &&
                     drm_is_render_client(file_priv)))
                return -EACCES;

        return 0;
}

Everything makes sense so far. I can indeed call drmModeSetCrtc because I am the current DRM master. (I'm not sure why. This might have to do with X11 properly waiving its rights once I switch to another VT. Perhaps this alone allows me to become automatically the new DRM master once I start messing with ioctl?)

Anyway, let's take a look at the drmDropMaster and drmSetMaster definitions:

drivers/gpu/drm/drm_ioctl.c

static const struct drm_ioctl_desc drm_ioctls[] = {
        ...
        DRM_IOCTL_DEF(DRM_IOCTL_SET_MASTER, drm_setmaster_ioctl, DRM_ROOT_ONLY),
        DRM_IOCTL_DEF(DRM_IOCTL_DROP_MASTER, drm_dropmaster_ioctl, DRM_ROOT_ONLY),
        ...
 };

What.

So my confusion was correct. I don't do anything wrong, things really are this way.

I'm under the impression that this is a serious kernel bug. Either I shouldn't be able to set CRTC at all, or I should be able to drop/set master. In any case, revoking every non-root program rights to draw to screen because

any random application could display whatever it wanted to your screen

is too aggressive. I, as the user, should have the freedom to control that without giving root access to the whole program, nor depending on systemd, for example by making chmod 0777 /dev/dri/card0 (or group management). As it is now, it looks to me like lazy man's answer to proper permission management.

like image 22
rr- Avatar answered Sep 30 '22 23:09

rr-


Thanks for writing this up. This is indeed the expected outcome; you don't need to look for a subtle bug in your code.

It's definitely intended that you can become the master implicitly. A dev wrote example code as initial documentation for DRM, and it does not use SetMaster. And there is a comment in the source code (now drm_auth.c) "successfully became the device master (either through the SET_MASTER IOCTL, or implicitly through opening the primary device node when no one else is the current master that time)".

DRM_ROOT_ONLY is commented as

/**
 * @DRM_ROOT_ONLY:
 *
 * Anything that could potentially wreak a master file descriptor needs
 * to have this flag set. Current that's only for the SETMASTER and
 * DROPMASTER ioctl, which e.g. logind can call to force a non-behaving
 * master (display compositor) into compliance.
 *
 * This is equivalent to callers with the SYSADMIN capability.
 */

The above requires some clarification IMO. The way logind forces a non-behaving master is not simply by calling SETMASTER for a different master - that would actually fail. First, it must call DROPMASTER on the non-behaving master. So logind is relying on this permission check, to make sure the non-behaving master cannot then race logind and call SETMASTER first.

Equally logind is assuming the unprivileged user doesn't have permission to open the device node directly. I would suspect the ability to implicitly become master on open() is some form of backwards compatibility.

Notice, if you could drop your master, you couldn't use SETMASTER to get it back. This means the point of doing so is rather limited - you can't use it to implement the traditional switching back and forth between multiple graphics servers.

There is a way you can drop the master and get it back: close the fd, and re-open it when needed. It sounds to me like this would match how old-style X (pre-DRM?) worked - wasn't it possible to switch between multiple instances of the X server, and each of them would have to completely take over the hardware? So you always had to start from scratch after a VT switch. This is not as good as being able to switch masters though; logind says

            /* On DRM devices we simply drop DRM-Master but keep it open.
             * This allows the user to keep resources allocated. The
             * CAP_SYS_ADMIN restriction to DRM-Master prevents users from
             * circumventing this. */
like image 43
sourcejedi Avatar answered Sep 30 '22 22:09

sourcejedi