tipos segun mexicana ejemplos definicion corrupcion consecuencias causas caracteristicas autores c++ c memory memory-management openssl

c++ - segun - ¿Qué puede explicar la corrupción de montón en una llamada a free()?



la corrupcion (2)

He estado depurando un bloqueo durante días, que ocurre en las profundidades de OpenSSL (discusión con los mantenedores aquí ). Me tomé un tiempo investigando, así que trataré de hacer que esta pregunta sea interesante e informativa.

Primero y para dar algún contexto, mi muestra mínima que reproduce el bloqueo es la siguiente:

#include <openssl/crypto.h> #include <openssl/ec.h> #include <openssl/objects.h> #include <openssl/pem.h> #include <openssl/err.h> #include <openssl/engine.h> int main() { ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); ENGINE_load_builtin_engines(); EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sect571k1); EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); EC_KEY* eckey = EC_KEY_new(); EC_KEY_set_group(eckey, group); EC_KEY_generate_key(eckey); BIO* out = BIO_new(BIO_s_file()); BIO_set_fp(out, stdout, BIO_NOCLOSE); PEM_write_bio_ECPrivateKey(out, eckey, NULL, NULL, 0, NULL, NULL); // <= CRASH. }

Básicamente, este código genera una clave de curva elíptica e intenta dar salida a stdout . Se puede encontrar un código similar en openssl.exe ecparam y en Wikis en línea. Funciona bien en Linux (valgrind informa que no hay ningún error). Solo se bloquea en Windows (Visual Studio 2013 - x64). Me aseguré de que los tiempos de ejecución correctos estuvieran vinculados a ( /MD en mi caso, para todas las dependencias).

Sin temor a ningún mal, recompuse OpenSSL en x64-debug (esta vez vinculando todo en /MDd ) y /MDd el código para encontrar el conjunto de instrucciones ofensivo. Mi búsqueda me llevó a este código (en el archivo tasn_fre.c de OpenSSL):

static void asn1_item_combine_free(ASN1_VALUE **pval, const ASN1_ITEM *it, int combine) { // ... some code, not really relevant. tt = it->templates + it->tcount - 1; for (i = 0; i < it->tcount; tt--, i++) { ASN1_VALUE **pseqval; seqtt = asn1_do_adb(pval, tt, 0); if (!seqtt) continue; pseqval = asn1_get_field_ptr(pval, seqtt); ASN1_template_free(pseqval, seqtt); } if (asn1_cb) asn1_cb(ASN1_OP_FREE_POST, pval, it, NULL); if (!combine) { OPENSSL_free(*pval); // <= CRASH OCCURS ON free() *pval = NULL; } // Some more code... }

Para aquellos que no están demasiado familiarizados con OpenSSL y sus rutinas ASN.1, básicamente lo que hace for -loop es que va a través de todos los elementos de una secuencia (comenzando con el último elemento) y los "borra" (más sobre esto más adelante) .

Justo antes de que ocurra el bloqueo, se elimina una secuencia de 3 elementos (en *pval , que es 0x00000053379575E0 ). Mirando la memoria, uno puede ver que suceden las siguientes cosas:

La secuencia tiene 12 bytes de largo, cada elemento tiene una longitud de 4 bytes (en este caso, 2 , 5 y 10 ). En cada iteración de bucle, OpenSSL "borra" los elementos (en este contexto, no se llaman ni delete ni free : simplemente se establecen en un valor específico). Así es como la memoria se ve después de una iteración:

El último elemento aquí se configuró en ff ff ff 7f que supongo que es la forma en que OpenSSL asegura que no se filtre información clave cuando la memoria no se asigna más tarde.

Justo después del bucle (y antes de la llamada a OPENSSL_free() ), la memoria es la siguiente:

Todos los elementos se configuraron en ff ff ff 7f , asn1_cb es NULL por lo que no se realiza ninguna llamada. Lo siguiente que sucede es la llamada a OPENSSL_free(*pval) .

Esta llamada a free() en lo que parece ser una memoria válida y asignada falla y hace que la ejecución aborte con un mensaje: "HEOP CORRUPTION DETECTED" .

Curioso acerca de esto, me enganché en malloc , realloc y free (como OpenSSL lo permite) para asegurar que no se realloc una memoria doblemente libre o libre en una memoria nunca asignada. Resulta que la memoria en 0x00000053379575E0 realmente es un bloque de 12 bytes que efectivamente fue asignado (y nunca antes liberado).

Estoy en la pérdida de averiguar lo que sucede aquí: de mi investigación, parece que free() falla en un puntero que normalmente devuelve malloc() . Además de eso, esta ubicación de memoria se estaba escribiendo en un par de instrucciones antes sin ningún problema que confirma la hipótesis de que la memoria se asigna correctamente.

Sé que es difícil, si no imposible, depurar de manera remota sin toda la información, pero no tengo idea de cuáles serán mis próximos pasos.

Entonces mi pregunta es: ¿cómo es exactamente esta "CORRUPCIÓN DE CORRECCIÓN" detectada por el depurador de Visual Studio? ¿Cuáles son todas las posibles causas cuando se origina de una llamada a free() ?


Finalmente pude encontrar el problema y resolverlo.

Resultó que algunas instrucciones estaban escribiendo bytes más allá del almacenamiento intermedio de montón asignado (de ahí el 0x00000000 lugar del 0xfdfdfdfd esperado).

En el modo de depuración, esta sobreescritura de los resguardos de memoria permanece sin detectar hasta que la memoria se libera con free() o se reasigna con realloc() . Esto es lo que causó el mensaje HEOP CORRUPTION que enfrenté.

Espero que en el modo de lanzamiento, esto podría haber tenido efectos dramáticos, como sobrescribir un bloque de memoria válido utilizado en otro lugar de la aplicación.

Para referencia futura a personas que enfrentan problemas similares, así es como lo hice:

OpenSSL proporciona una función CRYPTO_set_mem_ex_functions() , definida así:

int CRYPTO_set_mem_ex_functions(void *(*m) (size_t, const char *, int), void *(*r) (void *, size_t, const char *, int), void (*f) (void *))

Esta función le permite enganchar y reemplazar funciones de asignación / liberación de memoria dentro de OpenSSL. Lo bueno es la adición de los parámetros const char *, int que básicamente son completados por OpenSSL y contienen el nombre de archivo y el número de línea de la asignación.

Armado con esta información, fue fácil averiguar el lugar donde se asignó el bloque de memoria. Podría pasar el código mientras miro al inspector de memoria esperando que el bloque de memoria se corrompa.

En mi caso, lo que sucedió fue:

if (!combine) { *pval = OPENSSL_malloc(it->size); // <== The allocation is here. if (!*pval) goto memerr; memset(*pval, 0, it->size); asn1_do_lock(pval, 0, it); asn1_enc_init(pval, it); } for (i = 0, tt = it->templates; i < it->tcount; tt++, i++) { pseqval = asn1_get_field_ptr(pval, tt); if (!ASN1_template_new(pseqval, tt)) goto memerr; }

Se llama a ASN1_template_new() en los 3 elementos de secuencia para inicializarlos.

Resulta llamadas ASN1_template_new() a su vez asn1_item_ex_combine_new() que hace esto:

if (!combine) *pval = NULL;

pval es ASN1_VALUE** , esta instrucción establece 8 bytes en sistemas Windows x64 en lugar de los 4 bytes previstos, lo que genera daños en la memoria para el último elemento de la lista.

Para la discusión completa sobre cómo se resolvió este problema en sentido ascendente, consulte este hilo .


En general, las posibilidades incluyen:

  1. Duplicado gratis.
  2. Prior duplicado gratis.
  3. (Más probable) Su código escribió más allá de los límites del trozo de memoria asignado, ya sea antes o después del final. malloc() y sus amigos ponen aquí información de contabilidad adicional, como el tamaño, y probablemente una verificación de cordura, que fallará sobrescribiendo.
  4. Liberando algo que no había sido malloc() -ed.
  5. Continuar escribiendo en un fragmento que ya había sido free() -d.