Changeset 248494 in webkit
- Timestamp:
- Aug 9, 2019 6:20:22 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 3 added
- 14 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r248491 r248494 1 2019-08-09 Yusuke Suzuki <ysuzuki@apple.com> 2 3 Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive 4 https://bugs.webkit.org/show_bug.cgi?id=199864 5 6 Reviewed by Saam Barati. 7 8 * http/tests/security/cross-frame-access-object-put-optimization-expected.txt: Added. 9 * http/tests/security/cross-frame-access-object-put-optimization.html: Added. 10 * http/tests/security/resources/cross-frame-iframe-for-object-put-optimization-test.html: Added. 11 1 12 2019-08-09 Ali Juma <ajuma@chromium.org> 2 13 -
trunk/Source/JavaScriptCore/ChangeLog
r248462 r248494 1 2019-08-09 Yusuke Suzuki <ysuzuki@apple.com> 2 3 Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive 4 https://bugs.webkit.org/show_bug.cgi?id=199864 5 6 Reviewed by Saam Barati. 7 8 Our JSObject::put implementation is not correct in term of the spec. Our [[Put]] implementation is something like this. 9 10 JSObject::put(object): 11 if (can-do-fast-path(object)) 12 return fast-path(object); 13 // slow-path 14 do { 15 object-put-check-and-setter-calls(object); // (1) 16 object = object->prototype; 17 } while (is-object(object)); 18 return do-put(object); 19 20 Since JSObject::put is registered in the methodTable, the derived classes can override it. Some of classes are adding 21 extra checks to this put. 22 23 Derived::put(object): 24 if (do-extra-check(object)) 25 fail 26 return JSObject::put(object) 27 28 The problem is that Derived::put is only called when the |this| object is the Derived class. When traversing [[Prototype]] in 29 JSObject::put, at (1), we do not perform the extra checks added in Derived::put even if `object` is Derived one. This means that 30 we skip the check. 31 32 Currently, JSObject::put and WebCore checking mechanism are broken. JSObject::put should call getOwnPropertySlot at (1) to 33 perform the additional checks. This behavior is matching against the spec. However, currently, our JSObject::getOwnPropertySlot 34 does not propagate setter information. This is required to cache cacheable [[Put]] at (1) for CustomValue, CustomAccessor, and 35 Accessors. We also need to reconsider how to integrate static property setters to this mechanism. So, basically, this involves 36 large refactoring to renew our JSObject::put and JSObject::getOwnPropertySlot. 37 38 To work-around for now, we add a new TypeInfo flag, HasPutPropertySecurityCheck . And adding this flag to DOM objects 39 that implements the addition checks. We also add doPutPropertySecurityCheck method hook to perform the check in JSObject. 40 When we found this flag at (1), we perform doPutPropertySecurityCheck to properly perform the checks. 41 42 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]. 43 44 [1]: https://bugs.webkit.org/show_bug.cgi?id=200562 45 46 * runtime/ClassInfo.h: 47 * runtime/JSCJSValue.cpp: 48 (JSC::JSValue::putToPrimitive): 49 * runtime/JSCell.cpp: 50 (JSC::JSCell::doPutPropertySecurityCheck): 51 * runtime/JSCell.h: 52 * runtime/JSObject.cpp: 53 (JSC::JSObject::putInlineSlow): 54 (JSC::JSObject::getOwnPropertyDescriptor): 55 * runtime/JSObject.h: 56 (JSC::JSObject::doPutPropertySecurityCheck): 57 * runtime/JSTypeInfo.h: 58 (JSC::TypeInfo::hasPutPropertySecurityCheck const): 59 1 60 2019-08-08 Per Arne Vollan <pvollan@apple.com> 2 61 -
trunk/Source/JavaScriptCore/runtime/ClassInfo.h
r248192 r248494 72 72 using GetOwnPropertySlotByIndexFunctionPtr = bool (*)(JSObject*, ExecState*, unsigned, PropertySlot&); 73 73 GetOwnPropertySlotByIndexFunctionPtr METHOD_TABLE_ENTRY(getOwnPropertySlotByIndex); 74 75 using DoPutPropertySecurityCheckFunctionPtr = void (*)(JSObject*, ExecState*, PropertyName, PutPropertySlot&); 76 DoPutPropertySecurityCheckFunctionPtr METHOD_TABLE_ENTRY(doPutPropertySecurityCheck); 74 77 75 78 using ToThisFunctionPtr = JSValue (*)(JSCell*, ExecState*, ECMAMode); … … 161 164 &ClassName::getOwnPropertySlot, \ 162 165 &ClassName::getOwnPropertySlotByIndex, \ 166 &ClassName::doPutPropertySecurityCheck, \ 163 167 &ClassName::toThis, \ 164 168 &ClassName::defaultValue, \ -
trunk/Source/JavaScriptCore/runtime/JSCJSValue.cpp
r246490 r248494 163 163 JSValue prototype; 164 164 if (propertyName != vm.propertyNames->underscoreProto) { 165 for (; !obj->structure(vm)->hasReadOnlyOrGetterSetterPropertiesExcludingProto(); obj = asObject(prototype)) { 165 while (true) { 166 Structure* structure = obj->structure(vm); 167 if (structure->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || structure->typeInfo().hasPutPropertySecurityCheck()) 168 break; 166 169 prototype = obj->getPrototype(vm, exec); 167 170 RETURN_IF_EXCEPTION(scope, false); … … 169 172 if (prototype.isNull()) 170 173 return typeError(exec, scope, slot.isStrictMode(), ReadonlyPropertyWriteError); 174 obj = asObject(prototype); 171 175 } 172 176 } 173 177 174 178 for (; ; obj = asObject(prototype)) { 179 Structure* structure = obj->structure(vm); 180 if (UNLIKELY(structure->typeInfo().hasPutPropertySecurityCheck())) { 181 obj->methodTable(vm)->doPutPropertySecurityCheck(obj, exec, propertyName, slot); 182 RETURN_IF_EXCEPTION(scope, false); 183 } 175 184 unsigned attributes; 176 PropertyOffset offset = obj->structure(vm)->get(vm, propertyName, attributes);185 PropertyOffset offset = structure->get(vm, propertyName, attributes); 177 186 if (offset != invalidOffset) { 178 187 if (attributes & PropertyAttribute::ReadOnly) -
trunk/Source/JavaScriptCore/runtime/JSCell.cpp
r233765 r248494 213 213 } 214 214 215 void JSCell::doPutPropertySecurityCheck(JSObject*, ExecState*, PropertyName, PutPropertySlot&) 216 { 217 RELEASE_ASSERT_NOT_REACHED(); 218 } 219 215 220 void JSCell::getOwnPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode) 216 221 { -
trunk/Source/JavaScriptCore/runtime/JSCell.h
r240965 r248494 266 266 static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&); 267 267 static bool getOwnPropertySlotByIndex(JSObject*, ExecState*, unsigned propertyName, PropertySlot&); 268 static NO_RETURN_DUE_TO_CRASH void doPutPropertySecurityCheck(JSObject*, ExecState*, PropertyName, PutPropertySlot&); 268 269 269 270 private: -
trunk/Source/JavaScriptCore/runtime/JSObject.cpp
r246084 r248494 791 791 JSObject* obj = this; 792 792 for (;;) { 793 Structure* structure = obj->structure(vm); 794 if (UNLIKELY(structure->typeInfo().hasPutPropertySecurityCheck())) { 795 obj->methodTable(vm)->doPutPropertySecurityCheck(obj, exec, propertyName, slot); 796 RETURN_IF_EXCEPTION(scope, false); 797 } 793 798 unsigned attributes; 794 PropertyOffset offset = obj->structure(vm)->get(vm, propertyName, attributes);799 PropertyOffset offset = structure->get(vm, propertyName, attributes); 795 800 if (isValidOffset(offset)) { 796 801 if (attributes & PropertyAttribute::ReadOnly) { … … 802 807 if (gs.isGetterSetter()) { 803 808 // We need to make sure that we decide to cache this property before we potentially execute aribitrary JS. 804 if (! structure(vm)->isDictionary())809 if (!this->structure(vm)->isDictionary()) 805 810 slot.setCacheableSetter(obj, offset); 806 811 … … 3469 3474 return false; 3470 3475 3476 3477 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=200560 3478 // This breaks the assumption that getOwnPropertySlot should return "own" property. 3479 // We should fix DebuggerScope, ProxyObject etc. to remove this. 3480 // 3471 3481 // DebuggerScope::getOwnPropertySlot() (and possibly others) may return attributes from the prototype chain 3472 3482 // but getOwnPropertyDescriptor() should only work for 'own' properties so we exit early if we detect that -
trunk/Source/JavaScriptCore/runtime/JSObject.h
r247386 r248494 169 169 JS_EXPORT_PRIVATE static bool getOwnPropertySlotByIndex(JSObject*, ExecState*, unsigned propertyName, PropertySlot&); 170 170 bool getOwnPropertySlotInline(ExecState*, PropertyName, PropertySlot&); 171 static void doPutPropertySecurityCheck(JSObject*, ExecState*, PropertyName, PutPropertySlot&); 171 172 172 173 // The key difference between this and getOwnPropertySlot is that getOwnPropertySlot … … 1405 1406 } 1406 1407 1408 ALWAYS_INLINE void JSObject::doPutPropertySecurityCheck(JSObject*, ExecState*, PropertyName, PutPropertySlot&) 1409 { 1410 } 1411 1407 1412 // It may seem crazy to inline a function this large but it makes a big difference 1408 1413 // since this is function very hot in variable lookup -
trunk/Source/JavaScriptCore/runtime/JSTypeInfo.h
r240951 r248494 57 57 static const unsigned InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero = 1 << 15; 58 58 static const unsigned StructureIsImmortal = 1 << 16; 59 static const unsigned HasPutPropertySecurityCheck = 1 << 17; 59 60 60 61 class TypeInfo { … … 97 98 bool getOwnPropertySlotIsImpure() const { return isSetOnFlags2(GetOwnPropertySlotIsImpure); } 98 99 bool getOwnPropertySlotIsImpureForPropertyAbsence() const { return isSetOnFlags2(GetOwnPropertySlotIsImpureForPropertyAbsence); } 100 bool hasPutPropertySecurityCheck() const { return isSetOnFlags2(HasPutPropertySecurityCheck); } 99 101 bool newImpurePropertyFiresWatchpoints() const { return isSetOnFlags2(NewImpurePropertyFiresWatchpoints); } 100 102 bool isImmutablePrototypeExoticObject() const { return isSetOnFlags2(IsImmutablePrototypeExoticObject); } -
trunk/Source/WebCore/ChangeLog
r248493 r248494 1 2019-08-09 Yusuke Suzuki <ysuzuki@apple.com> 2 3 Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive 4 https://bugs.webkit.org/show_bug.cgi?id=199864 5 6 Reviewed by Saam Barati. 7 8 Test: http/tests/security/cross-frame-access-object-put-optimization.html 9 10 * bindings/js/JSDOMWindowCustom.cpp: 11 (WebCore::JSDOMWindow::doPutPropertySecurityCheck): 12 * bindings/js/JSLocationCustom.cpp: 13 (WebCore::JSLocation::doPutPropertySecurityCheck): 14 * bindings/scripts/CodeGeneratorJS.pm: 15 (GenerateHeader): 16 * bindings/scripts/test/JS/JSTestActiveDOMObject.h: 17 1 18 2019-08-09 Saam Barati <sbarati@apple.com> 2 19 -
trunk/Source/WebCore/bindings/js/JSDOMWindowCustom.cpp
r248155 r248494 267 267 } 268 268 269 void JSDOMWindow::doPutPropertySecurityCheck(JSObject* cell, ExecState* state, PropertyName propertyName, PutPropertySlot&) 270 { 271 VM& vm = state->vm(); 272 auto scope = DECLARE_THROW_SCOPE(vm); 273 274 auto* thisObject = jsCast<JSDOMWindow*>(cell); 275 if (!thisObject->wrapped().frame()) 276 return; 277 278 String errorMessage; 279 if (!BindingSecurity::shouldAllowAccessToDOMWindow(*state, thisObject->wrapped(), errorMessage)) { 280 // We only allow setting "location" attribute cross-origin. 281 if (propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().locationPublicName()) 282 return; 283 throwSecurityError(*state, scope, errorMessage); 284 return; 285 } 286 } 287 269 288 bool JSDOMWindow::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& slot) 270 289 { -
trunk/Source/WebCore/bindings/js/JSLocationCustom.cpp
r245249 r248494 127 127 } 128 128 129 void JSLocation::doPutPropertySecurityCheck(JSObject* object, ExecState* state, PropertyName propertyName, PutPropertySlot&) 130 { 131 auto* thisObject = jsCast<JSLocation*>(object); 132 ASSERT_GC_OBJECT_INHERITS(thisObject, info()); 133 134 VM& vm = state->vm(); 135 136 // Always allow assigning to the whole location. 137 // However, alllowing assigning of pieces might inadvertently disclose parts of the original location. 138 // So fall through to the access check for those. 139 if (propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().hrefPublicName()) 140 return; 141 142 // Block access and throw if there is a security error. 143 BindingSecurity::shouldAllowAccessToDOMWindow(state, thisObject->wrapped().window(), ThrowSecurityError); 144 } 145 129 146 bool JSLocation::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& putPropertySlot) 130 147 { -
trunk/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm
r248276 r248494 2655 2655 push(@headerContent, " static bool getOwnPropertySlotByIndex(JSC::JSObject*, JSC::ExecState*, unsigned propertyName, JSC::PropertySlot&);\n"); 2656 2656 $structureFlags{"JSC::InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero"} = 1; 2657 } 2658 2659 if ($interface->extendedAttributes->{CheckSecurity}) { 2660 push(@headerContent, " static void doPutPropertySecurityCheck(JSC::JSObject*, JSC::ExecState*, JSC::PropertyName, JSC::PutPropertySlot&);\n"); 2661 $structureFlags{"JSC::HasPutPropertySecurityCheck"} = 1; 2657 2662 } 2658 2663 -
trunk/Source/WebCore/bindings/scripts/test/JS/JSTestActiveDOMObject.h
r239191 r248494 40 40 static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&); 41 41 static TestActiveDOMObject* toWrapped(JSC::VM&, JSC::JSValue); 42 static void doPutPropertySecurityCheck(JSC::JSObject*, JSC::ExecState*, JSC::PropertyName, JSC::PutPropertySlot&); 42 43 static void destroy(JSC::JSCell*); 43 44 … … 52 53 static void heapSnapshot(JSCell*, JSC::HeapSnapshotBuilder&); 53 54 public: 54 static const unsigned StructureFlags = Base::StructureFlags | JSC::Has StaticPropertyTable;55 static const unsigned StructureFlags = Base::StructureFlags | JSC::HasPutPropertySecurityCheck | JSC::HasStaticPropertyTable; 55 56 protected: 56 57 JSTestActiveDOMObject(JSC::Structure*, JSDOMGlobalObject&, Ref<TestActiveDOMObject>&&);
Note: See TracChangeset
for help on using the changeset viewer.