Created
May 20, 2019 13:20
-
-
Save tkhai/40bda78e304d2fe0d90863214b9ac5b5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _GNU_SOURCE | |
#include <linux/mman.h> | |
#include <sys/mman.h> | |
#include <sys/types.h> | |
#include <signal.h> | |
#include <sched.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <fcntl.h> | |
#include <pthread.h> | |
#include <unistd.h> | |
#include <sys/syscall.h> | |
#include <errno.h> | |
#include <err.h> | |
#include <wait.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
/* Check process_vm_mmap() merges splitted vmas */ | |
#define __NR_process_vm_mmap 434 | |
#define PVMMAP_FIXED 0x01 | |
#define PVMMAP_FIXED_NOREPLACE 0x02 | |
#define PAGE_SIZE 4096 | |
#define MAX_SIZE (PAGE_SIZE * 32768 * 16UL * 6) | |
struct map { | |
unsigned long start, end, off; | |
char r, w, x, p; | |
}; | |
typedef _Bool bool; | |
static void *process_vm_mmap(pid_t pid, unsigned long src_addr, | |
unsigned long len, | |
unsigned long dst_addr, | |
unsigned long flags) | |
{ | |
unsigned long ret; | |
ret = syscall(__NR_process_vm_mmap, pid, src_addr, len, dst_addr, flags); | |
if (ret < PAGE_SIZE) { | |
errno = (int)ret; | |
return MAP_FAILED; | |
} | |
return (void *)ret; | |
} | |
static int get_maps_line(pid_t pid, unsigned long addr, struct map *map) | |
{ | |
char buf[64] = { 0 }; | |
char *line = NULL; | |
size_t n = 0; | |
int ret = -1; | |
FILE *fp; | |
snprintf(buf, sizeof(buf) - 1, "/proc/%d/maps", pid); | |
fp = fopen(buf, "r"); | |
if (fp == NULL) { | |
perror("fopen"); | |
goto out; | |
} | |
while (getline(&line, &n, fp) != -1) { | |
if (sscanf(line, "%lx-%lx %c%c%c%c %lx", &map->start, &map->end, | |
&map->r, &map->w, &map->x, &map->p, &map->off) != 7) { | |
printf("Can't parse map %s", line); | |
goto out; | |
} | |
if (map->start <= addr && map->end > addr) { | |
ret = 0; | |
goto out; | |
} | |
} | |
out: | |
fclose(fp); | |
return ret; | |
} | |
static int child(int sk, unsigned long size, pid_t ppid) | |
{ | |
unsigned long addr; | |
void *faddr; | |
if (read(sk, &addr, sizeof(unsigned long)) != sizeof(unsigned long)) { | |
warn("read: %d", __LINE__); | |
goto kill; | |
} | |
faddr = process_vm_mmap(ppid, (unsigned long)addr, size, 0, 0); | |
if (faddr == MAP_FAILED) { | |
warn("process_vm_mmap: %d", __LINE__); | |
goto kill; | |
} | |
if (write(sk, &faddr, sizeof(unsigned long)) != sizeof(unsigned long)) { | |
warn("write: %d", __LINE__); | |
goto kill; | |
} | |
while (1) | |
sleep(1); | |
return 0; | |
kill: | |
kill(ppid, SIGKILL); | |
return 1; | |
} | |
static int test(bool file, bool shared, bool huge, int map_prot, bool tail, | |
unsigned long size, unsigned long offset) | |
{ | |
int status, mmap_flags = 0, fd = -1; | |
void *addr, *faddr, *addr2 = NULL, *addr3; | |
long i, ret, retval = -1; | |
unsigned long size2; | |
struct map map; | |
pid_t pid, ppid = getpid(); | |
int sk[2]; | |
if (file) { | |
fd = open("/tmp/proc_vm_mmap.test", O_RDWR|O_CREAT); | |
if (fd < 0) { | |
perror("open"); | |
return -1; | |
} | |
if (ftruncate(fd, size)) { | |
perror("ftruncate"); | |
return -1; | |
} | |
} else { | |
mmap_flags |= MAP_ANONYMOUS; | |
} | |
if (shared) | |
mmap_flags |= MAP_SHARED; | |
else | |
mmap_flags |= MAP_PRIVATE; | |
if (huge) | |
mmap_flags |= MAP_HUGE_2MB; | |
if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sk) < 0) { | |
perror("socketpair"); | |
return -1; | |
} | |
pid = fork(); | |
if (pid < 0) { | |
perror("fork"); | |
return -1; | |
} | |
if (pid == 0) | |
return child(sk[0], size, ppid); | |
addr = mmap(0, size, map_prot, mmap_flags, fd, 0); | |
if (addr == MAP_FAILED) { | |
warn("mmap: %d", __LINE__); | |
goto kill; | |
} | |
if (huge) { | |
ret = madvise(addr, size, MADV_HUGEPAGE); | |
if (ret) { | |
perror("madvise"); | |
goto kill; | |
} | |
} | |
/* Notify child about addr */ | |
if (write(sk[1], (void *)&addr, sizeof(unsigned long)) != sizeof(unsigned long)) { | |
perror("write"); | |
goto kill; | |
} | |
/* Wait till child maps addr */ | |
if (read(sk[1], &faddr, sizeof(unsigned long)) != sizeof(unsigned long)) { | |
perror("Can't read"); | |
goto kill; | |
} | |
if (tail) { | |
size2 = size-offset; | |
addr2 = addr + offset; | |
faddr += offset; | |
} else { | |
size2 = offset; | |
addr2 = addr; | |
} | |
munmap(addr2, size2); | |
addr2 = process_vm_mmap(pid, (unsigned long)faddr, size2, | |
(unsigned long)addr2, PVMMAP_FIXED); | |
if (addr2 == MAP_FAILED) { | |
warn("process_vm_mmap: %d", __LINE__); | |
goto kill; | |
} | |
addr3 = process_vm_mmap(pid, (unsigned long)faddr, size2, | |
(unsigned long)addr2, PVMMAP_FIXED_NOREPLACE); | |
if (addr3 != MAP_FAILED) { | |
warn("process_vm_mmap did not fail: %d", __LINE__); | |
goto kill; | |
} | |
ret = get_maps_line(getpid(), (unsigned long)addr, &map); | |
if (ret) { | |
printf("Can't find map\n"); | |
goto kill; | |
} | |
if ((unsigned long)addr + size > map.end) { | |
printf("vma splited\n"); | |
goto kill; | |
} | |
if (((map.r == 'r') != !!(map_prot & PROT_READ)) || | |
((map.w == 'w') != !!(map_prot & PROT_WRITE)) || | |
((map.x == 'x') != !!(map_prot & PROT_EXEC)) || | |
((map.p == 'p') != !!(mmap_flags & MAP_PRIVATE))) { | |
printf("flags/prot changed\n"); | |
goto kill; | |
} | |
retval = 0; | |
kill: | |
printf("file=%d shared=%d huge=%d map_prot=%d tail=%d size=%lu offset=%lu: %s\n", | |
file, shared, huge, map_prot, tail, size, offset, | |
retval ? "FAIL" : "OK"); | |
kill(pid, SIGKILL); | |
if (fd >= 0) | |
close(fd); | |
munmap(addr, size); | |
wait(&status); | |
close(sk[0]); | |
close(sk[1]); | |
return retval; | |
} | |
int main() | |
{ | |
int file, shared, huge, map_prot, tail, ret; | |
unsigned long size, offset; | |
/* test(bool file, bool shared, bool huge, int map_prot, bool tail, unsigned long size, unsigned long offset) */ | |
for (file = 0; file <= 1; file++) | |
for (shared = 0; shared <= 1; shared++) | |
for (huge = 0; huge <= 1; huge++) | |
for (tail = 0; tail <= 1; tail++) | |
for (size = PAGE_SIZE; size <= MAX_SIZE; size <<= 1) | |
for (offset = PAGE_SIZE; offset < size; offset = (offset << 1)) { | |
map_prot = PROT_WRITE|PROT_READ; | |
while (1) { | |
ret = test(file, shared, huge, map_prot, tail, size, offset); | |
if (ret < 0) | |
exit(1); | |
if (map_prot == PROT_READ|PROT_WRITE|PROT_EXEC) | |
break; | |
map_prot |= PROT_EXEC; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment