pgtable-test: Add exec perm test
diff --git a/kvmtest/pgtable-test/exec-perm.c b/kvmtest/pgtable-test/exec-perm.c
new file mode 100644
index 0000000..72fceab
--- /dev/null
+++ b/kvmtest/pgtable-test/exec-perm.c
@@ -0,0 +1,129 @@
+/*
+ * XXX - Credit, licence, copyright
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/kvm.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "helpers.h"
+
+#define GUEST_PHYS_ADDR	(1UL << 30)
+#define GUEST_MMIO_ADDR	(1UL << 38)
+#define GUEST_MEM_SIZE	(PMD_SIZE*4)
+
+/*
+ * x0[0] = x1;
+ * x1 = x0[0];
+ */
+void guest_code()
+{
+	asm(
+	    "	str x1, [x0]		\n"
+	    "	brk #0			\n"
+	    "	ldr x1, [x0]		\n"
+	    "	brk #0			\n"
+	    ::);
+
+}
+#define GUEST_NR_INST	4
+#define GUEST_CODE_SIZE() (GUEST_NR_INST * ARM64_INST_SIZE)
+
+void guest_brk()
+{
+	asm("brk #0 \n" ::);
+}
+
+static void guest_write_memory(int vcpufd, struct kvm_run *run, uint64_t addr)
+{
+	set_one_reg(vcpufd, REG_X(0), addr);
+	set_one_reg(vcpufd, REG_X(1), 1);
+	set_one_reg(vcpufd, REG_PC, GUEST_PHYS_ADDR);
+	assert(vcpu_run(vcpufd, run) == KVM_EXIT_DEBUG);
+}
+
+static void guest_read_memory(int vcpufd, struct kvm_run *run, uint64_t addr)
+{
+	set_one_reg(vcpufd, REG_X(0), addr);
+	set_one_reg(vcpufd, REG_PC, GUEST_PHYS_ADDR + 2 * ARM64_INST_SIZE);
+	assert(vcpu_run(vcpufd, run) == KVM_EXIT_DEBUG);
+}
+
+struct kvm_userspace_memory_region guest_prepare_memory(int vmfd, int vcpufd)
+{
+	uint8_t *mem_code;
+
+	/* Note: this uses MAP_HUGETLB */
+	mem_code = mmap(NULL, GUEST_MEM_SIZE, PROT_READ | PROT_WRITE,
+			MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
+	if (mem_code == MAP_FAILED)
+		err(-ENOMEM, "Are there PMD hugetlb pages available on the system?");
+	memcpy(mem_code, guest_code, GUEST_CODE_SIZE());
+
+	/* Map code memory to the second page frame. */
+	struct kvm_userspace_memory_region region = {
+		.slot = 0,
+		.guest_phys_addr = GUEST_PHYS_ADDR,
+		.memory_size = GUEST_MEM_SIZE,
+		.userspace_addr = (uint64_t)mem_code,
+	};
+	KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
+
+	vm_add_mmio_page(vmfd, 1, GUEST_MMIO_ADDR);
+	set_one_reg(vcpufd, REG_X(3), GUEST_MMIO_ADDR);
+
+	return region;
+}
+
+int main(void)
+{
+	struct kvm_userspace_memory_region region;
+	uint64_t addr, offset, user_addr;
+	struct kvm_run *run = NULL;
+	struct kvm_dirty_log dlog;
+	int kvm, vmfd, vcpufd;
+	uint8_t *dirty_bm;
+	int i;
+
+	/* Prepare the guest */
+	kvm = get_kvm();
+	vmfd = create_vm(kvm);
+	vcpufd = create_vcpu(kvm, vmfd, &run, true);
+
+	/* Create a memslot backed by hugetlb */
+	region = guest_prepare_memory(vmfd, vcpufd);
+
+	/* Enable dirty logging, to force PTEs despite having huge pages */
+	region.flags = KVM_MEM_LOG_DIRTY_PAGES;
+	KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
+
+	/*
+	 * The address from which we read and write is in the same page as the
+	 * code, so the first read / exec faults should get us the RX mappings
+	 * we want, and then the write should trigger the bug Will found out
+	 */
+	offset = GUEST_CODE_SIZE();
+	addr = GUEST_PHYS_ADDR + offset;
+
+	/* Force user_mem_abort() to create the RX mapping, with PTEs*/
+	guest_read_memory(vcpufd, run, addr);
+
+	/* Disable dirty logging, to now get block mappings */
+	region.flags = 0;
+	KVM_IOCTL(vmfd, KVM_SET_USER_MEMORY_REGION, &region);
+
+	/* Write to the same page now, to trigger the write fault */
+	guest_write_memory(vcpufd, run, addr);
+
+	printf("Passing.\n");
+}