at v6.1-rc4 386 lines 10 kB view raw
1/* 2 * Copyright (c) 2022 Alexey Dobriyan <adobriyan@gmail.com> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16/* 17 * Create a process without mappings by unmapping everything at once and 18 * holding it with ptrace(2). See what happens to 19 * 20 * /proc/${pid}/maps 21 * /proc/${pid}/numa_maps 22 * /proc/${pid}/smaps 23 * /proc/${pid}/smaps_rollup 24 */ 25#undef NDEBUG 26#include <assert.h> 27#include <errno.h> 28#include <stdio.h> 29#include <stdlib.h> 30#include <string.h> 31#include <fcntl.h> 32#include <sys/mman.h> 33#include <sys/ptrace.h> 34#include <sys/resource.h> 35#include <sys/types.h> 36#include <sys/wait.h> 37#include <unistd.h> 38 39/* 40 * 0: vsyscall VMA doesn't exist vsyscall=none 41 * 1: vsyscall VMA is --xp vsyscall=xonly 42 * 2: vsyscall VMA is r-xp vsyscall=emulate 43 */ 44static int g_vsyscall; 45static const char *g_proc_pid_maps_vsyscall; 46static const char *g_proc_pid_smaps_vsyscall; 47 48static const char proc_pid_maps_vsyscall_0[] = ""; 49static const char proc_pid_maps_vsyscall_1[] = 50"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n"; 51static const char proc_pid_maps_vsyscall_2[] = 52"ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n"; 53 54static const char proc_pid_smaps_vsyscall_0[] = ""; 55 56static const char proc_pid_smaps_vsyscall_1[] = 57"ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n" 58"Size: 4 kB\n" 59"KernelPageSize: 4 kB\n" 60"MMUPageSize: 4 kB\n" 61"Rss: 0 kB\n" 62"Pss: 0 kB\n" 63"Pss_Dirty: 0 kB\n" 64"Shared_Clean: 0 kB\n" 65"Shared_Dirty: 0 kB\n" 66"Private_Clean: 0 kB\n" 67"Private_Dirty: 0 kB\n" 68"Referenced: 0 kB\n" 69"Anonymous: 0 kB\n" 70"LazyFree: 0 kB\n" 71"AnonHugePages: 0 kB\n" 72"ShmemPmdMapped: 0 kB\n" 73"FilePmdMapped: 0 kB\n" 74"Shared_Hugetlb: 0 kB\n" 75"Private_Hugetlb: 0 kB\n" 76"Swap: 0 kB\n" 77"SwapPss: 0 kB\n" 78"Locked: 0 kB\n" 79"THPeligible: 0\n" 80/* 81 * "ProtectionKey:" field is conditional. It is possible to check it as well, 82 * but I don't have such machine. 83 */ 84; 85 86static const char proc_pid_smaps_vsyscall_2[] = 87"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n" 88"Size: 4 kB\n" 89"KernelPageSize: 4 kB\n" 90"MMUPageSize: 4 kB\n" 91"Rss: 0 kB\n" 92"Pss: 0 kB\n" 93"Pss_Dirty: 0 kB\n" 94"Shared_Clean: 0 kB\n" 95"Shared_Dirty: 0 kB\n" 96"Private_Clean: 0 kB\n" 97"Private_Dirty: 0 kB\n" 98"Referenced: 0 kB\n" 99"Anonymous: 0 kB\n" 100"LazyFree: 0 kB\n" 101"AnonHugePages: 0 kB\n" 102"ShmemPmdMapped: 0 kB\n" 103"FilePmdMapped: 0 kB\n" 104"Shared_Hugetlb: 0 kB\n" 105"Private_Hugetlb: 0 kB\n" 106"Swap: 0 kB\n" 107"SwapPss: 0 kB\n" 108"Locked: 0 kB\n" 109"THPeligible: 0\n" 110/* 111 * "ProtectionKey:" field is conditional. It is possible to check it as well, 112 * but I'm too tired. 113 */ 114; 115 116static void sigaction_SIGSEGV(int _, siginfo_t *__, void *___) 117{ 118 _exit(EXIT_FAILURE); 119} 120 121static void sigaction_SIGSEGV_vsyscall(int _, siginfo_t *__, void *___) 122{ 123 _exit(g_vsyscall); 124} 125 126/* 127 * vsyscall page can't be unmapped, probe it directly. 128 */ 129static void vsyscall(void) 130{ 131 pid_t pid; 132 int wstatus; 133 134 pid = fork(); 135 if (pid < 0) { 136 fprintf(stderr, "fork, errno %d\n", errno); 137 exit(1); 138 } 139 if (pid == 0) { 140 setrlimit(RLIMIT_CORE, &(struct rlimit){}); 141 142 /* Hide "segfault at ffffffffff600000" messages. */ 143 struct sigaction act = {}; 144 act.sa_flags = SA_SIGINFO; 145 act.sa_sigaction = sigaction_SIGSEGV_vsyscall; 146 sigaction(SIGSEGV, &act, NULL); 147 148 g_vsyscall = 0; 149 /* gettimeofday(NULL, NULL); */ 150 asm volatile ( 151 "call %P0" 152 : 153 : "i" (0xffffffffff600000), "D" (NULL), "S" (NULL) 154 : "rax", "rcx", "r11" 155 ); 156 157 g_vsyscall = 1; 158 *(volatile int *)0xffffffffff600000UL; 159 160 g_vsyscall = 2; 161 exit(g_vsyscall); 162 } 163 waitpid(pid, &wstatus, 0); 164 if (WIFEXITED(wstatus)) { 165 g_vsyscall = WEXITSTATUS(wstatus); 166 } else { 167 fprintf(stderr, "error: vsyscall wstatus %08x\n", wstatus); 168 exit(1); 169 } 170} 171 172static int test_proc_pid_maps(pid_t pid) 173{ 174 char buf[4096]; 175 snprintf(buf, sizeof(buf), "/proc/%u/maps", pid); 176 int fd = open(buf, O_RDONLY); 177 if (fd == -1) { 178 perror("open /proc/${pid}/maps"); 179 return EXIT_FAILURE; 180 } else { 181 ssize_t rv = read(fd, buf, sizeof(buf)); 182 close(fd); 183 if (g_vsyscall == 0) { 184 assert(rv == 0); 185 } else { 186 size_t len = strlen(g_proc_pid_maps_vsyscall); 187 assert(rv == len); 188 assert(memcmp(buf, g_proc_pid_maps_vsyscall, len) == 0); 189 } 190 return EXIT_SUCCESS; 191 } 192} 193 194static int test_proc_pid_numa_maps(pid_t pid) 195{ 196 char buf[4096]; 197 snprintf(buf, sizeof(buf), "/proc/%u/numa_maps", pid); 198 int fd = open(buf, O_RDONLY); 199 if (fd == -1) { 200 if (errno == ENOENT) { 201 /* 202 * /proc/${pid}/numa_maps is under CONFIG_NUMA, 203 * it doesn't necessarily exist. 204 */ 205 return EXIT_SUCCESS; 206 } 207 perror("open /proc/${pid}/numa_maps"); 208 return EXIT_FAILURE; 209 } else { 210 ssize_t rv = read(fd, buf, sizeof(buf)); 211 close(fd); 212 assert(rv == 0); 213 return EXIT_SUCCESS; 214 } 215} 216 217static int test_proc_pid_smaps(pid_t pid) 218{ 219 char buf[4096]; 220 snprintf(buf, sizeof(buf), "/proc/%u/smaps", pid); 221 int fd = open(buf, O_RDONLY); 222 if (fd == -1) { 223 if (errno == ENOENT) { 224 /* 225 * /proc/${pid}/smaps is under CONFIG_PROC_PAGE_MONITOR, 226 * it doesn't necessarily exist. 227 */ 228 return EXIT_SUCCESS; 229 } 230 perror("open /proc/${pid}/smaps"); 231 return EXIT_FAILURE; 232 } else { 233 ssize_t rv = read(fd, buf, sizeof(buf)); 234 close(fd); 235 if (g_vsyscall == 0) { 236 assert(rv == 0); 237 } else { 238 size_t len = strlen(g_proc_pid_maps_vsyscall); 239 /* TODO "ProtectionKey:" */ 240 assert(rv > len); 241 assert(memcmp(buf, g_proc_pid_maps_vsyscall, len) == 0); 242 } 243 return EXIT_SUCCESS; 244 } 245} 246 247static const char g_smaps_rollup[] = 248"00000000-00000000 ---p 00000000 00:00 0 [rollup]\n" 249"Rss: 0 kB\n" 250"Pss: 0 kB\n" 251"Pss_Dirty: 0 kB\n" 252"Pss_Anon: 0 kB\n" 253"Pss_File: 0 kB\n" 254"Pss_Shmem: 0 kB\n" 255"Shared_Clean: 0 kB\n" 256"Shared_Dirty: 0 kB\n" 257"Private_Clean: 0 kB\n" 258"Private_Dirty: 0 kB\n" 259"Referenced: 0 kB\n" 260"Anonymous: 0 kB\n" 261"LazyFree: 0 kB\n" 262"AnonHugePages: 0 kB\n" 263"ShmemPmdMapped: 0 kB\n" 264"FilePmdMapped: 0 kB\n" 265"Shared_Hugetlb: 0 kB\n" 266"Private_Hugetlb: 0 kB\n" 267"Swap: 0 kB\n" 268"SwapPss: 0 kB\n" 269"Locked: 0 kB\n" 270; 271 272static int test_proc_pid_smaps_rollup(pid_t pid) 273{ 274 char buf[4096]; 275 snprintf(buf, sizeof(buf), "/proc/%u/smaps_rollup", pid); 276 int fd = open(buf, O_RDONLY); 277 if (fd == -1) { 278 if (errno == ENOENT) { 279 /* 280 * /proc/${pid}/smaps_rollup is under CONFIG_PROC_PAGE_MONITOR, 281 * it doesn't necessarily exist. 282 */ 283 return EXIT_SUCCESS; 284 } 285 perror("open /proc/${pid}/smaps_rollup"); 286 return EXIT_FAILURE; 287 } else { 288 ssize_t rv = read(fd, buf, sizeof(buf)); 289 close(fd); 290 assert(rv == sizeof(g_smaps_rollup) - 1); 291 assert(memcmp(buf, g_smaps_rollup, sizeof(g_smaps_rollup) - 1) == 0); 292 return EXIT_SUCCESS; 293 } 294} 295 296int main(void) 297{ 298 int rv = EXIT_SUCCESS; 299 300 vsyscall(); 301 302 switch (g_vsyscall) { 303 case 0: 304 g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_0; 305 g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_0; 306 break; 307 case 1: 308 g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_1; 309 g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_1; 310 break; 311 case 2: 312 g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_2; 313 g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_2; 314 break; 315 default: 316 abort(); 317 } 318 319 pid_t pid = fork(); 320 if (pid == -1) { 321 perror("fork"); 322 return EXIT_FAILURE; 323 } else if (pid == 0) { 324 rv = ptrace(PTRACE_TRACEME, 0, NULL, NULL); 325 if (rv != 0) { 326 if (errno == EPERM) { 327 fprintf(stderr, 328"Did you know? ptrace(PTRACE_TRACEME) doesn't work under strace.\n" 329 ); 330 kill(getppid(), SIGTERM); 331 return EXIT_FAILURE; 332 } 333 perror("ptrace PTRACE_TRACEME"); 334 return EXIT_FAILURE; 335 } 336 337 /* 338 * Hide "segfault at ..." messages. Signal handler won't run. 339 */ 340 struct sigaction act = {}; 341 act.sa_flags = SA_SIGINFO; 342 act.sa_sigaction = sigaction_SIGSEGV; 343 sigaction(SIGSEGV, &act, NULL); 344 345#ifdef __amd64__ 346 munmap(NULL, ((size_t)1 << 47) - 4096); 347#else 348#error "implement 'unmap everything'" 349#endif 350 return EXIT_FAILURE; 351 } else { 352 /* 353 * TODO find reliable way to signal parent that munmap(2) completed. 354 * Child can't do it directly because it effectively doesn't exist 355 * anymore. Looking at child's VM files isn't 100% reliable either: 356 * due to a bug they may not become empty or empty-like. 357 */ 358 sleep(1); 359 360 if (rv == EXIT_SUCCESS) { 361 rv = test_proc_pid_maps(pid); 362 } 363 if (rv == EXIT_SUCCESS) { 364 rv = test_proc_pid_numa_maps(pid); 365 } 366 if (rv == EXIT_SUCCESS) { 367 rv = test_proc_pid_smaps(pid); 368 } 369 if (rv == EXIT_SUCCESS) { 370 rv = test_proc_pid_smaps_rollup(pid); 371 } 372 /* 373 * TODO test /proc/${pid}/statm, task_statm() 374 * ->start_code, ->end_code aren't updated by munmap(). 375 * Output can be "0 0 0 2 0 0 0\n" where "2" can be anything. 376 */ 377 378 /* Cut the rope. */ 379 int wstatus; 380 waitpid(pid, &wstatus, 0); 381 assert(WIFSTOPPED(wstatus)); 382 assert(WSTOPSIG(wstatus) == SIGSEGV); 383 } 384 385 return rv; 386}