Skip to contentSkip to conversation

make sigaltstack() compatible with swapcontext()

PATCH Superseded Stas Sergeev 1 participant 5 messages Jan 31, 2016

Subsystems (6)
x86 Kselftest MM core Scheduler +2 more subsystems

v2 of 6 Superseded Latest is v6 →

lore
Share & tools
lore
#0 lore raw

v2 cover make sigaltstack() compatible with swapcontext()

The following patches add the self-test for sigaltstack(SS_DISABLE) inside the signal handler, and allow an app to temporarily disable and re-enable the sigaltstack within a sighandler.
This is needed to make sigaltstack() compatible with swapcontext(): before using swapcontext() inside the sighandler, the sigaltstack should be disabled, or the stack will be corrupted by the subsequent signals.

v2: avoid duplicating the "!sigstack" check in get_sigframe() as requested by Andy Lutomirski.

 arch/score/kernel/signal.c                   |    2
 arch/x86/kernel/signal.c                     |    2
 include/linux/sched.h                        |    8 +
 include/linux/signal.h                       |    2
 kernel/fork.c                                |    4
 kernel/signal.c                              |   54 +++++++----
 tools/testing/selftests/Makefile             |    1
 tools/testing/selftests/sigaltstack/Makefile |    8 +
 tools/testing/selftests/sigaltstack/sas.c    |  132 
+++++++++++++++++++++++++++
 9 files changed, 189 insertions(+), 24 deletions(-)
#1 lore raw

PATCH v2 1/4 selftests: Add test for sigaltstack(SS_DISABLE) inside sighandler

sigaltstack needs to be disabled before the signal handler can safely use swapcontext(). Unfortunately linux implementation of sigaltstack() returns EPERM in that case.
Re-enabling is also needed and tested.

---
 tools/testing/selftests/Makefile             |   1 +
 tools/testing/selftests/sigaltstack/Makefile |   8 ++
 tools/testing/selftests/sigaltstack/sas.c    | 132 
+++++++++++++++++++++++++++
 3 files changed, 141 insertions(+)
 create mode 100644 tools/testing/selftests/sigaltstack/Makefile
 create mode 100644 tools/testing/selftests/sigaltstack/sas.c
tools/testing/selftests/Makefile +1 -0
index b04afc3..ff9e5f2 100644
@@ -19,6 +19,7 @@ TARGETS += powerpc
 TARGETS += pstore
 TARGETS += ptrace
 TARGETS += seccomp
+TARGETS += sigaltstack
 TARGETS += size
 TARGETS += static_keys
 TARGETS += sysctl
tools/testing/selftests/sigaltstack/Makefile new file +8 -0
new file mode 100644
index 0000000..56af56e
@@ -0,0 +1,8 @@
+CFLAGS = -Wall
+BINARIES = sas
+all: $(BINARIES)
+
+include ../lib.mk
+
+clean:
+    rm -rf $(BINARIES)
tools/testing/selftests/sigaltstack/sas.c new file +132 -0

132 lines not on this page · raw message

Signature · 1 line
2.5.0
#2 lore raw

PATCH v2 2/4 score: signal: fix sigaltstack check

In reply to Stas Sergeev

Currently get_sigframe() checks only
(ka->sa.sa_flags & SA_ONSTACK) && (!on_sig_stack(sp)) to determine whether the switch to sigaltstack is needed. It forgets to checks whether the sigaltstack was previously set.

This patch replaces the !on_sig_stack(sp) with the standard check sas_ss_flags(sp) == 0
that takes into account both conditions: it succeeds only if the sigaltstack is enabled but currently not active.

CC Andy Lutomirski <[email protected]>
CC Chen Liqin <[email protected]>
CC Lennox Wu <[email protected]>
CC Michael Ellerman <[email protected]>
CC Andrew Morton <[email protected]>
CC James Hogan <[email protected]>
---
 arch/score/kernel/signal.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
arch/score/kernel/signal.c +1 -1
index e381c8c..bd1c7c8 100644
@@ -127,7 +127,7 @@ static void __user *get_sigframe(struct k_sigaction *ka,
     sp -= 32;
 
     /* This is the X/Open sanctioned signal stack switching.  */
-    if ((ka->sa.sa_flags & SA_ONSTACK) && (!on_sig_stack(sp)))
+    if ((ka->sa.sa_flags & SA_ONSTACK) && (sas_ss_flags(sp) == 0))
         sp = current->sas_ss_sp + current->sas_ss_size;
 
     return (void __user*)((sp - frame_size) & ~7);
Signature · 1 line
2.5.0
#3 lore raw

PATCH v2 3/4 x86: signal: unify the sigaltstack check with other arches

In reply to Stas Sergeev

Currently x86's get_sigframe() checks for "current->sas_ss_size" to determine whether there is a need to switch to sigaltstack. The common practice used by all other arches is to check for sas_ss_flags(sp) == 0

This patch makes the code consistent with other arches and also allows for the further sigaltstack improvements within this patch serie. The slight complexity of the patch is added by the optimization on !sigstack check that was requested by Andy Lutomirski: sas_ss_flags(sp)==0 already implies that we are not on a sigstack, so the code is shuffled to avoid the duplicate checking.

CC Andy Lutomirski <[email protected]>
CC Thomas Gleixner <[email protected]>
CC Ingo Molnar <[email protected]>
CC "H. Peter Anvin" <[email protected]>
CC Borislav Petkov <[email protected]>
CC Brian Gerst <[email protected]>
CC Oleg Nesterov <[email protected]>
CC Richard Weinberger <[email protected]>
---
 arch/x86/kernel/signal.c | 23 +++++++++++------------
 1 file changed, 11 insertions(+), 12 deletions(-)
arch/x86/kernel/signal.c +11 -12
index cb6282c..285183b 100644
@@ -213,18 +213,17 @@ get_sigframe(struct k_sigaction *ka, struct pt_regs *regs, size_t frame_size,
     if (config_enabled(CONFIG_X86_64))
         sp -= 128;
 
-    if (!onsigstack) {
-        /* This is the X/Open sanctioned signal stack switching. */
-        if (ka->sa.sa_flags & SA_ONSTACK) {
-            if (current->sas_ss_size)
-                sp = current->sas_ss_sp + current->sas_ss_size;
-        } else if (config_enabled(CONFIG_X86_32) &&
-               (regs->ss & 0xffff) != __USER_DS &&
-               !(ka->sa.sa_flags & SA_RESTORER) &&
-               ka->sa.sa_restorer) {
-                /* This is the legacy signal stack switching. */
-                sp = (unsigned long) ka->sa.sa_restorer;
-        }
+    /* This is the X/Open sanctioned signal stack switching.  */
+    if (ka->sa.sa_flags & SA_ONSTACK) {
+        if (sas_ss_flags(sp) == 0)
+            sp = current->sas_ss_sp + current->sas_ss_size;
+    } else if (config_enabled(CONFIG_X86_32) &&
+           !onsigstack &&
+           (regs->ss & 0xffff) != __USER_DS &&
+           !(ka->sa.sa_flags & SA_RESTORER) &&
+           ka->sa.sa_restorer) {
+        /* This is the legacy signal stack switching. */
+        sp = (unsigned long) ka->sa.sa_restorer;
     }
 
     if (fpu->fpstate_active) {
Signature · 1 line
2.5.0
#4 lore raw

PATCH v2 4/4 sigaltstack: allow disabling and re-enabling sas within sighandler

In reply to Stas Sergeev

linux implements the sigaltstack() in a way that makes it impossible to use with swapcontext(). Per the man page, sigaltstack is allowed to return EPERM if the process is altering its sigaltstack while running on sigaltstack.
This is likely needed to consistently return oss->ss_flags, that indicates whether the process is being on sigaltstack or not. Unfortunately, linux takes that permission to return EPERM too literally:
it returns EPERM even if you don't want to change to another sigaltstack, but only want to temporarily disable sigaltstack with SS_DISABLE. You can't use swapcontext() without disabling sigaltstack first, or the stack will be re-used and overwritten by a subsequent signal.

With this patch, disabling sigaltstack inside a signal handler became possible, and the swapcontext() can then be used safely. After switching back to the sighandler, the app can re-enable the sigatlstack. The oss->ss_flags will correctly indicate the current use of sigaltstack, even if it is temporarily disabled. Any attempt to modify the sigaltstack (rather than to disable or re-enable it) within the sighandler, will still be punished with EPERM as suggested by POSIX.

CC Ingo Molnar <[email protected]>
CC Peter Zijlstra <[email protected]>
CC Andrew Morton <[email protected]>
CC Oleg Nesterov <[email protected]>
CC "Amanieu d'Antras" <[email protected]>
CC Richard Weinberger <[email protected]>
CC Andy Lutomirski <[email protected]>
CC Tejun Heo <[email protected]>
CC "Kirill A. Shutemov" <[email protected]>
CC Jason Low <[email protected]>
CC Heinrich Schuchardt <[email protected]>
CC Andrea Arcangeli <[email protected]>
CC Konstantin Khlebnikov <[email protected]>
CC Josh Triplett <[email protected]>
CC "Eric W. Biederman" <[email protected]>
CC Aleksa Sarai <[email protected]>
CC Paul Moore <[email protected]>
CC Palmer Dabbelt <[email protected]>
CC Vladimir Davydov <[email protected]>
---
 include/linux/sched.h  |  8 ++++++--
 include/linux/signal.h |  2 +-
 kernel/fork.c          |  4 +++-
 kernel/signal.c        | 54 
+++++++++++++++++++++++++++++++++-----------------
 4 files changed, 46 insertions(+), 22 deletions(-)
include/linux/sched.h +6 -2
index a10494a..38f67dc 100644
@@ -1587,6 +1587,7 @@ struct task_struct {
 
     unsigned long sas_ss_sp;
     size_t sas_ss_size;
+    int sas_ss_flags;
 
     struct callback_head *task_works;
 
@@ -2569,8 +2570,11 @@ static inline int sas_ss_flags(unsigned long sp)
 {
     if (!current->sas_ss_size)
         return SS_DISABLE;
-
-    return on_sig_stack(sp) ? SS_ONSTACK : 0;
+    if (on_sig_stack(sp))
+        return SS_ONSTACK;
+    if (current->sas_ss_flags == SS_DISABLE)
+        return SS_DISABLE;
+    return 0;
 }
 
 static inline unsigned long sigsp(unsigned long sp, struct ksignal *ksig)
include/linux/signal.h +1 -1
index 92557bb..844b113 100644
@@ -432,7 +432,7 @@ int __save_altstack(stack_t __user *, unsigned long);
     stack_t __user *__uss = uss; \
     struct task_struct *t = current; \
     put_user_ex((void __user *)t->sas_ss_sp, &__uss->ss_sp); \
-    put_user_ex(sas_ss_flags(sp), &__uss->ss_flags); \
+    put_user_ex(t->sas_ss_flags, &__uss->ss_flags); \
     put_user_ex(t->sas_ss_size, &__uss->ss_size); \
 } while (0);
 
kernel/fork.c +3 -1
index 2e391c7..ce840a9 100644
@@ -1482,8 +1482,10 @@ static struct task_struct *copy_process(unsigned long clone_flags,
     /*
      * sigaltstack should be cleared when sharing the same VM
      */
-    if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
+    if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM) {
         p->sas_ss_sp = p->sas_ss_size = 0;
+        p->sas_ss_flags = SS_DISABLE;
+    }
 
     /*
      * Syscall tracing and stepping should be turned off in the
kernel/signal.c +1 -11
index f3f1f7a..d19180e 100644
@@ -3101,6 +3101,7 @@ do_sigaltstack (const stack_t __user *uss, stack_t __user *uoss, unsigned long s
         void __user *ss_sp;
         size_t ss_size;
         int ss_flags;
+        int onsigstack;
 
         error = -EFAULT;
         if (!access_ok(VERIFY_READ, uss, sizeof(*uss)))
@@ -3111,32 +3112,49 @@ do_sigaltstack (const stack_t __user *uss, stack_t __user *uoss, unsigned long s
         if (error)
             goto out;
 
-        error = -EPERM;
-        if (on_sig_stack(sp))
-            goto out;
-
         error = -EINVAL;
-        /*
-         * Note - this code used to test ss_flags incorrectly:
-         *        old code may have been written using ss_flags==0
-         *      to mean ss_flags==SS_ONSTACK (as this was the only
-         *      way that worked) - this fix preserves that older
-         *      mechanism.
-         */
         if (ss_flags != SS_DISABLE && ss_flags != SS_ONSTACK && 

ss_flags != 0)

             goto out;

- if (ss_flags == SS_DISABLE) {
- ss_size = 0;
- ss_sp = NULL;

-        } else {
+        onsigstack = on_sig_stack(sp);
+        if (ss_size == 0) {

+ switch (ss_flags) {
+ case 0:
+ error = -EPERM;
+ if (onsigstack)
+ goto out;

+                current->sas_ss_sp = 0;
+                current->sas_ss_size = 0;
+                current->sas_ss_flags = SS_DISABLE;
+                break;
+            case SS_ONSTACK:
+                /* re-enable previously disabled sas */
+                error = -EINVAL;
+                if (current->sas_ss_size == 0)

+ goto out;
+ break;
+ default:
+ break;
+ }
+ } else if (ss_flags != SS_DISABLE) {
+ error = -EPERM;
+ if (onsigstack)

+                goto out;
             error = -ENOMEM;
             if (ss_size < MINSIGSTKSZ)
                 goto out;
+            current->sas_ss_sp = (unsigned long) ss_sp;
+            current->sas_ss_size = ss_size;
+            /* unfortunately POSIX forces us to treat 0
+             * as SS_ONSTACK here, and some legacy apps
+             * perhaps used that...
+             */
+            if (ss_flags == 0)
+                ss_flags = SS_ONSTACK;
         }
-        current->sas_ss_sp = (unsigned long) ss_sp;
-        current->sas_ss_size = ss_size;
+        if (ss_flags != 0)
+            current->sas_ss_flags = ss_flags;
     }

     error = 0;
@@ -3168,7 +3186,7 @@ int __save_altstack(stack_t __user *uss, unsigned long sp)
 {
     struct task_struct *t = current;
     return  __put_user((void __user *)t->sas_ss_sp, &uss->ss_sp) |
-        __put_user(sas_ss_flags(sp), &uss->ss_flags) |
+        __put_user(t->sas_ss_flags, &uss->ss_flags) |
         __put_user(t->sas_ss_size, &uss->ss_size);

}

Signature · 1 line
2.5.0