1 /* 2 * Copyright (c) 2019 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 * Fork and exec tiny 1 page executable which precisely controls its VM. 18 * Test /proc/$PID/maps 19 * Test /proc/$PID/smaps 20 * Test /proc/$PID/smaps_rollup 21 * Test /proc/$PID/statm 22 * 23 * FIXME require CONFIG_TMPFS which can be disabled 24 * FIXME test other values from "smaps" 25 * FIXME support other archs 26 */ 27 #undef NDEBUG 28 #include <assert.h> 29 #include <errno.h> 30 #include <sched.h> 31 #include <signal.h> 32 #include <stdint.h> 33 #include <stdio.h> 34 #include <string.h> 35 #include <stdlib.h> 36 #include <sys/mount.h> 37 #include <sys/types.h> 38 #include <sys/stat.h> 39 #include <fcntl.h> 40 #include <unistd.h> 41 #include <sys/syscall.h> 42 #include <sys/uio.h> 43 #include <linux/kdev_t.h> 44 45 static inline long sys_execveat(int dirfd, const char *pathname, char **argv, char **envp, int flags) 46 { 47 return syscall(SYS_execveat, dirfd, pathname, argv, envp, flags); 48 } 49 50 static void make_private_tmp(void) 51 { 52 if (unshare(CLONE_NEWNS) == -1) { 53 if (errno == ENOSYS || errno == EPERM) { 54 exit(4); 55 } 56 exit(1); 57 } 58 if (mount(NULL, "/", NULL, MS_PRIVATE|MS_REC, NULL) == -1) { 59 exit(1); 60 } 61 if (mount(NULL, "/tmp", "tmpfs", 0, NULL) == -1) { 62 exit(1); 63 } 64 } 65 66 static pid_t pid = -1; 67 static void ate(void) 68 { 69 if (pid > 0) { 70 kill(pid, SIGTERM); 71 } 72 } 73 74 struct elf64_hdr { 75 uint8_t e_ident[16]; 76 uint16_t e_type; 77 uint16_t e_machine; 78 uint32_t e_version; 79 uint64_t e_entry; 80 uint64_t e_phoff; 81 uint64_t e_shoff; 82 uint32_t e_flags; 83 uint16_t e_ehsize; 84 uint16_t e_phentsize; 85 uint16_t e_phnum; 86 uint16_t e_shentsize; 87 uint16_t e_shnum; 88 uint16_t e_shstrndx; 89 }; 90 91 struct elf64_phdr { 92 uint32_t p_type; 93 uint32_t p_flags; 94 uint64_t p_offset; 95 uint64_t p_vaddr; 96 uint64_t p_paddr; 97 uint64_t p_filesz; 98 uint64_t p_memsz; 99 uint64_t p_align; 100 }; 101 102 #ifdef __x86_64__ 103 #define PAGE_SIZE 4096 104 #define VADDR (1UL << 32) 105 #define MAPS_OFFSET 73 106 107 #define syscall 0x0f, 0x05 108 #define mov_rdi(x) \ 109 0x48, 0xbf, \ 110 (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff, \ 111 ((x)>>32)&0xff, ((x)>>40)&0xff, ((x)>>48)&0xff, ((x)>>56)&0xff 112 113 #define mov_rsi(x) \ 114 0x48, 0xbe, \ 115 (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff, \ 116 ((x)>>32)&0xff, ((x)>>40)&0xff, ((x)>>48)&0xff, ((x)>>56)&0xff 117 118 #define mov_eax(x) \ 119 0xb8, (x)&0xff, ((x)>>8)&0xff, ((x)>>16)&0xff, ((x)>>24)&0xff 120 121 static const uint8_t payload[] = { 122 /* Casually unmap stack, vDSO and everything else. */ 123 /* munmap */ 124 mov_rdi(VADDR + 4096), 125 mov_rsi((1ULL << 47) - 4096 - VADDR - 4096), 126 mov_eax(11), 127 syscall, 128 129 /* Ping parent. */ 130 /* write(0, &c, 1); */ 131 0x31, 0xff, /* xor edi, edi */ 132 0x48, 0x8d, 0x35, 0x00, 0x00, 0x00, 0x00, /* lea rsi, [rip] */ 133 0xba, 0x01, 0x00, 0x00, 0x00, /* mov edx, 1 */ 134 mov_eax(1), 135 syscall, 136 137 /* 1: pause(); */ 138 mov_eax(34), 139 syscall, 140 141 0xeb, 0xf7, /* jmp 1b */ 142 }; 143 144 static int make_exe(const uint8_t *payload, size_t len) 145 { 146 struct elf64_hdr h; 147 struct elf64_phdr ph; 148 149 struct iovec iov[3] = { 150 {&h, sizeof(struct elf64_hdr)}, 151 {&ph, sizeof(struct elf64_phdr)}, 152 {(void *)payload, len}, 153 }; 154 int fd, fd1; 155 char buf[64]; 156 157 memset(&h, 0, sizeof(h)); 158 h.e_ident[0] = 0x7f; 159 h.e_ident[1] = 'E'; 160 h.e_ident[2] = 'L'; 161 h.e_ident[3] = 'F'; 162 h.e_ident[4] = 2; 163 h.e_ident[5] = 1; 164 h.e_ident[6] = 1; 165 h.e_ident[7] = 0; 166 h.e_type = 2; 167 h.e_machine = 0x3e; 168 h.e_version = 1; 169 h.e_entry = VADDR + sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr); 170 h.e_phoff = sizeof(struct elf64_hdr); 171 h.e_shoff = 0; 172 h.e_flags = 0; 173 h.e_ehsize = sizeof(struct elf64_hdr); 174 h.e_phentsize = sizeof(struct elf64_phdr); 175 h.e_phnum = 1; 176 h.e_shentsize = 0; 177 h.e_shnum = 0; 178 h.e_shstrndx = 0; 179 180 memset(&ph, 0, sizeof(ph)); 181 ph.p_type = 1; 182 ph.p_flags = (1<<2)|1; 183 ph.p_offset = 0; 184 ph.p_vaddr = VADDR; 185 ph.p_paddr = 0; 186 ph.p_filesz = sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + sizeof(payload); 187 ph.p_memsz = sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + sizeof(payload); 188 ph.p_align = 4096; 189 190 fd = openat(AT_FDCWD, "/tmp", O_WRONLY|O_EXCL|O_TMPFILE, 0700); 191 if (fd == -1) { 192 exit(1); 193 } 194 195 if (writev(fd, iov, 3) != sizeof(struct elf64_hdr) + sizeof(struct elf64_phdr) + len) { 196 exit(1); 197 } 198 199 /* Avoid ETXTBSY on exec. */ 200 snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd); 201 fd1 = open(buf, O_RDONLY|O_CLOEXEC); 202 close(fd); 203 204 return fd1; 205 } 206 #endif 207 208 #ifdef __x86_64__ 209 int main(void) 210 { 211 int pipefd[2]; 212 int exec_fd; 213 214 atexit(ate); 215 216 make_private_tmp(); 217 218 /* Reserve fd 0 for 1-byte pipe ping from child. */ 219 close(0); 220 if (open("/", O_RDONLY|O_DIRECTORY|O_PATH) != 0) { 221 return 1; 222 } 223 224 exec_fd = make_exe(payload, sizeof(payload)); 225 226 if (pipe(pipefd) == -1) { 227 return 1; 228 } 229 if (dup2(pipefd[1], 0) != 0) { 230 return 1; 231 } 232 233 pid = fork(); 234 if (pid == -1) { 235 return 1; 236 } 237 if (pid == 0) { 238 sys_execveat(exec_fd, "", NULL, NULL, AT_EMPTY_PATH); 239 return 1; 240 } 241 242 char _; 243 if (read(pipefd[0], &_, 1) != 1) { 244 return 1; 245 } 246 247 struct stat st; 248 if (fstat(exec_fd, &st) == -1) { 249 return 1; 250 } 251 252 /* Generate "head -n1 /proc/$PID/maps" */ 253 char buf0[256]; 254 memset(buf0, ' ', sizeof(buf0)); 255 int len = snprintf(buf0, sizeof(buf0), 256 "%08lx-%08lx r-xp 00000000 %02lx:%02lx %llu", 257 VADDR, VADDR + PAGE_SIZE, 258 MAJOR(st.st_dev), MINOR(st.st_dev), 259 (unsigned long long)st.st_ino); 260 buf0[len] = ' '; 261 snprintf(buf0 + MAPS_OFFSET, sizeof(buf0) - MAPS_OFFSET, 262 "/tmp/#%llu (deleted)\n", (unsigned long long)st.st_ino); 263 264 265 /* Test /proc/$PID/maps */ 266 { 267 char buf[256]; 268 ssize_t rv; 269 int fd; 270 271 snprintf(buf, sizeof(buf), "/proc/%u/maps", pid); 272 fd = open(buf, O_RDONLY); 273 if (fd == -1) { 274 return 1; 275 } 276 rv = read(fd, buf, sizeof(buf)); 277 assert(rv == strlen(buf0)); 278 assert(memcmp(buf, buf0, strlen(buf0)) == 0); 279 } 280 281 /* Test /proc/$PID/smaps */ 282 { 283 char buf[1024]; 284 ssize_t rv; 285 int fd; 286 287 snprintf(buf, sizeof(buf), "/proc/%u/smaps", pid); 288 fd = open(buf, O_RDONLY); 289 if (fd == -1) { 290 return 1; 291 } 292 rv = read(fd, buf, sizeof(buf)); 293 assert(0 <= rv && rv <= sizeof(buf)); 294 295 assert(rv >= strlen(buf0)); 296 assert(memcmp(buf, buf0, strlen(buf0)) == 0); 297 298 #define RSS1 "Rss: 4 kB\n" 299 #define RSS2 "Rss: 0 kB\n" 300 #define PSS1 "Pss: 4 kB\n" 301 #define PSS2 "Pss: 0 kB\n" 302 assert(memmem(buf, rv, RSS1, strlen(RSS1)) || 303 memmem(buf, rv, RSS2, strlen(RSS2))); 304 assert(memmem(buf, rv, PSS1, strlen(PSS1)) || 305 memmem(buf, rv, PSS2, strlen(PSS2))); 306 307 static const char *S[] = { 308 "Size: 4 kB\n", 309 "KernelPageSize: 4 kB\n", 310 "MMUPageSize: 4 kB\n", 311 "Anonymous: 0 kB\n", 312 "AnonHugePages: 0 kB\n", 313 "Shared_Hugetlb: 0 kB\n", 314 "Private_Hugetlb: 0 kB\n", 315 "Locked: 0 kB\n", 316 }; 317 int i; 318 319 for (i = 0; i < sizeof(S)/sizeof(S[0]); i++) { 320 assert(memmem(buf, rv, S[i], strlen(S[i]))); 321 } 322 } 323 324 /* Test /proc/$PID/smaps_rollup */ 325 { 326 char bufr[256]; 327 memset(bufr, ' ', sizeof(bufr)); 328 len = snprintf(bufr, sizeof(bufr), 329 "%08lx-%08lx ---p 00000000 00:00 0", 330 VADDR, VADDR + PAGE_SIZE); 331 bufr[len] = ' '; 332 snprintf(bufr + MAPS_OFFSET, sizeof(bufr) - MAPS_OFFSET, 333 "[rollup]\n"); 334 335 char buf[1024]; 336 ssize_t rv; 337 int fd; 338 339 snprintf(buf, sizeof(buf), "/proc/%u/smaps_rollup", pid); 340 fd = open(buf, O_RDONLY); 341 if (fd == -1) { 342 return 1; 343 } 344 rv = read(fd, buf, sizeof(buf)); 345 assert(0 <= rv && rv <= sizeof(buf)); 346 347 assert(rv >= strlen(bufr)); 348 assert(memcmp(buf, bufr, strlen(bufr)) == 0); 349 350 assert(memmem(buf, rv, RSS1, strlen(RSS1)) || 351 memmem(buf, rv, RSS2, strlen(RSS2))); 352 assert(memmem(buf, rv, PSS1, strlen(PSS1)) || 353 memmem(buf, rv, PSS2, strlen(PSS2))); 354 355 static const char *S[] = { 356 "Anonymous: 0 kB\n", 357 "AnonHugePages: 0 kB\n", 358 "Shared_Hugetlb: 0 kB\n", 359 "Private_Hugetlb: 0 kB\n", 360 "Locked: 0 kB\n", 361 }; 362 int i; 363 364 for (i = 0; i < sizeof(S)/sizeof(S[0]); i++) { 365 assert(memmem(buf, rv, S[i], strlen(S[i]))); 366 } 367 } 368 369 /* Test /proc/$PID/statm */ 370 { 371 char buf[64]; 372 ssize_t rv; 373 int fd; 374 375 snprintf(buf, sizeof(buf), "/proc/%u/statm", pid); 376 fd = open(buf, O_RDONLY); 377 if (fd == -1) { 378 return 1; 379 } 380 rv = read(fd, buf, sizeof(buf)); 381 assert(rv == 7 * 2); 382 383 assert(buf[0] == '1'); /* ->total_vm */ 384 assert(buf[1] == ' '); 385 assert(buf[2] == '0' || buf[2] == '1'); /* rss */ 386 assert(buf[3] == ' '); 387 assert(buf[4] == '0' || buf[2] == '1'); /* file rss */ 388 assert(buf[5] == ' '); 389 assert(buf[6] == '1'); /* ELF executable segments */ 390 assert(buf[7] == ' '); 391 assert(buf[8] == '0'); 392 assert(buf[9] == ' '); 393 assert(buf[10] == '0'); /* ->data_vm + ->stack_vm */ 394 assert(buf[11] == ' '); 395 assert(buf[12] == '0'); 396 assert(buf[13] == '\n'); 397 } 398 399 return 0; 400 } 401 #else 402 int main(void) 403 { 404 return 4; 405 } 406 #endif 407