Skip to content

Commit 52a5a42

Browse files
sppmacdtrflynn89
authored andcommitted
LibJS: Guard against stack overflow in ProxyObject has_property()
If proxy has an undefined trap, it will fallback to target's internal_has_property, which will then check target's prototype for the requested property. If Proxy's prototype is set to the Proxy itself, it will check in itself in a loop, causing a stack overflow.
1 parent e7502d4 commit 52a5a42

File tree

2 files changed

+30
-0
lines changed

2 files changed

+30
-0
lines changed

Userland/Libraries/LibJS/Runtime/ProxyObject.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,19 @@ ThrowCompletionOr<bool> ProxyObject::internal_has_property(PropertyKey const& pr
402402
// 3. Assert: Type(handler) is Object.
403403
// 4. Let target be O.[[ProxyTarget]].
404404

405+
// NOTE: We need to protect ourselves from a Proxy with the handler's prototype set to the
406+
// Proxy itself, which would by default bounce between these functions indefinitely and lead to
407+
// a stack overflow when the Proxy's (p) or Proxy handler's (h) Object::get() is called and the
408+
// handler doesn't have a `has` trap:
409+
//
410+
// 1. p -> ProxyObject::internal_has_property() <- you are here
411+
// 2. target -> Object::internal_has_property()
412+
// 3. target.[[Prototype]] (which is internal_has_property) -> Object::internal_has_property()
413+
//
414+
// In JS code: `const proxy = new Proxy({}, {}); proxy.__proto__ = Object.create(proxy); "foo" in proxy;`
415+
if (vm.did_reach_stack_space_limit())
416+
return vm.throw_completion<InternalError>(ErrorType::CallStackSizeExceeded);
417+
405418
// 5. Let trap be ? GetMethod(handler, "has").
406419
auto trap = TRY(Value(m_handler).get_method(vm, vm.names.has));
407420

Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-has.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,20 @@ describe("[[Has]] invariants", () => {
8585
);
8686
});
8787
});
88+
89+
test("Proxy handler that has the Proxy itself as its prototype", () => {
90+
const handler = {};
91+
const proxy = new Proxy({}, handler);
92+
handler.__proto__ = proxy;
93+
expect(() => {
94+
"foo" in proxy;
95+
}).toThrowWithMessage(InternalError, "Call stack size limit exceeded");
96+
});
97+
98+
test("Proxy that has the Proxy itself as its prototype", () => {
99+
const proxy = new Proxy({}, {});
100+
proxy.__proto__ = Object.create(proxy);
101+
expect(() => {
102+
"foo" in proxy;
103+
}).toThrowWithMessage(InternalError, "Call stack size limit exceeded");
104+
});

0 commit comments

Comments
 (0)