From 256a81671bc11dd362317b37d0b61aacb3e34c38 Mon Sep 17 00:00:00 2001 From: Jakob Gruber Date: Tue, 24 Sep 2019 12:55:20 +0200 Subject: [PATCH] [regexp] Adhere to the stack limit in the interpreter This introduces a limit for the interpreter's BacktrackStack to match the limit used by generated code (RegExpStack::kMaximumStackSize). Bug: chromium:1006670 Change-Id: I0b7613698e61257aecca89535ad9109c7e454692 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1821458 Commit-Queue: Jakob Gruber Commit-Queue: Peter Marshall Auto-Submit: Jakob Gruber Reviewed-by: Peter Marshall Cr-Commit-Position: refs/heads/master@{#63945} --- src/regexp/regexp-interpreter.cc | 41 ++++++++++++++++++++----- src/regexp/regexp-stack.h | 6 ++-- test/mjsunit/regress/regress-1006670.js | 5 +++ 3 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 test/mjsunit/regress/regress-1006670.js diff --git a/src/regexp/regexp-interpreter.cc b/src/regexp/regexp-interpreter.cc index fc0d6effa5..c4ba4a74bb 100644 --- a/src/regexp/regexp-interpreter.cc +++ b/src/regexp/regexp-interpreter.cc @@ -12,6 +12,7 @@ #include "src/objects/objects-inl.h" #include "src/regexp/regexp-bytecodes.h" #include "src/regexp/regexp-macro-assembler.h" +#include "src/regexp/regexp-stack.h" // For kMaximumStackSize. #include "src/regexp/regexp.h" #include "src/strings/unicode.h" #include "src/utils/utils.h" @@ -118,7 +119,10 @@ class BacktrackStack { public: BacktrackStack() = default; - void push(int v) { data_.emplace_back(v); } + V8_WARN_UNUSED_RESULT bool push(int v) { + data_.emplace_back(v); + return (static_cast(data_.size()) <= kMaxSize); + } int peek() const { DCHECK(!data_.empty()); return data_.back(); @@ -141,13 +145,17 @@ class BacktrackStack { // static stack-allocated backing store, but small enough not to waste space. static constexpr int kStaticCapacity = 64; - base::SmallVector data_; + using ValueT = int; + base::SmallVector data_; + + static constexpr int kMaxSize = + RegExpStack::kMaximumStackSize / sizeof(ValueT); DISALLOW_COPY_AND_ASSIGN(BacktrackStack); }; -IrregexpInterpreter::Result StackOverflow(Isolate* isolate, - RegExp::CallOrigin call_origin) { +IrregexpInterpreter::Result ThrowStackOverflow(Isolate* isolate, + RegExp::CallOrigin call_origin) { CHECK(call_origin == RegExp::CallOrigin::kFromRuntime); // We abort interpreter execution after the stack overflow is thrown, and thus // allow allocation here despite the outer DisallowHeapAllocationScope. @@ -156,6 +164,17 @@ IrregexpInterpreter::Result StackOverflow(Isolate* isolate, return IrregexpInterpreter::EXCEPTION; } +// Only throws if called from the runtime, otherwise just returns the EXCEPTION +// status code. +IrregexpInterpreter::Result MaybeThrowStackOverflow( + Isolate* isolate, RegExp::CallOrigin call_origin) { + if (call_origin == RegExp::CallOrigin::kFromRuntime) { + return ThrowStackOverflow(isolate, call_origin); + } else { + return IrregexpInterpreter::EXCEPTION; + } +} + template void UpdateCodeAndSubjectReferences( Isolate* isolate, Handle code_array, @@ -208,7 +227,7 @@ IrregexpInterpreter::Result HandleInterrupts( Handle subject_handle(*subject_string_out, isolate); if (js_has_overflowed) { - return StackOverflow(isolate, call_origin); + return ThrowStackOverflow(isolate, call_origin); } else if (check.InterruptRequested()) { const bool was_one_byte = String::IsOneByteRepresentationUnderneath(*subject_string_out); @@ -376,17 +395,23 @@ IrregexpInterpreter::Result RawMatch(Isolate* isolate, ByteArray code_array, BYTECODE(BREAK) { UNREACHABLE(); } BYTECODE(PUSH_CP) { ADVANCE(PUSH_CP); - backtrack_stack.push(current); + if (!backtrack_stack.push(current)) { + return MaybeThrowStackOverflow(isolate, call_origin); + } DISPATCH(); } BYTECODE(PUSH_BT) { ADVANCE(PUSH_BT); - backtrack_stack.push(Load32Aligned(pc + 4)); + if (!backtrack_stack.push(Load32Aligned(pc + 4))) { + return MaybeThrowStackOverflow(isolate, call_origin); + } DISPATCH(); } BYTECODE(PUSH_REGISTER) { ADVANCE(PUSH_REGISTER); - backtrack_stack.push(registers[insn >> BYTECODE_SHIFT]); + if (!backtrack_stack.push(registers[insn >> BYTECODE_SHIFT])) { + return MaybeThrowStackOverflow(isolate, call_origin); + } DISPATCH(); } BYTECODE(SET_REGISTER) { diff --git a/src/regexp/regexp-stack.h b/src/regexp/regexp-stack.h index 7ecaa40b81..d3c5415f1f 100644 --- a/src/regexp/regexp-stack.h +++ b/src/regexp/regexp-stack.h @@ -73,6 +73,9 @@ class RegExpStack { char* RestoreStack(char* from); void FreeThreadResources() { thread_local_.Free(); } + // Maximal size of allocated stack area. + static constexpr size_t kMaximumStackSize = 64 * MB; + private: RegExpStack(); ~RegExpStack(); @@ -84,9 +87,6 @@ class RegExpStack { // Minimal size of allocated stack area. static const size_t kMinimumStackSize = 1 * KB; - // Maximal size of allocated stack area. - static const size_t kMaximumStackSize = 64 * MB; - // Structure holding the allocated memory, size and limit. struct ThreadLocal { ThreadLocal() { Clear(); } diff --git a/test/mjsunit/regress/regress-1006670.js b/test/mjsunit/regress/regress-1006670.js new file mode 100644 index 0000000000..4d1408b3d1 --- /dev/null +++ b/test/mjsunit/regress/regress-1006670.js @@ -0,0 +1,5 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +assertThrows(() => /(a?;?){4000000}/.exec("a"), RangeError);