diff --git a/ecmascript/ecma_vm.cpp b/ecmascript/ecma_vm.cpp index 720da8a1ea657b7766a697e56f91e0aa57fc1257..63653749ce643d93cac5f080bc68eada56a72eeb 100644 --- a/ecmascript/ecma_vm.cpp +++ b/ecmascript/ecma_vm.cpp @@ -396,6 +396,7 @@ bool EcmaVM::Initialize() sustainingJSHandleList_ = new SustainingJSHandleList(); initialized_ = true; regExpParserCache_ = new RegExpParserCache(); + stackOverflowError_ = factory_->GetJSError(base::ErrorType::RANGE_ERROR, "xyh: Stack overflow!", StackCheck::NO); return true; } diff --git a/ecmascript/ecma_vm.h b/ecmascript/ecma_vm.h index 856435ae0cafec707e33634e939fe556b6faf669..1953d41c89eabf709ade0313d31dfe4cc4d7bda0 100644 --- a/ecmascript/ecma_vm.h +++ b/ecmascript/ecma_vm.h @@ -1333,6 +1333,11 @@ public: } #endif // PANDA_JS_ETS_HYBRID_MODE + JSHandle GetStackOverflowError() const + { + return stackOverflowError_; + } + protected: void PrintJSErrorInfo(const JSHandle &exceptionInfo) const; @@ -1618,6 +1623,8 @@ private: // store Application versionCode uint32_t applicationVersionCode_ {0}; + + JSHandle stackOverflowError_; }; } // namespace ecmascript } // namespace panda diff --git a/ecmascript/js_thread.cpp b/ecmascript/js_thread.cpp index 8b3327e1f876181eff02f5e77a5369ef90a20f80..0f6f5fa3c844e84b5f2537f359da52ea06a3e17c 100644 --- a/ecmascript/js_thread.cpp +++ b/ecmascript/js_thread.cpp @@ -146,6 +146,10 @@ JSThread *JSThread::Create(EcmaVM *vm) jsThread->glueData_.currentFrame_ = jsThread->glueData_.frameBase_ + maxStackSize; EcmaInterpreter::InitStackFrame(jsThread); + if (!InitStackOverflowCheck(jsThread)) { + LOG_ECMA(ERROR) << "init stackoverflow check failed"; + } + jsThread->glueData_.stackLimit_ = GetAsmStackLimit(); jsThread->glueData_.stackStart_ = GetCurrentStackPosition(); jsThread->glueData_.isEnableMutantArray_ = vm->IsEnableMutantArray(); diff --git a/ecmascript/platform/asm_stack.h b/ecmascript/platform/asm_stack.h index 6c4afbd4337371232451beb6c074e1b5718d1572..6144464a05f857601bccd5c1d6332f2f8e70d15a 100644 --- a/ecmascript/platform/asm_stack.h +++ b/ecmascript/platform/asm_stack.h @@ -17,7 +17,16 @@ #define ECMASCRIPT_PLATFORM_ASM_STACK_H #include +#include "ecmascript/js_thread.h" namespace panda::ecmascript { + +#define SIGSTACK_SIZE (8 * 1024) + +static uintptr_t pregion; +static stack_t sigstack; +static JSThread *mainThread; + +bool InitStackOverflowCheck(JSThread *jsThread); size_t GetAsmStackLimit(); bool IsMainThread(); } diff --git a/ecmascript/platform/common/asm_stack.cpp b/ecmascript/platform/common/asm_stack.cpp index 3ad1400e60a67d4e8ed797bd7faf8433f9d58ba0..9d285f3a3590c1e05fda92c6c19e242b39bfe889 100644 --- a/ecmascript/platform/common/asm_stack.cpp +++ b/ecmascript/platform/common/asm_stack.cpp @@ -13,12 +13,80 @@ * limitations under the License. */ +#include #include +#include +#include "ecmascript/base/error_type.h" +#include "ecmascript/ecma_vm.h" +#include "ecmascript/object_factory.h" #include "ecmascript/platform/asm_stack.h" -#include "ecmascript/js_thread.h" namespace panda::ecmascript { +void SegfaultHandler(int sig, siginfo_t *info, void *ucontext) { + ucontext_t *uc = (ucontext_t *)ucontext; + + LOG_ECMA(ERROR) << "si addr: " << std::hex << info->si_addr; + LOG_ECMA(ERROR) << "rip: " << std::hex << (void*)uc->uc_mcontext.gregs[REG_RIP]; + LOG_ECMA(ERROR) << "rsp: " << std::hex << (void*)uc->uc_mcontext.gregs[REG_RSP]; + + if (reinterpret_cast(info->si_addr) <= pregion) { + EcmaVM *vm = mainThread->GetEcmaVM(); + vm->CheckThread(); + LOG_ECMA(ERROR) << "Stack overflow! Remaining stack size is: (should be 0)"; + if (LIKELY(!mainThread->HasPendingException())) { + mainThread->SetException(vm->GetStackOverflowError().GetTaggedValue()); + LOG_ECMA(ERROR) << "set exception success"; + } + } else { + LOG_ECMA(ERROR) << "this branch should be unreachable"; + } + + LOG_ECMA(INFO) << "try open protected region and return"; + + size_t pageSize = sysconf(_SC_PAGESIZE); + + if (mprotect(reinterpret_cast(pregion - 4 * pageSize), 4 * pageSize, PROT_READ | PROT_WRITE) == -1) { + LOG_ECMA(ERROR) << "unable to unprotect region in stack for implicit overflow check. " + << "Reason: " << strerror(errno); + } else { + LOG_ECMA(INFO) << "unprotect success!"; + } +} + +bool InitStackOverflowCheck(JSThread *jsThread) +{ + struct sigaction sa; + void *stack_mem = malloc(SIGSTACK_SIZE); + mainThread = jsThread; + + if (stack_mem == nullptr) { + LOG_ECMA(ERROR) << "mmap for altstack failed"; + return false; + } + + sigstack.ss_sp = stack_mem; + sigstack.ss_size = SIGSTACK_SIZE; + sigstack.ss_flags = 0; + + if (sigaltstack(&sigstack, NULL) == -1) { + LOG_ECMA(ERROR) << "sigaltstack failed"; + return false; + } + + LOG_ECMA(INFO) << "Alternate signal stack set at " << std::hex << sigstack.ss_sp << ", size: " << sigstack.ss_size; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = SegfaultHandler; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART | SA_NODEFER; + + if (sigaction(SIGSEGV, &sa, NULL) == -1) { + LOG_ECMA(ERROR) << "sigaction failed"; + return false; + } + return true; +} + size_t GetAsmStackLimit() { // js stack limit @@ -103,6 +171,46 @@ size_t GetAsmStackLimit() << ", Current thread asm stack limit: " << reinterpret_cast(currentThreadAsmStackLimit) << ", Result: " << reinterpret_cast(result); } + + size_t pageSize = sysconf(_SC_PAGESIZE); + uintptr_t result_ = AlignUp(result, pageSize); + // result_ += 32 * pageSize; + + struct RecurseDownStack { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wframe-larger-than=" + __attribute__((noinline)) + __attribute__((no_sanitize("memtag"))) static void Touch(uintptr_t target) { + volatile size_t zero = 0; + constexpr size_t kAsanMultiplier = +#ifdef ADDRESS_SANITIZER + 2u; +#else + 1u; +#endif + volatile char space[sysconf(_SC_PAGESIZE) - (kAsanMultiplier * 256)] __attribute__((uninitialized)); + [[maybe_unused]] char sink = space[zero]; + uintptr_t addr = reinterpret_cast(space); + if (addr >= target + sysconf(_SC_PAGESIZE)) { + Touch(target); + } + zero *= 2; + } +#pragma GCC diagnostic pop + }; + RecurseDownStack::Touch(result_); + + LOG_ECMA(INFO) << "installing stack protected region at " << std::hex << result_; + LOG_ECMA(INFO) << "current stack start: " << std::hex << threadStackStart; + pregion = result_ + 4 * pageSize; + + if (mprotect(reinterpret_cast(result_), 4 * pageSize, PROT_NONE) == -1) { + LOG_ECMA(ERROR) << "unable to create protected region in stack for implicit overflow check. " + << "Reason: " << strerror(errno); + } else { + LOG_ECMA(INFO) << "mprotect success!"; + } + return result; } diff --git a/ecmascript/stubs/runtime_stubs.cpp b/ecmascript/stubs/runtime_stubs.cpp index 5dd40211a3a77086ee6b78d9afb816e7ceb4c396..0fc8c0dc55b853c8326986746dfdad8d067a4fcd 100644 --- a/ecmascript/stubs/runtime_stubs.cpp +++ b/ecmascript/stubs/runtime_stubs.cpp @@ -2767,14 +2767,16 @@ DEF_RUNTIME_STUBS(ThrowNonConstructorException) DEF_RUNTIME_STUBS(ThrowStackOverflowException) { RUNTIME_STUBS_HEADER(ThrowStackOverflowException); - EcmaVM *ecmaVm = thread->GetEcmaVM(); - // Multi-thread could cause stack-overflow-check failed too, - // so check thread here to distinguish it with the actual stack overflow. - ecmaVm->CheckThread(); - ObjectFactory *factory = ecmaVm->GetFactory(); - JSHandle error = factory->GetJSError(ErrorType::RANGE_ERROR, "Stack overflow!", StackCheck::NO); if (LIKELY(!thread->HasPendingException())) { + EcmaVM *ecmaVm = thread->GetEcmaVM(); + // Multi-thread could cause stack-overflow-check failed too, + // so check thread here to distinguish it with the actual stack overflow. + ecmaVm->CheckThread(); + ObjectFactory *factory = ecmaVm->GetFactory(); + JSHandle error = factory->GetJSError(ErrorType::RANGE_ERROR, "Stack overflow!", StackCheck::NO); thread->SetException(error.GetTaggedValue()); + } else { + LOG_ECMA(ERROR) << "xyh: stackoverflow exception thrown"; } return JSTaggedValue::Exception().GetRawData(); }