Two-level page permissions fixes
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 18 Feb 2009 01:18:31 +0000 (17:18 -0800)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 18 Feb 2009 01:18:31 +0000 (17:18 -0800)
showmapping now properly considers the PDE.  PDE permissions are set
during pgdir_walks so that the userspace UVPT mapping can actually see
pages other than 'UPD'.  Added checks for permissions in check_boot.

kern/monitor.c
kern/pmap.c

index 99b71e1..5716a2d 100644 (file)
@@ -131,7 +131,7 @@ int mon_showmapping(int argc, char **argv, struct Trapframe *tf)
                return 1;
        }
        pde_t* pgdir = (pde_t*)vpd;
-       pte_t* pte;
+       pte_t *pte, *pde;
        struct Page* page;
        uintptr_t start, i;
        size_t size;
@@ -146,12 +146,14 @@ int mon_showmapping(int argc, char **argv, struct Trapframe *tf)
        for(i = 0; i < size; i += PGSIZE, start += PGSIZE) {
                page = page_lookup(pgdir, (void*)start, &pte);
                cprintf("%08p  ", start);
-               if (page) 
+               if (page) {
+                       pde = &pgdir[PDX(start)];
+                       // for a jumbo, pde = pte and PTE_PS (better be) = 1
                        cprintf("%08p  %1d  %1d  %1d  %1d  %1d  %1d %1d\n", page2pa(page), 
                               (*pte & PTE_PS) >> 7, (*pte & PTE_D) >> 6, (*pte & PTE_A) >> 5,
                               (*pte & PTE_PCD) >> 4, (*pte & PTE_PWT) >> 3, 
-                              (*pte & PTE_U) >> 2, (*pte & PTE_W) >> 1);
-               else
+                              (*pte & *pde & PTE_U) >> 2, (*pte & *pde & PTE_W) >> 1);
+               else
                        cprintf("%08p\n", 0);
        }
        return 0;
@@ -161,11 +163,13 @@ int mon_setmapperm(int argc, char **argv, struct Trapframe *tf)
 {
        if (argc < 2) {
                cprintf("Sets VIRT_ADDR's mapping's permissions to PERMS (in hex)\n");
+               cprintf("Only affects the lowest level PTE.  To adjust the PDE, do the math.\n");
+               cprintf("Be careful with this around UVPT, VPT, and friends.\n");
                cprintf("Usage: setmapperm VIRT_ADDR PERMS\n");
                return 1;
        }
        pde_t* pgdir = (pde_t*)vpd;
-       pte_t* pte;
+       pte_t *pte, *pde;
        struct Page* page;
        uintptr_t va;
        va = ROUNDDOWN(strtol(argv[1], 0, 16), PGSIZE);
@@ -174,18 +178,19 @@ int mon_setmapperm(int argc, char **argv, struct Trapframe *tf)
                cprintf("No such mapping\n");
                return 1;
        }
+       pde = &pgdir[PDX(va)];
        cprintf("   Virtual    Physical  Ps Dr Ac CD WT U W\n");
        cprintf("------------------------------------------\n");
        cprintf("%08p  %08p  %1d  %1d  %1d  %1d  %1d  %1d %1d\n", va, page2pa(page), 
               (*pte & PTE_PS) >> 7, (*pte & PTE_D) >> 6, (*pte & PTE_A) >> 5, 
-              (*pte & PTE_PCD) >> 4, (*pte & PTE_PWT) >> 3, (*pte & PTE_U) >> 2, 
-              (*pte & PTE_W) >> 1);
+              (*pte & PTE_PCD) >> 4, (*pte & PTE_PWT) >> 3, (*pte & *pde & PTE_U) >> 2, 
+              (*pte & *pde & PTE_W) >> 1);
        *pte = PTE_ADDR(*pte) | (*pte & PTE_PS) |
               (PGOFF(strtol(argv[2], 0, 16)) & ~PTE_PS ) | PTE_P;
        cprintf("%08p  %08p  %1d  %1d  %1d  %1d  %1d  %1d %1d\n", va, page2pa(page), 
               (*pte & PTE_PS) >> 7, (*pte & PTE_D) >> 6, (*pte & PTE_A) >> 5, 
-              (*pte & PTE_PCD) >> 4, (*pte & PTE_PWT) >> 3, (*pte & PTE_U) >> 2, 
-              (*pte & PTE_W) >> 1);
+              (*pte & PTE_PCD) >> 4, (*pte & PTE_PWT) >> 3, (*pte & *pde & PTE_U) >> 2, 
+              (*pte & *pde & PTE_W) >> 1);
        return 0;
 }
 
index 068fac4..b3ad4f2 100644 (file)
@@ -155,6 +155,9 @@ boot_alloc(uint32_t n, uint32_t align)
 //
 // Supports returning jumbo (4MB PSE) PTEs.  To create with a jumbo, pass in 2.
 // 
+// Maps non-PSE PDEs as U/W.  W so the kernel can, U so the user can read via
+// UVPT.  UVPT security comes from the UVPT mapping (U/R).  All other kernel pages
+// protected at the second layer
 static pte_t*
 boot_pgdir_walk(pde_t *pgdir, uintptr_t la, int create)
 {
@@ -176,7 +179,7 @@ boot_pgdir_walk(pde_t *pgdir, uintptr_t la, int create)
        }
        new_table = boot_alloc(PGSIZE, PGSIZE);
        memset(new_table, 0, PGSIZE);
-       *the_pde = (pde_t)PADDR(new_table) | PTE_P | PTE_W;
+       *the_pde = (pde_t)PADDR(new_table) | PTE_P | PTE_W | PTE_U;
        return &((pde_t*)KADDR(PTE_ADDR(*the_pde)))[PTX(la)];
 }
 
@@ -433,12 +436,13 @@ i386_vm_init(void)
 // but it is a pretty good sanity check. 
 //
 static physaddr_t check_va2pa(pde_t *pgdir, uintptr_t va);
+static pte_t get_vaperms(pde_t *pgdir, uintptr_t va);
 
 static void
 check_boot_pgdir(bool pse)
 {
        uint32_t i, n;
-       pde_t *pgdir;
+       pde_t *pgdir, pte;
 
        pgdir = boot_pgdir;
 
@@ -463,7 +467,6 @@ check_boot_pgdir(bool pse)
        else
                for (i = 0; i < maxpa; i += PGSIZE)
                        assert(check_va2pa(pgdir, KERNBASE + i) == i);
-               
 
        // check kernel stack
        for (i = 0; i < KSTKSIZE; i += PGSIZE)
@@ -490,6 +493,41 @@ check_boot_pgdir(bool pse)
                        break;
                }
        }
+
+       // check permissions
+       // user read-only.  check for user and write, should be only user
+       // eagle-eyed viewers should be able to explain the extra cases
+       for (i = UENVS; i < ULIM; i+=PGSIZE) {
+               pte = get_vaperms(pgdir, i);
+               if ((pte & PTE_P) && (i != UVPT+(VPT>>10))) {
+                       if (pte & PTE_PS) {
+                               assert((pte & PTE_U) != PTE_U);
+                               assert((pte & PTE_W) != PTE_W);
+                       } else {
+                               assert((pte & PTE_U) == PTE_U);
+                               assert((pte & PTE_W) != PTE_W);
+                       }
+               }
+       }
+       // kernel read-write.
+       for (i = ULIM; i <= KERNBASE + maxpa - PGSIZE; i+=PGSIZE) {
+               pte = get_vaperms(pgdir, i);
+               if ((pte & PTE_P) && (i != VPT+(UVPT>>10))) {
+                       assert((pte & PTE_U) != PTE_U);
+                       assert((pte & PTE_W) == PTE_W);
+               }
+       }
+       // special mappings
+       pte = get_vaperms(pgdir, UVPT+(VPT>>10));
+       assert((pte & PTE_U) != PTE_U);
+       assert((pte & PTE_W) != PTE_W);
+
+       // note this means the kernel cannot directly manipulate this virtual address
+       // convince yourself this isn't a big deal, eagle-eyes!
+       pte = get_vaperms(pgdir, VPT+(UVPT>>10));
+       assert((pte & PTE_U) != PTE_U);
+       assert((pte & PTE_W) != PTE_W);
+
        cprintf("check_boot_pgdir() succeeded!\n");
 }
 
@@ -513,6 +551,23 @@ check_va2pa(pde_t *pgdir, uintptr_t va)
                return ~0;
        return PTE_ADDR(p[PTX(va)]);
 }
+
+/* 
+ * This function returns a PTE with the aggregate permissions equivalent
+ * to walking the two levels of paging.  PPN = 0.  Somewhat fragile, in that
+ * it returns PTE_PS if either entry has PTE_PS (which should only happen
+ * for some of the recusive walks)
+ */
+
+static pte_t
+get_vaperms(pde_t *pgdir, uintptr_t va)
+{
+       pde_t* pde = &pgdir[PDX(va)];
+       pte_t* pte = pgdir_walk(pgdir, (void*)va, 0);
+       if (!pte || !(*pte & PTE_P))
+               return 0;
+       return PGOFF(*pde & *pte) + PTE_PS & (*pde | *pte);
+}
                
 // --------------------------------------------------------------
 // Tracking of physical pages.
@@ -662,7 +717,7 @@ pgdir_walk(pde_t *pgdir, const void *va, int create)
                return NULL;
        new_table->pp_ref = 1;
        memset(page2kva(new_table), 0, PGSIZE);
-       *the_pde = (pde_t)page2pa(new_table) | PTE_P | PTE_W;
+       *the_pde = (pde_t)page2pa(new_table) | PTE_P | PTE_W | PTE_U;
        return &((pde_t*)KADDR(PTE_ADDR(*the_pde)))[PTX(va)];
 }
 //