drmDropMaster 需要 root 权限?

drmDropMaster requires root privileges?

请原谅冗长的介绍,但我还没有在 SO 上看到任何其他问题。

我正在玩 DRM(Direct Rendering Manager,Linux 内核模式设置的包装器),但我很难理解其设计的一部分。

基本上,我可以在我的虚拟终端中打开一个图形卡设备,设置帧缓冲区,更改连接器及其 CRTC 就好了。这使我能够在不需要 X 服务器的情况下以轻量级图形模式渲染到 VT(这就是 kms 的意义所在,实际上 X 服务器在底层使用它)。

然后我想实现优雅的 VT 切换,所以当我按 ctrl+alt+f3 等时,我可以看到我的其他控制台。事实证明,使用来自 linux/vt.h 的内容调用 ioctl() 并处理一些用户信号很容易。

但后来我尝试从我的图形程序切换到 运行ning X 服务器。嗡!根本没用。 X 服务器根本没有绘制任何东西。经过一番挖掘,我发现在 Linux 内核中,只有一个程序可以进行内核模式设置。那么会发生什么:

  1. 我从 X 切换到虚拟终端
  2. 我运行我的程序
  3. 此程序进入图形模式 drmOpendrmModeSetCRTC
  4. 我换回X
  5. X 不再具有恢复其自身模式的权限。

然后我在 wayland 源代码中找到了这个:drmDropMaster()drmSetMaster()。这些函数应该释放和重新获得设置模式的权限,以便 X server 可以继续工作,并且在切换回我的程序后,它可以从那里获取它。


终于是正题了。 这些功能需要 root 权限。这是我不明白的部分。我可以搞乱内核模式,但我不能说 "okay X11, I'm done playing, I'm giving you the access now"?为什么?或者这应该在理论上起作用,而我只是在我的代码中做错了什么? (例如,使用错误的文件描述符等。)

如果我以普通用户的身份尝试 运行 我的程序,我会得到 "permission denied"。如果我 运行 它作为 root,它工作正常 - 我可以从 X 切换到我的程序,反之亦然。

为什么?

是的,drmSetMasterdrmDropMaster 需要 root 权限,因为它们允许您进行模式设置。否则,任何随机应用程序都可以在您的屏幕上显示它想要的任何内容。 weston 通过 setuid 启动程序来处理这个问题。 systemd 人员还向 systemd-logind(运行s 作为 root)添加了功能来为您执行 drm{Set,Drop}Master 调用。这就是使最近的 X 服务器能够 运行 而无需 root 权限的原因。如果你不介意依赖 systemd,你可以看看这个。

您的 post 似乎表明您可以在没有 root 权限的情况下成功调用 drmModeSetCRTC。这对我来说没有意义。你确定吗?

在调用 VT_RELDISP ioctl 之前调用 drmDropMaster 可以显示像 X、weston 和任何你正在处理的服务器,以便下一个会话可以成功调用 drmSetMaster .

在深入研究它为什么不起作用之前,我必须了解它是如何工作的。

因此,在 libdrm 中调用 drmModeSetCRTCdrmSetMaster 实际上只是调用 ioctl:

include/xf86drm.c

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

这是由内核处理的。在我的程序中,控制显示的最重要的函数是 drmModeSetCRTCdrmModeAddFB,其余的只是诊断。那么让我们看看内核是如何处理它们的。原来有一个很大的 table 将 ioctl 事件映射到它们的处理程序:

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),
        ...,
},

这是drm_ioctl使用的,其中最有趣的部分是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;
}

到目前为止一切都说得通。我确实可以调用 drmModeSetCrtc 因为我是当前的 DRM 管理员。 (我不确定为什么。这可能与我切换到另一个 VT 后 X11 正确放弃其权利有关。也许仅此一项就可以让我在开始搞乱 ioctl 后自动成为新的 DRM 主人?)

无论如何,让我们看一下drmDropMasterdrmSetMaster的定义:

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),
        ...
 };

什么。

所以我的困惑是正确的。我没做错,事情真的是这样。

我的印象是这是一个严重的内核错误。要么我根本无法设置 CRTC,要么我应该能够 drop/set 掌握。无论如何,取消所有非根程序在屏幕上绘制的权利,因为

any random application could display whatever it wanted to your screen

过于激进。作为用户,我应该可以自由控制 而无需 授予对整个程序的 root 访问权限,也不依赖于 systemd,例如通过 chmod 0777 /dev/dri/card0 (或组管理).就像现在一样,在我看来它就像是懒人对正确权限管理的回答。

感谢您写这篇文章。这确实是预期的结果;您无需在代码中查找细微错误。

绝对是为了让你隐性成为主人。开发人员编写了 example code 作为 DRM 的初始文档,并且它不使用 SetMaster。并且在源码中有注释(现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 评论为

/**
 * @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.
 */

以上内容需要海事组织进行一些澄清。 logind 强制 non-behaving master 的方式不仅仅是通过为不同的 master 调用 SETMASTER - 实际上会失败。首先,它必须在 non-behaving 主机上调用 DROPMASTER。所以 logind 依赖于这个权限检查,以确保 non-behaving master 不能然后 race logind 并首先调用 SETMASTER。

Equally logind 假设非特权用户没有权限直接打开设备节点。我怀疑在 open() 上隐式成为 master 的能力是某种形式的向后兼容性。

请注意,如果您可以 删除您的主人,您将无法使用 SETMASTER 将其取回。这意味着这样做的意义是相当有限的——你不能用它来实现传统的在多个图形服务器之间来回切换。

有一种方法可以删除 master 并将其取回:关闭 fd,并在需要时 re-open 关闭它。在我看来这与 old-style X (pre-DRM?) 的工作方式相匹配 - 不可能在 X 服务器的多个实例之间切换,并且每个实例都必须完全接管硬件?因此,您总是必须在 VT 切换后从头开始。不过,这不如能够切换主人。登录说

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

从 Linux 5.8 开始,drmDropMaster() 不再需要 root 权限。

相关提交是45bc3d26c: drm: rework SET_MASTER and DROP_MASTER perm handling

source code comments很好地总结了新旧情况:

In the olden days the SET/DROP_MASTER ioctls used to return EACCES when CAP_SYS_ADMIN was not set. This was used to prevent rogue applications from becoming master and/or failing to release it.

At the same time, the first client (for a given VT) is always master. Thus in order for the ioctls to succeed, one had to explicitly run the application as root or flip the setuid bit.

If the CAP_SYS_ADMIN was missing, no other client could become master... EVER :-( Leading to a) the graphics session dying badly or b) a completely locked session.

...

Here we implement the next best thing:

  • ensure the logind style of fd passing works unchanged, and
  • allow a client to drop/set master, iff it is/was master at a given point in time.

...