
Среда с уязвимым ядром для эксплуатации драйвера TEE (CVE-2021-44733)
Недавно была обнаружена уязвимость use-after-free в подсистеме TEE ядра Linux, вплоть до версии 5.15.11 включительно, которой был присвоен идентификатор CVE-2021-44733 [1].
На первый взгляд она казалась неэксплуатируемой по нескольким причинам, однако после дальнейшего анализа уязвимого пути кода и реализации чернового эксплойта proof-of-concept стало возможным перезаписать указатель на функцию в ядре. В этой статье не представлена полезная нагрузка для повышения привилегий, однако вся среда для запуска OPTEE и эксплойта доступна для дальнейшего тестирования, см. «Настройка среды».
TEE (Trusted Execution Environment) — это доверенная ОС, работающая в некотором безопасном окружении, например, TrustZone на процессорах ARM. Драйвер TEE обрабатывает детали, необходимые для взаимодействия с TEE. Одними из наиболее важных задач драйвера являются предоставление общего API к TEE на основе спецификации Globalplatform TEE Client API [3], а также управление разделяемой памятью между Linux и TEE. Эта подсистема может быть включена с помощью конфигурации CONFIG_OPTEE в настройках ядра для архитектур ARM.
В защищенном мире работает доверенная ОС, обозначаемая как OP-TEE OS [4]. Поверх этой ОС могут работать так называемые доверенные приложения (Trusted Applications, TAs), которые могут выполнять некоторые операции в изолированной среде, см. Рисунок 1.
Рисунок 1: Обзор TEE — из презентации Linaro [5]
Обычный мир (пространство пользователя/ядро Linux) может взаимодействовать с этими приложениями с помощью клиентских приложений (CAs) и API, предоставляемого подсистемой TEE. CA может открыть сессию к конкретному TA и вызывать функции, которые реализует TA. Передача любых аргументов между TA и CA осуществляется с использованием разделяемой памяти. Далее описывается взаимодействие между CA и TA с использованием всех соответствующих системных вызовов.
CA открывает /dev/tee[0-9] для связи с драйвером. Обратите внимание, что для обычного способа использования этих API это делается неявно с помощью libteec.
Разделяемая память может быть зарегистрирована CA с помощью IOCTL TEE_IOC_SHM_ALLOC. Это выделяет разделяемую память и возвращает файловый дескриптор, который пространство пользователя может использовать как часть mmap.
Следующий шаг — установление сессии с помощью IOCTL TEE_IOC_OPEN_SESSION с указанием uuid конкретного TA. Этот uuid жестко закодирован во время компиляции TA.
Чтобы вызвать любую конкретную функцию в TA, CA вызывает это, указывая идентификатор функции вместе с входными аргументами; это делается с помощью TEE_IOC_INVOKE.
Когда CA завершает все запросы, сессия может быть закрыта с помощью TEE_IOC_CLOSE_SESSION.
Рисунок 2: Сессия между CA и TA — из презентации Linaro [5]
Большая часть взаимодействия между клиентами и TEE является непрозрачной для драйвера. Основная задача драйвера — управление контекстом, получение запросов от клиентов, их пересылка в TEE и отправка результатов обратно [2].