Подтвердить что ты не робот

Можно ли гарантировать, что код делает запись в памяти не оптимизирована в С++?

С++ компиляторам разрешено оптимизировать запись в память:

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

При работе с конфиденциальными данными типичный подход использует указатели volatile*, чтобы гарантировать, что запись в память испускается компилятором. Здесь реализована функция SecureZeroMemory() в библиотеке времени исполнения Visual С++ (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. Таким образом, согласно С++. Стандартное определение наблюдаемого поведения пишет в buffer не считается наблюдаемым поведением и выглядит так, будто его можно оптимизировать.

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

Поведение аналогично 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 lvalues ​​бывают.

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

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

Ответ 4

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

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

Ответ 5

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

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

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