import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,458 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AccessCheck.h"
#include "nsJSPrincipals.h"
#include "nsGlobalWindow.h"
#include "XPCWrapper.h"
#include "XrayWrapper.h"
#include "FilteringWrapper.h"
#include "jsfriendapi.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/LocationBinding.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "nsIDOMWindowCollection.h"
#include "nsJSUtils.h"
#include "xpcprivate.h"
using namespace mozilla;
using namespace JS;
using namespace js;
namespace xpc {
nsIPrincipal*
GetCompartmentPrincipal(JSCompartment* compartment)
{
return nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
}
nsIPrincipal*
GetObjectPrincipal(JSObject* obj)
{
return GetCompartmentPrincipal(js::GetObjectCompartment(obj));
}
// Does the principal of compartment a subsume the principal of compartment b?
bool
AccessCheck::subsumes(JSCompartment* a, JSCompartment* b)
{
nsIPrincipal* aprin = GetCompartmentPrincipal(a);
nsIPrincipal* bprin = GetCompartmentPrincipal(b);
return aprin->Subsumes(bprin);
}
bool
AccessCheck::subsumes(JSObject* a, JSObject* b)
{
return subsumes(js::GetObjectCompartment(a), js::GetObjectCompartment(b));
}
// Same as above, but considering document.domain.
bool
AccessCheck::subsumesConsideringDomain(JSCompartment* a, JSCompartment* b)
{
nsIPrincipal* aprin = GetCompartmentPrincipal(a);
nsIPrincipal* bprin = GetCompartmentPrincipal(b);
return aprin->SubsumesConsideringDomain(bprin);
}
// Does the compartment of the wrapper subsumes the compartment of the wrappee?
bool
AccessCheck::wrapperSubsumes(JSObject* wrapper)
{
MOZ_ASSERT(js::IsWrapper(wrapper));
JSObject* wrapped = js::UncheckedUnwrap(wrapper);
return AccessCheck::subsumes(js::GetObjectCompartment(wrapper),
js::GetObjectCompartment(wrapped));
}
bool
AccessCheck::isChrome(JSCompartment* compartment)
{
bool privileged;
nsIPrincipal* principal = GetCompartmentPrincipal(compartment);
return NS_SUCCEEDED(nsXPConnect::SecurityManager()->IsSystemPrincipal(principal, &privileged)) && privileged;
}
bool
AccessCheck::isChrome(JSObject* obj)
{
return isChrome(js::GetObjectCompartment(obj));
}
nsIPrincipal*
AccessCheck::getPrincipal(JSCompartment* compartment)
{
return GetCompartmentPrincipal(compartment);
}
// Hardcoded policy for cross origin property access. See the HTML5 Spec.
static bool
IsPermitted(CrossOriginObjectType type, JSFlatString* prop, bool set)
{
size_t propLength = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(prop));
if (!propLength)
return false;
char16_t propChar0 = JS_GetFlatStringCharAt(prop, 0);
if (type == CrossOriginLocation)
return dom::LocationBinding::IsPermitted(prop, propChar0, set);
if (type == CrossOriginWindow)
return dom::WindowBinding::IsPermitted(prop, propChar0, set);
return false;
}
static bool
IsFrameId(JSContext* cx, JSObject* obj, jsid idArg)
{
MOZ_ASSERT(!js::IsWrapper(obj));
RootedId id(cx, idArg);
nsGlobalWindow* win = WindowOrNull(obj);
if (!win) {
return false;
}
nsCOMPtr<nsIDOMWindowCollection> col = win->GetFrames();
if (!col) {
return false;
}
nsCOMPtr<mozIDOMWindowProxy> domwin;
if (JSID_IS_INT(id)) {
col->Item(JSID_TO_INT(id), getter_AddRefs(domwin));
} else if (JSID_IS_STRING(id)) {
nsAutoJSString idAsString;
if (!idAsString.init(cx, JSID_TO_STRING(id))) {
return false;
}
col->NamedItem(idAsString, getter_AddRefs(domwin));
}
return domwin != nullptr;
}
CrossOriginObjectType
IdentifyCrossOriginObject(JSObject* obj)
{
obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
const js::Class* clasp = js::GetObjectClass(obj);
MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(Jsvalify(clasp)), "shouldn't have a holder here");
if (clasp->name[0] == 'L' && !strcmp(clasp->name, "Location"))
return CrossOriginLocation;
if (clasp->name[0] == 'W' && !strcmp(clasp->name, "Window"))
return CrossOriginWindow;
return CrossOriginOpaque;
}
bool
AccessCheck::isCrossOriginAccessPermitted(JSContext* cx, HandleObject wrapper, HandleId id,
Wrapper::Action act)
{
if (act == Wrapper::CALL)
return false;
if (act == Wrapper::ENUMERATE)
return true;
// For the case of getting a property descriptor, we allow if either GET or SET
// is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
return isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::GET) ||
isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::SET);
}
RootedObject obj(cx, js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false));
CrossOriginObjectType type = IdentifyCrossOriginObject(obj);
if (JSID_IS_STRING(id)) {
if (IsPermitted(type, JSID_TO_FLAT_STRING(id), act == Wrapper::SET))
return true;
} else if (type != CrossOriginOpaque &&
IsCrossOriginWhitelistedSymbol(cx, id)) {
// We always allow access to @@toStringTag, @@hasInstance, and
// @@isConcatSpreadable. But then we nerf them to be a value descriptor
// with value undefined in CrossOriginXrayWrapper.
return true;
}
if (act != Wrapper::GET)
return false;
// Check for frame IDs. If we're resolving named frames, make sure to only
// resolve ones that don't shadow native properties. See bug 860494.
if (type == CrossOriginWindow) {
if (JSID_IS_STRING(id)) {
bool wouldShadow = false;
if (!XrayUtils::HasNativeProperty(cx, wrapper, id, &wouldShadow) ||
wouldShadow)
{
// If the named subframe matches the name of a DOM constructor,
// the global resolve triggered by the HasNativeProperty call
// above will try to perform a CheckedUnwrap on |wrapper|, and
// throw a security error if it fails. That exception isn't
// really useful for our callers, so we silence it and just
// deny access to the property (since it matched a builtin).
//
// Note that this would be a problem if the resolve code ever
// tried to CheckedUnwrap the wrapper _before_ concluding that
// the name corresponds to a builtin global property, since it
// would mean that we'd never permit cross-origin named subframe
// access (something we regrettably need to support).
JS_ClearPendingException(cx);
return false;
}
}
return IsFrameId(cx, obj, id);
}
return false;
}
bool
AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, HandleValue v)
{
// Primitives are fine.
if (!v.isObject())
return true;
RootedObject obj(cx, &v.toObject());
// Non-wrappers are fine.
if (!js::IsWrapper(obj))
return true;
// CPOWs use COWs (in the unprivileged junk scope) for all child->parent
// references. Without this test, the child process wouldn't be able to
// pass any objects at all to CPOWs.
if (mozilla::jsipc::IsWrappedCPOW(obj) &&
js::GetObjectCompartment(wrapper) == js::GetObjectCompartment(xpc::UnprivilegedJunkScope()) &&
XRE_IsParentProcess())
{
return true;
}
// COWs are fine to pass to chrome if and only if they have __exposedProps__,
// since presumably content should never have a reason to pass an opaque
// object back to chrome.
if (AccessCheck::isChrome(js::UncheckedUnwrap(wrapper)) && WrapperFactory::IsCOW(obj)) {
RootedObject target(cx, js::UncheckedUnwrap(obj));
JSAutoCompartment ac(cx, target);
RootedId id(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS));
bool found = false;
if (!JS_HasPropertyById(cx, target, id, &found))
return false;
if (found)
return true;
}
// Same-origin wrappers are fine.
if (AccessCheck::wrapperSubsumes(obj))
return true;
// Badness.
JS_ReportErrorASCII(cx, "Permission denied to pass object to privileged code");
return false;
}
bool
AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, const CallArgs& args)
{
if (!checkPassToPrivilegedCode(cx, wrapper, args.thisv()))
return false;
for (size_t i = 0; i < args.length(); ++i) {
if (!checkPassToPrivilegedCode(cx, wrapper, args[i]))
return false;
}
return true;
}
enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 };
static void
EnterAndThrowASCII(JSContext* cx, JSObject* wrapper, const char* msg)
{
JSAutoCompartment ac(cx, wrapper);
JS_ReportErrorASCII(cx, "%s", msg);
}
bool
ExposedPropertiesOnly::check(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act)
{
RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper));
if (act == Wrapper::CALL)
return false;
// For the case of getting a property descriptor, we allow if either GET or SET
// is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
return check(cx, wrapper, id, Wrapper::GET) ||
check(cx, wrapper, id, Wrapper::SET);
}
RootedId exposedPropsId(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS));
// We need to enter the wrappee's compartment to look at __exposedProps__,
// but we want to be in the wrapper's compartment if we call Deny().
//
// Unfortunately, |cx| can be in either compartment when we call ::check. :-(
JSAutoCompartment ac(cx, wrappedObject);
bool found = false;
if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
return false;
// If no __exposedProps__ existed, deny access.
if (!found) {
// Previously we automatically granted access to indexed properties and
// .length for Array COWs. We're not doing that anymore, so make sure to
// let people know what's going on.
bool isArray;
if (!JS_IsArrayObject(cx, wrappedObject, &isArray))
return false;
if (!isArray)
isArray = JS_IsTypedArrayObject(wrappedObject);
bool isIndexedAccessOnArray = isArray && JSID_IS_INT(id) && JSID_TO_INT(id) >= 0;
bool isLengthAccessOnArray = isArray && JSID_IS_STRING(id) &&
JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length");
if (isIndexedAccessOnArray || isLengthAccessOnArray) {
JSAutoCompartment ac2(cx, wrapper);
ReportWrapperDenial(cx, id, WrapperDenialForCOW,
"Access to elements and length of privileged Array not permitted");
}
return false;
}
if (id == JSID_VOID)
return true;
Rooted<PropertyDescriptor> desc(cx);
if (!JS_GetPropertyDescriptorById(cx, wrappedObject, exposedPropsId, &desc))
return false;
if (!desc.object())
return false;
if (desc.hasGetterOrSetter()) {
EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be a value property");
return false;
}
RootedValue exposedProps(cx, desc.value());
if (exposedProps.isNullOrUndefined())
return false;
if (!exposedProps.isObject()) {
EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object");
return false;
}
RootedObject hallpass(cx, &exposedProps.toObject());
if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) {
EnterAndThrowASCII(cx, wrapper, "Invalid __exposedProps__");
return false;
}
Access access = NO_ACCESS;
if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) {
return false; // Error
}
if (!desc.object() || !desc.enumerable())
return false;
if (!desc.value().isString()) {
EnterAndThrowASCII(cx, wrapper, "property must be a string");
return false;
}
JSFlatString* flat = JS_FlattenString(cx, desc.value().toString());
if (!flat)
return false;
size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat));
for (size_t i = 0; i < length; ++i) {
char16_t ch = JS_GetFlatStringCharAt(flat, i);
switch (ch) {
case 'r':
if (access & READ) {
EnterAndThrowASCII(cx, wrapper, "duplicate 'readable' property flag");
return false;
}
access = Access(access | READ);
break;
case 'w':
if (access & WRITE) {
EnterAndThrowASCII(cx, wrapper, "duplicate 'writable' property flag");
return false;
}
access = Access(access | WRITE);
break;
default:
EnterAndThrowASCII(cx, wrapper, "properties can only be readable or read and writable");
return false;
}
}
if (access == NO_ACCESS) {
EnterAndThrowASCII(cx, wrapper, "specified properties must have a permission bit set");
return false;
}
if ((act == Wrapper::SET && !(access & WRITE)) ||
(act != Wrapper::SET && !(access & READ))) {
return false;
}
// Inspect the property on the underlying object to check for red flags.
if (!JS_GetPropertyDescriptorById(cx, wrappedObject, id, &desc))
return false;
// Reject accessor properties.
if (desc.hasGetterOrSetter()) {
EnterAndThrowASCII(cx, wrapper, "Exposing privileged accessor properties is prohibited");
return false;
}
// Reject privileged or cross-origin callables.
if (desc.value().isObject()) {
RootedObject maybeCallable(cx, js::UncheckedUnwrap(&desc.value().toObject()));
if (JS::IsCallable(maybeCallable) && !AccessCheck::subsumes(wrapper, maybeCallable)) {
EnterAndThrowASCII(cx, wrapper, "Exposing privileged or cross-origin callable is prohibited");
return false;
}
}
return true;
}
bool
ExposedPropertiesOnly::deny(js::Wrapper::Action act, HandleId id)
{
// Fail silently for GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR.
if (act == js::Wrapper::GET || act == js::Wrapper::ENUMERATE ||
act == js::Wrapper::GET_PROPERTY_DESCRIPTOR)
{
AutoJSContext cx;
return ReportWrapperDenial(cx, id, WrapperDenialForCOW,
"Access to privileged JS object not permitted");
}
return false;
}
} // namespace xpc

View file

@ -0,0 +1,106 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __AccessCheck_h__
#define __AccessCheck_h__
#include "jswrapper.h"
#include "js/Id.h"
class nsIPrincipal;
namespace xpc {
class AccessCheck {
public:
static bool subsumes(JSCompartment* a, JSCompartment* b);
static bool subsumes(JSObject* a, JSObject* b);
static bool wrapperSubsumes(JSObject* wrapper);
static bool subsumesConsideringDomain(JSCompartment* a, JSCompartment* b);
static bool isChrome(JSCompartment* compartment);
static bool isChrome(JSObject* obj);
static nsIPrincipal* getPrincipal(JSCompartment* compartment);
static bool isCrossOriginAccessPermitted(JSContext* cx, JS::HandleObject obj,
JS::HandleId id, js::Wrapper::Action act);
static bool checkPassToPrivilegedCode(JSContext* cx, JS::HandleObject wrapper,
JS::HandleValue value);
static bool checkPassToPrivilegedCode(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args);
};
enum CrossOriginObjectType {
CrossOriginWindow,
CrossOriginLocation,
CrossOriginOpaque
};
CrossOriginObjectType IdentifyCrossOriginObject(JSObject* obj);
struct Policy {
static bool checkCall(JSContext* cx, JS::HandleObject wrapper, const JS::CallArgs& args) {
MOZ_CRASH("As a rule, filtering wrappers are non-callable");
}
};
// This policy allows no interaction with the underlying callable. Everything throws.
struct Opaque : public Policy {
static bool check(JSContext* cx, JSObject* wrapper, jsid id, js::Wrapper::Action act) {
return false;
}
static bool deny(js::Wrapper::Action act, JS::HandleId id) {
return false;
}
static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
return false;
}
};
// Like the above, but allows CALL.
struct OpaqueWithCall : public Policy {
static bool check(JSContext* cx, JSObject* wrapper, jsid id, js::Wrapper::Action act) {
return act == js::Wrapper::CALL;
}
static bool deny(js::Wrapper::Action act, JS::HandleId id) {
return false;
}
static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
return false;
}
static bool checkCall(JSContext* cx, JS::HandleObject wrapper, const JS::CallArgs& args) {
return AccessCheck::checkPassToPrivilegedCode(cx, wrapper, args);
}
};
// This policy only permits access to properties that are safe to be used
// across origins.
struct CrossOriginAccessiblePropertiesOnly : public Policy {
static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act) {
return AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act);
}
static bool deny(js::Wrapper::Action act, JS::HandleId id) {
// Silently fail for enumerate-like operations.
if (act == js::Wrapper::ENUMERATE)
return true;
return false;
}
static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
return false;
}
};
// This policy only permits access to properties if they appear in the
// objects exposed properties list.
struct ExposedPropertiesOnly : public Policy {
static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act);
static bool deny(js::Wrapper::Action act, JS::HandleId id);
static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
return false;
}
};
} // namespace xpc
#endif /* __AccessCheck_h__ */

View file

@ -0,0 +1,270 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AddonWrapper.h"
#include "WrapperFactory.h"
#include "XrayWrapper.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "nsIAddonInterposition.h"
#include "xpcprivate.h"
#include "mozilla/dom/BindingUtils.h"
#include "nsGlobalWindow.h"
#include "GeckoProfiler.h"
#include "nsID.h"
using namespace js;
using namespace JS;
namespace xpc {
bool
InterposeProperty(JSContext* cx, HandleObject target, const nsIID* iid, HandleId id,
MutableHandle<PropertyDescriptor> descriptor)
{
// We only want to do interpostion on DOM instances and
// wrapped natives.
RootedObject unwrapped(cx, UncheckedUnwrap(target));
const js::Class* clasp = js::GetObjectClass(unwrapped);
bool isCPOW = jsipc::IsWrappedCPOW(unwrapped);
if (!mozilla::dom::IsDOMClass(clasp) &&
!IS_WN_CLASS(clasp) &&
!IS_PROTO_CLASS(clasp) &&
clasp != &OuterWindowProxyClass &&
!isCPOW) {
return true;
}
XPCWrappedNativeScope* scope = ObjectScope(CurrentGlobalOrNull(cx));
MOZ_ASSERT(scope->HasInterposition());
nsCOMPtr<nsIAddonInterposition> interp = scope->GetInterposition();
InterpositionWhitelist* wl = XPCWrappedNativeScope::GetInterpositionWhitelist(interp);
// We do InterposeProperty only if the id is on the whitelist of the interpostion
// or if the target is a CPOW.
if ((!wl || !wl->has(JSID_BITS(id.get()))) && !isCPOW)
return true;
JSAddonId* addonId = AddonIdOfObject(target);
RootedValue addonIdValue(cx, StringValue(StringOfAddonId(addonId)));
RootedValue prop(cx, IdToValue(id));
RootedValue targetValue(cx, ObjectValue(*target));
RootedValue descriptorVal(cx);
nsresult rv = interp->InterposeProperty(addonIdValue, targetValue,
iid, prop, &descriptorVal);
if (NS_FAILED(rv)) {
xpc::Throw(cx, rv);
return false;
}
if (!descriptorVal.isObject())
return true;
// We need to be careful parsing descriptorVal. |cx| is in the compartment
// of the add-on and the descriptor is in the compartment of the
// interposition. We could wrap the descriptor in the add-on's compartment
// and then parse it. However, parsing the descriptor fetches properties
// from it, and we would try to interpose on those property accesses. So
// instead we parse in the interposition's compartment and then wrap the
// descriptor.
{
JSAutoCompartment ac(cx, &descriptorVal.toObject());
if (!JS::ObjectToCompletePropertyDescriptor(cx, target, descriptorVal, descriptor))
return false;
}
// Always make the property non-configurable regardless of what the
// interposition wants.
descriptor.setAttributes(descriptor.attributes() | JSPROP_PERMANENT);
if (!JS_WrapPropertyDescriptor(cx, descriptor))
return false;
return true;
}
bool
InterposeCall(JSContext* cx, JS::HandleObject target, const JS::CallArgs& args, bool* done)
{
*done = false;
XPCWrappedNativeScope* scope = ObjectScope(CurrentGlobalOrNull(cx));
MOZ_ASSERT(scope->HasInterposition());
nsCOMPtr<nsIAddonInterposition> interp = scope->GetInterposition();
RootedObject unwrappedTarget(cx, UncheckedUnwrap(target));
XPCWrappedNativeScope* targetScope = ObjectScope(unwrappedTarget);
bool hasInterpostion = targetScope->HasCallInterposition();
if (!hasInterpostion)
return true;
// If there is a call interpostion, we don't want to propogate the
// call to Base:
*done = true;
JSAddonId* addonId = AddonIdOfObject(target);
RootedValue addonIdValue(cx, StringValue(StringOfAddonId(addonId)));
RootedValue targetValue(cx, ObjectValue(*target));
RootedValue thisValue(cx, args.thisv());
RootedObject argsArray(cx, ConvertArgsToArray(cx, args));
if (!argsArray)
return false;
RootedValue argsVal(cx, ObjectValue(*argsArray));
RootedValue returnVal(cx);
nsresult rv = interp->InterposeCall(addonIdValue, targetValue,
thisValue, argsVal, args.rval());
if (NS_FAILED(rv)) {
xpc::Throw(cx, rv);
return false;
}
return true;
}
template<typename Base>
bool AddonWrapper<Base>::call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const
{
bool done = false;
if (!InterposeCall(cx, wrapper, args, &done))
return false;
return done || Base::call(cx, wrapper, args);
}
template<typename Base>
bool
AddonWrapper<Base>::getPropertyDescriptor(JSContext* cx, HandleObject wrapper,
HandleId id, MutableHandle<PropertyDescriptor> desc) const
{
if (!InterposeProperty(cx, wrapper, nullptr, id, desc))
return false;
if (desc.object())
return true;
return Base::getPropertyDescriptor(cx, wrapper, id, desc);
}
template<typename Base>
bool
AddonWrapper<Base>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper,
HandleId id, MutableHandle<PropertyDescriptor> desc) const
{
if (!InterposeProperty(cx, wrapper, nullptr, id, desc))
return false;
if (desc.object())
return true;
return Base::getOwnPropertyDescriptor(cx, wrapper, id, desc);
}
template<typename Base>
bool
AddonWrapper<Base>::get(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<Value> receiver,
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
Rooted<PropertyDescriptor> desc(cx);
if (!InterposeProperty(cx, wrapper, nullptr, id, &desc))
return false;
if (!desc.object())
return Base::get(cx, wrapper, receiver, id, vp);
if (desc.getter()) {
return Call(cx, receiver, desc.getterObject(), HandleValueArray::empty(), vp);
} else {
vp.set(desc.value());
return true;
}
}
template<typename Base>
bool
AddonWrapper<Base>::set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::HandleValue v,
JS::HandleValue receiver, JS::ObjectOpResult& result) const
{
Rooted<PropertyDescriptor> desc(cx);
if (!InterposeProperty(cx, wrapper, nullptr, id, &desc))
return false;
if (!desc.object())
return Base::set(cx, wrapper, id, v, receiver, result);
if (desc.setter()) {
MOZ_ASSERT(desc.hasSetterObject());
JS::AutoValueVector args(cx);
if (!args.append(v))
return false;
RootedValue fval(cx, ObjectValue(*desc.setterObject()));
RootedValue ignored(cx);
if (!JS::Call(cx, receiver, fval, args, &ignored))
return false;
return result.succeed();
}
return result.failCantSetInterposed();
}
template<typename Base>
bool
AddonWrapper<Base>::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) const
{
Rooted<PropertyDescriptor> interpDesc(cx);
if (!InterposeProperty(cx, wrapper, nullptr, id, &interpDesc))
return false;
if (!interpDesc.object())
return Base::defineProperty(cx, wrapper, id, desc, result);
js::ReportASCIIErrorWithId(cx, "unable to modify interposed property %s", id);
return false;
}
template<typename Base>
bool
AddonWrapper<Base>::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
ObjectOpResult& result) const
{
Rooted<PropertyDescriptor> desc(cx);
if (!InterposeProperty(cx, wrapper, nullptr, id, &desc))
return false;
if (!desc.object())
return Base::delete_(cx, wrapper, id, result);
js::ReportASCIIErrorWithId(cx, "unable to delete interposed property %s", id);
return false;
}
#define AddonWrapperCC AddonWrapper<CrossCompartmentWrapper>
#define AddonWrapperXrayXPCWN AddonWrapper<PermissiveXrayXPCWN>
#define AddonWrapperXrayDOM AddonWrapper<PermissiveXrayDOM>
template<> const AddonWrapperCC AddonWrapperCC::singleton(0);
template<> const AddonWrapperXrayXPCWN AddonWrapperXrayXPCWN::singleton(0);
template<> const AddonWrapperXrayDOM AddonWrapperXrayDOM::singleton(0);
template class AddonWrapperCC;
template class AddonWrapperXrayXPCWN;
template class AddonWrapperXrayDOM;
#undef AddonWrapperCC
#undef AddonWrapperXrayXPCWN
#undef AddonWrapperXrayDOM
} // namespace xpc

View file

@ -0,0 +1,55 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef AddonWrapper_h
#define AddonWrapper_h
#include "mozilla/Attributes.h"
#include "nsID.h"
#include "jswrapper.h"
namespace xpc {
bool
InterposeProperty(JSContext* cx, JS::HandleObject target, const nsIID* iid, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> descriptor);
bool
InterposeCall(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, bool& done);
template<typename Base>
class AddonWrapper : public Base {
public:
explicit constexpr AddonWrapper(unsigned flags) : Base(flags) { }
virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::ObjectOpResult& result) const override;
virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
JS::ObjectOpResult& result) const override;
virtual bool get(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JS::Value> receiver,
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
virtual bool set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::HandleValue v,
JS::HandleValue receiver, JS::ObjectOpResult& result) const override;
virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const override;
virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
static const AddonWrapper singleton;
};
} // namespace xpc
#endif // AddonWrapper_h

View file

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ChromeObjectWrapper.h"
#include "WrapperFactory.h"
#include "AccessCheck.h"
#include "xpcprivate.h"
#include "jsapi.h"
#include "jswrapper.h"
#include "nsXULAppAPI.h"
using namespace JS;
namespace xpc {
const ChromeObjectWrapper ChromeObjectWrapper::singleton;
bool
ChromeObjectWrapper::defineProperty(JSContext* cx, HandleObject wrapper,
HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) const
{
if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, desc.value()))
return false;
return ChromeObjectWrapperBase::defineProperty(cx, wrapper, id, desc, result);
}
bool
ChromeObjectWrapper::set(JSContext* cx, HandleObject wrapper, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result) const
{
if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, v))
return false;
return ChromeObjectWrapperBase::set(cx, wrapper, id, v, receiver, result);
}
} // namespace xpc

View file

@ -0,0 +1,43 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __ChromeObjectWrapper_h__
#define __ChromeObjectWrapper_h__
#include "mozilla/Attributes.h"
#include "FilteringWrapper.h"
namespace xpc {
struct ExposedPropertiesOnly;
// When a vanilla chrome JS object is exposed to content, we use a wrapper that
// supports __exposedProps__ for legacy reasons. For extra security, we override
// the traps that allow content to pass an object to chrome, and perform extra
// security checks on them.
#define ChromeObjectWrapperBase \
FilteringWrapper<js::CrossCompartmentSecurityWrapper, ExposedPropertiesOnly>
class ChromeObjectWrapper : public ChromeObjectWrapperBase
{
public:
constexpr ChromeObjectWrapper() : ChromeObjectWrapperBase(0) {}
virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::ObjectOpResult& result) const override;
virtual bool set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
JS::HandleValue v, JS::HandleValue receiver,
JS::ObjectOpResult& result) const override;
static const ChromeObjectWrapper singleton;
};
} /* namespace xpc */
#endif /* __ChromeObjectWrapper_h__ */

View file

@ -0,0 +1,312 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FilteringWrapper.h"
#include "AccessCheck.h"
#include "ChromeObjectWrapper.h"
#include "XrayWrapper.h"
#include "jsapi.h"
using namespace JS;
using namespace js;
namespace xpc {
static JS::SymbolCode sCrossOriginWhitelistedSymbolCodes[] = {
JS::SymbolCode::toStringTag,
JS::SymbolCode::hasInstance,
JS::SymbolCode::isConcatSpreadable
};
bool
IsCrossOriginWhitelistedSymbol(JSContext* cx, JS::HandleId id)
{
if (!JSID_IS_SYMBOL(id)) {
return false;
}
JS::Symbol* symbol = JSID_TO_SYMBOL(id);
for (auto code : sCrossOriginWhitelistedSymbolCodes) {
if (symbol == JS::GetWellKnownSymbol(cx, code)) {
return true;
}
}
return false;
}
template <typename Policy>
static bool
Filter(JSContext* cx, HandleObject wrapper, AutoIdVector& props)
{
size_t w = 0;
RootedId id(cx);
for (size_t n = 0; n < props.length(); ++n) {
id = props[n];
if (Policy::check(cx, wrapper, id, Wrapper::GET) || Policy::check(cx, wrapper, id, Wrapper::SET))
props[w++].set(id);
else if (JS_IsExceptionPending(cx))
return false;
}
if (!props.resize(w))
return false;
return true;
}
template <typename Policy>
static bool
FilterPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id, MutableHandle<PropertyDescriptor> desc)
{
MOZ_ASSERT(!JS_IsExceptionPending(cx));
bool getAllowed = Policy::check(cx, wrapper, id, Wrapper::GET);
if (JS_IsExceptionPending(cx))
return false;
bool setAllowed = Policy::check(cx, wrapper, id, Wrapper::SET);
if (JS_IsExceptionPending(cx))
return false;
MOZ_ASSERT(getAllowed || setAllowed,
"Filtering policy should not allow GET_PROPERTY_DESCRIPTOR in this case");
if (!desc.hasGetterOrSetter()) {
// Handle value properties.
if (!getAllowed)
desc.value().setUndefined();
} else {
// Handle accessor properties.
MOZ_ASSERT(desc.value().isUndefined());
if (!getAllowed)
desc.setGetter(nullptr);
if (!setAllowed)
desc.setSetter(nullptr);
}
return true;
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::getPropertyDescriptor(JSContext* cx, HandleObject wrapper,
HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
if (!Base::getPropertyDescriptor(cx, wrapper, id, desc))
return false;
return FilterPropertyDescriptor<Policy>(cx, wrapper, id, desc);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper,
HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
if (!Base::getOwnPropertyDescriptor(cx, wrapper, id, desc))
return false;
return FilterPropertyDescriptor<Policy>(cx, wrapper, id, desc);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
AutoIdVector& props) const
{
assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
return Base::ownPropertyKeys(cx, wrapper, props) &&
Filter<Policy>(cx, wrapper, props);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::getOwnEnumerablePropertyKeys(JSContext* cx,
HandleObject wrapper,
AutoIdVector& props) const
{
assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
return Base::getOwnEnumerablePropertyKeys(cx, wrapper, props) &&
Filter<Policy>(cx, wrapper, props);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::enumerate(JSContext* cx, HandleObject wrapper,
MutableHandleObject objp) const
{
assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
// We refuse to trigger the enumerate hook across chrome wrappers because
// we don't know how to censor custom iterator objects. Instead we trigger
// the default proxy enumerate trap, which will use js::GetPropertyKeys
// for the list of (censored) ids.
return js::BaseProxyHandler::enumerate(cx, wrapper, objp);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const
{
if (!Policy::checkCall(cx, wrapper, args))
return false;
return Base::call(cx, wrapper, args);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::construct(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const
{
if (!Policy::checkCall(cx, wrapper, args))
return false;
return Base::construct(cx, wrapper, args);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::nativeCall(JSContext* cx, JS::IsAcceptableThis test,
JS::NativeImpl impl, const JS::CallArgs& args) const
{
if (Policy::allowNativeCall(cx, test, impl))
return Base::Permissive::nativeCall(cx, test, impl, args);
return Base::Restrictive::nativeCall(cx, test, impl, args);
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::getPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::MutableHandleObject protop) const
{
// Filtering wrappers do not allow access to the prototype.
protop.set(nullptr);
return true;
}
template <typename Base, typename Policy>
bool
FilteringWrapper<Base, Policy>::enter(JSContext* cx, HandleObject wrapper,
HandleId id, Wrapper::Action act, bool* bp) const
{
if (!Policy::check(cx, wrapper, id, act)) {
*bp = JS_IsExceptionPending(cx) ? false : Policy::deny(act, id);
return false;
}
*bp = true;
return true;
}
bool
CrossOriginXrayWrapper::getPropertyDescriptor(JSContext* cx,
JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<PropertyDescriptor> desc) const
{
if (!SecurityXrayDOM::getPropertyDescriptor(cx, wrapper, id, desc))
return false;
if (desc.object()) {
// Cross-origin DOM objects do not have symbol-named properties apart
// from the ones we add ourselves here.
MOZ_ASSERT(!JSID_IS_SYMBOL(id),
"What's this symbol-named property that appeared on a "
"Window or Location instance?");
// All properties on cross-origin DOM objects are |own|.
desc.object().set(wrapper);
// All properties on cross-origin DOM objects are non-enumerable and
// "configurable". Any value attributes are read-only.
desc.attributesRef() &= ~JSPROP_ENUMERATE;
desc.attributesRef() &= ~JSPROP_PERMANENT;
if (!desc.getter() && !desc.setter())
desc.attributesRef() |= JSPROP_READONLY;
} else if (IsCrossOriginWhitelistedSymbol(cx, id)) {
// Spec says to return PropertyDescriptor {
// [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true
// }.
//
desc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY);
desc.object().set(wrapper);
}
return true;
}
bool
CrossOriginXrayWrapper::getOwnPropertyDescriptor(JSContext* cx,
JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<PropertyDescriptor> desc) const
{
// All properties on cross-origin DOM objects are |own|.
return getPropertyDescriptor(cx, wrapper, id, desc);
}
bool
CrossOriginXrayWrapper::ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::AutoIdVector& props) const
{
// All properties on cross-origin objects are supposed |own|, despite what
// the underlying native object may report. Override the inherited trap to
// avoid passing JSITER_OWNONLY as a flag.
if (!SecurityXrayDOM::getPropertyKeys(cx, wrapper, JSITER_HIDDEN, props)) {
return false;
}
// Now add the three symbol-named props cross-origin objects have.
#ifdef DEBUG
for (size_t n = 0; n < props.length(); ++n) {
MOZ_ASSERT(!JSID_IS_SYMBOL(props[n]),
"Unexpected existing symbol-name prop");
}
#endif
if (!props.reserve(props.length() +
ArrayLength(sCrossOriginWhitelistedSymbolCodes))) {
return false;
}
for (auto code : sCrossOriginWhitelistedSymbolCodes) {
props.infallibleAppend(SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, code)));
}
return true;
}
bool
CrossOriginXrayWrapper::defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::Handle<PropertyDescriptor> desc,
JS::ObjectOpResult& result) const
{
JS_ReportErrorASCII(cx, "Permission denied to define property on cross-origin object");
return false;
}
bool
CrossOriginXrayWrapper::delete_(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id, JS::ObjectOpResult& result) const
{
JS_ReportErrorASCII(cx, "Permission denied to delete property on cross-origin object");
return false;
}
#define XOW FilteringWrapper<CrossOriginXrayWrapper, CrossOriginAccessiblePropertiesOnly>
#define NNXOW FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>
#define NNXOWC FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>
template<> const XOW XOW::singleton(0);
template<> const NNXOW NNXOW::singleton(0);
template<> const NNXOWC NNXOWC::singleton(0);
template class XOW;
template class NNXOW;
template class NNXOWC;
template class ChromeObjectWrapperBase;
} // namespace xpc

View file

@ -0,0 +1,91 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __FilteringWrapper_h__
#define __FilteringWrapper_h__
#include "XrayWrapper.h"
#include "mozilla/Attributes.h"
#include "jswrapper.h"
#include "js/CallNonGenericMethod.h"
namespace JS {
class AutoIdVector;
} // namespace JS
namespace xpc {
template <typename Base, typename Policy>
class FilteringWrapper : public Base {
public:
constexpr explicit FilteringWrapper(unsigned flags) : Base(flags) {}
virtual bool enter(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
js::Wrapper::Action act, bool* bp) const override;
virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::AutoIdVector& props) const override;
virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::AutoIdVector& props) const override;
virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::MutableHandle<JSObject*> objp) const override;
virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const override;
virtual bool construct(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const override;
virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl,
const JS::CallArgs& args) const override;
virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::MutableHandleObject protop) const override;
static const FilteringWrapper singleton;
};
/*
* The HTML5 spec mandates very particular object behavior for cross-origin DOM
* objects (Window and Location), some of which runs contrary to the way that
* other XrayWrappers behave. We use this class to implement those semantics.
*/
class CrossOriginXrayWrapper : public SecurityXrayDOM {
public:
constexpr explicit CrossOriginXrayWrapper(unsigned flags) :
SecurityXrayDOM(flags) {}
virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::ObjectOpResult& result) const override;
virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::AutoIdVector& props) const override;
virtual bool delete_(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id, JS::ObjectOpResult& result) const override;
virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
};
// Check whether the given jsid is a symbol whose value can be gotten
// cross-origin. Cross-origin gets always return undefined as the value.
bool IsCrossOriginWhitelistedSymbol(JSContext* cx, JS::HandleId id);
} // namespace xpc
#endif /* __FilteringWrapper_h__ */

View file

@ -0,0 +1,105 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WaiveXrayWrapper.h"
#include "WrapperFactory.h"
#include "jsapi.h"
using namespace JS;
namespace xpc {
static bool
WaiveAccessors(JSContext* cx, MutableHandle<PropertyDescriptor> desc)
{
if (desc.hasGetterObject() && desc.getterObject()) {
RootedValue v(cx, JS::ObjectValue(*desc.getterObject()));
if (!WrapperFactory::WaiveXrayAndWrap(cx, &v))
return false;
desc.setGetterObject(&v.toObject());
}
if (desc.hasSetterObject() && desc.setterObject()) {
RootedValue v(cx, JS::ObjectValue(*desc.setterObject()));
if (!WrapperFactory::WaiveXrayAndWrap(cx, &v))
return false;
desc.setSetterObject(&v.toObject());
}
return true;
}
bool
WaiveXrayWrapper::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
return CrossCompartmentWrapper::getPropertyDescriptor(cx, wrapper, id, desc) &&
WrapperFactory::WaiveXrayAndWrap(cx, desc.value()) && WaiveAccessors(cx, desc);
}
bool
WaiveXrayWrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
return CrossCompartmentWrapper::getOwnPropertyDescriptor(cx, wrapper, id, desc) &&
WrapperFactory::WaiveXrayAndWrap(cx, desc.value()) && WaiveAccessors(cx, desc);
}
bool
WaiveXrayWrapper::get(JSContext* cx, HandleObject wrapper, HandleValue receiver, HandleId id,
MutableHandleValue vp) const
{
return CrossCompartmentWrapper::get(cx, wrapper, receiver, id, vp) &&
WrapperFactory::WaiveXrayAndWrap(cx, vp);
}
bool
WaiveXrayWrapper::enumerate(JSContext* cx, HandleObject proxy,
MutableHandleObject objp) const
{
return CrossCompartmentWrapper::enumerate(cx, proxy, objp) &&
WrapperFactory::WaiveXrayAndWrap(cx, objp);
}
bool
WaiveXrayWrapper::call(JSContext* cx, HandleObject wrapper, const JS::CallArgs& args) const
{
return CrossCompartmentWrapper::call(cx, wrapper, args) &&
WrapperFactory::WaiveXrayAndWrap(cx, args.rval());
}
bool
WaiveXrayWrapper::construct(JSContext* cx, HandleObject wrapper, const JS::CallArgs& args) const
{
return CrossCompartmentWrapper::construct(cx, wrapper, args) &&
WrapperFactory::WaiveXrayAndWrap(cx, args.rval());
}
// NB: This is important as the other side of a handshake with FieldGetter. See
// nsXBLProtoImplField.cpp.
bool
WaiveXrayWrapper::nativeCall(JSContext* cx, JS::IsAcceptableThis test,
JS::NativeImpl impl, const JS::CallArgs& args) const
{
return CrossCompartmentWrapper::nativeCall(cx, test, impl, args) &&
WrapperFactory::WaiveXrayAndWrap(cx, args.rval());
}
bool
WaiveXrayWrapper::getPrototype(JSContext* cx, HandleObject wrapper, MutableHandleObject protop) const
{
return CrossCompartmentWrapper::getPrototype(cx, wrapper, protop) &&
(!protop || WrapperFactory::WaiveXrayAndWrap(cx, protop));
}
bool
WaiveXrayWrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject wrapper, bool* isOrdinary,
MutableHandleObject protop) const
{
return CrossCompartmentWrapper::getPrototypeIfOrdinary(cx, wrapper, isOrdinary, protop) &&
(!protop || WrapperFactory::WaiveXrayAndWrap(cx, protop));
}
} // namespace xpc

View file

@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __CrossOriginWrapper_h__
#define __CrossOriginWrapper_h__
#include "mozilla/Attributes.h"
#include "jswrapper.h"
namespace xpc {
class WaiveXrayWrapper : public js::CrossCompartmentWrapper {
public:
explicit constexpr WaiveXrayWrapper(unsigned flags) : js::CrossCompartmentWrapper(flags) { }
virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool getPrototype(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::MutableHandle<JSObject*> protop) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> wrapper,
bool* isOrdinary,
JS::MutableHandle<JSObject*> protop) const override;
virtual bool get(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JS::Value> receiver,
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const override;
virtual bool construct(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const override;
virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::MutableHandle<JSObject*> objp) const override;
virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test,
JS::NativeImpl impl, const JS::CallArgs& args) const override;
virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
static const WaiveXrayWrapper singleton;
};
} // namespace xpc
#endif

View file

@ -0,0 +1,671 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WaiveXrayWrapper.h"
#include "FilteringWrapper.h"
#include "AddonWrapper.h"
#include "XrayWrapper.h"
#include "AccessCheck.h"
#include "XPCWrapper.h"
#include "ChromeObjectWrapper.h"
#include "WrapperFactory.h"
#include "xpcprivate.h"
#include "XPCMaps.h"
#include "mozilla/dom/BindingUtils.h"
#include "jsfriendapi.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "mozilla/Likely.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsContentUtils.h"
#include "nsXULAppAPI.h"
using namespace JS;
using namespace js;
using namespace mozilla;
namespace xpc {
// When chrome pulls a naked property across the membrane using
// .wrappedJSObject, we want it to cross the membrane into the
// chrome compartment without automatically being wrapped into an
// X-ray wrapper. We achieve this by wrapping it into a special
// transparent wrapper in the origin (non-chrome) compartment. When
// an object with that special wrapper applied crosses into chrome,
// we know to not apply an X-ray wrapper.
const Wrapper XrayWaiver(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG);
// When objects for which we waived the X-ray wrapper cross into
// chrome, we wrap them into a special cross-compartment wrapper
// that transitively extends the waiver to all properties we get
// off it.
const WaiveXrayWrapper WaiveXrayWrapper::singleton(0);
bool
WrapperFactory::IsCOW(JSObject* obj)
{
return IsWrapper(obj) &&
Wrapper::wrapperHandler(obj) == &ChromeObjectWrapper::singleton;
}
JSObject*
WrapperFactory::GetXrayWaiver(HandleObject obj)
{
// Object should come fully unwrapped but outerized.
MOZ_ASSERT(obj == UncheckedUnwrap(obj));
MOZ_ASSERT(!js::IsWindow(obj));
XPCWrappedNativeScope* scope = ObjectScope(obj);
MOZ_ASSERT(scope);
if (!scope->mWaiverWrapperMap)
return nullptr;
return scope->mWaiverWrapperMap->Find(obj);
}
JSObject*
WrapperFactory::CreateXrayWaiver(JSContext* cx, HandleObject obj)
{
// The caller is required to have already done a lookup.
// NB: This implictly performs the assertions of GetXrayWaiver.
MOZ_ASSERT(!GetXrayWaiver(obj));
XPCWrappedNativeScope* scope = ObjectScope(obj);
JSAutoCompartment ac(cx, obj);
JSObject* waiver = Wrapper::New(cx, obj, &XrayWaiver);
if (!waiver)
return nullptr;
// Add the new waiver to the map. It's important that we only ever have
// one waiver for the lifetime of the target object.
if (!scope->mWaiverWrapperMap) {
scope->mWaiverWrapperMap =
JSObject2JSObjectMap::newMap(XPC_WRAPPER_MAP_LENGTH);
}
if (!scope->mWaiverWrapperMap->Add(cx, obj, waiver))
return nullptr;
return waiver;
}
JSObject*
WrapperFactory::WaiveXray(JSContext* cx, JSObject* objArg)
{
RootedObject obj(cx, objArg);
obj = UncheckedUnwrap(obj);
MOZ_ASSERT(!js::IsWindow(obj));
JSObject* waiver = GetXrayWaiver(obj);
if (!waiver) {
waiver = CreateXrayWaiver(cx, obj);
}
MOZ_ASSERT(!ObjectIsMarkedGray(waiver));
return waiver;
}
/* static */ bool
WrapperFactory::AllowWaiver(JSCompartment* target, JSCompartment* origin)
{
return CompartmentPrivate::Get(target)->allowWaivers &&
AccessCheck::subsumes(target, origin);
}
/* static */ bool
WrapperFactory::AllowWaiver(JSObject* wrapper) {
MOZ_ASSERT(js::IsCrossCompartmentWrapper(wrapper));
return AllowWaiver(js::GetObjectCompartment(wrapper),
js::GetObjectCompartment(js::UncheckedUnwrap(wrapper)));
}
inline bool
ShouldWaiveXray(JSContext* cx, JSObject* originalObj)
{
unsigned flags;
(void) js::UncheckedUnwrap(originalObj, /* stopAtWindowProxy = */ true, &flags);
// If the original object did not point through an Xray waiver, we're done.
if (!(flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG))
return false;
// If the original object was not a cross-compartment wrapper, that means
// that the caller explicitly created a waiver. Preserve it so that things
// like WaiveXrayAndWrap work.
if (!(flags & Wrapper::CROSS_COMPARTMENT))
return true;
// Otherwise, this is a case of explicitly passing a wrapper across a
// compartment boundary. In that case, we only want to preserve waivers
// in transactions between same-origin compartments.
JSCompartment* oldCompartment = js::GetObjectCompartment(originalObj);
JSCompartment* newCompartment = js::GetContextCompartment(cx);
bool sameOrigin =
AccessCheck::subsumesConsideringDomain(oldCompartment, newCompartment) &&
AccessCheck::subsumesConsideringDomain(newCompartment, oldCompartment);
return sameOrigin;
}
void
WrapperFactory::PrepareForWrapping(JSContext* cx, HandleObject scope,
HandleObject objArg, HandleObject objectPassedToWrap,
MutableHandleObject retObj)
{
bool waive = ShouldWaiveXray(cx, objectPassedToWrap);
RootedObject obj(cx, objArg);
retObj.set(nullptr);
// Outerize any raw inner objects at the entry point here, so that we don't
// have to worry about them for the rest of the wrapping code.
if (js::IsWindow(obj)) {
JSAutoCompartment ac(cx, obj);
obj = js::ToWindowProxyIfWindow(obj);
MOZ_ASSERT(obj);
// ToWindowProxyIfWindow can return a CCW if |obj| was a
// navigated-away-from Window. Strip any CCWs.
obj = js::UncheckedUnwrap(obj);
if (JS_IsDeadWrapper(obj)) {
JS_ReportErrorASCII(cx, "Can't wrap dead object");
return;
}
MOZ_ASSERT(js::IsWindowProxy(obj));
// We crossed a compartment boundary there, so may now have a gray
// object. This function is not allowed to return gray objects, so
// don't do that.
ExposeObjectToActiveJS(obj);
}
// If we've got a WindowProxy, there's nothing special that needs to be
// done here, and we can move on to the next phase of wrapping. We handle
// this case first to allow us to assert against wrappers below.
if (js::IsWindowProxy(obj)) {
retObj.set(waive ? WaiveXray(cx, obj) : obj);
return;
}
// Here are the rules for wrapping:
// We should never get a proxy here (the JS engine unwraps those for us).
MOZ_ASSERT(!IsWrapper(obj));
// Now, our object is ready to be wrapped, but several objects (notably
// nsJSIIDs) have a wrapper per scope. If we are about to wrap one of
// those objects in a security wrapper, then we need to hand back the
// wrapper for the new scope instead. Also, global objects don't move
// between scopes so for those we also want to return the wrapper. So...
if (!IS_WN_REFLECTOR(obj) || JS_IsGlobalObject(obj)) {
retObj.set(waive ? WaiveXray(cx, obj) : obj);
return;
}
XPCWrappedNative* wn = XPCWrappedNative::Get(obj);
JSAutoCompartment ac(cx, obj);
XPCCallContext ccx(cx, obj);
RootedObject wrapScope(cx, scope);
{
if (NATIVE_HAS_FLAG(&ccx, WantPreCreate)) {
// We have a precreate hook. This object might enforce that we only
// ever create JS object for it.
// Note: this penalizes objects that only have one wrapper, but are
// being accessed across compartments. We would really prefer to
// replace the above code with a test that says "do you only have one
// wrapper?"
nsresult rv = wn->GetScriptableInfo()->GetCallback()->
PreCreate(wn->Native(), cx, scope, wrapScope.address());
if (NS_FAILED(rv)) {
retObj.set(waive ? WaiveXray(cx, obj) : obj);
return;
}
// If the handed back scope differs from the passed-in scope and is in
// a separate compartment, then this object is explicitly requesting
// that we don't create a second JS object for it: create a security
// wrapper.
if (js::GetObjectCompartment(scope) != js::GetObjectCompartment(wrapScope)) {
retObj.set(waive ? WaiveXray(cx, obj) : obj);
return;
}
RootedObject currentScope(cx, JS_GetGlobalForObject(cx, obj));
if (MOZ_UNLIKELY(wrapScope != currentScope)) {
// The wrapper claims it wants to be in the new scope, but
// currently has a reflection that lives in the old scope. This
// can mean one of two things, both of which are rare:
//
// 1 - The object has a PreCreate hook (we checked for it above),
// but is deciding to request one-wrapper-per-scope (rather than
// one-wrapper-per-native) for some reason. Usually, a PreCreate
// hook indicates one-wrapper-per-native. In this case we want to
// make a new wrapper in the new scope.
//
// 2 - We're midway through wrapper reparenting. The document has
// moved to a new scope, but |wn| hasn't been moved yet, and
// we ended up calling JS_WrapObject() on its JS object. In this
// case, we want to return the existing wrapper.
//
// So we do a trick: call PreCreate _again_, but say that we're
// wrapping for the old scope, rather than the new one. If (1) is
// the case, then PreCreate will return the scope we pass to it
// (the old scope). If (2) is the case, PreCreate will return the
// scope of the document (the new scope).
RootedObject probe(cx);
rv = wn->GetScriptableInfo()->GetCallback()->
PreCreate(wn->Native(), cx, currentScope, probe.address());
// Check for case (2).
if (probe != currentScope) {
MOZ_ASSERT(probe == wrapScope);
retObj.set(waive ? WaiveXray(cx, obj) : obj);
return;
}
// Ok, must be case (1). Fall through and create a new wrapper.
}
// Nasty hack for late-breaking bug 781476. This will confuse identity checks,
// but it's probably better than any of our alternatives.
//
// Note: We have to ignore domain here. The JS engine assumes that, given a
// compartment c, if c->wrap(x) returns a cross-compartment wrapper at time t0,
// it will also return a cross-compartment wrapper for any time t1 > t0 unless
// an explicit transplant is performed. In particular, wrapper recomputation
// assumes that recomputing a wrapper will always result in a wrapper.
//
// This doesn't actually pose a security issue, because we'll still compute
// the correct (opaque) wrapper for the object below given the security
// characteristics of the two compartments.
if (!AccessCheck::isChrome(js::GetObjectCompartment(wrapScope)) &&
AccessCheck::subsumes(js::GetObjectCompartment(wrapScope),
js::GetObjectCompartment(obj)))
{
retObj.set(waive ? WaiveXray(cx, obj) : obj);
return;
}
}
}
// This public WrapNativeToJSVal API enters the compartment of 'wrapScope'
// so we don't have to.
RootedValue v(cx);
nsresult rv =
nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, wrapScope, wn->Native(), nullptr,
&NS_GET_IID(nsISupports), false, &v);
if (NS_FAILED(rv)) {
return;
}
obj.set(&v.toObject());
MOZ_ASSERT(IS_WN_REFLECTOR(obj), "bad object");
MOZ_ASSERT(!ObjectIsMarkedGray(obj), "Should never return gray reflectors");
// Because the underlying native didn't have a PreCreate hook, we had
// to a new (or possibly pre-existing) XPCWN in our compartment.
// This could be a problem for chrome code that passes XPCOM objects
// across compartments, because the effects of QI would disappear across
// compartments.
//
// So whenever we pull an XPCWN across compartments in this manner, we
// give the destination object the union of the two native sets. We try
// to do this cleverly in the common case to avoid too much overhead.
XPCWrappedNative* newwn = XPCWrappedNative::Get(obj);
RefPtr<XPCNativeSet> unionSet = XPCNativeSet::GetNewOrUsed(newwn->GetSet(),
wn->GetSet(), false);
if (!unionSet) {
return;
}
newwn->SetSet(unionSet.forget());
retObj.set(waive ? WaiveXray(cx, obj) : obj);
}
#ifdef DEBUG
static void
DEBUG_CheckUnwrapSafety(HandleObject obj, const js::Wrapper* handler,
JSCompartment* origin, JSCompartment* target)
{
if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) {
// If the caller is chrome (or effectively so), unwrap should always be allowed.
MOZ_ASSERT(!handler->hasSecurityPolicy());
} else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) {
// Similarly, if this is a privileged scope that has opted to make itself
// accessible to the world (allowed only during automation), unwrap should
// be allowed.
MOZ_ASSERT(!handler->hasSecurityPolicy());
} else {
// Otherwise, it should depend on whether the target subsumes the origin.
MOZ_ASSERT(handler->hasSecurityPolicy() == !AccessCheck::subsumesConsideringDomain(target, origin));
}
}
#else
#define DEBUG_CheckUnwrapSafety(obj, handler, origin, target) {}
#endif
static const Wrapper*
SelectWrapper(bool securityWrapper, bool wantXrays, XrayType xrayType,
bool waiveXrays, bool originIsXBLScope, JSObject* obj)
{
// Waived Xray uses a modified CCW that has transparent behavior but
// transitively waives Xrays on arguments.
if (waiveXrays) {
MOZ_ASSERT(!securityWrapper);
return &WaiveXrayWrapper::singleton;
}
// If we don't want or can't use Xrays, select a wrapper that's either
// entirely transparent or entirely opaque.
if (!wantXrays || xrayType == NotXray) {
if (!securityWrapper)
return &CrossCompartmentWrapper::singleton;
return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
}
// Ok, we're using Xray. If this isn't a security wrapper, use the permissive
// version and skip the filter.
if (!securityWrapper) {
if (xrayType == XrayForWrappedNative)
return &PermissiveXrayXPCWN::singleton;
else if (xrayType == XrayForDOMObject)
return &PermissiveXrayDOM::singleton;
else if (xrayType == XrayForJSObject)
return &PermissiveXrayJS::singleton;
MOZ_ASSERT(xrayType == XrayForOpaqueObject);
return &PermissiveXrayOpaque::singleton;
}
// This is a security wrapper. Use the security versions and filter.
if (xrayType == XrayForDOMObject && IdentifyCrossOriginObject(obj) != CrossOriginOpaque)
return &FilteringWrapper<CrossOriginXrayWrapper,
CrossOriginAccessiblePropertiesOnly>::singleton;
// There's never any reason to expose other objects to non-subsuming actors.
// Just use an opaque wrapper in these cases.
//
// In general, we don't want opaque function wrappers to be callable.
// But in the case of XBL, we rely on content being able to invoke
// functions exposed from the XBL scope. We could remove this exception,
// if needed, by using ExportFunction to generate the content-side
// representations of XBL methods.
if (xrayType == XrayForJSObject && originIsXBLScope)
return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
}
static const Wrapper*
SelectAddonWrapper(JSContext* cx, HandleObject obj, const Wrapper* wrapper)
{
JSAddonId* originAddon = JS::AddonIdOfObject(obj);
JSAddonId* targetAddon = JS::AddonIdOfObject(JS::CurrentGlobalOrNull(cx));
MOZ_ASSERT(AccessCheck::isChrome(JS::CurrentGlobalOrNull(cx)));
MOZ_ASSERT(targetAddon);
if (targetAddon == originAddon)
return wrapper;
// Add-on interposition only supports certain wrapper types, so we check if
// we would have used one of the supported ones.
if (wrapper == &CrossCompartmentWrapper::singleton)
return &AddonWrapper<CrossCompartmentWrapper>::singleton;
else if (wrapper == &PermissiveXrayXPCWN::singleton)
return &AddonWrapper<PermissiveXrayXPCWN>::singleton;
else if (wrapper == &PermissiveXrayDOM::singleton)
return &AddonWrapper<PermissiveXrayDOM>::singleton;
// |wrapper| is not supported for interposition, so we don't do it.
return wrapper;
}
JSObject*
WrapperFactory::Rewrap(JSContext* cx, HandleObject existing, HandleObject obj)
{
MOZ_ASSERT(!IsWrapper(obj) ||
GetProxyHandler(obj) == &XrayWaiver ||
js::IsWindowProxy(obj),
"wrapped object passed to rewrap");
MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(JS_GetClass(obj)), "trying to wrap a holder");
MOZ_ASSERT(!js::IsWindow(obj));
MOZ_ASSERT(dom::IsJSAPIActive());
// Compute the information we need to select the right wrapper.
JSCompartment* origin = js::GetObjectCompartment(obj);
JSCompartment* target = js::GetContextCompartment(cx);
bool originIsChrome = AccessCheck::isChrome(origin);
bool targetIsChrome = AccessCheck::isChrome(target);
bool originSubsumesTarget = AccessCheck::subsumesConsideringDomain(origin, target);
bool targetSubsumesOrigin = AccessCheck::subsumesConsideringDomain(target, origin);
bool sameOrigin = targetSubsumesOrigin && originSubsumesTarget;
XrayType xrayType = GetXrayType(obj);
const Wrapper* wrapper;
//
// First, handle the special cases.
//
// If UniversalXPConnect is enabled, this is just some dumb mochitest. Use
// a vanilla CCW.
if (xpc::IsUniversalXPConnectEnabled(target)) {
CrashIfNotInAutomation();
wrapper = &CrossCompartmentWrapper::singleton;
}
// Let the SpecialPowers scope make its stuff easily accessible to content.
else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) {
CrashIfNotInAutomation();
wrapper = &CrossCompartmentWrapper::singleton;
}
// Special handling for chrome objects being exposed to content.
else if (originIsChrome && !targetIsChrome) {
// If this is a chrome function being exposed to content, we need to allow
// call (but nothing else). We allow CPOWs that purport to be function's
// here, but only in the content process.
if ((IdentifyStandardInstance(obj) == JSProto_Function ||
(jsipc::IsCPOW(obj) && JS::IsCallable(obj) &&
XRE_IsContentProcess())))
{
wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
}
// For Vanilla JSObjects exposed from chrome to content, we use a wrapper
// that supports __exposedProps__. We'd like to get rid of these eventually,
// but in their current form they don't cause much trouble.
else if (IdentifyStandardInstance(obj) == JSProto_Object) {
wrapper = &ChromeObjectWrapper::singleton;
}
// Otherwise we get an opaque wrapper.
else {
wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
}
}
//
// Now, handle the regular cases.
//
// These are wrappers we can compute using a rule-based approach. In order
// to do so, we need to compute some parameters.
//
else {
// The wrapper is a security wrapper (protecting the wrappee) if and
// only if the target does not subsume the origin.
bool securityWrapper = !targetSubsumesOrigin;
// Xrays are warranted if either the target or the origin don't trust
// each other. This is generally the case, unless the two are same-origin
// and the caller has not requested same-origin Xrays.
//
// Xrays are a bidirectional protection, since it affords clarity to the
// caller and privacy to the callee.
bool sameOriginXrays = CompartmentPrivate::Get(origin)->wantXrays ||
CompartmentPrivate::Get(target)->wantXrays;
bool wantXrays = !sameOrigin || sameOriginXrays;
// If Xrays are warranted, the caller may waive them for non-security
// wrappers (unless explicitly forbidden from doing so).
bool waiveXrays = wantXrays && !securityWrapper &&
CompartmentPrivate::Get(target)->allowWaivers &&
HasWaiveXrayFlag(obj);
// We have slightly different behavior for the case when the object
// being wrapped is in an XBL scope.
bool originIsContentXBLScope = IsContentXBLScope(origin);
wrapper = SelectWrapper(securityWrapper, wantXrays, xrayType, waiveXrays,
originIsContentXBLScope, obj);
// If we want to apply add-on interposition in the target compartment,
// then we try to "upgrade" the wrapper to an interposing one.
if (CompartmentPrivate::Get(target)->scope->HasInterposition())
wrapper = SelectAddonWrapper(cx, obj, wrapper);
}
if (!targetSubsumesOrigin) {
// Do a belt-and-suspenders check against exposing eval()/Function() to
// non-subsuming content.
if (JSFunction* fun = JS_GetObjectFunction(obj)) {
if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) {
NS_WARNING("Trying to expose eval or Function to non-subsuming content!");
wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
}
}
}
DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target);
if (existing)
return Wrapper::Renew(cx, existing, obj, wrapper);
return Wrapper::New(cx, obj, wrapper);
}
// Call WaiveXrayAndWrap when you have a JS object that you don't want to be
// wrapped in an Xray wrapper. cx->compartment is the compartment that will be
// using the returned object. If the object to be wrapped is already in the
// correct compartment, then this returns the unwrapped object.
bool
WrapperFactory::WaiveXrayAndWrap(JSContext* cx, MutableHandleValue vp)
{
if (vp.isPrimitive())
return JS_WrapValue(cx, vp);
RootedObject obj(cx, &vp.toObject());
if (!WaiveXrayAndWrap(cx, &obj))
return false;
vp.setObject(*obj);
return true;
}
bool
WrapperFactory::WaiveXrayAndWrap(JSContext* cx, MutableHandleObject argObj)
{
MOZ_ASSERT(argObj);
RootedObject obj(cx, js::UncheckedUnwrap(argObj));
MOZ_ASSERT(!js::IsWindow(obj));
if (js::IsObjectInContextCompartment(obj, cx)) {
argObj.set(obj);
return true;
}
// Even though waivers have no effect on access by scopes that don't subsume
// the underlying object, good defense-in-depth dictates that we should avoid
// handing out waivers to callers that can't use them. The transitive waiving
// machinery unconditionally calls WaiveXrayAndWrap on return values from
// waived functions, even though the return value might be not be same-origin
// with the function. So if we find ourselves trying to create a waiver for
// |cx|, we should check whether the caller has any business with waivers
// to things in |obj|'s compartment.
JSCompartment* target = js::GetContextCompartment(cx);
JSCompartment* origin = js::GetObjectCompartment(obj);
obj = AllowWaiver(target, origin) ? WaiveXray(cx, obj) : obj;
if (!obj)
return false;
if (!JS_WrapObject(cx, &obj))
return false;
argObj.set(obj);
return true;
}
/*
* Calls to JS_TransplantObject* should go through these helpers here so that
* waivers get fixed up properly.
*/
static bool
FixWaiverAfterTransplant(JSContext* cx, HandleObject oldWaiver, HandleObject newobj)
{
MOZ_ASSERT(Wrapper::wrapperHandler(oldWaiver) == &XrayWaiver);
MOZ_ASSERT(!js::IsCrossCompartmentWrapper(newobj));
// Create a waiver in the new compartment. We know there's not one already
// because we _just_ transplanted, which means that |newobj| was either
// created from scratch, or was previously cross-compartment wrapper (which
// should have no waiver). CreateXrayWaiver asserts this.
JSObject* newWaiver = WrapperFactory::CreateXrayWaiver(cx, newobj);
if (!newWaiver)
return false;
// Update all the cross-compartment references to oldWaiver to point to
// newWaiver.
if (!js::RemapAllWrappersForObject(cx, oldWaiver, newWaiver))
return false;
// There should be no same-compartment references to oldWaiver, and we
// just remapped all cross-compartment references. It's dead, so we can
// remove it from the map.
XPCWrappedNativeScope* scope = ObjectScope(oldWaiver);
JSObject* key = Wrapper::wrappedObject(oldWaiver);
MOZ_ASSERT(scope->mWaiverWrapperMap->Find(key));
scope->mWaiverWrapperMap->Remove(key);
return true;
}
JSObject*
TransplantObject(JSContext* cx, JS::HandleObject origobj, JS::HandleObject target)
{
RootedObject oldWaiver(cx, WrapperFactory::GetXrayWaiver(origobj));
RootedObject newIdentity(cx, JS_TransplantObject(cx, origobj, target));
if (!newIdentity || !oldWaiver)
return newIdentity;
if (!FixWaiverAfterTransplant(cx, oldWaiver, newIdentity))
return nullptr;
return newIdentity;
}
nsIGlobalObject*
NativeGlobal(JSObject* obj)
{
obj = js::GetGlobalForObjectCrossCompartment(obj);
// Every global needs to hold a native as its private or be a
// WebIDL object with an nsISupports DOM object.
MOZ_ASSERT((GetObjectClass(obj)->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS |
JSCLASS_HAS_PRIVATE)) ||
dom::UnwrapDOMObjectToISupports(obj));
nsISupports* native = dom::UnwrapDOMObjectToISupports(obj);
if (!native) {
native = static_cast<nsISupports*>(js::GetObjectPrivate(obj));
MOZ_ASSERT(native);
// In some cases (like for windows) it is a wrapped native,
// in other cases (sandboxes, backstage passes) it's just
// a direct pointer to the native. If it's a wrapped native
// let's unwrap it first.
if (nsCOMPtr<nsIXPConnectWrappedNative> wn = do_QueryInterface(native)) {
native = wn->Native();
}
}
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(native);
MOZ_ASSERT(global, "Native held by global needs to implement nsIGlobalObject!");
return global;
}
} // namespace xpc

View file

@ -0,0 +1,68 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _xpc_WRAPPERFACTORY_H
#define _xpc_WRAPPERFACTORY_H
#include "jswrapper.h"
namespace xpc {
class WrapperFactory {
public:
enum { WAIVE_XRAY_WRAPPER_FLAG = js::Wrapper::LAST_USED_FLAG << 1,
IS_XRAY_WRAPPER_FLAG = WAIVE_XRAY_WRAPPER_FLAG << 1 };
// Return true if any of any of the nested wrappers have the flag set.
static bool HasWrapperFlag(JSObject* wrapper, unsigned flag) {
unsigned flags = 0;
js::UncheckedUnwrap(wrapper, true, &flags);
return !!(flags & flag);
}
static bool IsXrayWrapper(JSObject* wrapper) {
return HasWrapperFlag(wrapper, IS_XRAY_WRAPPER_FLAG);
}
static bool HasWaiveXrayFlag(JSObject* wrapper) {
return HasWrapperFlag(wrapper, WAIVE_XRAY_WRAPPER_FLAG);
}
static bool IsCOW(JSObject* wrapper);
static JSObject* GetXrayWaiver(JS::HandleObject obj);
static JSObject* CreateXrayWaiver(JSContext* cx, JS::HandleObject obj);
static JSObject* WaiveXray(JSContext* cx, JSObject* obj);
// Computes whether we should allow the creation of an Xray waiver from
// |target| to |origin|.
static bool AllowWaiver(JSCompartment* target, JSCompartment* origin);
// Convenience method for the above, operating on a wrapper.
static bool AllowWaiver(JSObject* wrapper);
// Prepare a given object for wrapping in a new compartment.
static void PrepareForWrapping(JSContext* cx,
JS::HandleObject scope,
JS::HandleObject obj,
JS::HandleObject objectPassedToWrap,
JS::MutableHandleObject retObj);
// Rewrap an object that is about to cross compartment boundaries.
static JSObject* Rewrap(JSContext* cx,
JS::HandleObject existing,
JS::HandleObject obj);
// Wrap wrapped object into a waiver wrapper and then re-wrap it.
static bool WaiveXrayAndWrap(JSContext* cx, JS::MutableHandleValue vp);
static bool WaiveXrayAndWrap(JSContext* cx, JS::MutableHandleObject object);
};
extern const js::Wrapper XrayWaiver;
} // namespace xpc
#endif /* _xpc_WRAPPERFACTORY_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,620 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef XrayWrapper_h
#define XrayWrapper_h
#include "mozilla/Attributes.h"
#include "WrapperFactory.h"
#include "jswrapper.h"
#include "js/Proxy.h"
// Slot where Xray functions for Web IDL methods store a pointer to
// the Xray wrapper they're associated with.
#define XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT 0
// Slot where in debug builds Xray functions for Web IDL methods store
// a pointer to their themselves, just so we can assert that they're the
// sort of functions we expect.
#define XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF 1
// Xray wrappers re-resolve the original native properties on the native
// object and always directly access to those properties.
// Because they work so differently from the rest of the wrapper hierarchy,
// we pull them out of the Wrapper inheritance hierarchy and create a
// little world around them.
class nsIPrincipal;
class XPCWrappedNative;
namespace xpc {
namespace XrayUtils {
bool IsXPCWNHolderClass(const JSClass* clasp);
bool CloneExpandoChain(JSContext* cx, JSObject* src, JSObject* dst);
bool
IsTransparent(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id);
JSObject*
GetNativePropertiesObject(JSContext* cx, JSObject* wrapper);
bool
HasNativeProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
bool* hasProp);
} // namespace XrayUtils
enum XrayType {
XrayForDOMObject,
XrayForWrappedNative,
XrayForJSObject,
XrayForOpaqueObject,
NotXray
};
class XrayTraits
{
public:
constexpr XrayTraits() {}
static JSObject* getTargetObject(JSObject* wrapper) {
return js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false);
}
virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) = 0;
// NB: resolveOwnProperty may decide whether or not to cache what it finds
// on the holder. If the result is not cached, the lookup will happen afresh
// for each access, which is the right thing for things like dynamic NodeList
// properties.
virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper,
JS::HandleObject wrapper, JS::HandleObject holder,
JS::HandleId id, JS::MutableHandle<JS::PropertyDescriptor> desc);
bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
JS::ObjectOpResult& result) {
return result.succeed();
}
static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance,
js::ESClass* cls) {
return baseInstance.getBuiltinClass(cx, wrapper, cls);
}
static const char* className(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance) {
return baseInstance.className(cx, wrapper);
}
virtual void preserveWrapper(JSObject* target) = 0;
bool getExpandoObject(JSContext* cx, JS::HandleObject target,
JS::HandleObject consumer, JS::MutableHandleObject expandObject);
JSObject* ensureExpandoObject(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject target);
JSObject* getHolder(JSObject* wrapper);
JSObject* ensureHolder(JSContext* cx, JS::HandleObject wrapper);
virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) = 0;
JSObject* getExpandoChain(JS::HandleObject obj);
bool setExpandoChain(JSContext* cx, JS::HandleObject obj, JS::HandleObject chain);
bool cloneExpandoChain(JSContext* cx, JS::HandleObject dst, JS::HandleObject src);
protected:
// Get the JSClass we should use for our expando object.
virtual const JSClass* getExpandoClass(JSContext* cx,
JS::HandleObject target) const;
private:
bool expandoObjectMatchesConsumer(JSContext* cx, JS::HandleObject expandoObject,
nsIPrincipal* consumerOrigin,
JS::HandleObject exclusiveGlobal);
bool getExpandoObjectInternal(JSContext* cx, JS::HandleObject target,
nsIPrincipal* origin, JSObject* exclusiveGlobal,
JS::MutableHandleObject expandoObject);
JSObject* attachExpandoObject(JSContext* cx, JS::HandleObject target,
nsIPrincipal* origin,
JS::HandleObject exclusiveGlobal);
XrayTraits(XrayTraits&) = delete;
const XrayTraits& operator=(XrayTraits&) = delete;
};
class XPCWrappedNativeXrayTraits : public XrayTraits
{
public:
enum {
HasPrototype = 0
};
static const XrayType Type = XrayForWrappedNative;
virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) override;
virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) override;
bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::Handle<JS::PropertyDescriptor> existingDesc,
JS::ObjectOpResult& result, bool* defined)
{
*defined = false;
return true;
}
virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, unsigned flags,
JS::AutoIdVector& props);
static bool call(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, const js::Wrapper& baseInstance);
static bool construct(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, const js::Wrapper& baseInstance);
static XPCWrappedNative* getWN(JSObject* wrapper);
virtual void preserveWrapper(JSObject* target) override;
virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override;
static const JSClass HolderClass;
static XPCWrappedNativeXrayTraits singleton;
};
class DOMXrayTraits : public XrayTraits
{
public:
constexpr DOMXrayTraits() = default;
enum {
HasPrototype = 1
};
static const XrayType Type = XrayForDOMObject;
virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) override
{
// Xrays for DOM binding objects have a prototype chain that consists of
// Xrays for the prototypes of the DOM binding object (ignoring changes
// in the prototype chain made by script, plugins or XBL). All properties for
// these Xrays are really own properties, either of the instance object or
// of the prototypes.
// FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=1072482
// This should really be:
// MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1");
// but we can't do that yet because XrayUtils::HasNativeProperty calls this.
return true;
}
virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) override;
bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::ObjectOpResult& result);
bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::Handle<JS::PropertyDescriptor> existingDesc,
JS::ObjectOpResult& result, bool* defined);
virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, unsigned flags,
JS::AutoIdVector& props);
static bool call(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, const js::Wrapper& baseInstance);
static bool construct(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, const js::Wrapper& baseInstance);
static bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject target,
JS::MutableHandleObject protop);
virtual void preserveWrapper(JSObject* target) override;
virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override;
static DOMXrayTraits singleton;
protected:
virtual const JSClass* getExpandoClass(JSContext* cx,
JS::HandleObject target) const override;
};
class JSXrayTraits : public XrayTraits
{
public:
enum {
HasPrototype = 1
};
static const XrayType Type = XrayForJSObject;
virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) override
{
MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1");
}
virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) override;
bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::ObjectOpResult& result);
bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::Handle<JS::PropertyDescriptor> existingDesc,
JS::ObjectOpResult& result, bool* defined);
virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, unsigned flags,
JS::AutoIdVector& props);
static bool call(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, const js::Wrapper& baseInstance)
{
JSXrayTraits& self = JSXrayTraits::singleton;
JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper));
if (self.getProtoKey(holder) == JSProto_Function)
return baseInstance.call(cx, wrapper, args);
JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
js::ReportIsNotFunction(cx, v);
return false;
}
static bool construct(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, const js::Wrapper& baseInstance);
bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject target,
JS::MutableHandleObject protop)
{
JS::RootedObject holder(cx, ensureHolder(cx, wrapper));
JSProtoKey key = getProtoKey(holder);
if (isPrototype(holder)) {
JSProtoKey protoKey = js::InheritanceProtoKeyForStandardClass(key);
if (protoKey == JSProto_Null) {
protop.set(nullptr);
return true;
}
key = protoKey;
}
{
JSAutoCompartment ac(cx, target);
if (!JS_GetClassPrototype(cx, key, protop))
return false;
}
return JS_WrapObject(cx, protop);
}
virtual void preserveWrapper(JSObject* target) override {
// In the case of pure JS objects, there is no underlying object, and
// the target is the canonical representation of state. If it gets
// collected, then expandos and such should be collected too. So there's
// nothing to do here.
}
enum {
SLOT_PROTOKEY = 0,
SLOT_ISPROTOTYPE,
SLOT_CONSTRUCTOR_FOR,
SLOT_COUNT
};
virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override;
static JSProtoKey getProtoKey(JSObject* holder) {
int32_t key = js::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32();
return static_cast<JSProtoKey>(key);
}
static bool isPrototype(JSObject* holder) {
return js::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean();
}
static JSProtoKey constructorFor(JSObject* holder) {
int32_t key = js::GetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR).toInt32();
return static_cast<JSProtoKey>(key);
}
// Operates in the wrapper compartment.
static bool getOwnPropertyFromWrapperIfSafe(JSContext* cx,
JS::HandleObject wrapper,
JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc);
// Like the above, but operates in the target compartment.
static bool getOwnPropertyFromTargetIfSafe(JSContext* cx,
JS::HandleObject target,
JS::HandleObject wrapper,
JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc);
static const JSClass HolderClass;
static JSXrayTraits singleton;
};
// These traits are used when the target is not Xrayable and we therefore want
// to make it opaque modulo the usual Xray machinery (like expandos and
// .wrappedJSObject).
class OpaqueXrayTraits : public XrayTraits
{
public:
enum {
HasPrototype = 1
};
static const XrayType Type = XrayForOpaqueObject;
virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) override
{
MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1");
}
virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper,
JS::HandleObject holder, JS::HandleId id,
JS::MutableHandle<JS::PropertyDescriptor> desc) override;
bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::Handle<JS::PropertyDescriptor> existingDesc,
JS::ObjectOpResult& result, bool* defined)
{
*defined = false;
return true;
}
virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, unsigned flags,
JS::AutoIdVector& props)
{
return true;
}
static bool call(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, const js::Wrapper& baseInstance)
{
JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
js::ReportIsNotFunction(cx, v);
return false;
}
static bool construct(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, const js::Wrapper& baseInstance)
{
JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
js::ReportIsNotFunction(cx, v);
return false;
}
bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject target,
JS::MutableHandleObject protop)
{
// Opaque wrappers just get targetGlobal.Object.prototype as their
// prototype. This is preferable to using a null prototype because it
// lets things like |toString| and |__proto__| work.
{
JSAutoCompartment ac(cx, target);
if (!JS_GetClassPrototype(cx, JSProto_Object, protop))
return false;
}
return JS_WrapObject(cx, protop);
}
static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance,
js::ESClass* cls) {
*cls = js::ESClass::Other;
return true;
}
static const char* className(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance) {
return "Opaque";
}
virtual void preserveWrapper(JSObject* target) override { }
virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override
{
return JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
}
static OpaqueXrayTraits singleton;
};
XrayType GetXrayType(JSObject* obj);
XrayTraits* GetXrayTraits(JSObject* obj);
// NB: Base *must* derive from JSProxyHandler
template <typename Base, typename Traits = XPCWrappedNativeXrayTraits >
class XrayWrapper : public Base {
public:
constexpr explicit XrayWrapper(unsigned flags)
: Base(flags | WrapperFactory::IS_XRAY_WRAPPER_FLAG, Traits::HasPrototype)
{ };
/* Standard internal methods. */
virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::ObjectOpResult& result) const override;
virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::AutoIdVector& props) const override;
virtual bool delete_(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id, JS::ObjectOpResult& result) const override;
virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::MutableHandle<JSObject*> objp) const override;
virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::MutableHandleObject protop) const override;
virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject proto, JS::ObjectOpResult& result) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject wrapper, bool* isOrdinary,
JS::MutableHandleObject protop) const override;
virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper,
bool* succeeded) const override;
virtual bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::ObjectOpResult& result) const override;
virtual bool isExtensible(JSContext* cx, JS::Handle<JSObject*> wrapper, bool* extensible) const override;
virtual bool has(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
bool* bp) const override;
virtual bool get(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::HandleValue receiver,
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
virtual bool set(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
JS::ObjectOpResult& result) const override;
virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const override;
virtual bool construct(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const override;
/* SpiderMonkey extensions. */
virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
bool* bp) const override;
virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::AutoIdVector& props) const override;
virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject wapper, js::ESClass* cls) const override;
virtual const char* className(JSContext* cx, JS::HandleObject proxy) const override;
static const XrayWrapper singleton;
private:
template <bool HasPrototype>
typename mozilla::EnableIf<HasPrototype, bool>::Type
getPrototypeHelper(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject target, JS::MutableHandleObject protop) const
{
return Traits::singleton.getPrototype(cx, wrapper, target, protop);
}
template <bool HasPrototype>
typename mozilla::EnableIf<!HasPrototype, bool>::Type
getPrototypeHelper(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject target, JS::MutableHandleObject protop) const
{
return Base::getPrototype(cx, wrapper, protop);
}
bool getPrototypeHelper(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject target, JS::MutableHandleObject protop) const
{
return getPrototypeHelper<Traits::HasPrototype>(cx, wrapper, target,
protop);
}
protected:
bool getPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper, unsigned flags,
JS::AutoIdVector& props) const;
};
#define PermissiveXrayXPCWN xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::XPCWrappedNativeXrayTraits>
#define SecurityXrayXPCWN xpc::XrayWrapper<js::CrossCompartmentSecurityWrapper, xpc::XPCWrappedNativeXrayTraits>
#define PermissiveXrayDOM xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::DOMXrayTraits>
#define SecurityXrayDOM xpc::XrayWrapper<js::CrossCompartmentSecurityWrapper, xpc::DOMXrayTraits>
#define PermissiveXrayJS xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::JSXrayTraits>
#define PermissiveXrayOpaque xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::OpaqueXrayTraits>
extern template class PermissiveXrayXPCWN;
extern template class SecurityXrayXPCWN;
extern template class PermissiveXrayDOM;
extern template class SecurityXrayDOM;
extern template class PermissiveXrayJS;
extern template class PermissiveXrayOpaque;
extern template class PermissiveXrayXPCWN;
class SandboxProxyHandler : public js::Wrapper {
public:
constexpr SandboxProxyHandler() : js::Wrapper(0)
{
}
virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
// We just forward the high-level methods to the BaseProxyHandler versions
// which implement them in terms of lower-level methods.
virtual bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
bool* bp) const override;
virtual bool get(JSContext* cx, JS::Handle<JSObject*> proxy, JS::HandleValue receiver,
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
virtual bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
JS::ObjectOpResult& result) const override;
virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
bool* bp) const override;
virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::AutoIdVector& props) const override;
virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::MutableHandle<JSObject*> objp) const override;
};
extern const SandboxProxyHandler sandboxProxyHandler;
// A proxy handler that lets us wrap callables and invoke them with
// the correct this object, while forwarding all other operations down
// to them directly.
class SandboxCallableProxyHandler : public js::Wrapper {
public:
constexpr SandboxCallableProxyHandler() : js::Wrapper(0)
{
}
virtual bool call(JSContext* cx, JS::Handle<JSObject*> proxy,
const JS::CallArgs& args) const override;
static const size_t SandboxProxySlot = 0;
static inline JSObject* getSandboxProxy(JS::Handle<JSObject*> proxy)
{
return &js::GetProxyExtra(proxy, SandboxProxySlot).toObject();
}
};
extern const SandboxCallableProxyHandler sandboxCallableProxyHandler;
class AutoSetWrapperNotShadowing;
/*
* Slots for Xray expando objects. See comments in XrayWrapper.cpp for details
* of how these get used; we mostly want the value of JSSLOT_EXPANDO_COUNT here.
*/
enum ExpandoSlots {
JSSLOT_EXPANDO_NEXT = 0,
JSSLOT_EXPANDO_ORIGIN,
JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL,
JSSLOT_EXPANDO_PROTOTYPE,
JSSLOT_EXPANDO_COUNT
};
extern const JSClassOps XrayExpandoObjectClassOps;
/*
* Clear the given slot on all Xray expandos for the given object.
*
* No-op when called on non-main threads (where Xrays don't exist).
*/
void
ClearXrayExpandoSlots(JSObject* target, size_t slotIndex);
/*
* Ensure the given wrapper has an expando object and return it. This can
* return null on failure. Will only be called when "wrapper" is an Xray for a
* DOM object.
*/
JSObject*
EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper);
} // namespace xpc
#endif

View file

@ -0,0 +1,41 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS += [
'WrapperFactory.h',
]
UNIFIED_SOURCES += [
'AccessCheck.cpp',
'AddonWrapper.cpp',
'ChromeObjectWrapper.cpp',
'FilteringWrapper.cpp',
'WaiveXrayWrapper.cpp',
'WrapperFactory.cpp',
]
# XrayWrapper needs to be built separately becaue of template instantiations.
SOURCES += [
'XrayWrapper.cpp',
]
# warning C4661 for FilteringWrapper
if CONFIG['_MSC_VER']:
CXXFLAGS += [
'-wd4661', # no suitable definition provided for explicit template instantiation request
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'../../../dom/base',
'../src',
]
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-shadow']