drmDropMaster требует привилегий root?

Извините за длинное введение, но я не видел других вопросов по этому поводу на SO.

Я играю с DRM (Direct Rendering Manager, оболочка для настройки режима ядра Linux), и мне трудно понять часть его дизайна.

По сути, я могу открыть устройство с графической картой в своем виртуальном терминале, настроить буферы кадров, изменить разъем и его CRTC. Это приводит к тому, что я могу рендерить в VT в облегченном графическом режиме без необходимости в X-сервере (это то, что касается kms, и на самом деле X-сервер использует его внизу).

Затем я хотел реализовать плавное переключение VT, чтобы при нажатии ctrl+alt+f3 и т. д. я мог видеть другие свои консоли. Оказывается, это легко сделать, вызывая ioctl() с данными из linux/vt.h и обрабатывая некоторые пользовательские сигналы.

Но затем я попытался переключиться с моей графической программы на работающий X-сервер. Бзз! вообще не работал. X-сервер вообще ничего не рисовал. Немного покопавшись, я обнаружил, что в ядре Linux только одна программа может выполнять настройку режима ядра. Итак, что происходит:

  1. Я переключаюсь с X на виртуальный терминал
  2. я запускаю свою программу
  3. Эта программа входит в графический режим с помощью drmOpen, drmModeSetCRTC и т. д.
  4. Я переключаюсь обратно на X
  5. X больше не имеет права восстанавливать собственный режим.

Затем я нашел это в исходном коде Wayland: drmDropMaster() и drmSetMaster(). Эти функции должны освобождать и восстанавливать привилегии для установки режимов, чтобы X-сервер мог продолжать работать, и после переключения обратно на мою программу он мог взять его оттуда.


Наконец настоящий вопрос. Эти функции требуют привилегий root. Это та часть, которую я не понимаю. Я могу возиться с модами ядра, но я не могу сказать «хорошо, X11, я закончил играть, теперь я даю тебе доступ»? Почему? Или это должно работать в теории, и я просто делаю что-то не так в своем коде? (например, работа с неправильными файловыми дескрипторами или что-то еще.)

Если я попытаюсь запустить свою программу как обычный пользователь, я получу «отказано в доступе». Если я запускаю его как root, он работает нормально - я могу переключиться с X на свою программу и наоборот.

Почему?


person rr-    schedule 17.04.2015    source источник


Ответы (4)


Да, drmSetMaster и drmDropMaster требуют привилегий суперпользователя, потому что они позволяют вам выполнять настройку режима. В противном случае любое случайное приложение могло бы отобразить на вашем экране все, что захочет. Weston справляется с этим с помощью программы запуска setuid. Люди из systemd также добавили функциональность в systemd-logind (которая запускается от имени пользователя root), чтобы выполнять drm{Set,Drop}Master вызовы за вас. Это то, что позволяет последним X-серверам работать без привилегий root. Вы можете изучить это, если не возражаете, в зависимости от systemd.

Ваш пост, кажется, предполагает, что вы можете успешно вызывать drmModeSetCRTC без привилегий root. Это не имеет смысла для меня. Уверены ли вы?

Отображаемые серверы, такие как X, weston и все, над чем вы работаете, должны вызывать drmDropMaster до того, как он вызовет VT_RELDISP ioctl, чтобы следующий сеанс мог успешно вызвать drmSetMaster.

person user2669812    schedule 17.04.2015
comment
Ваш пост, кажется, предполагает, что вы можете успешно вызывать drmModeSetCRTC без привилегий root. Это не имеет смысла для меня. Уверены ли вы? Да, я уверен. В основном поэтому я не понимаю всего этого. Я могу сделать это только с текстового виртуального терминала, что для меня имеет смысл - на текстовом VT нет конфликта интересов. - person rr-; 18.04.2015

Прежде чем копаться в том, почему это не работает, я должен был понять, как это работает.

Таким образом, вызов drmModeSetCRTC и drmSetMaster в libdrm на самом деле просто вызывает ioctl:

include/xf86drm.c

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

Этим занимается ядро. В моей программе самая важная функция, управляющая дисплеем, это drmModeSetCRTC и drmModeAddFB, остальное - просто диагностика. Итак, давайте посмотрим, как они обрабатываются ядром. Оказывается, есть большая таблица, которая сопоставляет события ioctl с их обработчиками:

драйверы/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.

драйверы/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. (Я не уверен, почему. Это может быть связано с тем, что X11 должным образом отказывается от своих прав, когда я переключаюсь на другой VT. Возможно, это само по себе позволяет мне автоматически стать новым мастером DRM, как только я начну возиться с ioctl?)

В любом случае, давайте взглянем на определения drmDropMaster и drmSetMaster:

драйверы/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, либо я должен иметь возможность удалить/установить master. В любом случае, отозвать все права программы без полномочий root для рисования на экране, потому что

любое случайное приложение может отображать на вашем экране все, что захочет

слишком агрессивно. Я, как пользователь, должен иметь возможность управлять этим без предоставления root-доступа ко всей программе и не в зависимости от systemd, например, путем создания chmod 0777 /dev/dri/card0 (или группового управления). Сейчас это выглядит как ответ ленивого человека на правильное управление разрешениями.

person rr-    schedule 18.04.2015

Спасибо, что написали это. Это действительно ожидаемый результат; вам не нужно искать тонкую ошибку в вашем коде.

Определенно предполагается, что вы можете стать мастером безоговорочно. Разработчик написал пример кода в качестве исходного документацию по DRM и не использует SetMaster. И есть комментарий в исходном коде (теперь drm_auth.c) «успешно стал мастером устройства (либо через SET_MASTER IOCTL, либо неявно через открытие основного узла устройства, когда никто другой не является текущим мастером в это время)».

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 заставляет недействующий мастер, заключается не в простом вызове SETMASTER для другого мастера - это на самом деле потерпит неудачу. Во-первых, он должен вызвать DROPMASTER на неактивном мастере. Таким образом, logind полагается на эту проверку разрешений, чтобы убедиться, что неведущий мастер не может затем запустить гонку logind и сначала вызвать SETMASTER.

Точно так же logind предполагает, что у непривилегированного пользователя нет разрешения на непосредственное открытие узла устройства. Я подозреваю, что возможность неявно стать мастером в open() — это некая форма обратной совместимости.

Обратите внимание: если вы могли удалить мастер, вы не могли использовать SETMASTER, чтобы вернуть его. Это означает, что смысл этого довольно ограничен — вы не можете использовать его для реализации традиционного переключения между несколькими графическими серверами.

Есть способ, которым вы можете удалить мастер и вернуть его: закройте файл fd и снова откройте его при необходимости. Мне кажется, что это соответствовало бы тому, как работал X в старом стиле (до 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. */
person sourcejedi    schedule 02.03.2018

Начиная с Linux 5.8, drmDropMaster() больше не требует привилегий root.

Соответствующая фиксация: 45bc3d26c: drm: переработать обработку разрешений SET_MASTER и DROP_MASTER.

комментарии к исходному коду содержат хорошее резюме старой и новой ситуации:

Раньше IOCTLS SET/DROP_MASTER возвращали EACCES, когда CAP_SYS_ADMIN не был установлен. Это использовалось для предотвращения того, чтобы мошеннические приложения становились главными и/или не могли их выпустить.

При этом первый клиент (для данной ВТ) всегда является ведущим. Таким образом, для успешного выполнения ioctl необходимо было явно запустить приложение от имени пользователя root или изменить бит setuid.

Если CAP_SYS_ADMIN отсутствовал, ни один другой клиент не мог стать мастером... НИКОГДА :-( Это приводило к а) серьезному завершению графического сеанса или б) полностью заблокированному сеансу.

...

Здесь мы реализуем следующую лучшую вещь:

  • убедитесь, что стиль входа в систему при передаче fd работает без изменений, и
  • разрешить клиенту удалять/устанавливать мастер, если он является/был мастером в данный момент времени.

...

person jpa    schedule 05.08.2020