vmsplice es una llamada al sistema que puede ser accedida desde espacio de usuario mediante syscall().
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:
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:
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.