diff options
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/bindings/codegen/CodegenRust.py | 232 | ||||
-rw-r--r-- | components/script/dom/bindings/codegen/Configuration.py | 8 | ||||
-rw-r--r-- | components/script/dom/bindings/proxyhandler.rs | 344 | ||||
-rw-r--r-- | components/script/dom/windowproxy.rs | 3 |
4 files changed, 557 insertions, 30 deletions
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 4de72f74db4..0819e781258 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -1656,10 +1656,15 @@ class MethodDefiner(PropertyDefiner): """ A class for defining methods on a prototype object. """ - def __init__(self, descriptor, name, static, unforgeable): + def __init__(self, descriptor, name, static, unforgeable, crossorigin=False): assert not (static and unforgeable) + assert not (static and crossorigin) + assert not (unforgeable and crossorigin) PropertyDefiner.__init__(self, descriptor, name) + # TODO: Separate the `(static, unforgeable, crossorigin) = (False, False, True)` case + # to a separate class or something. + # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822 # We should be able to check for special operations without an # identifier. For now we check if the name starts with __ @@ -1668,14 +1673,15 @@ class MethodDefiner(PropertyDefiner): if not descriptor.interface.isCallback() or static: methods = [m for m in descriptor.interface.members if m.isMethod() and m.isStatic() == static + and (bool(m.getExtendedAttribute("CrossOriginCallable")) or not crossorigin) and not m.isIdentifierLess() - and MemberIsUnforgeable(m, descriptor) == unforgeable] + and (MemberIsUnforgeable(m, descriptor) == unforgeable or crossorigin)] else: methods = [] self.regular = [{"name": m.identifier.name, "methodInfo": not m.isStatic(), "length": methodLength(m), - "flags": "JSPROP_ENUMERATE", + "flags": "JSPROP_READONLY" if crossorigin else "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor)} for m in methods] @@ -1693,6 +1699,7 @@ class MethodDefiner(PropertyDefiner): # neither. if (not static and not unforgeable + and not crossorigin and descriptor.supportsIndexedProperties()): # noqa if hasIterator(methods, self.regular): # noqa raise TypeError("Cannot have indexed getter/attr on " @@ -1709,7 +1716,7 @@ class MethodDefiner(PropertyDefiner): # Generate the keys/values/entries aliases for value iterables. maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable - if (not static and not unforgeable + if (not static and not unforgeable and not crossorigin and maplikeOrSetlikeOrIterable and maplikeOrSetlikeOrIterable.isIterable() and maplikeOrSetlikeOrIterable.isValueIterator()): @@ -1754,7 +1761,7 @@ class MethodDefiner(PropertyDefiner): }) isUnforgeableInterface = bool(descriptor.interface.getExtendedAttribute("Unforgeable")) - if not static and unforgeable == isUnforgeableInterface: + if not static and unforgeable == isUnforgeableInterface and not crossorigin: stringifier = descriptor.operations['Stringifier'] if stringifier: self.regular.append({ @@ -1765,6 +1772,7 @@ class MethodDefiner(PropertyDefiner): "condition": PropertyDefiner.getControllingCondition(stringifier, descriptor) }) self.unforgeable = unforgeable + self.crossorigin = crossorigin def generateArray(self, array, name): if len(array) == 0: @@ -1796,36 +1804,59 @@ class MethodDefiner(PropertyDefiner): jitinfo = "ptr::null()" accessor = 'Some(%s)' % m.get("nativeName", m["name"]) if m["name"].startswith("@@"): + assert not self.crossorigin name = 'JSPropertySpec_Name { symbol_: SymbolCode::%s as usize + 1 }' % m["name"][2:] else: name = ('JSPropertySpec_Name { string_: %s as *const u8 as *const libc::c_char }' % str_to_const_array(m["name"])) return (name, accessor, jitinfo, m["length"], flags, selfHostedName) - return self.generateGuardedArray( - array, name, + specTemplate = ( ' JSFunctionSpec {\n' ' name: %s,\n' ' call: JSNativeWrapper { op: %s, info: %s },\n' ' nargs: %s,\n' ' flags: (%s) as u16,\n' ' selfHostedName: %s\n' - ' }', + ' }') + specTerminator = ( ' JSFunctionSpec {\n' ' name: JSPropertySpec_Name { string_: ptr::null() },\n' ' call: JSNativeWrapper { op: None, info: ptr::null() },\n' ' nargs: 0,\n' ' flags: 0,\n' ' selfHostedName: ptr::null()\n' - ' }', - 'JSFunctionSpec', - condition, specData) + ' }') + + if self.crossorigin: + groups = groupby(array, lambda m: condition(m, self.descriptor)) + assert len(list(groups)) == 1 # can't handle mixed condition + elems = [specTemplate % specData(m) for m in array] + return dedent( + """ + const %s: &[JSFunctionSpec] = &[ + %s, + %s, + ]; + """) % (name, ',\n'.join(elems), specTerminator) + else: + return self.generateGuardedArray( + array, name, + specTemplate, specTerminator, + 'JSFunctionSpec', + condition, specData) class AttrDefiner(PropertyDefiner): - def __init__(self, descriptor, name, static, unforgeable): + def __init__(self, descriptor, name, static, unforgeable, crossorigin=False): assert not (static and unforgeable) + assert not (static and crossorigin) + assert not (unforgeable and crossorigin) PropertyDefiner.__init__(self, descriptor, name) + + # TODO: Separate the `(static, unforgeable, crossorigin) = (False, False, True)` case + # to a separate class or something. + self.name = name self.descriptor = descriptor self.regular = [ @@ -1837,12 +1868,16 @@ class AttrDefiner(PropertyDefiner): } for m in descriptor.interface.members if m.isAttr() and m.isStatic() == static - and MemberIsUnforgeable(m, descriptor) == unforgeable + and (MemberIsUnforgeable(m, descriptor) == unforgeable or crossorigin) + and (not crossorigin + or m.getExtendedAttribute("CrossOriginReadable") + or m.getExtendedAttribute("CrossOriginWritable")) ] self.static = static self.unforgeable = unforgeable + self.crossorigin = crossorigin - if not static and not unforgeable and not ( + if not static and not unforgeable and not crossorigin and not ( descriptor.interface.isNamespace() or descriptor.interface.isCallback() ): self.regular.append({ @@ -1858,6 +1893,10 @@ class AttrDefiner(PropertyDefiner): def getter(attr): attr = attr['attr'] + + if self.crossorigin and not attr.getExtendedAttribute("CrossOriginReadable"): + return "JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }" + if self.static: accessor = 'get_' + self.descriptor.internalNameFor(attr.identifier.name) jitinfo = "0 as *const JSJitInfo" @@ -1874,8 +1913,10 @@ class AttrDefiner(PropertyDefiner): def setter(attr): attr = attr['attr'] - if (attr.readonly and not attr.getExtendedAttribute("PutForwards") - and not attr.getExtendedAttribute("Replaceable")): + + if ((attr.readonly and not attr.getExtendedAttribute("PutForwards") + and not attr.getExtendedAttribute("Replaceable")) + or (self.crossorigin and not attr.getExtendedAttribute("CrossOriginReadable"))): return "JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }" if self.static: @@ -1941,12 +1982,24 @@ class AttrDefiner(PropertyDefiner): } """ - return self.generateGuardedArray( - array, name, - template, - ' JSPropertySpec::ZERO', - 'JSPropertySpec', - condition, specData) + if self.crossorigin: + groups = groupby(array, lambda m: condition(m, self.descriptor)) + assert len(list(groups)) == 1 # can't handle mixed condition + elems = [template(m) % specData(m) for m in array] + return dedent( + """ + const %s: &[JSPropertySpec] = &[ + %s, + JSPropertySpec::ZERO, + ]; + """) % (name, ',\n'.join(elems)) + else: + return self.generateGuardedArray( + array, name, + template, + ' JSPropertySpec::ZERO', + 'JSPropertySpec', + condition, specData) class ConstDefiner(PropertyDefiner): @@ -3071,6 +3124,25 @@ class PropertyArrays(): return define +class CGCrossOriginProperties(CGThing): + def __init__(self, descriptor): + self.methods = MethodDefiner(descriptor, "CrossOriginMethods", static=False, + unforgeable=False, crossorigin=True) + self.attributes = AttrDefiner(descriptor, "CrossOriginAttributes", static=False, + unforgeable=False, crossorigin=True) + + def define(self): + return str(self.methods) + str(self.attributes) + dedent( + """ + const CROSS_ORIGIN_PROPERTIES: proxyhandler::CrossOriginProperties = + proxyhandler::CrossOriginProperties { + attributes: sCrossOriginAttributes, + methods: sCrossOriginMethods, + }; + """ + ) + + class CGCollectJSONAttributesMethod(CGAbstractMethod): """ Generate the CollectJSONAttributes method for an interface descriptor @@ -3451,11 +3523,12 @@ class CGDefineProxyHandler(CGAbstractMethod): def definition_body(self): customDefineProperty = 'proxyhandler::define_property' - if self.descriptor.operations['IndexedSetter'] or self.descriptor.operations['NamedSetter']: + if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['IndexedSetter'] or \ + self.descriptor.operations['NamedSetter']: customDefineProperty = 'defineProperty' customDelete = 'proxyhandler::delete' - if self.descriptor.operations['NamedDeleter']: + if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['NamedDeleter']: customDelete = 'delete' getOwnEnumerablePropertyKeys = "own_property_keys" @@ -5353,6 +5426,26 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): indexedGetter = self.descriptor.operations['IndexedGetter'] get = "let cx = SafeJSContext::from_ptr(cx);\n" + + if self.descriptor.isMaybeCrossOriginObject(): + get += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + if !proxyhandler::cross_origin_get_own_property_helper( + cx, proxy, &CROSS_ORIGIN_PROPERTIES, id, desc + ) { + return false; + } + if desc.obj.is_null() { + return proxyhandler::cross_origin_property_fallback(cx, proxy, id, desc); + } + return true; + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if indexedGetter: get += "let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" @@ -5450,6 +5543,17 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): def getBody(self): set = "let cx = SafeJSContext::from_ptr(cx);\n" + if self.descriptor.isMaybeCrossOriginObject(): + set += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::report_cross_origin_denial(cx, id, "define"); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + indexedSetter = self.descriptor.operations['IndexedSetter'] if indexedSetter: set += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" @@ -5497,6 +5601,18 @@ class CGDOMJSProxyHandler_delete(CGAbstractExternMethod): def getBody(self): set = "let cx = SafeJSContext::from_ptr(cx);\n" + + if self.descriptor.isMaybeCrossOriginObject(): + set += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::report_cross_origin_denial(cx, id, "delete"); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if self.descriptor.operations['NamedDeleter']: if self.descriptor.hasUnforgeableMembers: raise TypeError("Can't handle a deleter on an interface that has " @@ -5524,6 +5640,17 @@ class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod): let unwrapped_proxy = UnwrapProxy(proxy); """) + if self.descriptor.isMaybeCrossOriginObject(): + body += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::cross_origin_own_property_keys(cx, proxy, &CROSS_ORIGIN_PROPERTIES, props); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if self.descriptor.operations['IndexedGetter']: body += dedent( """ @@ -5583,6 +5710,18 @@ class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod): let unwrapped_proxy = UnwrapProxy(proxy); """) + if self.descriptor.isMaybeCrossOriginObject(): + body += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + // There are no enumerable cross-origin props, so we're done. + return true; + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if self.descriptor.operations['IndexedGetter']: body += dedent( """ @@ -5621,6 +5760,18 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): def getBody(self): indexedGetter = self.descriptor.operations['IndexedGetter'] indexed = "let cx = SafeJSContext::from_ptr(cx);\n" + + if self.descriptor.isMaybeCrossOriginObject(): + indexed += dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::cross_origin_has_own(cx, proxy, &CROSS_ORIGIN_PROPERTIES, id, bp); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + if indexedGetter: indexed += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" + "if let Some(index) = index {\n" @@ -5682,6 +5833,18 @@ class CGDOMJSProxyHandler_get(CGAbstractExternMethod): # https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty def getBody(self): + if self.descriptor.isMaybeCrossOriginObject(): + maybeCrossOriginGet = dedent( + """ + if !proxyhandler::is_platform_object_same_origin(cx, proxy) { + return proxyhandler::cross_origin_get(cx, proxy, receiver, id, vp); + } + + // Safe to enter the Realm of proxy now. + let _ac = JSAutoRealm::new(*cx, proxy.get()); + """) + else: + maybeCrossOriginGet = "" getFromExpando = """\ rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); get_expando_object(proxy, expando.handle_mut()); @@ -5737,6 +5900,9 @@ if !expando.is_null() { //MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), //"Should not have a XrayWrapper here"); let cx = SafeJSContext::from_ptr(cx); + +%s + let proxy_lt = Handle::from_raw(proxy); let vp_lt = MutableHandle::from_raw(vp); let id_lt = Handle::from_raw(id); @@ -5753,7 +5919,7 @@ if found { } %s vp.set(UndefinedValue()); -return true;""" % (getIndexedOrExpando, getNamed) +return true;""" % (maybeCrossOriginGet, getIndexedOrExpando, getNamed) def definition_body(self): return CGGeneric(self.getBody()) @@ -6392,6 +6558,9 @@ class CGDescriptor(CGThing): if descriptor.proxy: cgThings.append(CGDefineProxyHandler(descriptor)) + if descriptor.isMaybeCrossOriginObject(): + cgThings.append(CGCrossOriginProperties(descriptor)) + properties = PropertyArrays(descriptor) if defaultToJSONMethod: @@ -6410,15 +6579,24 @@ class CGDescriptor(CGThing): cgThings.append(CGDOMJSProxyHandler_get(descriptor)) cgThings.append(CGDOMJSProxyHandler_hasOwn(descriptor)) - if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']: + if descriptor.isMaybeCrossOriginObject() or descriptor.operations['IndexedSetter'] or \ + descriptor.operations['NamedSetter']: cgThings.append(CGDOMJSProxyHandler_defineProperty(descriptor)) # We want to prevent indexed deleters from compiling at all. assert not descriptor.operations['IndexedDeleter'] - if descriptor.operations['NamedDeleter']: + if descriptor.isMaybeCrossOriginObject() or descriptor.operations['NamedDeleter']: cgThings.append(CGDOMJSProxyHandler_delete(descriptor)) + if descriptor.isMaybeCrossOriginObject(): + # TODO: CGDOMJSProxyHandler_getPrototype(descriptor), + # TODO: CGDOMJSProxyHandler_getPrototypeIfOrdinary(descriptor), + # TODO: CGDOMJSProxyHandler_setPrototype(descriptor), + # TODO: CGDOMJSProxyHandler_setImmutablePrototype(descriptor), + # TODO: CGDOMJSProxyHandler_set(descriptor), + pass + # cgThings.append(CGDOMJSProxyHandler(descriptor)) # cgThings.append(CGIsMethod(descriptor)) pass diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index b92f68af3b9..cf6885be265 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -299,6 +299,9 @@ class Descriptor(DescriptorProvider): if iface: iface.setUserData('hasConcreteDescendant', True) + if self.isMaybeCrossOriginObject(): + self.proxy = True + if self.proxy: iface = self.interface while iface.parent: @@ -404,6 +407,11 @@ class Descriptor(DescriptorProvider): def supportsIndexedProperties(self): return self.operations['IndexedGetter'] is not None + def isMaybeCrossOriginObject(self): + # If we're isGlobal and have cross-origin members, we're a Window, and + # that's not a cross-origin object. The WindowProxy is. + return self.concrete and self.interface.hasCrossOriginMembers and not self.isGlobal() + def hasDescendants(self): return (self.interface.getUserData("hasConcreteDescendant", False) or self.interface.getUserData("hasProxyDescendant", False)) diff --git a/components/script/dom/bindings/proxyhandler.rs b/components/script/dom/bindings/proxyhandler.rs index 11913c03642..b851d8f5ee5 100644 --- a/components/script/dom/bindings/proxyhandler.rs +++ b/components/script/dom/bindings/proxyhandler.rs @@ -7,24 +7,43 @@ #![deny(missing_docs)] use crate::dom::bindings::conversions::is_dom_proxy; +use crate::dom::bindings::error::{throw_dom_exception, Error}; +use crate::dom::bindings::principals::ServoJSPrincipalsRef; use crate::dom::bindings::utils::delete_property_by_id; -use js::glue::GetProxyHandlerFamily; +use crate::dom::globalscope::GlobalScope; +use crate::realms::{AlreadyInRealm, InRealm}; +use crate::script_runtime::JSContext as SafeJSContext; +use js::conversions::ToJSValConvertible; +use js::glue::{ + GetProxyHandler, GetProxyHandlerFamily, InvokeGetOwnPropertyDescriptor, RUST_SYMBOL_TO_JSID, +}; use js::glue::{GetProxyPrivate, SetProxyPrivate}; +use js::jsapi; use js::jsapi::GetStaticPrototype; use js::jsapi::Handle as RawHandle; use js::jsapi::HandleId as RawHandleId; use js::jsapi::HandleObject as RawHandleObject; +use js::jsapi::HandleValue as RawHandleValue; +use js::jsapi::JS_AtomizeAndPinString; use js::jsapi::JS_DefinePropertyById; +use js::jsapi::JS_GetOwnPropertyDescriptorById; +use js::jsapi::JS_IsExceptionPending; +use js::jsapi::MutableHandle as RawMutableHandle; +use js::jsapi::MutableHandleIdVector as RawMutableHandleIdVector; use js::jsapi::MutableHandleObject as RawMutableHandleObject; +use js::jsapi::MutableHandleValue as RawMutableHandleValue; use js::jsapi::ObjectOpResult; +use js::jsapi::{jsid, GetObjectRealmOrNull, GetRealmPrincipals, JSFunctionSpec, JSPropertySpec}; use js::jsapi::{DOMProxyShadowsResult, JSContext, JSObject, PropertyDescriptor}; +use js::jsapi::{GetWellKnownSymbol, SymbolCode}; use js::jsapi::{JSErrNum, SetDOMProxyInformation}; use js::jsval::ObjectValue; use js::jsval::UndefinedValue; use js::rust::wrappers::JS_AlreadyHasOwnPropertyById; use js::rust::wrappers::JS_NewObjectWithGivenProto; -use js::rust::{Handle, HandleObject, MutableHandle, MutableHandleObject}; -use std::ptr; +use js::rust::wrappers::{AppendToIdVector, RUST_INTERNED_STRING_TO_JSID}; +use js::rust::{get_context_realm, Handle, HandleObject, MutableHandle, MutableHandleObject}; +use std::{ffi::CStr, ptr}; /// Determine if this id shadows any existing properties for this proxy. pub unsafe extern "C" fn shadow_check_callback( @@ -177,3 +196,322 @@ pub fn fill_property_descriptor( desc.getter = None; desc.setter = None; } + +/// <https://html.spec.whatwg.org/multipage/#isplatformobjectsameorigin-(-o-)> +pub unsafe fn is_platform_object_same_origin(cx: SafeJSContext, obj: RawHandleObject) -> bool { + let subject_realm = get_context_realm(*cx); + let obj_realm = GetObjectRealmOrNull(*obj); + assert!(!obj_realm.is_null()); + + let subject = ServoJSPrincipalsRef::from_raw_unchecked(GetRealmPrincipals(subject_realm)); + let obj = ServoJSPrincipalsRef::from_raw_unchecked(GetRealmPrincipals(obj_realm)); + + let subject_origin = subject.origin(); + let obj_origin = obj.origin(); + + subject_origin.same_origin_domain(&obj_origin) +} + +/// Report a cross-origin denial for a property, Always returns `false`, so it +/// can be used as `return report_cross_origin_denial(...);`. +/// +/// What this function does corresponds to the operations in +/// <https://html.spec.whatwg.org/multipage/#the-location-interface> denoted as +/// "Throw a `SecurityError` DOMException". +pub unsafe fn report_cross_origin_denial( + cx: SafeJSContext, + _id: RawHandleId, + _access: &str, +) -> bool { + let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); + if !JS_IsExceptionPending(*cx) { + let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); + // TODO: include `id` and `access` in the exception message + throw_dom_exception(cx, &*global, Error::Security); + } + false +} + +/// Property and method specs that correspond to the elements of +/// [`CrossOriginProperties(O)`]. +/// +/// [`CrossOriginProperties(O)`]: https://html.spec.whatwg.org/multipage/#crossoriginproperties-(-o-) +pub struct CrossOriginProperties { + pub attributes: &'static [JSPropertySpec], + pub methods: &'static [JSFunctionSpec], +} + +impl CrossOriginProperties { + /// Enumerate the property keys defined by `self`. + fn keys(&self) -> impl Iterator<Item = *const std::os::raw::c_char> + '_ { + // Safety: All cross-origin property keys are strings, not symbols + self.attributes + .iter() + .map(|spec| unsafe { spec.name.string_ }) + .chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ })) + .filter(|ptr| !ptr.is_null()) + } +} + +/// Implementation of [`CrossOriginOwnPropertyKeys`]. +/// +/// [`CrossOriginOwnPropertyKeys`]: https://html.spec.whatwg.org/multipage/#crossoriginownpropertykeys-(-o-) +pub unsafe fn cross_origin_own_property_keys( + cx: SafeJSContext, + _proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + props: RawMutableHandleIdVector, +) -> bool { + for key in cross_origin_properties.keys() { + let jsstring = JS_AtomizeAndPinString(*cx, key); + rooted!(in(*cx) let rooted = jsstring); + rooted!(in(*cx) let mut rooted_jsid: jsid); + RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), rooted_jsid.handle_mut()); + AppendToIdVector(props, rooted_jsid.handle()); + } + true +} + +/// Implementation of [`CrossOriginGet`]. +/// +/// [`CrossOriginGet`]: https://html.spec.whatwg.org/multipage/#crossoriginget-(-o,-p,-receiver-) +pub unsafe fn cross_origin_get( + cx: SafeJSContext, + proxy: RawHandleObject, + receiver: RawHandleValue, + id: RawHandleId, + vp: RawMutableHandleValue, +) -> bool { + // > 1. Let `desc` be `? O.[[GetOwnProperty]](P)`. + rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default()); + if !InvokeGetOwnPropertyDescriptor( + GetProxyHandler(*proxy), + *cx, + proxy, + id, + descriptor.handle_mut().into(), + ) { + return false; + } + // let descriptor = descriptor.get(); + + // > 2. Assert: `desc` is not undefined. + assert!( + !descriptor.obj.is_null(), + "Callees should throw in all cases when they are not finding \ + a property decriptor" + ); + + // > 3. If `! IsDataDescriptor(desc)` is true, then return `desc.[[Value]]`. + if is_data_descriptor(&descriptor) { + vp.set(descriptor.value); + return true; + } + + // > 4. Assert: `IsAccessorDescriptor(desc)` is `true`. + assert!(is_accessor_descriptor(&descriptor)); + + // > 5. Let `getter` be `desc.[[Get]]`. + // > + // > 6. If `getter` is `undefined`, then throw a `SecurityError` + // > `DOMException`. + rooted!(in(*cx) let mut getter = ptr::null_mut::<JSObject>()); + get_getter_object(&descriptor, getter.handle_mut().into()); + if getter.get().is_null() { + return report_cross_origin_denial(cx, id, "get"); + } + + rooted!(in(*cx) let mut getter_jsval = UndefinedValue()); + getter.get().to_jsval(*cx, getter_jsval.handle_mut()); + + // > 7. Return `? Call(getter, Receiver)`. + jsapi::Call( + *cx, + receiver, + getter_jsval.handle().into(), + &jsapi::HandleValueArray::new(), + vp, + ) +} + +unsafe fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) { + if (d.attrs & jsapi::JSPROP_GETTER as u32) != 0 { + out.set(std::mem::transmute(d.getter)); + } +} + +/// <https://tc39.es/ecma262/#sec-isaccessordescriptor> +fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool { + d.attrs & (jsapi::JSPROP_GETTER as u32 | jsapi::JSPROP_SETTER as u32) != 0 +} + +/// <https://tc39.es/ecma262/#sec-isdatadescriptor> +fn is_data_descriptor(d: &PropertyDescriptor) -> bool { + let is_accessor = is_accessor_descriptor(d); + let is_generic = d.attrs & + (jsapi::JSPROP_GETTER as u32 | + jsapi::JSPROP_SETTER as u32 | + jsapi::JSPROP_IGNORE_READONLY | + jsapi::JSPROP_IGNORE_VALUE) == + jsapi::JSPROP_IGNORE_READONLY | jsapi::JSPROP_IGNORE_VALUE; + !is_accessor && !is_generic +} + +/// Evaluate `CrossOriginGetOwnPropertyHelper(proxy, id) != null`. +pub unsafe fn cross_origin_has_own( + cx: SafeJSContext, + _proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + id: RawHandleId, + bp: *mut bool, +) -> bool { + // TODO: Once we have the slot for the holder, it'd be more efficient to + // use `ensure_cross_origin_property_holder`. + *bp = if let Some(key) = + crate::dom::bindings::conversions::jsid_to_string(*cx, Handle::from_raw(id)) + { + cross_origin_properties.keys().any(|defined_key| { + let defined_key = CStr::from_ptr(defined_key); + defined_key.to_bytes() == key.as_bytes() + }) + } else { + false + }; + + true +} + +/// Implementation of [`CrossOriginGetOwnPropertyHelper`]. +/// +/// `cx` and `obj` are expected to be different-Realm here. `obj` can be a +/// `WindowProxy` or a `Location` or a `DissimilarOrigin*` proxy for one of +/// those. +/// +/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-) +pub unsafe fn cross_origin_get_own_property_helper( + cx: SafeJSContext, + proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + id: RawHandleId, + mut desc: RawMutableHandle<PropertyDescriptor>, +) -> bool { + rooted!(in(*cx) let mut holder = ptr::null_mut::<JSObject>()); + + ensure_cross_origin_property_holder( + cx, + proxy, + cross_origin_properties, + holder.handle_mut().into(), + ); + + if !JS_GetOwnPropertyDescriptorById(*cx, holder.handle().into(), id, desc) { + return false; + } + + if !desc.obj.is_null() { + desc.obj = proxy.get(); + } + + true +} + +/// Implementation of [`CrossOriginPropertyFallback`]. +/// + +/// [`CrossOriginPropertyFallback`]: https://html.spec.whatwg.org/multipage/#crossoriginpropertyfallback-(-p-) +pub unsafe fn cross_origin_property_fallback( + cx: SafeJSContext, + proxy: RawHandleObject, + id: RawHandleId, + mut desc: RawMutableHandle<PropertyDescriptor>, +) -> bool { + assert!(desc.obj.is_null(), "why are we being called?"); + + // > 1. If P is `then`, `@@toStringTag`, `@@hasInstance`, or + // > `@@isConcatSpreadable`, then return `PropertyDescriptor{ [[Value]]: + // > undefined, [[Writable]]: false, [[Enumerable]]: false, + // > [[Configurable]]: true }`. + if is_cross_origin_allowlisted_prop(cx, id) { + *desc = PropertyDescriptor { + getter: None, + setter: None, + value: UndefinedValue(), + attrs: jsapi::JSPROP_READONLY as u32, + obj: proxy.get(), + }; + return true; + } + + // > 2. Throw a `SecurityError` `DOMException`. + report_cross_origin_denial(cx, id, "access") +} + +unsafe fn is_cross_origin_allowlisted_prop(cx: SafeJSContext, id: RawHandleId) -> bool { + const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[ + SymbolCode::toStringTag, + SymbolCode::hasInstance, + SymbolCode::isConcatSpreadable, + ]; + + crate::dom::bindings::conversions::jsid_to_string(*cx, Handle::from_raw(id)) + .filter(|st| st == "then") + .is_some() || + { + rooted!(in(*cx) let mut allowed_id: jsid); + ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| { + RUST_SYMBOL_TO_JSID( + GetWellKnownSymbol(*cx, allowed_code), + allowed_id.handle_mut().into(), + ); + // `jsid`s containing `JS::Symbol *` can be compared by + // referential equality + allowed_id.get().asBits == id.asBits + }) + } +} + +/// Get the holder for cross-origin properties for the current global of the +/// `JSContext`, creating one and storing it in a slot of the proxy object if it +/// doesn't exist yet. +/// +/// This essentially creates a cache of [`CrossOriginGetOwnPropertyHelper`]'s +/// results for all property keys. +/// +/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-) +unsafe fn ensure_cross_origin_property_holder( + cx: SafeJSContext, + _proxy: RawHandleObject, + cross_origin_properties: &'static CrossOriginProperties, + out_holder: RawMutableHandleObject, +) -> bool { + // TODO: We don't have the slot to store the holder yet. For now, + // the holder is constructed every time this function is called, + // which is not only inefficient but also deviates from the + // specification in a subtle yet observable way. + + // Create a holder for the current Realm + out_holder.set(jsapi::JS_NewObjectWithGivenProto( + *cx, + ptr::null_mut(), + RawHandleObject::null(), + )); + + if out_holder.get().is_null() || + !jsapi::JS_DefineProperties( + *cx, + out_holder.handle(), + cross_origin_properties.attributes.as_ptr(), + ) || + !jsapi::JS_DefineFunctions( + *cx, + out_holder.handle(), + cross_origin_properties.methods.as_ptr(), + ) + { + return false; + } + + // TODO: Store the holder in the slot that we don't have yet. + + true +} diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index 244b42dc8a5..bccded0f43c 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -1084,6 +1084,9 @@ pub fn new_window_proxy_handler() -> WindowProxyHandler { // These traps often throw security errors, and only pass on calls to methods // defined in the DissimilarOriginWindow IDL. +// TODO: reuse the infrastructure in `proxyhandler.rs`. For starters, the calls +// to this function should be replaced with those to +// `report_cross_origin_denial`. #[allow(unsafe_code)] unsafe fn throw_security_error(cx: *mut JSContext, realm: InRealm) -> bool { if !JS_IsExceptionPending(cx) { |