vserver 1.9.3
[linux-2.6.git] / arch / i386 / mm / fault.c
index dcec5e7..e929384 100644 (file)
@@ -24,9 +24,8 @@
 
 #include <asm/system.h>
 #include <asm/uaccess.h>
-#include <asm/pgalloc.h>
-#include <asm/hardirq.h>
 #include <asm/desc.h>
+#include <asm/kdebug.h>
 
 extern void die(const char *,struct pt_regs *,long);
 
@@ -108,7 +107,7 @@ static inline unsigned long get_segment_eip(struct pt_regs *regs,
                desc = (void *)desc + (seg & ~7);
        } else {
                /* Must disable preemption while reading the GDT. */
-               desc = (u32 *)&cpu_gdt_table[get_cpu()];
+               desc = (u32 *)&per_cpu(cpu_gdt_table, get_cpu());
                desc = (void *)desc + (seg & ~7);
        }
 
@@ -189,11 +188,16 @@ 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;
 } 
 
@@ -222,6 +226,9 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
        /* get the address */
        __asm__("movl %%cr2,%0":"=r" (address));
 
+       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();
@@ -262,7 +269,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)
@@ -355,7 +382,7 @@ 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;
@@ -365,7 +392,7 @@ bad_area_nosemaphore:
                info.si_signo = SIGSEGV;
                info.si_errno = 0;
                /* info.si_code has been set above */
-               info.si_addr = (void *)address;
+               info.si_addr = (void __user *)address;
                force_sig_info(SIGSEGV, &info, tsk);
                return;
        }
@@ -396,7 +423,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;
 
 /*
@@ -406,12 +433,20 @@ no_context:
 
        bust_spinlocks(1);
 
+#ifdef CONFIG_X86_PAE
+       if (error_code & 16) {
+               pte_t *pte = lookup_address(address);
+
+               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)
                printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference");
        else
                printk(KERN_ALERT "Unable to handle kernel paging request");
        printk(" at virtual address %08lx\n",address);
-       printk(" printing eip:\n");
+       printk(KERN_ALERT " printing eip:\n");
        printk("%08lx\n", regs->eip);
        asm("movl %%cr3,%0":"=r" (page));
        page = ((unsigned long *) __va(page))[address >> 22];
@@ -458,7 +493,7 @@ 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;
@@ -467,7 +502,7 @@ do_sigbus:
        info.si_signo = SIGBUS;
        info.si_errno = 0;
        info.si_code = BUS_ADRERR;
-       info.si_addr = (void *)address;
+       info.si_addr = (void __user *)address;
        force_sig_info(SIGBUS, &info, tsk);
        return;
 
@@ -481,12 +516,13 @@ 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;
                pmd_t *pmd, *pmd_k;
                pte_t *pte_k;
 
-               asm("movl %%cr3,%0":"=r" (pgd));
-               pgd = index + (pgd_t *)__va(pgd);
+               asm("movl %%cr3,%0":"=r" (pgd_paddr));
+               pgd = index + (pgd_t *)__va(pgd_paddr);
                pgd_k = init_mm.pgd + index;
 
                if (!pgd_present(*pgd_k))