Оптимизация — Можно ли гарантировать, что код, выполняющий запись в память, не оптимизирован в C ++?

Компиляторы C ++ могут оптимизировать записи в память:

 {
//all this code can be eliminated
char buffer[size];
std::fill_n( buffer, size, 0);
}

При работе с конфиденциальными данными типичный подход использует volatile* указатели, чтобы гарантировать, что записи памяти испускаются компилятором. Вот как SecureZeroMemory() реализована функция в библиотеке времени выполнения Visual C ++ (WinNT.h):

FORCEINLINE PVOID RtlSecureZeroMemory(
__in_bcount(cnt) PVOID ptr, __in SIZE_T cnt )
{
volatile char *vptr = (volatile char *)ptr;
#if defined(_M_AMD64)
__stosb((PBYTE )((DWORD64)vptr), 0, cnt);
#else
while (cnt) {
*vptr = 0;
vptr++;
cnt--;
}
#endif
return ptr;
}

Функция бросает переданный указатель на volatile* указатель, а затем пишет через последний. Однако, если я использую его в локальной переменной:

char buffer[size];
SecureZeroMemory( buffer, size );

сама переменная не volatile, Таким образом, согласно стандартному определению C ++ наблюдаемое поведение записывается в buffer не считается наблюдаемым поведением и похоже, что его можно оптимизировать.

Ниже приведено много комментариев о файлах страниц, кэшах и т. Д., Которые все действительны, но давайте просто проигнорируем их в этом вопросе. Вопрос только в том, оптимизирован ли код для записи в память или нет.

Можно ли гарантировать, что код, выполняющий запись в память, не оптимизирован в C ++? Это решение в SecureZeroMemory() соответствует стандарту C ++?

27

Решение

Нет портативного решения. Если он хочет, компилятор мог бы сделать копии данных, пока вы использовали их в нескольких местах в памяти, и любая нулевая функция могла бы обнулять только ту, которую она использовала в то время. Любое решение будет непереносимым.

7

Другие решения

С библиотечными функциями, такими как SecureZeroMemoryавторы библиотек, как правило, прилагают все усилия, чтобы компилятор не вставлял такие функции.
Это означает, что во фрагменте

char buffer[size];
SecureZeroMemory( buffer, size );

компилятор не знает что SecureZeroMemory делает с bufferоптимизатор не может доказать, что удаление фрагмента не влияет на наблюдаемое поведение программы.
Другими словами, разработчики библиотеки уже сделали все возможное, чтобы такой код не был оптимизирован.

4

volatile Ключевое слово может быть применено к указателю (или ссылке, в C ++), не требуя приведения, означая, что доступ через этот указатель не должен быть оптимизирован. Объявление переменной не имеет значения.

Поведение аналогично const:

char buffer[16];
char const *p = buffer;

buffer[0] = 'a';          // okay
p[0] = 'b';               // error

Это const указатель на буфер существует, никак не изменяет поведение переменной, только поведение модифицированного указателя. Если переменная объявлена constтогда запрещено генерироватьconst указатели на это:

char const buffer[16];
char *p = buffer;         // error

Так же,

char buffer[16];
char volatile *p = buffer;

buffer[0] = 'a';          // may be optimized out
p[0] = 'b';               // will be emitted

а также

char volatile buffer[16];
char *p = buffer;         // error

Компилятор может свободно удалять доступ черезvolatile lvalues а также вызовы функций, где он может доказать, что нет доступа к volatile Значения случаются.

RtlSecureZeroMemory Функция безопасна в использовании, потому что компилятор может видеть определение (включая volatile доступ внутри цикла или, в зависимости от платформы, оператор ассемблера, который непрозрачен для компилятора и, следовательно, предполагается, что он не оптимизируемый), или он должен предполагать, что функция будет выполнять volatile доступ.

Если вы хотите избежать зависимости от <WINNT.H> заголовочный файл, тогда аналогичная функция будет отлично работать с любым соответствующим компилятором.

2

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

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

1

Ни C, ни C ++ Standard не предъявляют никаких требований к тому, как реализации хранят вещи в физической памяти. Однако реализации могут свободно определять такие вещи, а качественные реализации, которые подходят для приложений, требующих определенных поведений физической памяти, будут указывать, что они будут последовательно вести себя подходящим образом.

Многие реализации обрабатывают как минимум два различных диалекта. При обработке своего диалекта «оптимизации отключены» они часто подробно описывают, сколько действий будет взаимодействовать с физической памятью. К сожалению, включение оптимизации обычно переключается на семантически более слабый диалект, который почти ничего не гарантирует о том, как какие-либо действия будут взаимодействовать с физической памятью. В то время как должна быть возможность обрабатывать много простых и простых оптимизаций, все еще обрабатывая вещи способом, который согласуется с диалектом «отключенных оптимизаций» в определенных легко идентифицируемых случаях, где это могло бы иметь значение, авторы компилятора не заинтересованы в обеспечение режимов, ориентированных на безопасные низко висящие фрукты.

Единственный надежный способ гарантировать, что физическая память обрабатывается определенным образом, — это использовать диалект, который обещает обращаться с физической памятью таким образом. Если вы сделаете это, получить необходимое лечение, как правило, будет легко. Если этого не сделать, ничто не гарантирует, что «творческая» реализация не сделает ничего неожиданного.

1