Skip to content

Instantly share code, notes, and snippets.

@tkhai
Created May 20, 2019 13:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tkhai/40bda78e304d2fe0d90863214b9ac5b5 to your computer and use it in GitHub Desktop.
Save tkhai/40bda78e304d2fe0d90863214b9ac5b5 to your computer and use it in GitHub Desktop.
#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