summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Whitcroft <apw@shadowen.org>2004-06-29 05:15:56 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2004-06-29 05:15:56 -0700
commitca1628beddae9a7f7e6b262f045f70dbbd6ca653 (patch)
tree694e049db91ea30c35ea66c785e8168d87397be1
parent7ce2374673c74feb5086ac14d2aa5607632400a8 (diff)
[PATCH] ppc64: fix deadlocks when oopsing while mmap_sem is held
If a fault in the kernel leads to an unexpected protection fault whilst in a code path which holds mmap_sem we will deadlock in do_page_fault() while trying to classify the fault. By carefully testing the source of the fault we can detect and OOPS on the vast majority of these, greatly enhancing diagnosis of such bugs. Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-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;