- PVSM.RU - https://www.pvsm.ru -
Продолжаем наш цикл статей, посвященный столетию Великой Октябрьской… ARM TrustZone.
Сегодня мы разберемся, что такое Secure World, Normal World, как на программном уровне взаимодействуют две ОС – доверенная (TEE) и гостевая. Узнаем, для чего нужен и как работает Secure Monitor, как обрабатываются прерывания от устройств.
Если готовы – добро пожаловать под кат.
В прошлой статье [1] я рассказывал об аппаратной реализации. Там есть все про аппаратное разделение миров, как не допустить гостевую ОС к доверенной памяти и периферии и так далее. Возьмем оттуда одну мысль в качестве связующей:
С точки зрения программной реализации это все, что нам нужно. Аппаратное обеспечение находится по ту сторону абстракции, наблюдая только за NS, а программное обеспечение не только исполняется по-разному при NS=0 и 1, но и может этот бит менять.
В обоих режимах (NS=0 и NS=1) процессор может полноценно работать, настолько, что в каждом режиме может существовать своя ОС:
У каждой ОС будут своя карта виртуальной памяти, свои приложения, прерывания, драйверы и так далее.
Конечно, не всегда у нас на ARM крутятся две операционки. Доверенный код может быть и не полноценной ОС, а каким-то маленьким монитором безопасности. Или может полностью отсутствовать. Но в смартфонах и планшетах отсутствие доверенной ОС – редкость, там в основном есть и TEE (доверенная ОС) и обычная ОС (например, Android).
Только не принимайте за чистую монету название TEE, Trusted Execution Environment. Если TEE называется доверенной – значит, кто-то ее коду доверяет, потому что код ведет к достижению его целей. Может быть, его цель – уничтожить Вселенную, как знать? Вы же исходников не видите.
Процессоры всегда стартуют в режиме Secure. Есть много процессоров ARMv7A, где Security Extensions отключены. И тогда они всегда работают как Secure. Например, всеми любимая Sitara.
Но в любом случае – процессоры всегда стартуют в режиме Secure.
Первым в процессе загрузки участвует загрузчик, и в случае с TrustZone используется одна из реализаций идеи Trusted Boot – механизма, проверяющего подпись образа перед его запуском. Общий алгоритм тут таков:
Открытый ключ для проверки подписи в процессор прошивается однократно, и после этого только первичный загрузчик, подписанный закрытой частью этого ключа, может быть запущен. Здесь есть и поле для злоупотреблений со стороны крупных производителей.
Подробнее о загрузке ARM – в этой статье [2].
Далее загрузчик проверит подпись доверенной ОС (TEE) и запустит TEE. Та инициализирует все, что нужно в TrustZone, покидает режим Secure и передаст управление гостевой ОС (например, Linux).
Если же никакая TEE не используется, и управление прямо из загрузчика передается в Linux, то Linux так и работает в режиме Secure. Это, однако, не делает его безопасной ОС: без барьера между Secure World и Normal World нет и доверенной ОС.
Заметим, что без Trusted Boot безопасность TEE была бы скомпрометирована, так как ее можно было бы подменить, подменив загрузчик. Важна вся цепочка удостоверений подлинности, обеспечиваемая подписями бинарников.
На картинке изображены две ОС, которые мы только что загрузили. Гостевая ОС может вызывать функции TEE, для этого она использует Secure Monitor.
В этой статье мы разберемся, что это за Secure Monitor, как его используют и как он работает.
В ARMv7A есть довольно много режимов работы. На картинке они разделены на уровни PL0, PL1, PL2 и некоторые из уровней Secure, а некоторые – Non-secure.
PL0 – это непривилегированный (unprivileged) режим, в котором в ОС исполняются обычные программы. Каждая программа запущена со своей картой памяти, настроенной через MMU, так что к другим программам вот так просто залезть не может. Но и в ОС она также залезть не может, потому что так настроила сама ОС. Чтобы обратиться к ОС, приложение делает системный вызов (Supervisor Call, команда SVC), и процессор перепрыгивает в режим Supervisor, PL1.
Весь основной код ОС исполняется в режиме Supervisor (SVC), на уровне PL1. Тут у ядра ОС тоже есть своя таблица MMU, и ядро видит память не так, как приложения. Кстати, ядро не обязательно должно видеть все страницы памяти приложения, это будет менее безопасно.
Другой важный режим ядра – это IRQ. Туда попадают при срабатывании прерываний. IRQ находится на уровне PL1, и поэтому-то все нормальные драйверы устройств в Linux работают на уровне ядра. Парный IRQ режим FIQ – это быстрое прерывание. В Linux он совсем не используется, но в реализации TrustZone ему нашли применение – об этом мы поговорим позднее.
Еще есть режимы Undef, Abort – это исключения при работе программы. Если приложение ОС (или код ядра) попытается выполнить недопустимую команду, произойдет Undef, если обратится к запрещенной ему памяти – будет Abort. Об этом я уже писал в прошлой статье [1]. В реализации TrustZone мы можем выбрать, будет Abort обрабатываться в гостевой ОС (Linux) или перенаправляться в доверенную ОС (TEE). В последнем случае мы можем, например, запротоколировать попытку гостевой ОС залезть в область доверенной ОС.
Всеми покинутый и забытый режим System редко, если вообще, используется.
Все вышеперечисленные режимы есть как в Secure, так и в Non-Secure-режимах работы. При этом, например, Secure Supervisor и Non-Secure Supervisor – отдельные режимы. У них разные таблицы MMU, разные права доступа (из-за NS-бита), их данные хранятся в разных линейках кеша и т.п.
Именно из-за дублирования режимов Secure и Non-Secure на одном ядре и можно запустить две ОС.
На рисунке выше была еще пара режимов:
Режим HYP используется для аппаратной виртуализации, как в VMWare. Он находится на уровне PL2, – он даже главнее ядра гостевой ОС и может там все разрешить и запретить, прямо как TEE. Но мы совсем не будем в этой статье говорить про виртуализацию по двум причинам: во-первых, мало процессоров ARMv7 и софта с ее поддержкой, во-вторых, от Virtualization Extensions в ARM все становится еще запутанней. Так что лучше оставить виртуализацию пока в стороне.
А вот режим Secure Monitor нам очень нужен, он сделан для переключения между Secure и Non-Secure OC. Давайте его рассмотрим со всех сторон.
У нас есть две полноценные ОС, и глобально отличаются они только битом NS:
Ведь логично, что гостевая ОС не может поменять себе бит NS и получить привилегии Secure? Абсолютно логично. Менее ожидаемо, что и Secure OS не может вот так взять и переключиться в режим Non-Secure, поменяв NS на 1. Но это тоже так.
Дело в том, что переключение между режимами оказалось несколько сложнее, чем один бит поменять:
Вот для этого и придумали режим Secure Monitor. Попадают туда с помощью вызова “SMC #0“, что расшифровывается как Secure Monitor call. Причем и Secure код должен вызывать “SMC #0“, чтобы переключиться в Non-Secure. И Non-Secure в Secure перепрыгивает также.
В целом, вызов SMC подобен системному вызову операционной системы (SVC):
Различие заключается в том, что возврат из системного вызова осуществляется не так, как сам вызов, а вот переход между Secure и Non-Secure – симметрично, через SMC #0.
Три особенности режима Secure Monitor позволяют ему выполнять переключение контекста Secure/Non-Secure.
Например, мы хотим, чтобы TEE нам подписала какой-то документ. Данные о документе мы положим в регистры процессора, например, так:
Мы вызываем SMC #0 для вызова TEE. В ответ мы ожидаем от TEE подпись в указанном буфере и код результата в регистре R0, чтобы понять – успешно прошла операция или нет.
То есть, налицо некий протокол обмена между гостевой ОС и TEE. В ARM в общем случае можно вести себя как угодно и придумывать любые концепции обмена, но есть принятый всеми механизм обмена, описанный в ARM SMC calling convention [3]. Там описано, какие регистры используются для передачи кода команды, данных, для возвращаемых значений и так далее.
Начнем с того, что код инициализации TEE записывает адрес точки входа в Secure Monitor (адрес подпрограммы) в таблицу векторов исключений режима Monitor, на которую указывает регистр MVBAR.
Регистр MVBAR доступен только в режиме Secure и указывает на особую таблицу векторов исключений, используемую только при переходе в режим Secure Monitor.
У ARM есть и обычная таблица векторов, в которой указаны точки входа в SVC, IRQ, FIQ и так далее. Эта таблица расположена по умолчанию по адресу 0x00000000, но адрес может быть настроен регистром VBAR.
Разумеется, для работы двух ОС там предусмотрено два регистра: Secure VBAR и Non-secure VBAR. Какой из них доступен, зависит от бита NS.
Так вот, MVBAR используется не для SVC, IRQ и так далее, а только для SMC и пары исключений, которые можно настроить на попадание в Monitor Mode. Например, мы можем настроить Abort и FIQ на попадание в Secure Monitor, и благодаря этому перехватывать эти исключения.
При инициализации TEE также устанавливает адрес головы стека для Secure Monitor, и you’re all set, как говорят за океаном.
Пример реализации Secure Monitor можете посмотреть в исходниках OP-TEE, код действительно несложный: https://github.com/OP-TEE/optee_os/blob/master/core/arch/arm/sm/sm_a32.S [4].
Посмотрим теперь, что произойдет при вызове команды SMC #0 из гостевой ОС.
srsdb sp!, #CPSR_MODE_MON // Записать на стек LR и SPSR
rfefd sp! // rfe=return from exception
Вот очень упрощенное описание того, что вы увидите в приведенном выше по ссылке коде. Там операции даже не все выполняются в таком же порядке. Цель была передать общий смысл, не более того.
На самом деле, это почти все, что делает Secure Monitor – передает управление Secure OS. Когда Secure OS закончит с вызовом, она так же вызовет SMC #0. Secure Monitor поймет по NS=0, что сейчас он Secure, и нужно возвращаться в Non-Secure, и сделает те же команды, но немного наоборот.
Если вы полезли разбираться с кодом, то вот еще хинт под спойлером:
Fast Call – это как прерывание, он обязательно возвратит управление достаточно быстро. Standard Call – как RPC, после его вызова TEE начинает работать на полную катушку, выполнять разные операции, переключать контексты, может быть и ждать результатов операции.
В принципе, Secure Monitor мог бы и оставить эту проверку на TEE и сразу переключить туда, но тут такая реализация. Важно не запутаться в этом коде и увидеть, что оба вызова исполняются в режиме Secure Supervisor, а не в Secure Monitor.
Еще раз: все вызовы из Non-Secure World попадают на обработку в Secure, а Secure Monitor сам ничего не обрабатывает. Привратник.
Гостевая ОС, как правило, не очень-то и знает, что она гостевая. Настраивает себе память, прерывания, выполняет задачи. Все работает как надо, пока не налетит на какое-то ограничение, наложенное на нее TEE. Если налетит – произойдет Abort, как мы и писали в прошлой статье.
При этом гостевая ОС загрузит кучу драйверов, будет назначать устройствам прерывания и прерывания эти будут приходить в гостевую ОС. А почему, спрашивается, в нее? Вполне может быть, что TEE хочет управлять какими-то устройствами в монопольном режиме и получать от них свои прерывания. Сейчас мы разберемся, как две операционки делят между собой прерывания.
В процессоре ARM основной контроллер прерываний – один (пусть это GICv2), нет отдельных контроллеров для Secure и Non-Secure.
Если происходит прерывание, то GICv2 по умолчанию доставит его в режим Secure. Тогда, если произошло прерывание – будет загружен вектор из Secure VBAR.
Но если мы запускаем TEE и Linux параллельно, то нужно как-то поделить прерывания. Не дело, если все прерывания будут приходить только в TEE (Secure) или в Linux (Non-secure).
Поэтому в GICv2 в рамках поддержки Security Extensions придумали сделать группировку прерываний (регистр GICD_IGROUP):
При такой реализации можно запустить Linux без всяких TEE – и тогда он будет запущен в режиме Secure по умолчанию, настроит себе Secure VBAR, все прерывания будут идти к нему (про VBAR мы писали выше). А если Linux запущен в гостевом режиме, то TEE заранее настроит все ненужные ему прерывания на Group 1, а Linux запустит в Non-Secure режиме. Linux настроит себе Non-Secure VBAR, и все его прерывания будут идти к нему. Идиллия и программная совместимость, драйвер GIC в Linux и знать не должен, работает он в Secure или гостевом режиме.
Ну, казалось бы, все хорошо и понятно. Если произошло Secure-прерывание – будет загружен вектор из Secure VBAR, иначе Non-Secure VBAR.
Так нет же! Мы помним, что просто так перейти из Secure-режима в Non-Secure нельзя, для этого у нас есть Secure Monitor.
Поэтому:
Сухой остаток – Secure-прерывание может произойти в режиме Non-Secure, и тогда оно пойдет через Secure Monitor. Механизм его работы, описанный выше, теперь должен разобраться, не в прерывание ли он послан, и соответственно все обработать. И там все в коде у OP-TEE есть, посмотрите.
Очень полезная таблица на этот счет есть здесь: http://infocenter.arm.com/help/topic/com.arm.doc.faqs/ka16352.html [5]
Но и это еще не все! На самом деле, чтобы это работало, нужно еще кое-что настроить. В регистре SCR, уже знакомом нам, есть биты, настраивающие, какие прерывания и исключения направлять в Secure Monitor, а какие – обрабатывать через VBAR.
На картинке – SCR от ARM Cortex-A5. Биты EA, FIQ и IRQ влияют на маршрутизацию, соответственно, External Abort, и FIQ, и IRQ.
К сожалению, там нет IRQ Group 0 и IRQ Group 1, и можно только либо все IRQ направить в Secure Monitor, либо оставить как есть, через VBAR. Как есть – нас не устроит. Поэтому все разработчики с подачи ARM используют такую схему:
Все эти настройки гостевая ОС уже не сможет изменить. В результате Secure-прерывание всегда генерирует FIQ, а FIQ всегда попадает в Secure Monitor из режима Non-Secure.
Вот так, ARMv7 штука сложная и иногда запутанная.
Таким же образом (через регистр SCR) можно настроить и ловлю External Abort из Non-Secure-режима в Secure Monitor. Это может быть полезно, т.к. External Abort может произойти, например, при попытке доступа из режима Non-Secure к Secure-периферии.
Описать все программирование TrustZone в одной обзорной статье не получилось, и будет продолжение.
В этот раз мы рассмотрели разделение на Secure и Normal World, поразбирались в работе Secure Monitor и узнали, как ловить прерывания в доверенной среде.
В следующей статье будет про TEE: что она делает, насколько она на самом деле самостоятельная ОС, для чего нужны трастлеты, и какой у них жизненный цикл.
Автор: vlk77
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/268868
Ссылки в тексте:
[1] прошлой статье: https://habrahabr.ru/company/aladdinrd/blog/340912/
[2] в этой статье: https://habrahabr.ru/company/aladdinrd/blog/338806/
[3] ARM SMC calling convention: http://infocenter.arm.com/help/topic/com.arm.doc.den0028b/ARM_DEN0028B_SMC_Calling_Convention.pdf
[4] https://github.com/OP-TEE/optee_os/blob/master/core/arch/arm/sm/sm_a32.S: https://github.com/OP-TEE/optee_os/blob/master/core/arch/arm/sm/sm_a32.S
[5] http://infocenter.arm.com/help/topic/com.arm.doc.faqs/ka16352.html: http://infocenter.arm.com/help/topic/com.arm.doc.faqs/ka16352.html
[6] Источник: https://habrahabr.ru/post/342924/
Нажмите здесь для печати.