Усиление защиты Media Framework

Чтобы повысить безопасность устройства, в Android 7.0 монолитный процесс mediaserver был разбит на несколько процессов с разрешениями и возможностями, ограниченными только теми, которые необходимы каждому процессу. Эти изменения устраняют уязвимости в системе безопасности Media Framework следующим образом:

  • Разделение компонентов AV-конвейера на изолированные процессы для каждого приложения.
  • Включение обновляемых медиакомпонентов (экстракторов, кодеков и т. д.).

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

Производителям оригинального оборудования и однокристальных систем необходимо обновить HAL и фреймворк, чтобы обеспечить совместимость с новой архитектурой. В частности, поскольку код Android, предоставленный поставщиком, часто предполагает, что все выполняется в одном процессе, поставщики должны обновить свой код, чтобы передавать собственные дескрипторы (native_handle), которые имеют значение в разных процессах. Пример реализации изменений, связанных с усилением защиты медиа, можно найти в frameworks/av и frameworks/native.

Изменения в архитектуре

В предыдущих версиях Android использовался единый монолитный процесс mediaserver с большим количеством разрешений (доступ к камере, аудио, видеодрайверу, файлам, сети и т. д.). В Android 7.0 процесс mediaserver разделен на несколько новых процессов, каждому из которых требуется гораздо меньше разрешений:

усиление защиты mediaserver;

Рисунок 1. Изменения в архитектуре для усиления защиты mediaserver

Благодаря новой архитектуре даже в случае взлома процесса вредоносный код не получит доступ ко всему набору разрешений, которые ранее были у mediaserver. Процессы ограничены правилами SELinux и seccomp.

Примечание. Из-за зависимостей поставщиков некоторые кодеки по-прежнему работают в mediaserver и, следовательно, предоставляют mediaserver больше разрешений, чем необходимо. В частности, Widevine Classic продолжает работать в mediaserver для Android 7.0.

Изменения в MediaServer

В Android 7.0 процесс mediaserver используется для воспроизведения и записи, например для передачи и синхронизации буферов между компонентами и процессами. Процессы взаимодействуют через стандартный механизм Binder.

В стандартном сеансе воспроизведения локального файла приложение передает дескриптор файла (FD) в mediaserver (обычно через MediaPlayer Java API), а mediaserver:

  1. Оборачивает дескриптор файла в объект Binder DataSource, который передается процессу извлечения. Этот процесс использует объект для чтения из файла с помощью Binder IPC. (Mediaextractor не получает дескриптор файла, а выполняет обратные вызовы Binder к mediaserver, чтобы получить данные.)
  2. Проверяет файл, создает подходящий экстрактор для типа файла (например, MP3Extractor или MPEG4Extractor) и возвращает интерфейс Binder для экстрактора в процесс mediaserver.
  3. Выполняет вызовы Binder IPC к экстрактору, чтобы определить тип данных в файле (например, MP3 или H.264).
  4. Вызывает процесс mediacodec для создания кодеков нужного типа и получает интерфейсы Binder для этих кодеков.
  5. Многократно вызывает Binder IPC для извлечения закодированных образцов, отправляет закодированные данные в процесс mediacodec для декодирования и получает декодированные данные.

В некоторых вариантах использования кодек не используется (например, при воспроизведении с разгрузкой, когда закодированные данные отправляются непосредственно на устройство вывода) или кодек может напрямую отображать декодированные данные вместо того, чтобы возвращать буфер декодированных данных (воспроизведение видео).

Изменения в MediaCodecService

Сервис кодеков – это место, где хранятся кодировщики и декодировщики. Из-за зависимостей от поставщиков не все кодеки ещё находятся в процессе кодирования. В Android 7.0:

  • Небезопасные декодеры и программные видеокодеры находятся в процессе кодека.
  • Защищенные декодеры и аппаратные кодеры находятся в mediaserver (без изменений).

Приложение (или mediaserver) вызывает процесс кодека, чтобы создать кодек нужного типа, а затем вызывает этот кодек, чтобы передать закодированные данные и получить декодированные данные (для декодирования) или передать декодированные данные и получить закодированные данные (для кодирования). Передача данных в кодеки и из них уже использует общую память, поэтому этот процесс не изменился.

Изменения в MediaDrmServer

Сервер DRM используется при воспроизведении контента, защищенного DRM, например фильмов из Google Play Фильмов. Он безопасно расшифровывает зашифрованные данные и поэтому имеет доступ к хранилищу сертификатов и ключей, а также другим конфиденциальным компонентам. Из-за зависимостей от поставщиков процесс DRM пока не используется во всех случаях.

Изменения в AudioServer

Процесс AudioServer содержит компоненты, связанные со звуком, например ввод и вывод аудио, сервис policymanager, который определяет маршрутизацию аудио, и сервис FM-радио. Подробные сведения об изменениях в Audio и инструкции по реализации приведены в статье Как реализовать аудио.

Изменения в CameraServer

CameraServer управляет камерой и используется при записи видео для получения кадров видео с камеры и их передачи в mediaserver для дальнейшей обработки. Подробнее об изменениях и рекомендациях по реализации для CameraServer см. в разделе Усиление защиты фреймворка Камеры.

Изменения в ExtractorService

Сервис извлечения содержит экстракторы – компоненты, которые анализируют различные форматы файлов, поддерживаемые медиафреймворком. Сервис извлечения обладает минимальными привилегиями среди всех сервисов. Он не может считывать дескрипторы файлов, поэтому для доступа к файлам он вызывает интерфейс Binder, который предоставляется ему mediaserver for для каждого сеанса воспроизведения.

Приложение (или mediaserver) вызывает процесс извлечения, чтобы получить IMediaExtractor, вызывает этот IMediaExtractor, чтобы получить IMediaSources для дорожки, содержащейся в файле, а затем вызывает IMediaSources, чтобы прочитать данные из них.

Чтобы передать данные между процессами, приложение (или mediaserver) включает их в ответный объект Parcel как часть транзакции Binder или использует общую память:

  • При использовании общей памяти требуется дополнительный вызов Binder, чтобы освободить ее, но это происходит быстрее и потребляет меньше энергии для больших буферов.
  • При использовании in-Parcel требуется дополнительное копирование, но для буферов размером менее 64 КБ этот способ быстрее и потребляет меньше энергии.

Реализация

Чтобы перенести компоненты MediaDrm и MediaCrypto в новый процесс mediadrmserver, поставщики должны изменить метод выделения защищенных буферов, чтобы буферы можно было использовать совместно в разных процессах.

В предыдущих версиях Android защищенные буферы выделяются в mediaserver с помощью OMX::allocateBuffer и используются во время расшифровки в том же процессе, как показано ниже:

Рисунок 2. Выделение буфера в mediaserver на устройствах с Android 6.0 и более ранними версиями.

В Android 7.0 процесс выделения буфера был изменен на новый механизм, который обеспечивает гибкость и минимизирует влияние на существующие реализации. В стеках MediaDrm и MediaCrypto в новом процессе mediadrmserver буферы выделяются иначе, и поставщики должны обновить дескрипторы безопасных буферов, чтобы их можно было передавать через Binder, когда MediaCodec вызывает операцию расшифровки в MediaCrypto.

Рисунок 3. Android 7.0 и более поздних версий: выделение буфера в mediaserver.

Использовать нативные дескрипторы

Функция OMX::allocateBuffer должна возвращать указатель на структуру native_handle, которая содержит дескрипторы файлов и дополнительные целочисленные данные. native_handle обладает всеми преимуществами использования дескрипторов файлов, включая существующую поддержку связывателя для сериализации/десериализации, и при этом обеспечивает большую гибкость для поставщиков, которые в настоящее время не используют дескрипторы файлов.

Используйте native_handle_create(), чтобы выделить нативный дескриптор. Код фреймворка получает право собственности на выделенную структуру native_handle и отвечает за освобождение ресурсов как в процессе, в котором изначально выделяется native_handle, так и в процессе, в котором она десериализуется. Фреймворк выпускает нативные дескрипторы с помощью native_handle_close(), а затем native_handle_delete(), а также сериализует и десериализует native_handle с помощью Parcel::writeNativeHandle()/readNativeHandle().

Поставщики SoC, которые используют дескрипторы файлов для представления защищенных буферов, могут заполнить дескриптор файла в native_handle. Поставщики, которые не используют дескрипторы файлов, могут представлять защищенные буферы с помощью дополнительных полей в native_buffer.

Как задать местоположение для расшифровки

Поставщики должны обновить метод расшифровки OEMCrypto, который работает с native_handle, чтобы выполнить любые необходимые для поставщика операции, которые сделают native_handle пригодным для использования в новом пространстве процесса (обычно изменения включают обновления библиотек OEMCrypto).

Поскольку allocateBuffer – это стандартная операция OMX, в Android 7.0 включено новое расширение OMX (OMX.google.android.index.allocateNativeHandle) для запроса этой поддержки и вызов OMX_SetParameter, который уведомляет реализацию OMX о том, что она должна использовать нативные дескрипторы.