¿El uso de un compilador de C obsoleto es un riesgo para la seguridad?

140

Tenemos algunos sistemas de construcción en producción que a nadie le importan y estas máquinas ejecutan versiones antiguas de GCC como GCC 3 o GCC 2.

Y no puedo persuadir a la gerencia para que lo actualice a uno más reciente: dicen, "si no está roto, no lo arregles".

Dado que mantenemos una base de código muy antigua (escrita en los años 80), este código C89 se compila muy bien en estos compiladores.

Pero no estoy seguro de que sea una buena idea usar estas cosas viejas.

Mi pregunta es:

¿El uso de un compilador de C antiguo puede comprometer la seguridad del programa compilado?

ACTUALIZAR:

Visual Studio 2008 crea el mismo código para los destinos de Windows, y MSVC aún no es compatible con C99 o C11 (no sé si el MSVC más nuevo lo hace), y puedo compilarlo en mi caja de Linux usando el último GCC. Entonces, si colocáramos un GCC más nuevo, probablemente se construiría tan bien como antes.

Calmarius
fuente
5
Pregunta interesante - esto también podría valer la pena una lectura rápida - developers.slashdot.org/story/13/10/29/2150211/… .. por lo que los compiladores más nuevos también pueden comprometer la seguridad al optimizar.
Neil
6
¿Esas versiones antiguas de gcc admiten la compilación en PIC / PIE para ASLR? ¿Admiten canarios de pila? W ^ X (NX)? De lo contrario, la falta de mitigaciones para las vulnerabilidades es una buena razón para actualizar.
EOF
12
Solo mirar las advertencias de gcc 4.x puede revelar inmediatamente una gran cantidad de agujeros de seguridad existentes que no sabía que tenía.
OrangeDog
7
@OrangeDog: ¿Por qué gcc 4.x? gcc6 es la serie de versiones actual, y gcc 5 ha existido por un tiempo. Pero sí, solucionar cualquier problema identificado por -O3 -Wall -Wextra -fsanitize=undefinedgcc moderno y clang debería ayudar.
Peter Cordes
4
@OrangeDog GCC se ha dirigido a los números de versión de marketing. GCC 5 merecía un aumento de versión mayor, porque cambiaron los estándares predeterminados de C y C ++ y la ABI de libstdc ++. GCC 6 debería haberse llamado 5.1.
zwol

Respuestas:

103

En realidad, yo diría lo contrario.

Hay una serie de casos en los que el estándar C no define el comportamiento, pero es obvio lo que sucedería con un "compilador tonto" en una plataforma determinada. Casos como permitir que un entero con signo se desborde o acceder a la misma memoria a través de variables de dos tipos diferentes.

Las versiones recientes de gcc (y clang) han comenzado a tratar estos casos como oportunidades de optimización sin importarles si cambian el comportamiento del binario en la condición de "comportamiento indefinido". Esto es muy malo si su base de código fue escrita por personas que trataron a C como un "ensamblador portátil". Con el paso del tiempo, los optimizadores han comenzado a buscar fragmentos de código cada vez más grandes al hacer estas optimizaciones, lo que aumenta la posibilidad de que el binario termine haciendo algo diferente de "lo que haría un binario creado por un compilador tonto".

Hay modificadores de compilación para restaurar el comportamiento "tradicional" (-fwrapv y -fno-estricto-aliasing para los dos que mencioné anteriormente), pero primero debes conocerlos.

Si bien, en principio, un error del compilador podría convertir el código compatible en un agujero de seguridad, consideraría que el riesgo de esto es insignificante en el gran esquema de las cosas.

enchufar
fuente
13
Pero este argumento funciona en ambos sentidos. Si un compilador tiene un "comportamiento indefinido" predecible, podría decirse que puede ser más fácil hacer un uso malintencionado de él ...
André
18
@Andre El código compilado tiene un comportamiento indefinido predecible de todos modos. Es decir, una vez que se ha compilado el código, cualquier comportamiento impredecible ahora es predecible, en esa versión compilada en particular.
user253751
6
people who treated C like a "portable assembler"¿No es lo que es C?
Máximo
10
@Max Esta respuesta advierte precisamente sobre el hecho de que la noción de "ensamblador portátil" está al menos desactualizada en la práctica, debido a los optimizadores modernos. Y yo diría que, para empezar, nunca fue conceptualmente correcto.
Theodoros Chatzigiannakis
7
Aquí no hay simpatía por aquellos que confían en un comportamiento indefinido y luego comienzan a cosechar lo que sembraron. Esto no significa que los compiladores más nuevos sean intrínsecamente menos seguros; significa que el código no compatible fue una bomba de tiempo. La culpa debe repartirse en consecuencia.
underscore_d
52

Hay riesgos en ambos cursos de acción.


Los compiladores más antiguos tienen la ventaja de la madurez, y todo lo que se rompió en ellos probablemente (pero no hay garantía) se ha solucionado con éxito.

En este caso, un nuevo compilador es una fuente potencial de nuevos errores.


Por otro lado, los compiladores más nuevos vienen con herramientas adicionales :

  • GCC y Clang ahora cuentan con desinfectantes que pueden instrumentar el tiempo de ejecución para detectar comportamientos indefinidos de varios tipos (Chandler Carruth, del equipo del compilador de Google, afirmó el año pasado que espera que hayan alcanzado una cobertura completa)
  • Clang, al menos, presenta el endurecimiento , por ejemplo, Control Flow Integrity se trata de detectar altas tomas del flujo de control, también hay implementos de endurecimiento para proteger contra ataques de aplastamiento de la pila (separando la parte de flujo de control de la pila de la parte de datos) ; Las funciones de endurecimiento generalmente tienen una sobrecarga baja (<1% de sobrecarga de CPU)
  • Clang / LLVM también está trabajando en libFuzzer , una herramienta para crear pruebas unitarias de fuzzing instrumentadas que exploran el espacio de entrada de la función bajo prueba de manera inteligente (ajustando la entrada para tomar rutas de ejecución aún no exploradas)

Instrumentar su binario con los desinfectantes (Address Sanitizer, Memory Sanitizer o Undefined Behavior Sanitizer) y luego confundirlo (usando American Fuzzy Lop, por ejemplo) ha descubierto vulnerabilidades en una serie de softwares de alto perfil, consulte, por ejemplo, este artículo de LWN.net .

Esas nuevas herramientas, y todas las herramientas futuras, son inaccesibles para usted a menos que actualice su compilador.

Al permanecer en un compilador de poca potencia, está metiendo la cabeza en la arena y cruzando los dedos para que no se encuentre ninguna vulnerabilidad. Si su producto es un objetivo de alto valor, le insto a que lo reconsidere.


Nota: incluso si NO actualiza el compilador de producción, es posible que desee utilizar un nuevo compilador para comprobar la vulnerabilidad de todos modos; tenga en cuenta que, dado que son compiladores diferentes, las garantías se reducen.

Matthieu M.
fuente
1
+1 por molestarse en mencionar casos en los que los nuevos compiladores pueden ser más seguros, en lugar de apilarse en el tren 'b-pero mi viejo UB' de las otras respuestas. esto se suma a las muchas otras mejoras que ofrecen que no están directamente relacionadas con la seguridad, pero brindan aún más ímpetu para ser razonablemente modernos.
underscore_d
Aunque se siente como defender la "seguridad a través de la oscuridad"; los errores que afectan a los compiladores antiguos son conocidos y públicos. Si bien estoy de acuerdo en que los nuevos compiladores introducirán errores, estos errores aún no son públicos como los de las versiones anteriores, lo cual es algo de seguridad si actualiza la aplicación con frecuencia.
The6P4C
Chandler Carruth es tan lindo y habla de cosas tan maravillosas. Me casaría con él si pudiera.
Daniel Kamil Kozar
46

Su código compilado contiene errores que podrían explotarse. Los errores provienen de tres fuentes: errores en su código fuente, errores en el compilador y bibliotecas, y comportamiento indefinido en su código fuente que el compilador convierte en un error. (El comportamiento indefinido es un error, pero aún no es un error en el código compilado. Por ejemplo, i = i ++; en C o C ++ es un error, pero en su código compilado puede aumentar i en 1 y estar bien, o establecer yo a un poco de basura y ser un error).

La tasa de errores en su código compilado es presumiblemente baja debido a las pruebas y la corrección de errores debido a los informes de errores de los clientes. Por lo tanto, es posible que haya habido una gran cantidad de errores inicialmente, pero eso se ha ido.

Si actualiza a un compilador más nuevo, puede perder errores que fueron introducidos por errores del compilador. Pero estos errores serían todos errores que, según tu conocimiento, nadie encontró y nadie explotó. Pero el nuevo compilador puede tener errores por sí solo y, lo que es más importante, los compiladores más nuevos tienen una tendencia más fuerte a convertir el comportamiento indefinido en errores en el código compilado.

Por lo que tendrá muchos errores nuevos en su código compilado; todos los errores que los piratas informáticos podrían encontrar y explotar. Y a menos que realice muchas pruebas y deje su código a los clientes para que encuentren errores durante mucho tiempo, será menos seguro.

gnasher729
fuente
6
Entonces, en otras palabras ... no hay una manera fácil de saber qué problemas presenta el compilador, y al cambiar todo lo que hace es obtener un conjunto diferente de problemas desconocidos.
Jeremy Kato
1
@JeremyKato: bueno, hay algunos casos en los que también tienes un conjunto diferente de problemas conocidos. No estoy seguro de qué fallas de seguridad conocidas hay en el compilador en sí, pero por el bien de un ejemplo concreto, suponga que actualizar a un nuevo compilador significa también poder tomar la última libc (mientras que usar la antigua significa no poder para hacer esto), entonces sabría que está arreglando esta falla en getaddrinfo(): access.redhat.com/articles/2161461 . Ese ejemplo no es en realidad una falla de seguridad del compilador, pero durante más de 10 años seguramente habrá algunas fallas corregidas conocidas.
Steve Jessop
2
Je, en realidad, ese defecto se introdujo en 2008, por lo que el interrogador podría estar a salvo de él. Pero mi punto no es sobre ese ejemplo en particular, es que existen errores conocidos que una vieja cadena de herramientas pondrá en su código. Entonces, cuando actualiza, es cierto que presenta un nuevo conjunto de incógnitas, pero eso no es todo lo que hace . Básicamente, solo tiene que adivinar si está "más seguro" dejando el defecto crítico conocido que corrige la cadena de herramientas más reciente, o asumiendo las consecuencias desconocidas de tirar los dados nuevamente en todo el comportamiento indefinido en su propio código.
Steve Jessop
19

Si no está roto, no lo arregles

Su jefe suena correcto al decir esto, sin embargo, lo más importante factor es salvaguardar las entradas, salidas y desbordamientos de búfer. La falta de esos es invariablemente el eslabón más débil de la cadena desde ese punto de vista, independientemente del compilador utilizado.

Sin embargo, si la base del código es antigua, y se trabajó para mitigar las debilidades del K&R C utilizado, como la falta de seguridad de tipos, fgets inseguros, etc., sopese la pregunta " ¿Actualizaría el compilador a un C99 más moderno? / ¿Los estándares C11 lo rompen todo? "

Siempre que haya un camino claro para migrar a los estándares C más nuevos, lo que podría inducir efectos secundarios, lo mejor sería intentar una bifurcación del código base anterior, evaluarlo y realizar verificaciones de tipo adicionales, verificaciones de cordura y determinar si se actualiza a el compilador más nuevo tiene algún efecto en los conjuntos de datos de entrada / salida.

Luego puede mostrárselo a su jefe, " Aquí está el código base actualizado, refactorizado, más en línea con los estándares C99 / C11 aceptados por la industria ... ".

Esa es la apuesta en la que habría que sopesar, con mucho cuidado , la resistencia al cambio podría mostrarse allí en ese entorno y podría negarse a tocar las cosas más nuevas.

EDITAR

Me senté unos minutos, me di cuenta de esto, el código generado por K&R podría estar ejecutándose en una plataforma de 16 bits, lo más probable es que la actualización a un compilador más moderno podría romper la base del código, estoy pensando en términos de arquitectura, se generaría un código de 32 bits , esto podría tener efectos secundarios divertidos en las estructuras utilizadas para los conjuntos de datos de entrada / salida, que es otro gran factor que se debe sopesar cuidadosamente.

Además, dado que OP ha mencionado el uso de Visual Studio 2008 para construir la base de código, el uso de gcc podría inducir la incorporación al entorno de MinGW o Cygwin, lo que podría tener un cambio de impacto en el entorno, a menos que el objetivo sea Linux, entonces sería Vale la pena intentarlo, es posible que tenga que incluir cambios adicionales en el compilador para minimizar el ruido en la base del código K&R antiguo, la otra cosa importante es realizar muchas pruebas para asegurarse de que no se rompa ninguna funcionalidad, puede resultar un ejercicio doloroso.

t0mm13b
fuente
Visual Studio 2008 crea el mismo código para los destinos de Windows, y MSVC no es compatible con C99 o C11 todavía (no sé si el MSVC más nuevo lo hace), y puedo compilarlo en mi caja de Linux usando el último GCC. Entonces, si simplemente colocáramos un GCC más nuevo, probablemente se construiría tan bien como antes.
Calmarius
@Calmarius, gracias por el aviso, tal vez sea mejor editar su pregunta para incluir el comentario, Eso es importante :) Y debería haber estado allí; D
t0mm13b
@Calmarius ha editado mi respuesta, que es mi pensamiento sobre la pregunta recién actualizada.
t0mm13b
2
"Podría ejecutarse en una plataforma de 16 bits, lo más probable es que la actualización a un compilador más moderno podría romper la base del código, estoy pensando en términos de arquitectura, código de 32 bits" No creo que la pregunta sea sobre la migración del código a una nueva implementación definida parámetros.
Pascal Cuoq
Convenido. Es posible que un error del compilador cree una vulnerabilidad en tiempo de ejecución. Pero es mucho más probable que el código contenga vulnerabilidades en tiempo de ejecución debido a cosas como saturaciones de búfer y pila. Entonces, cuando invierte tiempo en hacer que esta base de código sea más segura, debe invertirlo en hacer cosas como verificar la longitud de las cadenas de entrada para asegurarse de que no excedan los límites de su programa. Obtener un compilador más nuevo no ayudará mucho. Reescribir el código desde cero en un lenguaje con cadenas nativas y objetos de matriz ayudará mucho. Pero tu jefe no pagará por eso.
O. Jones
9

¿El uso de un compilador C antiguo puede comprometer la seguridad del programa compilado?

Por supuesto que puede, si el compilador antiguo contiene errores conocidos que usted sabe que afectarían su programa.

La pregunta es ¿verdad? Para estar seguro, tendría que leer todo el registro de cambios desde su versión hasta la fecha actual y verificar cada error corregido a lo largo de los años.

Si no encuentra evidencia de errores del compilador que puedan afectar su programa, actualizar GCC por el simple hecho de hacerlo parece un poco paranoico. Debería tener en cuenta que las versiones más recientes pueden contener errores nuevos que aún no se han descubierto. Recientemente se realizaron muchos cambios con el soporte de GCC 5 y C11.

Dicho esto, es muy probable que el código escrito en los años 80 ya esté lleno hasta el borde con agujeros de seguridad y dependencia de un comportamiento mal definido, sin importar el compilador. Estamos hablando de C preestándar aquí.

Lundin
fuente
6
No creo que sea paranoia; Creo que el OP está tratando de inventar razones para convencer a su jefe. Probablemente el OP realmente quiera un nuevo compilador porque hacen un mejor ensamblaje (incluida la optimización de archivos cruzados con LTO), tienen diagnósticos / advertencias más útiles y permiten características y sintaxis del lenguaje moderno. (por ejemplo, C11 stdatomic).
Peter Cordes
9

Existe un riesgo de seguridad en el que un desarrollador malintencionado puede colarse por la puerta trasera a través de un error del compilador. Dependiendo de la cantidad de errores conocidos en el compilador en uso, la puerta trasera puede parecer más o menos discreta (en cualquier caso, el punto es que el código es correcto, incluso si es complicado, en el nivel fuente. Revisiones y pruebas del código fuente usando un compilador sin errores no encontrará la puerta trasera, porque la puerta trasera no existe en estas condiciones). Para puntos de negación adicionales, el desarrollador malintencionado también puede buscar errores del compilador previamente desconocidos por su cuenta. Nuevamente, la calidad del camuflaje dependerá de la elección de los errores del compilador encontrados.

Este ataque se ilustra en el programa sudo en este artículo . bcrypt escribió un excelente seguimiento para los minificadores de Javascript .

Además de esta preocupación, la evolución de los compiladores de C ha sido para explotar un comportamiento indefinido más y más y más agresiva, por lo que el código C de edad que fue escrito de buena fe en realidad sería segura compilado más con un compilador C del tiempo, o recopilada en el -O0 (pero algunas optimizaciones nuevas de explotación de UB que rompen el programa se introducen en nuevas versiones de compiladores incluso en -O0 ).

Pascal Cuoq
fuente
7

Es posible que los compiladores más antiguos no tengan protección contra ataques de piratería conocidos. La protección contra la rotura de pilas, por ejemplo, no se introdujo hasta GCC 4.1 . Así que sí, el código compilado con compiladores más antiguos puede ser vulnerable en formas contra las que los compiladores más nuevos protegen.

DrMcCleod
fuente
6

Otro aspecto del que preocuparse es el desarrollo de nuevo código .

Los compiladores más antiguos pueden tener un comportamiento diferente para algunas características del lenguaje de lo que está estandarizado y esperado por el programador. Este desajuste puede ralentizar el desarrollo e introducir errores sutiles que se pueden aprovechar.

Los compiladores más antiguos ofrecen menos funciones (¡incluidas funciones de lenguaje!) Y no optimizan tan bien. Los programadores solucionarán estas deficiencias, por ejemplo, reimplementando las funciones faltantes o escribiendo código inteligente que es oscuro pero se ejecuta más rápido, creando nuevas oportunidades para la creación de errores sutiles.


fuente
5

No

La razón es simple, el compilador antiguo puede tener errores y exploits antiguos, pero el nuevo compilador tendrá errores y exploits nuevos.

No está "arreglando" ningún error actualizando a un nuevo compilador. Su cambio de errores y exploits antiguos por nuevos errores y exploits.

coteyr
fuente
3
Esto parece muy simplista: el nuevo compilador puede tener sus debilidades, pero esperaría que fueran menores que en el compilador antiguo, y es probable que detecte varias vulnerabilidades de código que se han dado a conocer desde entonces.
PJTraill
Pero el nuevo compilador puede tener nuevas debilidades desconocidas. El compilador por sí solo no es un riesgo de seguridad que deba actualizarse. No está reduciendo su superficie. Está negociando un conjunto conocido de problemas por un conjunto desconocido.
Coteyr
Las herramientas para ayudar a encontrar errores han mejorado enormemente desde los primeros días de GCC, y estas herramientas (análisis estático, análisis / desinfectantes dinámicos de código instrumentado, fuzzers, etc.) también se han aplicado al código del compilador para ayudar a mejorar la calidad. Fue mucho más difícil encontrar todas las clases de errores en la era GCC 2. Comparación de los errores del compilador con las versiones: consulte la página 7: cs.utah.edu/~regehr/papers/pldi11-preprint.pdf GCC 4.5 y LLVM 2.8 (el último en publicación) tienen la menor cantidad de errores por fuzzing.
Jetski S-type
2

Bueno, existe una mayor probabilidad de que cualquier error en el compilador antiguo sea bien conocido y documentado en lugar de usar un compilador nuevo, por lo que se pueden tomar acciones para evitar esos errores codificando alrededor de ellos. Entonces, de alguna manera, eso no es suficiente como argumento para actualizar. Tenemos las mismas discusiones donde yo trabajo, usamos GCC 4.6.1 en una base de código para software embebido y hay una gran renuencia (entre la administración) a actualizar al último compilador por temor a errores nuevos e indocumentados.

AndersK
fuente
0

Tu pregunta se divide en dos partes:

  • Explícito: "¿Existe un mayor riesgo al usar el compilador más antiguo?" (Más o menos como en su título)
  • Implícito: "¿Cómo puedo persuadir a la gerencia para que actualice?"

Quizás pueda responder tanto encontrando un defecto explotable en su base de código existente como mostrando que un compilador más nuevo lo habría detectado. Por supuesto, su administración puede decir "lo encontró con el compilador antiguo", pero puede señalar que costó un esfuerzo considerable. O lo ejecuta a través del nuevo compilador para encontrar la vulnerabilidad, luego explótelo, si puede / tiene permiso para compilar el código con el nuevo compilador. Es posible que desee la ayuda de un pirata informático amigable, pero eso depende de confiar en ellos y poder / permitirles mostrarles el código (y usar el nuevo compilador).

Pero si su sistema no está expuesto a piratas informáticos, tal vez debería estar más interesado en saber si una actualización del compilador aumentaría su efectividad: el análisis de código de MSVS 2013 a menudo encuentra errores potenciales mucho antes que MSVS 2010, y es más o menos compatible con C99 / C11. - no estoy seguro si lo hace oficialmente, pero las declaraciones pueden seguir a las declaraciones y puede declarar variables en for-loops.

PJTraill
fuente