diff options
| -rw-r--r-- | arch/ppc64/mm/fault.c | 24 |
1 files changed, 23 insertions, 1 deletions
diff --git a/arch/ppc64/mm/fault.c b/arch/ppc64/mm/fault.c index 72eba98f4dd8..34f4e251172a 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,6 +230,7 @@ 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; |
