Как стать автором
Обновить

Комментарии 17

В прошлом году интересовался вопросом сохранности содержимого флешки SPI, и появилась мысль об аппаратной защите от записи этих микросхем. И вот что успел выяснить: если это BIOS, то в большинстве случаев ему не нужен доступ на запись в SPI, и аппаратная блокировка помогает сохранить содержимое, при ЛЮБЫХ попытках ЛЮБОГО софта что-то записать. Делается так: флешку на программатор, выставляется конфигурация, где включена защита от записи, прошивается (содержимое флешки остаётся тем же), и третий вывод подтягивается на «землю» (на четвёртый, например). В таком состоянии даже программатор уже не может ни записать, ни стереть.
Для UEFI платформы первый эксперимент провалился, а дальше пока не было у меня времени и плат для экспериментов. Провалился по простой причине — в современных микросхемах с большой ёмкостью используется QuadSPI, с этом режиме третий вывод не используется для защиты от записи, а используется для чтения-записи в режиме Quad. Возможно, это не у всех микросхем есть такая функция. Но на той 25Q64FVAIQ не получилось сделать защиту, нужна микосхема без IQ в конце, или какая-то другая. Время появится — буду пробовать ещё.
Я тоже интересовался этой темой и у меня получилось защитить микросхему SPI от записи, но для этого нужно либо выносить NVRAM на отдельную микросхему, либо заниматься его эмуляцией. Режим QuadSPI можно отключить, но на работающей системе это будет сделать довольно непросто — придется патчить сразу в нескольких местах.
В моём случае не получилось выключить, т.к. на микросхеме за включение этого режима отвечают биты, часть которых OTP (однократно записываемые), и, видимо, настроено уже производителем флешки. Но не исключаю и варианта, что просто мой программатор как-то не так с ней работает.
Меня больше интересует не то, как эмулировать NVRAM или как вынести его на отдельную мс, а тот случай, когда NVRAM уже содержит нужные значения, и при этом закрыть возможность записи — будет ли корректно работать UEFI, загрузчик, ОС? Да пусть даже не совсем корректно (например, нельзя будет изменить порядок загрузки или ещё что) — но хотя бы просто смогла бы запуститься ОС.
В том то и дело, что корректно работать такая система не будет — упадет на первом же SetVariable, т.к. запись в NVRAM происходит при каждой загрузке (сохраняется последняя загруженная конфигурация, обновляется список загрузочных устройств, обновляется значение MonitonicCounter и т.п.). Чтобы справиться с этой проблемой, нужно либо заменять оригинальный драйвер NVRAM на такой, который на флеш вообще не пишет, либо сразу же загружать вышеупомянутый эмулятор CrEmuVariable, который подменит оригинальные сервисы GetVariable/SetVarible/GetNextVariableName/QueryVariableInfo собственными, и система будет работать. В таком режиме, понятно, тоже есть свои минусы, настройки из BIOS Setup не сохраняются, к примеру, но если все уже настроено — это работает.
Разве UEFI проверяет результат процесса записи какой-либо переменной? Почему-то вот BIOS никак не проверяет — можно даже спокойно писать разными утилитами и в область firmware, и в DMI, ни одна из утилит не ругается, пока не начнёт сверять — при записи флешка «делает вид» что всё пишется (или просто программно это не отследить), но при сверке (считывании) сразу понятно, что ничего не записалось. Поэтому мне не совсем понятно, почему в UEFI должно падать при SetVariable, ведь переменная уже имеет какое-то значение, ранее сохранённое, и если стоит защита от записи, то после попытки записи нового значения просто ничего не поменяется и всё.
Падает потому, что драйвер SPI возвращает ошибку при верификации, и ошибка эта затем доходит до драйвера NVRAM, который из SetVariable возвращает либо EFI_DEVICE_ERROR, либо EFI_WRITE_PROTECTED. Оно не то, чтобы «должно падать», но опыт показывает, что падает. Понятно, что можно сделать написать NVRAM таким образом, чтобы он уживался с RO-флешем, и у AMI есть даже build token для такого режима, но если брать неадаптированную прошивку, она неизбежно упадет где-нибудь посреди фазы DXE, т.к. первая же SetVariable вместо ожидаемого EFI_SUCCESS вернет что-нибудь из вышеперечисленного.
Спасибо за разъяснение! Значит, в случае UEFI сделать аппаратную защиту от записи намного сложнее, но всё же можно.
Пожалуйста. Я бы сказал, что даже нужно, но сделать её получится нормально только на весьма небольшом количестве систем, т.к. кроме UEFI на том же чипе у нас еще хранятся данные ME (который тоже без устали пишет в свой NVRAM), GbE (этот переживает RO без проблем) и EC (это тоже). Проще всего разделить код и данные на две разных микросхемы, оставив МЕ и NVRAM в первой, а все исполняемые компоненты — во второй, которую и защитить от записи аппаратно (или, на крайний случай, джампером на выводе #WP).
У меня такая система уже есть и работает, но в ней изначально задумывалось посадочное место под второй чип. Можно сделать такую же конфигарацию на системах AMD с одним чипом, либо договариваться с Intel об отключении ME, но тут мне уже NDA не позволяет продолжать.
Это всё по большей части ноутбуков касается. А если обычную десктопную плату взять — то для домашнего пользователя intel me как бы и не особо надо, EC просто нет на плате.
Мне кажется, что и оставлять для записи даже отдельную мс не нужно — пару раз попадались десктопные платы, которые не стартовали нормально из-за мусора (нет, не в SPI флешке) в CMOS, или даже не мусора, а каких-то некорректных записей. Так же и с NVRAM может случиться — если вдруг содержимое каких-то переменных будет испорчено — кто знает, как себя поведёт UEFI — хорошо, если просто не загрузится, а ведь может и что-то аппаратно испортить (ну там частоты не те выставить или ещё что).
Получается, если хотим полностью защищённую систему от записи — нужно систему на BIOS, с чипом SPI, и ОС на SD-карте с блокировкой от записи (и проверенный картридер, а то некоторые игнорируют защёлку).

Ещё одна мысль пришла — а почему производители не учитывают тот факт, что в SPI чипах можно выбрать несколько разных регионов для защиты от записи, при этом остальное будет незащищено? Тогда можно было бы основную firmware писать в те области, которые будут после записи защищены, а для NVRAM и прочего выделять место, которое будет доступно для записи, ну и после этого WP# на землю. При этом можно что угодно сколько угодно писать, и основная прошивка никак не пострадает. Хотя с учётом того, что бывает просто износ, и UEFI проверяет корректность записи… тоже мало что даст.
МЕ нынче просто так не отключишь, так что нужен он домашнему пользователю или нет — Интел за него уже все решил.
Платы, не стартующие из за мусора в CMOS, я тоже видел, причем иногда они даже но кнопку питания не реагируют, до того залипли. С NVRAM шансы на такой исход пониже, все-таки там форматы поинтереснее, чем голый SRAM, и валидация похитрее.
Польностью защищенную систему стоит собирать на процессоре AMD без PSP (на eKabini, например), вместо BIOS лучше использовать Coreboot/Libreboot с reproducible builds, а NVRAM не использовать вообще, задавая все настройки при компиляции.
Почему не пользуются защитой отдельных регионов? Никто заморачиваться не хочет, т.к. тот же уровень защиты обеспечивают PR-регистры чипсета, которые при этом намного проще программируются и от производителя SPI-чипа не зависят.
— «Проще всего разделить код и данные на две разных микросхемы» стандартным интеловским Fitc+xml или чтото свое с бубнами и на коленке?

— «договариваться с Intel об отключении ME» нам Интел ответил «увы это на всегда» (вырезали/вычистили все, но система получала резет каждые 30 минут)
Стандартно через FITC, да. Если там выбрать два чипа (и развести их правильно, по соотвествующему SPI_CSx на каждый), то чипсет их отображает друг за другом в непрерывное адресное пространство в конце четвертого гигабайта, а дальше просто оставляем все, кроме томов PEI и DXE, по адресам первой микросхемы, а на вторую после прошивки ставим RO через регистр STATUS и вывод #WP.

По поводу МЕ — NDA мешает рассказывать, что Intel сказал нам, могу только сказать, что на рынке Embedded и Industrial достаточно плат с отключенным МЕ, только танцев с бубном вокруг его отключения и работы системы без него с каждым поколением становится все больше.
Скажем так, по информации от китайских коллег «отключенный ME» на самом деле не очень честная фраза, ME продолжает дрючить какой-то строжевой таймер и ещё какую-то лажу, но не даёт mailbox и прочие свои «вкусности», по сути дела в плате остаётся minimal ME. И судя по тенденции (945/965 — делайчтохочешь, по x45 — делай правильный дескриптор @ удаляй, далее жоппа) скоро штеуд в ME засунет первоначальную настройку оборудования (хехехе, например MRC туда мигрирует полностью) и всё будет зашибись, и наверное вообще не надо будет думать.
«Отключить» МЕ нужно так, чтобы он а) не мешал никому своими вотчдогами, б) ничего не писал на флеш (читает пусть сколько угодно) и в) система при этом не сильно проседала по производительности или возможностям. Понятно, что совсем честное отключение — это когда региона МЕ вообще нет, но на безрыбье народ согласен и на вышеперечисленное, хоть и добиться его становится сложнее с каждым поколением.
Ну если есть куууча времени — берите две системы одинаковые, на одной вырезайте, далее анализируйте ВСЕ чипсетные регистры на обеих системах одновременно. Китайцы что-то раскопали, но после допроса с пристрастием была получена только фраза «тама ro регистра прыгает, немагу её модифицировать однако», правда где и что — делиться не хотят.

Зависит от реализации, но в большинстве своем достаточно тупое, зато работающее "пишем в хранилище, пока оно не заполнилось, когда заполнилось - убираем мусор". Биты на NOR flash можно менять произвольно только с Erase Polarity (т.е. с 1) на 0, поэтому переменные делают так, чтобы их статут с "валидная" на "удалена" можно было менять сменой одного бита с 1 на 0, для этого обычно и используется поле State в заголовке, о котором вы спрашивали в соседней теме. Т.е. запись в переменную получается в виде "прошелся по всем переменным, если нашел уже одну с таким же именем, и она валидная - поставь ей признак "удаляется прямо сейчас", найди место под новую переменную в конце хранилища, если оно есть - пиши туда заголовок новой переменной, если записался - ставь признак "заголовок валидный", пиши новое тело переменной, если записалось, ставь признак "тело валидное", если все нормально - ставь в предыдущую переменную в заголовок признак "удалена", и так далее. Если место кончилось, можно все валидные переменные из хранилища по одной переписать в память, или в другую копию хранилища (обычно их не менее двух, потому что так они переживают повреждение заголовка у самого хранилища, а не только у переменных).

Надеюсь, что смог на пальцах пояснить, если нет - исходники стандартной реализации из EDK2 доступны для изучения:

  1. https://github.com/tianocore/edk2/blob/1f026ababf350746c6071c0873d9d1c8824029ca/MdeModulePkg/Universal/Variable/RuntimeDxe/VariableDxe.c и вокруг - драйвер для UEFI NVRAM.

  2. https://github.com/tianocore/edk2/blob/7c0ad2c33810ead45b7919f8f8d0e282dae52e71/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.c - отдельный FTW-драйвер, используемый предыдущим для безопасной работы процедуры Reclaim, т.е. сборки мусора, для которой не нужно дополнительное хранилище такого же размера, а достаточно меньшего по размеру FTW-блока.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации