Created
May 15, 2019 14:58
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> | |
/* Check process_vm_mmap() merges splitted vmas */ | |
#define __NR_process_vm_mmap 428 | |
#define PVMMAP_FIXED 0x01 | |
#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() | |
{ | |
while (1) | |
sleep(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; | |
long i, ret, retval = -1; | |
unsigned long size2; | |
struct map map; | |
pid_t pid; | |
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; | |
pid = fork(); | |
if (pid < 0) { | |
perror("fork"); | |
return -1; | |
} | |
if (pid == 0) | |
return child(); | |
addr = mmap(0, size, map_prot, mmap_flags, fd, 0); | |
if (addr == MAP_FAILED) { | |
perror("mmap\n"); | |
goto kill; | |
} | |
if (huge) { | |
ret = madvise(addr, size, MADV_HUGEPAGE); | |
if (ret) { | |
perror("madvise"); | |
goto kill; | |
} | |
} | |
faddr = process_vm_mmap(-pid, (unsigned long)addr, size, 0, 0); | |
if (faddr == MAP_FAILED) { | |
warn("process_vm_mmap: %d", __LINE__); | |
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; | |
} | |
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); | |
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