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

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

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

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

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

dm-verity-хэш-таблица

Рисунок 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 в метаданные verity.
  6. Объедините образ системы, истинные метаданные и хеш-дерево.

Подробное описание хеш-дерева и таблицы dm-verity см. вThe Chromium Projects — Verified Boot .

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

Как описано во введении, хеш-дерево является неотъемлемой частью 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 МБ.

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

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

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

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

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

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

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

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

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

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

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

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

Включение подписи таблицы в метаданные

Объедините сигнатуру таблицы и таблицу dm-verity в метаданные 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, чтобы найти наилучшую конфигурацию для вашего устройства.