La función vector.resize corrompe la memoria cuando el tamaño es demasiado grande

StackOverflow https://stackoverflow.com/questions/1615518

  •  06-07-2019
  •  | 
  •  

Pregunta

Lo que sucede es que estoy leyendo paquetes de cifrado y encuentro un paquete corrupto que devuelve un número aleatorio muy grande para su longitud.

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 que se activa la función de cambio de tamaño.El problema no es que el cambio de tamaño genere (nosotros manejamos las excepciones), sino que el cambio de tamaño ya ha dañado la memoria y esto genera más excepciones más adelante.

Lo que quiero hacer es saber si la longitud es demasiado grande antes de llamar a cambiar tamaño, y luego llamar a cambiar tamaño solo si está bien.Intenté poner este código antes de la llamada para 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 utiliza la función miembro std::vector max_size para probar si nLengthRemaining es mayor.Sin embargo, eso no debe ser confiable, porque nLengthRemaining sigue siendo menor que nMaxSize, pero aparentemente sigue siendo lo suficientemente grande como para causar que el cambio de tamaño tenga un problema (nMaxSize era 4xxxxxxxxx y nLengthRemaining es 3xxxxxxxxx).

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

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

ACTUALIZAR:

@Miguel.Por ahora ignoraré el paquete si tiene más de 5 MB.Hablaré con otros miembros del equipo sobre la posibilidad de validar los paquetes (es posible que ya estén allí y simplemente 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 std::, lo cual me sorprendió.Intentaré averiguar con mi supervisor qué versión de STL estamos ejecutando también (¿cómo puedo comprobarlo?).

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

// VectorMaxSize.cpp :Define el punto de entrada para la aplicación de 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 de desarrollo VS6 tiene el mismo comportamiento que el proyecto de cifrado, causa todo tipo de estragos.Cuando lo construyo y lo ejecuto en mi máquina Visual Studio 2008, el cambio de tamaño generará una excepción std::bad_alloc y el vector no se dañará, ¡tal como esperábamos!¡Es hora de jugar al fútbol de la NCAA de EA Sport, jeje!

¿Fue útil?

Solución

Creo que vector::max_size() Casi siempre es algo "codificado": es independiente de cuánta memoria 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" puede que sea una palabra demasiado fuerte. vector::resize() se define en términos de vector::insert() y la norma dice esto sobre vector::insert():

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

Entonces parece que puede haber momentos en que el resize() Se permite que la operación corrompa un vector, pero aún así sería bueno si la operación fuera segura para excepciones (y creo que no estaría fuera de lugar esperar que la biblioteca hiciera eso, pero tal vez sea más difícil de lo que imagino).

Parece que tienes un par de opciones razonables:

  • cambie o actualice a una biblioteca que no tenga el error de corrupción (¿qué versión del compilador/biblioteca está utilizando?)
  • en lugar de comparar vector::max_size() colocar nMaxSize a su propio 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() eso podría tener algo que ver con tu problema, aunque mirando el parche honestamente no veo cómo (en realidad es un error en vector::insert(), pero como se mencionó, resize() llamadas insert()).Supongo que valdría la pena visitarlo. Página de Dinkumwares para corregir errores en VC6 y aplicar las correcciones.

El problema también podría tener algo que ver con la <xmemory> parche en esa página: no está claro cuál es el error que se analiza allí, pero vector::insert() llama _Destroy() y vector<> define el nombre _Ty entonces es posible que te encuentres con ese problema.Una cosa buena: no tendrás que preocuparte por administrar los cambios en los encabezados, ya que Microsoft nunca volverá a tocarlos.Solo asegúrese de que los parches lleguen al control de versiones y estén documentados.

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


Una edición más:

Gracias por el programa de prueba...

VC6 _Allocate() implementación para el asignador predeterminado (en <xmemory>) usa un int con signo para especificar la cantidad de elementos a asignar, y si el tamaño pasado es negativo (que aparentemente es lo que estás haciendo, ciertamente en el programa de prueba que estás) el _Allocate() La función 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 comprueba si hay un fallo de todos modos), por lo que el vector::resize() La función intenta alegremente mover su contenido al nuevo bloque, que no es lo suficientemente grande por decir lo menos.Entonces el montón se corrompe, probablemente llegará a una página de memoria no válida y, de todos modos, su programa está arruinado.

Entonces, la conclusión es que nunca le pidan 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 no).

Además, debe tener en cuenta que VC6 utiliza un modismo preestándar de devolver 0 de new cuando una asignación falla en lugar de tirar bad_alloc.

Otros consejos

¡Sugeriría ENCARECIDAMENTE que verifique sus datos en busca de daños ANTES de llamar a las funciones de la biblioteca con argumentos quizás incorrectos!

Use 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 hacer lo siguiente: Puede ser que le otorgue un tamaño corrupto pero aún válido (desde el punto de vista de la biblioteca) que es realmente grande, por lo que asigna, por ejemplo, 768 MB de RAM. Esto puede funcionar si hay suficiente memoria libre en el sistema, pero puede fallar si hay otros programas en ejecución que consumen demasiada memoria en su máquina de 1024 MB.

Entonces, como se dijo anteriormente: ¡Verifique primero!

No tengo idea de lo que quieres decir cuando dices " cambiar el tamaño ha corrompido la memoria " ;. ¿Cómo determinas eso?

FWIW, no estoy de acuerdo con la respuesta de Michael . Si std::vector<>::resize() arroja la expansión vectorial, veo dos posibilidades:

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

Con std::vector<unsigned char> podemos descartar con seguridad el n. ° 1, de modo que quede el n. ° 2. Si no usa ningún asignador especial, entonces debe usarse std::allocator y, AFAIK, eso llamará a new para asignar memoria. Y std::bad_alloc arrojaría std::exception. Sin embargo, dices que no puedes atrapar esto, así que no sé qué sucede.

Sea lo que sea, debe derivarse de catch(...), por lo que puede hacer esto para averiguarlo:

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 corromper la memoria. O eso es un error en su implementación std lib (poco probable, si me pregunta, a menos que use uno muy viejo) o ha hecho algo mal en otro lugar.


Editar ahora que dijo que está usando VS6 ...

Deberías haber dicho esto antes. 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 fue de Dinkumware (buena), 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 soportó. Honestamente, ¿qué esperas de un producto tan viejo?

Si no puede cambiar a una versión VC decente (recomendaría al menos VC7.1, también conocido como VS.NET 2003, ya que este fue el que dio el salto principal hacia la conformidad estándar), al menos vea si Dinkumware todavía vende 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, sin embargo, no estoy seguro acerca de VC7.1). . Entonces, si un bloqueo de captura detecta algo, no sabría si esta fue una excepción de C ++. Mi consejo sería usar solo throw; en combinación con <=> para permitir que esa excepción pase. Si lo hace, obtendrá un bloqueo real en AV y podrá apilarlos en el depurador. Si no lo hace, los AV se tragarán y luego se quedará atrapado con una aplicación que se ha vuelto loca sin siquiera saberlo. Pero hacer cualquier cosa menos abortar con una aplicación AV no tiene sentido. Un AV es el resultado de comportamiento indefinido y después de eso, todas las apuestas están desactivadas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top