Fuzzing style 1989

Con el inicio de 2019, es bueno recordar el pasado y pensar en el futuro. Miremos hacia atrás 30 años y reflexionemos sobre los primeros artículos científicos sobre fuzzing: “Un estudio empírico sobre la confiabilidad de las utilidades de UNIX” y el trabajo posterior de 1995 “Revision of Fuzzing” del mismo autor Barton Miller .

En este artículo, intentaremos encontrar errores en las versiones modernas de Ubuntu Linux, utilizando las mismas herramientas que en los trabajos originales de fuzzing. Debe leer los documentos originales no solo para el contexto, sino también para su comprensión. Resultó ser muy profético con respecto a vulnerabilidades y hazañas en las próximas décadas. Los lectores atentos pueden notar la fecha de publicación del artículo original: 1990. Aún más atento notará los derechos de autor en los comentarios de la fuente: 1989.

Breve reseña


Para aquellos que no han leído los documentos (aunque esto realmente debería hacerse), esta sección contiene un breve resumen y algunas citas seleccionadas.

El programa fuzzing genera secuencias aleatorias de caracteres, con la capacidad de generar solo caracteres imprimibles o no imprimibles. Utiliza un cierto valor inicial (semilla), lo que garantiza resultados reproducibles, que a menudo carecen de fuzzers modernos. Se ejecuta un conjunto de scripts en los programas probados y verifica la presencia de volcados básicos. Los bloqueos se detectan manualmente. Los adaptadores proporcionan entrada aleatoria para programas interactivos (artículo de 1990), servicios de red (1995) y aplicaciones gráficas X (1995).

Un artículo de 1990 probó cuatro arquitecturas de procesador (i386, CVAX, Sparc, 68020) y cinco sistemas operativos (4.3 BSD, SunOS, AIX, Xenix, Dynix). En un artículo de 1995, una elección similar de plataformas. En el primer artículo, el 25-33% de las utilidades fallan, dependiendo de la plataforma. En un artículo posterior, estos números varían del 9% al 33%, con GNU (en SunOS) y Linux con el menor porcentaje de bloqueos.

Un artículo de 1990 concluyó que 1) los programadores no verifican los límites de la matriz o los códigos de error, 2) las macros hacen que sea difícil leer y depurar el código, y 3) C es muy inseguro. La función extremadamente insegura gets funciones y se ha mencionado especialmente el tipo de sistema C. Durante las pruebas, los autores encontraron vulnerabilidades en la cadena de formato años antes de su explotación masiva. El artículo concluye con una encuesta a los usuarios sobre la frecuencia con la que corrigen errores o los denuncian. Resultó que informar errores era difícil y había poco interés en solucionarlos.

Un artículo de 1995 menciona el software de código abierto y analiza por qué tiene menos errores. Cita:

Cuando investigamos las causas de las fallas, apareció un fenómeno perturbador: muchos de los errores (alrededor del 40%) que se informaron en 1990 todavía están presentes en su forma exacta en 1995. ...

Los métodos utilizados aquí son simples y en su mayoría automatizados. Es difícil entender por qué los desarrolladores no usan esta fuente fácil y gratuita para aumentar la confiabilidad.

Solo en 15-20 años, la técnica de fuzzing se convertirá en una práctica estándar para los grandes vendedores.

También me parece que esta declaración de 1990 prevé eventos futuros:

A menudo, el estilo lacónico de programación C se lleva al extremo, la forma prevalece sobre la función correcta. La posibilidad de un desbordamiento en el búfer de entrada es un agujero de seguridad potencial, como mostró el reciente gusano de Internet .

Metodología de prueba


Afortunadamente, 30 años después, el Dr. Barton todavía proporciona el código fuente completo, los scripts y los datos para reproducir sus hallazgos : un ejemplo encomiable que otros investigadores deberían seguir. Los scripts funcionan sin problemas, y la herramienta fuzzing solo requirió cambios menores para compilar y ejecutar.

Para estas pruebas, utilizamos scripts y entradas del repositorio básico fuzz-1995 , porque existe la última lista de aplicaciones probadas . Según README , aquí están las mismas entradas aleatorias que en el estudio original. Los resultados a continuación para Linux moderno se obtienen exactamente en el mismo código fuzzing y datos de entrada que en los artículos originales. Solo la lista de utilidades para las pruebas ha cambiado.

Cambios de utilidad durante 30 años.


Obviamente, ha habido algunos cambios en los paquetes de software de Linux en los últimos 30 años, aunque algunas utilidades probadas han continuado su pedigrí durante décadas. Cuando fue posible, tomamos versiones modernas de los mismos programas de un artículo de 1995. Algunos programas ya no están disponibles, los reemplazamos. Justificación para todos los reemplazos:

  • cfecc1 : Equivalente al preprocesador C del artículo de 1995.
  • dbxgdb : Equivalente al depurador de 1995.
  • ditroffgroff : ditroff ya no está disponible.
  • dtblgtbl : Equivalente a GNU Troff de la antigua utilidad dtbl .
  • lispclisp : la implementación estándar de lisp.
  • moreless : ¡Menos es más!
  • prologswipl : hay dos opciones para prolog: SWI Prolog y GNU Prolog. SWI Prolog es preferible porque es una implementación más antigua y más completa.
  • awkgawk : versión GNU de awk .
  • ccgcc : el compilador estándar de C.
  • compressgzip : GZip es el descendiente conceptual de la antigua utilidad de compress Unix.
  • lintsplint : lint reescrita bajo la licencia GPL.
  • /bin/mail/usr/bin/mail : utilidad equivalente de una manera diferente.
  • f77fort77 : Hay dos variaciones del compilador Fortan77: GNU Fortran y Fort77. El primero se recomienda para Fortran 90 y el segundo para el soporte de Fortran77. El programa f2c apoya activamente; su lista de cambios se ha mantenido desde 1989.

Resultados


La técnica de fuzzing de 1989 todavía encuentra errores en 2018. Pero hay algo de progreso.

Para medir el progreso, necesitas alguna base. Afortunadamente, dicho marco existe para las utilidades de Linux. Aunque Linux no existía en el momento del artículo original en 1990, una segunda prueba en 1995 lanzó el mismo código fuzzing en las utilidades de la distribución Slackware 2.1.0 de 1995. Los resultados correspondientes se dan en la tabla 3 del artículo de 1995 (p. 7-9) . En comparación con los competidores comerciales, GNU / Linux se ve muy bien:

El porcentaje de fallas en la utilidad en la versión gratuita de Linux de UNIX fue el segundo más alto: 9%.

Entonces, comparemos las utilidades de Linux de 1995 y 2018 con las herramientas fuzzing de 1989:

Ubuntu 18.10 (2018)Ubuntu 18.04 (2018)Ubuntu 16.04 (2016)Ubuntu 14.04 (2014)Slackware 2.1.0 (1995)
Choques1 (f77)1 (f77)2 (f77, ul)2 (swipl, f77)4 (ul, flex, sangría, gdb)
Se congela1 (hechizo)1 (hechizo)1 (hechizo)2 (hechizo, unidades)1 (etiquetas)
Total probado8181818155
Fallos / congelamientos,%2%2%4%5%9%

Sorprendentemente, la cantidad de bloqueos y bloqueos de Linux sigue siendo mayor que cero, incluso en la última versión de Ubuntu. Entonces, f77 llama al programa f2c con un error de segmentación, y el programa de spell cuelga en dos versiones de la entrada de prueba.

Que errores


Pude encontrar manualmente la causa raíz de algunos errores. Algunos resultados, como un error glibc, fueron inesperados, mientras que otros, como sprintf con un buffer de tamaño fijo, fueron predecibles.

Fracaso ul


El error en ul es en realidad un error en glibc. En particular, se informó aquí y aquí (otra persona lo encontró en ul ) en 2016. Según el rastreador de errores, el error aún no se ha solucionado. Dado que el error no se puede reproducir en Ubuntu 18.04 y versiones posteriores, se corrige en el nivel de distribución. A juzgar por los comentarios sobre el rastreador de errores, el problema principal puede ser muy grave.

Choque f77


El programa f77 viene en el paquete fort77, que es un script de shell alrededor de f2c , el traductor fuente de Fortran77 a C. La depuración de f2c muestra que ocurre una falla cuando la función errstr imprime un mensaje de error que es demasiado largo. El código fuente de f2c muestra que la función sprintf se usa para escribir una cadena de longitud variable en un búfer de tamaño fijo:

 errstr(const char *s, const char *t) #endif { char buff[100]; sprintf(buff, s, t); err(buff); } 

Parece que este código se ha conservado desde la creación de f2c . El programa tiene una historia de cambios desde al menos 1989. En 1995, cuando se volvió a difuminar, el compilador Fortran77 no se probó, de lo contrario, el problema se habría encontrado antes.

Hechizo de congelación


Un gran ejemplo de punto muerto clásico. spell delegados de spell ispell través de una tubería. spell lee el texto línea por línea y produce un registro de bloqueo del tamaño de la línea en ispell . Sin embargo, ispell lee un máximo de BUFSIZ/2 bytes a la vez (4096 bytes en mi sistema) y emite un registro de bloqueo para garantizar que el cliente haya recibido los datos de validación que se han procesado hasta ahora. Dos entradas de prueba diferentes forzaron al spell a escribir una cadena de más de 4096 caracteres para ispell , lo que resultó en un punto muerto: el spell espera a que ispell lea toda la cadena, mientras que el ispell espera al spell confirmar que ha leído las correcciones de ortografía originales.

Unidades colgantes


A primera vista, parece que hay una condición de bucle infinito. El bloqueo parece estar en libreadline y no en units , aunque las versiones más nuevas de units no sufren este error. El registro de cambios indica que se agregó un filtro de entrada que podría solucionar este problema accidentalmente. Sin embargo, una investigación exhaustiva de las razones está más allá del alcance de este blog. Quizás la forma de colgar libreadline sigue ahí.

Swipl crash


En aras de la exhaustividad, quiero mencionar el fallo swipl , aunque no lo swipl fondo, ya que el error se ha solucionado durante mucho tiempo y parece ser de bastante alta calidad. La falla es en realidad una declaración (es decir, una que nunca debería suceder) que se llama cuando se convierten los caracteres:

[Thread 1] pl-fli.c:2495: codeToAtom: Assertion failed: chrcode >= 0
C-stack trace labeled "crash":
[0] __assert_fail+0x41
[1] PL_put_term+0x18e
[2] PL_unify_text+0x1c4


El bloqueo siempre es malo, pero al menos aquí el programa puede informar un error, bloqueándose temprano y en voz alta.

Conclusión


En los últimos 30 años, el difuminado se ha mantenido como una forma simple y confiable de encontrar errores. Aunque se está realizando una investigación activa en esta área , incluso el difusor de hace 30 años encuentra con éxito errores en las modernas utilidades de Linux.

El autor de los artículos originales predijo los problemas de seguridad que C causaría en las próximas décadas. Argumenta convincentemente que el código inseguro es demasiado fácil de escribir en C y debe evitarse si es posible. En particular, los artículos demuestran que los errores aparecen incluso con la fase más simple, y tales pruebas deben incluirse en la práctica estándar de desarrollo de software. Lamentablemente, este consejo no se ha seguido durante décadas.

Espero que hayan disfrutado esta retrospectiva de 30 años. Espere el próximo artículo "Fuzzing in 2000", donde examinaremos qué tan sólidas son las aplicaciones de Windows 10 en comparación con sus equivalentes de Windows NT / 2000 cuando se prueban con fuzzer . Creo que la respuesta es predecible.

Source: https://habr.com/ru/post/es435484/


All Articles