summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/ppc64/mm/fault.c24
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;