Наиболее эффективная замена для IsBadReadPtr? - PullRequest
7 голосов
/ 30 января 2009

У меня есть некоторый код Visual C ++, который получает указатель на буфер с данными, которые должны быть обработаны моим кодом и длиной этого буфера. Из-за ошибки, находящейся вне моего контроля, иногда этот указатель попадает в мой код неинициализированным или непригодным для чтения иным образом (т.е. он вызывает сбой при попытке доступа к данным в буфере.)

Итак, мне нужно проверить этот указатель, прежде чем я его использую. Я не хочу использовать IsBadReadPtr или IsBadWritePtr, потому что все согласны с тем, что они глючат. (Используйте их в качестве примера.) Они также не являются поточно-ориентированными - это, вероятно, не является проблемой в этом случае, хотя поточно-ориентированное решение было бы неплохо.

Я видел предложения по достижению этой цели с помощью VirtualQuery или просто с помощью memcpy внутри обработчика исключений. Однако код, в котором необходимо выполнить эту проверку, чувствителен ко времени, поэтому мне нужна максимально эффективная проверка, которая также эффективна на 100%. Любые идеи будут оценены.

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

Обратите внимание, что мне все равно, если указанные данные действительны. Мой код ищет конкретные шаблоны данных и будет игнорировать данные, если не найдет их. Я просто пытаюсь предотвратить сбой, возникающий при запуске memcpy для этих данных, и обработка исключения в точке, в которой выполняется попытка memcpy, потребует изменения дюжины мест в моем коде (но если бы у меня было что-то вроде IsBadReadPtr для вызова, я бы только нужно сменить код в одном месте).

Ответы [ 10 ]

10 голосов
/ 23 февраля 2016
bool IsBadReadPtr(void* p)
{
    MEMORY_BASIC_INFORMATION mbi = {0};
    if (::VirtualQuery(p, &mbi, sizeof(mbi)))
    {
        DWORD mask = (PAGE_READONLY|PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY);
        bool b = !(mbi.Protect & mask);
        // check the page is not a guard page
        if (mbi.Protect & (PAGE_GUARD|PAGE_NOACCESS)) b = true;

        return b;
    }
    return true;
}
9 голосов
/ 30 января 2009

было бы неплохо потоковое решение

Я предполагаю, что только IsBadWritePtr не является потокобезопасным.

просто делает memcpy внутри обработчика исключений

Это фактически то, что делает IsBadReadPtr ... и если вы сделали это в своем коде, то ваш код будет иметь ту же ошибку, что и реализация IsBadReadPtr: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx

- Edit: -

Единственная проблема с IsBadReadPtr, о которой я читал, заключается в том, что неправильный указатель может указывать (и поэтому вы можете случайно коснуться) страницы защиты стека. Возможно, вы могли бы избежать этой проблемы (и, следовательно, безопасно использовать IsBadReadPtr):

  • Знайте, какие потоки работают в вашем процессе
  • Знать, где стеки потоков, и насколько они велики
  • Пройдите вниз по каждой стопке, сознательно касаясь каждой страницы стопки хотя бы один раз, прежде чем вы начнете вызывать isBadReadPtr

Кроме того, некоторые из комментариев, связанных с указанным выше URL-адресом, также предлагают использовать VirtualQuery.

6 голосов
/ 30 января 2009

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

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

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

Какая это функция?

1 голос
/ 30 января 2009

Почему вы не можете вызвать API

AfxIsValidAddress ((p), sizeof (тип), FALSE));

0 голосов
/ 03 января 2018

Если вам нужно прибегнуть к проверке шаблонов в данных, вот несколько советов:

  • Если вы упомянули об использовании IsBadReadPtr, вы, вероятно, разрабатываете для Windows x86 или x64.

  • Вы можете проверить дальность проверки указателя. Указатели на объекты будут выровнены по словам. В 32-разрядных окнах указатели пространства пользователя находятся в диапазоне 0x00401000-0x7FFFFFFF, а для приложений с большим адресом - 0x00401000-0xBFFFFFFF. Верхние 2 ГБ / 1 ГБ зарезервированы для указателей пространства ядра.

  • Сам объект будет жить в памяти чтения / записи, которая не является исполняемой. Это может жить в куче, или это может быть глобальная переменная. Если это глобальная переменная, вы можете проверить, что она находится в правильном модуле.

  • Если у вашего объекта есть VTable, и вы не используете другие классы, сравните его указатель VTable с другим указателем VTable из известного хорошего объекта.

  • Диапазон проверки переменных, чтобы увидеть, если они являются допустимыми. Например, bools может быть только 1 или 0, поэтому, если вы видите значение со значением 242, это явно неправильно. Указатели также могут быть проверены по диапазону и проверены на выравнивание.

  • Если в нем содержатся объекты, проверьте их VTables и данные.

  • Если есть указатели на другие объекты, вы можете проверить, что объект находится в памяти, которая является Чтением / Записью и не является исполняемой, проверить VTable, если это применимо, и диапазон проверить данные.

Если у вас нет хорошего объекта с известным адресом VTable, вы можете использовать эти правила для проверки допустимости VTable:

  • Пока объект находится в памяти для чтения / записи, а указатель VTable является частью объекта, сам VTable будет жить в памяти, доступной только для чтения и не исполняемой, и будет выровнен по границе слова. Он также будет принадлежать модулю.
  • Записи в VTable являются указателями на код, который будет доступен только для чтения и может быть выполнен и недоступен для записи. Для кодовых адресов нет ограничений на выравнивание. Код будет принадлежать модулю.
0 голосов
/ 15 сентября 2016

Если переменная не инициализирована, вы попали в шланг. Рано или поздно это будет адрес для чего-то, с чем вы не хотите играть (например, ваш собственный стек).

Если вы считаете, что это необходимо, и (uintptr_t) var <65536 не достаточно (Windows не разрешает выделять нижние 64 КБ), реального решения не существует. VirtualQuery и т. Д. Кажутся «работающими», но рано или поздно вас обожгут. </p>

0 голосов
/ 31 мая 2016

Самое быстрое решение, которое я могу придумать, - обратиться к диспетчеру виртуальной памяти, используя VirtualQuery, чтобы узнать, есть ли читаемая страница по указанному адресу, и кэшировать результаты (однако любое кэширование снизит точность проверки).

Пример (без кеширования):

BOOL CanRead(LPVOID p)
{
  MEMORY_BASIC_INFORMATION mbi;
  mbi.Protect = 0;
  ::VirtualQuery(((LPCSTR)p) + len - 1, &mbi, sizeof(mbi));
  return ((mbi.Protect & 0xE6) != 0 && (mbi.Protect & PAGE_GUARD) == 0);
}
0 голосов
/ 30 января 2009

Любая реализация проверки правильности памяти подчиняется тем же константам, которые приводят к сбою IsBadReadPtr. Можете ли вы опубликовать пример callstack, где вы хотите проверить правильность памяти указателя, переданного вам из Windows? Это может помочь другим людям (включая меня) диагностировать, зачем вам это нужно делать в первую очередь.

0 голосов
/ 30 января 2009

, если вы используете VC ++, тогда я предлагаю использовать ключевые слова, определенные для Microsoft и поймать исключения HW

0 голосов
/ 30 января 2009

Боюсь, вам не повезло - нет способа надежно проверить правильность указателя. Какой код Microsoft дает вам плохие указатели?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...