Cuando pequeños detalles desencadenan complejos problemas

Cuando pequeños detalles desencadenan complejos problemas

Hace más de un mes se divulgó una falla de seguridad en Linux que hasta el día de hoy se discute desde varios puntos de vista: La reacción de los desarrolladores, la forma en que se manejan estos problemas, y las decisiones de diseño que pueden ayudar a que éstas ocurran.

El problema en sí es bastante sencillo, y es fácil de entender para todo aquél que alguna vez haya tenido que programar en lenguajes cercanos a la máquina como es el caso del lenguaje C.  Uno de los problemas comunes es el uso de variables no inicializadas, específicamente referencias a la dirección 0, también conocida como null.

Esto ocurre cuando se usa una variable que debería apuntar a un objeto válido en memoria, pero al no ser inicializada apunta a la dirección 0 (null pointer dereference).  Esto podría provocar resultados indeterminados, pero en Linux se usa una característica del hardware para que este tipo de errores se detecte inmediatamente.  Lo que se hace es que en la dirección cero no se mapea ninguna página de memoria, por lo tanto cualquier intento de acceder a esa zona provocará una excepción de hardware indicando que no hay nada en esa ubicación, lo que el usuario ve como un Segmentation Fault.

Los programadores, confiando en este sistema de detección pueden asumir que estos casos serán manejados siempre de esta forma, con un resultado conocido.  Pero qué pasa si alguien efectivamente pone código en esa zona para que un error de este tipo no provoque una excepción de hardware, sino que la ejecución de código malicioso.

El kernel impide que esto se pueda hacer, estableciendo un límite inferior de dirección de memoria mapeable.  Pero cuando se da mayor privilegio al código, estos límites no son considerados, y es ahí en donde se puede abusar del sistema.

Un usuario mostró que este caso es posible, y que las referencias a null podrían ser abusadas si se lograba poner código en la zona prohibida.  La demostración la hizo con PulseAudio, que permite cargar bibliotecas indicadas por el usuario, si el ejecutable de PulseAudio se ejecuta con privilegios de root, como lo hacen algunas distribuciones, y se usa SELinux en donde los límites no son considerados, se puede cargar código que se activará cuando se use un puntero no inicializado.

Aunque parezca contradictorio, en este caso en particular, el sistema de seguridad SELinux originado en la National Security Agency (NSA) dejaba al sistema vulnerable frente a esta falla de seguridad.

Las correcciones no tardaron (tanto) en llegar, aunque este tipo de problemas son igual que los accidentes, se debe dar una compleja combinación de factores para que ocurran, pero eventualmente ocurrirán.

¿Qué es lo que falló?  ¿Las auditoría son insuficientes? ¿Problemas de diseño? Es la naturaleza imperfecta del humano, manifestada en el software.  Se generó una gran discusión respecto a aumentar los niveles de control y revisión, y es aquí en donde nuevamente Linus enseña algo importante en la lista de correo del kernel:

La buena memoria no se trata de recordar todo.  La buena memoria se trata de olvidar lo irrelevante, de tal forma que las cosas importantes permanezcan y se puedan recordar.  Pero el asunto es que: Sí, debes olvidar cosas. Y eso significa que vas a perder los detalles – pero se espera que se pierdan aquellos detalles que no son importantes.  Aquí las palabras claves son “se espera”.  La mayor parte del tiempo funciona, pero todos sabemos que a veces somos capaces de olvidar detalles que resultan ser cruciales después de todo.

Link: Null pointers, one month later (LWN.net)