Merge branch 'core-signals-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull core signal updates from Ingo Molnar: "These updates from Stas Sergeev and Andy Lutomirski, improve the sigaltstack interface by extending its ABI with the SS_AUTODISARM feature, which makes it possible to use swapcontext() in a sighandler that works on sigaltstack. Without this flag, the subsequent signal will corrupt the state of the switched-away sighandler. The inspiration is more robust dosemu signal handling" * 'core-signals-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: signals/sigaltstack: Change SS_AUTODISARM to (1U << 31) signals/sigaltstack: Report current flag bits in sigaltstack() selftests/sigaltstack: Fix the sigaltstack test on old kernels signals/sigaltstack: If SS_AUTODISARM, bypass on_sig_stack() selftests/sigaltstack: Add new testcase for sigaltstack(SS_ONSTACK|SS_AUTODISARM) signals/sigaltstack: Implement SS_AUTODISARM flag signals/sigaltstack: Prepare to add new SS_xxx flags signals/sigaltstack, x86/signals: Unify the x86 sigaltstack check with other architectures
This commit is contained in:
commit
230e51f211
|
@ -248,18 +248,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) {
|
||||
|
|
|
@ -1595,6 +1595,7 @@ struct task_struct {
|
|||
|
||||
unsigned long sas_ss_sp;
|
||||
size_t sas_ss_size;
|
||||
unsigned sas_ss_flags;
|
||||
|
||||
struct callback_head *task_works;
|
||||
|
||||
|
@ -2574,6 +2575,18 @@ static inline int kill_cad_pid(int sig, int priv)
|
|||
*/
|
||||
static inline int on_sig_stack(unsigned long sp)
|
||||
{
|
||||
/*
|
||||
* If the signal stack is SS_AUTODISARM then, by construction, we
|
||||
* can't be on the signal stack unless user code deliberately set
|
||||
* SS_AUTODISARM when we were already on it.
|
||||
*
|
||||
* This improves reliability: if user state gets corrupted such that
|
||||
* the stack pointer points very close to the end of the signal stack,
|
||||
* then this check will enable the signal to be handled anyway.
|
||||
*/
|
||||
if (current->sas_ss_flags & SS_AUTODISARM)
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_STACK_GROWSUP
|
||||
return sp >= current->sas_ss_sp &&
|
||||
sp - current->sas_ss_sp < current->sas_ss_size;
|
||||
|
@ -2591,6 +2604,13 @@ static inline int sas_ss_flags(unsigned long sp)
|
|||
return on_sig_stack(sp) ? SS_ONSTACK : 0;
|
||||
}
|
||||
|
||||
static inline void sas_ss_reset(struct task_struct *p)
|
||||
{
|
||||
p->sas_ss_sp = 0;
|
||||
p->sas_ss_size = 0;
|
||||
p->sas_ss_flags = SS_DISABLE;
|
||||
}
|
||||
|
||||
static inline unsigned long sigsp(unsigned long sp, struct ksignal *ksig)
|
||||
{
|
||||
if (unlikely((ksig->ka.sa.sa_flags & SA_ONSTACK)) && ! sas_ss_flags(sp))
|
||||
|
|
|
@ -432,8 +432,10 @@ 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); \
|
||||
if (t->sas_ss_flags & SS_AUTODISARM) \
|
||||
sas_ss_reset(t); \
|
||||
} while (0);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
|
|
@ -7,4 +7,9 @@
|
|||
#define SS_ONSTACK 1
|
||||
#define SS_DISABLE 2
|
||||
|
||||
/* bit-flags */
|
||||
#define SS_AUTODISARM (1U << 31) /* disable sas during sighandling */
|
||||
/* mask for all SS_xxx flags */
|
||||
#define SS_FLAG_BITS SS_AUTODISARM
|
||||
|
||||
#endif /* _UAPI_LINUX_SIGNAL_H */
|
||||
|
|
|
@ -1494,7 +1494,7 @@ 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)
|
||||
p->sas_ss_sp = p->sas_ss_size = 0;
|
||||
sas_ss_reset(p);
|
||||
|
||||
/*
|
||||
* Syscall tracing and stepping should be turned off in the
|
||||
|
|
|
@ -3099,12 +3099,14 @@ do_sigaltstack (const stack_t __user *uss, stack_t __user *uoss, unsigned long s
|
|||
|
||||
oss.ss_sp = (void __user *) current->sas_ss_sp;
|
||||
oss.ss_size = current->sas_ss_size;
|
||||
oss.ss_flags = sas_ss_flags(sp);
|
||||
oss.ss_flags = sas_ss_flags(sp) |
|
||||
(current->sas_ss_flags & SS_FLAG_BITS);
|
||||
|
||||
if (uss) {
|
||||
void __user *ss_sp;
|
||||
size_t ss_size;
|
||||
int ss_flags;
|
||||
unsigned ss_flags;
|
||||
int ss_mode;
|
||||
|
||||
error = -EFAULT;
|
||||
if (!access_ok(VERIFY_READ, uss, sizeof(*uss)))
|
||||
|
@ -3119,18 +3121,13 @@ do_sigaltstack (const stack_t __user *uss, stack_t __user *uoss, unsigned long s
|
|||
if (on_sig_stack(sp))
|
||||
goto out;
|
||||
|
||||
ss_mode = ss_flags & ~SS_FLAG_BITS;
|
||||
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)
|
||||
if (ss_mode != SS_DISABLE && ss_mode != SS_ONSTACK &&
|
||||
ss_mode != 0)
|
||||
goto out;
|
||||
|
||||
if (ss_flags == SS_DISABLE) {
|
||||
if (ss_mode == SS_DISABLE) {
|
||||
ss_size = 0;
|
||||
ss_sp = NULL;
|
||||
} else {
|
||||
|
@ -3141,6 +3138,7 @@ do_sigaltstack (const stack_t __user *uss, stack_t __user *uoss, unsigned long s
|
|||
|
||||
current->sas_ss_sp = (unsigned long) ss_sp;
|
||||
current->sas_ss_size = ss_size;
|
||||
current->sas_ss_flags = ss_flags;
|
||||
}
|
||||
|
||||
error = 0;
|
||||
|
@ -3171,9 +3169,14 @@ int restore_altstack(const stack_t __user *uss)
|
|||
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) |
|
||||
int err = __put_user((void __user *)t->sas_ss_sp, &uss->ss_sp) |
|
||||
__put_user(t->sas_ss_flags, &uss->ss_flags) |
|
||||
__put_user(t->sas_ss_size, &uss->ss_size);
|
||||
if (err)
|
||||
return err;
|
||||
if (t->sas_ss_flags & SS_AUTODISARM)
|
||||
sas_ss_reset(t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
|
|
|
@ -19,6 +19,7 @@ TARGETS += powerpc
|
|||
TARGETS += pstore
|
||||
TARGETS += ptrace
|
||||
TARGETS += seccomp
|
||||
TARGETS += sigaltstack
|
||||
TARGETS += size
|
||||
TARGETS += static_keys
|
||||
TARGETS += sysctl
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
CFLAGS = -Wall
|
||||
BINARIES = sas
|
||||
all: $(BINARIES)
|
||||
|
||||
include ../lib.mk
|
||||
|
||||
clean:
|
||||
rm -rf $(BINARIES)
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Stas Sergeev <stsp@users.sourceforge.net>
|
||||
*
|
||||
* test sigaltstack(SS_ONSTACK | SS_AUTODISARM)
|
||||
* If that succeeds, then swapcontext() can be used inside sighandler safely.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <ucontext.h>
|
||||
#include <alloca.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef SS_AUTODISARM
|
||||
#define SS_AUTODISARM (1U << 31)
|
||||
#endif
|
||||
|
||||
static void *sstack, *ustack;
|
||||
static ucontext_t uc, sc;
|
||||
static const char *msg = "[OK]\tStack preserved";
|
||||
static const char *msg2 = "[FAIL]\tStack corrupted";
|
||||
struct stk_data {
|
||||
char msg[128];
|
||||
int flag;
|
||||
};
|
||||
|
||||
void my_usr1(int sig, siginfo_t *si, void *u)
|
||||
{
|
||||
char *aa;
|
||||
int err;
|
||||
stack_t stk;
|
||||
struct stk_data *p;
|
||||
|
||||
register unsigned long sp asm("sp");
|
||||
|
||||
if (sp < (unsigned long)sstack ||
|
||||
sp >= (unsigned long)sstack + SIGSTKSZ) {
|
||||
printf("[FAIL]\tSP is not on sigaltstack\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
/* put some data on stack. other sighandler will try to overwrite it */
|
||||
aa = alloca(1024);
|
||||
assert(aa);
|
||||
p = (struct stk_data *)(aa + 512);
|
||||
strcpy(p->msg, msg);
|
||||
p->flag = 1;
|
||||
printf("[RUN]\tsignal USR1\n");
|
||||
err = sigaltstack(NULL, &stk);
|
||||
if (err) {
|
||||
perror("[FAIL]\tsigaltstack()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (stk.ss_flags != SS_DISABLE)
|
||||
printf("[FAIL]\tss_flags=%i, should be SS_DISABLE\n",
|
||||
stk.ss_flags);
|
||||
else
|
||||
printf("[OK]\tsigaltstack is disabled in sighandler\n");
|
||||
swapcontext(&sc, &uc);
|
||||
printf("%s\n", p->msg);
|
||||
if (!p->flag) {
|
||||
printf("[RUN]\tAborting\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void my_usr2(int sig, siginfo_t *si, void *u)
|
||||
{
|
||||
char *aa;
|
||||
struct stk_data *p;
|
||||
|
||||
printf("[RUN]\tsignal USR2\n");
|
||||
aa = alloca(1024);
|
||||
/* dont run valgrind on this */
|
||||
/* try to find the data stored by previous sighandler */
|
||||
p = memmem(aa, 1024, msg, strlen(msg));
|
||||
if (p) {
|
||||
printf("[FAIL]\tsigaltstack re-used\n");
|
||||
/* corrupt the data */
|
||||
strcpy(p->msg, msg2);
|
||||
/* tell other sighandler that his data is corrupted */
|
||||
p->flag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void switch_fn(void)
|
||||
{
|
||||
printf("[RUN]\tswitched to user ctx\n");
|
||||
raise(SIGUSR2);
|
||||
setcontext(&sc);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
stack_t stk;
|
||||
int err;
|
||||
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_sigaction = my_usr1;
|
||||
sigaction(SIGUSR1, &act, NULL);
|
||||
act.sa_sigaction = my_usr2;
|
||||
sigaction(SIGUSR2, &act, NULL);
|
||||
sstack = mmap(NULL, SIGSTKSZ, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
|
||||
if (sstack == MAP_FAILED) {
|
||||
perror("mmap()");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
err = sigaltstack(NULL, &stk);
|
||||
if (err) {
|
||||
perror("[FAIL]\tsigaltstack()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (stk.ss_flags == SS_DISABLE) {
|
||||
printf("[OK]\tInitial sigaltstack state was SS_DISABLE\n");
|
||||
} else {
|
||||
printf("[FAIL]\tInitial sigaltstack state was %i; should have been SS_DISABLE\n", stk.ss_flags);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
stk.ss_sp = sstack;
|
||||
stk.ss_size = SIGSTKSZ;
|
||||
stk.ss_flags = SS_ONSTACK | SS_AUTODISARM;
|
||||
err = sigaltstack(&stk, NULL);
|
||||
if (err) {
|
||||
if (errno == EINVAL) {
|
||||
printf("[NOTE]\tThe running kernel doesn't support SS_AUTODISARM\n");
|
||||
/*
|
||||
* If test cases for the !SS_AUTODISARM variant were
|
||||
* added, we could still run them. We don't have any
|
||||
* test cases like that yet, so just exit and report
|
||||
* success.
|
||||
*/
|
||||
return 0;
|
||||
} else {
|
||||
perror("[FAIL]\tsigaltstack(SS_ONSTACK | SS_AUTODISARM)");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
ustack = mmap(NULL, SIGSTKSZ, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
|
||||
if (ustack == MAP_FAILED) {
|
||||
perror("mmap()");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
getcontext(&uc);
|
||||
uc.uc_link = NULL;
|
||||
uc.uc_stack.ss_sp = ustack;
|
||||
uc.uc_stack.ss_size = SIGSTKSZ;
|
||||
makecontext(&uc, switch_fn, 0);
|
||||
raise(SIGUSR1);
|
||||
|
||||
err = sigaltstack(NULL, &stk);
|
||||
if (err) {
|
||||
perror("[FAIL]\tsigaltstack()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (stk.ss_flags != SS_AUTODISARM) {
|
||||
printf("[FAIL]\tss_flags=%i, should be SS_AUTODISARM\n",
|
||||
stk.ss_flags);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
printf("[OK]\tsigaltstack is still SS_AUTODISARM after signal\n");
|
||||
|
||||
printf("[OK]\tTest passed\n");
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue