Merge branch 'timers-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[safe/jmp/linux-2.6] / arch / x86 / mm / fault.c
index 99d273d..455f3fe 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/string.h>
 #include <linux/types.h>
 #include <linux/ptrace.h>
+#include <linux/mmiotrace.h>
 #include <linux/mman.h>
 #include <linux/mm.h>
 #include <linux/smp.h>
 #define PF_RSVD                (1<<3)
 #define PF_INSTR       (1<<4)
 
+static inline int kmmio_fault(struct pt_regs *regs, unsigned long addr)
+{
+#ifdef CONFIG_MMIOTRACE_HOOKS
+       if (unlikely(is_kmmio_active()))
+               if (kmmio_handler(regs, addr) == 1)
+                       return -1;
+#endif
+       return 0;
+}
+
 static inline int notify_page_fault(struct pt_regs *regs)
 {
 #ifdef CONFIG_KPROBES
        int ret = 0;
 
        /* kprobe_running() needs smp_processor_id() */
-#ifdef CONFIG_X86_32
        if (!user_mode_vm(regs)) {
-#else
-       if (!user_mode(regs)) {
-#endif
                preempt_disable();
                if (kprobe_running() && kprobe_fault_handler(regs, 14))
                        ret = 1;
@@ -91,12 +98,10 @@ static int is_prefetch(struct pt_regs *regs, unsigned long addr,
        int prefetch = 0;
        unsigned char *max_instr;
 
-#ifdef CONFIG_X86_32
-       if (!(__supported_pte_mask & _PAGE_NX))
-               return 0;
-#endif
-
-       /* If it was a exec fault on NX page, ignore */
+       /*
+        * If it was a exec (instruction fetch) fault on NX page, then
+        * do not ignore the fault:
+        */
        if (error_code & PF_INSTR)
                return 0;
 
@@ -186,7 +191,7 @@ static int bad_address(void *p)
 }
 #endif
 
-void dump_pagetable(unsigned long address)
+static void dump_pagetable(unsigned long address)
 {
 #ifdef CONFIG_X86_32
        __typeof__(pte_val(__pte(0))) page;
@@ -240,7 +245,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;
@@ -378,10 +384,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))
@@ -390,28 +397,16 @@ 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");
-       printk(KERN_CONT " at %016lx\n", address);
-
+       printk(KERN_CONT " at %p\n", (void *) address);
        printk(KERN_ALERT "IP:");
        printk_address(regs->ip, 1);
        dump_pagetable(address);
-#endif
 }
 
 #ifdef CONFIG_X86_64
@@ -434,6 +429,62 @@ static noinline void pgtable_bad(unsigned long address, struct pt_regs *regs,
 }
 #endif
 
+static int spurious_fault_check(unsigned long error_code, pte_t *pte)
+{
+       if ((error_code & PF_WRITE) && !pte_write(*pte))
+               return 0;
+       if ((error_code & PF_INSTR) && !pte_exec(*pte))
+               return 0;
+
+       return 1;
+}
+
+/*
+ * 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;
+
+       if (pud_large(*pud))
+               return spurious_fault_check(error_code, (pte_t *) pud);
+
+       pmd = pmd_offset(pud, address);
+       if (!pmd_present(*pmd))
+               return 0;
+
+       if (pmd_large(*pmd))
+               return spurious_fault_check(error_code, (pte_t *) pmd);
+
+       pte = pte_offset_kernel(pmd, address);
+       if (!pte_present(*pte))
+               return 0;
+
+       return spurious_fault_check(error_code, pte);
+}
+
 /*
  * X86_32
  * Handle a fault on the vmalloc or module mapping area
@@ -449,6 +500,11 @@ static int vmalloc_fault(unsigned long address)
        unsigned long pgd_paddr;
        pmd_t *pmd_k;
        pte_t *pte_k;
+
+       /* Make sure we are in vmalloc area */
+       if (!(address >= VMALLOC_START && address < VMALLOC_END))
+               return -1;
+
        /*
         * Synchronize this task's top level page-table
         * with the 'reference' page table.
@@ -470,6 +526,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. */
@@ -549,6 +609,8 @@ void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code)
 
        if (notify_page_fault(regs))
                return;
+       if (unlikely(kmmio_fault(regs, address)))
+               return;
 
        /*
         * We fault-in kernel-space virtual memory on-demand. The
@@ -565,9 +627,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.
@@ -575,9 +645,11 @@ 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))
+       if (regs->flags & (X86_EFLAGS_IF | X86_VM_MASK))
                local_irq_enable();
 
        /*
@@ -587,23 +659,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();
 
@@ -650,11 +705,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;
@@ -754,14 +805,10 @@ bad_area_nosemaphore:
                if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV) &&
                    printk_ratelimit()) {
                        printk(
-#ifdef CONFIG_X86_32
-                       "%s%s[%d]: segfault at %lx ip %08lx sp %08lx error %lx",
-#else
-                       "%s%s[%d]: segfault at %lx ip %lx sp %lx error %lx",
-#endif
+                       "%s%s[%d]: segfault at %lx ip %p sp %p error %lx",
                        task_pid_nr(tsk) > 1 ? KERN_INFO : KERN_EMERG,
-                       tsk->comm, task_pid_nr(tsk), address, regs->ip,
-                       regs->sp, error_code);
+                       tsk->comm, task_pid_nr(tsk), address,
+                       (void *) regs->ip, (void *) regs->sp, error_code);
                        print_vma_addr(" in ", regs->ip);
                        printk("\n");
                }
@@ -803,23 +850,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 */
@@ -833,18 +878,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);
@@ -867,22 +910,13 @@ 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)
 {
 #ifdef CONFIG_X86_32
-       /*
-        * Note that races in the updates of insync and start aren't
-        * problematic: insync can only get set bits added, and updates to
-        * start are only improving performance (without affecting correctness
-        * if undone).
-        */
-       static DECLARE_BITMAP(insync, PTRS_PER_PGD);
-       static unsigned long start = TASK_SIZE;
+       unsigned long start = VMALLOC_START & PGDIR_MASK;
        unsigned long address;
 
        if (SHARED_KERNEL_PMD)
@@ -890,61 +924,38 @@ void vmalloc_sync_all(void)
 
        BUILD_BUG_ON(TASK_SIZE & ~PGDIR_MASK);
        for (address = start; address >= TASK_SIZE; address += PGDIR_SIZE) {
-               if (!test_bit(pgd_index(address), insync)) {
-                       unsigned long flags;
-                       struct page *page;
-
-                       spin_lock_irqsave(&pgd_lock, flags);
-                       for (page = pgd_list; page; page =
-                                       (struct page *)page->index)
-                               if (!vmalloc_sync_one(page_address(page),
-                                                               address)) {
-                                       BUG_ON(page != pgd_list);
-                                       break;
-                               }
-                       spin_unlock_irqrestore(&pgd_lock, flags);
-                       if (!page)
-                               set_bit(pgd_index(address), insync);
+               unsigned long flags;
+               struct page *page;
+
+               spin_lock_irqsave(&pgd_lock, flags);
+               list_for_each_entry(page, &pgd_list, lru) {
+                       if (!vmalloc_sync_one(page_address(page),
+                                             address))
+                               break;
                }
-               if (address == start && test_bit(pgd_index(address), insync))
-                       start = address + PGDIR_SIZE;
+               spin_unlock_irqrestore(&pgd_lock, flags);
        }
 #else /* CONFIG_X86_64 */
-       /*
-        * Note that races in the updates of insync and start aren't
-        * problematic: insync can only get set bits added, and updates to
-        * start are only improving performance (without affecting correctness
-        * if undone).
-        */
-       static DECLARE_BITMAP(insync, PTRS_PER_PGD);
-       static unsigned long start = VMALLOC_START & PGDIR_MASK;
+       unsigned long start = VMALLOC_START & PGDIR_MASK;
        unsigned long address;
 
        for (address = start; address <= VMALLOC_END; address += PGDIR_SIZE) {
-               if (!test_bit(pgd_index(address), insync)) {
-                       const pgd_t *pgd_ref = pgd_offset_k(address);
-                       struct page *page;
-
-                       if (pgd_none(*pgd_ref))
-                               continue;
-                       spin_lock(&pgd_lock);
-                       list_for_each_entry(page, &pgd_list, lru) {
-                               pgd_t *pgd;
-                               pgd = (pgd_t *)page_address(page) + pgd_index(address);
-                               if (pgd_none(*pgd))
-                                       set_pgd(pgd, *pgd_ref);
-                               else
-                                       BUG_ON(pgd_page_vaddr(*pgd) != pgd_page_vaddr(*pgd_ref));
-                       }
-                       spin_unlock(&pgd_lock);
-                       set_bit(pgd_index(address), insync);
+               const pgd_t *pgd_ref = pgd_offset_k(address);
+               unsigned long flags;
+               struct page *page;
+
+               if (pgd_none(*pgd_ref))
+                       continue;
+               spin_lock_irqsave(&pgd_lock, flags);
+               list_for_each_entry(page, &pgd_list, lru) {
+                       pgd_t *pgd;
+                       pgd = (pgd_t *)page_address(page) + pgd_index(address);
+                       if (pgd_none(*pgd))
+                               set_pgd(pgd, *pgd_ref);
+                       else
+                               BUG_ON(pgd_page_vaddr(*pgd) != pgd_page_vaddr(*pgd_ref));
                }
-               if (address == start)
-                       start = address + PGDIR_SIZE;
+               spin_unlock_irqrestore(&pgd_lock, flags);
        }
-       /* Check that there is no need to do the same for the modules area. */
-       BUILD_BUG_ON(!(MODULES_VADDR > __START_KERNEL));
-       BUILD_BUG_ON(!(((MODULES_END - 1) & PGDIR_MASK) ==
-                               (__START_KERNEL & PGDIR_MASK)));
 #endif
 }