Despues del revuelo ocasinado por el bug vmsplice() en el kernel Linux vamos a realizar un pequeño análisis de su funcionamiento. Para el que no lo sepa, la explotación del bug vmsplice permite a un usuario del sistema convertirse en root. Esto ocurre en los kernels que van desde la versión 2.6.17 a la 2.6.24.1.
Algunos exploits disponibles son:
http://www.milw0rm.com/exploits/5092
http://www.milw0rm.com/exploits/5093
Veamos un ejemplo con el segundo exploit:
$ uname -r
2.6.23
$ gcc exploit.c
$ ./a.out
-----------------------------------
Linux vmsplice Local Root Exploit
By qaaz
-----------------------------------
[+] addr: 0xc011481b
[+] root
$ id
uid=0(root) gid=0(root) grupos=501(user)
La llamada al sistema vmsplice:
vmsplice es una llamada al sistema que puede ser accedida desde espacio de usuario mediante syscall().
syscall(__NR_vmsplice, fd, iov, nr_segs, flags);
Encontramos su implementación en fs/splice.c, en las fuentes del kernel Linux:
asmlinkage long sys_vmsplice(
int fd, const struct iovec __user *iov,
unsigned long nr_segs, unsigned int flags)
{
...
}
Nos interesa prestar atención a los parámetros recibidos por la llamada al sistema, pues son los que el usuario puede controlar al llamar a la función.
Cuando un usuario llama a sys_vmsplice() se produce la siguiente secuencia de llamadas:
- sys_vmsplice() - vmsplice_to_pipe() - get_iovec_page_array()Cuando un usuario llama a sys_vmsplice() se produce la siguiente secuencia de llamadas:
Dado que el bug se encuentra en get_iovec_page_array() nos interesará saber qué parámetros de la misma puede controlar el usuario. Es decir, qué parámetros de sys_vmsplice() llegan hasta get_iovec_page_array() y si estos parámetros pasan por algun filtro durante el proceso.
Si leemos con atención las funciones implicadas vemos que desde la llamada a sys_vmsplice() hasta la ejecución de la función vulnerable get_iovec_page_array() se mantienen los parámetros: iov y nr_segs.
Si leemos con atención las funciones implicadas vemos que desde la llamada a sys_vmsplice() hasta la ejecución de la función vulnerable get_iovec_page_array() se mantienen los parámetros: iov y nr_segs.
La función get_iovec_page_array():
La función get_iovec_page_array() es la función vulnerable que el exploit aprovecha para obtener privilegios de root. Como se ha comentado en el apartado anterior, el usuario puede controlar los parámetros iov y nr_segs de esta función.
Observemos el siguiente código de la función vulnerable:
"iov" es copiada en "entry" y el valor de "iov_len" asignado a la variable "len". Por lo tanto, el usuario tiene el control de la variable "len".
A continuación se calcula "npages" mediante off + len + PAGE_SIZE - 1.
En la función get_user_pages() se asume que npages es como mínimo 1. Dado que "len" no debe ser 0, off + len + PAGE_SIZE - 1 siempre será mayor o igual que PAGE_SIZE. Sin embargo, si "len" tiene un valor cercano a UINT32_MAX, entonces npages podría valer 0 (integer overflow).
Como consecuencia get_user_pages() podría retornar más de
A continuación, en get_iovec_page_array(), viene el siguiente código:
El tamaño del array "partial" es PIPE_BUFFERS pero como hemos comentado, el valor de "error" será superior a este. Por lo tanto, las estructuras de datos que se encuentren a continuación de "partial" serán sobreescritas con "off" y "plen".
Normalmente la víctima de esta sobreescritura es el array "pages" que contiene punteros. En este caso, por ejemplo, se podria sobreescribir "pages" con valores NULL.
Cómo ejecutar código arbitrario:
Normalmente cuando el Kernel intenta acceder a un puntero NULL se obtiene una excepción que finaliza el proceso. Pero existe una técnica que puede ser usada por el atacanta para que en lugar de obtener un error se ejecute código arbitrario. Esta técnica consiste en mapear la dirección 0 y guardar allí datos que permitiran el control del usuario y la ejecución de código en el contexto del kernel.
Posibles soluciones:Observemos el siguiente código de la función vulnerable:
struct iovec entry;
...
if(copy_from_user_mmap_sem(&entry, iov, sizeof(entry)))
break;
...
len = entry.iov_len;
...
npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
if (npages > PIPE_BUFFERS - buffers)
npages = PIPE_BUFFERS - buffers;
error = get_user_pages(current, current->mm,
(unsigned long) base, npages, 0, 0,
&pages[buffers], NULL);
"iov" es copiada en "entry" y el valor de "iov_len" asignado a la variable "len". Por lo tanto, el usuario tiene el control de la variable "len".
A continuación se calcula "npages" mediante off + len + PAGE_SIZE - 1.
En la función get_user_pages() se asume que npages es como mínimo 1. Dado que "len" no debe ser 0, off + len + PAGE_SIZE - 1 siempre será mayor o igual que PAGE_SIZE. Sin embargo, si "len" tiene un valor cercano a UINT32_MAX, entonces npages podría valer 0 (integer overflow).
Como consecuencia get_user_pages() podría retornar más de
PIPE_BUFFERS
entradas, lo que como veremos a continuación, produce un buffer overflow.A continuación, en get_iovec_page_array(), viene el siguiente código:
for (i = 0; i < error; i++)
{
const int plen = min_t(size_t, len, PAGE_SIZE - off);
partial[buffers].offset = off;
partial[buffers].len = plen;
off = 0;
len -= plen;
buffers++;
}
El tamaño del array "partial" es PIPE_BUFFERS pero como hemos comentado, el valor de "error" será superior a este. Por lo tanto, las estructuras de datos que se encuentren a continuación de "partial" serán sobreescritas con "off" y "plen".
Normalmente la víctima de esta sobreescritura es el array "pages" que contiene punteros. En este caso, por ejemplo, se podria sobreescribir "pages" con valores NULL.
Cómo ejecutar código arbitrario:
Normalmente cuando el Kernel intenta acceder a un puntero NULL se obtiene una excepción que finaliza el proceso. Pero existe una técnica que puede ser usada por el atacanta para que en lugar de obtener un error se ejecute código arbitrario. Esta técnica consiste en mapear la dirección 0 y guardar allí datos que permitiran el control del usuario y la ejecución de código en el contexto del kernel.
Sin duda la mejor solución al problema consiste en actualizar el kernel o aplicar el parche correspondiente. Pero en casos de que eso no sea posible, por ejemplo, por la imposibilidad de reiniciar la máquina, puede usarse el siguiente módulo:
http://home.powertech.no/oystein/ptpatch2008/ptpatch2008.c
Referencias:
- Informe de McAfee Labs
- http://www.coseinc.com/coseinc_linux_advisory_3.pdf
1 comentario:
Eres la ostia, saludos desde Mex. M laten tus publicaciones, sin enredo y al grano!!!
Continua asi !!!
Publicar un comentario