From 4631d34d0d8317b964b135d64c69eb2f3a98bc24 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 4 Oct 2023 07:07:43 -0700 Subject: [PATCH] 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. --- examples/stackoverflow.c | 142 +++++++++++++++---------- libc/calls/parsepromises.c | 5 +- libc/calls/pledge.c | 7 +- libc/calls/pledge.internal.h | 2 +- libc/calls/sig.c | 133 ++++++++++++++++------- libc/calls/sigaltstack.c | 63 +++++------ libc/intrin/describebacktrace.c | 15 ++- libc/intrin/getmainstack.c | 2 +- libc/intrin/strace.internal.h | 2 +- libc/intrin/wantcrashreports.c | 21 ---- libc/log/internal.h | 1 - libc/log/showcrashreports.c | 1 - libc/nt/struct/ntexceptionrecord.h | 4 +- libc/runtime/isstackoverflow.c | 42 ++------ libc/runtime/mmap.c | 16 ++- libc/runtime/winmain.greg.c | 3 + libc/thread/pthread_create.c | 22 +++- test/libc/calls/pledge_test.c | 2 + test/libc/thread/pthread_create_test.c | 52 --------- test/libc/thread/stackoverflow1_test.c | 104 ++++++++++++++++++ test/libc/thread/stackoverflow2_test.c | 105 ++++++++++++++++++ test/libc/thread/stackoverflow3_test.c | 116 ++++++++++++++++++++ third_party/make/job.c | 2 +- tool/build/pledge.c | 2 +- 24 files changed, 590 insertions(+), 274 deletions(-) delete mode 100644 libc/intrin/wantcrashreports.c create mode 100644 test/libc/thread/stackoverflow1_test.c create mode 100644 test/libc/thread/stackoverflow2_test.c create mode 100644 test/libc/thread/stackoverflow3_test.c diff --git a/examples/stackoverflow.c b/examples/stackoverflow.c index 7a97d130c..4e3ad91fa 100644 --- a/examples/stackoverflow.c +++ b/examples/stackoverflow.c @@ -7,74 +7,104 @@ │ • http://creativecommons.org/publicdomain/zero/1.0/ │ ╚─────────────────────────────────────────────────────────────────*/ #endif -#include "libc/calls/calls.h" -#include "libc/dce.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigaltstack.h" +#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/limits.h" -#include "libc/log/check.h" +#include "libc/mem/gc.internal.h" +#include "libc/mem/mem.h" #include "libc/runtime/runtime.h" -#include "libc/runtime/stack.h" -#include "libc/stdio/stdio.h" -#include "libc/sysv/consts/prot.h" +#include "libc/runtime/sysconf.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/ss.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" /** - * @fileoverview Stack Overflow Demo + * stack overflow recovery technique #3 + * rewrite thread cpu state to call pthread_exit + * this method returns gracefully from signal handlers + * unfortunately it relies on cpu architecture knowledge + * + * @see test/libc/thread/stackoverflow1_test.c + * @see test/libc/thread/stackoverflow2_test.c + * @see test/libc/thread/stackoverflow3_test.c */ -#define N INT_MAX +volatile bool smashed_stack; -int A(int f(), int n) { - if (n < N) { +void Exiter(void *rc) { + struct sigaltstack ss; + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(0, ss.ss_flags); + pthread_exit(rc); +} + +void CrashHandler(int sig, siginfo_t *si, void *arg) { + ucontext_t *ctx = arg; + struct sigaltstack ss; + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(SS_ONSTACK, ss.ss_flags); + kprintf("kprintf avoids overflowing %G %p\n", si->si_signo, si->si_addr); + smashed_stack = true; + ASSERT_TRUE(__is_stack_overflow(si, ctx)); + // + // the backtrace will look like this + // + // 0x000000000042561d: pthread_exit at pthread_exit.c:99 + // 0x0000000000418777: Exiter at stackoverflow2_test.c:40 + // 0x00000000004186d8: CrashHandler at stackoverflow2_test.c:49 + // 0x000000000041872a: StackOverflow at stackoverflow2_test.c:53 + // 0x000000000041872a: StackOverflow at stackoverflow2_test.c:53 + // 0x000000000041872a: StackOverflow at stackoverflow2_test.c:53 + // ... + // + ctx->uc_mcontext.ARG0 = 123; + ctx->uc_mcontext.PC = (long)Exiter; + ctx->uc_mcontext.SP += 32768; + ctx->uc_mcontext.SP &= -16; + ctx->uc_mcontext.SP -= 8; +} + +int StackOverflow(int f(), int n) { + if (n < INT_MAX) { return f(f, n + 1) - 1; } else { - return N; + return INT_MAX; } } -int (*Ap)(int (*)(), int) = A; +int (*pStackOverflow)(int (*)(), int) = StackOverflow; + +void *MyPosixThread(void *arg) { + struct sigaction sa; + struct sigaltstack ss; + ss.ss_flags = 0; + ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 4096; + ss.ss_sp = gc(malloc(ss.ss_size)); + ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = CrashHandler; + sigaction(SIGBUS, &sa, 0); + sigaction(SIGSEGV, &sa, 0); + exit(pStackOverflow(pStackOverflow, 0)); + return 0; +} int main(int argc, char *argv[]) { - if (IsWindows()) { - fprintf(stderr, "stack overflow not possible to catch on windows yet\n"); - exit(1); - } - ShowCrashReports(); - return !!Ap(Ap, 0); + void *res; + pthread_t th; + struct sigaltstack ss; + smashed_stack = false; + pthread_create(&th, 0, MyPosixThread, 0); + pthread_join(th, &res); + ASSERT_EQ((void *)123L, res); + ASSERT_TRUE(smashed_stack); + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(SS_DISABLE, ss.ss_flags); } - -/* -error: Uncaught SIGSEGV (Stack Overflow) on rhel5 pid 368 - ./o//examples/stackoverflow.com - EUNKNOWN[No error information][0] - Linux rhel5 2.6.18-8.el5 #1 SMP Thu Mar 15 19:46:53 EDT 2007 - -0x0000000000406896: A at examples/stackoverflow.c:24 -0x0000000000406898: A at examples/stackoverflow.c:24 -0x0000000000406898: A at examples/stackoverflow.c:24 -0x0000000000406898: A at examples/stackoverflow.c:24 -0x0000000000406898: A at examples/stackoverflow.c:24 -0x0000000000406898: A at examples/stackoverflow.c:24 -0x0000000000406898: A at examples/stackoverflow.c:24 -0x0000000000406898: A at examples/stackoverflow.c:24 -0x0000000000406898: A at examples/stackoverflow.c:24 -etc. etc. - -RAX 0000000000000000 RBX 0000000000000001 RDI 000000000040687e ST(0) 0.0 -RCX 0000000000417125 RDX 000000000041cd70 RSI 0000000000000efe ST(1) 0.0 -RBP 00006ffffffe1000 RSP 00006ffffffe1000 RIP 0000000000406897 ST(2) 0.0 - R8 0000000000000000 R9 0000000000000022 R10 0000000000000008 ST(3) 0.0 -R11 0000000000000293 R12 0000000000000001 R13 00007ffc70b4fc48 ST(4) 0.0 -R14 00007ffc70b4fc58 R15 00007ffc70b4fd18 VF IF - -XMM0 00000000000000000000000000000000 XMM8 00000000000000000000000000000000 -XMM1 ffffffffffffeb030000000000000000 XMM9 00000000000000000000000000000000 -XMM2 0000000000000000ffffffffffffffff XMM10 00000000000000000000000000000000 -XMM3 00000000000000000000000000000000 XMM11 00000000000000000000000000000000 -XMM4 00000000000000000000000000000000 XMM12 00000000000000000000000000000000 -XMM5 00000000000000000000000000000000 XMM13 00000000000000000000000000000000 -XMM6 00000000000000000000000000000000 XMM14 00000000000000000000000000000000 -XMM7 00000000000000000000000000000000 XMM15 00000000000000000000000000000000 - -100080000000-100080030000 rw-pa-- 3x automap -6ffffffe0000-6fffffff0000 rw-paSF 1x stack -# 4 frames mapped w/ 0 frames gapped -*/ diff --git a/libc/calls/parsepromises.c b/libc/calls/parsepromises.c index 3929d63d5..c5de9e150 100644 --- a/libc/calls/parsepromises.c +++ b/libc/calls/parsepromises.c @@ -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; diff --git a/libc/calls/pledge.c b/libc/calls/pledge.c index 0c2f989bc..74794e647 100644 --- a/libc/calls/pledge.c +++ b/libc/calls/pledge.c @@ -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))); diff --git a/libc/calls/pledge.internal.h b/libc/calls/pledge.internal.h index b79498b59..c26ed5f97 100644 --- a/libc/calls/pledge.internal.h +++ b/libc/calls/pledge.internal.h @@ -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) */ diff --git a/libc/calls/sig.c b/libc/calls/sig.c index 6cacfc783..c31915fd4 100644 --- a/libc/calls/sig.c +++ b/libc/calls/sig.c @@ -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; } diff --git a/libc/calls/sigaltstack.c b/libc/calls/sigaltstack.c index d30ad39af..8afcb94ba 100644 --- a/libc/calls/sigaltstack.c +++ b/libc/calls/sigaltstack.c @@ -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); } diff --git a/libc/intrin/describebacktrace.c b/libc/intrin/describebacktrace.c index 87183e87f..a59735cdf 100644 --- a/libc/intrin/describebacktrace.c +++ b/libc/intrin/describebacktrace.c @@ -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; } diff --git a/libc/intrin/getmainstack.c b/libc/intrin/getmainstack.c index 498ce2add..3229595ee 100644 --- a/libc/intrin/getmainstack.c +++ b/libc/intrin/getmainstack.c @@ -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); diff --git a/libc/intrin/strace.internal.h b/libc/intrin/strace.internal.h index 17ba1a1a5..92cc9101e 100644 --- a/libc/intrin/strace.internal.h +++ b/libc/intrin/strace.internal.h @@ -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 */ diff --git a/libc/intrin/wantcrashreports.c b/libc/intrin/wantcrashreports.c deleted file mode 100644 index 8b2d88935..000000000 --- a/libc/intrin/wantcrashreports.c +++ /dev/null @@ -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; diff --git a/libc/log/internal.h b/libc/log/internal.h index 60e1d1747..74e46ba12 100644 --- a/libc/log/internal.h +++ b/libc/log/internal.h @@ -5,7 +5,6 @@ COSMOPOLITAN_C_START_ extern bool __nocolor; -extern bool _wantcrashreports; extern bool g_isrunningundermake; void __start_fatal(const char *, int); diff --git a/libc/log/showcrashreports.c b/libc/log/showcrashreports.c index 095e79f6c..6c3e0620b 100644 --- a/libc/log/showcrashreports.c +++ b/libc/log/showcrashreports.c @@ -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) diff --git a/libc/nt/struct/ntexceptionrecord.h b/libc/nt/struct/ntexceptionrecord.h index ff1b50e42..ef07a7c10 100644 --- a/libc/nt/struct/ntexceptionrecord.h +++ b/libc/nt/struct/ntexceptionrecord.h @@ -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) */ diff --git a/libc/runtime/isstackoverflow.c b/libc/runtime/isstackoverflow.c index c31b80f2f..40b10d14e 100644 --- a/libc/runtime/isstackoverflow.c +++ b/libc/runtime/isstackoverflow.c @@ -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); } diff --git a/libc/runtime/mmap.c b/libc/runtime/mmap.c index ea026ef25..1b752d821 100644 --- a/libc/runtime/mmap.c +++ b/libc/runtime/mmap.c @@ -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)); } } } diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c index 7db5477a3..6efff52b2 100644 --- a/libc/runtime/winmain.greg.c +++ b/libc/runtime/winmain.greg.c @@ -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; diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index 51586acd3..e39da45e6 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -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); } diff --git a/test/libc/calls/pledge_test.c b/test/libc/calls/pledge_test.c index 7283225fe..110f7acc5 100644 --- a/test/libc/calls/pledge_test.c +++ b/test/libc/calls/pledge_test.c @@ -552,6 +552,8 @@ TEST(pledge_openbsd, execpromisesIsNull_letsItDoAnything) { } EXPECT_NE(-1, wait(&ws)); EXPECT_TRUE(WIFEXITED(ws)); + EXPECT_FALSE(WIFSIGNALED(ws)); + EXPECT_EQ(0, WTERMSIG(ws)); EXPECT_EQ(3, WEXITSTATUS(ws)); } diff --git a/test/libc/thread/pthread_create_test.c b/test/libc/thread/pthread_create_test.c index 5c8c65e06..9c737b361 100644 --- a/test/libc/thread/pthread_create_test.c +++ b/test/libc/thread/pthread_create_test.c @@ -255,58 +255,6 @@ TEST(pthread_cleanup, pthread_normal) { ASSERT_TRUE(g_cleanup2); } -//////////////////////////////////////////////////////////////////////////////// - -jmp_buf recover; -volatile bool smashed_stack; - -void CrashHandler(int sig, siginfo_t *si, void *ctx) { - kprintf("kprintf avoids overflowing %G %p\n", si->si_signo, si->si_addr); - smashed_stack = true; - ASSERT_TRUE(__is_stack_overflow(si, ctx)); - longjmp(recover, 123); -} - -int StackOverflow(int f(), int n) { - if (n < INT_MAX) { - return f(f, n + 1) - 1; - } else { - return INT_MAX; - } -} - -int (*pStackOverflow)(int (*)(), int) = StackOverflow; - -void *MyPosixThread(void *arg) { - int jumpcode; - struct sigaction sa, o1, o2; - struct sigaltstack ss; - ss.ss_flags = 0; - ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 4096; - ss.ss_sp = gc(malloc(ss.ss_size)); - ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); - sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important - sigemptyset(&sa.sa_mask); - sa.sa_sigaction = CrashHandler; - sigaction(SIGBUS, &sa, &o1); - sigaction(SIGSEGV, &sa, &o2); - if (!(jumpcode = setjmp(recover))) { - exit(pStackOverflow(pStackOverflow, 0)); - } - ASSERT_EQ(123, jumpcode); - sigaction(SIGSEGV, &o2, 0); - sigaction(SIGBUS, &o1, 0); - return 0; -} - -TEST(cosmo, altstack_thread) { - pthread_t th; - if (IsWindows()) return; - pthread_create(&th, 0, MyPosixThread, 0); - pthread_join(th, 0); - ASSERT_TRUE(smashed_stack); -} - //////////////////////////////////////////////////////////////////////////////// // BENCHMARKS diff --git a/test/libc/thread/stackoverflow1_test.c b/test/libc/thread/stackoverflow1_test.c new file mode 100644 index 000000000..19fa2b6f4 --- /dev/null +++ b/test/libc/thread/stackoverflow1_test.c @@ -0,0 +1,104 @@ +/*-*- 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 2023 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/calls/struct/rlimit.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigaltstack.h" +#include "libc/calls/struct/siginfo.h" +#include "libc/dce.h" +#include "libc/intrin/kprintf.h" +#include "libc/limits.h" +#include "libc/mem/gc.internal.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/runtime/sysconf.h" +#include "libc/sysv/consts/rlimit.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/ss.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" + +/** + * stack overflow recovery technique #1 + * overflow the gigantic main process stack + * simple but it can upset kernels / libraries + */ + +jmp_buf recover; +volatile bool smashed_stack; + +void CrashHandler(int sig, siginfo_t *si, void *ctx) { + struct sigaltstack ss; + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(SS_ONSTACK, ss.ss_flags); + kprintf("kprintf avoids overflowing %G %p\n", si->si_signo, si->si_addr); + smashed_stack = true; + ASSERT_TRUE(__is_stack_overflow(si, ctx)); + longjmp(recover, 123); +} + +void SetUp(void) { + + // tune down the main process's stack size to a reasonable amount + // some operating systems, e.g. freebsd, will do things like have + // 500mb RLIMIT_STACK by default, even on machines with 400mb RAM + struct rlimit rl = {2 * 1024 * 1024, 2 * 1024 * 1024}; + if (!IsWindows()) setrlimit(RLIMIT_STACK, &rl); + + // set up the signal handler and alternative stack + struct sigaction sa; + struct sigaltstack ss; + ss.ss_flags = 0; + ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 8192; + ss.ss_sp = _mapanon(ss.ss_size); + ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = CrashHandler; + sigaction(SIGBUS, &sa, 0); + sigaction(SIGSEGV, &sa, 0); +} + +int StackOverflow(int f(), int n) { + if (n < INT_MAX) { + return f(f, n + 1) - 1; + } else { + return INT_MAX; + } +} + +int (*pStackOverflow)(int (*)(), int) = StackOverflow; + +TEST(stackoverflow, standardStack_altStack_process_longjmp) { + int jumpcode; + if (!(jumpcode = setjmp(recover))) { + exit(pStackOverflow(pStackOverflow, 0)); + } + ASSERT_EQ(123, jumpcode); + ASSERT_TRUE(smashed_stack); + + // here's where longjmp() gets us into trouble + struct sigaltstack ss; + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + if (IsXnu() || IsNetbsd()) { + ASSERT_EQ(SS_ONSTACK, ss.ss_flags); // wut + } else { + ASSERT_EQ(0, ss.ss_flags); + } +} diff --git a/test/libc/thread/stackoverflow2_test.c b/test/libc/thread/stackoverflow2_test.c new file mode 100644 index 000000000..807f59f3a --- /dev/null +++ b/test/libc/thread/stackoverflow2_test.c @@ -0,0 +1,105 @@ +/*-*- 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 2023 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/calls/struct/sigaction.h" +#include "libc/calls/struct/sigaltstack.h" +#include "libc/calls/struct/siginfo.h" +#include "libc/dce.h" +#include "libc/intrin/kprintf.h" +#include "libc/limits.h" +#include "libc/mem/gc.internal.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/runtime/sysconf.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/ss.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" + +/** + * stack overflow recovery technique #2 + * longjmp out of signal back into thread + * simple but it can upset kernels / libraries + */ + +jmp_buf recover; +volatile bool smashed_stack; + +void CrashHandler(int sig, siginfo_t *si, void *ctx) { + struct sigaltstack ss; + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(SS_ONSTACK, ss.ss_flags); + kprintf("kprintf avoids overflowing %G %p\n", si->si_signo, si->si_addr); + smashed_stack = true; + ASSERT_TRUE(__is_stack_overflow(si, ctx)); + longjmp(recover, 123); +} + +int StackOverflow(int f(), int n) { + if (n < INT_MAX) { + return f(f, n + 1) - 1; + } else { + return INT_MAX; + } +} + +int (*pStackOverflow)(int (*)(), int) = StackOverflow; + +void *MyPosixThread(void *arg) { + int jumpcode; + struct sigaction sa, o1, o2; + struct sigaltstack ss; + ss.ss_flags = 0; + ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 4096; + ss.ss_sp = gc(malloc(ss.ss_size)); + ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = CrashHandler; + sigaction(SIGBUS, &sa, &o1); + sigaction(SIGSEGV, &sa, &o2); + if (!(jumpcode = setjmp(recover))) { + exit(pStackOverflow(pStackOverflow, 0)); + } + ASSERT_EQ(123, jumpcode); + sigaction(SIGSEGV, &o2, 0); + sigaction(SIGBUS, &o1, 0); + // here's where longjmp() gets us into trouble + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + if (IsXnu() || IsNetbsd()) { + ASSERT_EQ(SS_ONSTACK, ss.ss_flags); // wut + } else { + ASSERT_EQ(0, ss.ss_flags); + } + return 0; +} + +TEST(stackoverflow, standardStack_altStack_thread_longjmp) { + pthread_t th; + struct sigaltstack ss; + for (int i = 0; i < 2; ++i) { + smashed_stack = false; + pthread_create(&th, 0, MyPosixThread, 0); + pthread_join(th, 0); + ASSERT_TRUE(smashed_stack); + // this should be SS_DISABLE but ShowCrashReports() creates an alt stack + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(0, ss.ss_flags); + } +} diff --git a/test/libc/thread/stackoverflow3_test.c b/test/libc/thread/stackoverflow3_test.c new file mode 100644 index 000000000..6e3ecd2e6 --- /dev/null +++ b/test/libc/thread/stackoverflow3_test.c @@ -0,0 +1,116 @@ +/*-*- 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 2023 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/calls/struct/sigaction.h" +#include "libc/calls/struct/sigaltstack.h" +#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/limits.h" +#include "libc/mem/gc.internal.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/runtime/sysconf.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/ss.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" + +/** + * stack overflow recovery technique #3 + * rewrite thread cpu state to call pthread_exit + * this method returns gracefully from signal handlers + * unfortunately it relies on cpu architecture knowledge + */ + +volatile bool smashed_stack; + +void Exiter(void *rc) { + struct sigaltstack ss; + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(0, ss.ss_flags); + pthread_exit(rc); +} + +void CrashHandler(int sig, siginfo_t *si, void *arg) { + ucontext_t *ctx = arg; + struct sigaltstack ss; + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(SS_ONSTACK, ss.ss_flags); + kprintf("kprintf avoids overflowing %G %p\n", si->si_signo, si->si_addr); + smashed_stack = true; + ASSERT_TRUE(__is_stack_overflow(si, ctx)); + // + // the backtrace will look like this + // + // 0x000000000042561d: pthread_exit at pthread_exit.c:99 + // 0x0000000000418777: Exiter at stackoverflow2_test.c:40 + // 0x00000000004186d8: CrashHandler at stackoverflow2_test.c:49 + // 0x000000000041872a: StackOverflow at stackoverflow2_test.c:53 + // 0x000000000041872a: StackOverflow at stackoverflow2_test.c:53 + // 0x000000000041872a: StackOverflow at stackoverflow2_test.c:53 + // ... + // + ctx->uc_mcontext.ARG0 = 123; + ctx->uc_mcontext.PC = (long)Exiter; + ctx->uc_mcontext.SP += 32768; + ctx->uc_mcontext.SP &= -16; + ctx->uc_mcontext.SP -= 8; +} + +int StackOverflow(int f(), int n) { + if (n < INT_MAX) { + return f(f, n + 1) - 1; + } else { + return INT_MAX; + } +} + +int (*pStackOverflow)(int (*)(), int) = StackOverflow; + +void *MyPosixThread(void *arg) { + struct sigaction sa; + struct sigaltstack ss; + ss.ss_flags = 0; + ss.ss_size = sysconf(_SC_MINSIGSTKSZ) + 4096; + ss.ss_sp = gc(malloc(ss.ss_size)); + ASSERT_SYS(0, 0, sigaltstack(&ss, 0)); + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // <-- important + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = CrashHandler; + sigaction(SIGBUS, &sa, 0); + sigaction(SIGSEGV, &sa, 0); + exit(pStackOverflow(pStackOverflow, 0)); + return 0; +} + +TEST(stackoverflow, standardStack_altStack_thread_teleport) { + void *res; + pthread_t th; + struct sigaltstack ss; + smashed_stack = false; + pthread_create(&th, 0, MyPosixThread, 0); + pthread_join(th, &res); + ASSERT_EQ((void *)123L, res); + ASSERT_TRUE(smashed_stack); + // this should be SS_DISABLE but ShowCrashReports() creates an alt stack + ASSERT_SYS(0, 0, sigaltstack(0, &ss)); + ASSERT_EQ(0, ss.ss_flags); +} diff --git a/third_party/make/job.c b/third_party/make/job.c index ce1d7cd80..5003fb91f 100644 --- a/third_party/make/job.c +++ b/third_party/make/job.c @@ -1939,7 +1939,7 @@ child_execute_job (struct childbase *child, (STRING_SIZE_TUPLE (".PLEDGE"), c ? c->file : 0, 0)); promises = ps ? xstrdup (ps) : 0; - if (ParsePromises (promises, &ipromises)) + if (ParsePromises (promises, &ipromises, 0)) { OSS (error, NILF, "%s: invalid .PLEDGE string: %s", argv[0], strerror (errno)); diff --git a/tool/build/pledge.c b/tool/build/pledge.c index bb587939c..dfb44a3cf 100644 --- a/tool/build/pledge.c +++ b/tool/build/pledge.c @@ -785,7 +785,7 @@ int main(int argc, char *argv[]) { } } - if (ParsePromises(g_promises, &ipromises) == -1) { + if (ParsePromises(g_promises, &ipromises, 0) == -1) { kprintf("error: bad promises list: %s\n", g_promises); _Exit(21); }