]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
vm: add VM_FAULT_SIGSEGV handling support
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 29 Jan 2015 18:51:32 +0000 (10:51 -0800)
committerBen Hutchings <ben@decadent.org.uk>
Fri, 20 Feb 2015 00:49:40 +0000 (00:49 +0000)
commit 33692f27597fcab536d7cbbcc8f52905133e4aa7 upstream.

The core VM already knows about VM_FAULT_SIGBUS, but cannot return a
"you should SIGSEGV" error, because the SIGSEGV case was generally
handled by the caller - usually the architecture fault handler.

That results in lots of duplication - all the architecture fault
handlers end up doing very similar "look up vma, check permissions, do
retries etc" - but it generally works.  However, there are cases where
the VM actually wants to SIGSEGV, and applications _expect_ SIGSEGV.

In particular, when accessing the stack guard page, libsigsegv expects a
SIGSEGV.  And it usually got one, because the stack growth is handled by
that duplicated architecture fault handler.

However, when the generic VM layer started propagating the error return
from the stack expansion in commit fee7e49d4514 ("mm: propagate error
from stack expansion even for guard page"), that now exposed the
existing VM_FAULT_SIGBUS result to user space.  And user space really
expected SIGSEGV, not SIGBUS.

To fix that case, we need to add a VM_FAULT_SIGSEGV, and teach all those
duplicate architecture fault handlers about it.  They all already have
the code to handle SIGSEGV, so it's about just tying that new return
value to the existing code, but it's all a bit annoying.

This is the mindless minimal patch to do this.  A more extensive patch
would be to try to gather up the mostly shared fault handling logic into
one generic helper routine, and long-term we really should do that
cleanup.

Just from this patch, you can generally see that most architectures just
copied (directly or indirectly) the old x86 way of doing things, but in
the meantime that original x86 model has been improved to hold the VM
semaphore for shorter times etc and to handle VM_FAULT_RETRY and other
"newer" things, so it would be a good idea to bring all those
improvements to the generic case and teach other architectures about
them too.

Reported-and-tested-by: Takashi Iwai <tiwai@suse.de>
Tested-by: Jan Engelhardt <jengelh@inai.de>
Acked-by: Heiko Carstens <heiko.carstens@de.ibm.com> # "s390 still compiles and boots"
Cc: linux-arch@vger.kernel.org
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[bwh: Backported to 3.2:
 - Adjust filenames, context
 - Drop arc, metag, nios2 and lustre changes
 - For sh, patch both 32-bit and 64-bit implementations to use goto bad_area
 - For s390, pass int_code and trans_exc_code as arguments to do_no_context()
   and do_sigsegv()]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
27 files changed:
arch/alpha/mm/fault.c
arch/avr32/mm/fault.c
arch/cris/mm/fault.c
arch/frv/mm/fault.c
arch/ia64/mm/fault.c
arch/m32r/mm/fault.c
arch/m68k/mm/fault.c
arch/microblaze/mm/fault.c
arch/mips/mm/fault.c
arch/mn10300/mm/fault.c
arch/openrisc/mm/fault.c
arch/parisc/mm/fault.c
arch/powerpc/mm/fault.c
arch/powerpc/platforms/cell/spu_fault.c
arch/s390/mm/fault.c
arch/score/mm/fault.c
arch/sh/mm/fault_32.c
arch/sh/mm/tlbflush_64.c
arch/sparc/mm/fault_32.c
arch/sparc/mm/fault_64.c
arch/tile/mm/fault.c
arch/um/kernel/trap.c
arch/x86/mm/fault.c
arch/xtensa/mm/fault.c
include/linux/mm.h
mm/ksm.c
mm/memory.c

index fadd5f882ff9182e89bb8fdddbed626b8a4a5c1d..e576b912536edd632de8ff0dda71045f6fa68de2 100644 (file)
@@ -150,6 +150,8 @@ do_page_fault(unsigned long address, unsigned long mmcsr,
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index f7040a1e399f0acd24a0cd51a17940e5ca73dfc8..632b649c7be07bf07baaf3f121d866b063425f25 100644 (file)
@@ -136,6 +136,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 9dcac8ec8fa0b8837f7faa9a08b31ccd5a256716..280c8eafd1b5335a211348f95290a1c122b5d45a 100644 (file)
@@ -166,6 +166,8 @@ do_page_fault(unsigned long address, struct pt_regs *regs,
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index a325d57a83d5fdae12d8323480564d8204e86117..46a3c185444c67399ee4ff0fb172fdb528a0425f 100644 (file)
@@ -167,6 +167,8 @@ asmlinkage void do_page_fault(int datammu, unsigned long esr0, unsigned long ear
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 20b3593761282746777f2f27304e5ac03bdee1da..1e362cdb96142ad0708695062c3e45411ca02aec 100644 (file)
@@ -163,6 +163,8 @@ ia64_do_page_fault (unsigned long address, unsigned long isr, struct pt_regs *re
                 */
                if (fault & VM_FAULT_OOM) {
                        goto out_of_memory;
+               } else if (fault & VM_FAULT_SIGSEGV) {
+                       goto bad_area;
                } else if (fault & VM_FAULT_SIGBUS) {
                        signal = SIGBUS;
                        goto bad_area;
index 2c9aeb453847e2c33d899c195f1645a1d434e1e0..beda9cc8e94330d310552e77bf0a35d620683392 100644 (file)
@@ -199,6 +199,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 2db6099784ba33d9254772417dcce05a7ac5eb36..d605b93e67a5ee345fc4939370a79f9dd46ec4cb 100644 (file)
@@ -147,6 +147,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto map_err;
                else if (fault & VM_FAULT_SIGBUS)
                        goto bus_err;
                BUG();
index ae97d2ccdc22c88da115c8fd8400ebc697d04804..31bb38181003582dfe2e8e5f9a48115251c52a33 100644 (file)
@@ -215,6 +215,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 937cf3368164c6f6d4a6db4b1867ca5866a36ed7..b8314cfe33685349f5a8957b8763b7b4029fe9f7 100644 (file)
@@ -149,6 +149,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 0945409a802219cb8b1d7f09ef5410941a4756c0..fe2ceb727f350baac37a142df1e14810b3f41f85 100644 (file)
@@ -256,6 +256,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index a5dce82f864b2d41f0a39a3daea998ba1938f069..162abfbced69c8120c23c0174210beacfaf9e182 100644 (file)
@@ -163,6 +163,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 18162ce4261ef63eae30818c2eb5ffa4608ba728..a9b765a999ef17c067e2e864850f03f4212f518c 100644 (file)
@@ -210,6 +210,8 @@ good_area:
                 */
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto bad_area;
                BUG();
index 5efe8c96d37fede5785f3a3e3947cb050d1a02de..7450843aa739993db97935694d268b851c89cf5b 100644 (file)
@@ -312,6 +312,8 @@ good_area:
         */
        ret = handle_mm_fault(mm, vma, address, is_write ? FAULT_FLAG_WRITE : 0);
        if (unlikely(ret & VM_FAULT_ERROR)) {
+               if (ret & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                if (ret & VM_FAULT_OOM)
                        goto out_of_memory;
                else if (ret & VM_FAULT_SIGBUS)
index 641e7273d75ae687335716c5b4e87c9afcdb98c7..62f3e4e48a0b235a0bfe6ad577feccc8a3dc36aa 100644 (file)
@@ -75,7 +75,7 @@ int spu_handle_mm_fault(struct mm_struct *mm, unsigned long ea,
                if (*flt & VM_FAULT_OOM) {
                        ret = -ENOMEM;
                        goto out_unlock;
-               } else if (*flt & VM_FAULT_SIGBUS) {
+               } else if (*flt & (VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV)) {
                        ret = -EFAULT;
                        goto out_unlock;
                }
index 0fc0a7e3c0ee2f92ebd7d3e152175360603b8891..b53339d9045b22cc15fbedc359ceaa09f36013e9 100644 (file)
@@ -249,6 +249,13 @@ static noinline void do_fault_error(struct pt_regs *regs, long int_code,
                                do_no_context(regs, int_code, trans_exc_code);
                        else
                                pagefault_out_of_memory();
+               } else if (fault & VM_FAULT_SIGSEGV) {
+                       /* Kernel mode? Handle exceptions or die */
+                       if (!user_mode(regs))
+                               do_no_context(regs, int_code, trans_exc_code);
+                       else
+                               do_sigsegv(regs, int_code, SEGV_MAPERR,
+                                          trans_exc_code);
                } else if (fault & VM_FAULT_SIGBUS) {
                        /* Kernel mode? Handle exceptions or die */
                        if (!(regs->psw.mask & PSW_MASK_PSTATE))
index 47b600e4b2c50ae853b72404f3e1b051dce15f53..b3744ca70be6118ce23d236b2da95d653f481160 100644 (file)
@@ -110,6 +110,8 @@ survive:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 7bebd044f2a1fc02f598c3293a358064784c2045..db1448247a5cb9f7ad6680df68f8202ff3dcc274 100644 (file)
@@ -206,6 +206,8 @@ good_area:
                        goto out_of_memory;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                BUG();
        }
        if (fault & VM_FAULT_MAJOR) {
index e3430e093d436d300bb1928ea4320ce230b1d632..43eef7b170263cbb66a09ae3dc5fa5354727e126 100644 (file)
@@ -195,6 +195,8 @@ good_area:
                        goto out_of_memory;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                BUG();
        }
 
index 8023fd7e77b50c67e07a3830bc0e4b5e0571b746..802b80688933975fb4c496f05459388debe3f685 100644 (file)
@@ -294,6 +294,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 2c0b966427ea8c22d636243a6cdfffb95d28cdb9..bfd7c029ed433bd217bac7f4f5b09b4168019e2d 100644 (file)
@@ -435,6 +435,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 25b7b90fd62064b46cf6faf0204a7cbc78b28cc1..c796ce44fc8f3b43cbe0ac398635d62e4328f362 100644 (file)
@@ -424,6 +424,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index dafc9471595021748eda5e750a80d3483a662379..f79ffc901d0e83cf3ad078e5944c8d1223a3b126 100644 (file)
@@ -69,6 +69,8 @@ good_area:
                if (unlikely(fault & VM_FAULT_ERROR)) {
                        if (fault & VM_FAULT_OOM) {
                                goto out_of_memory;
+                       } else if (fault & VM_FAULT_SIGSEGV) {
+                               goto out;
                        } else if (fault & VM_FAULT_SIGBUS) {
                                err = -EACCES;
                                goto out;
index 53a7b695d7a545bffc0bf9103bc716de46f81091..8cac08822a0f734d2f3892fb0de79cf13b737842 100644 (file)
@@ -877,6 +877,8 @@ mm_fault_error(struct pt_regs *regs, unsigned long error_code,
                if (fault & (VM_FAULT_SIGBUS|VM_FAULT_HWPOISON|
                             VM_FAULT_HWPOISON_LARGE))
                        do_sigbus(regs, error_code, address, fault);
+               else if (fault & VM_FAULT_SIGSEGV)
+                       bad_area_nosemaphore(regs, error_code, address);
                else
                        BUG();
        }
index e367e30264366d82c17dc66d157a2aa2534388bc..4439a1d6babf0fb1d964456bea12766ff73a17f0 100644 (file)
@@ -109,6 +109,8 @@ good_area:
        if (unlikely(fault & VM_FAULT_ERROR)) {
                if (fault & VM_FAULT_OOM)
                        goto out_of_memory;
+               else if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
                else if (fault & VM_FAULT_SIGBUS)
                        goto do_sigbus;
                BUG();
index 5a9c599a1f7798fbd6e1f45621d532d3f27a7c46..e5ee683b0ad3079984028fcd99b770757646226e 100644 (file)
@@ -840,6 +840,7 @@ static inline int page_mapped(struct page *page)
 #define VM_FAULT_WRITE 0x0008  /* Special case for get_user_pages */
 #define VM_FAULT_HWPOISON 0x0010       /* Hit poisoned small page */
 #define VM_FAULT_HWPOISON_LARGE 0x0020  /* Hit poisoned large page. Index encoded in upper bits */
+#define VM_FAULT_SIGSEGV 0x0040
 
 #define VM_FAULT_NOPAGE        0x0100  /* ->fault installed the pte, not return page */
 #define VM_FAULT_LOCKED        0x0200  /* ->fault locked the returned page */
@@ -847,8 +848,8 @@ static inline int page_mapped(struct page *page)
 
 #define VM_FAULT_HWPOISON_LARGE_MASK 0xf000 /* encodes hpage index for large hwpoison */
 
-#define VM_FAULT_ERROR (VM_FAULT_OOM | VM_FAULT_SIGBUS | VM_FAULT_HWPOISON | \
-                        VM_FAULT_HWPOISON_LARGE)
+#define VM_FAULT_ERROR (VM_FAULT_OOM | VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV | \
+                        VM_FAULT_HWPOISON | VM_FAULT_HWPOISON_LARGE)
 
 /* Encode hstate index for a hwpoisoned large page */
 #define VM_FAULT_SET_HINDEX(x) ((x) << 12)
index 310544a379ae9c7b886b3b50815e5f3d5a991ba8..6741c9df5c80c0d544f7df4caacab7fadac2587c 100644 (file)
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -342,7 +342,7 @@ static int break_ksm(struct vm_area_struct *vma, unsigned long addr)
                else
                        ret = VM_FAULT_WRITE;
                put_page(page);
-       } while (!(ret & (VM_FAULT_WRITE | VM_FAULT_SIGBUS | VM_FAULT_OOM)));
+       } while (!(ret & (VM_FAULT_WRITE | VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV | VM_FAULT_OOM)));
        /*
         * We must loop because handle_mm_fault() may back out if there's
         * any difficulty e.g. if pte accessed bit gets updated concurrently.
index 759f915ec20bd6b9e0732b3601593880bd7f5344..816002cc45130230f0770effebdc2381f87bcf6b 100644 (file)
@@ -1767,7 +1767,7 @@ int __get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
                                                else
                                                        return -EFAULT;
                                        }
-                                       if (ret & VM_FAULT_SIGBUS)
+                                       if (ret & (VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV))
                                                return i ? i : -EFAULT;
                                        BUG();
                                }
@@ -1871,7 +1871,7 @@ int fixup_user_fault(struct task_struct *tsk, struct mm_struct *mm,
                        return -ENOMEM;
                if (ret & (VM_FAULT_HWPOISON | VM_FAULT_HWPOISON_LARGE))
                        return -EHWPOISON;
-               if (ret & VM_FAULT_SIGBUS)
+               if (ret & (VM_FAULT_SIGBUS | VM_FAULT_SIGSEGV))
                        return -EFAULT;
                BUG();
        }