1 #if defined __amd64__ || defined __i386__
3 * Copyright (c) 2022 Alexey Dobriyan <adobriyan@gmail.com>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 * Create a process without mappings by unmapping everything at once and
19 * holding it with ptrace(2). See what happens to
22 * /proc/${pid}/numa_maps
24 * /proc/${pid}/smaps_rollup
38 #include <sys/ptrace.h>
39 #include <sys/resource.h>
40 #include <sys/syscall.h>
41 #include <sys/types.h>
50 #ifndef SYS_pkey_alloc
51 #define SYS_pkey_alloc 330
54 #define SYS_pkey_free 331
56 #elif defined __i386__
57 #ifndef SYS_pkey_alloc
58 #define SYS_pkey_alloc 381
61 #define SYS_pkey_free 382
64 #error "SYS_pkey_alloc"
67 static int g_protection_key_support
;
69 static int protection_key_support(void)
71 long rv
= syscall(SYS_pkey_alloc
, 0, 0);
73 syscall(SYS_pkey_free
, (int)rv
);
75 } else if (rv
== -1 && errno
== ENOSYS
) {
77 } else if (rv
== -1 && errno
== EINVAL
) {
81 fprintf(stderr
, "%s: error: rv %ld, errno %d\n", __func__
, rv
, errno
);
87 * 0: vsyscall VMA doesn't exist vsyscall=none
88 * 1: vsyscall VMA is --xp vsyscall=xonly
89 * 2: vsyscall VMA is r-xp vsyscall=emulate
91 static volatile int g_vsyscall
;
92 static const char *g_proc_pid_maps_vsyscall
;
93 static const char *g_proc_pid_smaps_vsyscall
;
95 static const char proc_pid_maps_vsyscall_0
[] = "";
96 static const char proc_pid_maps_vsyscall_1
[] =
97 "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n";
98 static const char proc_pid_maps_vsyscall_2
[] =
99 "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n";
101 static const char proc_pid_smaps_vsyscall_0
[] = "";
103 static const char proc_pid_smaps_vsyscall_1
[] =
104 "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n"
106 "KernelPageSize: 4 kB\n"
107 "MMUPageSize: 4 kB\n"
111 "Shared_Clean: 0 kB\n"
112 "Shared_Dirty: 0 kB\n"
113 "Private_Clean: 0 kB\n"
114 "Private_Dirty: 0 kB\n"
119 "AnonHugePages: 0 kB\n"
120 "ShmemPmdMapped: 0 kB\n"
121 "FilePmdMapped: 0 kB\n"
122 "Shared_Hugetlb: 0 kB\n"
123 "Private_Hugetlb: 0 kB\n"
130 static const char proc_pid_smaps_vsyscall_2
[] =
131 "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n"
133 "KernelPageSize: 4 kB\n"
134 "MMUPageSize: 4 kB\n"
138 "Shared_Clean: 0 kB\n"
139 "Shared_Dirty: 0 kB\n"
140 "Private_Clean: 0 kB\n"
141 "Private_Dirty: 0 kB\n"
146 "AnonHugePages: 0 kB\n"
147 "ShmemPmdMapped: 0 kB\n"
148 "FilePmdMapped: 0 kB\n"
149 "Shared_Hugetlb: 0 kB\n"
150 "Private_Hugetlb: 0 kB\n"
157 static void sigaction_SIGSEGV(int _
, siginfo_t
*__
, void *___
)
163 static void sigaction_SIGSEGV_vsyscall(int _
, siginfo_t
*__
, void *___
)
169 * vsyscall page can't be unmapped, probe it directly.
171 static void vsyscall(void)
178 fprintf(stderr
, "fork, errno %d\n", errno
);
182 setrlimit(RLIMIT_CORE
, &(struct rlimit
){});
184 /* Hide "segfault at ffffffffff600000" messages. */
185 struct sigaction act
= {};
186 act
.sa_flags
= SA_SIGINFO
;
187 act
.sa_sigaction
= sigaction_SIGSEGV_vsyscall
;
188 sigaction(SIGSEGV
, &act
, NULL
);
191 /* gettimeofday(NULL, NULL); */
192 uint64_t rax
= 0xffffffffff600000;
196 : "D" (NULL
), "S" (NULL
)
201 *(volatile int *)0xffffffffff600000UL
;
206 waitpid(pid
, &wstatus
, 0);
207 if (WIFEXITED(wstatus
)) {
208 g_vsyscall
= WEXITSTATUS(wstatus
);
210 fprintf(stderr
, "error: vsyscall wstatus %08x\n", wstatus
);
216 static int test_proc_pid_maps(pid_t pid
)
219 snprintf(buf
, sizeof(buf
), "/proc/%u/maps", pid
);
220 int fd
= open(buf
, O_RDONLY
);
222 perror("open /proc/${pid}/maps");
225 ssize_t rv
= read(fd
, buf
, sizeof(buf
));
227 if (g_vsyscall
== 0) {
230 size_t len
= strlen(g_proc_pid_maps_vsyscall
);
232 assert(memcmp(buf
, g_proc_pid_maps_vsyscall
, len
) == 0);
238 static int test_proc_pid_numa_maps(pid_t pid
)
241 snprintf(buf
, sizeof(buf
), "/proc/%u/numa_maps", pid
);
242 int fd
= open(buf
, O_RDONLY
);
244 if (errno
== ENOENT
) {
246 * /proc/${pid}/numa_maps is under CONFIG_NUMA,
247 * it doesn't necessarily exist.
251 perror("open /proc/${pid}/numa_maps");
254 ssize_t rv
= read(fd
, buf
, sizeof(buf
));
261 static int test_proc_pid_smaps(pid_t pid
)
264 snprintf(buf
, sizeof(buf
), "/proc/%u/smaps", pid
);
265 int fd
= open(buf
, O_RDONLY
);
267 if (errno
== ENOENT
) {
269 * /proc/${pid}/smaps is under CONFIG_PROC_PAGE_MONITOR,
270 * it doesn't necessarily exist.
274 perror("open /proc/${pid}/smaps");
277 ssize_t rv
= read(fd
, buf
, sizeof(buf
));
281 assert(rv
<= sizeof(buf
));
283 if (g_vsyscall
== 0) {
286 size_t len
= strlen(g_proc_pid_smaps_vsyscall
);
288 assert(memcmp(buf
, g_proc_pid_smaps_vsyscall
, len
) == 0);
290 if (g_protection_key_support
) {
291 #define PROTECTION_KEY "ProtectionKey: 0\n"
292 assert(memmem(buf
, rv
, PROTECTION_KEY
, strlen(PROTECTION_KEY
)));
299 static const char g_smaps_rollup
[] =
300 "00000000-00000000 ---p 00000000 00:00 0 [rollup]\n"
307 "Shared_Clean: 0 kB\n"
308 "Shared_Dirty: 0 kB\n"
309 "Private_Clean: 0 kB\n"
310 "Private_Dirty: 0 kB\n"
315 "AnonHugePages: 0 kB\n"
316 "ShmemPmdMapped: 0 kB\n"
317 "FilePmdMapped: 0 kB\n"
318 "Shared_Hugetlb: 0 kB\n"
319 "Private_Hugetlb: 0 kB\n"
325 static int test_proc_pid_smaps_rollup(pid_t pid
)
328 snprintf(buf
, sizeof(buf
), "/proc/%u/smaps_rollup", pid
);
329 int fd
= open(buf
, O_RDONLY
);
331 if (errno
== ENOENT
) {
333 * /proc/${pid}/smaps_rollup is under CONFIG_PROC_PAGE_MONITOR,
334 * it doesn't necessarily exist.
338 perror("open /proc/${pid}/smaps_rollup");
341 ssize_t rv
= read(fd
, buf
, sizeof(buf
));
343 assert(rv
== sizeof(g_smaps_rollup
) - 1);
344 assert(memcmp(buf
, g_smaps_rollup
, sizeof(g_smaps_rollup
) - 1) == 0);
349 static const char *parse_u64(const char *p
, const char *const end
, uint64_t *rv
)
352 for (; p
!= end
; p
+= 1) {
353 if ('0' <= *p
&& *p
<= '9') {
354 assert(!__builtin_mul_overflow(*rv
, 10, rv
));
355 assert(!__builtin_add_overflow(*rv
, *p
- '0', rv
));
365 * There seems to be 2 types of valid output:
366 * "0 A A B 0 0 0\n" for dynamic exeuctables,
367 * "0 0 0 B 0 0 0\n" for static executables.
369 static int test_proc_pid_statm(pid_t pid
)
372 snprintf(buf
, sizeof(buf
), "/proc/%u/statm", pid
);
373 int fd
= open(buf
, O_RDONLY
);
375 perror("open /proc/${pid}/statm");
379 ssize_t rv
= read(fd
, buf
, sizeof(buf
));
383 assert(rv
<= sizeof(buf
));
386 const char *const end
= p
+ rv
;
389 assert(p
!= end
&& *p
++ == '0');
390 assert(p
!= end
&& *p
++ == ' ');
393 p
= parse_u64(p
, end
, &resident
);
394 assert(p
!= end
&& *p
++ == ' ');
397 p
= parse_u64(p
, end
, &shared
);
398 assert(p
!= end
&& *p
++ == ' ');
401 p
= parse_u64(p
, end
, &text
);
402 assert(p
!= end
&& *p
++ == ' ');
404 assert(p
!= end
&& *p
++ == '0');
405 assert(p
!= end
&& *p
++ == ' ');
408 assert(p
!= end
&& *p
++ == '0');
409 assert(p
!= end
&& *p
++ == ' ');
411 assert(p
!= end
&& *p
++ == '0');
412 assert(p
!= end
&& *p
++ == '\n');
417 * "text" is "mm->end_code - mm->start_code" at execve(2) time.
418 * munmap() doesn't change it. It can be anything (just link
419 * statically). It can't be 0 because executing to this point
420 * implies at least 1 page of code.
425 * These two are always equal. Always 0 for statically linked
426 * executables and sometimes 0 for dynamically linked executables.
427 * There is no way to tell one from another without parsing ELF
428 * which is too much for this test.
430 assert(resident
== shared
);
437 int rv
= EXIT_SUCCESS
;
443 switch (g_vsyscall
) {
445 g_proc_pid_maps_vsyscall
= proc_pid_maps_vsyscall_0
;
446 g_proc_pid_smaps_vsyscall
= proc_pid_smaps_vsyscall_0
;
449 g_proc_pid_maps_vsyscall
= proc_pid_maps_vsyscall_1
;
450 g_proc_pid_smaps_vsyscall
= proc_pid_smaps_vsyscall_1
;
453 g_proc_pid_maps_vsyscall
= proc_pid_maps_vsyscall_2
;
454 g_proc_pid_smaps_vsyscall
= proc_pid_smaps_vsyscall_2
;
460 g_protection_key_support
= protection_key_support();
466 } else if (pid
== 0) {
467 rv
= ptrace(PTRACE_TRACEME
, 0, NULL
, NULL
);
469 if (errno
== EPERM
) {
471 "Did you know? ptrace(PTRACE_TRACEME) doesn't work under strace.\n"
473 kill(getppid(), SIGTERM
);
476 perror("ptrace PTRACE_TRACEME");
481 * Hide "segfault at ..." messages. Signal handler won't run.
483 struct sigaction act
= {};
484 act
.sa_flags
= SA_SIGINFO
;
485 act
.sa_sigaction
= sigaction_SIGSEGV
;
486 sigaction(SIGSEGV
, &act
, NULL
);
489 munmap(NULL
, ((size_t)1 << 47) - 4096);
490 #elif defined __i386__
494 for (len
= -4096;; len
-= 4096) {
499 #error "implement 'unmap everything'"
504 * TODO find reliable way to signal parent that munmap(2) completed.
505 * Child can't do it directly because it effectively doesn't exist
506 * anymore. Looking at child's VM files isn't 100% reliable either:
507 * due to a bug they may not become empty or empty-like.
511 if (rv
== EXIT_SUCCESS
) {
512 rv
= test_proc_pid_maps(pid
);
514 if (rv
== EXIT_SUCCESS
) {
515 rv
= test_proc_pid_numa_maps(pid
);
517 if (rv
== EXIT_SUCCESS
) {
518 rv
= test_proc_pid_smaps(pid
);
520 if (rv
== EXIT_SUCCESS
) {
521 rv
= test_proc_pid_smaps_rollup(pid
);
523 if (rv
== EXIT_SUCCESS
) {
524 rv
= test_proc_pid_statm(pid
);
529 waitpid(pid
, &wstatus
, 0);
530 assert(WIFSTOPPED(wstatus
));
531 assert(WSTOPSIG(wstatus
) == SIGSEGV
);