Browse Source

LibJS: Guard against stack overflow in ProxyObject set_property()

For similar reason as in the previous commit.
Maciej 2 năm trước cách đây
mục cha
commit
5d2e915623

+ 17 - 1
Userland/Libraries/LibJS/Runtime/ProxyObject.cpp

@@ -470,7 +470,7 @@ ThrowCompletionOr<Value> ProxyObject::internal_get(PropertyKey const& property_k
     // 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
+    // NOTE: We need to protect ourselves from a Proxy with its (or 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 `get` trap:
@@ -539,6 +539,22 @@ ThrowCompletionOr<bool> ProxyObject::internal_set(PropertyKey const& property_ke
     // 3. Assert: Type(handler) is Object.
     // 4. Let target be O.[[ProxyTarget]].
 
+    // NOTE: We need to protect ourselves from a Proxy with its 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_set()  <- you are here
+    // 2. target -> Object::internal_set()
+    // 3. target -> Object::ordinary_set_with_own_descriptor()
+    // 4. target.[[Prototype]] -> Object::internal_set()
+    // 5. target.[[Prototype]] -> Object::ordinary_set_with_own_descriptor()
+    // 6. target.[[Prototype]].[[Prototype]] (which is ProxyObject) -> Object::internal_set()
+    //
+    // In JS code: `const proxy = new Proxy({}, {}); proxy.__proto__ = Object.create(proxy); proxy["foo"] = "bar";`
+    if (vm.did_reach_stack_space_limit())
+        return vm.throw_completion<InternalError>(ErrorType::CallStackSizeExceeded);
+
     // 5. Let trap be ? GetMethod(handler, "set").
     auto trap = TRY(Value(m_handler).get_method(vm, vm.names.set));
 

+ 17 - 0
Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-set.js

@@ -112,3 +112,20 @@ describe("[[Set]] invariants", () => {
         );
     });
 });
+
+test("Proxy handler that has the Proxy itself as its prototype", () => {
+    const handler = {};
+    const proxy = new Proxy({}, handler);
+    handler.__proto__ = proxy;
+    expect(() => {
+        proxy["foo"] = "bar";
+    }).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(() => {
+        proxy["foo"] = "bar";
+    }).toThrowWithMessage(InternalError, "Call stack size limit exceeded");
+});