diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp index 215f06bd3c..0bf1588fb9 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -402,6 +402,19 @@ ThrowCompletionOr ProxyObject::internal_has_property(PropertyKey const& pr // 3. Assert: Type(handler) is Object. // 4. Let target be O.[[ProxyTarget]]. + // NOTE: We need to protect ourselves from a Proxy with the handler's prototype set to the + // Proxy itself, which would by default bounce between these functions indefinitely and lead to + // a stack overflow when the Proxy's (p) or Proxy handler's (h) Object::get() is called and the + // handler doesn't have a `has` trap: + // + // 1. p -> ProxyObject::internal_has_property() <- you are here + // 2. target -> Object::internal_has_property() + // 3. target.[[Prototype]] (which is internal_has_property) -> Object::internal_has_property() + // + // In JS code: `const proxy = new Proxy({}, {}); proxy.__proto__ = Object.create(proxy); "foo" in proxy;` + if (vm.did_reach_stack_space_limit()) + return vm.throw_completion(ErrorType::CallStackSizeExceeded); + // 5. Let trap be ? GetMethod(handler, "has"). auto trap = TRY(Value(m_handler).get_method(vm, vm.names.has)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js index 39d48f0ca2..db2e9d9054 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js @@ -85,3 +85,20 @@ describe("[[Has]] invariants", () => { ); }); }); + +test("Proxy handler that has the Proxy itself as its prototype", () => { + const handler = {}; + const proxy = new Proxy({}, handler); + handler.__proto__ = proxy; + expect(() => { + "foo" in proxy; + }).toThrowWithMessage(InternalError, "Call stack size limit exceeded"); +}); + +test("Proxy that has the Proxy itself as its prototype", () => { + const proxy = new Proxy({}, {}); + proxy.__proto__ = Object.create(proxy); + expect(() => { + "foo" in proxy; + }).toThrowWithMessage(InternalError, "Call stack size limit exceeded"); +});