| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- #if defined __amd64__ || defined __i386__
- /*
- * Copyright (c) 2022 Alexey Dobriyan <adobriyan@gmail.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- /*
- * Create a process without mappings by unmapping everything at once and
- * holding it with ptrace(2). See what happens to
- *
- * /proc/${pid}/maps
- * /proc/${pid}/numa_maps
- * /proc/${pid}/smaps
- * /proc/${pid}/smaps_rollup
- */
- #undef _GNU_SOURCE
- #define _GNU_SOURCE
- #undef NDEBUG
- #include <assert.h>
- #include <errno.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <sys/ptrace.h>
- #include <sys/resource.h>
- #include <sys/syscall.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <unistd.h>
- #ifdef __amd64__
- #define TEST_VSYSCALL
- #endif
- #if defined __amd64__
- #ifndef SYS_pkey_alloc
- #define SYS_pkey_alloc 330
- #endif
- #ifndef SYS_pkey_free
- #define SYS_pkey_free 331
- #endif
- #elif defined __i386__
- #ifndef SYS_pkey_alloc
- #define SYS_pkey_alloc 381
- #endif
- #ifndef SYS_pkey_free
- #define SYS_pkey_free 382
- #endif
- #else
- #error "SYS_pkey_alloc"
- #endif
- static int g_protection_key_support;
- static int protection_key_support(void)
- {
- long rv = syscall(SYS_pkey_alloc, 0, 0);
- if (rv > 0) {
- syscall(SYS_pkey_free, (int)rv);
- return 1;
- } else if (rv == -1 && errno == ENOSYS) {
- return 0;
- } else if (rv == -1 && errno == EINVAL) {
- // ospke=n
- return 0;
- } else {
- fprintf(stderr, "%s: error: rv %ld, errno %d\n", __func__, rv, errno);
- exit(EXIT_FAILURE);
- }
- }
- /*
- * 0: vsyscall VMA doesn't exist vsyscall=none
- * 1: vsyscall VMA is --xp vsyscall=xonly
- * 2: vsyscall VMA is r-xp vsyscall=emulate
- */
- static volatile int g_vsyscall;
- static const char *g_proc_pid_maps_vsyscall;
- static const char *g_proc_pid_smaps_vsyscall;
- static const char proc_pid_maps_vsyscall_0[] = "";
- static const char proc_pid_maps_vsyscall_1[] =
- "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n";
- static const char proc_pid_maps_vsyscall_2[] =
- "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n";
- static const char proc_pid_smaps_vsyscall_0[] = "";
- static const char proc_pid_smaps_vsyscall_1[] =
- "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n"
- "Size: 4 kB\n"
- "KernelPageSize: 4 kB\n"
- "MMUPageSize: 4 kB\n"
- "Rss: 0 kB\n"
- "Pss: 0 kB\n"
- "Pss_Dirty: 0 kB\n"
- "Shared_Clean: 0 kB\n"
- "Shared_Dirty: 0 kB\n"
- "Private_Clean: 0 kB\n"
- "Private_Dirty: 0 kB\n"
- "Referenced: 0 kB\n"
- "Anonymous: 0 kB\n"
- "KSM: 0 kB\n"
- "LazyFree: 0 kB\n"
- "AnonHugePages: 0 kB\n"
- "ShmemPmdMapped: 0 kB\n"
- "FilePmdMapped: 0 kB\n"
- "Shared_Hugetlb: 0 kB\n"
- "Private_Hugetlb: 0 kB\n"
- "Swap: 0 kB\n"
- "SwapPss: 0 kB\n"
- "Locked: 0 kB\n"
- "THPeligible: 0\n"
- ;
- static const char proc_pid_smaps_vsyscall_2[] =
- "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n"
- "Size: 4 kB\n"
- "KernelPageSize: 4 kB\n"
- "MMUPageSize: 4 kB\n"
- "Rss: 0 kB\n"
- "Pss: 0 kB\n"
- "Pss_Dirty: 0 kB\n"
- "Shared_Clean: 0 kB\n"
- "Shared_Dirty: 0 kB\n"
- "Private_Clean: 0 kB\n"
- "Private_Dirty: 0 kB\n"
- "Referenced: 0 kB\n"
- "Anonymous: 0 kB\n"
- "KSM: 0 kB\n"
- "LazyFree: 0 kB\n"
- "AnonHugePages: 0 kB\n"
- "ShmemPmdMapped: 0 kB\n"
- "FilePmdMapped: 0 kB\n"
- "Shared_Hugetlb: 0 kB\n"
- "Private_Hugetlb: 0 kB\n"
- "Swap: 0 kB\n"
- "SwapPss: 0 kB\n"
- "Locked: 0 kB\n"
- "THPeligible: 0\n"
- ;
- static void sigaction_SIGSEGV(int _, siginfo_t *__, void *___)
- {
- _exit(EXIT_FAILURE);
- }
- #ifdef TEST_VSYSCALL
- static void sigaction_SIGSEGV_vsyscall(int _, siginfo_t *__, void *___)
- {
- _exit(g_vsyscall);
- }
- /*
- * vsyscall page can't be unmapped, probe it directly.
- */
- static void vsyscall(void)
- {
- pid_t pid;
- int wstatus;
- pid = fork();
- if (pid < 0) {
- fprintf(stderr, "fork, errno %d\n", errno);
- exit(1);
- }
- if (pid == 0) {
- setrlimit(RLIMIT_CORE, &(struct rlimit){});
- /* Hide "segfault at ffffffffff600000" messages. */
- struct sigaction act = {};
- act.sa_flags = SA_SIGINFO;
- act.sa_sigaction = sigaction_SIGSEGV_vsyscall;
- sigaction(SIGSEGV, &act, NULL);
- g_vsyscall = 0;
- /* gettimeofday(NULL, NULL); */
- uint64_t rax = 0xffffffffff600000;
- asm volatile (
- "call *%[rax]"
- : [rax] "+a" (rax)
- : "D" (NULL), "S" (NULL)
- : "rcx", "r11"
- );
- g_vsyscall = 1;
- *(volatile int *)0xffffffffff600000UL;
- g_vsyscall = 2;
- exit(g_vsyscall);
- }
- waitpid(pid, &wstatus, 0);
- if (WIFEXITED(wstatus)) {
- g_vsyscall = WEXITSTATUS(wstatus);
- } else {
- fprintf(stderr, "error: vsyscall wstatus %08x\n", wstatus);
- exit(1);
- }
- }
- #endif
- static int test_proc_pid_maps(pid_t pid)
- {
- char buf[4096];
- snprintf(buf, sizeof(buf), "/proc/%u/maps", pid);
- int fd = open(buf, O_RDONLY);
- if (fd == -1) {
- perror("open /proc/${pid}/maps");
- return EXIT_FAILURE;
- } else {
- ssize_t rv = read(fd, buf, sizeof(buf));
- close(fd);
- if (g_vsyscall == 0) {
- assert(rv == 0);
- } else {
- size_t len = strlen(g_proc_pid_maps_vsyscall);
- assert(rv == len);
- assert(memcmp(buf, g_proc_pid_maps_vsyscall, len) == 0);
- }
- return EXIT_SUCCESS;
- }
- }
- static int test_proc_pid_numa_maps(pid_t pid)
- {
- char buf[4096];
- snprintf(buf, sizeof(buf), "/proc/%u/numa_maps", pid);
- int fd = open(buf, O_RDONLY);
- if (fd == -1) {
- if (errno == ENOENT) {
- /*
- * /proc/${pid}/numa_maps is under CONFIG_NUMA,
- * it doesn't necessarily exist.
- */
- return EXIT_SUCCESS;
- }
- perror("open /proc/${pid}/numa_maps");
- return EXIT_FAILURE;
- } else {
- ssize_t rv = read(fd, buf, sizeof(buf));
- close(fd);
- assert(rv == 0);
- return EXIT_SUCCESS;
- }
- }
- static int test_proc_pid_smaps(pid_t pid)
- {
- char buf[4096];
- snprintf(buf, sizeof(buf), "/proc/%u/smaps", pid);
- int fd = open(buf, O_RDONLY);
- if (fd == -1) {
- if (errno == ENOENT) {
- /*
- * /proc/${pid}/smaps is under CONFIG_PROC_PAGE_MONITOR,
- * it doesn't necessarily exist.
- */
- return EXIT_SUCCESS;
- }
- perror("open /proc/${pid}/smaps");
- return EXIT_FAILURE;
- }
- ssize_t rv = read(fd, buf, sizeof(buf));
- close(fd);
- assert(0 <= rv);
- assert(rv <= sizeof(buf));
- if (g_vsyscall == 0) {
- assert(rv == 0);
- } else {
- size_t len = strlen(g_proc_pid_smaps_vsyscall);
- assert(rv > len);
- assert(memcmp(buf, g_proc_pid_smaps_vsyscall, len) == 0);
- if (g_protection_key_support) {
- #define PROTECTION_KEY "ProtectionKey: 0\n"
- assert(memmem(buf, rv, PROTECTION_KEY, strlen(PROTECTION_KEY)));
- }
- }
- return EXIT_SUCCESS;
- }
- static const char g_smaps_rollup[] =
- "00000000-00000000 ---p 00000000 00:00 0 [rollup]\n"
- "Rss: 0 kB\n"
- "Pss: 0 kB\n"
- "Pss_Dirty: 0 kB\n"
- "Pss_Anon: 0 kB\n"
- "Pss_File: 0 kB\n"
- "Pss_Shmem: 0 kB\n"
- "Shared_Clean: 0 kB\n"
- "Shared_Dirty: 0 kB\n"
- "Private_Clean: 0 kB\n"
- "Private_Dirty: 0 kB\n"
- "Referenced: 0 kB\n"
- "Anonymous: 0 kB\n"
- "KSM: 0 kB\n"
- "LazyFree: 0 kB\n"
- "AnonHugePages: 0 kB\n"
- "ShmemPmdMapped: 0 kB\n"
- "FilePmdMapped: 0 kB\n"
- "Shared_Hugetlb: 0 kB\n"
- "Private_Hugetlb: 0 kB\n"
- "Swap: 0 kB\n"
- "SwapPss: 0 kB\n"
- "Locked: 0 kB\n"
- ;
- static int test_proc_pid_smaps_rollup(pid_t pid)
- {
- char buf[4096];
- snprintf(buf, sizeof(buf), "/proc/%u/smaps_rollup", pid);
- int fd = open(buf, O_RDONLY);
- if (fd == -1) {
- if (errno == ENOENT) {
- /*
- * /proc/${pid}/smaps_rollup is under CONFIG_PROC_PAGE_MONITOR,
- * it doesn't necessarily exist.
- */
- return EXIT_SUCCESS;
- }
- perror("open /proc/${pid}/smaps_rollup");
- return EXIT_FAILURE;
- } else {
- ssize_t rv = read(fd, buf, sizeof(buf));
- close(fd);
- assert(rv == sizeof(g_smaps_rollup) - 1);
- assert(memcmp(buf, g_smaps_rollup, sizeof(g_smaps_rollup) - 1) == 0);
- return EXIT_SUCCESS;
- }
- }
- static const char *parse_u64(const char *p, const char *const end, uint64_t *rv)
- {
- *rv = 0;
- for (; p != end; p += 1) {
- if ('0' <= *p && *p <= '9') {
- assert(!__builtin_mul_overflow(*rv, 10, rv));
- assert(!__builtin_add_overflow(*rv, *p - '0', rv));
- } else {
- break;
- }
- }
- assert(p != end);
- return p;
- }
- /*
- * There seems to be 2 types of valid output:
- * "0 A A B 0 0 0\n" for dynamic exeuctables,
- * "0 0 0 B 0 0 0\n" for static executables.
- */
- static int test_proc_pid_statm(pid_t pid)
- {
- char buf[4096];
- snprintf(buf, sizeof(buf), "/proc/%u/statm", pid);
- int fd = open(buf, O_RDONLY);
- if (fd == -1) {
- perror("open /proc/${pid}/statm");
- return EXIT_FAILURE;
- }
- ssize_t rv = read(fd, buf, sizeof(buf));
- close(fd);
- assert(rv >= 0);
- assert(rv <= sizeof(buf));
- const char *p = buf;
- const char *const end = p + rv;
- /* size */
- assert(p != end && *p++ == '0');
- assert(p != end && *p++ == ' ');
- uint64_t resident;
- p = parse_u64(p, end, &resident);
- assert(p != end && *p++ == ' ');
- uint64_t shared;
- p = parse_u64(p, end, &shared);
- assert(p != end && *p++ == ' ');
- uint64_t text;
- p = parse_u64(p, end, &text);
- assert(p != end && *p++ == ' ');
- assert(p != end && *p++ == '0');
- assert(p != end && *p++ == ' ');
- /* data */
- assert(p != end && *p++ == '0');
- assert(p != end && *p++ == ' ');
- assert(p != end && *p++ == '0');
- assert(p != end && *p++ == '\n');
- assert(p == end);
- /*
- * "text" is "mm->end_code - mm->start_code" at execve(2) time.
- * munmap() doesn't change it. It can be anything (just link
- * statically). It can't be 0 because executing to this point
- * implies at least 1 page of code.
- */
- assert(text > 0);
- /*
- * These two are always equal. Always 0 for statically linked
- * executables and sometimes 0 for dynamically linked executables.
- * There is no way to tell one from another without parsing ELF
- * which is too much for this test.
- */
- assert(resident == shared);
- return EXIT_SUCCESS;
- }
- int main(void)
- {
- int rv = EXIT_SUCCESS;
- #ifdef TEST_VSYSCALL
- vsyscall();
- #endif
- switch (g_vsyscall) {
- case 0:
- g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_0;
- g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_0;
- break;
- case 1:
- g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_1;
- g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_1;
- break;
- case 2:
- g_proc_pid_maps_vsyscall = proc_pid_maps_vsyscall_2;
- g_proc_pid_smaps_vsyscall = proc_pid_smaps_vsyscall_2;
- break;
- default:
- abort();
- }
- g_protection_key_support = protection_key_support();
- pid_t pid = fork();
- if (pid == -1) {
- perror("fork");
- return EXIT_FAILURE;
- } else if (pid == 0) {
- rv = ptrace(PTRACE_TRACEME, 0, NULL, NULL);
- if (rv != 0) {
- if (errno == EPERM) {
- fprintf(stderr,
- "Did you know? ptrace(PTRACE_TRACEME) doesn't work under strace.\n"
- );
- kill(getppid(), SIGTERM);
- return EXIT_FAILURE;
- }
- perror("ptrace PTRACE_TRACEME");
- return EXIT_FAILURE;
- }
- /*
- * Hide "segfault at ..." messages. Signal handler won't run.
- */
- struct sigaction act = {};
- act.sa_flags = SA_SIGINFO;
- act.sa_sigaction = sigaction_SIGSEGV;
- sigaction(SIGSEGV, &act, NULL);
- #ifdef __amd64__
- munmap(NULL, ((size_t)1 << 47) - 4096);
- #elif defined __i386__
- {
- size_t len;
- for (len = -4096;; len -= 4096) {
- munmap(NULL, len);
- }
- }
- #else
- #error "implement 'unmap everything'"
- #endif
- return EXIT_FAILURE;
- } else {
- /*
- * TODO find reliable way to signal parent that munmap(2) completed.
- * Child can't do it directly because it effectively doesn't exist
- * anymore. Looking at child's VM files isn't 100% reliable either:
- * due to a bug they may not become empty or empty-like.
- */
- sleep(1);
- if (rv == EXIT_SUCCESS) {
- rv = test_proc_pid_maps(pid);
- }
- if (rv == EXIT_SUCCESS) {
- rv = test_proc_pid_numa_maps(pid);
- }
- if (rv == EXIT_SUCCESS) {
- rv = test_proc_pid_smaps(pid);
- }
- if (rv == EXIT_SUCCESS) {
- rv = test_proc_pid_smaps_rollup(pid);
- }
- if (rv == EXIT_SUCCESS) {
- rv = test_proc_pid_statm(pid);
- }
- /* Cut the rope. */
- int wstatus;
- waitpid(pid, &wstatus, 0);
- assert(WIFSTOPPED(wstatus));
- assert(WSTOPSIG(wstatus) == SIGSEGV);
- }
- return rv;
- }
- #else
- int main(void)
- {
- return 4;
- }
- #endif
|