Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid unaligned data access for grasshopper cipher. #376

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

shanechko
Copy link

Compiler can produce incorrect instructions for arch with strict memory
access like ARM. Root case is compiler assuming some data type is always
aligned properly.

Pointer cast from unaligned pointer to this type `grasshopper_w128_t`
can trigger segmentation fault.

@@ -183,7 +192,7 @@ static EVP_PKEY *load_private_key(int key_nid, int param_nid, const char *pk,
BN_free(y);
if (EC_POINT_cmp(group, pkey, xy, bc) == 0)
printf("Public key %08x matches private key %08x\n",
*(int *)pub, *(int *)pk);
charbuf_to_uint(pub), charbuf_to_uint(pk));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем это изменение - как вы поняли, что там могут быть unaligned данные?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это исправление предупреждения компилятора.
Компилятор добавляет предупреждения при касте указателя с малого типа (char) в больший (int).

На архитектурах типа arm не каждый char* может быть конвертирован в int*.

Так как char* входящий параметр он не обязан быть выровнен по int.

@@ -667,29 +655,36 @@ static int gost_grasshopper_cipher_do_cfb(EVP_CIPHER_CTX *ctx, unsigned char *ou
(gost_grasshopper_cipher_ctx *) EVP_CIPHER_CTX_get_cipher_data(ctx);
const unsigned char *in_ptr = in;
unsigned char *out_ptr = out;
unsigned char *buf = EVP_CIPHER_CTX_buf_noconst(ctx);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему вы решили, что этот буфер unaligned?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

скорее всего потому, что он выделяется изнутри openssl. Коллегам наверняка нужно выравнивание по 16 байтам, а компилятор по умолчанию выравнивает по 8. Компилятор openssl при этом ничего не знает про требования engine к выравниванию.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Спасибо. Но, почему должно быть выравнивание по 16 байт?
Особенно в том примере где просто доступ к (int *)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему вы решили, что этот буфер unaligned?

Я исправил предупреждение компилятора. Компилятор рассматривает наихудший допустимый случай на основе типа данных.

@beldmit у компилятора (и языка С) есть ABI и который устанавливает обязательное выравнивание типов и структур. Проблема в том что когда метод возвращает указатель на char из поля структуры, информация о выравнивании теряется, и компилятор считает что это произвольный допустимый указатель на char.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Спасибо. Но, почему должно быть выравнивание по 16 байт? Особенно в том примере где просто доступ к (int *)?

  • Нет требования про 16 байт, это недопонимание.
  • Изменения в этом MR направлены на избавления от одного UB: Каста указателя меньшего типа к большему. Так делать нельзя, потому что это приводит к ошибкам на архитектурах с инструкциями требующими выровненного обращению к памяти.

@vt-alt
Copy link
Member

vt-alt commented Dec 16, 2021

Кстати, по поводу остальных мест - скорее всего надо просто убрать ALIGN(16), чтоб компилятор не думал, что там есть выравнивание.

@vt-alt
Copy link
Member

vt-alt commented Dec 16, 2021

Кстати, по поводу остальных мест - скорее всего надо просто убрать ALIGN(16), чтоб компилятор не думал, что там есть выравнивание.

В общем, моё мнение такое — я не одобряю этого PR, так как он испортит, а не улучшит код.

  1. Лучше вообще не использовать align лишний раз (как и cast'ы к большему размеру) чем заниматься таким с выравниванием по 16 байт (которое ниоткуда логически не следует, и это уже магия (в плохом смысле), которую нужно помнить, а значит будущий код не защищен от таких "ошибок" (так как они не рациональны) — с моей точки зрения, это просто случайные "фиксы"). Не нужно сообщать компилятору ложную информацию (про align(16)) и он не сгенерирует unaligned код.
  2. Лучше фиксить реальные баги — где есть реальный segmentation fault — (пример с charbuf_to_uint показывает, что тут это не так) — иначе, это просто нагромождение исправлений того что не сломано. Так же я рекомендую написать тесты, которые падают до фикса и работают после, доказывающие как наличие бага, так и эффективность фикса.
  3. Такое нагромождение кода невозможно разевьювить.

@vt-alt
Copy link
Member

vt-alt commented Dec 16, 2021

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

@shanechko
Copy link
Author

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

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

Посмотрите как реализованы другие блочные шифры.

@shanechko
Copy link
Author

Кстати, по поводу остальных мест - скорее всего надо просто убрать ALIGN(16), чтоб компилятор не думал, что там есть выравнивание.

Это так не работает. Выравнивание есть всегда, на основе типа данных и ABI. И компилятор ожидает этого выравнивания.

Например если функция получает int* компилятор уверен что указатель кратен четырём. Если это SSE тип то компилятор знает что он выровнен по 16 байтам, и использует инструкции для работы с выровненными данными.

ALIGN(16) добавлен потому что на x86 архитектуре SSE требует выровненных данных. На ARM архитектуре работа с int требует выровненных данных.

@shanechko
Copy link
Author

В общем, моё мнение такое — я не одобряю этого PR, так как он испортит, а не улучшит код.

Это не улучшение, а исправление реальных и возможных сбоев. Эти сбои никогда не воспроизведутся на x86 архитектуре.

1. Лучше вообще не использовать align лишний раз (как и cast'ы к большему размеру) чем заниматься таким с выравниванием по 16 байт (которое ниоткуда логически не следует, и это уже магия (в плохом смысле), которую нужно помнить, а значит будущий код не защищен от таких "ошибок" (так как они не рациональны) — с моей точки зрения, это просто случайные "фиксы"). Не нужно сообщать компилятору ложную информацию (про align(16)) и он не сгенерирует unaligned код.

Позвольте объяснить для чего используется ALIGN. Для переносимого кода. Если бы писалось для компьютеров с SSE то можно было бы просто использовать SSE тип данных. Но используются типы данных универсальные для всех архитектур, однако, чтобы с ними можно было работать как с SSE их нужно ДОПОЛНИТЕЛЬНО выровнить, чтобы был безопасный cast. Для архитектур без SSE это НЕНУЖНОЕ выравнивание, но оно безвредное.

Абсолютно согласен что касты к большему типу не нужны. Я расскажу как делал этот MR
Добавил флаг компилятору показывающий все неправильные касты из меньшего типа к большему, и исправил предупреждения.

Компилятор всегда генерирует aligned код. Потому что так работает процессор.

2. Лучше фиксить реальные баги — где есть реальный segmentation fault  — (пример с `charbuf_to_uint` показывает, что тут это не так) — иначе, это просто нагромождение исправлений того что не сломано. Так же я рекомендую написать тесты, которые падают до фикса и работают после, доказывающие как наличие бага, так и эффективность фикса.

charbuf_to_uint это исправление недопустимого каста.
про тесты согласен, только проблема в том что для их запуска нужен arm.

3. Такое нагромождение кода невозможно разевьювить.

В каждом коммите менятся только одна функция, и не более 20 изменений строк. Подскажите какие требования к изменениям?

@shanechko shanechko requested review from beldmit and vt-alt December 17, 2021 14:11
@beldmit
Copy link
Contributor

beldmit commented Dec 17, 2021

Посмотрю на той неделе, сегодня и на выходных недееспособен, извините.

@vt-alt
Copy link
Member

vt-alt commented Dec 17, 2021

@shanechko, спасибо аз ответ. Извиняюсь, что не внимательно посмотрел ваш PR.

У меня есть доступ к разным архитектурам, так что если вы напишете тесты, которые воспроизводят эти проблемы мы, вероятно, сможем их запустить. Даже если это будет не часто — это возможность тестирования на будущее.

Ранее, мы специально делали тесты такими (есть даже отключение MIPS_FIXADE для mipsel), и гоняли их да других архитектурах, чтоб всплывали проблемы с alignment.

ps. В теории, есть возможность вернуть CI на ppc64le, s390x и arm64 на Travis-CI (там это всё ещё доступно для Open Source repositories).

elseif(CMAKE_C_COMPILER_ID MATCHES "GNU")
set(CMAKE_C_FLAGS_RELEASE -O2)
set(CMAKE_C_FLAGS_DEBUG "-O0 -ggdb")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -ggdb")
add_compile_options(-Werror -Wall -Wno-unused-parameter -Wno-unused-function -Wno-missing-braces -Wno-error=unknown-pragmas -Wno-error=pragmas -Wno-deprecated-declarations)
add_compile_options(-Werror -Wall -Wcast-align -Wno-unused-parameter -Wno-unused-function -Wno-missing-braces -Wno-error=unknown-pragmas -Wno-error=pragmas -Wno-deprecated-declarations)
Copy link
Member

@vt-alt vt-alt Dec 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если мы хотим это фиксить для всех архитектур, то, видимо, надо -Wcast-align=strict. Чтоб и на x86 вылазили эти варнинги.

@vt-alt
Copy link
Member

vt-alt commented Dec 17, 2021

7499536 2021-12-16 Fix align-cast for test_derive.c (Mikhail Labiuk)

[Теперь я понял что] тут всё ОК. -Wcast-align=strict вывел этот варнинг, который фиксится. (Как я понял, в этом PR фиксятся в основном/только варнинги, а не реальные segmentation faults на которые наткнулись. Это ОК. Но можно было это написать в commit message, Так как я этого сразу не понял.) В этом коммите поменяется вывод key "id" на разных архитектурах (в зависимости от endianness), но тут это и не важно, так как главное там показать в дебаге, что ключи одинаковые. Спасибо.

@vt-alt
Copy link
Member

vt-alt commented Dec 17, 2021

Как я понял, в этом PR фиксятся в основном/только варнинги, а не реальные segmentation faults на которые наткнулись.

В таком случае тесты (для этих багов) не нужны! Я изначально не понял, что это фиксы варнингов и говорит про тесты и вообще не правильно понимал что тут фиксится.

@vt-alt
Copy link
Member

vt-alt commented Dec 19, 2021

На ARM архитектуре работа с int требует выровненных данных.

Ранее код был протестирован на sparc64 м mipsel (c MIPS_FIXADE 0) где доступ к int реально требует выровненных данных. А так же на armv7hf. Те segmentation faults, которые возникали были исправлены.

Далее, если вопрос сводится к фиксу варнинга -Wcast-align. Этот варнинг не входит ни в -Wall ни даже в -Wextra. Если включить -Wcast-align то при сборке openssl этот варнинг возникает 175 раз. Почему не внести сначала в openssl исправление реальных и возможных сбоев?

ps. И, на сколько я понимаю, на ARM ядро линукс умет исправлять unaligned access (как у mipsel).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants