#include <asm/io.h>
#include <asm/pgtable_64.h>
#include <linux/device.h>
#include <linux/fdtable.h>
#include <linux/fs.h>
#include <linux/ipc_namespace.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/msg.h>
#include <linux/pgtable.h>
#include <linux/pipe_fs_i.h>
#include <linux/slab.h>

#include "include/lkm.h"

#define DEVICE_NAME "lkm"
#define CLASS_NAME  "lkmclass"

int lkm_init_device_driver(void);
static int lkm_init(void);
static void lkm_cleanup(void);
int lkm_open(struct inode *inode, struct file *filp);
int lkm_release(struct inode *inode, struct file *filep);
long lkm_ioctl(struct file *file, unsigned int num, long unsigned int param);
void lkm_alloc(size_t id, size_t loc, size_t size);
pte_t *page_walk_pte(size_t addr);
void dpm_test(void);
void bpf_dpm_split(size_t size);

static struct file_operations lkm_fops = {
	.owner = THIS_MODULE,
	.open = lkm_open,
	.release = lkm_release,
	.unlocked_ioctl = lkm_ioctl,
};

static int lkm_major;
static struct class *lkm_class;
static struct device *lkm_device;

static pgd_t *_init_top_pgt;
static void *(*module_alloc)(unsigned long);
static int (*set_memory_rox)(unsigned long, int);
struct msg_queue;
struct msg_queue *(*ipc_obtain_object_check)(struct ipc_ids *ns, int id);
unsigned long slub_addr_base;
unsigned long __text;
unsigned long vmemmap_base;
unsigned long vmalloc_base;
unsigned long __end;
static struct kprobe kp = { .symbol_name = "init_top_pgt" };
static struct kprobe kp1 = { .symbol_name = "module_alloc" };
static struct kprobe kp2 = { .symbol_name = "set_memory_rox" };
static struct kprobe kp3 = { .symbol_name = "ipc_obtain_object_check" };
static struct kprobe kp4 = { .symbol_name = "slub_addr_base" };
static struct kprobe kp5 = { .symbol_name = "_text" };
static struct kprobe kp6 = { .symbol_name = "vmemmap_base" };
static struct kprobe kp7 = { .symbol_name = "vmalloc_base" };
static struct kprobe kp8 = { .symbol_name = "__brk_dmi_alloc" };

#define OBJS_SZ (1ULL << 20)
void *objs[OBJS_SZ];

/*
 * Initialization device driver
 */
#define V5_15
#define V6_5
#define V6_6
#define V6_8
int
lkm_init_device_driver(void)
{
	int ret;
	printk(KERN_INFO "lkm:init_device_driver: start\n");

	ret = register_chrdev(0, DEVICE_NAME, &lkm_fops);
	if (ret < 0)
		goto ERROR;
	lkm_major = ret;
#if defined(V6_5) || defined(V6_8) || defined(V6_6)
	lkm_class = class_create(CLASS_NAME);
#else
	lkm_class = class_create(0, CLASS_NAME);
#endif
	if (IS_ERR(lkm_class)) {
		ret = PTR_ERR(lkm_class);
		goto ERROR1;
	}

	lkm_device = device_create(lkm_class, 0, MKDEV(lkm_major, 0), 0,
	    DEVICE_NAME);
	if (IS_ERR(lkm_device)) {
		ret = PTR_ERR(lkm_device);
		goto ERROR2;
	}

	printk(KERN_INFO
	    "lkm:init_device_driver: done '/dev/%s c %d' 0 created\n",
	    DEVICE_NAME, lkm_major);
	return 0;

ERROR2:
	printk(KERN_ERR "lkm:init_device_driver: class destroy\n");
	class_unregister(lkm_class);
	class_destroy(lkm_class);
ERROR1:
	printk(KERN_ERR "lkm:init_device_driver: unregister chrdev\n");
	unregister_chrdev(lkm_major, CLASS_NAME);
ERROR:
	printk(KERN_ERR "lkm:init_device_driver: fail %d\n", ret);
	lkm_device = 0;
	lkm_class = 0;
	lkm_major = -1;
	return ret;
}

/*
 * Initialization
 */
static int
lkm_init(void)
{
	int ret;
	printk(KERN_INFO "lkm:init: start\n");

	ret = lkm_init_device_driver();
	if (ret)
		goto ERROR;

	register_kprobe(&kp);
	_init_top_pgt = (void *)kp.addr;
	register_kprobe(&kp1);
	module_alloc = (void *(*)(unsigned long))kp1.addr;
	register_kprobe(&kp2);
	set_memory_rox = (int (*)(unsigned long, int))kp2.addr;
	register_kprobe(&kp3);
	ipc_obtain_object_check = (struct msg_queue *
	    (*)(struct ipc_ids *, int)) kp3.addr;
	register_kprobe(&kp4);
	slub_addr_base = *(unsigned long *)kp4.addr;
	register_kprobe(&kp5);
	__text = (unsigned long)kp5.addr;
	register_kprobe(&kp6);
	vmemmap_base = *(unsigned long *)kp6.addr;
	register_kprobe(&kp7);
	vmalloc_base = *(unsigned long *)kp7.addr;
	register_kprobe(&kp8);
	__end = (unsigned long)kp8.addr;

	printk(KERN_INFO "lkm:init: init_top_pgt %016zx\n",
	    (size_t)_init_top_pgt);
	printk(KERN_INFO "lkm:init: lkm_class    %016zx\n", (size_t)lkm_class);
	printk(KERN_INFO "lkm:init: lkm_ioctl    %016zx\n", (size_t)lkm_ioctl);

	printk(KERN_INFO "lkm:init: done\n");
	return 0;

ERROR:
	printk(KERN_ERR "lkm:init: error\n");
	return ret;
}

/*
 * Cleanup
 */
static void
lkm_cleanup(void)
{
	printk(KERN_INFO "lkm:cleanup\n");
	unregister_kprobe(&kp);
	unregister_kprobe(&kp1);
	unregister_kprobe(&kp2);
	unregister_kprobe(&kp3);
	unregister_kprobe(&kp4);
	unregister_kprobe(&kp5);
	unregister_kprobe(&kp6);
	unregister_kprobe(&kp7);
	unregister_kprobe(&kp8);
	device_destroy(lkm_class, MKDEV(lkm_major, 0));
	class_unregister(lkm_class);
	class_destroy(lkm_class);
	unregister_chrdev(lkm_major, DEVICE_NAME);
}

/*
 * Close "/dev/lkm"
 */
int
lkm_release(struct inode *inode, struct file *filep)
{
	printk(KERN_INFO "lkm:release\n");
	module_put(THIS_MODULE);
	return 0;
}
EXPORT_SYMBOL(lkm_release);

/*
 * Open "/dev/lkm"
 */
int
lkm_open(struct inode *inode, struct file *filp)
{
	printk(KERN_INFO "lkm:open\n");
	try_module_get(THIS_MODULE);
	return 0;
}
EXPORT_SYMBOL(lkm_open);

#pragma GCC push_options
#pragma GCC optimize("O0")
void
lkm_alloc(size_t id, size_t loc, size_t size)
{
	switch (loc) {
	case 0:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 1:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 2:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 3:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 4:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 5:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 6:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 7:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 8:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 9:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 10:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 11:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 12:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 13:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 14:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 15:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 16:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 17:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 18:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 19:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 20:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 21:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 22:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 23:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 24:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 25:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 26:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 27:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 28:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 29:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 30:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	case 31:
		objs[id] = kmalloc(size, GFP_KERNEL);
		break;
	default:
		printk(KERN_ERR "lkm:alloc: invalid loc\n");
		break;
	}
}
#pragma GCC pop_options

pte_t *
page_walk_pte(size_t addr)
{
	unsigned long above = ((long)addr) >> __VIRTUAL_MASK_SHIFT;
	pgd_t *pgd;
	p4d_t *p4d;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;

	if (above != 0 && above != -1UL)
		return 0;

	pgd = pgd_offset_pgd(_init_top_pgt, addr);
	if (pgd_none(*pgd))
		return 0;

	p4d = p4d_offset(pgd, addr);
	if (!p4d_present(*p4d))
		return 0;

	pud = pud_offset(p4d, addr);
	if (!pud_present(*pud))
		return 0;

	if (pud_large(*pud))
		return 0;

	pmd = pmd_offset(pud, addr);
	if (!pmd_present(*pmd))
		return 0;

	if (pmd_large(*pmd))
		return 0;

	pte = pte_offset_kernel(pmd, addr);
	if (pte_none(*pte))
		return 0;

	return pte;
}
pmd_t *
page_walk_pmd(size_t addr)
{
	unsigned long above = ((long)addr) >> __VIRTUAL_MASK_SHIFT;
	pgd_t *pgd;
	p4d_t *p4d;
	pud_t *pud;
	pmd_t *pmd;

	if (above != 0 && above != -1UL)
		return 0;

	pgd = pgd_offset_pgd(_init_top_pgt, addr);
	if (pgd_none(*pgd))
		return 0;

	p4d = p4d_offset(pgd, addr);
	if (!p4d_present(*p4d))
		return 0;

	pud = pud_offset(p4d, addr);
	if (!pud_present(*pud))
		return 0;

	if (pud_large(*pud))
		return 0;

	pmd = pmd_offset(pud, addr);
	if (!pmd_present(*pmd))
		return 0;

	return pmd;
}

bool
isLargePage(size_t addr)
{
	unsigned long above = ((long)addr) >> __VIRTUAL_MASK_SHIFT;
	pgd_t *pgd;
	p4d_t *p4d;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;

	if (above != 0 && above != -1UL)
		return 0;

	pgd = pgd_offset_pgd(_init_top_pgt, addr);
	if (pgd_none(*pgd))
		return 0;

	p4d = p4d_offset(pgd, addr);
	if (!p4d_present(*p4d))
		return 0;

	pud = pud_offset(p4d, addr);
	if (!pud_present(*pud))
		return 0;

	if (pud_large(*pud))
		return 0;

	pmd = pmd_offset(pud, addr);
	if (!pmd_present(*pmd))
		return 0;

	if (pmd_large(*pmd))
		return 1;

	pte = pte_offset_kernel(pmd, addr);
	if (pte_none(*pte))
		return 0;

	return 0;
}

void
dpm_test(void)
{
	int max_large = 10;
	size_t start = 0;
	size_t end = 32ULL * 1024ULL * 1024ULL * 1024ULL;
	printk(KERN_INFO
	    "lkm:dpm_test: physical mapping paddr [%016zx - %016zx] vaddr [%016zx - %016zx]\n",
	    start, end, (size_t)__va(start), (size_t)__va(end));
	size_t count = 0;
	size_t paddr;
	for (paddr = start; paddr < end; paddr += PMD_SIZE) {
		size_t vaddr = (size_t)phys_to_virt(paddr);
		pte_t *pte = page_walk_pte(vaddr);
		if (pte) {
			size_t diff = 0;
			size_t writable = 0;
			size_t pagecachedisabled = 0;
			size_t global = 0;
			size_t _paddr;
			for (_paddr = paddr; _paddr < paddr + PMD_SIZE;
			    _paddr += PAGE_SIZE) {
				size_t vaddr = (size_t)phys_to_virt(_paddr);
				pte_t *pte = page_walk_pte(vaddr);
				if (!pte) {
					// printk(KERN_DEBUG "lkm:dpm_test:
					// vaddr %016zx pte 0\n", vaddr);
					continue;
				}
				diff += !!((pte->pte & 0xfff) ^ 0x163);
				writable += !!(pte->pte & _PAGE_RW);
				pagecachedisabled += !!(pte->pte & _PAGE_PCD);
				global += !!(pte->pte & _PAGE_GLOBAL);
				// printk(KERN_DEBUG "lkm:dpm_test: vaddr %016zx
				// pte %016zx\n", vaddr, pte->pte);
				count++;
			}
			printk(KERN_DEBUG
			    "lkm:dpm_test: vaddr %016zx with [%4zd diff] [%4zd wr] [%4zd pcd] [%4zd g]\n",
			    vaddr, diff, writable, pagecachedisabled, global);
		} else if (max_large > 0 && isLargePage(vaddr)) {
			max_large--;
			printk(KERN_DEBUG "lkm:dpm_test: 2MB: vaddr %016zx\n",
			    vaddr);
		}
	}
	printk(KERN_INFO "lkm:dpm_test: count/max   %5zu/%zu\n", count,
	    (end - start) / PAGE_SIZE);

	pmd_t *_text_pmd = page_walk_pmd(__text);
	pmd_t *_end_pmd = page_walk_pmd(__end);
	printk(KERN_INFO "lkm:dpm_text: _text %016zx\n", __text);
	printk(KERN_INFO "lkm:dpm_text: _end  %016zx\n", __end);
	size_t _text_pa = (size_t)__va(_text_pmd->pmd & 0xfffffffff000);
	size_t _end_pa = (size_t)__va(_end_pmd->pmd & 0xfffffffff000);
	printk(KERN_INFO "lkm:dpm_text: _text_pa %016zx %s\n", _text_pa,
	    isLargePage(_text_pa) ? "2MB page" : "4k page");
	printk(KERN_INFO "lkm:dpm_text: _end_pa  %016zx %s\n", _end_pa,
	    isLargePage(_end_pa) ? "2MB page" : "4k page");
}

void
bpf_dpm_split(size_t size)
{
	void *obj;
	pte_t *pte;
	size_t vaddr;

	obj = module_alloc(size);
	set_memory_rox((unsigned long)obj, size >> 12);
	pte = page_walk_pte((unsigned long)obj);
	vaddr = (size_t)phys_to_virt(pte->pte & 0xffffffffff000);
	printk(KERN_INFO "obj %016zx vaddr %016zx\n", (unsigned long)obj,
	    vaddr);
}

struct msg_queue {
	struct kern_ipc_perm q_perm;
	time64_t q_stime;
	time64_t q_rtime;
	time64_t q_ctime;
	unsigned long q_cbytes;
	unsigned long q_qnum;
	unsigned long q_qbytes;
	struct pid *q_lspid;
	struct pid *q_lrpid;
	struct list_head q_messages;
	struct list_head q_receivers;
	struct list_head q_senders;
};
#define IPC_MSG_IDS 1
#define msg_ids(ns) ((ns)->ids[IPC_MSG_IDS])
static unsigned long
msg_copy_to_user(size_t msqid, size_t type)
{
	struct msg_queue *msq;
	struct msg_msg *msg;
	struct msg_msg *found = 0;
	struct ipc_namespace *ns;
	struct ipc_ids *ids;
	rcu_read_lock();
	// printk(KERN_DEBUG "lkm:msg_copy_to_user: msqid %ld\n", msqid);
	ns = current->nsproxy->ipc_ns;
	// printk(KERN_DEBUG "lkm:msg_copy_to_user: ns %016zd\n", (unsigned
	// long)ns);
	ids = &msg_ids(ns);
	// printk(KERN_DEBUG "lkm:msg_copy_to_user: ids %016zd\n", (unsigned
	// long)ids);
	msq = ipc_obtain_object_check(ids, msqid);
	// printk(KERN_DEBUG "lkm:msg_copy_to_user: msq %016zd\n", (unsigned
	// long)msq);
	if (IS_ERR(msq))
		goto RETURN;
	list_for_each_entry(msg, &msq->q_messages, m_list)
	{
		if (msg->m_type == type) {
			found = msg;
			// printk(KERN_DEBUG "lkm:msg_copy_to_user: found
			// %016zx\n", (unsigned long)found);
			break;
		}
	}
RETURN:
	rcu_read_unlock();
	return (unsigned long)found;
}

#ifndef SLAB_BASE_ADDR
size_t SLAB_BASE_ADDR = 0;
#endif
#ifndef SLAB_END_ADDR
size_t SLAB_END_ADDR = 0;
#endif

static int
bad_address(void *p)
{
	unsigned long dummy;
	return get_kernel_nofault(dummy, (unsigned long *)p);
}

noinline void
pagetable_walk(msg_t *msg)
{
	size_t address = msg->ptw.uaddr;
	pgd_t *base = __va(read_cr3_pa());
	pgd_t *pgd = base + pgd_index(address);
	p4d_t *p4d;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;

	if (bad_address(pgd))
		goto out;

	// pr_info("PGD %lx ", pgd_val(*pgd));
	msg->ptw.pgde = pgd_val(*pgd);
	if (!pgd_present(*pgd))
		goto out;

	p4d = p4d_offset(pgd, address);
	if (bad_address(p4d))
		goto out;

	// pr_cont("P4D %lx ", p4d_val(*p4d));
	msg->ptw.p4de = p4d_val(*p4d);
	if (!p4d_present(*p4d) || p4d_large(*p4d))
		goto out;

	pud = pud_offset(p4d, address);
	if (bad_address(pud))
		goto out;

	// pr_cont("PUD %lx ", pud_val(*pud));
	msg->ptw.pude = pud_val(*pud);
	if (!pud_present(*pud) || pud_large(*pud))
		goto out;

	pmd = pmd_offset(pud, address);
	if (bad_address(pmd))
		goto out;

	// pr_cont("PMD %lx ", pmd_val(*pmd));
	msg->ptw.pmde = pmd_val(*pmd);
	if (!pmd_present(*pmd) || pmd_large(*pmd))
		goto out;

	pte = pte_offset_kernel(pmd, address);
	if (bad_address(pte))
		goto out;

	// pr_cont("PTE %lx", pte_val(*pte));
	msg->ptw.pte = pte_val(*pte);

out:
	// pr_cont("\n");
	return;
}

/*
 * ioctl code
 */
long
lkm_ioctl(struct file *_, unsigned int num, long unsigned int param)
{
	int ret;
	msg_t msg;
	size_t *uaddr = 0;
	size_t tmp = -1;
	// void *dummy;
	size_t id;
	struct files_struct *files;
	struct file *file;
	struct fdtable *fdt;
	struct pipe_inode_info *pipe;
	unsigned int tail;
	unsigned int head;
	unsigned int mask;

	// printk(KERN_INFO "lkm:ioctl: start num 0x%08x param 0x%016lx\n", num,
	// param);

	ret = copy_from_user((msg_t *)&msg, (msg_t *)param, sizeof(msg_t));
	if (ret < 0) {
		printk(KERN_ERR "lkm:ioctl: copy_from_user failed\n");
		ret = -1;
		goto RETURN;
	}

	switch (num) {
	case LKM_ALLOC:
		if (msg.al.id >= OBJS_SZ) {
			printk(KERN_ERR "lkm:ioctl: index %zd too large\n",
			    msg.al.id);
			ret = -1;
			goto RETURN;
		}
		if (msg.al.size > 4096) {
			printk(KERN_ERR "lkm:ioctl: size %zd too big\n",
			    msg.al.size);
			ret = -1;
			goto RETURN;
		}
		objs[msg.al.id] = kmalloc(msg.al.size, GFP_KERNEL);
		break;

	case LKM_FREE:
		if (msg.fr.id >= OBJS_SZ) {
			printk(KERN_ERR "lkm:ioctl: %zd too large\n",
			    msg.fr.id);
			ret = -1;
			goto RETURN;
		}
		kfree(objs[msg.fr.id]);
		break;

	case LKM_WRITE:
		// printk(KERN_INFO "lkm:ioctl: arbitrary write\n");
		*(size_t *)msg.wr.kaddr = msg.wr.value;
		break;

	case LKM_READ:
		// printk(KERN_INFO "lkm:ioctl: arbitrary read\n");
		tmp = *(size_t *)msg.rd.kaddr;
		uaddr = (size_t *)msg.rd.uaddr;
		goto COPY_TMP_TO_USER;

	case LKM_DPM_TEST:
		dpm_test();
		break;

	case LKM_ACCESS_PRIMITIVE:
		*(volatile size_t *)msg.ap.addr;
		break;

	case LKM_BPF_DPM_SPLIT:
		// printk(KERN_INFO "lkm:ioctl: bpf dpm split\n");
		bpf_dpm_split(msg.dpms.size);
		break;

	case LKM_MSG_MSG_LEAK:
		tmp = msg_copy_to_user(msg.mrd.msqid, msg.mrd.mtype);
		uaddr = (size_t *)msg.mrd.uaddr;
		// printk(KERN_DEBUG "lkm:ioctl: msg_msg leak %16zx\n", tmp);
		goto COPY_TMP_TO_USER;

	case LKM_DPM_LEAK:
		tmp = (size_t)__va(0);
		uaddr = (size_t *)msg.drd.uaddr;
		// printk(KERN_DEBUG "lkm:ioctl: dpm leak %16zx\n", tmp);
		goto COPY_TMP_TO_USER;

	case LKM_VIRTUAL_BASE_LEAK:
		tmp = (size_t)slub_addr_base;
		uaddr = (size_t *)msg.drd.uaddr;
		printk(KERN_INFO
		    "lkm:ioctl: slub_addr_base %16zx within [%016zx %016zx]\n",
		    tmp, SLAB_BASE_ADDR, SLAB_END_ADDR);
		goto COPY_TMP_TO_USER;

	case LKM_SEQ_FILE_LEAK:
		id = msg.frd.fd;
		files = current->files;

		spin_lock(&files->file_lock);
		fdt = files_fdtable(files);
		file = fdt->fd[id];
		spin_unlock(&files->file_lock);

		tmp = (size_t)file->private_data;
		// printk(KERN_INFO "lkm:ioctl: seq_file %016zx\n", tmp);
		uaddr = (size_t *)msg.frd.uaddr;
		goto COPY_TMP_TO_USER;

	case LKM_CRED_LEAK:
		tmp = (size_t)current->real_cred;
		uaddr = (size_t *)msg.drd.uaddr;
		// printk(KERN_INFO "lkm:ioctl: current->cred %016zx\n", tmp);
		goto COPY_TMP_TO_USER;

	case LKM_FILE_LEAK:
		id = msg.frd.fd;
		files = current->files;

		spin_lock(&files->file_lock);
		fdt = files_fdtable(files);
		tmp = (size_t)fdt->fd[id];
		spin_unlock(&files->file_lock);

		// printk(KERN_INFO "lkm:ioctl: file %016zx\n", tmp);
		uaddr = (size_t *)msg.frd.uaddr;
		goto COPY_TMP_TO_USER;

	case LKM_STACK_LEAK:
		tmp = (size_t)current->stack;
		uaddr = (size_t *)msg.drd.uaddr;
		// printk(KERN_DEBUG "lkm:ioctl: current->stack %16zx with
		// current %16zx within [%016zx %016zx]\n", tmp, (size_t)&dummy,
		// VMALLOC_START, VMALLOC_END);
		goto COPY_TMP_TO_USER;

	case LKM_CODE_LEAK:
		tmp = (size_t)__text;
		uaddr = (size_t *)msg.drd.uaddr;
		goto COPY_TMP_TO_USER;

	case LKM_VMEMMAP_LEAK:
		tmp = (size_t)vmemmap_base;
		uaddr = (size_t *)msg.drd.uaddr;
		goto COPY_TMP_TO_USER;

	case LKM_VMALLOC_BASE_LEAK:
		tmp = (size_t)vmalloc_base;
		uaddr = (size_t *)msg.drd.uaddr;
		goto COPY_TMP_TO_USER;

	case LKM_ARB_FREE:
		printk(KERN_DEBUG "lkm:ioctl: free %016zx\n", msg.af.kaddr);
		kfree((void *)msg.af.kaddr);
		break;

	case LKM_PIPE_BUFFER_LEAK:
		id = msg.pbrd.fd;
		files = current->files;

		spin_lock(&files->file_lock);
		fdt = files_fdtable(files);
		file = fdt->fd[id];
		spin_unlock(&files->file_lock);

		pipe = file->private_data;
		tail = pipe->tail;
		head = pipe->head;
		mask = pipe->ring_size - 1;
		if (msg.pbrd.rdend)
			tmp = (size_t)&pipe->bufs[tail & mask];
		else
			tmp = (size_t)&pipe->bufs[(head - 1) & mask];
		// printk(KERN_DEBUG "lkm:ioctl: pipe_buffer %016zx\n", tmp);
		uaddr = (size_t *)msg.pbrd.uaddr;
		goto COPY_TMP_TO_USER;

	case LKM_PAGETABLE_WALK:
		printk(KERN_DEBUG "lkm:ioctl: page table walk\n");
		msg.ptw.pgde = 0;
		msg.ptw.p4de = 0;
		msg.ptw.pude = 0;
		msg.ptw.pmde = 0;
		msg.ptw.pte = 0;
		pagetable_walk(&msg);
		ret = copy_to_user((msg_t *)param, (msg_t *)&msg,
		    sizeof(msg_t));
		if (ret < 0) {
			printk(KERN_ALERT "lkm:ioctl: copy_to_user failed\n");
			ret = -1;
			goto RETURN;
		}
		break;

	case LKM_IS_4KB:
		ret = !isLargePage(msg.ap.addr);
		// printk(KERN_DEBUG "lkm:ioctl: %016zx is %s page\n",
		// msg.ap.addr, ret == 1 ? "4kB" : "2MB");
		return ret;

	default:
		printk(KERN_ERR "lkm:ioctl: no valid num\n");
		ret = -1;
		goto RETURN;
	}
	ret = 0;
	goto DONE;

COPY_TMP_TO_USER:
	if (uaddr == 0) { // && "forgot to set uaddr");
		printk(KERN_ERR "lkm:ioctl: uaddr == 0\n");
		// ret = -1;
		goto RETURN;
	}
	if (tmp == -1) { // && "forgot to set tmp");
		printk(KERN_ERR "lkm:ioctl: tmp == -1\n");
		// ret = -1;
		goto RETURN;
	}
	// printk(KERN_INFO "lkm:ioctl: copy 0x%016zx to mem[0x%016zx]\n", tmp,
	// (size_t)uaddr);
	ret = copy_to_user(uaddr, &tmp, sizeof(size_t));
	if (ret < 0) {
		printk(KERN_ERR "lkm:ioctl: copy_to_user failed\n");
		ret = -1;
		goto RETURN;
	}
	ret = 0;

DONE:
	// printk(KERN_INFO "lkm:ioctl: done\n");

RETURN:
	return ret;
}
EXPORT_SYMBOL(lkm_ioctl);

module_init(lkm_init);
module_exit(lkm_cleanup);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lukas Maar");
MODULE_DESCRIPTION("LKM");
MODULE_VERSION("0.1");