Comments 28
А какая еще может быть польза от MPU, кроме как отслеживание переполнений стека/кучи ?
1--Зашита прошивки. Вы можете наложить запрет на запись тех адресов, в которых лежит исполняемый код прошивки.
2--Защита ключей шифрования от чтения.
3--Вся область RAM памяти может отображаться как доступная для чтения и записи. Если нам не нужно выполнять код из RAM, можно установить бит запрета выполнения (XN).
1) и 3) это защита от бага в самой прошивке ? На счет 2) не понятно от кого защита. Если есть возможность исполнить код, то и отключить MPU не проблема. Я сначала подумал, что с помощью MPU можно как-то отделить "ядро" от "приложения" в примитивной операционной системы. Но потом подумал еще и засомневался в этой идеи.
У меня есть простецкая однозадачная ОС для STM-ок в которой приложения отделены от ядра, но защиты никакой нет, т.е. приложение может легко запороть ядро системы, случайно или намеренно. Может ли тут мне помочь MPU ?
С защитой ключей шифрования от чтения нужно учесть, что MPU не проверяет обмены DMA. Так что если вы перекрыли загруженной пользовательской программе доступ к памяти ключей (и регистрам MPU, естественно), но не закрыли доступ к периферии с DMA - все труды были напрасны. Злоумышленник может через DMA переписать ваши ключи в свою область памяти или просто выкачать наружу.
С защитой ключей шифрования от чтения нужно учесть, что MPU не проверяет обмены DMA. Так что если вы перекрыли загруженной пользовательской программе доступ к памяти ключей (и регистрам MPU, естественно), но не закрыли доступ к периферии с DMA - все труды были напрасны. Злоумышленник может через DMA переписать ваши ключи в свою область памяти или просто выкачать наружу.
Для того чтобы наложить запрет на чтение или запись со стороны DMA в некоторых микроконтроллерах есть подсистема MAM (Matrix Access Monitor)
Модуль MAM предоставляет интегрированную масштабируемую архитектуру для управления доступом, защиты памяти и изоляции периферийных устройств. Он позволяет программному обеспечению настраивать ведущее устройство (включая ядра и не ядра) для управления правами доступа к каждому блоку ведомого устройства (памяти или периферийных устройств), а именно, доступом на чтение, запись, выполнение и супервизор.
MAM позволяет не просто наложить ограничение на память как это происходит в MPU, а наложить ограничение на память для конкретного блока: DMA0, DMA1, ETH или CPU2.
Перечисленные применения - это не главное, для чего предназначен MPU. Его главная задача - исключить для недоверенного процесса возможность как-то повлиять на код и данные операционной системы и других процессов. Это позволяет одновременно выполнять на одном ядре сертифицированный по функциональной безопасности код, и кроме того ещё и какой-то некритический код, который в случае проблем может быть отдельно перезапущен, перезагружен, и обновлён без прерывания работы всей системы.
Кроме того, MPU позволяет защитить часть кода в системе от считывания из другой части кода, что может быть важно для обеспечения секретности его алгоритмов работы. Например, так код радиомодуля может быть защищён от считывания из пользовательского кода и дизассемблирования.
То, о чем Вы пишите это задача для MMU. На сколько я понимаю, в MPU нет приоритезации уровней исполнения, а значит любой кусок кода может отключить MPU и сходить посмореть что там в "системе" или даже пропатчить её. Я как раз подумывают над тем как бы "скрыть" ядро системы от приложений и вижу, что с помощью только лишь MPU это не реализуемо.
Я сам не использовал MPU, но, насколько я понимаю, от отключения MPU можно защититься настройкой MPU так, чтобы его регистры, регистры управления прерываниями, и область памяти с векторами прерываний были защищены от записи. Тогда при попытке записи произойдёт прерывание MemManage Fault, которое будет гарантировано обработано кодом ОС. Правда, в этой статье говорится, что запись при этом всё-таки произойдёт, поэтому в этом случае вероятно потребуется перезагрузка, хотя не обязательно. Наверное, это недоработка в железе, запись в этом случае происходить не должна.
Хорошая идея. Защитить от записи сами регистры MPU. Я бы до такого никогда не додумался.
Я вот не уверен, что это возможно. Как Вы будете управлять таким MPU ? Вы же пишете, что в обработчике нужно обязательно отключать защиту иначе выход из обработчика будет невозможен.
Невозможен будет возврат к коду, который вызвал срабатывание обработчика, и продолжение его выполнения. Но это и не должно происходить - обработчик должен остановить выполнение процесса, нарушившего права доступа к памяти, инициировать его повторную инициализацию при необходимости, и переключиться на другие процессы. При этом в самом обработчике MemManage Fault переконфигурирование MPU может быть разрешено установкой специального бита HFNMIENA в MPU Control Register в 0.
При этом в самом обработчике MemManage Fault переконфигурирование MPU может быть разрешено установкой специального бита HFNMIENA в MPU Control Register в 0.
Так ведь запись в регистры MPU запрещена самим MPU ? Автор статьи пишет:
Если же из MemManage_Handler вызвать функцию из запрещенного к исполнению региона, то сработает уже HardFault_Handler.
Иными словами, запретив доступ к регистрам MPU мы остаемся без средств управления этим MPU и только Reset нас спасет.
Можно запретить доступ к региону для непривелегированного режиму, оставив доступ для привелегированного. Т.е. пользовательский код не имеет доступа к регистрам MPU, а операционка - имеет.
Смотрите поле AP регистра MPU_RASR, а также бит PRIVDEFENA в MPU_CTRL.
Специально для того, чтобы настройки MPU можно было изменить, для обработчиков прерываний MemManage Fault и HardFault Fault сделано аппаратное исключение - если бит HFNMIENA установлен в 0, то HardFault в MemManage_Handler не происходит.
Вот цитата из TRM:
HFNMIENA - This bit enables the MPU when in Hard Fault, NMI, and FAULTMASK escalated handlers.
If this bit = 1 and the ENABLE bit = 1, the MPU is enabled when in these handlers. If this bit = 0, the MPU is disabled when in these handlers, regardless of the value of ENABLE.
Там скорее недовереному коду должно быть запрещёно вообще писать в какие-либо регистры управления ядром и периферией самостоятельно, он должен делать это через ОС. Такому процессу должен быть разрешен доступ только к его собственному коду в ROM и его данным в RAM, и запрещено всё остальное. Возможно, кроме записи в экранный буфер, если он есть. Правила перекрытия регионов в MPU позволяют сделать это достаточно легко.
На Cortex-M для такого нужно ещё и наличие TrustZone (она есть в «новых» ядрах типа М33). Она добавляет уровни привилегий, позволяющие закрыть регистры MPU/MMU от юзерского кода. Без неё же, как уже написали, отключить/перенастроить MPU может кто угодно.
Вот!
TrustZone понижает требования к отсутствию уязвимостей в ОС и драйверах для гарантии недоступности памяти и периферии защищённых модулей. Но при отсутствии таких уязвимостей разделение памяти может работать и без TrustZone. С периферией сложнее, без TrustZone доступ к ней без дыр в защите возможен только из привилегированного режима, тогда она будет защищена от доступа из непривилегированного.
В MPU можно разрешить/запретить работу кэша для каждой области. (Для чего я его применял)
в этих областях можно отключить кэш програм/данных и , например, если шлете туда данные АЦП через DMA , потом читать из памяти процом, будут читаться валидные данные, а не то, что в кэше завалялось.
при отправке на экранчик через дма - аналогично, если нужен кэш (а быстродействия много не бывает), то буфер экрана тоже в мпу зоне с выключенным кэшем, иначе на экран полетит не то, что фактически в памяти, а то что кэш думает что там должно лежать
Я немного поисследовал вопрос, происходит ли запись вместе с прерыванием, если она запрещена в MPU, и не нашел никаких подтверждений, что она она всё равно происходит. Вероятно, у вас она выполняется после возвращения из прерывания, в котором отключается MPU - в этом случае она и должна происходить. А так, множество ОС используют этот механизм совместно с разделением процессов на privileged и unprivileged, и успешно защищают память в версиях ядра M3 и M4 безо всякого TrustZone. Например, Zephyr или FreeRTOS-MPU.
Интересный материал, спасибо за ваш труд. Давненько планирую сделать небольшую open-source библиотеку для отслеживания выхода потоков за границы стека через MPU. Реализация будет на основе "жёлтой/красной" зоны и ротации регионов по таймеру (чтобы даже при ограниченном числе регионов успевать проверять разные стеки). При срабатывании библиотека будет вызывать коллбэк с информацией о нарушении, а дальше пользователь сам решает: логировать, перезапускать поток или падать в fault. Что скажете о такой реализации?
Хорошо расписано, реально помогло на проекте
ARM Cortex-M: пуск Memory Рrotection Unit (MPU)