summaryrefslogtreecommitdiffstats
path: root/arch/i386/mm/fault.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/i386/mm/fault.c')
-rw-r--r--arch/i386/mm/fault.c153
1 files changed, 115 insertions, 38 deletions
diff --git a/arch/i386/mm/fault.c b/arch/i386/mm/fault.c
index 01c259f0f..50dcd0735 100644
--- a/arch/i386/mm/fault.c
+++ b/arch/i386/mm/fault.c
@@ -4,7 +4,6 @@
* Copyright (C) 1995 Linus Torvalds
*/
-#include <linux/config.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/head.h>
@@ -17,10 +16,61 @@
#include <linux/mm.h>
#include <asm/system.h>
-#include <asm/segment.h>
+#include <asm/uaccess.h>
#include <asm/pgtable.h>
-extern void die_if_kernel(char *,struct pt_regs *,long);
+extern void die_if_kernel(const char *,struct pt_regs *,long);
+
+/*
+ * Ugly, ugly, but the goto's result in better assembly..
+ */
+int __verify_write(const void * addr, unsigned long size)
+{
+ struct vm_area_struct * vma;
+ unsigned long start = (unsigned long) addr;
+
+ if (!size)
+ return 1;
+
+ vma = find_vma(current->mm, start);
+ if (!vma)
+ goto bad_area;
+ if (vma->vm_start > start)
+ goto check_stack;
+
+good_area:
+ if (!(vma->vm_flags & VM_WRITE))
+ goto bad_area;
+ size--;
+ size += start & ~PAGE_MASK;
+ size >>= PAGE_SHIFT;
+ start &= PAGE_MASK;
+
+ for (;;) {
+ do_wp_page(current, vma, start, 1);
+ if (!size)
+ break;
+ size--;
+ start += PAGE_SIZE;
+ if (start < vma->vm_end)
+ continue;
+ vma = vma->vm_next;
+ if (!vma || vma->vm_start != start)
+ goto bad_area;
+ if (!(vma->vm_flags & VM_WRITE))
+ goto bad_area;;
+ }
+ return 1;
+
+check_stack:
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto bad_area;
+ if (expand_stack(vma, start) == 0)
+ goto good_area;
+
+bad_area:
+ return 0;
+}
/*
* This routine handles page faults. It determines the address,
@@ -34,58 +84,76 @@ extern void die_if_kernel(char *,struct pt_regs *,long);
*/
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
+ void (*handler)(struct task_struct *,
+ struct vm_area_struct *,
+ unsigned long,
+ int);
+ struct task_struct *tsk = current;
+ struct mm_struct *mm = tsk->mm;
struct vm_area_struct * vma;
unsigned long address;
unsigned long page;
+ unsigned long fixup;
+ int write;
/* get the address */
__asm__("movl %%cr2,%0":"=r" (address));
- vma = find_vma(current, address);
+ down(&mm->mmap_sem);
+ vma = find_vma(mm, address);
if (!vma)
goto bad_area;
if (vma->vm_start <= address)
goto good_area;
if (!(vma->vm_flags & VM_GROWSDOWN))
goto bad_area;
- if (vma->vm_end - address > current->rlim[RLIMIT_STACK].rlim_cur)
+ if (error_code & 4) {
+ /*
+ * accessing the stack below %esp is always a bug.
+ * The "+ 32" is there due to some instructions (like
+ * pusha) doing pre-decrement on the stack and that
+ * doesn't show up until later..
+ */
+ if (address + 32 < regs->esp)
+ goto bad_area;
+ }
+ if (expand_stack(vma, address))
goto bad_area;
- vma->vm_offset -= vma->vm_start - (address & PAGE_MASK);
- vma->vm_start = (address & PAGE_MASK);
/*
* Ok, we have a good vm_area for this memory access, so
* we can handle it..
*/
good_area:
- /*
- * was it a write?
- */
- if (error_code & 2) {
- if (!(vma->vm_flags & VM_WRITE))
- goto bad_area;
- } else {
- /* read with protection fault? */
- if (error_code & 1)
- goto bad_area;
- if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
+ write = 0;
+ handler = do_no_page;
+ switch (error_code & 3) {
+ default: /* 3: write, present */
+ handler = do_wp_page;
+#ifdef TEST_VERIFY_AREA
+ if (regs->cs == KERNEL_CS)
+ printk("WP fault at %08lx\n", regs->eip);
+#endif
+ /* fall through */
+ case 2: /* write, not present */
+ if (!(vma->vm_flags & VM_WRITE))
+ goto bad_area;
+ write++;
+ break;
+ case 1: /* read, present */
goto bad_area;
+ case 0: /* read, not present */
+ if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
+ goto bad_area;
}
+ handler(tsk, vma, address, write);
+ up(&mm->mmap_sem);
/*
* Did it hit the DOS screen memory VA from vm86 mode?
*/
if (regs->eflags & VM_MASK) {
unsigned long bit = (address - 0xA0000) >> PAGE_SHIFT;
if (bit < 32)
- current->tss.screen_bitmap |= 1 << bit;
- }
- if (error_code & 1) {
-#ifdef CONFIG_TEST_VERIFY_AREA
- if (regs->cs == KERNEL_CS)
- printk("WP fault at %08x\n", regs->eip);
-#endif
- do_wp_page(vma, address, error_code & 2);
- return;
+ tsk->tss.screen_bitmap |= 1 << bit;
}
- do_no_page(vma, address, error_code & 2);
return;
/*
@@ -93,11 +161,20 @@ good_area:
* Fix it, but check if it's kernel or user first..
*/
bad_area:
+ up(&mm->mmap_sem);
+
+ /* Are we prepared to handle this fault? */
+ if ((fixup = search_exception_table(regs->eip)) != 0) {
+ printk("Exception at %lx (%lx)\n", regs->eip, fixup);
+ regs->eip = fixup;
+ return;
+ }
+
if (error_code & 4) {
- current->tss.cr2 = address;
- current->tss.error_code = error_code;
- current->tss.trap_no = 14;
- send_sig(SIGSEGV, current, 1);
+ tsk->tss.cr2 = address;
+ tsk->tss.error_code = error_code;
+ tsk->tss.trap_no = 14;
+ force_sig(SIGSEGV, tsk);
return;
}
/*
@@ -106,14 +183,14 @@ bad_area:
*
* First we check if it was the bootup rw-test, though..
*/
- if (wp_works_ok < 0 && address == TASK_SIZE && (error_code & 1)) {
+ if (wp_works_ok < 0 && !address && (error_code & 1)) {
wp_works_ok = 1;
pg0[0] = pte_val(mk_pte(0, PAGE_SHARED));
- invalidate();
+ flush_tlb();
printk("This processor honours the WP bit even when in supervisor mode. Good.\n");
return;
}
- if ((unsigned long) (address-TASK_SIZE) < PAGE_SIZE) {
+ if (address < PAGE_SIZE) {
printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference");
pg0[0] = pte_val(mk_pte(0, PAGE_SHARED));
} else
@@ -121,13 +198,13 @@ bad_area:
printk(" at virtual address %08lx\n",address);
__asm__("movl %%cr3,%0" : "=r" (page));
printk(KERN_ALERT "current->tss.cr3 = %08lx, %%cr3 = %08lx\n",
- current->tss.cr3, page);
- page = ((unsigned long *) page)[address >> 22];
+ tsk->tss.cr3, page);
+ page = ((unsigned long *) __va(page))[address >> 22];
printk(KERN_ALERT "*pde = %08lx\n", page);
if (page & 1) {
page &= PAGE_MASK;
address &= 0x003ff000;
- page = ((unsigned long *) page)[address >> PAGE_SHIFT];
+ page = ((unsigned long *) __va(page))[address >> PAGE_SHIFT];
printk(KERN_ALERT "*pte = %08lx\n", page);
}
die_if_kernel("Oops", regs, error_code);