589 lines · c
1/*2 * Copyright (c) 2019 Alexey Dobriyan <adobriyan@gmail.com>3 *4 * Permission to use, copy, modify, and distribute this software for any5 * purpose with or without fee is hereby granted, provided that the above6 * copyright notice and this permission notice appear in all copies.7 *8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.15 */16/*17 * Fork and exec tiny 1 page executable which precisely controls its VM.18 * Test /proc/$PID/maps19 * Test /proc/$PID/smaps20 * Test /proc/$PID/smaps_rollup21 * Test /proc/$PID/statm22 *23 * FIXME require CONFIG_TMPFS which can be disabled24 * FIXME test other values from "smaps"25 * FIXME support other archs26 */27#undef NDEBUG28#include <assert.h>29#include <errno.h>30#include <sched.h>31#include <signal.h>32#include <stdbool.h>33#include <stdint.h>34#include <stdio.h>35#include <string.h>36#include <stdlib.h>37#include <sys/mount.h>38#include <sys/types.h>39#include <sys/stat.h>40#include <sys/wait.h>41#include <fcntl.h>42#include <unistd.h>43#include <sys/syscall.h>44#include <sys/uio.h>45#include <linux/kdev_t.h>46#include <sys/time.h>47#include <sys/resource.h>48#include <linux/fs.h>49 50#include "../kselftest.h"51 52static inline long sys_execveat(int dirfd, const char *pathname, char **argv, char **envp, int flags)53{54 return syscall(SYS_execveat, dirfd, pathname, argv, envp, flags);55}56 57static void make_private_tmp(void)58{59 if (unshare(CLONE_NEWNS) == -1) {60 if (errno == ENOSYS || errno == EPERM) {61 exit(4);62 }63 exit(1);64 }65 if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) == -1) {66 exit(1);67 }68 if (mount(NULL, "/tmp", "tmpfs", 0, NULL) == -1) {69 exit(1);70 }71}72 73static pid_t pid = -1;74static void ate(void)75{76 if (pid > 0) {77 kill(pid, SIGTERM);78 }79}80 81struct elf64_hdr {82 uint8_t e_ident[16];83 uint16_t e_type;84 uint16_t e_machine;85 uint32_t e_version;86 uint64_t e_entry;87 uint64_t e_phoff;88 uint64_t e_shoff;89 uint32_t e_flags;90 uint16_t e_ehsize;91 uint16_t e_phentsize;92 uint16_t e_phnum;93 uint16_t e_shentsize;94 uint16_t e_shnum;95 uint16_t e_shstrndx;96};97 98struct elf64_phdr {99 uint32_t p_type;100 uint32_t p_flags;101 uint64_t p_offset;102 uint64_t p_vaddr;103 uint64_t p_paddr;104 uint64_t p_filesz;105 uint64_t p_memsz;106 uint64_t p_align;107};108 109#ifdef __x86_64__110#define PAGE_SIZE 4096111#define VADDR (1UL << 32)112#define MAPS_OFFSET 73113 114#define syscall 0x0f, 0x05115#define mov_rdi(x) \116 0x48, 0xbf, \117 (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff, \118 ((x)>>32)&0xff, ((x)>>40)&0xff, ((x)>>48)&0xff, ((x)>>56)&0xff119 120#define mov_rsi(x) \121 0x48, 0xbe, \122 (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff, \123 ((x)>>32)&0xff, ((x)>>40)&0xff, ((x)>>48)&0xff, ((x)>>56)&0xff124 125#define mov_eax(x) \126 0xb8, (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff127 128static const uint8_t payload[] = {129 /* Casually unmap stack, vDSO and everything else. */130 /* munmap */131 mov_rdi(VADDR + 4096),132 mov_rsi((1ULL << 47) - 4096 - VADDR - 4096),133 mov_eax(11),134 syscall,135 136 /* Ping parent. */137 /* write(0, &c, 1); */138 0x31, 0xff, /* xor edi, edi */139 0x48, 0x8d, 0x35, 0x00, 0x00, 0x00, 0x00, /* lea rsi, [rip] */140 0xba, 0x01, 0x00, 0x00, 0x00, /* mov edx, 1 */141 mov_eax(1),142 syscall,143 144 /* 1: pause(); */145 mov_eax(34),146 syscall,147 148 0xeb, 0xf7, /* jmp 1b */149};150 151static int make_exe(const uint8_t *payload, size_t len)152{153 struct elf64_hdr h;154 struct elf64_phdr ph;155 156 struct iovec iov[3] = {157 {&h, sizeof(struct elf64_hdr)},158 {&ph, sizeof(struct elf64_phdr)},159 {(void *)payload, len},160 };161 int fd, fd1;162 char buf[64];163 164 memset(&h, 0, sizeof(h));165 h.e_ident[0] = 0x7f;166 h.e_ident[1] = 'E';167 h.e_ident[2] = 'L';168 h.e_ident[3] = 'F';169 h.e_ident[4] = 2;170 h.e_ident[5] = 1;171 h.e_ident[6] = 1;172 h.e_ident[7] = 0;173 h.e_type = 2;174 h.e_machine = 0x3e;175 h.e_version = 1;176 h.e_entry = VADDR + sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr);177 h.e_phoff = sizeof(struct elf64_hdr);178 h.e_shoff = 0;179 h.e_flags = 0;180 h.e_ehsize = sizeof(struct elf64_hdr);181 h.e_phentsize = sizeof(struct elf64_phdr);182 h.e_phnum = 1;183 h.e_shentsize = 0;184 h.e_shnum = 0;185 h.e_shstrndx = 0;186 187 memset(&ph, 0, sizeof(ph));188 ph.p_type = 1;189 ph.p_flags = (1<<2)|1;190 ph.p_offset = 0;191 ph.p_vaddr = VADDR;192 ph.p_paddr = 0;193 ph.p_filesz = sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + len;194 ph.p_memsz = sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + len;195 ph.p_align = 4096;196 197 fd = openat(AT_FDCWD, "/tmp", O_WRONLY|O_EXCL|O_TMPFILE, 0700);198 if (fd == -1) {199 exit(1);200 }201 202 if (writev(fd, iov, 3) != sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + len) {203 exit(1);204 }205 206 /* Avoid ETXTBSY on exec. */207 snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd);208 fd1 = open(buf, O_RDONLY|O_CLOEXEC);209 close(fd);210 211 return fd1;212}213#endif214 215/*216 * 0: vsyscall VMA doesn't exist vsyscall=none217 * 1: vsyscall VMA is --xp vsyscall=xonly218 * 2: vsyscall VMA is r-xp vsyscall=emulate219 */220static volatile int g_vsyscall;221static const char *str_vsyscall;222 223static const char str_vsyscall_0[] = "";224static const char str_vsyscall_1[] =225"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n";226static const char str_vsyscall_2[] =227"ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n";228 229#ifdef __x86_64__230static void sigaction_SIGSEGV(int _, siginfo_t *__, void *___)231{232 _exit(g_vsyscall);233}234 235/*236 * vsyscall page can't be unmapped, probe it directly.237 */238static void vsyscall(void)239{240 pid_t pid;241 int wstatus;242 243 pid = fork();244 if (pid < 0) {245 fprintf(stderr, "fork, errno %d\n", errno);246 exit(1);247 }248 if (pid == 0) {249 struct rlimit rlim = {0, 0};250 (void)setrlimit(RLIMIT_CORE, &rlim);251 252 /* Hide "segfault at ffffffffff600000" messages. */253 struct sigaction act;254 memset(&act, 0, sizeof(struct sigaction));255 act.sa_flags = SA_SIGINFO;256 act.sa_sigaction = sigaction_SIGSEGV;257 (void)sigaction(SIGSEGV, &act, NULL);258 259 g_vsyscall = 0;260 /* gettimeofday(NULL, NULL); */261 uint64_t rax = 0xffffffffff600000;262 asm volatile (263 "call *%[rax]"264 : [rax] "+a" (rax)265 : "D" (NULL), "S" (NULL)266 : "rcx", "r11"267 );268 269 g_vsyscall = 1;270 *(volatile int *)0xffffffffff600000UL;271 272 g_vsyscall = 2;273 exit(g_vsyscall);274 }275 waitpid(pid, &wstatus, 0);276 if (WIFEXITED(wstatus)) {277 g_vsyscall = WEXITSTATUS(wstatus);278 } else {279 fprintf(stderr, "error: wstatus %08x\n", wstatus);280 exit(1);281 }282}283 284int main(void)285{286 int pipefd[2];287 int exec_fd;288 289 vsyscall();290 switch (g_vsyscall) {291 case 0:292 str_vsyscall = str_vsyscall_0;293 break;294 case 1:295 str_vsyscall = str_vsyscall_1;296 break;297 case 2:298 str_vsyscall = str_vsyscall_2;299 break;300 default:301 abort();302 }303 304 atexit(ate);305 306 make_private_tmp();307 308 /* Reserve fd 0 for 1-byte pipe ping from child. */309 close(0);310 if (open("/", O_RDONLY|O_DIRECTORY|O_PATH) != 0) {311 return 1;312 }313 314 exec_fd = make_exe(payload, sizeof(payload));315 316 if (pipe(pipefd) == -1) {317 return 1;318 }319 if (dup2(pipefd[1], 0) != 0) {320 return 1;321 }322 323 pid = fork();324 if (pid == -1) {325 return 1;326 }327 if (pid == 0) {328 sys_execveat(exec_fd, "", NULL, NULL, AT_EMPTY_PATH);329 return 1;330 }331 332 char _;333 if (read(pipefd[0], &_, 1) != 1) {334 return 1;335 }336 337 struct stat st;338 if (fstat(exec_fd, &st) == -1) {339 return 1;340 }341 342 /* Generate "head -n1 /proc/$PID/maps" */343 char buf0[256];344 memset(buf0, ' ', sizeof(buf0));345 int len = snprintf(buf0, sizeof(buf0),346 "%08lx-%08lx r-xp 00000000 %02lx:%02lx %llu",347 VADDR, VADDR + PAGE_SIZE,348 MAJOR(st.st_dev), MINOR(st.st_dev),349 (unsigned long long)st.st_ino);350 buf0[len] = ' ';351 snprintf(buf0 + MAPS_OFFSET, sizeof(buf0) - MAPS_OFFSET,352 "/tmp/#%llu (deleted)\n", (unsigned long long)st.st_ino);353 354 /* Test /proc/$PID/maps */355 {356 const size_t len = strlen(buf0) + strlen(str_vsyscall);357 char buf[256];358 ssize_t rv;359 int fd;360 361 snprintf(buf, sizeof(buf), "/proc/%u/maps", pid);362 fd = open(buf, O_RDONLY);363 if (fd == -1) {364 return 1;365 }366 rv = read(fd, buf, sizeof(buf));367 assert(rv == len);368 assert(memcmp(buf, buf0, strlen(buf0)) == 0);369 if (g_vsyscall > 0) {370 assert(memcmp(buf + strlen(buf0), str_vsyscall, strlen(str_vsyscall)) == 0);371 }372 }373 374 /* Test /proc/$PID/smaps */375 {376 char buf[4096];377 ssize_t rv;378 int fd;379 380 snprintf(buf, sizeof(buf), "/proc/%u/smaps", pid);381 fd = open(buf, O_RDONLY);382 if (fd == -1) {383 return 1;384 }385 rv = read(fd, buf, sizeof(buf));386 assert(0 <= rv && rv <= sizeof(buf));387 388 assert(rv >= strlen(buf0));389 assert(memcmp(buf, buf0, strlen(buf0)) == 0);390 391#define RSS1 "Rss: 4 kB\n"392#define RSS2 "Rss: 0 kB\n"393#define PSS1 "Pss: 4 kB\n"394#define PSS2 "Pss: 0 kB\n"395 assert(memmem(buf, rv, RSS1, strlen(RSS1)) ||396 memmem(buf, rv, RSS2, strlen(RSS2)));397 assert(memmem(buf, rv, PSS1, strlen(PSS1)) ||398 memmem(buf, rv, PSS2, strlen(PSS2)));399 400 static const char *S[] = {401 "Size: 4 kB\n",402 "KernelPageSize: 4 kB\n",403 "MMUPageSize: 4 kB\n",404 "Anonymous: 0 kB\n",405 "AnonHugePages: 0 kB\n",406 "Shared_Hugetlb: 0 kB\n",407 "Private_Hugetlb: 0 kB\n",408 "Locked: 0 kB\n",409 };410 int i;411 412 for (i = 0; i < ARRAY_SIZE(S); i++) {413 assert(memmem(buf, rv, S[i], strlen(S[i])));414 }415 416 if (g_vsyscall > 0) {417 assert(memmem(buf, rv, str_vsyscall, strlen(str_vsyscall)));418 }419 }420 421 /* Test /proc/$PID/smaps_rollup */422 {423 char bufr[256];424 memset(bufr, ' ', sizeof(bufr));425 len = snprintf(bufr, sizeof(bufr),426 "%08lx-%08lx ---p 00000000 00:00 0",427 VADDR, VADDR + PAGE_SIZE);428 bufr[len] = ' ';429 snprintf(bufr + MAPS_OFFSET, sizeof(bufr) - MAPS_OFFSET,430 "[rollup]\n");431 432 char buf[1024];433 ssize_t rv;434 int fd;435 436 snprintf(buf, sizeof(buf), "/proc/%u/smaps_rollup", pid);437 fd = open(buf, O_RDONLY);438 if (fd == -1) {439 return 1;440 }441 rv = read(fd, buf, sizeof(buf));442 assert(0 <= rv && rv <= sizeof(buf));443 444 assert(rv >= strlen(bufr));445 assert(memcmp(buf, bufr, strlen(bufr)) == 0);446 447 assert(memmem(buf, rv, RSS1, strlen(RSS1)) ||448 memmem(buf, rv, RSS2, strlen(RSS2)));449 assert(memmem(buf, rv, PSS1, strlen(PSS1)) ||450 memmem(buf, rv, PSS2, strlen(PSS2)));451 452 static const char *S[] = {453 "Anonymous: 0 kB\n",454 "AnonHugePages: 0 kB\n",455 "Shared_Hugetlb: 0 kB\n",456 "Private_Hugetlb: 0 kB\n",457 "Locked: 0 kB\n",458 };459 int i;460 461 for (i = 0; i < ARRAY_SIZE(S); i++) {462 assert(memmem(buf, rv, S[i], strlen(S[i])));463 }464 }465 466 /* Test /proc/$PID/statm */467 {468 char buf[64];469 ssize_t rv;470 int fd;471 472 snprintf(buf, sizeof(buf), "/proc/%u/statm", pid);473 fd = open(buf, O_RDONLY);474 if (fd == -1) {475 return 1;476 }477 rv = read(fd, buf, sizeof(buf));478 assert(rv == 7 * 2);479 480 assert(buf[0] == '1'); /* ->total_vm */481 assert(buf[1] == ' ');482 assert(buf[2] == '0' || buf[2] == '1'); /* rss */483 assert(buf[3] == ' ');484 assert(buf[4] == '0' || buf[2] == '1'); /* file rss */485 assert(buf[5] == ' ');486 assert(buf[6] == '1'); /* ELF executable segments */487 assert(buf[7] == ' ');488 assert(buf[8] == '0');489 assert(buf[9] == ' ');490 assert(buf[10] == '0'); /* ->data_vm + ->stack_vm */491 assert(buf[11] == ' ');492 assert(buf[12] == '0');493 assert(buf[13] == '\n');494 }495 496 /* Test PROCMAP_QUERY ioctl() for /proc/$PID/maps */497 {498 char path_buf[256], exp_path_buf[256];499 struct procmap_query q;500 int fd, err;501 502 snprintf(path_buf, sizeof(path_buf), "/proc/%u/maps", pid);503 fd = open(path_buf, O_RDONLY);504 if (fd == -1)505 return 1;506 507 /* CASE 1: exact MATCH at VADDR */508 memset(&q, 0, sizeof(q));509 q.size = sizeof(q);510 q.query_addr = VADDR;511 q.query_flags = 0;512 q.vma_name_addr = (__u64)(unsigned long)path_buf;513 q.vma_name_size = sizeof(path_buf);514 515 err = ioctl(fd, PROCMAP_QUERY, &q);516 assert(err == 0);517 518 assert(q.query_addr == VADDR);519 assert(q.query_flags == 0);520 521 assert(q.vma_flags == (PROCMAP_QUERY_VMA_READABLE | PROCMAP_QUERY_VMA_EXECUTABLE));522 assert(q.vma_start == VADDR);523 assert(q.vma_end == VADDR + PAGE_SIZE);524 assert(q.vma_page_size == PAGE_SIZE);525 526 assert(q.vma_offset == 0);527 assert(q.inode == st.st_ino);528 assert(q.dev_major == MAJOR(st.st_dev));529 assert(q.dev_minor == MINOR(st.st_dev));530 531 snprintf(exp_path_buf, sizeof(exp_path_buf),532 "/tmp/#%llu (deleted)", (unsigned long long)st.st_ino);533 assert(q.vma_name_size == strlen(exp_path_buf) + 1);534 assert(strcmp(path_buf, exp_path_buf) == 0);535 536 /* CASE 2: NO MATCH at VADDR-1 */537 memset(&q, 0, sizeof(q));538 q.size = sizeof(q);539 q.query_addr = VADDR - 1;540 q.query_flags = 0; /* exact match */541 542 err = ioctl(fd, PROCMAP_QUERY, &q);543 err = err < 0 ? -errno : 0;544 assert(err == -ENOENT);545 546 /* CASE 3: MATCH COVERING_OR_NEXT_VMA at VADDR - 1 */547 memset(&q, 0, sizeof(q));548 q.size = sizeof(q);549 q.query_addr = VADDR - 1;550 q.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;551 552 err = ioctl(fd, PROCMAP_QUERY, &q);553 assert(err == 0);554 555 assert(q.query_addr == VADDR - 1);556 assert(q.query_flags == PROCMAP_QUERY_COVERING_OR_NEXT_VMA);557 assert(q.vma_start == VADDR);558 assert(q.vma_end == VADDR + PAGE_SIZE);559 560 /* CASE 4: NO MATCH at VADDR + PAGE_SIZE */561 memset(&q, 0, sizeof(q));562 q.size = sizeof(q);563 q.query_addr = VADDR + PAGE_SIZE; /* point right after the VMA */564 q.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;565 566 err = ioctl(fd, PROCMAP_QUERY, &q);567 err = err < 0 ? -errno : 0;568 assert(err == -ENOENT);569 570 /* CASE 5: NO MATCH WRITABLE at VADDR */571 memset(&q, 0, sizeof(q));572 q.size = sizeof(q);573 q.query_addr = VADDR;574 q.query_flags = PROCMAP_QUERY_VMA_WRITABLE;575 576 err = ioctl(fd, PROCMAP_QUERY, &q);577 err = err < 0 ? -errno : 0;578 assert(err == -ENOENT);579 }580 581 return 0;582}583#else584int main(void)585{586 return 4;587}588#endif589