воскресенье, 21 июня 2009 г.

Опасайтесь не нуль-терминированных строк в реестре

Это перевод Beware of non-null-terminated registry strings. Автор: Реймонд Чен.

Хотя значение хранится в реестре как REG_SZ, это не означает, что данные действительно заканчиваются корректным терминатором. На самом нижнем уровне реестр - просто иерархически организованная база данных имя/значение.

И вы можете соврать, и это может сойти вам с рук.

Множество людей врут насчёт своих данных в реестре. Вы можете найти множество вещей, которые должны быть REG_DWORD, хранимых как 4-х байтовый REG_BINARY (частично это наследие из реестра Windows 95, который не поддерживал REG_DWORD).

Одна из наиболее коварной лжи касается длины строки, записываемой в реестр. Рассмотрим такую программу:
program Project1;

{$APPTYPE CONSOLE}

uses
  Windows;

var
  cb, dwRc: DWORD;
  sz: array[0..1] of WideChar;
begin
  RegSetValueExW(HKEY_CURRENT_USER, 'Scratch', 0, REG_SZ, PWideChar('12'), 2);

  cb := 0;
  RegQueryValueExW(HKEY_CURRENT_USER, 'Scratch', nil, nil, nil, @cb);
  Writeln('Size is ', cb, ' bytes');

  sz[0] := #$FFFF;
  sz[1] := #$FFFF;
  cb := SizeOf(sz);
  dwRc := RegQueryValueExW(HKEY_CURRENT_USER, 'Scratch',
                           nil, nil, @sz, @cb);
  Writeln('RegQueryValueExW requesting ', SizeOf(sz), ' bytes => ', dwRc);
  Writeln(cb, ' bytes required');
  if dwRc = ERROR_SUCCESS then
  begin
    Writeln('sz[0] = ', Word(sz[0]));
    Writeln('sz[1] = ', Word(sz[1]));
  end;

  RegDeleteValueW(HKEY_CURRENT_USER, 'Scratch');
end.
Если вы запустите эту программу, вы получите следующее:
Size is 2 bytes
RegQueryValueExW requesting 4 bytes => 0
2 bytes required
sz[0] = 49
sz[1] = 65535
Что же произошло?

Для начала, заметим что вызов RegSetValueExW врёт о длине строки, утверждая что она длиной два байта, хотя фактически их шесть! (два WideChar-а плюс терминатор).

Реестр ответственно выполняет запись этой лжи и показывает её потом всем интересующимся.

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

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

И вот, значение было прочитано из реестра и только два байта были прочитаны. sz[0] содержит символ '1', а sz[1] остаётся не инициализированным.

Это имеет последствия в плане безопасности.

Если ваша программа подразумевает, что строки в реестре всегда хранятся нуль-терминированными, то вам могут устроить переполнение буфера (buffer overflow), если вам подсунут строку, не заканчивающуюся нуль-терминатором.

Примечание: я не собираюсь ввязываться в спор, должно ли быть возможным создать такую ситуацию вообще. Я не проектировал реестр. Спор о прошлом не изменит настоящего, а настоящее - это то, как есть сейчас, и вам лучше бы быть готовым к нему.

Упражнение: изменить последний параметр RegSetValueExW на 3 и запустить программу снова. Объяснить результаты и обсудить их последствия.

Прим. пер.: упражнение от меня - объяснить, как и почему эта проблема решается в Delphi при использовании TRegistry.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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