Безопасная очистка памяти и перераспределение

После обсужденияВотЕсли вы хотите иметь безопасный класс для хранения конфиденциальной информации (например, паролей) в памяти, вам необходимо:

memset/clear the memory before freeing it reallocations must also follow the same rule - instead of using realloc, use malloc to create a new memory region, copy the old to the new, and then memset/clear the old memory before freeing it finally

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

Поскольку класс должен надежно очистить память перед уничтожением, я не должен быть в состоянии найти «LOLWUT». на coredump. Тем не менее, мне удалось найти их до сих пор, и я удивился, если моя реализация просто глючит. Однако я попытался сделать то же самое, используя библиотеку CryptoPP SecByteBlock:

#include <cryptopp/osrng.h>
#include <cryptopp/dh.h>
#include <cryptopp/sha.h>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <cryptopp/filters.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;

int main(){
   {
      CryptoPP::SecByteBlock moo;

      int i;
      for(i = 0; i < 234; i++){
         moo += (CryptoPP::SecByteBlock((byte*)"LOL", 3));
         moo += (CryptoPP::SecByteBlock((byte*)"WUT", 3));

         char buffer[33];
         sprintf(buffer, "%d", i);
         string thenumber (buffer);

         moo += (CryptoPP::SecByteBlock((byte*)thenumber.c_str(), thenumber.size()));
      }

      moo.CleanNew(0);

   }

   sleep(1);

   *((int*)NULL) = 1;

   return 0;
}

А затем скомпилировать, используя:

g++ clearer.cpp -lcryptopp -O0

А затем включите дамп ядра

ulimit -c 99999999

Но затем, включив дамп ядра и запустив его

./a.out ; grep LOLWUT core ; echo hello

дает следующий вывод

Segmentation fault (core dumped)
Binary file core matches
hello

Чем это вызвано? Весь регион памяти для приложения перераспределяется из-за перераспределения, вызванного добавлением SecByteBlock?

Также,Это документация SecByteBlock.

editПосле проверки дампа ядра с помощью vim я получил это: http://imgur.com/owkaw

edit2: обновленный код, чтобы его было легче компилировать, и инструкции по компиляции

final edit3Похоже, что memcpy является виновником. Смотри Rasmus & apos;mymemcpy Реализация на его ответ ниже.

 kamziro21 мая 2012 г., 13:10
@SteveJessop Хм, наверное, поэтому в Windows есть SecureZeroMemory или что-то в этом роде. Интересно, что будет эквивалент Linux / Posix .. если таковой имеется.
 Bo Persson21 мая 2012 г., 17:14
Достаточно агрессивный оптимизатор может удалитьmemset к блокам, которые никогда не читаются снова.SecureZeroMemory есть причина!
 kamziro21 мая 2012 г., 17:36
Мое королевство для posix / linux / iOS версии securezeromemory тогда!
 Steve Jessop21 мая 2012 г., 13:02
Я не думаю, что это причина того, что вы видите, но вы знаете, что звонитеmemset в какой-то памяти не препятствует тому, чтобы в каком-то файле подкачки все еще находилась его копия? И вообще, результатmemset не нужно проникать во все слои кеша, но я упоминаю файл подкачки, потому что он является наиболее постоянным и, следовательно, самым явно опасным местом для конфиденциальных данных.
 Steve Jessop21 мая 2012 г., 19:55
@kamziro: Я уверен, что Linux и iOS всегда будут очищать вашу память перед тем, как это увидит другой процесс. Если нет, то это потому, что вы что-то отключили при компиляции ядра :-) В конечном итоге вы боретесь за время, в течение которого злоумышленник может видеть содержимое памяти, провоцируя и читая ядро. сброс, прямая проверка ОЗУ и т. д. Окно либо существует, либо не зависит от того, что вы делаете, поскольку обычно злоумышленник не может делать такие вещиafter Вы очищаете это, но не раньше.

Ответы на вопрос(5)

что он отображается в coredump, пароль на самом деле не находится в памяти больше после очистки буферов. Проблема в том, чтоmemcpyв достаточно длинная строка пропускает пароль в регистры SSE, иthose это то, что появляется в coredump.

Когдаsize аргументmemcpy больше определенного Порог & # x2014;80 байтов на маке& # x2014; затем инструкции SSE используются для копирование памяти. Эти инструкции быстрее, потому что они могут копировать 16 байты за раз параллельно вместо того, чтобы идти символ за символом, побайтовое или пословное. Вот ключевая часть исходного кода из Libc на Mac:

LAlignedLoop:               // loop over 64-byte chunks
    movdqa  (%rsi,%rcx),%xmm0
    movdqa  16(%rsi,%rcx),%xmm1
    movdqa  32(%rsi,%rcx),%xmm2
    movdqa  48(%rsi,%rcx),%xmm3

    movdqa  %xmm0,(%rdi,%rcx)
    movdqa  %xmm1,16(%rdi,%rcx)
    movdqa  %xmm2,32(%rdi,%rcx)
    movdqa  %xmm3,48(%rdi,%rcx)

    addq    $64,%rcx
    jnz     LAlignedLoop

    jmp     LShort                  // copy remaining 0..63 bytes and done

%rcx регистр индекса цикла,%rsi этоsнаш адресный регистр, а также%rdi этоdадресный регистр. Каждый бег по кругу, 64 байта копируются из исходного буфера в 4 16-байтовых регистра SSE xmm{0,1,2,3}; затем значения в этих регистрах копируются в целевой буфер.

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

Однако & # x2014;the SSE registers are not cleared after use! Это означает, что 64 байта скопированного буфера все еще присутствует вxmm{0,1,2,3} регистры.

Вот модификация программы Rasmus, которая показывает это:

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <emmintrin.h>

inline void SecureWipeBuffer(char* buf, size_t n){
  volatile char* p = buf;
  asm volatile("rep stosb" : "+c"(n), "+D"(p) : "a"(0) : "memory");
}

int main(){
  const size_t size1 = 200;
  const size_t size2 = 400;

  char* b = new char[size1];
  for(int j=0;j<size1-10;j+=10){
    memcpy(b+j, "LOL", 3);
    memcpy(b+j+3, "WUT", 3);
    sprintf((char*) (b+j+6), "%d", j);
  }
  char* nb = new char[size2];
  memcpy(nb, b, size1);
  SecureWipeBuffer(b,size1);
  SecureWipeBuffer(nb,size2);

  /* Password is now in SSE registers used by memcpy() */
  union {
    __m128i a[4];
    char c;
  };
  asm ("MOVDQA %%xmm0, %0": "=x"(a[0]));
  asm ("MOVDQA %%xmm1, %0": "=x"(a[1]));
  asm ("MOVDQA %%xmm2, %0": "=x"(a[2]));
  asm ("MOVDQA %%xmm3, %0": "=x"(a[3]));
  for (int i = 0; i < 64; i++) {
      char p = *(&c + i);
      if (isprint(p)) {
        putchar(p);
      } else {
          printf("\\%x", p);
      }
  }
  putchar('\n');

  return 0;
}

На моем Mac это печатает:

0\0LOLWUT130\0LOLWUT140\0LOLWUT150\0LOLWUT160\0LOLWUT170\0LOLWUT180\0\0\0

Теперь, изучая дамп ядра, пароль появляется только один раз, и как это точно0\0LOLWUT130\0...180\0\0\0 строка. Дамп ядра должен содержит копию всех регистров, поэтому эта строка находится там, это значенияxmm{0,1,2,4} регистры.

Такthe password isn’t actually in RAM anymore after calling SecureWipeBuffer, it only appears to be потому что это на самом деле в некоторых регистры, которые появляются только в coredump. Если вы беспокоитесь о memcpy иметь уязвимость, которая может быть использована при замораживании ОЗУ, не беспокойся больше. Если наличие копии пароля в реестрах беспокоит вас, использовать модифицированныйmemcpy который не использует регистры SSE2 или очищает их когда это будет сделано. И если вы действительно параноики по этому поводу, продолжайте тестировать свой Coredumps, чтобы убедиться, что компилятор не оптимизирует ваш код очистки пароля.

Решение Вопроса

которая воспроизводит проблему более непосредственно:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

inline void SecureWipeBuffer(char* buf, size_t n){
  volatile char* p = buf;
  asm volatile("rep stosb" : "+c"(n), "+D"(p) : "a"(0) : "memory");
}

void mymemcpy(char* b, const char* a, size_t n){
  char* s1 = b;
  const char* s2= a;
  for(; 0<n; --n) *s1++ = *s2++;
}

int main(){
  const size_t size1 = 200;
  const size_t size2 = 400;

  char* b = new char[size1];
  for(int j=0;j<size1-10;j+=10){
    memcpy(b+j, "LOL", 3);
    memcpy(b+j+3, "WUT", 3);
    sprintf((char*) (b+j+6), "%d", j);
  }
  char* nb = new char[size2];
  memcpy(nb, b, size1);
  //mymemcpy(nb, b, size1);
  SecureWipeBuffer(b,size1);
  SecureWipeBuffer(nb,size2);

  *((int*)NULL) = 1;

  return 0;    
}

Если вы заменитеmemcpy сmymemcpy или используйте меньшие размеры, проблема исчезнет, поэтому я думаю, что встроенный memcpy делает что-то, что оставляет часть скопированных данных в памяти.

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

 22 мая 2012 г., 10:56
@kamziro: я создалa ticket в Crypto ++ для отслеживания ошибок.
 kamziro22 мая 2012 г., 10:45
На самом деле, ваш mymemcpy, похоже, решил проблему. Кажется, что стандарт действительно был тем, что вызвало рассеяние памяти в дикую природу!
 22 мая 2012 г., 10:51
@kamziro. Но как ни странно, использование -fno-builtin-memcpy не работает.
 kamziro22 мая 2012 г., 10:07
Проклятия, это неудачно. Я полагаю, что это исключает маркетинг моего приложения с помощью функции «Защищено от криминалистической экспертизы ОЗУ!»

ecByteBlock.

Этот другой SO вопрос достаточно хорош для объяснения этого: Строковый литерал в c ++ создан в статической памяти?

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

 kamziro22 мая 2012 г., 04:49
Кроме того, это скриншот vim соответствия дампов ядра:imgur.com/owkaw
 kamziro22 мая 2012 г., 04:48
Матч дампа памяти ядра выглядит примерно так: & Quot; @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ 0LOLWUT231LOLWUTOLWUT229LOLWUT23216LOLWUT217LOLWUT218LOLWUT219LOLWUT220LOLWUT221LOLWUT222LOLWUT223LOLWUT224LOLWUT225LOLWUT226LOL ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ @ ^ & Quot; (из vim), так что они определенно из второго блока. Установка цикла в ноль элементов не дает совпадений. Установка в ноль также выполняется с использованием пользовательского ввода, так что он определенно не "оптимизирован компилятором". или

encrypt данные в памяти. Таким образом, данные всегда защищены, независимо от того, находятся ли они в памяти или нет. Недостатком, конечно же, являются издержки с точки зрения шифрования / дешифрования данных при каждом обращении к ним.

 29 янв. 2013 г., 16:50
Я предлагаю вам только зашифровать / расшифровать тот конкретный раздел, который вы сейчас читаете / пишете.
 29 янв. 2013 г., 16:44
Компилятор "умный" достаточно, чтобы оптимизироватьmemset на память вы собираетесьfree, может быть в состоянии удалить последнее шифрование, а также. Или следующая версия компилятора может.

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

[Реализацияreallocate в Crypto ++ используетmemcpy_s при изменении размера памяти, поэтому вы сможете найти некоторое количествоLOLWUT Строки в памяти. Кроме того, тот факт, что многие разныеLOLWUT строки, перекрывающиеся в этом дампе, предполагают, что это временный буфер, который используется повторно.]

Пользовательская версияmemcpy это просто простой цикл, не требующий временного хранения за счетчиками, так что это, безусловно, будет более безопасным, чем какmemcpy_s реализовано.

 10 авг. 2018 г., 20:28
+1. memset_s - это единственный способ соответствовать спецификации, чтобы безопасно очищать буферы. Это также подразумевает, что до C ++ 11 не было способа надежно очистить буферы (т.е. в соответствии со спецификацией).

Ваш ответ на вопрос