Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
GBP
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
Перезаписывать память - зачем?

Перезаписывать память - зачем?

17 Янв 2012

Мы решили опубликовать эту статью в базе знаний, чтобы продемонстрировать программистам, как легко приватные данные могут выйти за рамки программы, работающей с ними. В анализаторе PVS-Studio есть диагностика V597, позволяющая выявлять вызовы функции memset(), которые не очищают память. Но опасность выглядит неубедительной и не правдоподобной. Эта статья хорошо показывает, что опасность реальна и её нельзя игнорировать.

Статья написана сотрудником компании ABBYY, впервые опубликована: "Блог компании ABBYY. Перезаписывать память – зачем?", публикуется здесь с разрешения правообладателя.

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

Остается один вопрос - зачем это? Можно найти пространные рассуждения о риске записи памяти программы в файл подкачки, файл hibernate или аварийный дамп, где его может найти злоумышленник. Это похоже на паранойю - далеко не всякий злоумышленник имеет возможность наложить руку на эти файлы.На самом деле, возможностей получить доступ к данным, которые программа забыла перезаписать, гораздо больше - иногда не нужно даже иметь доступа к машине. Дальше мы рассмотрим пример, и каждый сам решит, насколько оправдана паранойя.Все примеры будут в псевдокоде, подозрительно похожем на C++. Будет много букв и не очень чистого кода, потом станет понятно, что в более чистом коде ситуация ненамного лучше.Итак. В далекой-далекой функции мы получаем ключ шифрования, пароль или номер кредитной карты (далее - просто секрет), используем его и не перезаписываем:

{
  const int secretLength = 1024;
  WCHAR secret[secretLength] = {};
  obtainSecret( secret, secretLength );
  processWithSecret( what, secret, secretLength );
}

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

//MAX_FILE_PATH == 1024
error_status_t rpcRetrieveFile(
    [in] const WCHAR fileName[MAX_FILE_PATH],
    [out] BYTE_PIPE filePipe );

здесь второй параметр имеет специальный тип, дающий возможность передавать потоки данных произвольной длины. Первый параметр - массив символов под имя файла.Это описание компилируется компилятором MIDL, получается заголовочный файл (.h) с функцией

error_status_t rpcRetrieveFile (
  handle_t IDL_handle, 
  const WCHAR fileName[1024], 
  BYTE_PIPE filePipe);

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

void retrieveFile( handle_t binding )
{
  WCHAR remoteFileName[MAX_FILE_PATH];
  retrieveFileName( remoteFileName, MAX_FILE_PATH );
  CBytePipeImplementation pipe;
  rpcRetrieveFile( binding, remoteFileName, pipe );           
}

Все отлично - retrieveFileName() получает строку длиной не более MAX_FILE_PATH−1, завершенную нулевым символом (нулевой символ не забыли), вызываемая сторона получает строку и работает с ней - получает полный путь к файлу, открывает его и передает данные из него.Все полны оптимизма, с этим кодом делается несколько выпусков продукта, но слона пока никто не заметил. Слон вот. С точки зрения C++, параметр функции

const WCHAR fileName[1024]

это не массив, а указатель на первый элемент массива. Функция rpcRetrieveFile() - всего лишь прослойка, которая сгенерирована тем же MIDL. Она упаковывает все свои параметры и вызывает всегда одну и ту же функцию WinAPI NdrClientCall2(), смысл которой "Windows, выполни, пожалуйста, RPC-вызов вооот с этими параметрами", и передает параметры списком функции NdrClientCall2(). Одним из первых параметров идет строка форматирования, сгенерированная MIDL по описанию в IDL. Очень похоже на старый добрый printf().NdrClientCall2() внимательно смотрит на полученную строку форматирования и упаковывает параметры для передачи другой стороне (это называется marshalling). Рядом с каждым параметром указан его тип - каждый параметр упаковывается в зависимости от типа. В нашем случае для параметра fileName указан адрес первого элемента массива и в качестве типа - "массив из 1024 элементов типа WCHAR".Теперь в коде встречаем подряд два вызова:

processWithSecret( whatever );
retrieveFile( binding );

Функция processWithSecret() отъедает 2 килобайта под хранение секрета на стеке, а при завершении забывает о них. Дальше вызывается функция retrieveFile(), она извлекает имя файла длиной 18 символов (18 символов + завершающий нулевой - всего 19, т.е. 38 байт). Имя файла снова хранится на стеке и скорее всего, это будет точно та же область памяти, что была использована под секрет в первой функции.Дальше происходит удаленный вызов и функция упаковки добросовестно упаковывает весь массив (не 38 байт, а 2048) в пакет и этот пакет затем передается по сети.КРАЙНЕ НЕОЖИДАННОСекрет передается по сети. Программа даже не планировала когда-либо передавать секрет по сети, но он передается. Такой дефект может быть гораздо удобнее в "использовании", чем даже просмотр файла подкачки. Кто теперь параноик?Пример выше выглядит довольно сложным. Вот похожий по смыслу код, который можно опробовать на codepad.org

const int bufferSize = 32;

void first()
{
  char buffer[bufferSize];
  memset( buffer, 'A', sizeof( buffer ) );
}

void second()
{
  char buffer[bufferSize];
  memset( buffer, 'B', bufferSize / 2 );
  printf( "%s", buffer );
}

int main()
{
  first();
  second();
}

В нем неопределенное поведение. На момент написания поста результат работы - строка из 16 символов 'B' и 16 символов 'A'.Сейчас самое время для размахивания вилами и факелами и гневных возгласов, что никто в своем уме не использует обычные массивы, что нужно использовать std::vector, std::string и класс УниверсальныйВсемогутер, которые "правильно" работают с памятью, и священных войн на не менее чем 9 тысяч комментариев.На самом деле, здесь это бы не помогло - функция упаковки в недрах RPC все равно читала бы больше данных, чем туда записал вызывающий код. В результате читались бы данные по ближайшим адресам или (в отдельных случаях) возникал бы сбой при неверном обращении к памяти. По этим ближайшим адресам снова могли бы оказаться данные, не подлежащие передаче по сети.Кто здесь виноват? Как обычно, виноват разработчик - он неверно понял, как функция rpcRetrieveFile() работает с полученными параметрами. В результате - неопределенное поведение, которое в данном случае приводит к неконтролируемой передаче данных по сети. Это исправляется либо изменением RPC-интерфейса и правкой кода на обеих сторонах, либо использованием массива достаточно большого размера и его полной перезаписью перед копированием в него параметра.В этой ситуации и помогла бы SecureZeroMemory() - если бы первая функция перед завершением перезаписывала секрет, то ошибка во второй хотя бы приводила к передаче перезаписанного массива. Так сложнее получить премию Дарвина.

Популярные статьи по теме
Бесплатный PVS-Studio для тех, кто развивает открытые проекты

Дата: 22 Дек 2018

Автор: Андрей Карпов

В канун празднования нового 2019 года команда PVS-Studio решила сделать приятный подарок всем контрибьюторам open-source проектов, хостящихся на GitHub, GitLab или Bitbucket. Им предоставляется возмо…
Любите статический анализ кода!

Дата: 16 Окт 2017

Автор: Андрей Карпов

Я в шоке от возможностей статического анализа кода, хотя сам участвую в разработке инструмента PVS-Studio. На днях я был искренне удивлён тому, что анализатор оказался умнее и внимательнее меня.
Главный вопрос программирования, рефакторинга и всего такого

Дата: 14 Апр 2016

Автор: Андрей Карпов

Вы угадали, ответ - "42". Здесь приводится 42 рекомендации по программированию, которые помогут избежать множества ошибок, сэкономить время и нервы. Автором рекомендаций выступает Андрей Карпов - тех…
PVS-Studio для Java

Дата: 17 Янв 2019

Автор: Андрей Карпов

В седьмой версии статического анализатора PVS-Studio мы добавили поддержку языка Java. Пришло время немного рассказать, как мы начинали делать поддержку языка Java, что у нас получилось и какие дальн…
Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей

Дата: 21 Ноя 2018

Автор: Андрей Карпов

Краткое описание технологий, используемых в инструменте PVS-Studio, которые позволяют эффективно обнаруживать большое количество паттернов ошибок и потенциальных уязвимостей. Статья описывает реализа…
Статический анализ как часть процесса разработки Unreal Engine

Дата: 27 Июн 2017

Автор: Андрей Карпов

Проект Unreal Engine развивается - добавляется новый код и изменятся уже написанный. Неизбежное следствие развития проекта - появление в коде новых ошибок, которые желательно выявлять как можно раньш…
Как PVS-Studio оказался внимательнее, чем три с половиной программиста

Дата: 22 Окт 2018

Автор: Андрей Карпов

PVS-Studio, как и другие статические анализаторы кода, часто выдаёт ложные срабатывания. Но не стоит спешить считать странные срабатывания ложными. Это короткая история о том, как PVS-Studio вновь ок…
PVS-Studio ROI

Дата: 30 Янв 2019

Автор: Андрей Карпов

Время от времени нам задают вопрос, какую пользу в денежном эквиваленте получит компания от использования анализатора PVS-Studio. Мы решили оформить ответ в виде статьи и привести таблицы, которые по…
Как и почему статические анализаторы борются с ложными срабатываниями

Дата: 20 Мар 2017

Автор: Андрей Карпов

В своей предыдущей статье я писал, что мне не нравится подход, при котором статические анализаторы кода оцениваются с помощью синтетических тестов. В статье приводился пример, воспринимаемый анализат…
Зло живёт в функциях сравнения

Дата: 19 Май 2017

Автор: Андрей Карпов

Возможно, читатели помнят мою статью под названием "Эффект последней строки". В ней идёт речь о замеченной мной закономерности: ошибка чаще всего допускается в последней строке однотипных блоков текс…

Комментарии (0)

Следующие комментарии

На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Этот сайт использует куки и другие технологии, чтобы предоставить вам более персонализированный опыт. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Если вы не хотите, чтобы ваши данные обрабатывались, пожалуйста, покиньте данный сайт. Подробнее →
Принять