Категория > Новости > Сила четырех байтов. Как я нашел уязвимость CVE-2021-26708 в ядре Linux - «Новости»
Сила четырех байтов. Как я нашел уязвимость CVE-2021-26708 в ядре Linux - «Новости»24-10-2021, 00:00. Автор: Поликсена |
CVE-2021-26708. В этой статье я детально расскажу об эксплуатации одной из них с целью локального повышения привилегий на Fedora 33 Server для платформы x86_64. Я покажу, как с помощью небольшой ошибки доступа к памяти атакующий может получить контроль над всей операционной системой и при этом обойти средства обеспечения безопасности платформы. В заключение я расскажу про возможные средства предотвращения атаки. С докладом по этой теме я выступил на конференции Zer0Con 2021. Получилось интересное исследование. Состояние гонки в ядре Linux приводит к порче четырех байтов в ядерной памяти, и я постепенно превращаю это в произвольное чтение/запись и полный контроль над системой. Поэтому я назвал статью «Сила четырех байтов». УязвимостиУязвимости CVE-2021-26708 — это состояния гонки, вызванные неправильной работой с примитивами синхронизации в net/vmw_vsock/af_vsock.c. Эти ошибки были неявно внесены в код ядра версии Уязвимый код поставляется в дистрибутивах GNU/Linux в виде модулей
vsock = socket(AF_VSOCK, SOCK_STREAM, 0);
Создание сокета в домене Ошибки и исправления11 января я проверял результаты фаззинга ядра на своих стендах и обнаружил подозрительный отказ ядра в функции virtio_transport_notify_buffer_size(). Было странно, что фаззер не смог повторно воспроизвести этот эффект, поэтому я стал изучать исходный код и разрабатывать программу‑репродюсер вручную. Несколько дней спустя я нашел ошибку в ядерной функции
struct sock *sk;
struct vsock_sock *vsk;
const struct vsock_transport *transport;
/* ... */
sk = sock->sk;
vsk = vsock_sk(sk);
transport = vsk->transport;
lock_sock(sk);
Здесь указатель на транспорт виртуального сокета копируется в локальную переменную перед вызовом функции История разработки ядра в Git помогла понять, как появились эти пять ошибок. Дело в том, что изначально транспорт виртуального сокета не мог измениться, то есть можно было безопасно копировать значение Исправить эти уязвимости очень просто:
...
sk = sock->sk;
vsk = vsock_sk(sk);
- transport = vsk->transport;
lock_sock(sk);
+ transport = vsk->transport;
...
Ответственное разглашение, которое пошло не так30 января, после того как закончил прототип эксплоита, я отправил информацию об уязвимостях и исправление (патч) по адресу
На самом деле первый пункт довольно спорный. Линус решил принять мой патч сразу, без эмбарго на разглашение (disclosure embargo), потому что «этот патч не сильно отличается от патчей, которые мы принимаем каждый день» (the patch doesn’t look all that different from the kinds of patches we do every day). Я подчинился, но предложил отправить патч открыто. Это важно, потому что иначе каждый может отследить исправления безопасности, если отфильтрует коммиты, которые не обсуждались в публичном списке рассылки. Недавно эта техника была рассмотрена в одной исследовательской работе. 2 февраля вторая версия моего патча была принята в ветку
То есть меня попросили немедленно раскрыть информацию о найденных и исправленных уязвимостях в публичном списке рассылки Возникает вопрос: насколько эта практика немедленного принятия патча в ванильное ядро совместима с работой организаций в linux-distros? У меня есть контрпример. Когда я обнаружил ядерную уязвимость CVE-2017-2636 и выполнил ответственное разглашение, Кейс Кук (Kees Cook) и Грег организовали недельное эмбарго на разглашение информации. Мы уведомили организации из Как портится ядерная памятьТеперь рассмотрим эксплуатацию уязвимостей CVE-2021-26708. Для локального повышения привилегий в системе я выбрал состояние гонки в функции
setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
&size, sizeof(unsigned long));
Этот поток сохраняет указатель на виртуальный транспорт в локальную переменную (в этом заключается ошибка), а затем пытается захватить блокировку виртуального сокета в функции
struct sockaddr_vm addr = {
.svm_family = AF_VSOCK,
};
addr.svm_cid = VMADDR_CID_LOCAL;
connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));
addr.svm_cid = VMADDR_CID_HYPERVISOR;
connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));
При обработке системного вызова
if (vsk->transport) {
if (vsk->transport == new_transport)
return 0;
/* transport->release() must be called with sock lock acquired.
* This path can only be taken during vsock_stream_connect(),
* where we have already held the sock lock.
* In the other cases, this function is called on a new socket
* which is not assigned to any transport.
*/
vsk->transport->release(vsk);
vsock_deassign_transport(vsk);
}
Что происходит в этом коде? Второй вызов После этого
void virtio_transport_notify_buffer_size(struct vsock_sock *vsk, u64 *val)
{
struct virtio_vsock_sock *vvs = vsk->trans;
if (*val > VIRTIO_VSOCK_MAX_BUF_SIZE)
*val = VIRTIO_VSOCK_MAX_BUF_SIZE;
vvs->buf_alloc = *val;
virtio_transport_send_credit_update(vsk, VIRTIO_VSOCK_TYPE_STREAM, NULL);
}
Здесь Загадка фаззингаКак я уже упоминал, фаззер syzkaller не смог воспроизвести эту ошибку в ядре и я был вынужден писать программу‑репродюсер вручную. Почему же так произошло? Взгляд на код функции
if (val != vsk->buffer_size &&
transport && transport->notify_buffer_size)
transport->notify_buffer_size(vsk, &val);
vsk->buffer_size = val;
Здесь обработчик
struct timespec tp;
unsigned long size = 0;
clock_gettime(CLOCK_MONOTONIC, &tp);
size = tp.tv_nsec;
setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
&size, sizeof(unsigned long));
Здесь значение параметра Как бы то ни было, я до сих пор до конца не понимаю, как syzkaller смог спровоцировать этот отказ ядра. Похоже, фаззер сотворил какое‑то многопоточное «волшебство» с операциями Идея! Возможно, добавление способности рандомизировать аргументы системных вызовов в процессе самого фаззинга позволит фаззеру syzkaller находить больше ошибок типа CVE-2021-26708. С другой стороны, это может и ухудшить стабильность повторного воспроизведения уже найденных отказов ядра. Сила четырех байтовВ этом исследовании я выбрал объектом атаки Fedora 33 Server с ядром Linux версии 5.10.11-200.fc33.x86_64. С самого начала я нацелился обойти SMEP и SMAP (аппаратные средства защиты платформы x86_64). Итак, это состояние гонки может спровоцировать запись четырех контролируемых байтов в освобожденный 64-байтовый ядерный объект по отступу 40. Это очень ограниченный примитив эксплуатации, я преодолел большие трудности, чтобы превратить его в полный контроль над системой. Далее я расскажу, как разработал прототип эксплоита, в хронологическом порядке. infoЭти иллюстрации я сделал из фотографий экспонатов Государственного Эрмитажа. Замечательный музей! Первым делом я начал работать над стабильной техникой heap spraying. Ее суть в том, что эксплоит должен выполнить такие действия в пользовательском пространстве, которые заставят ядро выделить новый объект на месте освобожденной 64-байтовой структуры Перейти обратно к новости |