Improve stack overflow recovery

It's now possible to use sigaltstack() to recover from stack overflows
on Windows. Several bugs in sigaltstack() have been fixed, for all our
supported platforms. There's a newer better example showing how to use
this, along with three independent unit tests just to further showcase
the various techniques.
This commit is contained in:
Justine Tunney 2023-10-04 07:07:43 -07:00
parent 1694edf85c
commit 4631d34d0d
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
24 changed files with 590 additions and 274 deletions

View file

@ -35,7 +35,8 @@ static int FindPromise(const char *name) {
*
* @return 0 on success, or -1 if invalid
*/
int ParsePromises(const char *promises, unsigned long *out) {
int ParsePromises(const char *promises, unsigned long *out,
unsigned long current) {
int rc = 0;
int promise;
unsigned long ipromises;
@ -57,7 +58,7 @@ int ParsePromises(const char *promises, unsigned long *out) {
rc = -1;
}
} else {
ipromises = 0;
ipromises = current;
}
if (!rc) {
*out = ipromises;

View file

@ -239,9 +239,6 @@
int pledge(const char *promises, const char *execpromises) {
int e, rc;
unsigned long ipromises, iexecpromises;
if (promises && !execpromises) {
execpromises = promises;
}
if (!promises) {
// OpenBSD says NULL argument means it doesn't change, i.e.
// pledge(0,0) on OpenBSD does nothing. The Cosmopolitan Libc
@ -262,8 +259,8 @@ int pledge(const char *promises, const char *execpromises) {
return -1;
} else if (!IsTiny() && IsGenuineBlink()) {
rc = 0; // blink doesn't support seccomp; avoid noisy log warnings
} else if (!ParsePromises(promises, &ipromises) &&
!ParsePromises(execpromises, &iexecpromises)) {
} else if (!ParsePromises(promises, &ipromises, __promises) &&
!ParsePromises(execpromises, &iexecpromises, __execpromises)) {
if (IsLinux()) {
// copy exec and execnative from promises to execpromises
iexecpromises = ~(~iexecpromises | (~ipromises & (1ul << PROMISE_EXEC)));

View file

@ -14,7 +14,7 @@ struct Pledges {
extern const struct Pledges kPledge[PROMISE_LEN_];
int sys_pledge_linux(unsigned long, int);
int ParsePromises(const char *, unsigned long *);
int ParsePromises(const char *, unsigned long *, unsigned long);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -34,6 +34,7 @@
#include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/describebacktrace.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/popcnt.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
@ -72,9 +73,6 @@ struct ContextFrame {
struct NtContext nc;
};
void __stack_call(int, siginfo_t *, void *, long,
void (*)(int, siginfo_t *, void *), void *);
static textwindows bool __sig_ignored_by_default(int sig) {
return sig == SIGURG || //
sig == SIGCONT || //
@ -90,9 +88,21 @@ textwindows bool __sig_ignored(int sig) {
static textwindows bool __sig_should_use_altstack(unsigned flags,
struct CosmoTib *tib) {
return (flags & SA_ONSTACK) && //
tib->tib_sigstack_size && //
!(tib->tib_sigstack_flags & (SS_DISABLE | SS_ONSTACK));
if (!(flags & SA_ONSTACK)) {
return false; // signal handler didn't enable it
}
if (!tib->tib_sigstack_size) {
return false; // sigaltstack() wasn't installed on this thread
}
if (tib->tib_sigstack_flags & SS_DISABLE) {
return false; // sigaltstack() on this thread was disabled by user
}
char *bp = __builtin_frame_address(0);
if (tib->tib_sigstack_addr <= bp &&
bp <= tib->tib_sigstack_addr + tib->tib_sigstack_size) {
return false; // we're already on the alternate stack
}
return true;
}
static textwindows wontreturn void __sig_terminate(int sig) {
@ -129,20 +139,6 @@ static textwindows sigaction_f __sig_handler(unsigned rva) {
return (sigaction_f)(__executable_start + rva);
}
static textwindows void __sig_call(int sig, siginfo_t *si, ucontext_t *ctx,
unsigned rva, unsigned flags,
struct CosmoTib *tib) {
++__sig.count;
if (__sig_should_use_altstack(flags, tib)) {
tib->tib_sigstack_flags |= SS_ONSTACK;
__stack_call(sig, si, ctx, 0, __sig_handler(rva),
tib->tib_sigstack_addr + tib->tib_sigstack_size);
tib->tib_sigstack_flags &= ~SS_ONSTACK;
} else {
__sig_handler(rva)(sig, si, ctx);
}
}
textwindows int __sig_raise(int sig, int sic) {
unsigned rva, flags;
struct CosmoTib *tib = __get_tls();
@ -160,7 +156,7 @@ textwindows int __sig_raise(int sig, int sic) {
STRACE("entering raise(%G) signal handler %t with mask %s → %s", sig,
__sig_handler(rva), DescribeSigset(0, &ctx.uc_sigmask),
DescribeSigset(0, &(sigset_t){{pt->tib->tib_sigmask}}));
__sig_call(sig, &si, &ctx, rva, flags, tib);
__sig_handler(rva)(sig, &si, &ctx);
STRACE("leaving raise(%G) signal handler %t with mask %s → %s", sig,
__sig_handler(rva),
DescribeSigset(0, &(sigset_t){{pt->tib->tib_sigmask}}),
@ -258,9 +254,7 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
}
uintptr_t sp;
if (__sig_should_use_altstack(flags, pt->tib)) {
pt->tib->tib_sigstack_flags |= SS_ONSTACK;
sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size;
pt->tib->tib_sigstack_flags &= ~SS_ONSTACK;
} else {
sp = (nc.Rsp - 128 - sizeof(struct ContextFrame)) & -16;
}
@ -338,11 +332,11 @@ static int __sig_crash_sig(struct NtExceptionPointers *ep, int *code) {
case kNtSignalPrivInstruction:
*code = ILL_PRVOPC;
return SIGILL;
case kNtSignalGuardPage:
case kNtSignalInPageError:
case kNtStatusStackOverflow:
*code = SEGV_MAPERR;
return SIGSEGV;
case kNtSignalGuardPage:
case kNtSignalAccessViolation:
*code = SEGV_ACCERR;
return SIGSEGV;
@ -386,26 +380,25 @@ static int __sig_crash_sig(struct NtExceptionPointers *ep, int *code) {
}
}
// abashed the devil stood, and felt how awful goodness is
__msabi unsigned __sig_crash(struct NtExceptionPointers *ep) {
int code, sig = __sig_crash_sig(ep, &code);
static void __sig_crasher(struct NtExceptionPointers *ep, int code, int sig,
struct CosmoTib *tib) {
// increment the signal count for getrusage()
++__sig.count;
// log vital crash information reliably for --strace before doing much
// we don't print this without the flag since raw numbers scare people
// this needs at least one page of stack memory in order to get logged
// otherwise it'll print a warning message about the lack of stack mem
STRACE("win32 vectored exception 0x%08Xu raising %G "
"cosmoaddr2line %s %lx %s",
ep->ExceptionRecord->ExceptionCode, sig, program_invocation_name,
ep->ContextRecord->Rip,
DescribeBacktrace((struct StackFrame *)ep->ContextRecord->Rbp));
if (sig == SIGTRAP) {
ep->ContextRecord->Rip++;
if (__sig_ignored(sig)) {
return kNtExceptionContinueExecution;
}
}
struct PosixThread *pt = _pthread_self();
siginfo_t si = {.si_signo = sig,
.si_code = code,
.si_addr = ep->ExceptionRecord->ExceptionAddress};
// if the user didn't install a signal handler for this unmaskable
// exception, then print a friendly helpful hint message to stderr
unsigned rva = __sighandrvas[sig];
unsigned flags = __sighandflags[sig];
if (rva == (intptr_t)SIG_DFL || rva == (intptr_t)SIG_IGN) {
#ifndef TINY
intptr_t hStderr;
@ -418,16 +411,74 @@ __msabi unsigned __sig_crash(struct NtExceptionPointers *ep) {
#endif
__sig_terminate(sig);
}
// if this signal handler is configured to auto-reset to the default
// then that reset needs to happen before the user handler is called
unsigned flags = __sighandflags[sig];
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// determine the true memory address at which fault occurred
// if this is a stack overflow then reapply guard protection
void *si_addr;
if (ep->ExceptionRecord->ExceptionCode == kNtSignalGuardPage) {
si_addr = (void *)ep->ExceptionRecord->ExceptionInformation[1];
} else {
si_addr = ep->ExceptionRecord->ExceptionAddress;
}
// call the user signal handler
// with a temporarily replaced signal mask
// and a modifiable view of the faulting code's cpu state
// note ucontext_t is a hefty data structures on top of NtContext
ucontext_t ctx = {0};
siginfo_t si = {.si_signo = sig, .si_code = code, .si_addr = si_addr};
_ntcontext2linux(&ctx, ep->ContextRecord);
ctx.uc_sigmask.__bits[0] = pt->tib->tib_sigmask;
__sig_call(sig, &si, &ctx, rva, flags, pt->tib);
pt->tib->tib_sigmask = ctx.uc_sigmask.__bits[0];
ctx.uc_sigmask.__bits[0] = tib->tib_sigmask;
__sig_handler(rva)(sig, &si, &ctx);
tib->tib_sigmask = ctx.uc_sigmask.__bits[0];
_ntlinux2context(ep->ContextRecord, &ctx);
}
void __stack_call(struct NtExceptionPointers *, int, int, struct CosmoTib *,
void (*)(struct NtExceptionPointers *, int, int,
struct CosmoTib *),
void *);
//
// abashed the devil stood
// and felt how awful goodness is
//
__msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) {
// translate win32 to unix si_signo and si_code
int code, sig = __sig_crash_sig(ep, &code);
// advance the instruction pointer to skip over debugger breakpoints
// this behavior is consistent with how unix kernels are implemented
if (sig == SIGTRAP) {
ep->ContextRecord->Rip++;
if (__sig_ignored(sig)) {
return kNtExceptionContinueExecution;
}
}
// win32 stack overflow detection executes INSIDE the guard page
// thus switch to the alternate signal stack as soon as possible
struct CosmoTib *tib = __get_tls();
unsigned flags = __sighandflags[sig];
if (__sig_should_use_altstack(flags, tib)) {
__stack_call(ep, code, sig, tib, __sig_crasher,
tib->tib_sigstack_addr + tib->tib_sigstack_size);
} else {
__sig_crasher(ep, code, sig, tib);
}
// resume running user program
// hopefully the user fixed the cpu state
// otherwise the crash will keep happening
return kNtExceptionContinueExecution;
}

View file

@ -57,51 +57,36 @@ static void sigaltstack2linux(struct sigaltstack *linux,
static textwindows int sigaltstack_cosmo(const struct sigaltstack *neu,
struct sigaltstack *old) {
struct CosmoTib *tib;
tib = __get_tls();
struct CosmoTib *tib = __get_tls();
char *bp = __builtin_frame_address(0);
if (old) {
old->ss_sp = tib->tib_sigstack_addr;
old->ss_size = tib->tib_sigstack_size;
old->ss_flags = tib->tib_sigstack_flags;
old->ss_flags = tib->tib_sigstack_flags & ~SS_ONSTACK;
}
if (neu) {
tib->tib_sigstack_addr = (char *)ROUNDUP((uintptr_t)neu->ss_sp, 16);
tib->tib_sigstack_size = ROUNDDOWN(neu->ss_size, 16);
tib->tib_sigstack_flags &= SS_ONSTACK;
tib->tib_sigstack_flags |= neu->ss_flags & SS_DISABLE;
tib->tib_sigstack_flags = neu->ss_flags & SS_DISABLE;
}
if (tib->tib_sigstack_addr <= bp &&
bp <= tib->tib_sigstack_addr + tib->tib_sigstack_size) {
if (old) old->ss_flags |= SS_ONSTACK;
tib->tib_sigstack_flags = SS_ONSTACK; // can't disable if on it
} else if (!tib->tib_sigstack_size) {
if (old) old->ss_flags = SS_DISABLE;
tib->tib_sigstack_flags = SS_DISABLE;
}
return 0;
}
static int sigaltstack_sysv(const struct sigaltstack *neu,
struct sigaltstack *old) {
void *b;
const void *a;
struct sigaltstack_bsd bsd;
if (IsLinux()) {
a = neu;
b = old;
} else {
if (neu) {
sigaltstack2bsd(&bsd, neu);
a = &bsd;
} else {
a = 0;
}
if (old) {
b = &bsd;
} else {
b = 0;
}
}
if (!sys_sigaltstack(a, b)) {
if (IsBsd() && old) {
sigaltstack2linux(old, &bsd);
}
return 0;
} else {
return -1;
}
static int sigaltstack_bsd(const struct sigaltstack *neu,
struct sigaltstack *old) {
struct sigaltstack_bsd oldbsd, neubsd, *neup = 0;
if (neu) sigaltstack2bsd(&neubsd, neu), neup = &neubsd;
if (sys_sigaltstack(neup, &oldbsd) == -1) return -1;
if (old) sigaltstack2linux(old, &oldbsd);
return 0;
}
/**
@ -131,13 +116,13 @@ int sigaltstack(const struct sigaltstack *neu, struct sigaltstack *old) {
rc = efault();
} else if (neu && ((neu->ss_size >> 32) || //
(neu->ss_flags & ~(SS_ONSTACK | SS_DISABLE)))) {
return einval();
rc = einval();
} else if (neu && neu->ss_size < __get_minsigstksz()) {
rc = enomem();
} else if (IsLinux() || IsBsd()) {
if (!(rc = sigaltstack_sysv(neu, old))) {
sigaltstack_cosmo(neu, old);
}
} else if (IsLinux()) {
rc = sys_sigaltstack(neu, old);
} else if (IsBsd()) {
rc = sigaltstack_bsd(neu, old);
} else {
rc = sigaltstack_cosmo(neu, old);
}

View file

@ -24,20 +24,19 @@
dontinstrument const char *(DescribeBacktrace)(char buf[N],
struct StackFrame *fr) {
bool gotsome = false;
char *p = buf;
char *pe = p + N;
bool gotsome = false;
while (fr) {
if (gotsome) {
if (p + 1 < pe) {
if (p + 16 + 1 + 1 <= pe) {
if (gotsome) {
*p++ = ' ';
*p = 0;
} else {
gotsome = true;
}
} else {
gotsome = true;
}
if (p + 17 <= pe) {
p = __hexcpy(p, fr->addr);
} else {
break;
}
fr = fr->next;
}

View file

@ -103,7 +103,7 @@ void __get_main_stack(void **out_addr, size_t *out_size, int *out_guardsize) {
if (IsWindows()) {
*out_addr = (void *)GetStaticStackAddr(0);
*out_size = GetStaticStackSize();
*out_guardsize = 4096;
*out_guardsize = GetGuardSize();
return;
}
int pagesz = getauxval(AT_PAGESZ);

View file

@ -3,7 +3,7 @@
#include "libc/intrin/likely.h"
#include "libc/runtime/runtime.h"
#define _NTTRACE 1 /* not configurable w/ flag yet */
#define _NTTRACE 0 /* not configurable w/ flag yet */
#define _POLLTRACE 0 /* not configurable w/ flag yet */
#define _DATATRACE 1 /* not configurable w/ flag yet */
#define _LOCKTRACE 0 /* not configurable w/ flag yet */

View file

@ -1,21 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/log/internal.h"
bool _wantcrashreports;

View file

@ -5,7 +5,6 @@
COSMOPOLITAN_C_START_
extern bool __nocolor;
extern bool _wantcrashreports;
extern bool g_isrunningundermake;
void __start_fatal(const char *, int);

View file

@ -91,7 +91,6 @@ void ShowCrashReports(void) {
InstallCrashHandler(SIGBUS, SA_RESETHAND);
InstallCrashHandler(SIGABRT, SA_RESETHAND);
InstallCrashHandler(SIGSEGV, SA_RESETHAND | SA_ONSTACK);
_wantcrashreports = true;
}
IGNORE_LEAKS(ShowCrashReports)

View file

@ -2,7 +2,7 @@
#define COSMOPOLITAN_LIBC_NT_STRUCT_NTEXCEPTIONRECORD_H_
#define kNtExceptionMaximumParameters 15
#define kNtExceptionNoncontinuable 1
#define kNtExceptionNoncontinuable 1
#if !(__ASSEMBLER__ + __LINKER__ + 0)
@ -12,7 +12,7 @@ struct NtExceptionRecord {
struct NtExceptionRecord *ExceptionRecord; /* nested exceptions */
void *ExceptionAddress; /* %rip */
uint32_t NumberParameters; /* #ExceptionInformation */
uint32_t *ExceptionInformation[kNtExceptionMaximumParameters];
uint64_t ExceptionInformation[kNtExceptionMaximumParameters];
};
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -19,45 +19,19 @@
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/ucontext.internal.h"
#include "libc/calls/ucontext.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/sig.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
/**
* Returns true if signal is likely a stack overflow.
* Returns true if signal is most likely a stack overflow.
*/
char __is_stack_overflow(siginfo_t *si, void *ucontext) {
// check if signal has the information we need
ucontext_t *uc = ucontext;
if (!si) return false;
if (!uc) return false;
char __is_stack_overflow(siginfo_t *si, void *arg) {
ucontext_t *uc = arg;
if (!si || !uc) return false;
if (si->si_signo != SIGSEGV && si->si_signo != SIGBUS) return false;
// with threads we know exactly where the guard page is
int pagesz = getauxval(AT_PAGESZ);
uintptr_t addr = (uintptr_t)si->si_addr;
struct PosixThread *pt = _pthread_self();
if (pt->attr.__stacksize) {
uintptr_t stack = (uintptr_t)pt->attr.__stackaddr;
uintptr_t guard = pt->attr.__guardsize;
uintptr_t bot, top;
if (guard) {
bot = stack;
top = bot + guard;
} else {
bot = stack - pagesz;
top = stack;
}
return addr >= bot && addr < top;
}
// it's easy to guess with the main stack
// even though it's hard to know its exact boundaries
uintptr_t sp = uc->uc_mcontext.SP;
return addr <= sp && addr >= sp - pagesz;
intptr_t sp = uc->uc_mcontext.SP;
intptr_t fp = (intptr_t)si->si_addr;
return ABS(fp - sp) < getauxval(AT_PAGESZ);
}

View file

@ -28,6 +28,7 @@
#include "libc/intrin/bsr.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/directmap.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/likely.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/intrin/strace.internal.h"
@ -37,6 +38,9 @@
#include "libc/log/libfatal.internal.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/nt/enum/memflags.h"
#include "libc/nt/enum/pageflags.h"
#include "libc/nt/memory.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/processmemorycounters.h"
@ -396,9 +400,15 @@ inline void *__mmap_unlocked(void *addr, size_t size, int prot, int flags,
kAsanMmapSizeOverrun);
}
if (needguard) {
unassert(!mprotect(p, pagesize, PROT_NONE));
if (IsAsan()) {
__asan_poison(p, pagesize, kAsanStackOverflow);
if (!IsWindows()) {
unassert(!mprotect(p, pagesize, PROT_NONE));
if (IsAsan()) {
__asan_poison(p, pagesize, kAsanStackOverflow);
}
} else {
uint32_t oldattr;
unassert(VirtualProtect(p, pagesize, kNtPageReadwrite | kNtPageGuard,
&oldattr));
}
}
}

View file

@ -169,6 +169,9 @@ static abi wontreturn void WinInit(const char16_t *cmdline) {
uint32_t old;
__imp_VirtualProtect((void *)stackaddr, stacksize, kNtPageReadwrite, &old);
}
uint32_t oldattr;
__imp_VirtualProtect((void *)stackaddr, GetGuardSize(),
kNtPageReadwrite | kNtPageGuard, &oldattr);
_mmi.p[0].x = stackaddr >> 16;
_mmi.p[0].y = (stackaddr >> 16) + ((stacksize - 1) >> 16);
_mmi.p[0].prot = prot;

View file

@ -31,6 +31,7 @@
#include "libc/intrin/bsr.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/log/internal.h"
@ -38,6 +39,9 @@
#include "libc/mem/alloca.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/crc32.h"
#include "libc/nt/enum/memflags.h"
#include "libc/nt/enum/pageflags.h"
#include "libc/nt/memory.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/runtime/runtime.h"
@ -210,9 +214,19 @@ static errno_t pthread_create_impl(pthread_t *thread,
-1, 0, 0) != pt->attr.__stackaddr) {
notpossible;
}
if (pt->attr.__guardsize && !IsWindows() &&
mprotect(pt->attr.__stackaddr, pt->attr.__guardsize, PROT_NONE)) {
notpossible;
if (pt->attr.__guardsize) {
if (!IsWindows()) {
if (mprotect(pt->attr.__stackaddr, pt->attr.__guardsize,
PROT_NONE)) {
notpossible;
}
} else {
uint32_t oldattr;
if (!VirtualProtect(pt->attr.__stackaddr, pt->attr.__guardsize,
kNtPageReadwrite | kNtPageGuard, &oldattr)) {
notpossible;
}
}
}
}
}
@ -227,7 +241,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
}
}
pt->pt_flags |= PT_OWNSTACK;
if (IsAsan() && pt->attr.__guardsize) {
if (IsAsan() && !IsWindows() && pt->attr.__guardsize) {
__asan_poison(pt->attr.__stackaddr, pt->attr.__guardsize,
kAsanStackOverflow);
}