Очистить переменную в стеке

Фрагмент кода:

int secret_foo(void)
{
  int key = get_secret();
  /* use the key to do highly privileged  stuff */
  ....

  /* Need to clear the value of key on the stack before exit */
  key = 0;      
  /* Any half decent compiler would probably optimize out the statement above */
  /* How can I convince it not to do that? */

  return result;
}

Мне нужно очистить значение переменной key из стека перед returning (как показано в коде).

Если вам интересно, это было реальное требование клиента (встроенный домен).


person aiao    schedule 26.05.2016    source источник
comment
Не могли бы вы попробовать return result + (key = 0);?   -  person Weather Vane    schedule 27.05.2016
comment
@WeatherVane Compalir может интерпретировать это как return result;   -  person AlexD    schedule 27.05.2016
comment
@aiao тебе нужна дополнительная помощь? Что-то осталось неясным по этой теме?   -  person fukanchik    schedule 27.05.2016
comment
@fukanchik да, я все еще не уверен в правильности ответа AlexD. stackoverflow.com/questions/37489899/   -  person aiao    schedule 27.05.2016


Ответы (4)


Вы можете использовать volatile (выделено мной):

Каждый доступ (как для чтения, так и для записи), выполненный через выражение lvalue типа volatile-qualified, считается наблюдаемым побочным эффектом с целью оптимизации и оценивается строго в соответствии с правилами абстрактной машины (то есть все операции записи завершаются за некоторое время до следующей точки последовательности). Это означает, что в рамках одного потока выполнения изменчивый доступ не может быть оптимизирован или переупорядочен по отношению к другому видимому побочному эффекту, который отделен точкой последовательности от изменчивого доступа.

 volatile int key = get_secret();
person AlexD    schedule 26.05.2016
comment
Насколько я понимаю, volatile имеет дело только с хранилищем памяти, а не с ее использованием. Измененное значение ключа не используется, поэтому компилятор полностью удалит его, независимо от того, является ли оно изменчивым или нет. - person aiao; 27.05.2016
comment
@aiao: доступ к volatile нельзя оптимизировать. - person AlexD; 27.05.2016
comment
Не мой ДВ. Но разве вы не имеете в виду register int key = get_secret();? - person Weather Vane; 27.05.2016
comment
@aiao: Неправда, потому что одно из возможных значений volatile заключается в том, что это на самом деле драйвер устройства с отображением памяти. (Давным-давно я работал с набором источников света и переключателей, которые управлялись путем записи определенных значений в определенные места в памяти.) - person abelenky; 27.05.2016
comment
@WeatherVane Нет, я имел в виду volatile. (register все равно можно игнорировать, а если и нет, то значение может остаться, что тоже нехорошо.) - person AlexD; 27.05.2016
comment
@abelenky Пожалуйста, ознакомьтесь с этим ответом. stackoverflow.com/questions/4933314/unused-volatile-variable - person aiao; 27.05.2016
comment
@aiao Насколько я понимаю, они говорят о неиспользуемых переменных. - person AlexD; 27.05.2016
comment
такая же разница! Переменная оптимизируется, поскольку ее значение не используется в связанном вопросе. Состояние будет оптимизировано, поскольку его результат не будет использоваться. Какой-то компилятор выдаст предупреждение о том, что переменная установлена, но не используется, что заставляет меня поверить, что они собираются провести какую-то оптимизацию. - person aiao; 27.05.2016
comment
@aiao Если вы пишете в переменную, эта переменная используется, даже если вы не обращаетесь к переменной позже. Если переменная volatile, чтение/запись не может быть оптимизировано. - person AlexD; 27.05.2016
comment
@aiao Стандарт C говорит, что доступ к изменчивым переменным не должен быть оптимизирован. - person M.M; 27.05.2016
comment
@M.M securecoding.cert.org /confluence/display/c/ В абстрактной машине все выражения оцениваются в соответствии с семантикой. Фактическая реализация не должна оценивать часть выражения, если она может сделать вывод, что его значение не используется и что не создаются необходимые побочные эффекты (включая любые, вызванные вызовом функции или доступом к изменчивому объекту). Я также смог найти это обратно в ANSI также: port70.net/~nsz/c/c89/c89 -draft.html - person aiao; 27.05.2016
comment
@aiao Итак, они различают абстрактную машину и реальную реализацию. А затем: Доступ к volatile-объектам оценивается строго по правилам абстрактной машины - person AlexD; 27.05.2016
comment
@aiao фраза в скобках поясняет, что доступ к изменчивому объекту является необходимым побочным эффектом, и поэтому его необходимо оценивать. - person M.M; 27.05.2016
comment
volatile применяется только к оптимизации компилятора. Однако соседний ЦП не сразу увидит новое значение, поэтому возможны атаки по времени между кэшами ЦП. - person fukanchik; 28.05.2016

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

Используйте memset_s (начиная с C11): http://en.cppreference.com/w/c/string/byte/memset

memset может быть оптимизирован (согласно правилам «как если бы»), если к объекту, измененному этой функцией, не будет обращений снова до конца его жизни. По этой причине эту функцию нельзя использовать для очистки памяти (например, для заполнения массива, в котором хранится пароль, нулями). Эта оптимизация запрещена для memset_s: она гарантированно выполняет запись в память.

int secret_foo(void)
{
  int key = get_secret();
  /* use the key to do highly privileged  stuff */
  ....

  memset_s(&key, sizeof(int), 0, sizeof(int));
  return result;
}

Вы можете найти другие решения для различных платформ/стандартов C здесь: https://www.securecoding.cert.org/confluence/display/c/MSC06-C.+Beware+of+compiler+optimizations

Дополнение: ознакомьтесь с этой статьей Недостаточно обнуления буфера, что указывает на другие проблемы (помимо обнуления фактического буфера):

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

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

person fukanchik    schedule 26.05.2016
comment
Это выглядит красиво. Я предполагаю, что, поскольку это было добавлено в C11, эквивалента ANSI-C (C89/C90) не существует. Не могли бы вы подтвердить это? - person aiao; 27.05.2016
comment
@aiao Я не могу это подтвердить. Пожалуйста, посмотрите ссылку на безопасное кодирование, которую я добавил в ответ. - person fukanchik; 27.05.2016
comment
@aiao, если быть точным, C11 - это ANSI C, это единственный стандарт языка C, поддерживаемый ANSI сегодня (просто придирка, поскольку вы указали, что имели в виду C89/C90) - person Cubbi; 29.05.2016

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

int secret_foo(void)
{
  int *key = malloc(sizeof(int));
  *key = get_secret();
  memset(key, 0, sizeof(int));
  // other magical things...
  return result;
}
person Fire Crow    schedule 26.05.2016
comment
Я думаю, что вы все еще можете столкнуться с проблемой, когда компилятор видит, что окончательная запись в память никогда не читается, и поэтому может ее оптимизировать. Переход из стека в кучу не решает проблему. - person abelenky; 27.05.2016
comment
ах, а как насчет memset ‹отредактировано выше›, компилятор также оптимизирует memset? - person Fire Crow; 27.05.2016
comment
Тем не менее, нет никакой гарантии из-за правила как-если. - person AlexD; 27.05.2016

Одно из решений — отключить оптимизацию компилятора для той части кода, которую вы не хотите оптимизировать:

int secret_foo(void) {
     int key = get_secret();
     #pragma GCC push_options
     #pragma GCC optimize ("O0")

         key = 0;

     #pragma GCC pop_options
     return result;
}
person Fantastic Mr Fox    schedule 26.05.2016