![Sukadev Bhattiprolu](/assets/img/avatar_default.png)
This is an updated version of Eric Biederman's is_init() patch. (http://lkml.org/lkml/2006/2/6/280). It applies cleanly to 2.6.18-rc3 and replaces a few more instances of ->pid == 1 with is_init(). Further, is_init() checks pid and thus removes dependency on Eric's other patches for now. Eric's original description: There are a lot of places in the kernel where we test for init because we give it special properties. Most significantly init must not die. This results in code all over the kernel test ->pid == 1. Introduce is_init to capture this case. With multiple pid spaces for all of the cases affected we are looking for only the first process on the system, not some other process that has pid == 1. Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> Signed-off-by: Sukadev Bhattiprolu <sukadev@us.ibm.com> Cc: Dave Hansen <haveblue@us.ibm.com> Cc: Serge Hallyn <serue@us.ibm.com> Cc: Cedric Le Goater <clg@fr.ibm.com> Cc: <lxc-devel@lists.sourceforge.net> Acked-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
293 lines
7.4 KiB
C
293 lines
7.4 KiB
C
/*
|
|
* MMU fault handling support.
|
|
*
|
|
* Copyright (C) 1998-2002 Hewlett-Packard Co
|
|
* David Mosberger-Tang <davidm@hpl.hp.com>
|
|
*/
|
|
#include <linux/sched.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/smp_lock.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kprobes.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/system.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm/kdebug.h>
|
|
|
|
extern void die (char *, struct pt_regs *, long);
|
|
|
|
#ifdef CONFIG_KPROBES
|
|
ATOMIC_NOTIFIER_HEAD(notify_page_fault_chain);
|
|
|
|
/* Hook to register for page fault notifications */
|
|
int register_page_fault_notifier(struct notifier_block *nb)
|
|
{
|
|
return atomic_notifier_chain_register(¬ify_page_fault_chain, nb);
|
|
}
|
|
|
|
int unregister_page_fault_notifier(struct notifier_block *nb)
|
|
{
|
|
return atomic_notifier_chain_unregister(¬ify_page_fault_chain, nb);
|
|
}
|
|
|
|
static inline int notify_page_fault(enum die_val val, const char *str,
|
|
struct pt_regs *regs, long err, int trap, int sig)
|
|
{
|
|
struct die_args args = {
|
|
.regs = regs,
|
|
.str = str,
|
|
.err = err,
|
|
.trapnr = trap,
|
|
.signr = sig
|
|
};
|
|
return atomic_notifier_call_chain(¬ify_page_fault_chain, val, &args);
|
|
}
|
|
#else
|
|
static inline int notify_page_fault(enum die_val val, const char *str,
|
|
struct pt_regs *regs, long err, int trap, int sig)
|
|
{
|
|
return NOTIFY_DONE;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Return TRUE if ADDRESS points at a page in the kernel's mapped segment
|
|
* (inside region 5, on ia64) and that page is present.
|
|
*/
|
|
static int
|
|
mapped_kernel_page_is_present (unsigned long address)
|
|
{
|
|
pgd_t *pgd;
|
|
pud_t *pud;
|
|
pmd_t *pmd;
|
|
pte_t *ptep, pte;
|
|
|
|
pgd = pgd_offset_k(address);
|
|
if (pgd_none(*pgd) || pgd_bad(*pgd))
|
|
return 0;
|
|
|
|
pud = pud_offset(pgd, address);
|
|
if (pud_none(*pud) || pud_bad(*pud))
|
|
return 0;
|
|
|
|
pmd = pmd_offset(pud, address);
|
|
if (pmd_none(*pmd) || pmd_bad(*pmd))
|
|
return 0;
|
|
|
|
ptep = pte_offset_kernel(pmd, address);
|
|
if (!ptep)
|
|
return 0;
|
|
|
|
pte = *ptep;
|
|
return pte_present(pte);
|
|
}
|
|
|
|
void __kprobes
|
|
ia64_do_page_fault (unsigned long address, unsigned long isr, struct pt_regs *regs)
|
|
{
|
|
int signal = SIGSEGV, code = SEGV_MAPERR;
|
|
struct vm_area_struct *vma, *prev_vma;
|
|
struct mm_struct *mm = current->mm;
|
|
struct siginfo si;
|
|
unsigned long mask;
|
|
|
|
/* mmap_sem is performance critical.... */
|
|
prefetchw(&mm->mmap_sem);
|
|
|
|
/*
|
|
* If we're in an interrupt or have no user context, we must not take the fault..
|
|
*/
|
|
if (in_atomic() || !mm)
|
|
goto no_context;
|
|
|
|
#ifdef CONFIG_VIRTUAL_MEM_MAP
|
|
/*
|
|
* If fault is in region 5 and we are in the kernel, we may already
|
|
* have the mmap_sem (pfn_valid macro is called during mmap). There
|
|
* is no vma for region 5 addr's anyway, so skip getting the semaphore
|
|
* and go directly to the exception handling code.
|
|
*/
|
|
|
|
if ((REGION_NUMBER(address) == 5) && !user_mode(regs))
|
|
goto bad_area_no_up;
|
|
#endif
|
|
|
|
/*
|
|
* This is to handle the kprobes on user space access instructions
|
|
*/
|
|
if (notify_page_fault(DIE_PAGE_FAULT, "page fault", regs, code, TRAP_BRKPT,
|
|
SIGSEGV) == NOTIFY_STOP)
|
|
return;
|
|
|
|
down_read(&mm->mmap_sem);
|
|
|
|
vma = find_vma_prev(mm, address, &prev_vma);
|
|
if (!vma)
|
|
goto bad_area;
|
|
|
|
/* find_vma_prev() returns vma such that address < vma->vm_end or NULL */
|
|
if (address < vma->vm_start)
|
|
goto check_expansion;
|
|
|
|
good_area:
|
|
code = SEGV_ACCERR;
|
|
|
|
/* OK, we've got a good vm_area for this memory area. Check the access permissions: */
|
|
|
|
# define VM_READ_BIT 0
|
|
# define VM_WRITE_BIT 1
|
|
# define VM_EXEC_BIT 2
|
|
|
|
# if (((1 << VM_READ_BIT) != VM_READ || (1 << VM_WRITE_BIT) != VM_WRITE) \
|
|
|| (1 << VM_EXEC_BIT) != VM_EXEC)
|
|
# error File is out of sync with <linux/mm.h>. Please update.
|
|
# endif
|
|
|
|
if (((isr >> IA64_ISR_R_BIT) & 1UL) && (!(vma->vm_flags & (VM_READ | VM_WRITE))))
|
|
goto bad_area;
|
|
|
|
mask = ( (((isr >> IA64_ISR_X_BIT) & 1UL) << VM_EXEC_BIT)
|
|
| (((isr >> IA64_ISR_W_BIT) & 1UL) << VM_WRITE_BIT));
|
|
|
|
if ((vma->vm_flags & mask) != mask)
|
|
goto bad_area;
|
|
|
|
survive:
|
|
/*
|
|
* If for any reason at all we couldn't handle the fault, make
|
|
* sure we exit gracefully rather than endlessly redo the
|
|
* fault.
|
|
*/
|
|
switch (handle_mm_fault(mm, vma, address, (mask & VM_WRITE) != 0)) {
|
|
case VM_FAULT_MINOR:
|
|
++current->min_flt;
|
|
break;
|
|
case VM_FAULT_MAJOR:
|
|
++current->maj_flt;
|
|
break;
|
|
case VM_FAULT_SIGBUS:
|
|
/*
|
|
* We ran out of memory, or some other thing happened
|
|
* to us that made us unable to handle the page fault
|
|
* gracefully.
|
|
*/
|
|
signal = SIGBUS;
|
|
goto bad_area;
|
|
case VM_FAULT_OOM:
|
|
goto out_of_memory;
|
|
default:
|
|
BUG();
|
|
}
|
|
up_read(&mm->mmap_sem);
|
|
return;
|
|
|
|
check_expansion:
|
|
if (!(prev_vma && (prev_vma->vm_flags & VM_GROWSUP) && (address == prev_vma->vm_end))) {
|
|
if (!(vma->vm_flags & VM_GROWSDOWN))
|
|
goto bad_area;
|
|
if (REGION_NUMBER(address) != REGION_NUMBER(vma->vm_start)
|
|
|| REGION_OFFSET(address) >= RGN_MAP_LIMIT)
|
|
goto bad_area;
|
|
if (expand_stack(vma, address))
|
|
goto bad_area;
|
|
} else {
|
|
vma = prev_vma;
|
|
if (REGION_NUMBER(address) != REGION_NUMBER(vma->vm_start)
|
|
|| REGION_OFFSET(address) >= RGN_MAP_LIMIT)
|
|
goto bad_area;
|
|
/*
|
|
* Since the register backing store is accessed sequentially,
|
|
* we disallow growing it by more than a page at a time.
|
|
*/
|
|
if (address > vma->vm_end + PAGE_SIZE - sizeof(long))
|
|
goto bad_area;
|
|
if (expand_upwards(vma, address))
|
|
goto bad_area;
|
|
}
|
|
goto good_area;
|
|
|
|
bad_area:
|
|
up_read(&mm->mmap_sem);
|
|
#ifdef CONFIG_VIRTUAL_MEM_MAP
|
|
bad_area_no_up:
|
|
#endif
|
|
if ((isr & IA64_ISR_SP)
|
|
|| ((isr & IA64_ISR_NA) && (isr & IA64_ISR_CODE_MASK) == IA64_ISR_CODE_LFETCH))
|
|
{
|
|
/*
|
|
* This fault was due to a speculative load or lfetch.fault, set the "ed"
|
|
* bit in the psr to ensure forward progress. (Target register will get a
|
|
* NaT for ld.s, lfetch will be canceled.)
|
|
*/
|
|
ia64_psr(regs)->ed = 1;
|
|
return;
|
|
}
|
|
if (user_mode(regs)) {
|
|
si.si_signo = signal;
|
|
si.si_errno = 0;
|
|
si.si_code = code;
|
|
si.si_addr = (void __user *) address;
|
|
si.si_isr = isr;
|
|
si.si_flags = __ISR_VALID;
|
|
force_sig_info(signal, &si, current);
|
|
return;
|
|
}
|
|
|
|
no_context:
|
|
if ((isr & IA64_ISR_SP)
|
|
|| ((isr & IA64_ISR_NA) && (isr & IA64_ISR_CODE_MASK) == IA64_ISR_CODE_LFETCH))
|
|
{
|
|
/*
|
|
* This fault was due to a speculative load or lfetch.fault, set the "ed"
|
|
* bit in the psr to ensure forward progress. (Target register will get a
|
|
* NaT for ld.s, lfetch will be canceled.)
|
|
*/
|
|
ia64_psr(regs)->ed = 1;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Since we have no vma's for region 5, we might get here even if the address is
|
|
* valid, due to the VHPT walker inserting a non present translation that becomes
|
|
* stale. If that happens, the non present fault handler already purged the stale
|
|
* translation, which fixed the problem. So, we check to see if the translation is
|
|
* valid, and return if it is.
|
|
*/
|
|
if (REGION_NUMBER(address) == 5 && mapped_kernel_page_is_present(address))
|
|
return;
|
|
|
|
if (ia64_done_with_exception(regs))
|
|
return;
|
|
|
|
/*
|
|
* Oops. The kernel tried to access some bad page. We'll have to terminate things
|
|
* with extreme prejudice.
|
|
*/
|
|
bust_spinlocks(1);
|
|
|
|
if (address < PAGE_SIZE)
|
|
printk(KERN_ALERT "Unable to handle kernel NULL pointer dereference (address %016lx)\n", address);
|
|
else
|
|
printk(KERN_ALERT "Unable to handle kernel paging request at "
|
|
"virtual address %016lx\n", address);
|
|
die("Oops", regs, isr);
|
|
bust_spinlocks(0);
|
|
do_exit(SIGKILL);
|
|
return;
|
|
|
|
out_of_memory:
|
|
up_read(&mm->mmap_sem);
|
|
if (is_init(current)) {
|
|
yield();
|
|
down_read(&mm->mmap_sem);
|
|
goto survive;
|
|
}
|
|
printk(KERN_CRIT "VM: killing process %s\n", current->comm);
|
|
if (user_mode(regs))
|
|
do_exit(SIGKILL);
|
|
goto no_context;
|
|
}
|