]> err.no Git - linux-2.6/blobdiff - arch/x86/mm/fault.c
x86: support gbpages in pagetable dump
[linux-2.6] / arch / x86 / mm / fault.c
index 14a0c6e541de3b70b0f6c1e4f982dd95500939fa..ad8b9733d6b3fee4d2f90e2981e54dc2b71299e4 100644 (file)
@@ -92,18 +92,13 @@ static int is_prefetch(struct pt_regs *regs, unsigned long addr,
        unsigned char *max_instr;
 
 #ifdef CONFIG_X86_32
-# ifdef CONFIG_X86_PAE
-       /* If it was a exec fault on NX page, ignore */
-       if (nx_enabled && (error_code & PF_INSTR))
+       if (!(__supported_pte_mask & _PAGE_NX))
                return 0;
-# else
-       return 0;
-# endif
-#else /* CONFIG_X86_64 */
+#endif
+
        /* If it was a exec fault on NX page, ignore */
        if (error_code & PF_INSTR)
                return 0;
-#endif
 
        instr = (unsigned char *)convert_ip_to_linear(current, regs);
        max_instr = instr + 15;
@@ -245,7 +240,8 @@ void dump_pagetable(unsigned long address)
        pud = pud_offset(pgd, address);
        if (bad_address(pud)) goto bad;
        printk("PUD %lx ", pud_val(*pud));
-       if (!pud_present(*pud)) goto ret;
+       if (!pud_present(*pud) || pud_large(*pud))
+               goto ret;
 
        pmd = pmd_offset(pud, address);
        if (bad_address(pmd)) goto bad;
@@ -383,10 +379,11 @@ static void show_fault_oops(struct pt_regs *regs, unsigned long error_code,
 #ifdef CONFIG_X86_32
        if (!oops_may_print())
                return;
+#endif
 
 #ifdef CONFIG_X86_PAE
        if (error_code & PF_INSTR) {
-               int level;
+               unsigned int level;
                pte_t *pte = lookup_address(address, &level);
 
                if (pte && pte_present(*pte) && !pte_exec(*pte))
@@ -395,28 +392,20 @@ static void show_fault_oops(struct pt_regs *regs, unsigned long error_code,
                                "(uid: %d)\n", current->uid);
        }
 #endif
-       printk(KERN_ALERT "BUG: unable to handle kernel ");
-       if (address < PAGE_SIZE)
-               printk(KERN_CONT "NULL pointer dereference");
-       else
-               printk(KERN_CONT "paging request");
-       printk(KERN_CONT " at %08lx\n", address);
 
-       printk(KERN_ALERT "IP:");
-       printk_address(regs->ip, 1);
-       dump_pagetable(address);
-#else /* CONFIG_X86_64 */
        printk(KERN_ALERT "BUG: unable to handle kernel ");
        if (address < PAGE_SIZE)
                printk(KERN_CONT "NULL pointer dereference");
        else
                printk(KERN_CONT "paging request");
+#ifdef CONFIG_X86_32
+       printk(KERN_CONT " at %08lx\n", address);
+#else
        printk(KERN_CONT " at %016lx\n", address);
-
+#endif
        printk(KERN_ALERT "IP:");
        printk_address(regs->ip, 1);
        dump_pagetable(address);
-#endif
 }
 
 #ifdef CONFIG_X86_64
@@ -439,6 +428,51 @@ static noinline void pgtable_bad(unsigned long address, struct pt_regs *regs,
 }
 #endif
 
+/*
+ * Handle a spurious fault caused by a stale TLB entry.  This allows
+ * us to lazily refresh the TLB when increasing the permissions of a
+ * kernel page (RO -> RW or NX -> X).  Doing it eagerly is very
+ * expensive since that implies doing a full cross-processor TLB
+ * flush, even if no stale TLB entries exist on other processors.
+ * There are no security implications to leaving a stale TLB when
+ * increasing the permissions on a page.
+ */
+static int spurious_fault(unsigned long address,
+                         unsigned long error_code)
+{
+       pgd_t *pgd;
+       pud_t *pud;
+       pmd_t *pmd;
+       pte_t *pte;
+
+       /* Reserved-bit violation or user access to kernel space? */
+       if (error_code & (PF_USER | PF_RSVD))
+               return 0;
+
+       pgd = init_mm.pgd + pgd_index(address);
+       if (!pgd_present(*pgd))
+               return 0;
+
+       pud = pud_offset(pgd, address);
+       if (!pud_present(*pud))
+               return 0;
+
+       pmd = pmd_offset(pud, address);
+       if (!pmd_present(*pmd))
+               return 0;
+
+       pte = pte_offset_kernel(pmd, address);
+       if (!pte_present(*pte))
+               return 0;
+
+       if ((error_code & PF_WRITE) && !pte_write(*pte))
+               return 0;
+       if ((error_code & PF_INSTR) && !pte_exec(*pte))
+               return 0;
+
+       return 1;
+}
+
 /*
  * X86_32
  * Handle a fault on the vmalloc or module mapping area
@@ -475,6 +509,10 @@ static int vmalloc_fault(unsigned long address)
        pmd_t *pmd, *pmd_ref;
        pte_t *pte, *pte_ref;
 
+       /* Make sure we are in vmalloc area */
+       if (!(address >= VMALLOC_START && address < VMALLOC_END))
+               return -1;
+
        /* Copy kernel mappings over when needed. This can also
           happen within a race in page table update. In the later
           case just flush. */
@@ -570,9 +608,17 @@ void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code)
         */
 #ifdef CONFIG_X86_32
        if (unlikely(address >= TASK_SIZE)) {
+#else
+       if (unlikely(address >= TASK_SIZE64)) {
+#endif
                if (!(error_code & (PF_RSVD|PF_USER|PF_PROT)) &&
                    vmalloc_fault(address) >= 0)
                        return;
+
+               /* Can handle a stale RO->RW TLB */
+               if (spurious_fault(address, error_code))
+                       return;
+
                /*
                 * Don't take the mm semaphore here. If we fixup a prefetch
                 * fault we could otherwise deadlock.
@@ -580,6 +626,8 @@ void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code)
                goto bad_area_nosemaphore;
        }
 
+
+#ifdef CONFIG_X86_32
        /* It's safe to allow irq's after cr2 has been saved and the vmalloc
           fault has been handled. */
        if (regs->flags & (X86_EFLAGS_IF|VM_MASK))
@@ -592,23 +640,6 @@ void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code)
        if (in_atomic() || !mm)
                goto bad_area_nosemaphore;
 #else /* CONFIG_X86_64 */
-       if (unlikely(address >= TASK_SIZE64)) {
-               /*
-                * Don't check for the module range here: its PML4
-                * is always initialized because it's shared with the main
-                * kernel text. Only vmalloc may need PML4 syncups.
-                */
-               if (!(error_code & (PF_RSVD|PF_USER|PF_PROT)) &&
-                     ((address >= VMALLOC_START && address < VMALLOC_END))) {
-                       if (vmalloc_fault(address) >= 0)
-                               return;
-               }
-               /*
-                * Don't take the mm semaphore here. If we fixup a prefetch
-                * fault we could otherwise deadlock.
-                */
-               goto bad_area_nosemaphore;
-       }
        if (likely(regs->flags & X86_EFLAGS_IF))
                local_irq_enable();
 
@@ -655,11 +686,7 @@ again:
        vma = find_vma(mm, address);
        if (!vma)
                goto bad_area;
-#ifdef CONFIG_X86_32
        if (vma->vm_start <= address)
-#else
-       if (likely(vma->vm_start <= address))
-#endif
                goto good_area;
        if (!(vma->vm_flags & VM_GROWSDOWN))
                goto bad_area;
@@ -808,23 +835,21 @@ no_context:
  */
 #ifdef CONFIG_X86_32
        bust_spinlocks(1);
+#else
+       flags = oops_begin();
+#endif
 
        show_fault_oops(regs, error_code, address);
 
        tsk->thread.cr2 = address;
        tsk->thread.trap_no = 14;
        tsk->thread.error_code = error_code;
+
+#ifdef CONFIG_X86_32
        die("Oops", regs, error_code);
        bust_spinlocks(0);
        do_exit(SIGKILL);
-#else /* CONFIG_X86_64 */
-       flags = oops_begin();
-
-       show_fault_oops(regs, error_code, address);
-
-       tsk->thread.cr2 = address;
-       tsk->thread.trap_no = 14;
-       tsk->thread.error_code = error_code;
+#else
        if (__die("Oops", regs, error_code))
                regs = NULL;
        /* Executive summary in case the body of the oops scrolled away */
@@ -838,18 +863,16 @@ no_context:
  */
 out_of_memory:
        up_read(&mm->mmap_sem);
-#ifdef CONFIG_X86_32
        if (is_global_init(tsk)) {
                yield();
+#ifdef CONFIG_X86_32
                down_read(&mm->mmap_sem);
                goto survive;
-       }
 #else
-       if (is_global_init(current)) {
-               yield();
                goto again;
-       }
 #endif
+       }
+
        printk("VM: killing process %s\n", tsk->comm);
        if (error_code & PF_USER)
                do_group_exit(SIGKILL);
@@ -872,10 +895,8 @@ do_sigbus:
        force_sig_info_fault(SIGBUS, BUS_ADRERR, address, tsk);
 }
 
-#ifdef CONFIG_X86_64
 DEFINE_SPINLOCK(pgd_lock);
 LIST_HEAD(pgd_list);
-#endif
 
 void vmalloc_sync_all(void)
 {
@@ -900,13 +921,11 @@ void vmalloc_sync_all(void)
                        struct page *page;
 
                        spin_lock_irqsave(&pgd_lock, flags);
-                       for (page = pgd_list; page; page =
-                                       (struct page *)page->index)
+                       list_for_each_entry(page, &pgd_list, lru) {
                                if (!vmalloc_sync_one(page_address(page),
-                                                               address)) {
-                                       BUG_ON(page != pgd_list);
+                                                     address))
                                        break;
-                               }
+                       }
                        spin_unlock_irqrestore(&pgd_lock, flags);
                        if (!page)
                                set_bit(pgd_index(address), insync);