[asan] Unpoison signal alternate stack.

Summary:
Before unwinding the stack, `__asan_handle_no_return` is supposed to
unpoison the entire stack - that is, remove the entries in the shadow
memory corresponding to stack (e.g. redzone markers around variables).
This does not work correctly if `__asan_handle_no_return` is called from
the alternate stack used in signal handlers, because the stack top is
read from a cache, which yields the default stack top instead of the
signal alternate stack top.

It is also possible to jump between the default stack and the signal
alternate stack. Therefore, __asan_handle_no_return needs to unpoison
both.

Reviewers: vitalybuka, kubamracek, kcc, eugenis

Reviewed By: vitalybuka

Subscribers: phosek, #sanitizers

Tags: #sanitizers

Differential Revision: https://reviews.llvm.org/D76986
This commit is contained in:
Vitaly Buka 2020-06-16 02:07:24 -07:00
parent 068fa35746
commit 6b4aeec94a
2 changed files with 188 additions and 1 deletions

View File

@ -17,6 +17,7 @@
#include "asan_internal.h"
#include "asan_interceptors.h"
#include "asan_mapping.h"
#include "asan_poisoning.h"
#include "asan_report.h"
#include "asan_stack.h"
#include "sanitizer_common/sanitizer_libc.h"
@ -24,6 +25,7 @@
#include "sanitizer_common/sanitizer_procmaps.h"
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
@ -37,7 +39,31 @@ void AsanOnDeadlySignal(int signo, void *siginfo, void *context) {
ReportDeadlySignal(sig);
}
bool PlatformUnpoisonStacks() { return false; }
bool PlatformUnpoisonStacks() {
stack_t signal_stack;
CHECK_EQ(0, sigaltstack(nullptr, &signal_stack));
uptr sigalt_bottom = (uptr)signal_stack.ss_sp;
uptr sigalt_top = (uptr)((char *)signal_stack.ss_sp + signal_stack.ss_size);
// If we're executing on the signal alternate stack AND the Linux flag
// SS_AUTODISARM was used, then we cannot get the signal alternate stack
// bounds from sigaltstack -- sigaltstack's output looks just as if no
// alternate stack has ever been set up.
// We're always unpoisoning the signal alternate stack to support jumping
// between the default stack and signal alternate stack.
if (signal_stack.ss_flags != SS_DISABLE)
UnpoisonStack(sigalt_bottom, sigalt_top, "sigalt");
if (signal_stack.ss_flags != SS_ONSTACK)
return false;
// Since we're on the signal altnerate stack, we cannot find the DEFAULT
// stack bottom using a local variable.
uptr default_bottom, tls_addr, tls_size, stack_size;
GetThreadStackAndTls(/*main=*/false, &default_bottom, &stack_size, &tls_addr,
&tls_size);
UnpoisonStack(default_bottom, default_bottom + stack_size, "default");
return true;
}
// ---------------------- TSD ---------------- {{{1

View File

@ -0,0 +1,161 @@
// Tests that __asan_handle_no_return properly unpoisons the signal alternate
// stack.
// Don't optimize, otherwise the variables which create redzones might be
// dropped.
// RUN: %clangxx_asan -std=c++20 -fexceptions -O0 %s -o %t -pthread
// RUN: %run %t
#include <cassert>
#include <cerrno>
#include <csetjmp>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sanitizer/asan_interface.h>
namespace {
struct TestContext {
char *LeftRedzone;
char *RightRedzone;
std::jmp_buf JmpBuf;
};
TestContext defaultStack;
TestContext signalStack;
// Create a new stack frame to ensure that logically, the stack frame should be
// unpoisoned when the function exits. Exit is performed via jump, not return,
// such that we trigger __asan_handle_no_return and not ordinary unpoisoning.
template <class Jump>
void __attribute__((noinline)) poisonStackAndJump(TestContext &c, Jump jump) {
char Blob[100]; // This variable must not be optimized out, because we use it
// to create redzones.
c.LeftRedzone = Blob - 1;
c.RightRedzone = Blob + sizeof(Blob);
assert(__asan_address_is_poisoned(c.LeftRedzone));
assert(__asan_address_is_poisoned(c.RightRedzone));
// Jump to avoid normal cleanup of redzone markers. Instead,
// __asan_handle_no_return is called which unpoisons the stacks.
jump();
}
void testOnCurrentStack() {
TestContext c;
if (0 == setjmp(c.JmpBuf))
poisonStackAndJump(c, [&] { longjmp(c.JmpBuf, 1); });
assert(0 == __asan_region_is_poisoned(c.LeftRedzone,
c.RightRedzone - c.LeftRedzone));
}
void signalHandler(int, siginfo_t *, void *) {
{
stack_t Stack;
sigaltstack(nullptr, &Stack);
assert(Stack.ss_flags == SS_ONSTACK);
}
// test on signal alternate stack
testOnCurrentStack();
// test unpoisoning when jumping between stacks
poisonStackAndJump(signalStack, [] { longjmp(defaultStack.JmpBuf, 1); });
}
void setSignalAlternateStack(void *AltStack) {
sigaltstack((stack_t const *)AltStack, nullptr);
struct sigaction Action = {
.sa_sigaction = signalHandler,
.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK,
};
sigemptyset(&Action.sa_mask);
sigaction(SIGUSR1, &Action, nullptr);
}
// Main test function.
// Must be run on another thread to be able to control memory placement between
// default stack and alternate signal stack.
// If the alternate signal stack is placed in close proximity before the
// default stack, __asan_handle_no_return might unpoison both, even without
// being aware of the signal alternate stack.
// We want to test reliably that __asan_handle_no_return can properly unpoison
// the signal alternate stack.
void *threadFun(void *AltStack) {
// first test on default stack (sanity check), no signal alternate stack set
testOnCurrentStack();
setSignalAlternateStack(AltStack);
// test on default stack again, but now the signal alternate stack is set
testOnCurrentStack();
// set up jump to test unpoisoning when jumping between stacks
if (0 == setjmp(defaultStack.JmpBuf))
// Test on signal alternate stack, via signalHandler
poisonStackAndJump(defaultStack, [] { raise(SIGUSR1); });
assert(0 == __asan_region_is_poisoned(
defaultStack.LeftRedzone,
defaultStack.RightRedzone - defaultStack.LeftRedzone));
assert(0 == __asan_region_is_poisoned(
signalStack.LeftRedzone,
signalStack.RightRedzone - signalStack.LeftRedzone));
return nullptr;
}
} // namespace
// Check that __asan_handle_no_return properly unpoisons a signal alternate
// stack.
// __asan_handle_no_return tries to determine the stack boundaries and
// unpoisons all memory inside those. If this is not done properly, redzones for
// variables on can remain in shadow memory which might lead to false positive
// reports when the stack is reused.
int main() {
size_t const PageSize = sysconf(_SC_PAGESIZE);
// To align the alternate stack, we round this up to page_size.
size_t const DefaultStackSize =
(PTHREAD_STACK_MIN - 1 + PageSize) & ~(PageSize - 1);
// The alternate stack needs a certain size, or the signal handler segfaults.
size_t const AltStackSize = 10 * PageSize;
size_t const MappingSize = DefaultStackSize + AltStackSize;
// Using mmap guarantees proper alignment.
void *const Mapping = mmap(nullptr, MappingSize,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
stack_t const AltStack = {
.ss_sp = (char *)Mapping + DefaultStackSize,
.ss_flags = 0,
.ss_size = AltStackSize,
};
pthread_t Thread;
pthread_attr_t ThreadAttr;
pthread_attr_init(&ThreadAttr);
pthread_attr_setstack(&ThreadAttr, Mapping, DefaultStackSize);
pthread_create(&Thread, &ThreadAttr, &threadFun, (void *)&AltStack);
pthread_join(Thread, nullptr);
munmap(Mapping, MappingSize);
}