Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
/*
* The Components.Sandbox object.
*/
#include "AccessCheck.h"
#include "jsfriendapi.h"
#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable
#include "js/CharacterEncoding.h"
#include "js/CompilationAndEvaluation.h"
#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot
#include "js/PropertyAndElement.h" // JS_DefineFunction, JS_DefineFunctions, JS_DefineProperty, JS_GetElement, JS_GetProperty, JS_HasProperty, JS_SetProperty, JS_SetPropertyById
#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById, JS_GetPropertyDescriptorById
#include "js/PropertySpec.h"
#include "js/Proxy.h"
#include "js/SourceText.h"
#include "js/StructuredClone.h"
#include "nsContentUtils.h"
#include "nsGlobalWindowInner.h"
#include "nsIException.h" // for nsIStackFrame
#include "nsIScriptContext.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIURI.h"
#include "nsJSUtils.h"
#include "nsNetUtil.h"
#include "ExpandedPrincipal.h"
#include "WrapperFactory.h"
#include "xpcprivate.h"
#include "xpc_make_class.h"
#include "XPCWrapper.h"
#include "Crypto.h"
#include "mozilla/Result.h"
#include "mozilla/dom/AbortControllerBinding.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/BindingCallContext.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/CSSBinding.h"
#include "mozilla/dom/CSSRuleBinding.h"
#include "mozilla/dom/DirectoryBinding.h"
#include "mozilla/dom/DocumentBinding.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/DOMParserBinding.h"
#include "mozilla/dom/DOMTokenListBinding.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/EventBinding.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/FileBinding.h"
#include "mozilla/dom/HeadersBinding.h"
#include "mozilla/dom/IOUtilsBinding.h"
#include "mozilla/dom/InspectorUtilsBinding.h"
#include "mozilla/dom/MessageChannelBinding.h"
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/MIDIInputMapBinding.h"
#include "mozilla/dom/MIDIOutputMapBinding.h"
#include "mozilla/dom/ModuleLoader.h"
#include "mozilla/dom/NodeBinding.h"
#include "mozilla/dom/NodeFilterBinding.h"
#include "mozilla/dom/PathUtilsBinding.h"
#include "mozilla/dom/PerformanceBinding.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/PromiseDebuggingBinding.h"
#include "mozilla/dom/RangeBinding.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/ReadableStreamBinding.h"
#include "mozilla/dom/ResponseBinding.h"
#ifdef MOZ_WEBRTC
# include "mozilla/dom/RTCIdentityProviderRegistrar.h"
#endif
#include "mozilla/dom/FileReaderBinding.h"
#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SelectionBinding.h"
#include "mozilla/dom/StorageManager.h"
#include "mozilla/dom/TextDecoderBinding.h"
#include "mozilla/dom/TextEncoderBinding.h"
#include "mozilla/dom/URLBinding.h"
#include "mozilla/dom/URLSearchParamsBinding.h"
#include "mozilla/dom/XMLHttpRequest.h"
#include "mozilla/dom/WebSocketBinding.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/dom/XMLSerializerBinding.h"
#include "mozilla/dom/FormDataBinding.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/DeferredFinalize.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/Maybe.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/StaticPrefs_extensions.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace JS;
using namespace JS::loader;
using namespace xpc;
using mozilla::dom::DestroyProtoAndIfaceCache;
using mozilla::dom::IndexedDatabaseManager;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SandboxPrivate)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SandboxPrivate)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
NS_IMPL_CYCLE_COLLECTION_UNLINK(mModuleLoader)
tmp->UnlinkObjectsInGlobal();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SandboxPrivate)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mModuleLoader)
tmp->TraverseObjectsInGlobal(cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(SandboxPrivate)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SandboxPrivate)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SandboxPrivate)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptObjectPrincipal)
NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END
class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox,
public nsIXPCScriptable {
public:
// Aren't macros nice?
NS_DECL_ISUPPORTS
NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX
NS_DECL_NSIXPCSCRIPTABLE
public:
nsXPCComponents_utils_Sandbox();
private:
virtual ~nsXPCComponents_utils_Sandbox();
static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper,
JSContext* cx, HandleObject obj,
const CallArgs& args, bool* _retval);
};
already_AddRefed<nsIXPCComponents_utils_Sandbox> xpc::NewSandboxConstructor() {
nsCOMPtr<nsIXPCComponents_utils_Sandbox> sbConstructor =
new nsXPCComponents_utils_Sandbox();
return sbConstructor.forget();
}
static bool SandboxDump(JSContext* cx, unsigned argc, Value* vp) {
if (!nsJSUtils::DumpEnabled()) {
return true;
}
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() == 0) {
return true;
}
RootedString str(cx, ToString(cx, args[0]));
if (!str) {
return false;
}
JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str);
char* cstr = utf8str.get();
if (!cstr) {
return false;
}
#if defined(XP_MACOSX)
// Be nice and convert all \r to \n.
char* c = cstr;
char* cEnd = cstr + strlen(cstr);
while (c < cEnd) {
if (*c == '\r') {
*c = '\n';
}
c++;
}
#endif
MOZ_LOG(nsContentUtils::DOMDumpLog(), mozilla::LogLevel::Debug,
("[Sandbox.Dump] %s", cstr));
#ifdef ANDROID
__android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
#endif
fputs(cstr, stdout);
fflush(stdout);
args.rval().setBoolean(true);
return true;
}
static bool SandboxDebug(JSContext* cx, unsigned argc, Value* vp) {
#ifdef DEBUG
return SandboxDump(cx, argc, vp);
#else
return true;
#endif
}
static bool SandboxImport(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1 || args[0].isPrimitive()) {
XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
return false;
}
RootedString funname(cx);
if (args.length() > 1) {
// Use the second parameter as the function name.
funname = ToString(cx, args[1]);
if (!funname) {
return false;
}
} else {
// NB: funobj must only be used to get the JSFunction out.
RootedObject funobj(cx, &args[0].toObject());
if (js::IsProxy(funobj)) {
funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj);
}
JSAutoRealm ar(cx, funobj);
RootedValue funval(cx, ObjectValue(*funobj));
JSFunction* fun = JS_ValueToFunction(cx, funval);
if (!fun) {
XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
return false;
}
// Use the actual function name as the name.
funname = JS_GetFunctionId(fun);
if (!funname) {
XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
return false;
}
}
JS_MarkCrossZoneIdValue(cx, StringValue(funname));
RootedId id(cx);
if (!JS_StringToId(cx, funname, &id)) {
return false;
}
// We need to resolve the this object, because this function is used
// unbound and should still work and act on the original sandbox.
RootedObject thisObject(cx);
if (!args.computeThis(cx, &thisObject)) {
return false;
}
if (!JS_SetPropertyById(cx, thisObject, id, args[0])) {
return false;
}
args.rval().setUndefined();
return true;
}
bool xpc::SandboxCreateCrypto(JSContext* cx, JS::Handle<JSObject*> obj) {
MOZ_ASSERT(JS_IsGlobalObject(obj));
nsIGlobalObject* native = xpc::NativeGlobal(obj);
MOZ_ASSERT(native);
dom::Crypto* crypto = new dom::Crypto(native);
JS::RootedObject wrapped(cx, crypto->WrapObject(cx, nullptr));
return JS_DefineProperty(cx, obj, "crypto", wrapped, JSPROP_ENUMERATE);
}
#ifdef MOZ_WEBRTC
static bool SandboxCreateRTCIdentityProvider(JSContext* cx,
JS::HandleObject obj) {
MOZ_ASSERT(JS_IsGlobalObject(obj));
nsCOMPtr<nsIGlobalObject> nativeGlobal = xpc::NativeGlobal(obj);
MOZ_ASSERT(nativeGlobal);
dom::RTCIdentityProviderRegistrar* registrar =
new dom::RTCIdentityProviderRegistrar(nativeGlobal);
JS::RootedObject wrapped(cx, registrar->WrapObject(cx, nullptr));
return JS_DefineProperty(cx, obj, "rtcIdentityProvider", wrapped,
JSPROP_ENUMERATE);
}
#endif
static bool SandboxFetch(JSContext* cx, JS::HandleObject scope,
const CallArgs& args) {
if (args.length() < 1) {
JS_ReportErrorASCII(cx, "fetch requires at least 1 argument");
return false;
}
BindingCallContext callCx(cx, "fetch");
RequestOrUSVString request;
if (!request.Init(callCx, args[0], "Argument 1")) {
return false;
}
RootedDictionary<dom::RequestInit> options(cx);
if (!options.Init(callCx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
"Argument 2", false)) {
return false;
}
nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(scope);
if (!global) {
return false;
}
dom::CallerType callerType = nsContentUtils::IsSystemCaller(cx)
? dom::CallerType::System
: dom::CallerType::NonSystem;
ErrorResult rv;
RefPtr<dom::Promise> response = FetchRequest(
global, Constify(request), Constify(options), callerType, rv);
if (rv.MaybeSetPendingException(cx)) {
return false;
}
args.rval().setObject(*response->PromiseObj());
return true;
}
static bool SandboxFetchPromise(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
if (SandboxFetch(cx, scope, args)) {
return true;
}
return ConvertExceptionToPromise(cx, args.rval());
}
bool xpc::SandboxCreateFetch(JSContext* cx, JS::Handle<JSObject*> obj) {
MOZ_ASSERT(JS_IsGlobalObject(obj));
return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) &&
dom::Request_Binding::GetConstructorObject(cx) &&
dom::Response_Binding::GetConstructorObject(cx) &&
dom::Headers_Binding::GetConstructorObject(cx);
}
static bool SandboxCreateStorage(JSContext* cx, JS::HandleObject obj) {
MOZ_ASSERT(JS_IsGlobalObject(obj));
nsIGlobalObject* native = xpc::NativeGlobal(obj);
MOZ_ASSERT(native);
dom::StorageManager* storageManager = new dom::StorageManager(native);
JS::RootedObject wrapped(cx, storageManager->WrapObject(cx, nullptr));
return JS_DefineProperty(cx, obj, "storage", wrapped, JSPROP_ENUMERATE);
}
static bool SandboxStructuredClone(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "structuredClone", 1)) {
return false;
}
RootedDictionary<dom::StructuredSerializeOptions> options(cx);
BindingCallContext callCx(cx, "structuredClone");
if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
"Argument 2", false)) {
return false;
}
nsIGlobalObject* global = CurrentNativeGlobal(cx);
if (!global) {
JS_ReportErrorASCII(cx, "structuredClone: Missing global");
return false;
}
JS::Rooted<JS::Value> result(cx);
ErrorResult rv;
nsContentUtils::StructuredClone(cx, global, args[0], options, &result, rv);
if (rv.MaybeSetPendingException(cx)) {
return false;
}
MOZ_ASSERT_IF(result.isGCThing(),
!JS::GCThingIsMarkedGray(result.toGCCellPtr()));
args.rval().set(result);
return true;
}
bool xpc::SandboxCreateStructuredClone(JSContext* cx, HandleObject obj) {
MOZ_ASSERT(JS_IsGlobalObject(obj));
return JS_DefineFunction(cx, obj, "structuredClone", SandboxStructuredClone,
1, 0);
}
static bool SandboxIsProxy(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorASCII(cx, "Function requires at least 1 argument");
return false;
}
if (!args[0].isObject()) {
args.rval().setBoolean(false);
return true;
}
RootedObject obj(cx, &args[0].toObject());
// CheckedUnwrapStatic is OK here, since we only care about whether
// it's a scripted proxy and the things CheckedUnwrapStatic fails on
// are not.
obj = js::CheckedUnwrapStatic(obj);
if (!obj) {
args.rval().setBoolean(false);
return true;
}
args.rval().setBoolean(js::IsScriptedProxy(obj));
return true;
}
/*
* Expected type of the arguments and the return value:
* function exportFunction(function funToExport,
* object targetScope,
* [optional] object options)
*/
static bool SandboxExportFunction(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorASCII(cx, "Function requires at least 2 arguments");
return false;
}
RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
return ExportFunction(cx, args[0], args[1], options, args.rval());
}
static bool SandboxCreateObjectIn(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
JS_ReportErrorASCII(cx, "Function requires at least 1 argument");
return false;
}
RootedObject optionsObj(cx);
bool calledWithOptions = args.length() > 1;
if (calledWithOptions) {
if (!args[1].isObject()) {
JS_ReportErrorASCII(
cx, "Expected the 2nd argument (options) to be an object");
return false;
}
optionsObj = &args[1].toObject();
}
CreateObjectInOptions options(cx, optionsObj);
if (calledWithOptions && !options.Parse()) {
return false;
}
return xpc::CreateObjectIn(cx, args[0], options, args.rval());
}
static bool SandboxCloneInto(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2) {
JS_ReportErrorASCII(cx, "Function requires at least 2 arguments");
return false;
}
RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
return xpc::CloneInto(cx, args[0], args[1], options, args.rval());
}
static void sandbox_finalize(JS::GCContext* gcx, JSObject* obj) {
SandboxPrivate* priv = SandboxPrivate::GetPrivate(obj);
if (!priv) {
// priv can be null if CreateSandboxObject fails in the middle.
return;
}
priv->ForgetGlobalObject(obj);
DestroyProtoAndIfaceCache(obj);
DeferredFinalize(static_cast<nsIScriptObjectPrincipal*>(priv));
}
static size_t sandbox_moved(JSObject* obj, JSObject* old) {
// Note that this hook can be called before the private pointer is set. In
// this case the SandboxPrivate will not exist yet, so there is nothing to
// do.
SandboxPrivate* priv = SandboxPrivate::GetPrivate(obj);
if (!priv) {
return 0;
}
return priv->ObjectMoved(obj, old);
}
#define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT \
(XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
static const JSClassOps SandboxClassOps = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
JS_NewEnumerateStandardClasses, // newEnumerate
JS_ResolveStandardClass, // resolve
JS_MayResolveStandardClass, // mayResolve
sandbox_finalize, // finalize
nullptr, // call
nullptr, // construct
JS_GlobalObjectTraceHook, // trace
};
static const js::ClassExtension SandboxClassExtension = {
sandbox_moved, // objectMovedOp
};
static const JSClass SandboxClass = {
"Sandbox",
XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
&SandboxClassOps,
JS_NULL_CLASS_SPEC,
&SandboxClassExtension,
JS_NULL_OBJECT_OPS};
static const JSFunctionSpec SandboxFunctions[] = {
JS_FN("dump", SandboxDump, 1, 0), JS_FN("debug", SandboxDebug, 1, 0),
JS_FN("importFunction", SandboxImport, 1, 0), JS_FS_END};
bool xpc::IsSandbox(JSObject* obj) {
const JSClass* clasp = JS::GetClass(obj);
return clasp == &SandboxClass;
}
/***************************************************************************/
nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() = default;
nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() = default;
NS_IMPL_QUERY_INTERFACE(nsXPCComponents_utils_Sandbox,
nsIXPCComponents_utils_Sandbox, nsIXPCScriptable)
NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox)
NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox)
// We use the nsIXPScriptable macros to generate lots of stuff for us.
#define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox
#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox"
#define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT)
#include "xpc_map_end.h" /* This #undef's the above. */
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<Maybe<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 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::MutableHandleIdVector props) const override;
virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::MutableHandleIdVector props) const override;
private:
// Implements the custom getPropertyDescriptor behavior. If the getOwn
// argument is true we only look for "own" properties.
bool getPropertyDescriptorImpl(
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
bool getOwn, JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const;
};
static const SandboxProxyHandler sandboxProxyHandler;
namespace xpc {
bool IsSandboxPrototypeProxy(JSObject* obj) {
return js::IsProxy(obj) && js::GetProxyHandler(obj) == &sandboxProxyHandler;
}
bool IsWebExtensionContentScriptSandbox(JSObject* obj) {
return IsSandbox(obj) &&
CompartmentPrivate::Get(obj)->isWebExtensionContentScript;
}
} // namespace xpc
// 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::GetProxyReservedSlot(proxy, SandboxProxySlot).toObject();
}
};
static const SandboxCallableProxyHandler sandboxCallableProxyHandler;
bool SandboxCallableProxyHandler::call(JSContext* cx,
JS::Handle<JSObject*> proxy,
const JS::CallArgs& args) const {
// We forward the call to our underlying callable.
// Get our SandboxProxyHandler proxy.
RootedObject sandboxProxy(cx, getSandboxProxy(proxy));
MOZ_ASSERT(js::IsProxy(sandboxProxy) &&
js::GetProxyHandler(sandboxProxy) == &sandboxProxyHandler);
// The global of the sandboxProxy is the sandbox global, and the
// target object is the original proto.
RootedObject sandboxGlobal(cx, JS::GetNonCCWObjectGlobal(sandboxProxy));
MOZ_ASSERT(IsSandbox(sandboxGlobal));
// If our this object is the sandbox global, we call with this set to the
// original proto instead.
//
// There are two different ways we can compute |this|. If we use
// JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the
// caller, which may be undefined if a global function was invoked without
// an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this|
// in |vp| will be coerced to the global, which is not the correct
// behavior in ES5 strict mode. And we have no way to compute strictness
// here.
//
// The naive approach is simply to use JS_THIS_VALUE here. If |this| was
// explicit, we can remap it appropriately. If it was implicit, then we
// leave it as undefined, and let the callee sort it out. Since the callee
// is generally in the same compartment as its global (eg the Window's
// compartment, not the Sandbox's), the callee will generally compute the
// correct |this|.
//
// However, this breaks down in the Xray case. If the sandboxPrototype
// is an Xray wrapper, then we'll end up reifying the native methods in
// the Sandbox's scope, which means that they'll compute |this| to be the
// Sandbox, breaking old-style XPC_WN_CallMethod methods.
//
// Luckily, the intent of Xrays is to provide a vanilla view of a foreign
// DOM interface, which means that we don't care about script-enacted
// strictness in the prototype's home compartment. Indeed, since DOM
// methods are always non-strict, we can just assume non-strict semantics
// if the sandboxPrototype is an Xray Wrapper, which lets us appropriately
// remap |this|.
bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy);
RootedValue thisVal(cx, args.thisv());
if (isXray) {
RootedObject thisObject(cx);
if (!args.computeThis(cx, &thisObject)) {
return false;
}
thisVal.setObject(*thisObject);
}
if (thisVal == ObjectValue(*sandboxGlobal)) {
thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy));
}
RootedValue func(cx, js::GetProxyPrivate(proxy));
return JS::Call(cx, thisVal, func, args, args.rval());
}
/*
* Wrap a callable such that if we're called with oldThisObj as the
* "this" we will instead call it with newThisObj as the this.
*/
static JSObject* WrapCallable(JSContext* cx, HandleObject callable,
HandleObject sandboxProtoProxy) {
MOZ_ASSERT(JS::IsCallable(callable));
// Our proxy is wrapping the callable. So we need to use the
// callable as the private. We put the given sandboxProtoProxy in
// an extra slot, and our call() hook depends on that.
MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) &&
js::GetProxyHandler(sandboxProtoProxy) == &sandboxProxyHandler);
RootedValue priv(cx, ObjectValue(*callable));
// We want to claim to have the same proto as our wrapped callable, so set
// ourselves up with a lazy proto.
js::ProxyOptions options;
options.setLazyProto(true);
JSObject* obj = js::NewProxyObject(cx, &sandboxCallableProxyHandler, priv,
nullptr, options);
if (obj) {
js::SetProxyReservedSlot(obj, SandboxCallableProxyHandler::SandboxProxySlot,
ObjectValue(*sandboxProtoProxy));
}
return obj;
}
bool WrapAccessorFunction(JSContext* cx, MutableHandleObject accessor,
HandleObject sandboxProtoProxy) {
if (!accessor) {
return true;
}
accessor.set(WrapCallable(cx, accessor, sandboxProtoProxy));
return !!accessor;
}
static bool IsMaybeWrappedDOMConstructor(JSObject* obj) {
// We really care about the underlying object here, which might be wrapped in
// cross-compartment wrappers. CheckedUnwrapStatic is fine, since we just
// care whether it's a DOM constructor.
obj = js::CheckedUnwrapStatic(obj);
if (!obj) {
return false;
}
return dom::IsDOMConstructor(obj);
}
bool SandboxProxyHandler::getPropertyDescriptorImpl(
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
bool getOwn, MutableHandle<Maybe<PropertyDescriptor>> desc_) const {
JS::RootedObject obj(cx, wrappedObject(proxy));
MOZ_ASSERT(JS::GetCompartment(obj) == JS::GetCompartment(proxy));
if (getOwn) {
if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, desc_)) {
return false;
}
} else {
Rooted<JSObject*> holder(cx);
if (!JS_GetPropertyDescriptorById(cx, obj, id, desc_, &holder)) {
return false;
}
}
if (desc_.isNothing()) {
return true;
}
Rooted<PropertyDescriptor> desc(cx, *desc_);
// Now fix up the getter/setter/value as needed.
if (desc.hasGetter() && !WrapAccessorFunction(cx, desc.getter(), proxy)) {
return false;
}
if (desc.hasSetter() && !WrapAccessorFunction(cx, desc.setter(), proxy)) {
return false;
}
if (desc.hasValue() && desc.value().isObject()) {
RootedObject val(cx, &desc.value().toObject());
if (JS::IsCallable(val) &&
// Don't wrap DOM constructors: they don't care about the "this"
// they're invoked with anyway, being constructors. And if we wrap
// them here we break invariants like Node == Node and whatnot.
!IsMaybeWrappedDOMConstructor(val)) {
val = WrapCallable(cx, val, proxy);
if (!val) {
return false;
}
desc.value().setObject(*val);
}
}
desc_.set(Some(desc.get()));
return true;
}
bool SandboxProxyHandler::getOwnPropertyDescriptor(
JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
MutableHandle<Maybe<PropertyDescriptor>> desc) const {
return getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ true, desc);
}
/*
* Reuse the BaseProxyHandler versions of the derived traps that are implemented
* in terms of the fundamental traps.
*/
bool SandboxProxyHandler::has(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id, bool* bp) const {
// This uses JS_GetPropertyDescriptorById for backward compatibility.
Rooted<Maybe<PropertyDescriptor>> desc(cx);
if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) {
return false;
}
*bp = desc.isSome();
return true;
}
bool SandboxProxyHandler::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id, bool* bp) const {
return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
}
bool SandboxProxyHandler::get(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<JS::Value> receiver,
JS::Handle<jsid> id,
JS::MutableHandle<Value> vp) const {
// This uses JS_GetPropertyDescriptorById for backward compatibility.
Rooted<Maybe<PropertyDescriptor>> desc(cx);
if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) {
return false;
}
if (desc.isNothing()) {
vp.setUndefined();
return true;
} else {
desc->assertComplete();
}
// Everything after here follows [[Get]] for ordinary objects.
if (desc->isDataDescriptor()) {
vp.set(desc->value());
return true;
}
MOZ_ASSERT(desc->isAccessorDescriptor());
RootedObject getter(cx, desc->getter());
if (!getter) {
vp.setUndefined();
return true;
}
return Call(cx, receiver, getter, HandleValueArray::empty(), vp);
}
bool SandboxProxyHandler::set(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id, JS::Handle<Value> v,
JS::Handle<Value> receiver,
JS::ObjectOpResult& result) const {
return BaseProxyHandler::set(cx, proxy, id, v, receiver, result);
}
bool SandboxProxyHandler::getOwnEnumerablePropertyKeys(
JSContext* cx, JS::Handle<JSObject*> proxy,
MutableHandleIdVector props) const {
return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
}
bool SandboxProxyHandler::enumerate(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::MutableHandleIdVector props) const {
return BaseProxyHandler::enumerate(cx, proxy, props);
}
bool xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj) {
uint32_t length;
bool ok = JS::GetArrayLength(cx, obj, &length);
NS_ENSURE_TRUE(ok, false);
for (uint32_t i = 0; i < length; i++) {
RootedValue nameValue(cx);
ok = JS_GetElement(cx, obj, i, &nameValue);
NS_ENSURE_TRUE(ok, false);
if (!nameValue.isString()) {
JS_ReportErrorASCII(cx, "Property names must be strings");
return false;
}
JSLinearString* nameStr = JS_EnsureLinearString(cx, nameValue.toString());
if (!nameStr) {
return false;
}
if (JS_LinearStringEqualsLiteral(nameStr, "AbortController")) {
AbortController = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Blob")) {
Blob = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "ChromeUtils")) {
ChromeUtils = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "CSS")) {
CSS = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "CSSRule")) {
CSSRule = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Document")) {
Document = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Directory")) {
Directory = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "DOMException")) {
DOMException = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "DOMParser")) {
DOMParser = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "DOMTokenList")) {
DOMTokenList = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Element")) {
Element = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Event")) {
Event = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "File")) {
File = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "FileReader")) {
FileReader = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "FormData")) {
FormData = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Headers")) {
Headers = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "IOUtils")) {
IOUtils = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "InspectorUtils")) {
InspectorUtils = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "MessageChannel")) {
MessageChannel = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "MIDIInputMap")) {
MIDIInputMap = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "MIDIOutputMap")) {
MIDIOutputMap = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Node")) {
Node = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "NodeFilter")) {
NodeFilter = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "PathUtils")) {
PathUtils = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Performance")) {
Performance = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "PromiseDebugging")) {
PromiseDebugging = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Range")) {
Range = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Selection")) {
Selection = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "TextDecoder")) {
TextDecoder = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "TextEncoder")) {
TextEncoder = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "URL")) {
URL = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "URLSearchParams")) {
URLSearchParams = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "XMLHttpRequest")) {
XMLHttpRequest = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "WebSocket")) {
WebSocket = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "Window")) {
Window = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "XMLSerializer")) {
XMLSerializer = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "ReadableStream")) {
ReadableStream = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "atob")) {
atob = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "btoa")) {
btoa = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "caches")) {
caches = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "crypto")) {
crypto = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "fetch")) {
fetch = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "storage")) {
storage = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "structuredClone")) {
structuredClone = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "indexedDB")) {
indexedDB = true;
} else if (JS_LinearStringEqualsLiteral(nameStr, "isSecureContext")) {
isSecureContext = true;
#ifdef MOZ_WEBRTC
} else if (JS_LinearStringEqualsLiteral(nameStr, "rtcIdentityProvider")) {
rtcIdentityProvider = true;
#endif
} else {
RootedString nameStr(cx, nameValue.toString());
JS::UniqueChars name = JS_EncodeStringToUTF8(cx, nameStr);
if (!name) {
return false;
}
JS_ReportErrorUTF8(cx, "Unknown property name: %s", name.get());
return false;
}
}
return true;
}
bool xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj) {
MOZ_ASSERT(js::GetContextCompartment(cx) == JS::GetCompartment(obj));
// Properties will be exposed to System automatically but not to Sandboxes
// if |[Exposed=System]| is specified.
// This function holds common properties not exposed automatically but able
// to be requested either in |Cu.importGlobalProperties| or
// |wantGlobalProperties| of a sandbox.
if (AbortController &&
!dom::AbortController_Binding::GetConstructorObject(cx)) {
return false;
}
if (Blob && !dom::Blob_Binding::GetConstructorObject(cx)) return false;
if (ChromeUtils && !dom::ChromeUtils_Binding::GetConstructorObject(cx)) {
return false;
}
if (CSS && !dom::CSS_Binding::GetConstructorObject(cx)) {
return false;
}
if (CSSRule && !dom::CSSRule_Binding::GetConstructorObject(cx)) {
return false;
}
if (Directory && !dom::Directory_Binding::GetConstructorObject(cx))
return false;
if (Document && !dom::Document_Binding::GetConstructorObject(cx)) {
return false;
}
if (DOMException && !dom::DOMException_Binding::GetConstructorObject(cx)) {
return false;
}
if (DOMParser && !dom::DOMParser_Binding::GetConstructorObject(cx)) {
return false;
}
if (DOMTokenList && !dom::DOMTokenList_Binding::GetConstructorObject(cx)) {
return false;
}
if (Element && !dom::Element_Binding::GetConstructorObject(cx)) return false;
if (Event && !dom::Event_Binding::GetConstructorObject(cx)) return false;
if (File && !dom::File_Binding::GetConstructorObject(cx)) return false;
if (FileReader && !dom::FileReader_Binding::GetConstructorObject(cx)) {
return false;
}
if (FormData && !dom::FormData_Binding::GetConstructorObject(cx))
return false;
if (Headers && !dom::Headers_Binding::GetConstructorObject(cx)) {
return false;
}
if (IOUtils && !dom::IOUtils_Binding::GetConstructorObject(cx)) {
return false;
}
if (InspectorUtils && !dom::InspectorUtils_Binding::GetConstructorObject(cx))
return false;
if (MessageChannel &&
(!dom::MessageChannel_Binding::GetConstructorObject(cx) ||
!dom::MessagePort_Binding::GetConstructorObject(cx)))
return false;
if (MIDIInputMap && !dom::MIDIInputMap_Binding::GetConstructorObject(cx)) {
return false;
}
if (MIDIOutputMap && !dom::MIDIOutputMap_Binding::GetConstructorObject(cx)) {
return false;
}
if (Node && !dom::Node_Binding::GetConstructorObject(cx)) {
return false;
}
if (NodeFilter && !dom::NodeFilter_Binding::GetConstructorObject(cx)) {
return false;
}
if (PathUtils && !dom::PathUtils_Binding::GetConstructorObject(cx)) {
return false;
}
if (Performance && !dom::Performance_Binding::GetConstructorObject(cx)) {
return false;
}
if (PromiseDebugging &&
!dom::PromiseDebugging_Binding::GetConstructorObject(cx)) {
return false;
}
if (Range && !dom::Range_Binding::GetConstructorObject(cx)) {
return false;
}
if (Selection && !dom::Selection_Binding::GetConstructorObject(cx)) {
return false;
}
if (TextDecoder && !dom::TextDecoder_Binding::GetConstructorObject(cx))
return false;
if (TextEncoder && !dom::TextEncoder_Binding::GetConstructorObject(cx))
return false;
if (URL && !dom::URL_Binding::GetConstructorObject(cx)) return false;
if (URLSearchParams &&
!dom::URLSearchParams_Binding::GetConstructorObject(cx))
return false;
if (XMLHttpRequest && !dom::XMLHttpRequest_Binding::GetConstructorObject(cx))
return false;
if (WebSocket && !dom::WebSocket_Binding::GetConstructorObject(cx))
return false;
if (Window && !dom::Window_Binding::GetConstructorObject(cx)) return false;
if (XMLSerializer && !dom::XMLSerializer_Binding::GetConstructorObject(cx))
return false;
if (ReadableStream && !dom::ReadableStream_Binding::GetConstructorObject(cx))
return false;
if (atob && !JS_DefineFunction(cx, obj, "atob", Atob, 1, 0)) return false;
if (btoa && !JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0)) return false;
if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) {
return false;
}
if (crypto && !SandboxCreateCrypto(cx, obj)) {
return false;
}
if (fetch && !SandboxCreateFetch(cx, obj)) {
return false;
}
if (storage && !SandboxCreateStorage(cx, obj)) {
return false;
}
if (structuredClone && !SandboxCreateStructuredClone(cx, obj)) {
return false;
}
// Note that isSecureContext here doesn't mean the context is actually secure
// - just that the caller wants the property defined
if (isSecureContext) {
bool hasSecureContext = IsSecureContextOrObjectIsFromSecureContext(cx, obj);
JS::Rooted<JS::Value> secureJsValue(cx, JS::BooleanValue(hasSecureContext));
return JS_DefineProperty(cx, obj, "isSecureContext", secureJsValue,
JSPROP_ENUMERATE);
}
#ifdef MOZ_WEBRTC
if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj)) {
return false;
}
#endif
return true;
}
bool xpc::GlobalProperties::DefineInXPCComponents(JSContext* cx,
JS::HandleObject obj) {
if (indexedDB && !IndexedDatabaseManager::DefineIndexedDB(cx, obj))
return false;
return Define(cx, obj);
}
bool xpc::GlobalProperties::DefineInSandbox(JSContext* cx,
JS::HandleObject obj) {
MOZ_ASSERT(IsSandbox(obj));
MOZ_ASSERT(js::GetContextCompartment(cx) == JS::GetCompartment(obj));
if (indexedDB && !(IndexedDatabaseManager::ResolveSandboxBinding(cx) &&
IndexedDatabaseManager::DefineIndexedDB(cx, obj)))
return false;
return Define(cx, obj);
}
/**
* If enabled, apply the extension base CSP, then apply the
* content script CSP which will either be a default or one
* provided by the extension in its manifest.
*/
nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) {
nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
if (!principal) {
return NS_OK;
}
auto* basePrin = BasePrincipal::Cast(principal);
// We only get an addonPolicy if the principal is an
// expanded principal with an extension principal in it.
auto* addonPolicy = basePrin->ContentScriptAddonPolicy();
if (!addonPolicy) {
return NS_OK;
}
// For backwards compatibility, content scripts have no CSP
// in manifest v2. Only apply content script CSP to V3 or later.
if (addonPolicy->ManifestVersion() < 3) {
return NS_OK;
}
nsString url;
MOZ_TRY_VAR(url, addonPolicy->GetURL(u""_ns));
nsCOMPtr<nsIURI> selfURI;
MOZ_TRY(NS_NewURI(getter_AddRefs(selfURI), url));
const nsAString& baseCSP = addonPolicy->BaseCSP();
// If we got here, we're definitly an expanded principal.
auto expanded = basePrin->As<ExpandedPrincipal>();
nsCOMPtr<nsIContentSecurityPolicy> csp;
#ifdef MOZ_DEBUG
// Bug 1548468: Move CSP off ExpandedPrincipal
expanded->GetCsp(getter_AddRefs(csp));
if (csp) {
uint32_t count = 0;
csp->GetPolicyCount(&count);
if (count > 0) {
// Ensure that the policy was not already added.
nsAutoString parsedPolicyStr;
for (uint32_t i = 0; i < count; i++) {
csp->GetPolicyString(i, parsedPolicyStr);
MOZ_ASSERT(!parsedPolicyStr.Equals(baseCSP));
}
}
}
#endif
// Create a clone of the expanded principal to be used for the call to
// SetRequestContextWithPrincipal (to prevent the CSP and expanded
// principal instances to keep each other alive indefinitely, see
//
// This may not be necessary anymore once Bug 1548468 will move CSP
// off ExpandedPrincipal.
RefPtr<ExpandedPrincipal> clonedPrincipal = ExpandedPrincipal::Create(
expanded->AllowList(), expanded->OriginAttributesRef());
MOZ_ASSERT(clonedPrincipal);
csp = new nsCSPContext();
MOZ_TRY(
csp->SetRequestContextWithPrincipal(clonedPrincipal, selfURI, u""_ns, 0));
MOZ_TRY(csp->AppendPolicy(baseCSP, false, false));
expanded->SetCsp(csp);
return NS_OK;
}
nsresult xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp,
nsISupports* prinOrSop,
SandboxOptions& options) {
// Create the sandbox global object
nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(prinOrSop);
if (obj) {
nsGlobalWindowInner* window =
WindowOrNull(js::UncheckedUnwrap(obj->GetGlobalJSObject(), false));
// If we have a secure context window inherit from it's parent
if (window && window->IsSecureContext()) {
options.forceSecureContext = true;
}
}
if (!principal) {
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(prinOrSop);
if (sop) {
principal = sop->GetPrincipal();
} else {
RefPtr<NullPrincipal> nullPrin =
NullPrincipal::CreateWithoutOriginAttributes();
principal = nullPrin;
}
}
MOZ_ASSERT(principal);
JS::RealmOptions realmOptions;
auto& creationOptions = realmOptions.creationOptions();
bool isSystemPrincipal = principal->IsSystemPrincipal();
if (isSystemPrincipal) {
options.forceSecureContext = true;
}
// If we are able to see [SecureContext] API code
if (options.forceSecureContext) {
creationOptions.setSecureContext(true);
}
xpc::SetPrefableRealmOptions(realmOptions);
if (options.sameZoneAs) {
creationOptions.setNewCompartmentInExistingZone(
js::UncheckedUnwrap(options.sameZoneAs));
} else if (options.freshZone) {
creationOptions.setNewCompartmentAndZone();
} else if (isSystemPrincipal && !options.invisibleToDebugger &&
!options.freshCompartment) {
// Use a shared system compartment for system-principal sandboxes that don't
// require invisibleToDebugger (this is a compartment property, see bug
// 1482215).
creationOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
} else {
creationOptions.setNewCompartmentInSystemZone();
}
creationOptions.setInvisibleToDebugger(options.invisibleToDebugger)
.setTrace(TraceXPCGlobal);
realmOptions.behaviors().setDiscardSource(options.discardSource);
if (isSystemPrincipal) {
realmOptions.behaviors().setClampAndJitterTime(false);
}
const JSClass* clasp = &SandboxClass;
RootedObject sandbox(
cx, xpc::CreateGlobalObject(cx, clasp, principal, realmOptions));
if (!sandbox) {
return NS_ERROR_FAILURE;
}
// Use exclusive expandos for non-system-principal sandboxes.
bool hasExclusiveExpandos = !isSystemPrincipal;
// Set up the wantXrays flag, which indicates whether xrays are desired even
// for same-origin access.
//
// This flag has historically been ignored for chrome sandboxes due to
// quirks in the wrapping implementation that have now been removed. Indeed,
// same-origin Xrays for chrome->chrome access seems a bit superfluous.
// Arguably we should just flip the default for chrome and still honor the
// flag, but such a change would break code in subtle ways for minimal
// benefit. So we just switch it off here.
bool wantXrays = AccessCheck::isChrome(sandbox) ? false : options.wantXrays;
if (creationOptions.compartmentSpecifier() ==
JS::CompartmentSpecifier::ExistingCompartment) {
// Make sure the compartment we're reusing has flags that match what we
// would set on a new compartment.
CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox);
MOZ_RELEASE_ASSERT(priv->allowWaivers == options.allowWaivers);
MOZ_RELEASE_ASSERT(priv->isWebExtensionContentScript ==
options.isWebExtensionContentScript);
MOZ_RELEASE_ASSERT(priv->isUAWidgetCompartment == options.isUAWidgetScope);
MOZ_RELEASE_ASSERT(priv->hasExclusiveExpandos == hasExclusiveExpandos);
MOZ_RELEASE_ASSERT(priv->wantXrays == wantXrays);
} else {
CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox);
priv->allowWaivers = options.allowWaivers;
priv->isWebExtensionContentScript = options.isWebExtensionContentScript;
priv->isUAWidgetCompartment = options.isUAWidgetScope;
priv->hasExclusiveExpandos = hasExclusiveExpandos;
priv->wantXrays = wantXrays;
}
{
JSAutoRealm ar(cx, sandbox);
// This creates a SandboxPrivate and passes ownership of it to |sandbox|.
SandboxPrivate::Create(principal, sandbox);
// Ensure |Object.prototype| is instantiated before prototype-
// splicing below.
if (!JS::GetRealmObjectPrototype(cx)) {
return NS_ERROR_XPC_UNEXPECTED;
}
if (options.proto) {
bool ok = JS_WrapObject(cx, &options.proto);
if (!ok) {
return NS_ERROR_XPC_UNEXPECTED;
}
// Now check what sort of thing we've got in |proto|, and figure out
// if we need a SandboxProxyHandler.
//
// Note that, in the case of a window, we can't require that the
// Sandbox subsumes the prototype, because we have to hold our
// reference to it via an outer window, and the window may navigate
// at any time. So we have to handle that case separately.
bool useSandboxProxy =
!!WindowOrNull(js::UncheckedUnwrap(options.proto, false));
if (!useSandboxProxy) {
// We just wrapped options.proto into the compartment of whatever Realm
// is on the cx, so use that same realm for the CheckedUnwrapDynamic
// call.
JSObject* unwrappedProto =
js::CheckedUnwrapDynamic(options.proto, cx, false);
if (!unwrappedProto) {
JS_ReportErrorASCII(cx, "Sandbox must subsume sandboxPrototype");
return NS_ERROR_INVALID_ARG;
}
const JSClass* unwrappedClass = JS::GetClass(unwrappedProto);
useSandboxProxy = unwrappedClass->isWrappedNative() ||
mozilla::dom::IsDOMClass(unwrappedClass);
}
if (useSandboxProxy) {
// Wrap it up in a proxy that will do the right thing in terms
// of this-binding for methods.
RootedValue priv(cx, ObjectValue(*options.proto));
options.proto =
js::NewProxyObject(cx, &sandboxProxyHandler, priv, nullptr);
if (!options.proto) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
ok = JS_SetPrototype(cx, sandbox, options.proto);
if (!ok) {
return NS_ERROR_XPC_UNEXPECTED;
}
}
bool allowComponents = principal->IsSystemPrincipal();
if (options.wantComponents && allowComponents) {
if (!ObjectScope(sandbox)->AttachComponentsObject(cx)) {
return NS_ERROR_XPC_UNEXPECTED;
}
if (!ObjectScope(sandbox)->AttachJSServices(cx)) {
return NS_ERROR_XPC_UNEXPECTED;
}
}
if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox)) {
return NS_ERROR_XPC_UNEXPECTED;
}
if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) {
return NS_ERROR_XPC_UNEXPECTED;
}
if (options.wantExportHelpers &&
(!JS_DefineFunction(cx, sandbox, "exportFunction",
SandboxExportFunction, 3, 0) ||
!JS_DefineFunction(cx, sandbox, "createObjectIn",
SandboxCreateObjectIn, 2, 0) ||
!JS_DefineFunction(cx, sandbox, "cloneInto", SandboxCloneInto, 3, 0) ||
!JS_DefineFunction(cx, sandbox, "isProxy", SandboxIsProxy, 1, 0)))
return NS_ERROR_XPC_UNEXPECTED;
if (!options.globalProperties.DefineInSandbox(cx, sandbox)) {
return NS_ERROR_XPC_UNEXPECTED;
}
}
// We handle the case where the context isn't in a compartment for the
// benefit of UnprivilegedJunkScope().
vp.setObject(*sandbox);
if (js::GetContextCompartment(cx) && !JS_WrapValue(cx, vp)) {
return NS_ERROR_UNEXPECTED;
}
// Set the location information for the new global, so that tools like
// about:memory may use that information
xpc::SetLocationForGlobal(sandbox, options.sandboxName);
xpc::SetSandboxMetadata(cx, sandbox, options.metadata);
JSAutoRealm ar(cx, sandbox);
JS_FireOnNewGlobalObject(cx, sandbox);
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative* wrapper,
JSContext* cx, JSObject* objArg,
const CallArgs& args, bool* _retval) {
RootedObject obj(cx, objArg);
return CallOrConstruct(wrapper, cx, obj, args, _retval);
}
NS_IMETHODIMP
nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative* wrapper,
JSContext* cx, JSObject* objArg,
const CallArgs& args, bool* _retval) {
RootedObject obj(cx, objArg);
return CallOrConstruct(wrapper, cx, obj, args, _retval);
}
/*
* For sandbox constructor the first argument can be a URI string in which case
* we use the related Content Principal for the sandbox.
*/
bool ParsePrincipal(JSContext* cx, HandleString contentUrl,
const OriginAttributes& aAttrs, nsIPrincipal** principal) {
MOZ_ASSERT(principal);
MOZ_ASSERT(contentUrl);
nsCOMPtr<nsIURI> uri;
nsAutoJSString contentStr;
NS_ENSURE_TRUE(contentStr.init(cx, contentUrl), false);