diff --git a/lib/VM/JSProxy.cpp b/lib/VM/JSProxy.cpp index b5be88db71c..2b32a3ea84f 100644 --- a/lib/VM/JSProxy.cpp +++ b/lib/VM/JSProxy.cpp @@ -43,6 +43,15 @@ findTrap(Handle selfHandle, Runtime *runtime, Predefined::Str name) { // 5. Let target be O.[[ProxyTarget]]. // 6. Let trap be ? GetMethod(handler, « name »). Handle handler = runtime->makeHandle(handlerPtr); + // Calls to look up the trap are effectively recursion, and so + // require their own scope. They also need a + // ScopedNativeDepthTracker, as it's possible to use up arbitrary + // native stack depth with nested proxies. + GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); + } CallResult> trapVal = JSObject::getNamed_RJS(handler, runtime, Predefined::getSymbolID(name)); if (trapVal == ExecutionStatus::EXCEPTION) { @@ -57,7 +66,7 @@ findTrap(Handle selfHandle, Runtime *runtime, Predefined::Str name) { runtime->makeHandle(std::move(*trapVal)), " is not a Proxy trap function"); } - return runtime->makeHandle(std::move(trapVal->get())); + return runtime->makeHandleInParentScope(std::move(trapVal->get())); } } // namespace detail @@ -280,8 +289,15 @@ CallResult> JSProxy::getPrototypeOf( if (!*trapRes) { // a. Return ? target.[[GetPrototypeOf]](P). // All calls to the target in the no-trap case are effectively - // recursion, and so require their own scope. + // recursion, and so require their own scope. They also need a + // ScopedNativeDepthTracker, as it's possible to use up arbitrary + // native stack depth with nested proxies. GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } return JSObject::getPrototypeOf(target, runtime); } // 7. Let handlerProto be ? Call(trap, handler, « target »). @@ -342,6 +358,11 @@ CallResult JSProxy::setPrototypeOf( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[SetPrototypeOf]](V). return JSObject::setParent(*target, runtime, *parent); } @@ -398,6 +419,11 @@ CallResult JSProxy::isExtensible( // 6. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[IsExtensible]](). return JSObject::isExtensible(target, runtime); } @@ -441,6 +467,11 @@ CallResult JSProxy::preventExtensions( // 6. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[PreventExtensions]](). // We pass in opFlags here. If getThrowOnError, then this will cause // the underlying exception to bubble up. If !getThrowOnError, then @@ -494,6 +525,11 @@ CallResult JSProxy::getOwnProperty( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[GetOwnProperty]](P). return valueOrAccessor ? JSObject::getOwnComputedDescriptor( @@ -641,6 +677,11 @@ CallResult JSProxy::defineOwnProperty( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[GetOwnProperty]](P). return JSObject::defineOwnComputedPrimitive( target, runtime, nameValHandle, dpFlags, valueOrAccessor, opFlags); @@ -805,6 +846,11 @@ CallResult JSProxy::hasNamed( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[HasProperty]](P, Receiver). return JSObject::hasNamed(target, runtime, name); } @@ -832,6 +878,11 @@ CallResult JSProxy::hasComputed( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[HasProperty]](P, Receiver). return JSObject::hasComputed(target, runtime, nameValHandle); } @@ -921,6 +972,11 @@ CallResult> JSProxy::getNamed( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[Get]](P, Receiver). return JSObject::getNamedWithReceiver_RJS(target, runtime, name, receiver); } @@ -951,6 +1007,11 @@ CallResult> JSProxy::getComputed( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[Get]](P, Receiver). return JSObject::getComputedWithReceiver_RJS( target, runtime, nameValHandle, receiver); @@ -1050,6 +1111,11 @@ CallResult JSProxy::setNamed( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[Set]](P, V, Receiver). return JSObject::putNamedWithReceiver_RJS( target, runtime, name, valueHandle, receiver); @@ -1084,6 +1150,11 @@ CallResult JSProxy::setComputed( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[Set]](P, V, Receiver). return JSObject::putComputedWithReceiver_RJS( target, runtime, nameValHandle, valueHandle, receiver); @@ -1163,6 +1234,11 @@ CallResult JSProxy::deleteNamed( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[Delete]](P, Receiver). return JSObject::deleteNamed(target, runtime, name); } @@ -1190,6 +1266,11 @@ CallResult JSProxy::deleteComputed( // 7. If trap is undefined, then if (!*trapRes) { GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } // a. Return ? target.[[Delete]](P, Receiver). return JSObject::deleteComputed(target, runtime, nameValHandle); } @@ -1297,6 +1378,11 @@ CallResult> JSProxy::ownPropertyKeys( if (!*trapRes) { // a. Return ? target.[[OwnPropertyKeys]](). GCScope gcScope(runtime); + ScopedNativeDepthTracker depthTracker(runtime); + if (LLVM_UNLIKELY(depthTracker.overflowed())) { + return runtime->raiseStackOverflow( + Runtime::StackOverflowKind::NativeStack); + } CallResult> targetRes = // Include everything here, so that filterKeys has a chance to // make observable trap calls. diff --git a/test/hermes/proxy.js b/test/hermes/proxy.js index bc8102f3ed9..e7f59aeb6cc 100644 --- a/test/hermes/proxy.js +++ b/test/hermes/proxy.js @@ -2102,13 +2102,35 @@ arrayTests( print('misc'); // CHECK-LABEL: misc -// Do a deep target recursion +// Do a deep target recursion. This needs to be within the smallest +// (ASAN) limit for tests to pass, but larger numbers will work in +// production builds. var p = {a:1}; -for (var i = 0; i < 64; ++i) { +for (var i = 0; i < 20; ++i) { p = new Proxy(p, {}); } assert.equal(p.a, 1); +// Do a really deep target recursion to test for stack overflow +var p = {a:1}; +for (var i = 0; i < 10000; ++i) { + p = new Proxy({}, p); +} +checkThrows(RangeError)(_ => p.a); + +// Do a really deep handler recursion to test for stack overflow +var p = {a:1}; +for (var i = 0; i < 10000; ++i) { + p = new Proxy(p, {}); +} +checkThrows(RangeError)(_ => p.a); + +// Do an infinite recursion to test for stack overflow +var p1 = []; +var p2 = new Proxy(p1, {}); +p1.__proto__ = p2; +checkThrows(RangeError)(_ => p1.a); + // Test HermesInternal assert.equal( typeof HermesInternal !== 'object' ||