domingo, 16 de agosto de 2009

Cómo funciona el exploit sock_sendpage()

 
Tavis Ormandy y Julien Tinnes, del equipo de seguridad de Google, han descubierto un bug en el kernel Linux que nadie ha sido capaz de ver en los últimos ocho años. Este bug, que afecta a todas las versiones del kernel desde la 2.4.4 a la 2.4.37.4 y desde la 2.6.0 a la 2.6.30.4 permite escalar privilegios en el sistema. En este artículo vamos a analizar su funcionamiento.


Primer error:
Todo comienza en la función sock_sendpage() situada en net/socket.c. El problema reside en la llamada a sendpage() pues no se ha verificado que el puntero esté inicializado.


static ssize_t sock_sendpage(struct file *file, struct page *page,
              int offset, size_t size, loff_t *ppos, int more)
{
   struct socket *sock;
   int flags;

   sock = file->private_data;

   flags = !(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;
   if (more)
      flags |= MSG_MORE;

   return sock->ops->sendpage(sock, page, offset, size, flags);
}


A continuación, dejo una copia de la función sock_splice_read() que no tiene nada que ver ni con el bug ni con el exploit, pero nos sirve para ver como se podría haber evitado la vulnerabilidad.  En esta función se verifica el puntero a la función mediante unlikely() antes su llamada.


static ssize_t sock_splice_read(struct file *file, loff_t *ppos,
                 struct pipe_inode_info *pipe, size_t len,
            unsigned int flags)
{
   struct socket *sock = file->private_data;

   if (unlikely(!sock->ops->splice_read))
      return -EINVAL;

   return sock->ops->splice_read(sock, ppos, pipe, len, flags);
}


Segundo error:
Hemos visto que no se verifica si el puntero a sendpage() esta correctamente inicializado. Sera necesario ahora, encontrar situaciones en las que el puntero a la función sendpage() no se haya inicialiado.

Tavis Ormandy y Julien Tinnes en su investigación localizaron deficiencias en la incialización del puntero para la macro SOCKOPS_WRAP, definida en include/linux/net.h, que incluye PF_APPLETALK, PF_IPX, PF_IRDA, PF_X25 y PF_AX25. Tambien encontraron deficiencias en otros protocolos  como  PF_BLUETOOTH, PF_IUCV, PF_INET6 (con IPPROTO_SCTP), PF_PPPOX y PF_ISDN.


Veamos por ejemplo el caso del protocolo bluetooth. En net/bluetooth/bnep/sock.c nos encontramos con la estructura bnet_sock_ops en la que vemos que no se inicializa sendpage con sock_no_sendpage(), por lo que quedará apuntando a NULL.

static const struct proto_ops bnep_sock_ops = { 
        .family         = PF_BLUETOOTH,
        .owner          = THIS_MODULE,
        .release        = bnep_sock_release,
        .ioctl          = bnep_sock_ioctl,
#ifdef CONFIG_COMPAT
        .compat_ioctl   = bnep_sock_compat_ioctl,
#endif
        .bind           = sock_no_bind,
        .getname        = sock_no_getname,
        .sendmsg        = sock_no_sendmsg,
        .recvmsg        = sock_no_recvmsg,
        .poll           = sock_no_poll,
        .listen         = sock_no_listen,
        .shutdown       = sock_no_shutdown,
        .setsockopt     = sock_no_setsockopt,
        .getsockopt     = sock_no_getsockopt,
        .connect        = sock_no_connect,
        .socketpair     = sock_no_socketpair,
        .accept         = sock_no_accept,
        .mmap           = sock_no_mmap
};



El caso de SOCKOPS_WRAP es mas sutil, pues en net/ipx/af_ipx.c todo parece correcto. Podemos ver la inicialización en la última linea.


static const struct proto_ops SOCKOPS_WRAPPED(ipx_dgram_ops) = {
        .family         = PF_IPX,
        .owner          = THIS_MODULE,
        .release        = ipx_release,
        .bind           = ipx_bind,
        .connect        = ipx_connect,
        .socketpair     = sock_no_socketpair,
        .accept         = sock_no_accept,
        .getname        = ipx_getname,
        .poll           = datagram_poll,
        .ioctl          = ipx_ioctl,
#ifdef CONFIG_COMPAT
        .compat_ioctl   = ipx_compat_ioctl,
#endif
        .listen         = sock_no_listen,
        .shutdown       = sock_no_shutdown, /* FIXME: support shutdown */
        .setsockopt     = ipx_setsockopt,
        .getsockopt     = ipx_getsockopt,
        .sendmsg        = ipx_sendmsg,
        .recvmsg        = ipx_recvmsg,
        .mmap           = sock_no_mmap,
        .sendpage       = sock_no_sendpage,
};


Sin embargo, si estudiamos con detenimiento la macro SOCKOPS_WRAP en include/linux/net.h vemos que mietras otras llamadas se inicializan correctamente, no se realiza la inicialización de sendpage.
No pego el código por que es demasiado largo, consultar en include/linux/net.h y ver que la última inicialización es la de mmap(), olvidando sendpage().



Cómo explotarlo?
Lo primero que necesitaremos para explotar esta vulnerabilidad es una función que podamos ejecutar en espacio de usuario que use sock_sendpage(). Hay muchas funciones que lo hacen, una de ellas es sendfile().



Tavis Ormandy y Julien Tinnes y proponen como secuencia válida de llamadas para forzar el uso de sendpage(), la siguiente:

/* ... */
int fdin = mkstemp(template);
int fdout = socket(PF_PPPOX, SOCK_DGRAM, 0);

unlink(template);

ftruncate(fdin, PAGE_SIZE);

sendfile(fdout, fdin, NULL, PAGE_SIZE);
/* ... */



Cuando se ejecute la función sendpage(), si esta no está inicializada y apunta a NULL, el kernel intentará ejecutar las instrucciones que hay en la dirección 0. Podemos aprovechar esto mapeando la dirección 0 y almacenando allí las instrucciones que queremos que ejecute el kernel. Así, cuando se intente ejecutar sendpage() realmente se ejecutará nuestro código en espacio de kernel. Podremos escalar privilegios haciendo que el código almecando en la dirección 0 ponga nuestros uid a cero (root).

Este tipo de bugs son muy comunes en el kernel Linux y se conocen como bugs por 'NULL pointer dereference'. Ya vimos aquí uno de estos con el exploit vmsplice.


En las referencias dejo un ejemplo de exploit.




referencias:
- http://www.securityfocus.com/archive/1/505751
- http://blog.cr0.org/2009/08/linux-null-pointer-dereference-due-to.html
- exploit: http://www.frasunek.com/proto_ops.tgz

 

6 comentarios:

Unknown dijo...

Buen artículo. Muy didáctico y bien explicado! Espero que a nadie le dé por usarlo con "fines maléficos"...

Anónimo dijo...

Amigo, muy bueno el comentario:

tene cuidado de que no te plagien cambiando algunas palabras

http://www.delincuentedigital.com.ar/2009/09/explotando-sock_sendpage

Anónimo dijo...

amigo en algun lado ves que diga que yo soy el auto del exploit o del paper?
es una traduccion…ahora no encuentro de donde lo saque pero estaba en ingles..
gracias por comentar y la unica razon que aprobe tu comentario es para que veas que no tengo nada que ocultar…

Anónimo dijo...
Este comentario ha sido eliminado por el autor.
Anónimo dijo...

por cierto ..no hagas bardo al pedo y gracias por hacerle spam a mi blog gratis :D

ah..y yo borre de mi blog el link a este blog que nada tiene que ver...un saludo :)

dlerch dijo...

Hola,

Llevo años viendo plagios de mis artículos por ahí, así que ya no me molesta. Los blogs que copian
no suelen durar mucho.

En este caso, si hay plagio o no, no lo se. Pero no cuesta nada poner unas referencias. Si su artículo proviene de un artículo en inglés, pues eso, un enlace al mismo.

Lo que no cuela es eso de "... en algun lado ves que diga que yo soy el auto del exploit o del paper?"

Si el artículo esta en tu web, y no se indica lo contrario, será que es tuyo, no?