Memory protection and page fault handling
[akaros.git] / kern / pmap.c
index 3855853..62c6a91 100644 (file)
@@ -24,6 +24,8 @@ static char* boot_freemem;    // Pointer to next byte of free mem
 struct Page* pages;            // Virtual address of physical page array
 static struct Page_list page_free_list;        // Free list of physical pages
 
+extern struct Env *envs;
+
 // Global descriptor table.
 //
 // The kernel and user segments are identical (except for the DPL).
@@ -155,6 +157,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 +181,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)];
 }
 
@@ -240,10 +245,16 @@ i386_vm_init(void)
        size_t n;
        bool pse;
 
-       // check for PSE support (TODO)
-       pse = 1;
+       // check for PSE support
+       asm volatile ("movl    $1, %%eax;
+                   cpuid;
+                   andl    $0x00000008, %%edx"
+                     : "=d"(pse) 
+                                 : 
+                     : "%eax");
        // turn on PSE
        if (pse) {
+               cprintf("PSE capability detected.\n");
                uint32_t cr4;
                cr4 = rcr4();
                cr4 |= CR4_PSE;
@@ -354,7 +365,15 @@ i386_vm_init(void)
        //    - envs itself -- kernel RW, user NONE
        //    - the image of envs mapped at UENVS  -- kernel R, user R
        
-       // LAB 3: Your code here.
+       // round up to the nearest page
+       size_t env_array_size = ROUNDUP(NENV*sizeof(struct Env), PGSIZE);
+       envs = (struct Env*)boot_alloc(env_array_size, PGSIZE);
+       memset(envs, 0, env_array_size);
+       if (env_array_size > PTSIZE) {
+               warn("env_array_size bigger than PTSIZE, userland will not see all environments");
+               env_array_size = PTSIZE;
+       }
+       boot_map_segment(pgdir, UENVS, env_array_size, PADDR(envs), PTE_U);
 
        // Check that the initial page directory has been set up correctly.
        check_boot_pgdir(pse);
@@ -427,12 +446,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;
 
@@ -441,12 +461,10 @@ check_boot_pgdir(bool pse)
        for (i = 0; i < n; i += PGSIZE)
                assert(check_va2pa(pgdir, UPAGES + i) == PADDR(pages) + i);
 
-       /* // TODO - turn this on
        // check envs array (new test for lab 3)
        n = ROUNDUP(NENV*sizeof(struct Env), PGSIZE);
        for (i = 0; i < n; i += PGSIZE)
                assert(check_va2pa(pgdir, UENVS + i) == PADDR(envs) + i);
-       */
 
        // check phys mem
        //for (i = 0; KERNBASE + i != 0; i += PGSIZE)
@@ -457,7 +475,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)
@@ -470,7 +487,7 @@ check_boot_pgdir(bool pse)
                case PDX(UVPT):
                case PDX(KSTACKTOP-1):
                case PDX(UPAGES):
-               //case PDX(UENVS): // TODO - turn this on
+               case PDX(UENVS):
                        assert(pgdir[i]);
                        break;
                default:
@@ -484,6 +501,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");
 }
 
@@ -507,6 +559,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.
@@ -656,7 +725,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)];
 }
 //
@@ -784,8 +853,38 @@ static uintptr_t user_mem_check_addr;
 int
 user_mem_check(struct Env *env, const void *va, size_t len, int perm)
 {
-       // LAB 3: Your code here. 
+       // TODO - will need to sort this out wrt page faulting / PTE_P
+       // also could be issues with sleeping and waking up to find pages
+       // are unmapped, though i think the lab ignores this since the 
+       // kernel is uninterruptible
+       void *start, *end;
+       size_t num_pages, i;
+       pte_t *pte;
 
+       perm |= PTE_P;
+       start = ROUNDDOWN((void*)va, PGSIZE);
+       end = ROUNDUP((void*)va + len, PGSIZE);
+       if (start >= end) {
+               warn("Blimey!  Wrap around in VM range calculation!");  
+               return -E_FAULT;
+       }
+       num_pages = PPN(end - start);
+       for (i = 0; i < num_pages; i++, start += PGSIZE) {
+               pte = pgdir_walk(env->env_pgdir, start, 0);
+               // ensures the bits we want on are turned on.  if not, E_FAULT
+               if ( !pte || ((*pte & perm) != perm) ) {
+                       if (i = 0)
+                               user_mem_check_addr = (uintptr_t)va;
+                       else
+                               user_mem_check_addr = (uintptr_t)start;
+                       return -E_FAULT;
+               }
+       }
+       // this should never be needed, since the perms should catch it
+       if ((uintptr_t)end >= ULIM) {
+               warn ("I suck - Bug in user permission mappings!");
+               return -E_FAULT;
+       }
        return 0;
 }