make sigaltstack() compatible with swapcontext()
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(-)
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
@@ -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
@@ -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
PATCH v2 2/4 score: signal: fix sigaltstack check
In reply to Stas SergeevCurrently 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.
--- arch/score/kernel/signal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/score/kernel/signal.c +1 -1
@@ -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
PATCH v2 3/4 x86: signal: unify the sigaltstack check with other arches
In reply to Stas SergeevCurrently 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.
--- arch/x86/kernel/signal.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-)
arch/x86/kernel/signal.c +11 -12
@@ -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
PATCH v2 4/4 sigaltstack: allow disabling and re-enabling sas within sighandler
In reply to Stas Sergeevlinux 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.
--- 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
@@ -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
@@ -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
@@ -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
@@ -3101,6 +3101,7 @@ do_sigaltstack (const stack_t __user *uss, stack_t __user *uoss, unsigned long svoid __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 sif (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