lguest: audit pagetables on a user fault

This has proven useful in identifying changed pagetables we haven't
been told about.  It's a slow hack tho.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
---
 drivers/lguest/page_tables.c |  143 +++++++++++++++++++++++++++++++++++++++----
 1 file changed, 133 insertions(+), 10 deletions(-)

diff --git a/drivers/lguest/page_tables.c b/drivers/lguest/page_tables.c
index 81d0c60..fd3e1f5 100644
--- a/drivers/lguest/page_tables.c
+++ b/drivers/lguest/page_tables.c
@@ -188,6 +188,98 @@ static void check_gpgd(struct lg_cpu *cpu, pgd_t gpgd)
 		kill_guest(cpu, "bad page directory entry");
 }
 
+static pgd_t read_one_gpgd(struct lg_cpu *cpu, unsigned int i, unsigned int j)
+{
+	return lgread(cpu, cpu->lg->pgdirs[i].gpgdir + j*sizeof(pgd_t), pgd_t);
+}
+
+static void audit_pgtable(struct lg_cpu *cpu, unsigned int i)
+{
+	unsigned int j, k;
+
+	/* Every PGD entry except the Switcher at the top */
+	for (j = 0; j < SWITCHER_PGD_INDEX; j++) {
+		pgd_t gpgd, *spgd = cpu->lg->pgdirs[i].pgdir + j;
+		if (!(pgd_flags(*spgd) & _PAGE_PRESENT))
+			continue;
+
+		gpgd = read_one_gpgd(cpu, i, j);
+		if (!(pgd_flags(gpgd) & _PAGE_PRESENT)) {
+			printk("audit: pgtable %i (curr %i) gpgd %i not present!\n",
+				   i, cpu->cpu_pgd, j);
+			continue;
+		}
+		
+		for (k = 0; k < PTRS_PER_PTE; k++) {
+			pte_t gpte, *spte;
+			unsigned long pfn;
+
+			gpte = lgread(cpu, (pgd_pfn(gpgd) << PAGE_SHIFT)
+				      + k * sizeof(pte_t), pte_t);
+
+			spte = __va(pgd_pfn(*spgd) << PAGE_SHIFT);
+			spte += k;
+
+			if (!(pte_flags(*spte) & _PAGE_PRESENT))
+				continue;
+			if (!(pte_flags(gpte) & _PAGE_PRESENT)) {
+				printk("audit: pgtable %i (curr %i) gpgd %i, pte %i not present!\n",
+					   i, cpu->cpu_pgd, j, k);
+				continue;
+			}
+
+			/* We can't be writeable if they are read-only */
+			if ((pte_flags(*spte) & _PAGE_RW)
+			    && !(pte_flags(gpte) & _PAGE_RW)) {
+				printk("Audit: pgtable %i (curr %i) gpgd %i, pte %i should not be writable (shadow %#lx guest %#lx)!\n",
+					   i, cpu->cpu_pgd, j, k,
+					   pte_flags(*spte),
+					   pte_flags(gpte));
+				continue;
+			}
+
+			/* Other flags should match, except PAGE_GLOBAL, ACCESSED. */
+			if ((pte_flags(gpte) & ~(_PAGE_GLOBAL|_PAGE_RW|_PAGE_ACCESSED))
+			    != (pte_flags(*spte) & ~(_PAGE_RW|_PAGE_ACCESSED))) {
+				printk("Audit: pgtable %i (curr %i) gpgd %i, pte %i flags differ (shadow %#lx guest %#lx)!\n",
+					   i, cpu->cpu_pgd, j, k,
+					   pte_flags(*spte),
+					   pte_flags(gpte));
+				continue;
+			}
+
+			/* Page referred to should match. */
+			pfn = get_pfn((unsigned long)cpu->lg->mem_base / PAGE_SIZE + pte_pfn(gpte), 0);
+			if (pfn == -1) {
+				printk("Audit: pgtable %i (curr %i) gpgd %i, pte %i invalid pfn (%lu)!\n",
+					   i, cpu->cpu_pgd, j, k, pte_pfn(gpte));
+				continue;
+			}
+			put_page(pfn_to_page(pfn));
+
+			if (pfn != pte_pfn(*spte)) {
+				printk("Audit: pgtable %i (curr %i) gpgd %i, pte %i different pfn (guest=%lu/%#lx, host=%lu/%#lx)!\n",
+					   i, cpu->cpu_pgd, j, k,
+					   pte_pfn(gpte), pte_flags(gpte),
+					   pfn, pte_flags(*spte));
+				continue;
+			}
+		}
+	}
+}
+
+static void audit_pgtables(struct lg_cpu *cpu)
+{
+	unsigned int i;
+
+	/* Every shadow pagetable this Guest has */
+	for (i = 0; i < ARRAY_SIZE(cpu->lg->pgdirs); i++) {
+		if (cpu->lg->pgdirs[i].pgdir
+		    && cpu->lg->pgdirs[i].gpgdir != -1UL)
+			audit_pgtable(cpu, i);
+	}
+}
+
 /*H:330
  * (i) Looking up a page table entry when the Guest faults.
  *
@@ -209,8 +301,14 @@ int demand_page(struct lg_cpu *cpu, unsigned long vaddr, int errcode)
 	/* First step: get the top-level Guest page table entry. */
 	gpgd = lgread(cpu, gpgd_addr(cpu, vaddr), pgd_t);
 	/* Toplevel not present?  We can't map it in. */
-	if (!(pgd_flags(gpgd) & _PAGE_PRESENT))
+	if (!(pgd_flags(gpgd) & _PAGE_PRESENT)) {
+		if (errcode & 4) {
+			printk("User fault at %#lx on non-present toplevel\n",
+			       vaddr);
+			audit_pgtables(cpu);
+		}
 		return 0;
+	}
 
 	/* Now look at the matching shadow entry. */
 	spgd = spgd_addr(cpu, cpu->cpu_pgd, vaddr);
@@ -236,17 +334,33 @@ int demand_page(struct lg_cpu *cpu, unsigned long vaddr, int errcode)
 	gpte = lgread(cpu, gpte_ptr, pte_t);
 
 	/* If this page isn't in the Guest page tables, we can't page it in. */
-	if (!(pte_flags(gpte) & _PAGE_PRESENT))
+	if (!(pte_flags(gpte) & _PAGE_PRESENT)) {
+		if (errcode & 4) {
+//			printk("User fault at %#lx on non-present pte\n",
+//			       vaddr);
+//			audit_pgtables(cpu);
+		}
 		return 0;
+	}
 
-	/* Check they're not trying to write to a page the Guest wants
-	 * read-only (bit 2 of errcode == write). */
-	if ((errcode & 2) && !(pte_flags(gpte) & _PAGE_RW))
+	/* User access to a kernel-only page? (bit 3 == user access) */
+	if ((errcode & 4) && !(pte_flags(gpte) & _PAGE_USER)) {
+		if (errcode & 4) {
+			printk("User fault at %#lx on kernel pte\n",
+			       vaddr);
+			audit_pgtables(cpu);
+		}
 		return 0;
+	}
 
-	/* User access to a kernel-only page? (bit 3 == user access) */
-	if ((errcode & 4) && !(pte_flags(gpte) & _PAGE_USER))
+	/* Check they're not trying to write to a page the Guest wants
+	 * read-only (bit 2 of errcode == write). */
+	if ((errcode & 2) && !(pte_flags(gpte) & _PAGE_RW)) {
+//		if (errcode & 4)
+//			printk("User fault at %#lx on RO pte\n",
+//			       vaddr);
 		return 0;
+	}
 
 	/* Check that the Guest PTE flags are OK, and the page number is below
 	 * the pfn_limit (ie. not mapping the Launcher binary). */
@@ -372,12 +486,21 @@ unsigned long guest_pa(struct lg_cpu *cpu, unsigned long vaddr)
 	/* First step: get the top-level Guest page table entry. */
 	gpgd = lgread(cpu, gpgd_addr(cpu, vaddr), pgd_t);
 	/* Toplevel not present?  We can't map it in. */
-	if (!(pgd_flags(gpgd) & _PAGE_PRESENT))
+	if (!(pgd_flags(gpgd) & _PAGE_PRESENT)) {
 		kill_guest(cpu, "Bad address %#lx", vaddr);
+		printk("Bad address %#lx\n", vaddr);
+		audit_pgtables(cpu);
+		WARN_ON(1);
+		return -1UL;
+	}
 
 	gpte = lgread(cpu, gpte_addr(gpgd, vaddr), pte_t);
-	if (!(pte_flags(gpte) & _PAGE_PRESENT))
-		kill_guest(cpu, "Bad address %#lx", vaddr);
+	if (!(pte_flags(gpte) & _PAGE_PRESENT)) {
+		kill_guest(cpu, "Bad pte address %#lx", vaddr);
+		printk("Bad pte address %#lx\n", vaddr);
+		audit_pgtables(cpu);
+		WARN_ON(1);
+	}
 
 	return pte_pfn(gpte) * PAGE_SIZE | (vaddr & ~PAGE_MASK);
 }
