Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

proc: test how it holds up with mapping'less process

Create process without mappings and check

/proc/*/maps
/proc/*/numa_maps
/proc/*/smaps
/proc/*/smaps_rollup

They must be empty (excluding vsyscall page) or full of zeroes.

Retroactively this test should've caught embarassing /proc/*/smaps_rollup
oops:

[17752.703567] BUG: kernel NULL pointer dereference, address: 0000000000000000
[17752.703580] #PF: supervisor read access in kernel mode
[17752.703583] #PF: error_code(0x0000) - not-present page
[17752.703587] PGD 0 P4D 0
[17752.703593] Oops: 0000 [#1] PREEMPT SMP PTI
[17752.703598] CPU: 0 PID: 60649 Comm: cat Tainted: G W 5.19.9-100.fc35.x86_64 #1
[17752.703603] Hardware name: To Be Filled By O.E.M. To Be Filled By O.E.M./X99 Extreme6/3.1, BIOS P3.30 08/05/2016
[17752.703607] RIP: 0010:show_smaps_rollup+0x159/0x2e0

Note 1:
ProtectionKey field in /proc/*/smaps is optional,
so check most of its contents, not everything.

Note 2:
due to the nature of this test, child process hardly can signal
its readiness (after unmapping everything!) to parent.
I feel like "sleep(1)" is justified.
If you know how to do it without sleep please tell me.

Note 3:
/proc/*/statm is not tested but can be.

Link: https://lkml.kernel.org/r/Yz3liL6Dn+n2SD8Q@localhost.localdomain
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Alexey Dobriyan and committed by
Andrew Morton
5bc73bb3 0f4107d1

+388
+1
tools/testing/selftests/proc/.gitignore
··· 5 5 /proc-fsconfig-hidepid 6 6 /proc-loadavg-001 7 7 /proc-multiple-procfs 8 + /proc-empty-vm 8 9 /proc-pid-vm 9 10 /proc-self-map-files-001 10 11 /proc-self-map-files-002
+1
tools/testing/selftests/proc/Makefile
··· 8 8 TEST_GEN_PROGS += fd-002-posix-eq 9 9 TEST_GEN_PROGS += fd-003-kthread 10 10 TEST_GEN_PROGS += proc-loadavg-001 11 + TEST_GEN_PROGS += proc-empty-vm 11 12 TEST_GEN_PROGS += proc-pid-vm 12 13 TEST_GEN_PROGS += proc-self-map-files-001 13 14 TEST_GEN_PROGS += proc-self-map-files-002
+386
tools/testing/selftests/proc/proc-empty-vm.c
··· 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 + */ 44 + static int g_vsyscall; 45 + static const char *g_proc_pid_maps_vsyscall; 46 + static const char *g_proc_pid_smaps_vsyscall; 47 + 48 + static const char proc_pid_maps_vsyscall_0[] = ""; 49 + static const char proc_pid_maps_vsyscall_1[] = 50 + "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n"; 51 + static const char proc_pid_maps_vsyscall_2[] = 52 + "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n"; 53 + 54 + static const char proc_pid_smaps_vsyscall_0[] = ""; 55 + 56 + static 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 + 86 + static 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 + 116 + static void sigaction_SIGSEGV(int _, siginfo_t *__, void *___) 117 + { 118 + _exit(EXIT_FAILURE); 119 + } 120 + 121 + static 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 + */ 129 + static 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 + 172 + static 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 + 194 + static 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 + 217 + static 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 + 247 + static 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 + 272 + static 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 + 296 + int 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 + }