Title: [248508] branches/safari-608.1-branch
Revision
248508
Author
bshaf...@apple.com
Date
2019-08-10 20:02:10 -0700 (Sat, 10 Aug 2019)

Log Message

Cherry-pick r248494. rdar://problem/54171879

    Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive
    https://bugs.webkit.org/show_bug.cgi?id=199864

    Reviewed by Saam Barati.

    Source/_javascript_Core:

    Our JSObject::put implementation is not correct in term of the spec. Our [[Put]] implementation is something like this.

        JSObject::put(object):
            if (can-do-fast-path(object))
                return fast-path(object);
            // slow-path
            do {
                object-put-check-and-setter-calls(object); // (1)
                object = object->prototype;
            } while (is-object(object));
            return do-put(object);

    Since JSObject::put is registered in the methodTable, the derived classes can override it. Some of classes are adding
    extra checks to this put.

        Derived::put(object):
            if (do-extra-check(object))
                fail
            return JSObject::put(object)

    The problem is that Derived::put is only called when the |this| object is the Derived class. When traversing [[Prototype]] in
    JSObject::put, at (1), we do not perform the extra checks added in Derived::put even if `object` is Derived one. This means that
    we skip the check.

    Currently, JSObject::put and WebCore checking mechanism are broken. JSObject::put should call getOwnPropertySlot at (1) to
    perform the additional checks. This behavior is matching against the spec. However, currently, our JSObject::getOwnPropertySlot
    does not propagate setter information. This is required to cache cacheable [[Put]] at (1) for CustomValue, CustomAccessor, and
    Accessors. We also need to reconsider how to integrate static property setters to this mechanism. So, basically, this involves
    large refactoring to renew our JSObject::put and JSObject::getOwnPropertySlot.

    To work-around for now, we add a new TypeInfo flag, HasPutPropertySecurityCheck . And adding this flag to DOM objects
    that implements the addition checks. We also add doPutPropertySecurityCheck method hook to perform the check in JSObject.
    When we found this flag at (1), we perform doPutPropertySecurityCheck to properly perform the checks.

    Since our JSObject::put code is old and it does not match against the spec now, we should refactor it largely. This is tracked separately in [1].

    [1]: https://bugs.webkit.org/show_bug.cgi?id=200562

    * runtime/ClassInfo.h:
    * runtime/JSCJSValue.cpp:
    (JSC::JSValue::putToPrimitive):
    * runtime/JSCell.cpp:
    (JSC::JSCell::doPutPropertySecurityCheck):
    * runtime/JSCell.h:
    * runtime/JSObject.cpp:
    (JSC::JSObject::putInlineSlow):
    (JSC::JSObject::getOwnPropertyDescriptor):
    * runtime/JSObject.h:
    (JSC::JSObject::doPutPropertySecurityCheck):
    * runtime/JSTypeInfo.h:
    (JSC::TypeInfo::hasPutPropertySecurityCheck const):

    Source/WebCore:

    Test: http/tests/security/cross-frame-access-object-put-optimization.html

    * bindings/js/JSDOMWindowCustom.cpp:
    (WebCore::JSDOMWindow::doPutPropertySecurityCheck):
    * bindings/js/JSLocationCustom.cpp:
    (WebCore::JSLocation::doPutPropertySecurityCheck):
    * bindings/scripts/CodeGeneratorJS.pm:
    (GenerateHeader):
    * bindings/scripts/test/JS/JSTestActiveDOMObject.h:

    LayoutTests:

    * http/tests/security/cross-frame-access-object-put-optimization-expected.txt: Added.
    * http/tests/security/cross-frame-access-object-put-optimization.html: Added.
    * http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html: Added.

    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@248494 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Modified Paths

Added Paths

Diff

Modified: branches/safari-608.1-branch/LayoutTests/ChangeLog (248507 => 248508)


--- branches/safari-608.1-branch/LayoutTests/ChangeLog	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/LayoutTests/ChangeLog	2019-08-11 03:02:10 UTC (rev 248508)
@@ -1,5 +1,99 @@
 2019-08-10  Babak Shafiei  <bshaf...@apple.com>
 
+        Cherry-pick r248494. rdar://problem/54171879
+
+    Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive
+    https://bugs.webkit.org/show_bug.cgi?id=199864
+    
+    Reviewed by Saam Barati.
+    
+    Source/_javascript_Core:
+    
+    Our JSObject::put implementation is not correct in term of the spec. Our [[Put]] implementation is something like this.
+    
+        JSObject::put(object):
+            if (can-do-fast-path(object))
+                return fast-path(object);
+            // slow-path
+            do {
+                object-put-check-and-setter-calls(object); // (1)
+                object = object->prototype;
+            } while (is-object(object));
+            return do-put(object);
+    
+    Since JSObject::put is registered in the methodTable, the derived classes can override it. Some of classes are adding
+    extra checks to this put.
+    
+        Derived::put(object):
+            if (do-extra-check(object))
+                fail
+            return JSObject::put(object)
+    
+    The problem is that Derived::put is only called when the |this| object is the Derived class. When traversing [[Prototype]] in
+    JSObject::put, at (1), we do not perform the extra checks added in Derived::put even if `object` is Derived one. This means that
+    we skip the check.
+    
+    Currently, JSObject::put and WebCore checking mechanism are broken. JSObject::put should call getOwnPropertySlot at (1) to
+    perform the additional checks. This behavior is matching against the spec. However, currently, our JSObject::getOwnPropertySlot
+    does not propagate setter information. This is required to cache cacheable [[Put]] at (1) for CustomValue, CustomAccessor, and
+    Accessors. We also need to reconsider how to integrate static property setters to this mechanism. So, basically, this involves
+    large refactoring to renew our JSObject::put and JSObject::getOwnPropertySlot.
+    
+    To work-around for now, we add a new TypeInfo flag, HasPutPropertySecurityCheck . And adding this flag to DOM objects
+    that implements the addition checks. We also add doPutPropertySecurityCheck method hook to perform the check in JSObject.
+    When we found this flag at (1), we perform doPutPropertySecurityCheck to properly perform the checks.
+    
+    Since our JSObject::put code is old and it does not match against the spec now, we should refactor it largely. This is tracked separately in [1].
+    
+    [1]: https://bugs.webkit.org/show_bug.cgi?id=200562
+    
+    * runtime/ClassInfo.h:
+    * runtime/JSCJSValue.cpp:
+    (JSC::JSValue::putToPrimitive):
+    * runtime/JSCell.cpp:
+    (JSC::JSCell::doPutPropertySecurityCheck):
+    * runtime/JSCell.h:
+    * runtime/JSObject.cpp:
+    (JSC::JSObject::putInlineSlow):
+    (JSC::JSObject::getOwnPropertyDescriptor):
+    * runtime/JSObject.h:
+    (JSC::JSObject::doPutPropertySecurityCheck):
+    * runtime/JSTypeInfo.h:
+    (JSC::TypeInfo::hasPutPropertySecurityCheck const):
+    
+    Source/WebCore:
+    
+    Test: http/tests/security/cross-frame-access-object-put-optimization.html
+    
+    * bindings/js/JSDOMWindowCustom.cpp:
+    (WebCore::JSDOMWindow::doPutPropertySecurityCheck):
+    * bindings/js/JSLocationCustom.cpp:
+    (WebCore::JSLocation::doPutPropertySecurityCheck):
+    * bindings/scripts/CodeGeneratorJS.pm:
+    (GenerateHeader):
+    * bindings/scripts/test/JS/JSTestActiveDOMObject.h:
+    
+    LayoutTests:
+    
+    * http/tests/security/cross-frame-access-object-put-optimization-expected.txt: Added.
+    * http/tests/security/cross-frame-access-object-put-optimization.html: Added.
+    * http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html: Added.
+    
+    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@248494 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+    2019-08-09  Yusuke Suzuki  <ysuz...@apple.com>
+
+            Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive
+            https://bugs.webkit.org/show_bug.cgi?id=199864
+
+            Reviewed by Saam Barati.
+
+            * http/tests/security/cross-frame-access-object-put-optimization-expected.txt: Added.
+            * http/tests/security/cross-frame-access-object-put-optimization.html: Added.
+            * http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html: Added.
+
+2019-08-10  Babak Shafiei  <bshaf...@apple.com>
+
         Cherry-pick r248491. rdar://problem/54130644
 
     Don't allow cross-origin iframes to autofocus

Added: branches/safari-608.1-branch/LayoutTests/http/tests/security/cross-frame-access-object-put-optimization-expected.txt (0 => 248508)


--- branches/safari-608.1-branch/LayoutTests/http/tests/security/cross-frame-access-object-put-optimization-expected.txt	                        (rev 0)
+++ branches/safari-608.1-branch/LayoutTests/http/tests/security/cross-frame-access-object-put-optimization-expected.txt	2019-08-11 03:02:10 UTC (rev 248508)
@@ -0,0 +1,10 @@
+This tests that you can't get cross-origin object during [[Put]] operation.
+
+PASS data is not "null"
+PASS data is not "Cocoa"
+PASS data is not "null"
+PASS data is not "Cocoa"
+PASS: successfullyParsed should be 'true' and is.
+
+TEST COMPLETE
+

Added: branches/safari-608.1-branch/LayoutTests/http/tests/security/cross-frame-access-object-put-optimization.html (0 => 248508)


--- branches/safari-608.1-branch/LayoutTests/http/tests/security/cross-frame-access-object-put-optimization.html	                        (rev 0)
+++ branches/safari-608.1-branch/LayoutTests/http/tests/security/cross-frame-access-object-put-optimization.html	2019-08-11 03:02:10 UTC (rev 248508)
@@ -0,0 +1,54 @@
+<html>
+<head>
+    <script src=""
+    <script src=""
+    <script>
+        jsTestIsAsync = true;
+
+        data = ""
+
+        // Set up listener for message from iframe
+        addEventListener('message', function(event) {
+            if (event.data == "finishedLoad")
+                doTest();
+        }, false);
+
+        function turnLeakedException(object) {
+            try {
+                object.setter = {toString: function() { return {} } };
+            } catch (e) {
+                let crossOriginFunctionConstructor = e.constructor.constructor;
+                data = ""
+                    var element = document.getElementById("password");
+                    if (!element)
+                        return null;
+                    return element.textContent;`)();
+            }
+            shouldNotBeEqualToString(`data`, `null`);
+            shouldNotBeEqualToString(`data`, "Cocoa");
+            data = ""
+        }
+
+        doTest = function()
+        {
+            let targetWindow = document.getElementById("target").contentWindow;
+
+            // putInlineSlow
+            turnLeakedException({__proto__: targetWindow.location});
+
+            // putToPrimitive
+            num = 1337;
+            num.__proto__.__proto__ = targetWindow.location;
+            turnLeakedException(num);
+
+            finishJSTest();
+        }
+    </script>
+</head>
+<body>
+    <div>This tests that you can't get cross-origin object during [[Put]] operation.</div>
+    <iframe id="target" src=""
+    <pre id="console"></pre>
+    <script src=""
+</body>
+</html>

Added: branches/safari-608.1-branch/LayoutTests/http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html (0 => 248508)


--- branches/safari-608.1-branch/LayoutTests/http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html	                        (rev 0)
+++ branches/safari-608.1-branch/LayoutTests/http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html	2019-08-11 03:02:10 UTC (rev 248508)
@@ -0,0 +1,9 @@
+<h1 id="password">Cocoa</h1>
+<script>
+_onload_ = function() {
+    location.__defineSetter__('setter', function(value) {
+        throw new Error();
+    });
+    parent.postMessage("finishedLoad", "*");
+}
+</script>

Modified: branches/safari-608.1-branch/Source/_javascript_Core/ChangeLog (248507 => 248508)


--- branches/safari-608.1-branch/Source/_javascript_Core/ChangeLog	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/_javascript_Core/ChangeLog	2019-08-11 03:02:10 UTC (rev 248508)
@@ -1,3 +1,145 @@
+2019-08-10  Babak Shafiei  <bshaf...@apple.com>
+
+        Cherry-pick r248494. rdar://problem/54171879
+
+    Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive
+    https://bugs.webkit.org/show_bug.cgi?id=199864
+    
+    Reviewed by Saam Barati.
+    
+    Source/_javascript_Core:
+    
+    Our JSObject::put implementation is not correct in term of the spec. Our [[Put]] implementation is something like this.
+    
+        JSObject::put(object):
+            if (can-do-fast-path(object))
+                return fast-path(object);
+            // slow-path
+            do {
+                object-put-check-and-setter-calls(object); // (1)
+                object = object->prototype;
+            } while (is-object(object));
+            return do-put(object);
+    
+    Since JSObject::put is registered in the methodTable, the derived classes can override it. Some of classes are adding
+    extra checks to this put.
+    
+        Derived::put(object):
+            if (do-extra-check(object))
+                fail
+            return JSObject::put(object)
+    
+    The problem is that Derived::put is only called when the |this| object is the Derived class. When traversing [[Prototype]] in
+    JSObject::put, at (1), we do not perform the extra checks added in Derived::put even if `object` is Derived one. This means that
+    we skip the check.
+    
+    Currently, JSObject::put and WebCore checking mechanism are broken. JSObject::put should call getOwnPropertySlot at (1) to
+    perform the additional checks. This behavior is matching against the spec. However, currently, our JSObject::getOwnPropertySlot
+    does not propagate setter information. This is required to cache cacheable [[Put]] at (1) for CustomValue, CustomAccessor, and
+    Accessors. We also need to reconsider how to integrate static property setters to this mechanism. So, basically, this involves
+    large refactoring to renew our JSObject::put and JSObject::getOwnPropertySlot.
+    
+    To work-around for now, we add a new TypeInfo flag, HasPutPropertySecurityCheck . And adding this flag to DOM objects
+    that implements the addition checks. We also add doPutPropertySecurityCheck method hook to perform the check in JSObject.
+    When we found this flag at (1), we perform doPutPropertySecurityCheck to properly perform the checks.
+    
+    Since our JSObject::put code is old and it does not match against the spec now, we should refactor it largely. This is tracked separately in [1].
+    
+    [1]: https://bugs.webkit.org/show_bug.cgi?id=200562
+    
+    * runtime/ClassInfo.h:
+    * runtime/JSCJSValue.cpp:
+    (JSC::JSValue::putToPrimitive):
+    * runtime/JSCell.cpp:
+    (JSC::JSCell::doPutPropertySecurityCheck):
+    * runtime/JSCell.h:
+    * runtime/JSObject.cpp:
+    (JSC::JSObject::putInlineSlow):
+    (JSC::JSObject::getOwnPropertyDescriptor):
+    * runtime/JSObject.h:
+    (JSC::JSObject::doPutPropertySecurityCheck):
+    * runtime/JSTypeInfo.h:
+    (JSC::TypeInfo::hasPutPropertySecurityCheck const):
+    
+    Source/WebCore:
+    
+    Test: http/tests/security/cross-frame-access-object-put-optimization.html
+    
+    * bindings/js/JSDOMWindowCustom.cpp:
+    (WebCore::JSDOMWindow::doPutPropertySecurityCheck):
+    * bindings/js/JSLocationCustom.cpp:
+    (WebCore::JSLocation::doPutPropertySecurityCheck):
+    * bindings/scripts/CodeGeneratorJS.pm:
+    (GenerateHeader):
+    * bindings/scripts/test/JS/JSTestActiveDOMObject.h:
+    
+    LayoutTests:
+    
+    * http/tests/security/cross-frame-access-object-put-optimization-expected.txt: Added.
+    * http/tests/security/cross-frame-access-object-put-optimization.html: Added.
+    * http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html: Added.
+    
+    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@248494 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+    2019-08-09  Yusuke Suzuki  <ysuz...@apple.com>
+
+            Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive
+            https://bugs.webkit.org/show_bug.cgi?id=199864
+
+            Reviewed by Saam Barati.
+
+            Our JSObject::put implementation is not correct in term of the spec. Our [[Put]] implementation is something like this.
+
+                JSObject::put(object):
+                    if (can-do-fast-path(object))
+                        return fast-path(object);
+                    // slow-path
+                    do {
+                        object-put-check-and-setter-calls(object); // (1)
+                        object = object->prototype;
+                    } while (is-object(object));
+                    return do-put(object);
+
+            Since JSObject::put is registered in the methodTable, the derived classes can override it. Some of classes are adding
+            extra checks to this put.
+
+                Derived::put(object):
+                    if (do-extra-check(object))
+                        fail
+                    return JSObject::put(object)
+
+            The problem is that Derived::put is only called when the |this| object is the Derived class. When traversing [[Prototype]] in
+            JSObject::put, at (1), we do not perform the extra checks added in Derived::put even if `object` is Derived one. This means that
+            we skip the check.
+
+            Currently, JSObject::put and WebCore checking mechanism are broken. JSObject::put should call getOwnPropertySlot at (1) to
+            perform the additional checks. This behavior is matching against the spec. However, currently, our JSObject::getOwnPropertySlot
+            does not propagate setter information. This is required to cache cacheable [[Put]] at (1) for CustomValue, CustomAccessor, and
+            Accessors. We also need to reconsider how to integrate static property setters to this mechanism. So, basically, this involves
+            large refactoring to renew our JSObject::put and JSObject::getOwnPropertySlot.
+
+            To work-around for now, we add a new TypeInfo flag, HasPutPropertySecurityCheck . And adding this flag to DOM objects
+            that implements the addition checks. We also add doPutPropertySecurityCheck method hook to perform the check in JSObject.
+            When we found this flag at (1), we perform doPutPropertySecurityCheck to properly perform the checks.
+
+            Since our JSObject::put code is old and it does not match against the spec now, we should refactor it largely. This is tracked separately in [1].
+
+            [1]: https://bugs.webkit.org/show_bug.cgi?id=200562
+
+            * runtime/ClassInfo.h:
+            * runtime/JSCJSValue.cpp:
+            (JSC::JSValue::putToPrimitive):
+            * runtime/JSCell.cpp:
+            (JSC::JSCell::doPutPropertySecurityCheck):
+            * runtime/JSCell.h:
+            * runtime/JSObject.cpp:
+            (JSC::JSObject::putInlineSlow):
+            (JSC::JSObject::getOwnPropertyDescriptor):
+            * runtime/JSObject.h:
+            (JSC::JSObject::doPutPropertySecurityCheck):
+            * runtime/JSTypeInfo.h:
+            (JSC::TypeInfo::hasPutPropertySecurityCheck const):
+
 2019-07-24  Alan Coon  <alanc...@apple.com>
 
         Apply patch. rdar://problem/53457282

Modified: branches/safari-608.1-branch/Source/_javascript_Core/runtime/ClassInfo.h (248507 => 248508)


--- branches/safari-608.1-branch/Source/_javascript_Core/runtime/ClassInfo.h	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/_javascript_Core/runtime/ClassInfo.h	2019-08-11 03:02:10 UTC (rev 248508)
@@ -79,6 +79,9 @@
     using GetOwnPropertySlotByIndexFunctionPtr = bool (*)(JSObject*, ExecState*, unsigned, PropertySlot&);
     GetOwnPropertySlotByIndexFunctionPtr WTF_METHOD_TABLE_ENTRY(getOwnPropertySlotByIndex);
 
+    using DoPutPropertySecurityCheckFunctionPtr = void (*)(JSObject*, ExecState*, PropertyName, PutPropertySlot&);
+    DoPutPropertySecurityCheckFunctionPtr METHOD_TABLE_ENTRY(doPutPropertySecurityCheck);
+
     using ToThisFunctionPtr = JSValue (*)(JSCell*, ExecState*, ECMAMode);
     ToThisFunctionPtr WTF_METHOD_TABLE_ENTRY(toThis);
 
@@ -167,6 +170,7 @@
         &ClassName::deletePropertyByIndex, \
         &ClassName::getOwnPropertySlot, \
         &ClassName::getOwnPropertySlotByIndex, \
+        &ClassName::doPutPropertySecurityCheck, \
         &ClassName::toThis, \
         &ClassName::defaultValue, \
         &ClassName::getOwnPropertyNames, \

Modified: branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCJSValue.cpp (248507 => 248508)


--- branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCJSValue.cpp	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCJSValue.cpp	2019-08-11 03:02:10 UTC (rev 248508)
@@ -162,18 +162,27 @@
         return false;
     JSValue prototype;
     if (propertyName != vm.propertyNames->underscoreProto) {
-        for (; !obj->structure(vm)->hasReadOnlyOrGetterSetterPropertiesExcludingProto(); obj = asObject(prototype)) {
+        while (true) {
+            Structure* structure = obj->structure(vm);
+            if (structure->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || structure->typeInfo().hasPutPropertySecurityCheck())
+                break;
             prototype = obj->getPrototype(vm, exec);
             RETURN_IF_EXCEPTION(scope, false);
 
             if (prototype.isNull())
                 return typeError(exec, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
+            obj = asObject(prototype);
         }
     }
 
     for (; ; obj = asObject(prototype)) {
+        Structure* structure = obj->structure(vm);
+        if (UNLIKELY(structure->typeInfo().hasPutPropertySecurityCheck())) {
+            obj->methodTable(vm)->doPutPropertySecurityCheck(obj, exec, propertyName, slot);
+            RETURN_IF_EXCEPTION(scope, false);
+        }
         unsigned attributes;
-        PropertyOffset offset = obj->structure(vm)->get(vm, propertyName, attributes);
+        PropertyOffset offset = structure->get(vm, propertyName, attributes);
         if (offset != invalidOffset) {
             if (attributes & PropertyAttribute::ReadOnly)
                 return typeError(exec, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);

Modified: branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCell.cpp (248507 => 248508)


--- branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCell.cpp	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCell.cpp	2019-08-11 03:02:10 UTC (rev 248508)
@@ -212,6 +212,11 @@
     return false;
 }
 
+void JSCell::doPutPropertySecurityCheck(JSObject*, ExecState*, PropertyName, PutPropertySlot&)
+{
+    RELEASE_ASSERT_NOT_REACHED();
+}
+
 void JSCell::getOwnPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode)
 {
     RELEASE_ASSERT_NOT_REACHED();

Modified: branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCell.h (248507 => 248508)


--- branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCell.h	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSCell.h	2019-08-11 03:02:10 UTC (rev 248508)
@@ -265,6 +265,7 @@
     static bool defineOwnProperty(JSObject*, ExecState*, PropertyName, const PropertyDescriptor&, bool shouldThrow);
     static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&);
     static bool getOwnPropertySlotByIndex(JSObject*, ExecState*, unsigned propertyName, PropertySlot&);
+    static NO_RETURN_DUE_TO_CRASH void doPutPropertySecurityCheck(JSObject*, ExecState*, PropertyName, PutPropertySlot&);
 
 private:
     friend class LLIntOffsetsExtractor;

Modified: branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSObject.cpp (248507 => 248508)


--- branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSObject.cpp	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSObject.cpp	2019-08-11 03:02:10 UTC (rev 248508)
@@ -790,8 +790,13 @@
 
     JSObject* obj = this;
     for (;;) {
+        Structure* structure = obj->structure(vm);
+        if (UNLIKELY(structure->typeInfo().hasPutPropertySecurityCheck())) {
+            obj->methodTable(vm)->doPutPropertySecurityCheck(obj, exec, propertyName, slot);
+            RETURN_IF_EXCEPTION(scope, false);
+        }
         unsigned attributes;
-        PropertyOffset offset = obj->structure(vm)->get(vm, propertyName, attributes);
+        PropertyOffset offset = structure->get(vm, propertyName, attributes);
         if (isValidOffset(offset)) {
             if (attributes & PropertyAttribute::ReadOnly) {
                 ASSERT(this->prototypeChainMayInterceptStoreTo(vm, propertyName) || obj == this);
@@ -801,7 +806,7 @@
             JSValue gs = obj->getDirect(offset);
             if (gs.isGetterSetter()) {
                 // We need to make sure that we decide to cache this property before we potentially execute aribitrary JS.
-                if (!structure(vm)->isDictionary())
+                if (!this->structure(vm)->isDictionary())
                     slot.setCacheableSetter(obj, offset);
 
                 bool result = callSetter(exec, slot.thisValue(), gs, value, slot.isStrictMode() ? StrictMode : NotStrictMode);
@@ -3468,6 +3473,11 @@
     if (!result)
         return false;
 
+
+    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=200560
+    // This breaks the assumption that getOwnPropertySlot should return "own" property.
+    // We should fix DebuggerScope, ProxyObject etc. to remove this.
+    //
     // DebuggerScope::getOwnPropertySlot() (and possibly others) may return attributes from the prototype chain
     // but getOwnPropertyDescriptor() should only work for 'own' properties so we exit early if we detect that
     // the property is not an own property.

Modified: branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSObject.h (248507 => 248508)


--- branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSObject.h	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSObject.h	2019-08-11 03:02:10 UTC (rev 248508)
@@ -168,6 +168,7 @@
     static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&);
     JS_EXPORT_PRIVATE static bool getOwnPropertySlotByIndex(JSObject*, ExecState*, unsigned propertyName, PropertySlot&);
     bool getOwnPropertySlotInline(ExecState*, PropertyName, PropertySlot&);
+    static void doPutPropertySecurityCheck(JSObject*, ExecState*, PropertyName, PutPropertySlot&);
 
     // The key difference between this and getOwnPropertySlot is that getOwnPropertySlot
     // currently returns incorrect results for the DOM window (with non-own properties)
@@ -1404,6 +1405,10 @@
     return false;
 }
 
+ALWAYS_INLINE void JSObject::doPutPropertySecurityCheck(JSObject*, ExecState*, PropertyName, PutPropertySlot&)
+{
+}
+
 // It may seem crazy to inline a function this large but it makes a big difference
 // since this is function very hot in variable lookup
 template<bool checkNullStructure>

Modified: branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSTypeInfo.h (248507 => 248508)


--- branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSTypeInfo.h	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/_javascript_Core/runtime/JSTypeInfo.h	2019-08-11 03:02:10 UTC (rev 248508)
@@ -56,6 +56,7 @@
 static const unsigned GetOwnPropertySlotIsImpureForPropertyAbsence = 1 << 14;
 static const unsigned InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero = 1 << 15;
 static const unsigned StructureIsImmortal = 1 << 16;
+static const unsigned HasPutPropertySecurityCheck = 1 << 17;
 
 class TypeInfo {
 public:
@@ -96,6 +97,7 @@
     bool prohibitsPropertyCaching() const { return isSetOnFlags2(ProhibitsPropertyCaching); }
     bool getOwnPropertySlotIsImpure() const { return isSetOnFlags2(GetOwnPropertySlotIsImpure); }
     bool getOwnPropertySlotIsImpureForPropertyAbsence() const { return isSetOnFlags2(GetOwnPropertySlotIsImpureForPropertyAbsence); }
+    bool hasPutPropertySecurityCheck() const { return isSetOnFlags2(HasPutPropertySecurityCheck); }
     bool newImpurePropertyFiresWatchpoints() const { return isSetOnFlags2(NewImpurePropertyFiresWatchpoints); }
     bool isImmutablePrototypeExoticObject() const { return isSetOnFlags2(IsImmutablePrototypeExoticObject); }
     bool interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero() const { return isSetOnFlags2(InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero); }

Modified: branches/safari-608.1-branch/Source/WebCore/ChangeLog (248507 => 248508)


--- branches/safari-608.1-branch/Source/WebCore/ChangeLog	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/WebCore/ChangeLog	2019-08-11 03:02:10 UTC (rev 248508)
@@ -1,5 +1,105 @@
 2019-08-10  Babak Shafiei  <bshaf...@apple.com>
 
+        Cherry-pick r248494. rdar://problem/54171879
+
+    Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive
+    https://bugs.webkit.org/show_bug.cgi?id=199864
+    
+    Reviewed by Saam Barati.
+    
+    Source/_javascript_Core:
+    
+    Our JSObject::put implementation is not correct in term of the spec. Our [[Put]] implementation is something like this.
+    
+        JSObject::put(object):
+            if (can-do-fast-path(object))
+                return fast-path(object);
+            // slow-path
+            do {
+                object-put-check-and-setter-calls(object); // (1)
+                object = object->prototype;
+            } while (is-object(object));
+            return do-put(object);
+    
+    Since JSObject::put is registered in the methodTable, the derived classes can override it. Some of classes are adding
+    extra checks to this put.
+    
+        Derived::put(object):
+            if (do-extra-check(object))
+                fail
+            return JSObject::put(object)
+    
+    The problem is that Derived::put is only called when the |this| object is the Derived class. When traversing [[Prototype]] in
+    JSObject::put, at (1), we do not perform the extra checks added in Derived::put even if `object` is Derived one. This means that
+    we skip the check.
+    
+    Currently, JSObject::put and WebCore checking mechanism are broken. JSObject::put should call getOwnPropertySlot at (1) to
+    perform the additional checks. This behavior is matching against the spec. However, currently, our JSObject::getOwnPropertySlot
+    does not propagate setter information. This is required to cache cacheable [[Put]] at (1) for CustomValue, CustomAccessor, and
+    Accessors. We also need to reconsider how to integrate static property setters to this mechanism. So, basically, this involves
+    large refactoring to renew our JSObject::put and JSObject::getOwnPropertySlot.
+    
+    To work-around for now, we add a new TypeInfo flag, HasPutPropertySecurityCheck . And adding this flag to DOM objects
+    that implements the addition checks. We also add doPutPropertySecurityCheck method hook to perform the check in JSObject.
+    When we found this flag at (1), we perform doPutPropertySecurityCheck to properly perform the checks.
+    
+    Since our JSObject::put code is old and it does not match against the spec now, we should refactor it largely. This is tracked separately in [1].
+    
+    [1]: https://bugs.webkit.org/show_bug.cgi?id=200562
+    
+    * runtime/ClassInfo.h:
+    * runtime/JSCJSValue.cpp:
+    (JSC::JSValue::putToPrimitive):
+    * runtime/JSCell.cpp:
+    (JSC::JSCell::doPutPropertySecurityCheck):
+    * runtime/JSCell.h:
+    * runtime/JSObject.cpp:
+    (JSC::JSObject::putInlineSlow):
+    (JSC::JSObject::getOwnPropertyDescriptor):
+    * runtime/JSObject.h:
+    (JSC::JSObject::doPutPropertySecurityCheck):
+    * runtime/JSTypeInfo.h:
+    (JSC::TypeInfo::hasPutPropertySecurityCheck const):
+    
+    Source/WebCore:
+    
+    Test: http/tests/security/cross-frame-access-object-put-optimization.html
+    
+    * bindings/js/JSDOMWindowCustom.cpp:
+    (WebCore::JSDOMWindow::doPutPropertySecurityCheck):
+    * bindings/js/JSLocationCustom.cpp:
+    (WebCore::JSLocation::doPutPropertySecurityCheck):
+    * bindings/scripts/CodeGeneratorJS.pm:
+    (GenerateHeader):
+    * bindings/scripts/test/JS/JSTestActiveDOMObject.h:
+    
+    LayoutTests:
+    
+    * http/tests/security/cross-frame-access-object-put-optimization-expected.txt: Added.
+    * http/tests/security/cross-frame-access-object-put-optimization.html: Added.
+    * http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html: Added.
+    
+    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@248494 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+    2019-08-09  Yusuke Suzuki  <ysuz...@apple.com>
+
+            Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive
+            https://bugs.webkit.org/show_bug.cgi?id=199864
+
+            Reviewed by Saam Barati.
+
+            Test: http/tests/security/cross-frame-access-object-put-optimization.html
+
+            * bindings/js/JSDOMWindowCustom.cpp:
+            (WebCore::JSDOMWindow::doPutPropertySecurityCheck):
+            * bindings/js/JSLocationCustom.cpp:
+            (WebCore::JSLocation::doPutPropertySecurityCheck):
+            * bindings/scripts/CodeGeneratorJS.pm:
+            (GenerateHeader):
+            * bindings/scripts/test/JS/JSTestActiveDOMObject.h:
+
+2019-08-10  Babak Shafiei  <bshaf...@apple.com>
+
         Cherry-pick r248491. rdar://problem/54130644
 
     Don't allow cross-origin iframes to autofocus

Modified: branches/safari-608.1-branch/Source/WebCore/bindings/js/JSDOMWindowCustom.cpp (248507 => 248508)


--- branches/safari-608.1-branch/Source/WebCore/bindings/js/JSDOMWindowCustom.cpp	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/WebCore/bindings/js/JSDOMWindowCustom.cpp	2019-08-11 03:02:10 UTC (rev 248508)
@@ -264,6 +264,25 @@
     return Base::getOwnPropertySlotByIndex(thisObject, state, index, slot);
 }
 
+void JSDOMWindow::doPutPropertySecurityCheck(JSObject* cell, ExecState* state, PropertyName propertyName, PutPropertySlot&)
+{
+    VM& vm = state->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* thisObject = jsCast<JSDOMWindow*>(cell);
+    if (!thisObject->wrapped().frame())
+        return;
+
+    String errorMessage;
+    if (!BindingSecurity::shouldAllowAccessToDOMWindow(*state, thisObject->wrapped(), errorMessage)) {
+        // We only allow setting "location" attribute cross-origin.
+        if (propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().locationPublicName())
+            return;
+        throwSecurityError(*state, scope, errorMessage);
+        return;
+    }
+}
+
 bool JSDOMWindow::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
 {
     VM& vm = state->vm();

Modified: branches/safari-608.1-branch/Source/WebCore/bindings/js/JSLocationCustom.cpp (248507 => 248508)


--- branches/safari-608.1-branch/Source/WebCore/bindings/js/JSLocationCustom.cpp	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/WebCore/bindings/js/JSLocationCustom.cpp	2019-08-11 03:02:10 UTC (rev 248508)
@@ -126,6 +126,23 @@
     return false;
 }
 
+void JSLocation::doPutPropertySecurityCheck(JSObject* object, ExecState* state, PropertyName propertyName, PutPropertySlot&)
+{
+    auto* thisObject = jsCast<JSLocation*>(object);
+    ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+
+    VM& vm = state->vm();
+
+    // Always allow assigning to the whole location.
+    // However, alllowing assigning of pieces might inadvertently disclose parts of the original location.
+    // So fall through to the access check for those.
+    if (propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().hrefPublicName())
+        return;
+
+    // Block access and throw if there is a security error.
+    BindingSecurity::shouldAllowAccessToDOMWindow(state, thisObject->wrapped().window(), ThrowSecurityError);
+}
+
 bool JSLocation::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& putPropertySlot)
 {
     auto* thisObject = jsCast<JSLocation*>(cell);

Modified: branches/safari-608.1-branch/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm (248507 => 248508)


--- branches/safari-608.1-branch/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm	2019-08-11 03:02:10 UTC (rev 248508)
@@ -2639,6 +2639,11 @@
         push(@headerContent, "    static bool getOwnPropertySlotByIndex(JSC::JSObject*, JSC::ExecState*, unsigned propertyName, JSC::PropertySlot&);\n");
         $structureFlags{"JSC::InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero"} = 1;
     }
+
+    if ($interface->extendedAttributes->{CheckSecurity}) {
+        push(@headerContent, "    static void doPutPropertySecurityCheck(JSC::JSObject*, JSC::ExecState*, JSC::PropertyName, JSC::PutPropertySlot&);\n");
+        $structureFlags{"JSC::HasPutPropertySecurityCheck"} = 1;
+    }
     
     if (InstanceOverridesGetOwnPropertyNames($interface)) {
         push(@headerContent, "    static void getOwnPropertyNames(JSC::JSObject*, JSC::ExecState*, JSC::PropertyNameArray&, JSC::EnumerationMode = JSC::EnumerationMode());\n");

Modified: branches/safari-608.1-branch/Source/WebCore/bindings/scripts/test/JS/JSTestActiveDOMObject.h (248507 => 248508)


--- branches/safari-608.1-branch/Source/WebCore/bindings/scripts/test/JS/JSTestActiveDOMObject.h	2019-08-11 03:02:05 UTC (rev 248507)
+++ branches/safari-608.1-branch/Source/WebCore/bindings/scripts/test/JS/JSTestActiveDOMObject.h	2019-08-11 03:02:10 UTC (rev 248508)
@@ -39,6 +39,7 @@
     static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
     static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
     static TestActiveDOMObject* toWrapped(JSC::VM&, JSC::JSValue);
+    static void doPutPropertySecurityCheck(JSC::JSObject*, JSC::ExecState*, JSC::PropertyName, JSC::PutPropertySlot&);
     static void destroy(JSC::JSCell*);
 
     DECLARE_INFO;
@@ -51,7 +52,7 @@
     static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
     static void heapSnapshot(JSCell*, JSC::HeapSnapshotBuilder&);
 public:
-    static const unsigned StructureFlags = Base::StructureFlags | JSC::HasStaticPropertyTable;
+    static const unsigned StructureFlags = Base::StructureFlags | JSC::HasPutPropertySecurityCheck | JSC::HasStaticPropertyTable;
 protected:
     JSTestActiveDOMObject(JSC::Structure*, JSDOMGlobalObject&, Ref<TestActiveDOMObject>&&);
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to