[Из песочницы] Драйвер видеокарты: так чей же баг?
На подготовку данного материала натолкнула недавняя статья »Баг драйвера видеокарты может раскрыть просмотренное в режиме инкогнито». Данная статья появилась на свет после публикации тривиального способа отобразить изображение, принадлежащее любому (в том числе терминированному) процессу, возможно даже имеющему претензии на защиту информации.
Поскольку по удачному стечению обстоятельств занимаюсь в том числе разработкой графических драйверов, попробую кратко пояснить в чем заключается заблуждение автора исходного багрепорта, к сфере чьей ответственности относится обозначенная проблема и как ее можно решать.
Независимо от операционной системы, родственных ей системных API и прикладных интерфейсов для разработки графических приложений, драйвер произвольной видеокарты решает следующие общесистемные задачи:
- Инициализация контроллера дисплея (установка видеорежима, управление портами GPU, формирование одного/нескольких независимых изображений, …);
- Управление адресуемой памятью (очереди команд, линейная/тайловая адресации, выделение поверхностей, таблицы трансляции адресов, расширение PCI апертуры, …);
- 2D акселерация (курсор, аппаратные слои, alpha/chroma ключи, ROP, примитивы, …);
- 3D акселерация (OpenGL, OpenGL ES / EGL, OpenVG / EGL, OpenCL, Open*);
- Видео декодирование / воспроизведение аудио / вычитывание EDID / сжатие буфера кадров, …
Применяемые на каждом этапе подходы к решению поставленных задач уже достаточно давно сведены к устоявшимся практикам. Этим как раз и объясняется воспроизводимость обозначенной проблемы на устройствах различных производителей. Забегая вперед скажу, что можно получить подобный эффект также и на контроллерах Intel. Автор багрепорта совершенно точно определил в рамках решения какой задачи возникает эффект — управление адресуемой памятью.
Управление памятью
Основной сущностью, которой оперирует драйвер на данном этапе — это поверхность. Поверхностью в общем случае называется непрерывный фрагмент видео или оперативной памяти, используемый для формирования изображения некоторым приложением. Для контроллеров не имеющих собственной памяти, выделенный из ОЗУ ресурс может стать адресуемым через таблицу трансляции адресов (Graphics Translation Table, GTT). В противном случае изображение может быть отображено только при копировании поверхности в видеопамять либо средствами DMA-контроллера, если такой имеется, либо за счет ресурсов CPU.
На самом деле даже контроллеры с собственной дискретной памятью в большинстве случаев также адресуют ее через GTT, поскольку таким образом достигается возможность формирования виртуального адресного пространства по аналогии с TLB центрального процессора для обеспечения линейной или тайловой адресации. Способ адресации в каждом конкретном случае определяет драйвер и принципиальной разницы меду ними в рамках данной данной статьи нет.
Драйвер графического контроллера является интерфейсом для ОС к функционалу GPU, не более того. Все задачи по обеспечению защиты информации возлагаются на любой отвечающий за это вышележащий уровень. Для этого драйвера имеют всю имеющуюся функциональность, было бы желание ее использовать.
Итак, по запросу некоторого клиента драйвера ОС резервирует (выделяет) для него набор поверхностей. Поскольку, как утверждает автор, фрагментирование изображения возникает сравнительно редко, можем утверждать, что тайловая адресация в рассматриваемых случаях используется не часто. При линейной адресации каждая поверхность характеризуется в первую очередь офсетом от начала виртуального адресного пространства памяти контроллера. при выделении памяти драйвер возвращает ОС именно этот офсет, который соответствует свободному блоку памяти, способному вместить поверхность с характеристиками, запрошенными прикладным ПО. При этом драйвер выполняет лишь следующие действия: модифицирует GTT для дальнейшего использования страниц виртуальной памяти, следит за соблюдением требований по выравниванию физических адресов, определяет механизм доступа прикладного ПО к поверхности (PCI/GPU апертура, по физическому адресу вне апертуры, расширение GTT на лету), резервирует часть памяти для собственных нужд.
Исходя из вышесказанного можно сделать вывод о том, что располагая информацией об общем объеме доступной памяти контроллера и требуемых характеристиках поверхности, определение офсета для всех контроллеров может решаться единообразно. На практике так оно и есть (руководствуясь опытом работы с различными unix-подобными ОС): ОС предоставляет системный сервис/библиотеку, которая хранит список уже использованных блоков памяти и позволяет оперативно вычислить первый доступный офсет для логического резервирования в рамках этой библиотеки. При этом, располагая информацией от драйвера о механизме доступа к блоку памяти, ОС в общем случае допускает формирование для прикладного ПО по одним и тем же физическим адресам разделяемых/пересекающихся поверхностей.
Драйверу же поступают лишь офсеты (в байтах) в видео памяти, координаты фрагментов относительно этих офсетов (в пикселях), глубина цвета и некоторая информация для осуществления пиксельных операций.
Возвращаясь к исходной проблеме
Наверняка к этому моменту многие уже догадались к чему сводится критика. При выполнении различных приложений ОС запрашивает для их нужд поверхности у видео драйвера и повторно использует их по мере освобождения памяти (терминирование процесса). При этом драйвер не может знать о том, что некоторый блок памяти требует немедленного обнуления, поскольку он обладает некими требованиями по безопасности и не имеет других ссылок. Само зануление памяти есть тривиальная задача аппаратной заливки прямоугольника.
На самом деле тест, написанный для публикации багрепорта избыточен. В общем случае (когда видео-память укладывается в PCI/GPU апертуру) для unix-подобных систем не требуется никаких прикладных API. Достаточно обратиться к /dev/mem по известному из вывода утилиты «lspci» офсету.
Для контроллеров Intel ситуация отличается, но не сильно. Поскольку собственной памяти у контроллера нет, GTT формируется налету с выделением памяти из ОЗУ. При повторном выделении поверхности просто может не повезти с реальным расположением блока оперативной памяти, учитывая, что в данном случае решающую роль уже играет механизм виртуальной адресации самой ОС.
Вариантов решения несколько, причем, я полагаю, что выводы будут очевидными:
- Все производители драйверов должны реализовать дублирующий функционал по хранению информации о существующих поверхностях (вопрос о контроле разделяемых поверхностей остается открыт);
- ОС должна следить за необходимостью того или иного приложения прибрать за собой (это либо некое маркирование защищаемых поверхностей, либо избыточное зануление любых поверхностей как в ОЗУ, так и в видеопамяти);
- Прикладное ПО должно корректно прибирать за собой, раз уж претендует на причастность к информационной безопасности.
Надеюсь, что заметка была интересна.