2009-10-23 11 views
6

lo que está sucediendo es que estoy leyendo paquetes de cifrado, y encuentro un paquete dañado que devuelve un número aleatorio muy grande para la longitud.función vector.resize corrompe la memoria cuando el tamaño es demasiado grande

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

seckey.SecretValues.m_data.resize(nLengthRemaining); 

En este código m_data es un std::vector<unsigned char>. nLengthRemaining es demasiado grande debido a un paquete de datos dañado, por lo tanto, se lanza la función de cambio de tamaño. El problema no es que los cambios de tamaño (manejamos las excepciones), sino que el cambio de tamaño ya ha dañado la memoria y esto lleva a más excepciones más adelante.

Lo que quiero hacer es saber si la longitud es demasiado grande antes de llamar al cambio de tamaño, entonces solo llame el tamaño si está bien. He intentado poner este código antes de la llamada a cambiar el tamaño:

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); 

Este código está utilizando la función miembro std :: vector max_size para probar si el nLengthRemaining es mayor. Sin embargo, eso no debe ser confiable, porque nLengthRemaining es aún menor que nMaxSize, pero al parecer sigue siendo lo suficientemente grande como para causar que el tamaño tenga un problema (nMaxSize fue 4xxxxxxxxx y nLengthRemaining es 3xxxxxxxxx).

Además, no he determinado qué es el tamaño de excepción que arroja. No es std :: length_error y no es std :: bad_alloc. La excepción que está lanzando realmente no es demasiado importante para mí, pero tengo curiosidad por saberlo.

Por cierto, para que lo sepa, este código funciona correctamente en casos normales. Este caso de un paquete de datos dañado es el único lugar donde se vuelve loco. ¡Por favor ayuda! Gracias.

ACTUALIZACIÓN:

@ Michael. Por ahora solo voy a ignorar el paquete si es más grande que 5 MB. Discutiré con otros miembros del equipo sobre la posible validación de los paquetes (puede que ya esté allí y no lo sé). Estoy empezando a pensar que realmente es un error en nuestra versión de STL, la excepción que arroja ni siquiera es una excepción estándar, lo que me sorprendió. Trataré de averiguar de mi supervisor qué versión de STL estamos ejecutando también (¿cómo verificaría?).

OTRA ACTUALIZACIÓN: Solo demuestro que es un error en la versión de STL que estoy usando en mi máquina de desarrollo Visual Studio 6. Escribí esta aplicación de ejemplo:

// VectorMaxSize.cpp: define el punto de entrada para la aplicación de la consola. //

#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; 
} 

En mi máquina dev VS6 que tiene el mismo comportamiento que tiene el proyecto de cifrado, que provoca todo tipo de estragos. Cuando lo construya y lo ejecute en mi máquina Visual Studio 2008, resize arrojará una excepción std :: bad_alloc y el vector no se corromperá, ¡tal como lo habríamos esperado! Es hora de jugar fútbol EA EA NCAA, jeje!

+0

¿En qué plataforma estás? – sbi

+0

Tengo curiosidad de qué versión de compilador/stl está utilizando. Las implementaciones a las que tengo acceso no dañarán el objeto vector si falla la asignación. – jmucchiello

+0

@cchampion: "En mi máquina de desarrollo VS6 ..." Si hubiera dicho esto antes, no habríamos perdido tanto tiempo en esto. ¡Eso es más de 10 años de tecnología! Por supuesto, tiene errores. Ver mi respuesta – sbi

Respuesta

5

Creo que vector::max_size() es casi siempre algo "codificado", es independiente de la cantidad de memoria que el sistema/biblioteca está preparado para asignar dinámicamente. Su problema parece ser un error en la implementación del vector que corrompe las cosas cuando falla una asignación.

'Error' podría ser una palabra demasiado fuerte.vector::resize() se define en términos de vector::insert() y la norma dice esto acerca de vector::insert():

Si se produce una excepción que no sea por el constructor de copia o el operador de asignación de T no hay efectos

por lo que parece como puede haber ocasiones en que la operación resize() corrompa un vector, pero aún así sería bueno si la operación fuera excepcionalmente segura (y creo que no estaría fuera de línea esperar que la biblioteca hiciera eso, pero tal vez es más difícil de lo que imagino).

Parece que tienes un par de opciones razonables: (¿qué compilador/versión de la biblioteca se está utilizando)

  • cambio o actualización de una biblioteca que no tienen el error de corrupción
  • en vez de comprobar contra vector::max_size() establezca nMaxSize a su máximo razonable y haga lo que tiene arriba pero usando ese umbral en su lugar.

Editar:

veo que estás usando VC6 - definitivamente hay un error en vector::resize() que podría tener algo que ver con su problema, aunque mirando el parche sinceramente, no ver cómo (en realidad es un error en vector::insert(), pero como se mencionó, resize() llama a insert()). Supongo que valdría la pena visitar Dinkumwares' page for bug fixes to VC6 y aplicar las correcciones.

El problema también podría tener algo que ver con el parche <xmemory> en esa página - no está claro lo que el error es eso discuten allí, pero vector::insert() hace llamar _Destroy() y vector<> sí define el nombre _Ty por lo que podría estar ejecutando en ese problema . Una cosa buena: no tendrá que preocuparse por administrar los cambios en los encabezados, ya que Microsoft nunca los volverá a tocar. Solo asegúrate de que los parches estén en control de versiones y documentados.

Tenga en cuenta que Scott Meyers en "Effective STL" sugiere utilizar SGI's o STLPort's biblioteca para obtener una mejor compatibilidad con STL que viene con VC6. No he hecho eso, así que no estoy seguro de qué tan bien funcionan esas bibliotecas (pero tampoco he usado VC6 con STL). Por supuesto, si tiene la opción de pasar a una versión más nueva de VC, hágalo de todos modos.


Uno más editar:

Gracias por el programa de prueba ...

_Allocate() aplicación de VC6 para el asignador por defecto (en <xmemory>) utiliza un int firmado para especificar el número de elementos para asignar , y si el tamaño pasado es negativo (que aparentemente es lo que estás haciendo, sin duda en el programa de prueba que eres), la función _Allocate() fuerza el tamaño de asignación solicitado a cero y continúa. Tenga en cuenta que una solicitud de asignación de tamaño cero casi siempre tendrá éxito (no es que vector busque una falla de todos modos), por lo que la función vector::resize() alegremente intenta mover su contenido al nuevo bloque, que no es lo suficientemente grande como para decir lo menos . Por lo tanto, el montón se corrompe, es probable que llegue a una página de memoria no válida y, sin tener en cuenta, su programa tiene una manguera.

Así que la conclusión es que nunca le pidas a VC6 que asigne más de INT_MAX objetos de una sola vez. Probablemente no sea una gran idea en la mayoría de las circunstancias (VC6 o de otro tipo).

Además, debe tener en cuenta que VC6 usa una expresión idiomática preestablecida de devolver 0 desde new cuando falla una asignación en lugar de arrojar bad_alloc.

+0

"Si se lanza una excepción que no sea el constructor de copias o el operador de asignación de T, no hay efectos" IRTA ", es como si' resize() 'no se hubiera llamado." Y estoy bastante seguro de que no se supone que ninguna operación en un vector corrompa la memoria. – sbi

+0

La operación de inserción() puede dar como resultado operaciones de copia/asignación (cuando los contenidos del vector se copian a una nueva asignación) - se permite que 'tengan un efecto'. No debería hacer nada tan malo como corromper el montón, por ejemplo, pero no está claro si eso es lo que le está sucediendo al OP. Se permite que una excepción bajo esas condiciones resulte en un vector cambiado (tal vez no todos los elementos en el vector lleguen a uno nuevo). Su código puede encontrar que el vector ya no tiene sentido. De cualquier manera, no es un gran comportamiento, y estoy de acuerdo en que una implementación de STL podría manejar mejor la situación. –

+0

Tiene razón, aunque copiar/asignar elementos en un 'vector ' no debe dar lugar a ninguna excepción, me parece que apunta a una implementación de STL con errores que no maneja bien la situación de falta de memoria . Me interesarían los detalles sobre la plataforma/compilador/biblioteca que se está utilizando. –

5

¡Le RECOMIENDO encarecidamente que verifique sus datos en busca de daños ANTES de invocar funciones de la biblioteca con argumentos posiblemente incorrectos!

Utilice algún tipo de código hash o algoritmo de suma de verificación en sus paquetes. No puede confiar en la biblioteca para que lo ayude, ya que no puede: Puede ser que le dé un tamaño dañado pero aún válido (desde el punto de vista de la biblioteca) que es muy grande, por lo que asigna, por ejemplo 768MB de RAM. Esto puede funcionar si hay suficiente memoria libre en el sistema pero puede fallar si hay otros programas en ejecución que consuman demasiada memoria en su máquina de 1024MB.

Como dije anteriormente: ¡Compruebe primero!

+0

Estoy de acuerdo. Creo que la raíz de tu problema es que dependes de tu algoritmo de encriptación para decirte el tamaño que "pretende" tener. Realmente necesita una aplicación de bloque con relleno (como hace MD5), o tiene otra forma fuera de banda de proporcionar información de tamaño. –

+0

Acepto, necesitamos algún método para validar paquetes. Lo mencionaré a otros programadores en este proyecto el lunes.Por ahora solo saltearé el paquete si es más grande que 5 MB. – cchampion

4

No tengo idea de lo que quieres decir cuando dices "resize ha corrompido la memoria". ¿Cómo determinas eso?

FWIW, no estoy de acuerdo con Michael's answer. Si std::vector<>::resize() tiros en la expansión del vector, veo dos posibilidades:

  1. Cualquiera de los constructores utilizados para llenar el nuevo espacio (o copiar los elementos) arrojó o
  2. el asignador utilizada para cultivar el vector hizo
  3. o el vector determinado de antemano que el tamaño solicitado es demasiado y arroja.

Con std::vector<unsigned char> podemos descartar con seguridad el n. ° 1, por lo que deja n. ° 2. Si no usa ningún asignador especial, entonces se debe usar std::allocator y, AFAIK, que llamará al new para asignar memoria. Y new lanzaría std::bad_alloc. Sin embargo, dices que no puedes captar esto, así que no sé qué sucede.

Sea lo que sea, hay que deriva de std::exception, por lo que podría hacer esto para averiguar:

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

¿Cuál es el resultado de eso?

De todos modos, sea lo que sea, estoy bastante seguro de que no debería dañar la memoria. O eso es un error en su implementación de std lib (poco probable, si me preguntas, a menos que uses uno muy antiguo) o has hecho algo mal en otro lado.


Editar ahora que usted ha dicho que está utilizando VS6 ...

Usted debería haber dicho esto antes. El VC6 fue lanzado hace más de una década, después de que MS había perdido su voto en el comité estándar porque no habían aparecido en las reuniones por mucho tiempo.La implementación de std lib que enviaron era de Dinkumware (bueno), pero debido a problemas legales fue la de VC5 (muy mala), que tenía muchos errores más pequeños y más grandes y ni siquiera tenía soporte para plantillas de miembros, aunque el compilador VC6 lo apoyó. Honestamente, ¿qué esperas de un producto tan viejo?

Si no puede cambiar a una versión de VC decente (recomendaría al menos VC7.1 también conocido como VS.NET 2003 ya que este fue el que dio el gran salto hacia la conformidad estándar), al menos vea si Dinkumware aún se venden una versión VC6t de su excelente biblioteca. (En realidad, me sorprendería, pero solían tener uno y nunca se sabe ...)

En cuanto a las excepciones: En la versión anterior de VC (esto incluye VC6 y no incluye VC8 también conocido como VS.NET 2005 , No estoy seguro acerca de VC7.1, sin embargo) por defecto las violaciones de acceso podrían ser detectadas por catch(...). Entonces, si ese bloque catch captura algo, no sabría si esto era incluso una excepción C++. Mi consejo sería usar solo catch(...) en confunción con throw; para permitir que la excepción pase. Si lo haces, obtienes un bloqueo real en los antivirus y puedes rastrearlos en el depurador. Si no lo hace, se tragarán los AV y luego se encontrará con una aplicación que se ha vuelto loca sin que usted lo sepa. Pero hacer cualquier cosa que no sea abortar con una aplicación AV no tiene sentido. Un AV es un resultado de comportamiento indefinido y después de eso, todas las apuestas están desactivadas.

+0

La razón por la que digo que la memoria está dañada es porque las declaraciones de registro simples comienzan a arrojar excepciones y también siguen surgiendo un montón de aserciones relacionadas con la memoria. Después de la función de cambio de tamaño, todo el programa lo pierde de vista. Esto no sucede si pasa el cambio de tamaño de una longitud razonable. No sucede si salteo ese paquete tampoco. Voy a probar ese código para determinar el tipo de excepción, ¡no sabía sobre typeid! Gracias. – cchampion

+0

Si no lo sabía: tiene que '#incluir ' para eso. – sbi

+0

Créalo o no, la const std :: exception & handler NO LA ENCONTRÓ. No tengo idea de qué es esta excepción. Ahora estoy empezando a creer que la versión del STL que estamos usando tiene un error. Discutiré esto con el programador el lunes. – cchampion

Cuestiones relacionadas