¿Se habría prevenido el error Heartbleed si OpenSSL se escribiera en Go / D / Vala?

18

IIUC la vulnerabilidad Heartbleed ocurre debido a un error en el código fuente C de OpenSSL, al realizar un memcpy() desde un búfer que es demasiado corto. Me pregunto si el error se habría prevenido automáticamente en otros idiomas que tienen sistemas de administración de memoria de nivel superior a C o C ++.

En particular, entiendo que Go , D y Vala cada compilación a código nativo, no necesita una VM para ejecutarse y debe permitir escribir bibliotecas nativas que proporcionen una C -interfaz binaria compatible.

Entonces, ¿podrían usarse estos idiomas para implementar la biblioteca OpenSSL con la misma interfaz, y ofrecerían protección contra errores como la vulnerabilidad de Heartbleed?

    
pregunta oliver 08.04.2014 - 21:27
fuente

5 respuestas

20

En realidad, ninguno de estos idiomas habría prevenido el error, pero habría reducido las consecuencias.

El código de OpenSSL está haciendo algo que, desde el punto de vista de la máquina abstracta, no tiene sentido: lee más bytes de un búfer de lo que realmente hay en un búfer. Con C, la lectura aún "funciona" y devuelve los bytes que quedaron después del búfer. Con idiomas más estrictos, el acceso a la memoria fuera de los límites se habría atrapado y se habría desencadenado una excepción: en lugar de leer y enviar los bytes, el código ofensivo simplemente fallaría, lo que provocaría (en el contexto de un servidor web) la terminación de el hilo actual y probablemente el cierre de la conexión, sin alterar el resto del servidor.

Por lo tanto, sigue siendo un error, pero ya no es una vulnerabilidad real.

Esto solo tiene una relación indirecta con la administración automática de memoria. La verdadera red de seguridad aquí es la verificación sistemática de los límites de la matriz en los accesos. Esa comprobación sistemática es luego indirectamente soportada por la tipificación estricta (que evita el uso de cualquier otra cosa que no sea una "matriz de bytes" como una "matriz de bytes"). Los tipos estrictos son soportados indirectamente por la gestión automática de la memoria (GC), ya que evita los punteros colgantes y, por lo tanto, las condiciones de uso después del libre que violan la tipificación estricta.

El reciente "corazón de corazón" no es algo cualitativamente nuevo; OpenSSL ya ha tenido bastantes errores relacionados con el desbordamiento de búfer a lo largo de los años (algunos de los cuales provienen del código de manejo ASN.1, para el análisis de certificados). Este es solo otro de la lista.

    
respondido por el Thomas Pornin 08.04.2014 - 23:49
fuente
17

Si considera que el error es una lectura fuera de los límites de la estructura actual, es probable que esto se haya evitado en otros idiomas, ya que uno no tiene acceso ilimitado a la memoria y debería implementar estas cosas de manera diferente.

Pero preferiría clasificar este error como falta de validación de la entrada del usuario, por ejemplo, cree que el tamaño enviado en el paquete es en realidad el tamaño de la carga útil. Este tipo de errores no se solucionan simplemente usando otro idioma.

    
respondido por el Steffen Ullrich 08.04.2014 - 23:37
fuente
9

Desafortunadamente, el error no se ha evitado, ya que OpenSSL utiliza su propio asignador de memoria , en lugar del que proporciona el sistema.

El búfer desde el que se leen los datos del latido del corazón infame es asignado por una función llamada freelist_extract en ssl / s3_both.c . Esta función, de forma predeterminada, gestiona la propia lista de OpenSSL de memoria utilizada / no utilizada, y no realiza ninguna de las comprobaciones de seguridad modernas.

Incluso si se hubiera escrito en otro idioma, suponiendo que OpenSSL hubiera seguido manteniendo su propio asignador de búfer, entonces este error habría ocurrido de la misma manera. Al reutilizar una estructura de búfer anterior, independientemente del lenguaje de programación, la función memcpy o "copia de búfer" equivalente habría hecho lo mismo sin generar ningún error.

En un lenguaje de programación moderno, esto sería algo así como:

request = last_used_buffer;
/* I'm sure it doesn't actually read bytes like this, but you get the idea */
while (byte = read(connection)) {
    request[i++] = byte;
}

/* ... some time later, in the heartbeat processing function */

output = new Buffer();
output.write(header);
output.write(request, start, len); /* dutifully copies from the request buffer,
                                      but since end was not checked, it can copy
                                      bytes from last_used_buffer */

Si, por el contrario, OpenSSL hubiera estado utilizando directamente el sistema (libc) malloc y free en lugar de su propio asignador, este error podría haberse detectado hace un par de años. Muchas implementaciones de libc proporcionan comprobaciones de límites mucho mejores en la memoria asignada / liberada, y herramientas como valgrind podrían haber detectado este error fácilmente.

Ted Unangst mencionó esta consecuencia del asignador de memoria de OpenSSL en el funcionamiento del error del corazón del corazón en: enlace

    
respondido por el codebeard 10.04.2014 - 10:31
fuente
7

Creo que puedo responder a esta pregunta para el caso específico de una biblioteca criptográfica escrita en Go --- es fácil, y en absoluto hipotética, porque ya existe una paquete TLS, crypto/tls en Go que no depende de ninguna biblioteca externa.

Mientras que con respecto a los típicos desbordamientos de búfer, Go idiomático es mucho más seguro que C tradicional, Go ofrece al desarrollador entusiasta muchas opciones para eludirlo, como imitar la aritmética de punteros de C a través de unsafe.pointer . Uno se pregunta si podría acordar no usar código frágil en una pieza crítica de software.

La criptografía, por supuesto, es exactamente el tipo de software que utiliza un código tan frágil, por buenas razones. Después de todo, las comparaciones de tiempo constante implementadas en el paquete Go crypto.subtle sí requieren y tienen, de manera similar, extremas advertencias sobre el uso cuidadoso como las de unsafe . La única pregunta que queda, realmente, es si algún error aún puede sobrevivir en ese entorno.

Por lo que puedo decir, Go implementa comparaciones constantes de tiempo de valores hash correctamente. No me he molestado en mirar si los hash complicados que involucran cajas S se calculan de manera constante: nadie se molestó en ponerlos en un paquete con nombres como subtle y advertencias sobre lo fácil que es romper cosas. , entonces estoy realmente dudoso.

Lo que sí comprobé es que la criptografía de curva elíptica no se implementa de forma constante en Go. Nadie parece siquiera haber considerado intentarlo en lo más mínimo: la implementación llama a muchas funciones enteras de longitud arbitraria que ni siquiera están diseñadas para el uso criptográfico y, de hecho, utilizan algoritmos de tiempo no constante. Cuando recientemente se demostró que este tipo de canal lateral de sincronización también ocurre en OpenSSL en una arquitectura, fue lo suficientemente bueno para a documento que demuestra el compromiso de la clave privada .

La misma situación continua existe en Go, en todas las arquitecturas. Y, bueno, realmente no parece que a nadie le importen detalles como el trabajo de criptografía; el foco está solo en la legibilidad y la velocidad (bueno, y funciona de la manera en que el usuario casual y algunas pruebas de unidad son engañados por él, supongo). Después de todo, ¿por qué incluso se molestaría en elegir un algoritmo adecuado cuando el lenguaje ya lo mantiene a salvo de la mayoría de las formas de introducir desbordamientos de búfer, y al elegir un algoritmo correcto se corre el riesgo de arruinar la afirmación de Google de que TLS se ha vuelto computacionalmente barato? Dejando de lado el sarcasmo, hacer que el lenguaje responsable de detectar nuestros errores solo significará que todos los errores que el lenguaje no puede detectar para nosotros seguirán estando allí.

Finalmente, las bibliotecas como OpenSSL tienen las ventajas de que las correcciones de errores oportunas son una posibilidad razonable. Si bien, en principio, lo mismo se aplica a los paquetes Go, una vez que algo se convierte en parte del núcleo de Go, como el paquete TLS, se ve afectado por el calendario de lanzamiento. Obviamente, eso puede interferir con el despliegue oportuno de correcciones de errores. Supongo que el Go-1.3, que se entregará este verano, no solucionará el problema de la temporización del ECC, incluso si fuera reconocido como el problema crítico, dada la congelación de funciones.

    
respondido por el pyramids 08.04.2014 - 23:55
fuente
3

Data Tainting se implementó en Netscape JavaScript (navegador 3 y en el servidor en Enterprise Server) en respuesta a descubrimientos bastante tempranos sobre la naturaleza de la seguridad en Internet. Todas las entradas provenientes del usuario se consideraron contaminadas, a menos que se borrara la bandera, y la bandera contaminada se propague a través de las operaciones en los datos (por lo que el resultado de combinar datos y datos contaminados se considera contaminado). Como resultado, los datos contaminados siempre se pueden verificar.

Esto fue hace más de 10 años . Me sorprende que esto no haya encontrado su camino en los lenguajes administrados convencionales como Java o C # (o la implementación de JavaScript de cualquier otra persona).

Si combinó el análisis de corrupción de datos en tiempo de compilación con un modelo de memoria segura (código administrado, o al menos verificable), de todos modos se quedarían con errores lógicos, pero habría eliminado categorías enteras de ataques. De un golpe, incluyendo ambos factores contribuyentes a este.

    
respondido por el piers7 10.04.2014 - 16:23
fuente

Lea otras preguntas en las etiquetas