LINUX.ORG.RU

Как вычислить crc структуры в complie-time?

 , c++17


0

2

Привет, лор.

Речь про плюсы. Хочу разместить в начале прошивки структуру, содержащую некоторую инфу. Типа такого:

// вот упрощённая структура
struct AppInfo
{
    uint32_t signature;
    uint32_t version;
    char name[20];
    uint32_t crc;
}

// и вот я пытаюсь её снабдить CRC:
constexpr AppInfo appInfo __attribute__((section(".app_info"), used)) =
{
    0x0002,
    0x0001,
    "My Device",
    calcCrc32(&appInfo, offsetof(AppInfo, crc))
};

// вот как выглядит функция calcCrc32:
static constexpr uint32_t calcCrc32(const uint8_t* buf, size_t bufLen)
{
    // тут вычисление crc
}

И вот тут ну никак не получается. Компилятор отказывается преобразовывать указатель в const uint8_t* в compile-time. Я нагуглил кучу вопросов про это, и ни одного работающего решения. Вроде как в c++26 обещают что-то подкрутить. Единственное (очень костыльное) решение, которое я придумал - превратить структуру в массив uint8_t:

struct AppHeader
{
    uint8_t bytes[128];
    uint32_t crc;
};

constexpr AppInfo appInfo __attribute__((section(".app_info"), used)) =
{
    .bytes =
        "\0"
        "\1"
        "My Device",
    .crc = calcCrc32(appInfo.bytes, sizeof(appInfo.bytes))
};```

Это коряво, придётся потом вручную парсить массив байтов, вычленяя элементы. Но деваться некуда, таков уж современный c++. Или так, или добавлять в процесс сборки отдельный скрипт для подсчёта crc32. Грустно.
★★

Последнее исправление: Beewek (всего исправлений: 1)
Ответ на: комментарий от Ivan_qrt

Зачем указатель, если он не может быть null’ом?

Это остатки от универсального варианта

static constexpr uint32_t calcCrc32(const void* buf, size_t bufLen)

Поскольку функция с __builtin_bit_cast() вряд ли будет оптимальной для рантайма, я их разделил. Надо будет подчистить. И проверку на то, что bufLen меньше размера структуры, тоже можно добавить. Хотя подозреваю, что компилятор это сам проверит.

Beewek ★★
() автор топика
Ответ на: комментарий от zurg

во, переносимо, без проблем с паддингом, без грязнохачного __builtin_bit_cast

Зато с кучей ещё более грязнохачных макросов:)

К тому же не хватает механизма проверки crc в рантайме, обычный тут может не сработать из-за паддинга.

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

Ну вроде как выяснили, что имея c++20 можно решить эту задачу без препроцессора, и без явного использования __builtin_bit_cast. Используя официальный std::bit_cast().

Beewek ★★
() автор топика
Ответ на: комментарий от Beewek

К тому же не хватает механизма проверки crc в рантайме,

? да той же самой calcCrc32, пакованная структура ap_inf_bs же есть на стороне девайса

zurg
()
Последнее исправление: zurg (всего исправлений: 2)
Ответ на: комментарий от zurg

Вот представь, есть микроконтроллер, есть две области памяти: загрузчик и приложение. Формирование AppInfo происходит в момент компиляции приложения. При подаче питания управление получает загрузчик. Он считывает структуру AppInfo по заданному адресу и проверяет её валидность. Если всё в порядке - передаёт управление приложению.

Как в этом случае применить твой способ?

Beewek ★★
() автор топика
Ответ на: комментарий от Beewek

Он считывает структуру AppInfo по заданному адресу и проверяет её валидность.

по этому адресу лежит байтовый блоб ap_inf_bs, он же пакованная структура, он же и проверяется за вычетом последних 4 байт crc, ф-ей calcCrc32, после чего блоб распаковывается конструктуром в обычную структуру AppInfo

zurg
()
Ответ на: комментарий от Beewek

да это не то чтобы магия, довольно известная сишная идиома - X макросы, я сам-то вообще на раст сбежал, что б такого не видеть и не писать. Вообще ещё вот такое есть https://nedbatchelder.com/code/cog генератор кода на питоне

zurg
()
Ответ на: комментарий от zurg

Я написал «магия», чтобы избежать более крепких выражений, типа «кошмар»:) X-макросы я знаю, это тот же самый кошмар, да ещё и в квадрате. В общем, я очень рад, что смог без этого обойтись.

Beewek ★★
() автор топика

Почитал тему. Сложно как то всё. У меня это делает скрипт сборки в рамках CMake, формирует хедер, а в нем что угодно можно насчитать чем угодно, хоть питоном, и получится прекрасный хедер на стадии compile time…

То есть код хедера формируется сторонним скриптом, и поля типа CRC и прочие подобные пишутся текстом в файл

I-Love-Microsoft ★★★★★
()
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Почитал тему. Сложно как то всё.

А ты точно дочитал до решения? Там всё очень просто, кратко и прямолинейно. Единственный трюк - вызов __builtin_bit_cast() в процессе вычисления crc. Но и этот трюк временный, до того, как я смогу использовать c++20.

CMake, формирует хедер, а в нем что угодно можно насчитать чем угодно, хоть питоном

Если у тебя в процессе сборки участвует питон, то конечно можно и им. Мне же не хотелось добавлять питон, srec или любую другую утилиту, которая не входит в набор компилятора. Потому что потом всё это придётся как-то добавлять на сборочный компьютер заказчика, а там может быть винда. Короче, я не говорю, что решение со сторонней утилитой плохое, но мне лучше без.

Beewek ★★
() автор топика
Ответ на: комментарий от vromanov

К сожалению, arm-none-eabi-ld не умеет. В целом, думаю, без утилиты я не обйдусь, поскольку прошивку желательно зашифровать и подписать. Заодно добавлю crc в конец образа, чтобы проверить его цельность. Но crc заголовка в начале прошивки тоже будет полезен, он позволит быстро определить наличие/отсутствие прошивки и размер образа.

Beewek ★★
() автор топика
Ответ на: комментарий от Beewek

В начале хватит сигнатуры и размера просто. Подписи типа CMAC и ECDSA в конце. Ну и еще вначале всяких полей типа версии, даты сборки, списка устройств для которых прошивка годится итд. Кстати, содержимое прошивки (*.elf) можно править при помощи GDB. Т.е. открыть, записать значение в статическую переменную и сохранить. Типа такого батника

"%GDB%" --write %BASE_NAME%.tmp_elf --batch ^
-ex "set variable flash_info.flash_crc=0x%CRC%" ^
-ex "set variable flash_info.compile_year=%BUILD_YEAR%." ^
-ex "set variable flash_info.compile_month=%BUILD_MONTH%." ^
-ex "set variable flash_info.compile_day=%BUILD_DAY%." ^
-ex "set variable flash_info.compile_hour=%BUILD_HOUR%." ^
-ex "set variable flash_info.compile_min=%BUILD_MIN%." ^
-ex "set variable flash_info.compile_sec=%BUILD_SEC%."
vromanov ★★★
()
Ответ на: комментарий от Morin

Есть два проблемы:

  • Случайно повреждена прошивка или не записана до конца. В этом случае crc32 будет достаточно
  • Злодей модифицировал прошивку и залил ее. Тут надо делать проверку подписи. Но это еще и медленно. И ключи надо где-то секурно хранить. Не у всех на процах есть HSM. Можно поставить на плату секурный элемент и считать подпись на нем.
vromanov ★★★
()
Ответ на: комментарий от vromanov

Для защиты от модификации можно и просто шифровать. Расшифровал, проверил что-то типа сигнатуры, совпало - зашил. Не совпало - не зашил. AES256 много где есть аппаратный, да и софтовый вполне справляется.

Beewek ★★
() автор топика
Ответ на: комментарий от Beewek

Это другое :). Подписывать надо, чтобы

  • Никто не смог заменить прошивку на устройстве
  • Не смог считать прошивку и залить на другое подобное устройство

Ну и всякие вещи типа блокировки JTAG итд.

vromanov ★★★
()
Ответ на: комментарий от Morin

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

Beewek ★★
() автор топика
Ответ на: комментарий от Beewek

Ну большинство камней льются не только бутлоадером :) Ладно, не будем вдаваться в полемику.

Из параноидальных вариантов, что мы делали - шифрование бинарника, подпись бута, подпись апы, шифрование флэши камня сгенеренным и недоступным для чтения ключом (такое умеют совсем малое количество камней), фьюзами закрывается запись чтение флэша проводными интерфейсами, обновление только ОТА. на борту естественно два раздела для аппликухи.

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

Morin ★★★★★
()
Последнее исправление: Morin (всего исправлений: 1)
Ответ на: комментарий от pavel_l

С разными ограничениями. Например, у нас на проце память c ECC. И стандартно при чтении памяти с невосстановимой ошибкой проц ребутается. Заначит надо отключить такое поведение и только потом проверить контрольную сумму. Иначе при сбое записи у тебя проц будет в бесконечном цикле перезагрузок

vromanov ★★★
()