X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=arch%2Fppc64%2Fmm%2Ffault.c;h=71c159841de5683e0ed3c59588b5721bb12d58d3;hb=70790a4b5cd6c0291e5b1a2836e2832d46036ac6;hp=72eba98f4dd8eeb412bd6f7d7ada0a4790983d3a;hpb=413ba3004a4036bb5d6e52d2faa2b19cb13561dc;p=linux-2.6.git diff --git a/arch/ppc64/mm/fault.c b/arch/ppc64/mm/fault.c index 72eba98f4..71c159841 100644 --- a/arch/ppc64/mm/fault.c +++ b/arch/ppc64/mm/fault.c @@ -119,7 +119,28 @@ int do_page_fault(struct pt_regs *regs, unsigned long address, die("Weird page fault", regs, SIGSEGV); } - 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 (!user_mode(regs) && !search_exception_tables(regs->nip)) + goto bad_area_nosemaphore; + + down_read(&mm->mmap_sem); + } + vma = find_vma(mm, address); if (!vma) goto bad_area; @@ -209,12 +230,13 @@ good_area: bad_area: up_read(&mm->mmap_sem); +bad_area_nosemaphore: /* User mode accesses cause a SIGSEGV */ if (user_mode(regs)) { info.si_signo = SIGSEGV; info.si_errno = 0; info.si_code = code; - info.si_addr = (void *) address; + info.si_addr = (void __user *) address; force_sig_info(SIGSEGV, &info, current); return 0; } @@ -243,7 +265,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, current); return 0; }