From 5274ab4c3071d1a986cc0036f1696ed5fff9f1a8 Mon Sep 17 00:00:00 2001 From: seanmakesgames Date: Wed, 12 May 2021 23:08:22 -0700 Subject: [PATCH] FEATURE: Configurable max marshal stack depth (#202) `marshal_stack_depth` can be used to determine how deep objects are allowed to be when marshalling JS to Ruby objects in evals. --- .gitignore | 1 + README.md | 13 ++ .../mini_racer_extension.cc | 152 ++++++++++++++++-- lib/mini_racer.rb | 13 +- test/mini_racer_test.rb | 61 ++++++- 5 files changed, 222 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index a13ff12..8a73970 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Gemfile.lock lib/mini_racer_extension.so lib/mini_racer_loader.so *.bundle +.vscode/ diff --git a/README.md b/README.md index 96abf14..9d6a7a2 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,19 @@ context.eval 'var a = new Array(10000); while(true) {a = a.concat(new Array(1000 # => V8OutOfMemoryError is raised ``` +### Object marshal max stackdepth support + +Contexts can specify a stack depth limit for object marshalling. Max depth is unbounded by default. + +```ruby +# terminates script if stack depth exceeds max during marshal +context = MiniRacer::Context.new(marshal_stack_depth: 500) +context.attach("a", proc{|a| a}) + +context.eval("let arr = []; arr.push(arr); a(arr)") +# => RuntimeError is raised +``` + ### Rich debugging with "filename" support ```ruby diff --git a/ext/mini_racer_extension/mini_racer_extension.cc b/ext/mini_racer_extension/mini_racer_extension.cc index fc8219c..255a296 100644 --- a/ext/mini_racer_extension/mini_racer_extension.cc +++ b/ext/mini_racer_extension/mini_racer_extension.cc @@ -115,6 +115,7 @@ typedef struct { useconds_t timeout; EvalResult* result; size_t max_memory; + size_t marshal_stackdepth; } EvalParams; typedef struct { @@ -126,6 +127,7 @@ typedef struct { Local *argv; EvalResult result; size_t max_memory; + size_t marshal_stackdepth; } FunctionCall; class IsolateData { @@ -136,16 +138,27 @@ public: IN_GVL, // whether we are inside of ruby gvl or not DO_TERMINATE, // terminate as soon as possible MEM_SOFTLIMIT_REACHED, // we've hit the memory soft limit - MEM_SOFTLIMIT_VALUE, + MEM_SOFTLIMIT_MAX, // maximum memory value + MARSHAL_STACKDEPTH_REACHED, // we've hit our max stack depth + MARSHAL_STACKDEPTH_VALUE, // current stackdepth + MARSHAL_STACKDEPTH_MAX, // maximum stack depth during marshal }; + static void Init(Isolate *isolate) { + // zero out all fields in the bitfield + isolate->SetData(0, 0); + } + static uintptr_t Get(Isolate *isolate, Flag flag) { Bitfield u = { reinterpret_cast(isolate->GetData(0)) }; switch (flag) { case IN_GVL: return u.IN_GVL; case DO_TERMINATE: return u.DO_TERMINATE; case MEM_SOFTLIMIT_REACHED: return u.MEM_SOFTLIMIT_REACHED; - case MEM_SOFTLIMIT_VALUE: return u.MEM_SOFTLIMIT_VALUE << 10; + case MEM_SOFTLIMIT_MAX: return u.MEM_SOFTLIMIT_MAX << 10; + case MARSHAL_STACKDEPTH_REACHED: return u.MARSHAL_STACKDEPTH_REACHED; + case MARSHAL_STACKDEPTH_VALUE: return u.MARSHAL_STACKDEPTH_VALUE; + case MARSHAL_STACKDEPTH_MAX: return u.MARSHAL_STACKDEPTH_MAX; } } @@ -156,7 +169,10 @@ public: case DO_TERMINATE: u.DO_TERMINATE = value; break; case MEM_SOFTLIMIT_REACHED: u.MEM_SOFTLIMIT_REACHED = value; break; // drop least significant 10 bits 'store memory amount in kb' - case MEM_SOFTLIMIT_VALUE: u.MEM_SOFTLIMIT_VALUE = value >> 10; break; + case MEM_SOFTLIMIT_MAX: u.MEM_SOFTLIMIT_MAX = value >> 10; break; + case MARSHAL_STACKDEPTH_REACHED: u.MARSHAL_STACKDEPTH_REACHED = value; break; + case MARSHAL_STACKDEPTH_VALUE: u.MARSHAL_STACKDEPTH_VALUE = value; break; + case MARSHAL_STACKDEPTH_MAX: u.MARSHAL_STACKDEPTH_MAX = value; break; } isolate->SetData(0, reinterpret_cast(u.dataPtr)); } @@ -174,15 +190,88 @@ private: // order in this struct matters. For cpu performance keep larger subobjects // aligned on their boundaries (8 16 32), try not to straddle struct { - size_t MEM_SOFTLIMIT_VALUE:22; + size_t MEM_SOFTLIMIT_MAX:22; bool IN_GVL:1; bool DO_TERMINATE:1; bool MEM_SOFTLIMIT_REACHED:1; + bool MARSHAL_STACKDEPTH_REACHED:1; + uint8_t :0; // align to next 8bit bound + size_t MARSHAL_STACKDEPTH_VALUE:10; + uint8_t :0; // align to next 8bit bound + size_t MARSHAL_STACKDEPTH_MAX:10; }; }; }; }; +struct StackCounter { + static void Reset(Isolate* isolate) { + if (IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX) > 0) { + IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, 0); + IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, false); + } + } + + static void SetMax(Isolate* isolate, size_t marshalMaxStackDepth) { + if (marshalMaxStackDepth > 0) { + IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX, marshalMaxStackDepth); + IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, 0); + IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, false); + } + } + + StackCounter(Isolate* isolate) { + this->isActive = IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX) > 0; + + if (this->isActive) { + this->isolate = isolate; + this->IncDepth(1); + } + } + + bool IsTooDeep() { + if (!this->IsActive()) { + return false; + } + + size_t depth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE); + size_t maxDepth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_MAX); + if (depth > maxDepth) { + IsolateData::Set(this->isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, true); + return true; + } + + return false; + } + + bool IsActive() { + return this->isActive && !IsolateData::Get(this->isolate, IsolateData::DO_TERMINATE); + } + + ~StackCounter() { + if (this->IsActive()) { + this->IncDepth(-1); + } + } + +private: + Isolate* isolate; + bool isActive; + + void IncDepth(int direction) { + int inc = direction > 0 ? 1 : -1; + + size_t depth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE); + + // don't decrement past 0 + if (inc > 0 || depth > 0) { + depth += inc; + } + + IsolateData::Set(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, depth); + } +}; + static VALUE rb_cContext; static VALUE rb_cSnapshot; static VALUE rb_cIsolate; @@ -257,7 +346,7 @@ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) { return; } - size_t softlimit = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_VALUE); + size_t softlimit = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_MAX); HeapStatistics stats; isolate->GetHeapStatistics(&stats); @@ -376,10 +465,7 @@ nogvl_context_eval(void* arg) { Context::Scope context_scope(context); v8::ScriptOrigin *origin = NULL; - IsolateData::Set(isolate, IsolateData::IN_GVL, false); - IsolateData::Set(isolate, IsolateData::DO_TERMINATE, false); - IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_VALUE, 0); - IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_REACHED, false); + IsolateData::Init(isolate); MaybeLocal