]> 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)
committerZefan Li <lizefan@huawei.com>
Tue, 14 Apr 2015 09:33:57 +0000 (17:33 +0800)
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>
[lizf: Backported to 3.4:
 - adjust context in arch/power/mm/fault.c
 - apply the original change in upstream commit for s390]
Signed-off-by: Zefan Li <lizefan@huawei.com>
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 5eecab1a84efd3430deab218698ab3dab91c0e6a..f153733fb93844e04ab4e66b93089635e98e1c46 100644 (file)
@@ -149,6 +149,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 b4760d86e1bba7b8674b933eb66d798e5ce4d1c9..b07c00c016960c8e9ca49103056be23d80a87491 100644 (file)
@@ -167,6 +167,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 331c1e2cfb6760ee7ed3f38d4e5c92a2526d44bf..854549617dd2eb86c1598d3b5b851c75ad746650 100644 (file)
@@ -166,6 +166,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 02d29c2a132a028f92c2c55e92fc36bbc5edfb2e..3c820ea4212ffefae2d67652b40e910937d26969 100644 (file)
@@ -162,6 +162,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 3cdfa9c1d0915b71969c0943b27da64e64156d53..06827fc92bbf63b5088d9e946a139469d36c7f87 100644 (file)
@@ -198,6 +198,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 6b020a8461e746f6ef461b748ff865aa0f4b5ce0..8646409adf5b4322f24e99652f03de2351ec97f4 100644 (file)
@@ -146,6 +146,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 c38a265846dec68c427398834e9833ceced31753..c7fe3da485933231b00d9021d8a5f4de338d2565 100644 (file)
@@ -214,6 +214,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 c14f6dfed9958008a60daf1447b9d7013e01aa8e..5c9ba6a536d135efcbdc2d2ac25b71642a091062 100644 (file)
@@ -155,6 +155,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 90f346f7392d68df68844fd24472f7d5af8b1bf9..eb411b3cd7e9f044eba92747a12d4c4359d1868d 100644 (file)
@@ -255,6 +255,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 08ffcf52a8564211d609bc2cf1fa8dfc3a6e8678..3d30a4af627faea8a181b2cec2baad2be62e7125 100644 (file)
@@ -419,7 +419,11 @@ good_area:
         */
        fault = handle_mm_fault(mm, vma, address, flags);
        if (unlikely(fault & (VM_FAULT_RETRY|VM_FAULT_ERROR))) {
-               int rc = mm_fault_error(regs, address, fault);
+               int rc;
+
+               if (fault & VM_FAULT_SIGSEGV)
+                       goto bad_area;
+               rc = mm_fault_error(regs, address, fault);
                if (rc >= MM_FAULT_RETURN)
                        return rc;
        }
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 f2b11ee3fb3a0c35333cd17f5123f814934841ad..68ba1bb834bb1c45d458b208a9a5b5774fd25e94 100644 (file)
@@ -237,6 +237,12 @@ static noinline void do_fault_error(struct pt_regs *regs, int fault)
                                do_no_context(regs);
                        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);
+                       else
+                               do_sigsegv(regs, SEGV_MAPERR);
                } 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 e99b104d967a3464d555459b024a19e09263815f..a4919631ce03b0e05863c5a1fb04a0b8f4940333 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 11c5a18f10ed9655c5fbc7acc6e5202b28b05e93..92f0df95cf835b6b5056246922dc526a35082436 100644 (file)
@@ -194,6 +194,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 df3155a179918e0ad5e9741eceab601ae93d8abb..5c6238d720764581056876c80878b782ac87d201 100644 (file)
@@ -300,6 +300,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 1387acafc62c373af68f4096633b201de734cf9b..0dc7b9094a2934aa1aa033397264b347fe8279f3 100644 (file)
@@ -443,6 +443,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 22e58f51ed23eedd405e9dc0b719eddb9550f502..a362926d513acdbe5175f521cb833da5b7925bb5 100644 (file)
@@ -433,6 +433,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 4a0a2e810b4b2e61fc2b5b00a1ffac5827385c0d..b723f2e09f44cae7afd3342125ce81f8ac5d2c8d 100644 (file)
@@ -887,6 +887,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 b17885a0b508fe82e84b012b89539caf3862dfed..b3877be25ab3e8d9448543fd2be0a193e2538abe 100644 (file)
@@ -108,6 +108,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 dacfd4a31788087beafff98e8b19466d2ae3b8c5..ceebf63ef5da546cc437bea4d00bc9e44266bace 100644 (file)
@@ -841,6 +841,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 */
@@ -848,8 +849,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 47c885368890741407eb45e8699acb12fc773aca..77c71185fb790534c4f434b18e126a4f62bd7458 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 c9c93e238ce4ccf42abd0afdde3ca6fa9bcb2c63..f573cecf3583513c20ca0822b59495d8f67ac2ab 100644 (file)
@@ -1787,7 +1787,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();
                                }
@@ -1891,7 +1891,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();
        }