linux 2.6.16.38 w/ vs2.0.3-rc1
[linux-2.6.git] / arch / i386 / mm / fault.c
index 4ea886f..cf572d9 100644 (file)
 #include <linux/vt_kern.h>             /* For unblank_screen() */
 #include <linux/highmem.h>
 #include <linux/module.h>
+#include <linux/kprobes.h>
 
 #include <asm/system.h>
 #include <asm/uaccess.h>
-#include <asm/hardirq.h>
 #include <asm/desc.h>
-#include <asm/tlbflush.h>
+#include <asm/kdebug.h>
 
 extern void die(const char *,struct pt_regs *,long);
 
@@ -104,32 +104,18 @@ static inline unsigned long get_segment_eip(struct pt_regs *regs,
        if (seg & (1<<2)) {
                /* Must lock the LDT while reading it. */
                down(&current->mm->context.sem);
-#if 1
-               /* horrible hack for 4/4 disabled kernels.
-                  I'm not quite sure what the TLB flush is good for,
-                  it's mindlessly copied from the read_ldt code */
-               __flush_tlb_global();
-               desc = kmap(current->mm->context.ldt_pages[(seg&~7)/PAGE_SIZE]);
-               desc = (void *)desc + ((seg & ~7) % PAGE_SIZE);
-#else
                desc = current->mm->context.ldt;
                desc = (void *)desc + (seg & ~7);
-#endif
        } else {
                /* Must disable preemption while reading the GDT. */
-               desc = (u32 *)&cpu_gdt_table[get_cpu()];
+               desc = (u32 *)get_cpu_gdt_table(get_cpu());
                desc = (void *)desc + (seg & ~7);
        }
 
        /* Decode the code segment base from the descriptor */
-       base =   (desc[0] >> 16) |
-               ((desc[1] & 0xff) << 16) |
-                (desc[1] & 0xff000000);
+       base = get_desc_base((unsigned long *)desc);
 
        if (seg & (1<<2)) { 
-#if 1
-               kunmap((void *)((unsigned long)desc & PAGE_MASK));
-#endif
                up(&current->mm->context.sem);
        } else
                put_cpu();
@@ -161,7 +147,7 @@ static int __is_prefetch(struct pt_regs *regs, unsigned long addr)
 
                if (instr > limit)
                        break;
-               if (__get_user(opcode, (unsigned char *) instr))
+               if (__get_user(opcode, (unsigned char __user *) instr))
                        break; 
 
                instr_hi = opcode & 0xf0; 
@@ -188,7 +174,7 @@ static int __is_prefetch(struct pt_regs *regs, unsigned long addr)
                        scan_more = 0;
                        if (instr > limit)
                                break;
-                       if (__get_user(opcode, (unsigned char *) instr)) 
+                       if (__get_user(opcode, (unsigned char __user *) instr))
                                break;
                        prefetch = (instr_lo == 0xF) &&
                                (opcode == 0x0D || opcode == 0x18);
@@ -201,15 +187,32 @@ static int __is_prefetch(struct pt_regs *regs, unsigned long addr)
        return prefetch;
 }
 
-static inline int is_prefetch(struct pt_regs *regs, unsigned long addr)
+static inline int is_prefetch(struct pt_regs *regs, unsigned long addr,
+                             unsigned long error_code)
 {
        if (unlikely(boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&
-                    boot_cpu_data.x86 >= 6))
+                    boot_cpu_data.x86 >= 6)) {
+               /* Catch an obscure case of prefetch inside an NX page. */
+               if (nx_enabled && (error_code & 16))
+                       return 0;
                return __is_prefetch(regs, addr);
+       }
        return 0;
 } 
 
-asmlinkage void do_invalid_op(struct pt_regs *, unsigned long);
+static noinline void force_sig_info_fault(int si_signo, int si_code,
+       unsigned long address, struct task_struct *tsk)
+{
+       siginfo_t info;
+
+       info.si_signo = si_signo;
+       info.si_errno = 0;
+       info.si_code = si_code;
+       info.si_addr = (void __user *)address;
+       force_sig_info(si_signo, &info, tsk);
+}
+
+fastcall void do_invalid_op(struct pt_regs *, unsigned long);
 
 /*
  * This routine handles page faults.  It determines the address,
@@ -221,26 +224,29 @@ asmlinkage void do_invalid_op(struct pt_regs *, unsigned long);
  *     bit 1 == 0 means read, 1 means write
  *     bit 2 == 0 means kernel, 1 means user-mode
  */
-asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
+fastcall void __kprobes do_page_fault(struct pt_regs *regs,
+                                     unsigned long error_code)
 {
        struct task_struct *tsk;
        struct mm_struct *mm;
        struct vm_area_struct * vma;
        unsigned long address;
        unsigned long page;
-       int write;
-       siginfo_t info;
+       int write, si_code;
 
        /* get the address */
-       __asm__("movl %%cr2,%0":"=r" (address));
+        address = read_cr2();
 
+       if (notify_die(DIE_PAGE_FAULT, "page fault", regs, error_code, 14,
+                                       SIGSEGV) == NOTIFY_STOP)
+               return;
        /* It's safe to allow irq's after cr2 has been saved */
        if (regs->eflags & (X86_EFLAGS_IF|VM_MASK))
                local_irq_enable();
 
        tsk = current;
 
-       info.si_code = SEGV_MAPERR;
+       si_code = SEGV_MAPERR;
 
        /*
         * We fault-in kernel-space virtual memory on-demand. The
@@ -255,17 +261,6 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
         * (error_code & 4) == 0, and that the fault was not a
         * protection error (error_code & 1) == 0.
         */
-#ifdef CONFIG_X86_4G
-       /*
-        * On 4/4 all kernels faults are either bugs, vmalloc or prefetch
-        */
-       /* If it's vm86 fall through */
-       if (unlikely(!(regs->eflags & VM_MASK) && ((regs->xcs & 3) == 0))) {
-               if (error_code & 3)
-                       goto bad_area_nosemaphore;
-               goto vmalloc_fault;
-       }
-#else
        if (unlikely(address >= TASK_SIZE)) { 
                if (!(error_code & 5))
                        goto vmalloc_fault;
@@ -275,7 +270,6 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
                 */
                goto bad_area_nosemaphore;
        } 
-#endif
 
        mm = tsk->mm;
 
@@ -286,7 +280,27 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
        if (in_atomic() || !mm)
                goto bad_area_nosemaphore;
 
-       down_read(&mm->mmap_sem);
+       /* When running in the kernel we expect faults to occur only to
+        * addresses in user space.  All other faults represent errors in the
+        * kernel and should generate an OOPS.  Unfortunatly, in the case of an
+        * erroneous fault occuring in a code path which already holds mmap_sem
+        * we will deadlock attempting to validate the fault against the
+        * address space.  Luckily the kernel only validly references user
+        * space from well defined areas of code, which are listed in the
+        * exceptions table.
+        *
+        * As the vast majority of faults will be valid we will only perform
+        * the source reference check when there is a possibilty of a deadlock.
+        * Attempt to lock the address space, if we cannot we then validate the
+        * source.  If this is invalid we can skip the address space check,
+        * thus avoiding the deadlock.
+        */
+       if (!down_read_trylock(&mm->mmap_sem)) {
+               if ((error_code & 4) == 0 &&
+                   !search_exception_tables(regs->eip))
+                       goto bad_area_nosemaphore;
+               down_read(&mm->mmap_sem);
+       }
 
        vma = find_vma(mm, address);
        if (!vma)
@@ -312,7 +326,7 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
  * we can handle it..
  */
 good_area:
-       info.si_code = SEGV_ACCERR;
+       si_code = SEGV_ACCERR;
        write = 0;
        switch (error_code & 3) {
                default:        /* 3: write, present */
@@ -379,18 +393,14 @@ bad_area_nosemaphore:
                 * Valid to do another page fault here because this one came 
                 * from user space.
                 */
-               if (is_prefetch(regs, address))
+               if (is_prefetch(regs, address, error_code))
                        return;
 
                tsk->thread.cr2 = address;
                /* Kernel addresses are always protection faults */
                tsk->thread.error_code = error_code | (address >= TASK_SIZE);
                tsk->thread.trap_no = 14;
-               info.si_signo = SIGSEGV;
-               info.si_errno = 0;
-               /* info.si_code has been set above */
-               info.si_addr = (void *)address;
-               force_sig_info(SIGSEGV, &info, tsk);
+               force_sig_info_fault(SIGSEGV, si_code, address, tsk);
                return;
        }
 
@@ -420,7 +430,7 @@ no_context:
         * had been triggered by is_prefetch fixup_exception would have 
         * handled it.
         */
-       if (is_prefetch(regs, address))
+       if (is_prefetch(regs, address, error_code))
                return;
 
 /*
@@ -431,18 +441,11 @@ no_context:
        bust_spinlocks(1);
 
 #ifdef CONFIG_X86_PAE
-       {
-               pgd_t *pgd;
-               pmd_t *pmd;
-
-
+       if (error_code & 16) {
+               pte_t *pte = lookup_address(address);
 
-               pgd = init_mm.pgd + pgd_index(address);
-               if (pgd_present(*pgd)) {
-                       pmd = pmd_offset(pgd, address);
-                       if (pmd_val(*pmd) & _PAGE_NX)
-                               printk(KERN_CRIT "kernel tried to access NX-protected page - exploit attempt? (uid: %d)\n", current->uid);
-               }
+               if (pte && pte_present(*pte) && !pte_exec_kernel(*pte))
+                       printk(KERN_CRIT "kernel tried to execute NX-protected page - exploit attempt? (uid: %d)\n", current->uid);
        }
 #endif
        if (address < PAGE_SIZE)
@@ -452,7 +455,7 @@ no_context:
        printk(" at virtual address %08lx\n",address);
        printk(KERN_ALERT " printing eip:\n");
        printk("%08lx\n", regs->eip);
-       asm("movl %%cr3,%0":"=r" (page));
+       page = read_cr3();
        page = ((unsigned long *) __va(page))[address >> 22];
        printk(KERN_ALERT "*pde = %08lx\n", page);
        /*
@@ -469,6 +472,9 @@ no_context:
                printk(KERN_ALERT "*pte = %08lx\n", page);
        }
 #endif
+       tsk->thread.cr2 = address;
+       tsk->thread.trap_no = 14;
+       tsk->thread.error_code = error_code;
        die("Oops", regs, error_code);
        bust_spinlocks(0);
        do_exit(SIGKILL);
@@ -497,17 +503,13 @@ do_sigbus:
                goto no_context;
 
        /* User space => ok to do another page fault */
-       if (is_prefetch(regs, address))
+       if (is_prefetch(regs, address, error_code))
                return;
 
        tsk->thread.cr2 = address;
        tsk->thread.error_code = error_code;
        tsk->thread.trap_no = 14;
-       info.si_signo = SIGBUS;
-       info.si_errno = 0;
-       info.si_code = BUS_ADRERR;
-       info.si_addr = (void *)address;
-       force_sig_info(SIGBUS, &info, tsk);
+       force_sig_info_fault(SIGBUS, BUS_ADRERR, address, tsk);
        return;
 
 vmalloc_fault:
@@ -520,12 +522,14 @@ vmalloc_fault:
                 * an interrupt in the middle of a task switch..
                 */
                int index = pgd_index(address);
+               unsigned long pgd_paddr;
                pgd_t *pgd, *pgd_k;
+               pud_t *pud, *pud_k;
                pmd_t *pmd, *pmd_k;
                pte_t *pte_k;
 
-               asm("movl %%cr3,%0":"=r" (pgd));
-               pgd = index + (pgd_t *)__va(pgd);
+               pgd_paddr = read_cr3();
+               pgd = index + (pgd_t *)__va(pgd_paddr);
                pgd_k = init_mm.pgd + index;
 
                if (!pgd_present(*pgd_k))
@@ -533,11 +537,17 @@ vmalloc_fault:
 
                /*
                 * set_pgd(pgd, *pgd_k); here would be useless on PAE
-                * and redundant with the set_pmd() on non-PAE.
+                * and redundant with the set_pmd() on non-PAE. As would
+                * set_pud.
                 */
 
-               pmd = pmd_offset(pgd, address);
-               pmd_k = pmd_offset(pgd_k, address);
+               pud = pud_offset(pgd, address);
+               pud_k = pud_offset(pgd_k, address);
+               if (!pud_present(*pud_k))
+                       goto no_context;
+               
+               pmd = pmd_offset(pud, address);
+               pmd_k = pmd_offset(pud_k, address);
                if (!pmd_present(*pmd_k))
                        goto no_context;
                set_pmd(pmd, *pmd_k);