forked from OSchip/llvm-project
[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:
parent
068fa35746
commit
6b4aeec94a
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue