Внедрение dm-verity

Android 4.4 и более поздних версий поддерживает проверенную загрузку с помощью дополнительной функции ядра device-mapper-verity (dm-verity), которая обеспечивает прозрачную проверку целостности блочных устройств. dm-verity помогает предотвратить появление постоянных руткитов, которые могут завладеть root-правами и поставить под угрозу устройства. Эта функция помогает пользователям Android быть уверенными, что при загрузке устройства оно находится в том же состоянии, в котором оно использовалось в последний раз.

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

Функция dm-verity позволяет вам просмотреть блочное устройство, базовый уровень хранения файловой системы, и определить, соответствует ли оно ожидаемой конфигурации. Это делается с помощью криптографического хэш-дерева. Для каждого блока (обычно 4 КБ) существует хэш SHA256.

Поскольку хэш-значения хранятся в дереве страниц, для проверки остальной части дерева необходимо доверять только «корневому» хешу верхнего уровня. Возможность изменить любой из блоков будет эквивалентна взлому криптографического хеша. См. следующую диаграмму для изображения этой структуры.

дм-правдивость-хеш-таблица

Рисунок 1. хеш-таблица dm-verity

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

Операция

Защита dm-verity находится в ядре. Таким образом, если рутирующееся программное обеспечение скомпрометирует систему до появления ядра, оно сохранит этот доступ. Чтобы снизить этот риск, большинство производителей проверяют ядро ​​с помощью ключа, встроенного в устройство. Этот ключ нельзя изменить после того, как устройство покинет завод.

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

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

Вместо этого dm-verity проверяет блоки индивидуально и только при доступе к каждому из них. При чтении в память блок параллельно хешируется. Затем хэш проверяется вверх по дереву. А поскольку чтение блока — такая дорогостоящая операция, задержка, возникающая при этой проверке на уровне блока, сравнительно номинальна.

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

Приложения могут продолжить работу без результирующих данных, например, когда эти результаты не требуются для основной функции приложения. Однако если приложение не сможет продолжить работу без данных, оно завершится ошибкой.

Прямое исправление ошибок

В Android 7.0 и более поздних версиях надежность dm-verity повышается за счет прямой коррекции ошибок (FEC). Реализация AOSP начинается с обычного кода исправления ошибок Рида-Соломона и применяет метод, называемый чередованием, для уменьшения накладных расходов на пространство и увеличения количества поврежденных блоков, которые можно восстановить. Дополнительные сведения о FEC см. в разделе Строго принудительная проверенная загрузка с коррекцией ошибок .

Выполнение

Краткое содержание

  1. Создайте образ системы ext4.
  2. Создайте хэш-дерево для этого изображения.
  3. Создайте таблицу dm-verity для этого хэш-дерева.
  4. Подпишите эту таблицу dm-verity , чтобы создать подпись таблицы.
  5. Объедините подпись таблицы и таблицу dm-verity в метаданные истинности.
  6. Объедините образ системы, метаданные истинности и хеш-дерево.

Подробное описание хеш-дерева и таблицы dm-verity см. в разделе «Проекты Chromium — проверенная загрузка» .

Генерация хеш-дерева

Как описано во введении, хеш-дерево является неотъемлемой частью dm-verity. Инструмент cryptsetup сгенерирует для вас хэш-дерево. Альтернативно, совместимый определяется здесь:

<your block device name> <your block device name> <block size> <block size> <image size in blocks> <image size in blocks + 8> <root hash> <salt>

Для формирования хеша образ системы разбивается на уровне 0 на блоки по 4 КБ, каждому из которых назначается хеш SHA256. Уровень 1 формируется путем объединения только хэшей SHA256 в блоки по 4 КБ, в результате чего изображение становится намного меньше. Уровень 2 формируется идентично, используя хэши SHA256 уровня 1.

Это делается до тех пор, пока хэши SHA256 предыдущего слоя не смогут поместиться в один блок. Когда вы получите SHA256 этого блока, у вас будет корневой хэш дерева.

Размер хэш-дерева (и соответствующее использование дискового пространства) зависит от размера проверяемого раздела. На практике размер хеш-деревьев обычно невелик, часто менее 30 МБ.

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

Чтобы сгенерировать хэш-дерево, объедините хеши уровня 2 с хешами слоя 1, хэши слоя 3 с хэшами слоя 2 и т. д. Запишите все это на диск. Обратите внимание, что это не относится к уровню 0 корневого хеша.

Напомним, общий алгоритм построения хеш-дерева следующий:

  1. Выберите случайную соль (шестнадцатеричная кодировка).
  2. Распакуйте образ вашей системы на блоки по 4 КБ.
  3. Для каждого блока получите свой (соленый) хэш SHA256.
  4. Объедините эти хеши, чтобы сформировать уровень
  5. Дополните уровень нулями до границы блока 4 КБ.
  6. Объедините уровень с вашим хэш-деревом.
  7. Повторяйте шаги 2–6, используя предыдущий уровень в качестве источника для следующего, пока у вас не останется только один хеш.

Результатом этого является один хэш, который является вашим корневым хешем. Это и ваша соль используются при построении таблицы сопоставления dm-verity.

Построение таблицы сопоставления dm-verity

Создайте таблицу сопоставления dm-verity, которая идентифицирует блочное устройство (или цель) для ядра и расположение хэш-дерева (которое имеет то же значение). Это сопоставление используется для создания и загрузки fstab . В таблице также указаны размеры блоков и hash_start, начальное местоположение хеш-дерева (в частности, номер его блока от начала изображения).

См. cryptsetup для подробного описания полей таблицы сопоставления целей verity.

Подписание таблицы dm-verity

Подпишите таблицу dm-verity, чтобы создать подпись таблицы. При проверке раздела сначала проверяется подпись таблицы. Это делается с помощью ключа вашего загрузочного образа в фиксированном месте. Ключи обычно включаются в системы сборки производителей для автоматического включения на устройствах в фиксированном месте.

Чтобы проверить раздел с помощью этой подписи и комбинации клавиш:

  1. Добавьте ключ RSA-2048 в формате, совместимом с libmincrypt, в раздел /boot по адресу /verity_key . Определите местоположение ключа, используемого для проверки хэш-дерева.
  2. В fstab соответствующей записи добавьте verify к флагам fs_mgr .

Объединение подписи таблицы в метаданные

Объедините подпись таблицы и таблицу dm-verity в метаданные истинности. Весь блок метаданных имеет версии, поэтому его можно расширять, например, добавляя второй тип подписи или изменяя порядок.

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

Это гарантирует, что вы не выбрали проверку непроверенного раздела. Если это так, отсутствие этого магического числа остановит процесс проверки. Это число похоже на:
0xb001b001

Значения байтов в шестнадцатеричном формате:

  • первый байт = b0
  • второй байт = 01
  • третий байт = b0
  • четвертый байт = 01

На следующей диаграмме показана разбивка метаданных истинности:

<magic number>|<version>|<signature>|<table length>|<table>|<padding>
\-------------------------------------------------------------------/
\----------------------------------------------------------/   |
                            |                                  |
                            |                                 32K
                       block content

И эта таблица описывает эти поля метаданных.

Таблица 1. Поля метаданных Verity

Поле Цель Размер Ценить
магическое число используется fs_mgr для проверки работоспособности 4 байта 0xb001b001
версия используется для версии блока метаданных 4 байта в настоящее время 0
подпись подпись таблицы в дополненной форме PKCS1.5 256 байт
длина стола длина таблицы dm-verity в байтах 4 байта
стол таблица dm-verity, описанная ранее длина таблицы в байтах
прокладка эта структура дополнена 0 и имеет длину 32 КБ. 0

Оптимизация dm-verity

Чтобы получить максимальную производительность от dm-verity, вам необходимо:

  • В ядре включите NEON SHA-2 для ARMv7 и расширения SHA-2 для ARMv8.
  • Поэкспериментируйте с различными настройками упреждающего чтения и prefetch_cluster, чтобы найти лучшую конфигурацию для вашего устройства.