Функция vector.resize портит память при слишком большом размере - PullRequest
6 голосов
/ 23 октября 2009

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

size_t nLengthRemaining = packet.nLength - (packet.m_pSource->GetPosition() - packet.nDataOffset);

seckey.SecretValues.m_data.resize(nLengthRemaining);

В этом коде m_data представляет собой std::vector<unsigned char>. nLengthRemaining слишком велик из-за поврежденного пакета данных, поэтому выбрасывает функция изменения размера. Проблема не в том, что броски изменения размера (мы обрабатываем исключения), а в том, что изменение размера уже повредило память, и это приводит к большему количеству исключений позже.

Что я хочу сделать, это знать, что длина слишком велика, прежде чем я вызову resize, тогда вызову resize, только если все в порядке. Я попытался поставить этот код перед вызовом, чтобы изменить размер:

std::vector<unsigned char>::size_type nMaxSize = seckey.SecretValues.m_data.max_size();
if(seckey.SecretValues.m_data.size() + nLengthRemaining >=  nMaxSize) {
    throw IHPGP::PgpException("corrupted packet: length too big.");
}
seckey.SecretValues.m_data.resize(nLengthRemaining);

Этот код использует функцию-член max_size std :: vector для проверки, больше ли nLengthRemaining. Это не должно быть надежным, потому что nLengthRemaining все еще меньше, чем nMaxSize, но, по-видимому, все еще достаточно велик, чтобы вызвать изменение размера для проблемы (nMaxSize был 4xxxxxxxxx, а nLengthRemaining - 3xxxxxxxxx).

Кроме того, я не определил, какое исключение вызывает изменение размера. Это не std :: length_error и не std :: bad_alloc. Какое исключение он бросает на самом деле, не так уж важно для меня, но мне интересно знать.

Кстати, просто вы знаете, этот код работает нормально в нормальных случаях. Этот случай поврежденного пакета данных - единственное место, где он сходит с ума. Пожалуйста помоги! спасибо.

UPDATE:

@ Майкл. Сейчас я просто проигнорирую пакет, если он больше 5 МБ. Я поговорю с другими членами команды о возможной проверке пакетов (они могут быть уже там, и я просто не знаю). Я начинаю думать, что это действительно ошибка в нашей версии STL, исключение, которое она выдает, даже не std :: исключение, которое меня удивило. Я постараюсь выяснить у своего руководителя, какую версию STL мы тоже используем (как я могу проверить?).

ДРУГОЕ ОБНОВЛЕНИЕ: Я просто доказываю, что это ошибка в версии STL, которую я использую на своей машине для разработки Visual Studio 6. Я написал этот пример приложения:

// VectorMaxSize.cpp: определяет точку входа для консольного приложения. //

#include "stdafx.h"
#include <vector>
#include <iostream>
#include <math.h>
#include <typeinfo>

typedef std::vector<unsigned char> vector_unsigned_char;

void fill(vector_unsigned_char& v) {
    for (int i=0; i<100; i++) v.push_back(i);
}


void oput(vector_unsigned_char& v) {
    std::cout << "size: " << v.size() << std::endl;
    std::cout << "capacity: " << v.capacity() << std::endl;
    std::cout << "max_size: " << v.max_size() << std::endl << std::endl;
}

void main(int argc, char* argv[]) {
    {
        vector_unsigned_char v;

        fill(v);

        try{
            v.resize(static_cast<size_t>(3555555555));
        }catch(std::bad_alloc&) {
            std::cout << "caught bad alloc exception" << std::endl;
        }catch(const std::exception& x) {
            std::cerr << typeid(x).name() << std::endl;
        }catch(...) {
            std::cerr << "unknown exception" << std::endl;
        }

        oput(v);    
        v.reserve(500);
        oput(v);
        v.resize(500);
        oput(v);
    }

    std::cout << "done" << std::endl;
}

На моем компьютере с VS6 он имеет такое же поведение, как и проект шифрования, он вызывает все виды хаоса. Когда я собираю и запускаю его на компьютере с Visual Studio 2008, изменение размера вызовет исключение std :: bad_alloc, и вектор не будет поврежден, как мы и ожидали! Время для футбола EA Sport NCAA хехе!

Ответы [ 3 ]

5 голосов
/ 24 октября 2009

Я бы настоятельно рекомендовал вам проверить ваши данные на наличие повреждений ДО вызова функций библиотеки с ошибочными аргументами!

Используйте какой-нибудь хэш-код или алгоритм контрольной суммы для ваших пакетов. Вы не можете полагаться на библиотеку, чтобы помочь вам, так как она не может сделать: Может случиться так, что вы дадите ему поврежденный, но все еще действительный (с точки зрения библиотеки) размер, который действительно большой, поэтому он выделяет, например, 768 МБ ОЗУ. Это может работать, если в системе достаточно свободной памяти, но может не работать, если запущены другие программы, которые занимают слишком много памяти на вашем компьютере объемом 1024 МБ.

Итак, как сказано выше: сначала проверьте!

5 голосов
/ 23 октября 2009

Я думаю, что vector::max_size() почти всегда является «жестко запрограммированной» вещью - она ​​не зависит от того, сколько памяти система / библиотека готова динамически распределять. Кажется, ваша проблема - ошибка в векторной реализации, которая портит вещи при неудачном выделении.

«Ошибка» может быть слишком сильным словом. vector::resize() определяется в терминах vector::insert(), и в стандарте говорится об vector::insert():

Если исключение выдается не конструктором копирования или оператором присваивания T, эффекты отсутствуют

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

У вас, кажется, есть пара разумных вариантов:

  • изменить или обновить библиотеку, в которой нет ошибки повреждения (какую версию компилятора / библиотеки вы используете?)
  • вместо проверки vector::max_size() установите nMaxSize на свой разумный максимум и сделайте то, что у вас выше, но вместо этого используйте этот порог.

Edit:

Я вижу, что вы используете VC6 - определенно есть ошибка в vector::resize(), которая может иметь какое-то отношение к вашей проблеме, хотя, глядя на патч, я, честно говоря, не вижу как (на самом деле это ошибка в * 1030) *, но, как уже упоминалось, resize() вызывает insert()). Я предполагаю, что стоило бы посетить страницу Dinkumwares для исправления ошибок в VC6 и применить исправления.

Проблема также может быть связана с патчем <xmemory> на этой странице - неясно, что за ошибка там обсуждается, но vector::insert() вызывает _Destroy() и vector<> определяет имя _Ty так что вы можете столкнуться с этой проблемой. Одна приятная вещь - вам не придется беспокоиться об управлении изменениями заголовков, так как Microsoft никогда больше не трогает их. Просто убедитесь, что исправления попадают в систему контроля версий и документируются.

Обратите внимание, что Скотт Мейерс из "Effective STL" предлагает использовать SGI или STLPort библиотеку, чтобы получить лучшую поддержку STL, чем в VC6. Я этого не делал, поэтому я не уверен, насколько хорошо работают эти библиотеки (но я также не очень часто использовал VC6 с STL). Конечно, если у вас есть возможность перейти на более новую версию VC, обязательно сделайте это.


Еще одно редактирование:

Спасибо за тестовую программу ...

Реализация _Allocate() в VC6 для распределителя по умолчанию (в <xmemory>) использует подписанное int для указания количества элементов для выделения, и если переданный размер является отрицательным (что, очевидно, то, что вы делаете - конечно, в тестовой программе вы) функция _Allocate() устанавливает требуемый размер выделения на ноль и продолжает работу. Обратите внимание, что запрос на распределение нулевого размера будет в большинстве случаев успешным (не то, что vector в любом случае проверяет наличие сбоя), поэтому функция vector::resize() весело пытается переместить его содержимое в новый блок, который недостаточно велик. по меньшей мере. Таким образом, куча будет повреждена, она, скорее всего, попадет на недопустимую страницу памяти, и независимо от этого - ваша программа заблокирована.

Итак, суть в том, чтобы VC6 никогда не выделял более INT_MAX объектов за один раз. Вероятно, не очень хорошая идея в большинстве случаев (VC6 или иначе).

Кроме того, вы должны иметь в виду, что VC6 использует стандартный стандарт возврата 0 из new, когда выделение завершается неудачей, а не выбрасывает bad_alloc.

4 голосов
/ 24 октября 2009

Понятия не имею, что вы имеете в виду, когда говорите "изменение размера испортило память". Как вы это определяете?

FWIW, я не согласен с ответом Майкла . Если std::vector<>::resize() выбрасывает векторное расширение, я вижу две возможности:

  1. Либо один из конструкторов, использованных для заполнения нового пространства (или копирования элементов), бросил, либо
  2. распределитель, использованный для роста вектора
  3. или вектор, определенный заранее, что запрошенный размер слишком велик и выбрасывает.

С помощью std::vector<unsigned char> мы можем спокойно удалить # 1, так что оставим # 2. Если вы не используете какой-либо специальный распределитель, тогда следует использовать std::allocator и, AFAIK, который будет вызывать new для выделения памяти. И new бросил бы std::bad_alloc. Тем не менее, вы говорите, что не можете поймать это, поэтому я не знаю, что происходит.

Что бы это ни было, оно должно быть получено из std::exception, так что вы можете сделать это, чтобы узнать:

try {
  my_vec.resize( static_cast<std::size_t>(-1) );
} catch(const std::exception& x) {
  std::cerr << typeid(x).name() << '\n';
}

Каков результат этого?

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


Редактировать теперь, когда вы сказали, что используете VS6 ...

Ты должен был сказать это раньше. VC6 был выпущен более десяти лет назад, после того, как MS потеряли свой голос в комитете по стандартизации, потому что они не появлялись на собраниях слишком долго. Реализация std lib, которую они поставили, была от Dinkumware (хорошо), но из-за юридических проблем это была VC5 (очень плохо), которая имела много мелких и больших ошибок и даже не поддерживала шаблоны элементов, хотя компилятор VC6 поддерживал это. Честно говоря, что вы ожидаете от такого старого продукта?

Если вы не можете переключиться на приличную версию VC (я бы рекомендовал, по крайней мере, VC7.1 или VS.NET 2003, так как именно он сделал большой скачок к стандартному соответствию), по крайней мере, посмотрите, если Dinkumware все еще продают VC6t-версию своей превосходной библиотеки. (На самом деле, я был бы удивлен, но у них был один, и вы никогда не знаете ...)

Что касается исключений: в более ранней версии VC (включая VC6 и не включающей VC8, то есть VS.NET 2005, хотя я не уверен в VC7.1) по умолчанию нарушения прав доступа могут быть обнаружены catch(...) , Так что если такой блок catch что-то поймал, вы бы не знали, было ли это исключением из C ++. Я бы посоветовал использовать catch(...) только вместе с throw;, чтобы это исключение прошло. Если вы это сделаете, вы получите реальный сбой в AV и сможете отслеживать их в отладчике. Если вы этого не сделаете, AV будут проглочены, а затем вы застряли с приложением, которое стало бешеным без вашего ведома. Но делать что-либо, кроме прерывания с помощью приложения AV, бессмысленно. AV является одним из результатов неопределенного поведения , и после этого все ставки выключены.

...