js/xpconnect/src/Sandbox.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /*
michael@0 8 * The Components.Sandbox object.
michael@0 9 */
michael@0 10
michael@0 11 #include "AccessCheck.h"
michael@0 12 #include "jsfriendapi.h"
michael@0 13 #include "jsproxy.h"
michael@0 14 #include "js/OldDebugAPI.h"
michael@0 15 #include "js/StructuredClone.h"
michael@0 16 #include "nsContentUtils.h"
michael@0 17 #include "nsCxPusher.h"
michael@0 18 #include "nsGlobalWindow.h"
michael@0 19 #include "nsIScriptContext.h"
michael@0 20 #include "nsIScriptObjectPrincipal.h"
michael@0 21 #include "nsIScriptSecurityManager.h"
michael@0 22 #include "nsIURI.h"
michael@0 23 #include "nsJSUtils.h"
michael@0 24 #include "nsNetUtil.h"
michael@0 25 #include "nsPrincipal.h"
michael@0 26 #include "nsXMLHttpRequest.h"
michael@0 27 #include "WrapperFactory.h"
michael@0 28 #include "xpcprivate.h"
michael@0 29 #include "XPCQuickStubs.h"
michael@0 30 #include "XPCWrapper.h"
michael@0 31 #include "XrayWrapper.h"
michael@0 32 #include "mozilla/dom/BindingUtils.h"
michael@0 33 #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
michael@0 34 #include "mozilla/dom/PromiseBinding.h"
michael@0 35 #include "mozilla/dom/TextDecoderBinding.h"
michael@0 36 #include "mozilla/dom/TextEncoderBinding.h"
michael@0 37 #include "mozilla/dom/URLBinding.h"
michael@0 38
michael@0 39 using namespace mozilla;
michael@0 40 using namespace JS;
michael@0 41 using namespace js;
michael@0 42 using namespace xpc;
michael@0 43
michael@0 44 using mozilla::dom::DestroyProtoAndIfaceCache;
michael@0 45 using mozilla::dom::indexedDB::IndexedDatabaseManager;
michael@0 46
michael@0 47 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(SandboxPrivate)
michael@0 48 NS_IMPL_CYCLE_COLLECTING_ADDREF(SandboxPrivate)
michael@0 49 NS_IMPL_CYCLE_COLLECTING_RELEASE(SandboxPrivate)
michael@0 50 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SandboxPrivate)
michael@0 51 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
michael@0 52 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptObjectPrincipal)
michael@0 53 NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
michael@0 54 NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
michael@0 55 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
michael@0 56 NS_INTERFACE_MAP_END
michael@0 57
michael@0 58 const char kScriptSecurityManagerContractID[] = NS_SCRIPTSECURITYMANAGER_CONTRACTID;
michael@0 59
michael@0 60 class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox,
michael@0 61 public nsIXPCScriptable
michael@0 62 {
michael@0 63 public:
michael@0 64 // Aren't macros nice?
michael@0 65 NS_DECL_ISUPPORTS
michael@0 66 NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX
michael@0 67 NS_DECL_NSIXPCSCRIPTABLE
michael@0 68
michael@0 69 public:
michael@0 70 nsXPCComponents_utils_Sandbox();
michael@0 71 virtual ~nsXPCComponents_utils_Sandbox();
michael@0 72
michael@0 73 private:
michael@0 74 static nsresult CallOrConstruct(nsIXPConnectWrappedNative *wrapper,
michael@0 75 JSContext *cx, HandleObject obj,
michael@0 76 const CallArgs &args, bool *_retval);
michael@0 77 };
michael@0 78
michael@0 79 already_AddRefed<nsIXPCComponents_utils_Sandbox>
michael@0 80 xpc::NewSandboxConstructor()
michael@0 81 {
michael@0 82 nsCOMPtr<nsIXPCComponents_utils_Sandbox> sbConstructor =
michael@0 83 new nsXPCComponents_utils_Sandbox();
michael@0 84 return sbConstructor.forget();
michael@0 85 }
michael@0 86
michael@0 87 static bool
michael@0 88 SandboxDump(JSContext *cx, unsigned argc, jsval *vp)
michael@0 89 {
michael@0 90 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 91
michael@0 92 if (args.length() == 0)
michael@0 93 return true;
michael@0 94
michael@0 95 RootedString str(cx, ToString(cx, args[0]));
michael@0 96 if (!str)
michael@0 97 return false;
michael@0 98
michael@0 99 size_t length;
michael@0 100 const jschar *chars = JS_GetStringCharsZAndLength(cx, str, &length);
michael@0 101 if (!chars)
michael@0 102 return false;
michael@0 103
michael@0 104 nsDependentString wstr(chars, length);
michael@0 105 char *cstr = ToNewUTF8String(wstr);
michael@0 106 if (!cstr)
michael@0 107 return false;
michael@0 108
michael@0 109 #if defined(XP_MACOSX)
michael@0 110 // Be nice and convert all \r to \n.
michael@0 111 char *c = cstr, *cEnd = cstr + strlen(cstr);
michael@0 112 while (c < cEnd) {
michael@0 113 if (*c == '\r')
michael@0 114 *c = '\n';
michael@0 115 c++;
michael@0 116 }
michael@0 117 #endif
michael@0 118 #ifdef ANDROID
michael@0 119 __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr);
michael@0 120 #endif
michael@0 121
michael@0 122 fputs(cstr, stdout);
michael@0 123 fflush(stdout);
michael@0 124 NS_Free(cstr);
michael@0 125 args.rval().setBoolean(true);
michael@0 126 return true;
michael@0 127 }
michael@0 128
michael@0 129 static bool
michael@0 130 SandboxDebug(JSContext *cx, unsigned argc, jsval *vp)
michael@0 131 {
michael@0 132 #ifdef DEBUG
michael@0 133 return SandboxDump(cx, argc, vp);
michael@0 134 #else
michael@0 135 return true;
michael@0 136 #endif
michael@0 137 }
michael@0 138
michael@0 139 static bool
michael@0 140 SandboxImport(JSContext *cx, unsigned argc, Value *vp)
michael@0 141 {
michael@0 142 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 143
michael@0 144 if (args.length() < 1 || args[0].isPrimitive()) {
michael@0 145 XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
michael@0 146 return false;
michael@0 147 }
michael@0 148
michael@0 149 RootedString funname(cx);
michael@0 150 if (args.length() > 1) {
michael@0 151 // Use the second parameter as the function name.
michael@0 152 funname = ToString(cx, args[1]);
michael@0 153 if (!funname)
michael@0 154 return false;
michael@0 155 } else {
michael@0 156 // NB: funobj must only be used to get the JSFunction out.
michael@0 157 RootedObject funobj(cx, &args[0].toObject());
michael@0 158 if (js::IsProxy(funobj)) {
michael@0 159 funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj);
michael@0 160 }
michael@0 161
michael@0 162 JSAutoCompartment ac(cx, funobj);
michael@0 163
michael@0 164 RootedValue funval(cx, ObjectValue(*funobj));
michael@0 165 JSFunction *fun = JS_ValueToFunction(cx, funval);
michael@0 166 if (!fun) {
michael@0 167 XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
michael@0 168 return false;
michael@0 169 }
michael@0 170
michael@0 171 // Use the actual function name as the name.
michael@0 172 funname = JS_GetFunctionId(fun);
michael@0 173 if (!funname) {
michael@0 174 XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
michael@0 175 return false;
michael@0 176 }
michael@0 177 }
michael@0 178
michael@0 179 RootedId id(cx);
michael@0 180 if (!JS_StringToId(cx, funname, &id))
michael@0 181 return false;
michael@0 182
michael@0 183 // We need to resolve the this object, because this function is used
michael@0 184 // unbound and should still work and act on the original sandbox.
michael@0 185 RootedObject thisObject(cx, JS_THIS_OBJECT(cx, vp));
michael@0 186 if (!thisObject) {
michael@0 187 XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx);
michael@0 188 return false;
michael@0 189 }
michael@0 190 if (!JS_SetPropertyById(cx, thisObject, id, args[0]))
michael@0 191 return false;
michael@0 192
michael@0 193 args.rval().setUndefined();
michael@0 194 return true;
michael@0 195 }
michael@0 196
michael@0 197 static bool
michael@0 198 CreateXMLHttpRequest(JSContext *cx, unsigned argc, jsval *vp)
michael@0 199 {
michael@0 200 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 201
michael@0 202 nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
michael@0 203 if (!ssm)
michael@0 204 return false;
michael@0 205
michael@0 206 nsIPrincipal *subjectPrincipal = ssm->GetCxSubjectPrincipal(cx);
michael@0 207 if (!subjectPrincipal)
michael@0 208 return false;
michael@0 209
michael@0 210 RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
michael@0 211 MOZ_ASSERT(global);
michael@0 212
michael@0 213 nsIScriptObjectPrincipal *sop =
michael@0 214 static_cast<nsIScriptObjectPrincipal *>(xpc_GetJSPrivate(global));
michael@0 215 nsCOMPtr<nsIGlobalObject> iglobal = do_QueryInterface(sop);
michael@0 216
michael@0 217 nsCOMPtr<nsIXMLHttpRequest> xhr = new nsXMLHttpRequest();
michael@0 218 nsresult rv = xhr->Init(subjectPrincipal, nullptr, iglobal, nullptr);
michael@0 219 if (NS_FAILED(rv))
michael@0 220 return false;
michael@0 221
michael@0 222 rv = nsContentUtils::WrapNative(cx, xhr, args.rval());
michael@0 223 if (NS_FAILED(rv))
michael@0 224 return false;
michael@0 225
michael@0 226 return true;
michael@0 227 }
michael@0 228
michael@0 229 static bool
michael@0 230 IsProxy(JSContext *cx, unsigned argc, jsval *vp)
michael@0 231 {
michael@0 232 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 233 if (args.length() < 1) {
michael@0 234 JS_ReportError(cx, "Function requires at least 1 argument");
michael@0 235 return false;
michael@0 236 }
michael@0 237 if (!args[0].isObject()) {
michael@0 238 args.rval().setBoolean(false);
michael@0 239 return true;
michael@0 240 }
michael@0 241
michael@0 242 RootedObject obj(cx, &args[0].toObject());
michael@0 243 obj = js::CheckedUnwrap(obj);
michael@0 244 NS_ENSURE_TRUE(obj, false);
michael@0 245
michael@0 246 args.rval().setBoolean(js::IsScriptedProxy(obj));
michael@0 247 return true;
michael@0 248 }
michael@0 249
michael@0 250 namespace xpc {
michael@0 251
michael@0 252 bool
michael@0 253 ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleValue voptions,
michael@0 254 MutableHandleValue rval)
michael@0 255 {
michael@0 256 bool hasOptions = !voptions.isUndefined();
michael@0 257 if (!vscope.isObject() || !vfunction.isObject() || (hasOptions && !voptions.isObject())) {
michael@0 258 JS_ReportError(cx, "Invalid argument");
michael@0 259 return false;
michael@0 260 }
michael@0 261
michael@0 262 RootedObject funObj(cx, &vfunction.toObject());
michael@0 263 RootedObject targetScope(cx, &vscope.toObject());
michael@0 264 ExportOptions options(cx, hasOptions ? &voptions.toObject() : nullptr);
michael@0 265 if (hasOptions && !options.Parse())
michael@0 266 return false;
michael@0 267
michael@0 268 // We can only export functions to scopes those are transparent for us,
michael@0 269 // so if there is a security wrapper around targetScope we must throw.
michael@0 270 targetScope = CheckedUnwrap(targetScope);
michael@0 271 if (!targetScope) {
michael@0 272 JS_ReportError(cx, "Permission denied to export function into scope");
michael@0 273 return false;
michael@0 274 }
michael@0 275
michael@0 276 if (js::IsScriptedProxy(targetScope)) {
michael@0 277 JS_ReportError(cx, "Defining property on proxy object is not allowed");
michael@0 278 return false;
michael@0 279 }
michael@0 280
michael@0 281 {
michael@0 282 // We need to operate in the target scope from here on, let's enter
michael@0 283 // its compartment.
michael@0 284 JSAutoCompartment ac(cx, targetScope);
michael@0 285
michael@0 286 // Unwrapping to see if we have a callable.
michael@0 287 funObj = UncheckedUnwrap(funObj);
michael@0 288 if (!JS_ObjectIsCallable(cx, funObj)) {
michael@0 289 JS_ReportError(cx, "First argument must be a function");
michael@0 290 return false;
michael@0 291 }
michael@0 292
michael@0 293 RootedId id(cx, options.defineAs);
michael@0 294 if (JSID_IS_VOID(id)) {
michael@0 295 // If there wasn't any function name specified,
michael@0 296 // copy the name from the function being imported.
michael@0 297 JSFunction *fun = JS_GetObjectFunction(funObj);
michael@0 298 RootedString funName(cx, JS_GetFunctionId(fun));
michael@0 299 if (!funName)
michael@0 300 funName = JS_InternString(cx, "");
michael@0 301
michael@0 302 if (!JS_StringToId(cx, funName, &id))
michael@0 303 return false;
michael@0 304 }
michael@0 305 MOZ_ASSERT(JSID_IS_STRING(id));
michael@0 306
michael@0 307 // The function forwarder will live in the target compartment. Since
michael@0 308 // this function will be referenced from its private slot, to avoid a
michael@0 309 // GC hazard, we must wrap it to the same compartment.
michael@0 310 if (!JS_WrapObject(cx, &funObj))
michael@0 311 return false;
michael@0 312
michael@0 313 // And now, let's create the forwarder function in the target compartment
michael@0 314 // for the function the be exported.
michael@0 315 if (!NewFunctionForwarder(cx, id, funObj, /* doclone = */ true, rval)) {
michael@0 316 JS_ReportError(cx, "Exporting function failed");
michael@0 317 return false;
michael@0 318 }
michael@0 319
michael@0 320 // We have the forwarder function in the target compartment. If
michael@0 321 // defineAs was set, we also need to define it as a property on
michael@0 322 // the target.
michael@0 323 if (!JSID_IS_VOID(options.defineAs)) {
michael@0 324 if (!JS_DefinePropertyById(cx, targetScope, id, rval,
michael@0 325 JS_PropertyStub, JS_StrictPropertyStub,
michael@0 326 JSPROP_ENUMERATE)) {
michael@0 327 return false;
michael@0 328 }
michael@0 329 }
michael@0 330 }
michael@0 331
michael@0 332 // Finally we have to re-wrap the exported function back to the caller compartment.
michael@0 333 if (!JS_WrapValue(cx, rval))
michael@0 334 return false;
michael@0 335
michael@0 336 return true;
michael@0 337 }
michael@0 338
michael@0 339 /*
michael@0 340 * Expected type of the arguments and the return value:
michael@0 341 * function exportFunction(function funToExport,
michael@0 342 * object targetScope,
michael@0 343 * [optional] object options)
michael@0 344 */
michael@0 345 static bool
michael@0 346 ExportFunction(JSContext *cx, unsigned argc, jsval *vp)
michael@0 347 {
michael@0 348 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 349 if (args.length() < 2) {
michael@0 350 JS_ReportError(cx, "Function requires at least 2 arguments");
michael@0 351 return false;
michael@0 352 }
michael@0 353
michael@0 354 RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
michael@0 355 return ExportFunction(cx, args[0], args[1], options, args.rval());
michael@0 356 }
michael@0 357 } /* namespace xpc */
michael@0 358
michael@0 359 static bool
michael@0 360 GetFilenameAndLineNumber(JSContext *cx, nsACString &filename, unsigned &lineno)
michael@0 361 {
michael@0 362 JS::AutoFilename scriptFilename;
michael@0 363 if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineno)) {
michael@0 364 if (const char *cfilename = scriptFilename.get()) {
michael@0 365 filename.Assign(nsDependentCString(cfilename));
michael@0 366 return true;
michael@0 367 }
michael@0 368 }
michael@0 369 return false;
michael@0 370 }
michael@0 371
michael@0 372 bool
michael@0 373 xpc::IsReflector(JSObject *obj)
michael@0 374 {
michael@0 375 return IS_WN_REFLECTOR(obj) || dom::IsDOMObject(obj);
michael@0 376 }
michael@0 377
michael@0 378 enum ForwarderCloneTags {
michael@0 379 SCTAG_BASE = JS_SCTAG_USER_MIN,
michael@0 380 SCTAG_REFLECTOR
michael@0 381 };
michael@0 382
michael@0 383 static JSObject *
michael@0 384 CloneNonReflectorsRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t tag,
michael@0 385 uint32_t data, void *closure)
michael@0 386 {
michael@0 387 MOZ_ASSERT(closure, "Null pointer!");
michael@0 388 AutoObjectVector *reflectors = static_cast<AutoObjectVector *>(closure);
michael@0 389 if (tag == SCTAG_REFLECTOR) {
michael@0 390 MOZ_ASSERT(!data);
michael@0 391
michael@0 392 size_t idx;
michael@0 393 if (JS_ReadBytes(reader, &idx, sizeof(size_t))) {
michael@0 394 RootedObject reflector(cx, reflectors->handleAt(idx));
michael@0 395 MOZ_ASSERT(reflector, "No object pointer?");
michael@0 396 MOZ_ASSERT(IsReflector(reflector), "Object pointer must be a reflector!");
michael@0 397
michael@0 398 if (!JS_WrapObject(cx, &reflector))
michael@0 399 return nullptr;
michael@0 400 MOZ_ASSERT(WrapperFactory::IsXrayWrapper(reflector) ||
michael@0 401 IsReflector(reflector));
michael@0 402
michael@0 403 return reflector;
michael@0 404 }
michael@0 405 }
michael@0 406
michael@0 407 JS_ReportError(cx, "CloneNonReflectorsRead error");
michael@0 408 return nullptr;
michael@0 409 }
michael@0 410
michael@0 411 static bool
michael@0 412 CloneNonReflectorsWrite(JSContext *cx, JSStructuredCloneWriter *writer,
michael@0 413 Handle<JSObject *> obj, void *closure)
michael@0 414 {
michael@0 415 MOZ_ASSERT(closure, "Null pointer!");
michael@0 416
michael@0 417 // We need to maintain a list of reflectors to make sure all these objects
michael@0 418 // are properly rooter. Only their indices will be serialized.
michael@0 419 AutoObjectVector *reflectors = static_cast<AutoObjectVector *>(closure);
michael@0 420 if (IsReflector(obj)) {
michael@0 421 if (!reflectors->append(obj))
michael@0 422 return false;
michael@0 423
michael@0 424 size_t idx = reflectors->length()-1;
michael@0 425 if (JS_WriteUint32Pair(writer, SCTAG_REFLECTOR, 0) &&
michael@0 426 JS_WriteBytes(writer, &idx, sizeof(size_t))) {
michael@0 427 return true;
michael@0 428 }
michael@0 429 }
michael@0 430
michael@0 431 JS_ReportError(cx, "CloneNonReflectorsWrite error");
michael@0 432 return false;
michael@0 433 }
michael@0 434
michael@0 435 static const JSStructuredCloneCallbacks gForwarderStructuredCloneCallbacks = {
michael@0 436 CloneNonReflectorsRead,
michael@0 437 CloneNonReflectorsWrite,
michael@0 438 nullptr,
michael@0 439 nullptr,
michael@0 440 nullptr,
michael@0 441 nullptr
michael@0 442 };
michael@0 443
michael@0 444 /*
michael@0 445 * This is a special structured cloning, that clones only non-reflectors.
michael@0 446 * The function assumes the cx is already entered the compartment we want
michael@0 447 * to clone to, and that if val is an object is from the compartment we
michael@0 448 * clone from.
michael@0 449 */
michael@0 450 static bool
michael@0 451 CloneNonReflectors(JSContext *cx, MutableHandleValue val)
michael@0 452 {
michael@0 453 JSAutoStructuredCloneBuffer buffer;
michael@0 454 AutoObjectVector rootedReflectors(cx);
michael@0 455 {
michael@0 456 // For parsing val we have to enter its compartment.
michael@0 457 // (unless it's a primitive)
michael@0 458 Maybe<JSAutoCompartment> ac;
michael@0 459 if (val.isObject()) {
michael@0 460 ac.construct(cx, &val.toObject());
michael@0 461 } else if (val.isString() && !JS_WrapValue(cx, val)) {
michael@0 462 return false;
michael@0 463 }
michael@0 464
michael@0 465 if (!buffer.write(cx, val,
michael@0 466 &gForwarderStructuredCloneCallbacks,
michael@0 467 &rootedReflectors))
michael@0 468 {
michael@0 469 return false;
michael@0 470 }
michael@0 471 }
michael@0 472
michael@0 473 // Now recreate the clones in the target compartment.
michael@0 474 if (!buffer.read(cx, val,
michael@0 475 &gForwarderStructuredCloneCallbacks,
michael@0 476 &rootedReflectors))
michael@0 477 {
michael@0 478 return false;
michael@0 479 }
michael@0 480
michael@0 481 return true;
michael@0 482 }
michael@0 483
michael@0 484 namespace xpc {
michael@0 485
michael@0 486 bool
michael@0 487 EvalInWindow(JSContext *cx, const nsAString &source, HandleObject scope, MutableHandleValue rval)
michael@0 488 {
michael@0 489 // If we cannot unwrap we must not eval in it.
michael@0 490 RootedObject targetScope(cx, CheckedUnwrap(scope));
michael@0 491 if (!targetScope) {
michael@0 492 JS_ReportError(cx, "Permission denied to eval in target scope");
michael@0 493 return false;
michael@0 494 }
michael@0 495
michael@0 496 // Make sure that we have a window object.
michael@0 497 RootedObject inner(cx, CheckedUnwrap(targetScope, /* stopAtOuter = */ false));
michael@0 498 nsCOMPtr<nsIGlobalObject> global;
michael@0 499 nsCOMPtr<nsPIDOMWindow> window;
michael@0 500 if (!JS_IsGlobalObject(inner) ||
michael@0 501 !(global = GetNativeForGlobal(inner)) ||
michael@0 502 !(window = do_QueryInterface(global)))
michael@0 503 {
michael@0 504 JS_ReportError(cx, "Second argument must be a window");
michael@0 505 return false;
michael@0 506 }
michael@0 507
michael@0 508 nsCOMPtr<nsIScriptContext> context =
michael@0 509 (static_cast<nsGlobalWindow*>(window.get()))->GetScriptContext();
michael@0 510 if (!context) {
michael@0 511 JS_ReportError(cx, "Script context needed");
michael@0 512 return false;
michael@0 513 }
michael@0 514
michael@0 515 nsCString filename;
michael@0 516 unsigned lineNo;
michael@0 517 if (!GetFilenameAndLineNumber(cx, filename, lineNo)) {
michael@0 518 // Default values for non-scripted callers.
michael@0 519 filename.Assign("Unknown");
michael@0 520 lineNo = 0;
michael@0 521 }
michael@0 522
michael@0 523 RootedObject cxGlobal(cx, JS::CurrentGlobalOrNull(cx));
michael@0 524 {
michael@0 525 // CompileOptions must be created from the context
michael@0 526 // we will execute this script in.
michael@0 527 JSContext *wndCx = context->GetNativeContext();
michael@0 528 AutoCxPusher pusher(wndCx);
michael@0 529 JS::CompileOptions compileOptions(wndCx);
michael@0 530 compileOptions.setFileAndLine(filename.get(), lineNo);
michael@0 531
michael@0 532 // We don't want the JS engine to automatically report
michael@0 533 // uncaught exceptions.
michael@0 534 nsJSUtils::EvaluateOptions evaluateOptions;
michael@0 535 evaluateOptions.setReportUncaught(false);
michael@0 536
michael@0 537 nsresult rv = nsJSUtils::EvaluateString(wndCx,
michael@0 538 source,
michael@0 539 targetScope,
michael@0 540 compileOptions,
michael@0 541 evaluateOptions,
michael@0 542 rval);
michael@0 543
michael@0 544 if (NS_FAILED(rv)) {
michael@0 545 // If there was an exception we get it as a return value, if
michael@0 546 // the evaluation failed for some other reason, then a default
michael@0 547 // exception is raised.
michael@0 548 MOZ_ASSERT(!JS_IsExceptionPending(wndCx),
michael@0 549 "Exception should be delivered as return value.");
michael@0 550 if (rval.isUndefined()) {
michael@0 551 MOZ_ASSERT(rv == NS_ERROR_OUT_OF_MEMORY);
michael@0 552 return false;
michael@0 553 }
michael@0 554
michael@0 555 // If there was an exception thrown we should set it
michael@0 556 // on the calling context.
michael@0 557 RootedValue exn(wndCx, rval);
michael@0 558 // First we should reset the return value.
michael@0 559 rval.set(UndefinedValue());
michael@0 560
michael@0 561 // Then clone the exception.
michael@0 562 JSAutoCompartment ac(wndCx, cxGlobal);
michael@0 563 if (CloneNonReflectors(wndCx, &exn))
michael@0 564 js::SetPendingExceptionCrossContext(cx, exn);
michael@0 565
michael@0 566 return false;
michael@0 567 }
michael@0 568 }
michael@0 569
michael@0 570 // Let's clone the return value back to the callers compartment.
michael@0 571 if (!CloneNonReflectors(cx, rval)) {
michael@0 572 rval.set(UndefinedValue());
michael@0 573 return false;
michael@0 574 }
michael@0 575
michael@0 576 return true;
michael@0 577 }
michael@0 578
michael@0 579 /*
michael@0 580 * Expected type of the arguments:
michael@0 581 * value evalInWindow(string script,
michael@0 582 * object window)
michael@0 583 */
michael@0 584 static bool
michael@0 585 EvalInWindow(JSContext *cx, unsigned argc, jsval *vp)
michael@0 586 {
michael@0 587 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 588 if (args.length() < 2) {
michael@0 589 JS_ReportError(cx, "Function requires two arguments");
michael@0 590 return false;
michael@0 591 }
michael@0 592
michael@0 593 if (!args[0].isString() || !args[1].isObject()) {
michael@0 594 JS_ReportError(cx, "Invalid arguments");
michael@0 595 return false;
michael@0 596 }
michael@0 597
michael@0 598 RootedString srcString(cx, args[0].toString());
michael@0 599 RootedObject targetScope(cx, &args[1].toObject());
michael@0 600
michael@0 601 nsDependentJSString srcDepString;
michael@0 602 if (!srcDepString.init(cx, srcString)) {
michael@0 603 JS_ReportError(cx, "Source string is invalid");
michael@0 604 return false;
michael@0 605 }
michael@0 606
michael@0 607 return EvalInWindow(cx, srcDepString, targetScope, args.rval());
michael@0 608 }
michael@0 609
michael@0 610 static bool
michael@0 611 CreateObjectIn(JSContext *cx, unsigned argc, jsval *vp)
michael@0 612 {
michael@0 613 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 614 if (args.length() < 1) {
michael@0 615 JS_ReportError(cx, "Function requires at least 1 argument");
michael@0 616 return false;
michael@0 617 }
michael@0 618
michael@0 619 RootedObject optionsObj(cx);
michael@0 620 bool calledWithOptions = args.length() > 1;
michael@0 621 if (calledWithOptions) {
michael@0 622 if (!args[1].isObject()) {
michael@0 623 JS_ReportError(cx, "Expected the 2nd argument (options) to be an object");
michael@0 624 return false;
michael@0 625 }
michael@0 626 optionsObj = &args[1].toObject();
michael@0 627 }
michael@0 628
michael@0 629 CreateObjectInOptions options(cx, optionsObj);
michael@0 630 if (calledWithOptions && !options.Parse())
michael@0 631 return false;
michael@0 632
michael@0 633 return xpc::CreateObjectIn(cx, args[0], options, args.rval());
michael@0 634 }
michael@0 635
michael@0 636 static bool
michael@0 637 CloneInto(JSContext *cx, unsigned argc, jsval *vp)
michael@0 638 {
michael@0 639 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 640 if (args.length() < 2) {
michael@0 641 JS_ReportError(cx, "Function requires at least 2 arguments");
michael@0 642 return false;
michael@0 643 }
michael@0 644
michael@0 645 RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue());
michael@0 646 return xpc::CloneInto(cx, args[0], args[1], options, args.rval());
michael@0 647 }
michael@0 648
michael@0 649 } /* namespace xpc */
michael@0 650
michael@0 651 static bool
michael@0 652 sandbox_enumerate(JSContext *cx, HandleObject obj)
michael@0 653 {
michael@0 654 return JS_EnumerateStandardClasses(cx, obj);
michael@0 655 }
michael@0 656
michael@0 657 static bool
michael@0 658 sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id)
michael@0 659 {
michael@0 660 bool resolved;
michael@0 661 return JS_ResolveStandardClass(cx, obj, id, &resolved);
michael@0 662 }
michael@0 663
michael@0 664 static void
michael@0 665 sandbox_finalize(JSFreeOp *fop, JSObject *obj)
michael@0 666 {
michael@0 667 nsIScriptObjectPrincipal *sop =
michael@0 668 static_cast<nsIScriptObjectPrincipal *>(xpc_GetJSPrivate(obj));
michael@0 669 MOZ_ASSERT(sop);
michael@0 670 static_cast<SandboxPrivate *>(sop)->ForgetGlobalObject();
michael@0 671 NS_IF_RELEASE(sop);
michael@0 672 DestroyProtoAndIfaceCache(obj);
michael@0 673 }
michael@0 674
michael@0 675 static bool
michael@0 676 sandbox_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp)
michael@0 677 {
michael@0 678 if (type == JSTYPE_OBJECT) {
michael@0 679 vp.set(OBJECT_TO_JSVAL(obj));
michael@0 680 return true;
michael@0 681 }
michael@0 682
michael@0 683 return JS_ConvertStub(cx, obj, type, vp);
michael@0 684 }
michael@0 685
michael@0 686 #define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
michael@0 687
michael@0 688 static const JSClass SandboxClass = {
michael@0 689 "Sandbox",
michael@0 690 XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
michael@0 691 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
michael@0 692 sandbox_enumerate, sandbox_resolve, sandbox_convert, sandbox_finalize,
michael@0 693 nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook
michael@0 694 };
michael@0 695
michael@0 696 static const JSFunctionSpec SandboxFunctions[] = {
michael@0 697 JS_FS("dump", SandboxDump, 1,0),
michael@0 698 JS_FS("debug", SandboxDebug, 1,0),
michael@0 699 JS_FS("importFunction", SandboxImport, 1,0),
michael@0 700 JS_FS_END
michael@0 701 };
michael@0 702
michael@0 703 bool
michael@0 704 xpc::IsSandbox(JSObject *obj)
michael@0 705 {
michael@0 706 return GetObjectJSClass(obj) == &SandboxClass;
michael@0 707 }
michael@0 708
michael@0 709 /***************************************************************************/
michael@0 710 nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox()
michael@0 711 {
michael@0 712 }
michael@0 713
michael@0 714 nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox()
michael@0 715 {
michael@0 716 }
michael@0 717
michael@0 718 NS_INTERFACE_MAP_BEGIN(nsXPCComponents_utils_Sandbox)
michael@0 719 NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_utils_Sandbox)
michael@0 720 NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
michael@0 721 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_utils_Sandbox)
michael@0 722 NS_INTERFACE_MAP_END
michael@0 723
michael@0 724 NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox)
michael@0 725 NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox)
michael@0 726
michael@0 727 // We use the nsIXPScriptable macros to generate lots of stuff for us.
michael@0 728 #define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox
michael@0 729 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox"
michael@0 730 #define XPC_MAP_WANT_CALL
michael@0 731 #define XPC_MAP_WANT_CONSTRUCT
michael@0 732 #define XPC_MAP_FLAGS 0
michael@0 733 #include "xpc_map_end.h" /* This #undef's the above. */
michael@0 734
michael@0 735 xpc::SandboxProxyHandler xpc::sandboxProxyHandler;
michael@0 736
michael@0 737 bool
michael@0 738 xpc::IsSandboxPrototypeProxy(JSObject *obj)
michael@0 739 {
michael@0 740 return js::IsProxy(obj) &&
michael@0 741 js::GetProxyHandler(obj) == &xpc::sandboxProxyHandler;
michael@0 742 }
michael@0 743
michael@0 744 bool
michael@0 745 xpc::SandboxCallableProxyHandler::call(JSContext *cx, JS::Handle<JSObject*> proxy,
michael@0 746 const JS::CallArgs &args)
michael@0 747 {
michael@0 748 // We forward the call to our underlying callable.
michael@0 749
michael@0 750 // The parent of our proxy is the SandboxProxyHandler proxy
michael@0 751 RootedObject sandboxProxy(cx, JS_GetParent(proxy));
michael@0 752 MOZ_ASSERT(js::IsProxy(sandboxProxy) &&
michael@0 753 js::GetProxyHandler(sandboxProxy) == &xpc::sandboxProxyHandler);
michael@0 754
michael@0 755 // The parent of the sandboxProxy is the sandbox global, and the
michael@0 756 // target object is the original proto.
michael@0 757 RootedObject sandboxGlobal(cx, JS_GetParent(sandboxProxy));
michael@0 758 MOZ_ASSERT(js::GetObjectJSClass(sandboxGlobal) == &SandboxClass);
michael@0 759
michael@0 760 // If our this object is the sandbox global, we call with this set to the
michael@0 761 // original proto instead.
michael@0 762 //
michael@0 763 // There are two different ways we can compute |this|. If we use
michael@0 764 // JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the
michael@0 765 // caller, which may be undefined if a global function was invoked without
michael@0 766 // an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this|
michael@0 767 // in |vp| will be coerced to the global, which is not the correct
michael@0 768 // behavior in ES5 strict mode. And we have no way to compute strictness
michael@0 769 // here.
michael@0 770 //
michael@0 771 // The naive approach is simply to use JS_THIS_VALUE here. If |this| was
michael@0 772 // explicit, we can remap it appropriately. If it was implicit, then we
michael@0 773 // leave it as undefined, and let the callee sort it out. Since the callee
michael@0 774 // is generally in the same compartment as its global (eg the Window's
michael@0 775 // compartment, not the Sandbox's), the callee will generally compute the
michael@0 776 // correct |this|.
michael@0 777 //
michael@0 778 // However, this breaks down in the Xray case. If the sandboxPrototype
michael@0 779 // is an Xray wrapper, then we'll end up reifying the native methods in
michael@0 780 // the Sandbox's scope, which means that they'll compute |this| to be the
michael@0 781 // Sandbox, breaking old-style XPC_WN_CallMethod methods.
michael@0 782 //
michael@0 783 // Luckily, the intent of Xrays is to provide a vanilla view of a foreign
michael@0 784 // DOM interface, which means that we don't care about script-enacted
michael@0 785 // strictness in the prototype's home compartment. Indeed, since DOM
michael@0 786 // methods are always non-strict, we can just assume non-strict semantics
michael@0 787 // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately
michael@0 788 // remap |this|.
michael@0 789 bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy);
michael@0 790 RootedValue thisVal(cx, isXray ? args.computeThis(cx) : args.thisv());
michael@0 791 if (thisVal == ObjectValue(*sandboxGlobal)) {
michael@0 792 thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy));
michael@0 793 }
michael@0 794
michael@0 795 RootedValue func(cx, js::GetProxyPrivate(proxy));
michael@0 796 return JS::Call(cx, thisVal, func, args, args.rval());
michael@0 797 }
michael@0 798
michael@0 799 xpc::SandboxCallableProxyHandler xpc::sandboxCallableProxyHandler;
michael@0 800
michael@0 801 /*
michael@0 802 * Wrap a callable such that if we're called with oldThisObj as the
michael@0 803 * "this" we will instead call it with newThisObj as the this.
michael@0 804 */
michael@0 805 static JSObject*
michael@0 806 WrapCallable(JSContext *cx, JSObject *callable, JSObject *sandboxProtoProxy)
michael@0 807 {
michael@0 808 MOZ_ASSERT(JS_ObjectIsCallable(cx, callable));
michael@0 809 // Our proxy is wrapping the callable. So we need to use the
michael@0 810 // callable as the private. We use the given sandboxProtoProxy as
michael@0 811 // the parent, and our call() hook depends on that.
michael@0 812 MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) &&
michael@0 813 js::GetProxyHandler(sandboxProtoProxy) ==
michael@0 814 &xpc::sandboxProxyHandler);
michael@0 815
michael@0 816 RootedValue priv(cx, ObjectValue(*callable));
michael@0 817 js::ProxyOptions options;
michael@0 818 options.selectDefaultClass(true);
michael@0 819 return js::NewProxyObject(cx, &xpc::sandboxCallableProxyHandler,
michael@0 820 priv, nullptr,
michael@0 821 sandboxProtoProxy, options);
michael@0 822 }
michael@0 823
michael@0 824 template<typename Op>
michael@0 825 bool BindPropertyOp(JSContext *cx, Op &op, JSPropertyDescriptor *desc, HandleId id,
michael@0 826 unsigned attrFlag, HandleObject sandboxProtoProxy)
michael@0 827 {
michael@0 828 if (!op) {
michael@0 829 return true;
michael@0 830 }
michael@0 831
michael@0 832 RootedObject func(cx);
michael@0 833 if (desc->attrs & attrFlag) {
michael@0 834 // Already an object
michael@0 835 func = JS_FUNC_TO_DATA_PTR(JSObject *, op);
michael@0 836 } else {
michael@0 837 // We have an actual property op. For getters, we use 0
michael@0 838 // args, for setters we use 1 arg.
michael@0 839 uint32_t args = (attrFlag == JSPROP_GETTER) ? 0 : 1;
michael@0 840 RootedObject obj(cx, desc->obj);
michael@0 841 func = GeneratePropertyOp(cx, obj, id, args, op);
michael@0 842 if (!func)
michael@0 843 return false;
michael@0 844 }
michael@0 845 func = WrapCallable(cx, func, sandboxProtoProxy);
michael@0 846 if (!func)
michael@0 847 return false;
michael@0 848 op = JS_DATA_TO_FUNC_PTR(Op, func.get());
michael@0 849 desc->attrs |= attrFlag;
michael@0 850 return true;
michael@0 851 }
michael@0 852
michael@0 853 extern bool
michael@0 854 XPC_WN_Helper_GetProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp);
michael@0 855 extern bool
michael@0 856 XPC_WN_Helper_SetProperty(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp);
michael@0 857
michael@0 858 bool
michael@0 859 xpc::SandboxProxyHandler::getPropertyDescriptor(JSContext *cx,
michael@0 860 JS::Handle<JSObject*> proxy,
michael@0 861 JS::Handle<jsid> id,
michael@0 862 JS::MutableHandle<JSPropertyDescriptor> desc)
michael@0 863 {
michael@0 864 JS::RootedObject obj(cx, wrappedObject(proxy));
michael@0 865
michael@0 866 MOZ_ASSERT(js::GetObjectCompartment(obj) == js::GetObjectCompartment(proxy));
michael@0 867 if (!JS_GetPropertyDescriptorById(cx, obj, id, desc))
michael@0 868 return false;
michael@0 869
michael@0 870 if (!desc.object())
michael@0 871 return true; // No property, nothing to do
michael@0 872
michael@0 873 // Now fix up the getter/setter/value as needed to be bound to desc->obj
michael@0 874 // Don't mess with holder_get and holder_set, though, because those rely on
michael@0 875 // the "vp is prefilled with the value in the slot" behavior that property
michael@0 876 // ops can in theory rely on, but our property op forwarder doesn't know how
michael@0 877 // to make that happen. Since we really only need to rebind the DOM methods
michael@0 878 // here, not rebindings holder_get and holder_set is OK.
michael@0 879 //
michael@0 880 // Similarly, don't mess with XPC_WN_Helper_GetProperty and
michael@0 881 // XPC_WN_Helper_SetProperty, for the same reasons: that could confuse our
michael@0 882 // access to expandos when we're not doing Xrays.
michael@0 883 if (desc.getter() != xpc::holder_get &&
michael@0 884 desc.getter() != XPC_WN_Helper_GetProperty &&
michael@0 885 !BindPropertyOp(cx, desc.getter(), desc.address(), id, JSPROP_GETTER, proxy))
michael@0 886 return false;
michael@0 887 if (desc.setter() != xpc::holder_set &&
michael@0 888 desc.setter() != XPC_WN_Helper_SetProperty &&
michael@0 889 !BindPropertyOp(cx, desc.setter(), desc.address(), id, JSPROP_SETTER, proxy))
michael@0 890 return false;
michael@0 891 if (desc.value().isObject()) {
michael@0 892 JSObject* val = &desc.value().toObject();
michael@0 893 if (JS_ObjectIsCallable(cx, val)) {
michael@0 894 val = WrapCallable(cx, val, proxy);
michael@0 895 if (!val)
michael@0 896 return false;
michael@0 897 desc.value().setObject(*val);
michael@0 898 }
michael@0 899 }
michael@0 900
michael@0 901 return true;
michael@0 902 }
michael@0 903
michael@0 904 bool
michael@0 905 xpc::SandboxProxyHandler::getOwnPropertyDescriptor(JSContext *cx,
michael@0 906 JS::Handle<JSObject*> proxy,
michael@0 907 JS::Handle<jsid> id,
michael@0 908 JS::MutableHandle<JSPropertyDescriptor> desc)
michael@0 909 {
michael@0 910 if (!getPropertyDescriptor(cx, proxy, id, desc))
michael@0 911 return false;
michael@0 912
michael@0 913 if (desc.object() != wrappedObject(proxy))
michael@0 914 desc.object().set(nullptr);
michael@0 915
michael@0 916 return true;
michael@0 917 }
michael@0 918
michael@0 919 /*
michael@0 920 * Reuse the BaseProxyHandler versions of the derived traps that are implemented
michael@0 921 * in terms of the fundamental traps.
michael@0 922 */
michael@0 923
michael@0 924 bool
michael@0 925 xpc::SandboxProxyHandler::has(JSContext *cx, JS::Handle<JSObject*> proxy,
michael@0 926 JS::Handle<jsid> id, bool *bp)
michael@0 927 {
michael@0 928 return BaseProxyHandler::has(cx, proxy, id, bp);
michael@0 929 }
michael@0 930 bool
michael@0 931 xpc::SandboxProxyHandler::hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy,
michael@0 932 JS::Handle<jsid> id, bool *bp)
michael@0 933 {
michael@0 934 return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
michael@0 935 }
michael@0 936
michael@0 937 bool
michael@0 938 xpc::SandboxProxyHandler::get(JSContext *cx, JS::Handle<JSObject*> proxy,
michael@0 939 JS::Handle<JSObject*> receiver,
michael@0 940 JS::Handle<jsid> id,
michael@0 941 JS::MutableHandle<Value> vp)
michael@0 942 {
michael@0 943 return BaseProxyHandler::get(cx, proxy, receiver, id, vp);
michael@0 944 }
michael@0 945
michael@0 946 bool
michael@0 947 xpc::SandboxProxyHandler::set(JSContext *cx, JS::Handle<JSObject*> proxy,
michael@0 948 JS::Handle<JSObject*> receiver,
michael@0 949 JS::Handle<jsid> id,
michael@0 950 bool strict,
michael@0 951 JS::MutableHandle<Value> vp)
michael@0 952 {
michael@0 953 return BaseProxyHandler::set(cx, proxy, receiver, id, strict, vp);
michael@0 954 }
michael@0 955
michael@0 956 bool
michael@0 957 xpc::SandboxProxyHandler::keys(JSContext *cx, JS::Handle<JSObject*> proxy,
michael@0 958 AutoIdVector &props)
michael@0 959 {
michael@0 960 return BaseProxyHandler::keys(cx, proxy, props);
michael@0 961 }
michael@0 962
michael@0 963 bool
michael@0 964 xpc::SandboxProxyHandler::iterate(JSContext *cx, JS::Handle<JSObject*> proxy,
michael@0 965 unsigned flags, JS::MutableHandle<Value> vp)
michael@0 966 {
michael@0 967 return BaseProxyHandler::iterate(cx, proxy, flags, vp);
michael@0 968 }
michael@0 969
michael@0 970 bool
michael@0 971 xpc::GlobalProperties::Parse(JSContext *cx, JS::HandleObject obj)
michael@0 972 {
michael@0 973 MOZ_ASSERT(JS_IsArrayObject(cx, obj));
michael@0 974
michael@0 975 uint32_t length;
michael@0 976 bool ok = JS_GetArrayLength(cx, obj, &length);
michael@0 977 NS_ENSURE_TRUE(ok, false);
michael@0 978 bool promise = Promise;
michael@0 979 for (uint32_t i = 0; i < length; i++) {
michael@0 980 RootedValue nameValue(cx);
michael@0 981 ok = JS_GetElement(cx, obj, i, &nameValue);
michael@0 982 NS_ENSURE_TRUE(ok, false);
michael@0 983 if (!nameValue.isString()) {
michael@0 984 JS_ReportError(cx, "Property names must be strings");
michael@0 985 return false;
michael@0 986 }
michael@0 987 JSAutoByteString name(cx, nameValue.toString());
michael@0 988 NS_ENSURE_TRUE(name, false);
michael@0 989 if (promise && !strcmp(name.ptr(), "-Promise")) {
michael@0 990 Promise = false;
michael@0 991 } else if (!strcmp(name.ptr(), "indexedDB")) {
michael@0 992 indexedDB = true;
michael@0 993 } else if (!strcmp(name.ptr(), "XMLHttpRequest")) {
michael@0 994 XMLHttpRequest = true;
michael@0 995 } else if (!strcmp(name.ptr(), "TextEncoder")) {
michael@0 996 TextEncoder = true;
michael@0 997 } else if (!strcmp(name.ptr(), "TextDecoder")) {
michael@0 998 TextDecoder = true;
michael@0 999 } else if (!strcmp(name.ptr(), "URL")) {
michael@0 1000 URL = true;
michael@0 1001 } else if (!strcmp(name.ptr(), "atob")) {
michael@0 1002 atob = true;
michael@0 1003 } else if (!strcmp(name.ptr(), "btoa")) {
michael@0 1004 btoa = true;
michael@0 1005 } else {
michael@0 1006 JS_ReportError(cx, "Unknown property name: %s", name.ptr());
michael@0 1007 return false;
michael@0 1008 }
michael@0 1009 }
michael@0 1010 return true;
michael@0 1011 }
michael@0 1012
michael@0 1013 bool
michael@0 1014 xpc::GlobalProperties::Define(JSContext *cx, JS::HandleObject obj)
michael@0 1015 {
michael@0 1016 if (Promise && !dom::PromiseBinding::GetConstructorObject(cx, obj))
michael@0 1017 return false;
michael@0 1018
michael@0 1019 if (indexedDB && AccessCheck::isChrome(obj) &&
michael@0 1020 !IndexedDatabaseManager::DefineIndexedDB(cx, obj))
michael@0 1021 return false;
michael@0 1022
michael@0 1023 if (XMLHttpRequest &&
michael@0 1024 !JS_DefineFunction(cx, obj, "XMLHttpRequest", CreateXMLHttpRequest, 0, JSFUN_CONSTRUCTOR))
michael@0 1025 return false;
michael@0 1026
michael@0 1027 if (TextEncoder &&
michael@0 1028 !dom::TextEncoderBinding::GetConstructorObject(cx, obj))
michael@0 1029 return false;
michael@0 1030
michael@0 1031 if (TextDecoder &&
michael@0 1032 !dom::TextDecoderBinding::GetConstructorObject(cx, obj))
michael@0 1033 return false;
michael@0 1034
michael@0 1035 if (URL &&
michael@0 1036 !dom::URLBinding::GetConstructorObject(cx, obj))
michael@0 1037 return false;
michael@0 1038
michael@0 1039 if (atob &&
michael@0 1040 !JS_DefineFunction(cx, obj, "atob", Atob, 1, 0))
michael@0 1041 return false;
michael@0 1042
michael@0 1043 if (btoa &&
michael@0 1044 !JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0))
michael@0 1045 return false;
michael@0 1046
michael@0 1047 return true;
michael@0 1048 }
michael@0 1049
michael@0 1050 nsresult
michael@0 1051 xpc::CreateSandboxObject(JSContext *cx, MutableHandleValue vp, nsISupports *prinOrSop,
michael@0 1052 SandboxOptions& options)
michael@0 1053 {
michael@0 1054 // Create the sandbox global object
michael@0 1055 nsresult rv;
michael@0 1056 nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
michael@0 1057 if (NS_FAILED(rv))
michael@0 1058 return NS_ERROR_XPC_UNEXPECTED;
michael@0 1059
michael@0 1060 nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
michael@0 1061 if (!principal) {
michael@0 1062 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(prinOrSop);
michael@0 1063 if (sop) {
michael@0 1064 principal = sop->GetPrincipal();
michael@0 1065 } else {
michael@0 1066 principal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
michael@0 1067 MOZ_ASSERT(NS_FAILED(rv) || principal, "Bad return from do_CreateInstance");
michael@0 1068
michael@0 1069 if (!principal || NS_FAILED(rv)) {
michael@0 1070 if (NS_SUCCEEDED(rv)) {
michael@0 1071 rv = NS_ERROR_FAILURE;
michael@0 1072 }
michael@0 1073
michael@0 1074 return rv;
michael@0 1075 }
michael@0 1076 }
michael@0 1077 MOZ_ASSERT(principal);
michael@0 1078 }
michael@0 1079
michael@0 1080 JS::CompartmentOptions compartmentOptions;
michael@0 1081 if (options.sameZoneAs)
michael@0 1082 compartmentOptions.setSameZoneAs(js::UncheckedUnwrap(options.sameZoneAs));
michael@0 1083 else
michael@0 1084 compartmentOptions.setZone(JS::SystemZone);
michael@0 1085
michael@0 1086 compartmentOptions.setInvisibleToDebugger(options.invisibleToDebugger)
michael@0 1087 .setDiscardSource(options.discardSource)
michael@0 1088 .setTrace(TraceXPCGlobal);
michael@0 1089
michael@0 1090 RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, &SandboxClass,
michael@0 1091 principal, compartmentOptions));
michael@0 1092 if (!sandbox)
michael@0 1093 return NS_ERROR_FAILURE;
michael@0 1094
michael@0 1095 // Set up the wantXrays flag, which indicates whether xrays are desired even
michael@0 1096 // for same-origin access.
michael@0 1097 //
michael@0 1098 // This flag has historically been ignored for chrome sandboxes due to
michael@0 1099 // quirks in the wrapping implementation that have now been removed. Indeed,
michael@0 1100 // same-origin Xrays for chrome->chrome access seems a bit superfluous.
michael@0 1101 // Arguably we should just flip the default for chrome and still honor the
michael@0 1102 // flag, but such a change would break code in subtle ways for minimal
michael@0 1103 // benefit. So we just switch it off here.
michael@0 1104 xpc::GetCompartmentPrivate(sandbox)->wantXrays =
michael@0 1105 AccessCheck::isChrome(sandbox) ? false : options.wantXrays;
michael@0 1106
michael@0 1107 {
michael@0 1108 JSAutoCompartment ac(cx, sandbox);
michael@0 1109
michael@0 1110 if (options.proto) {
michael@0 1111 bool ok = JS_WrapObject(cx, &options.proto);
michael@0 1112 if (!ok)
michael@0 1113 return NS_ERROR_XPC_UNEXPECTED;
michael@0 1114
michael@0 1115 if (xpc::WrapperFactory::IsXrayWrapper(options.proto) && !options.wantXrays) {
michael@0 1116 RootedValue v(cx, ObjectValue(*options.proto));
michael@0 1117 if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v))
michael@0 1118 return NS_ERROR_FAILURE;
michael@0 1119 options.proto = &v.toObject();
michael@0 1120 }
michael@0 1121
michael@0 1122 // Now check what sort of thing we've got in |proto|
michael@0 1123 JSObject *unwrappedProto = js::UncheckedUnwrap(options.proto, false);
michael@0 1124 const js::Class *unwrappedClass = js::GetObjectClass(unwrappedProto);
michael@0 1125 if (IS_WN_CLASS(unwrappedClass) ||
michael@0 1126 mozilla::dom::IsDOMClass(Jsvalify(unwrappedClass))) {
michael@0 1127 // Wrap it up in a proxy that will do the right thing in terms
michael@0 1128 // of this-binding for methods.
michael@0 1129 RootedValue priv(cx, ObjectValue(*options.proto));
michael@0 1130 options.proto = js::NewProxyObject(cx, &xpc::sandboxProxyHandler,
michael@0 1131 priv, nullptr, sandbox);
michael@0 1132 if (!options.proto)
michael@0 1133 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1134 }
michael@0 1135
michael@0 1136 ok = JS_SetPrototype(cx, sandbox, options.proto);
michael@0 1137 if (!ok)
michael@0 1138 return NS_ERROR_XPC_UNEXPECTED;
michael@0 1139 }
michael@0 1140
michael@0 1141 nsCOMPtr<nsIScriptObjectPrincipal> sbp =
michael@0 1142 new SandboxPrivate(principal, sandbox);
michael@0 1143
michael@0 1144 // Pass on ownership of sbp to |sandbox|.
michael@0 1145 JS_SetPrivate(sandbox, sbp.forget().take());
michael@0 1146
michael@0 1147 bool allowComponents = nsContentUtils::IsSystemPrincipal(principal) ||
michael@0 1148 nsContentUtils::IsExpandedPrincipal(principal);
michael@0 1149 if (options.wantComponents && allowComponents &&
michael@0 1150 !GetObjectScope(sandbox)->AttachComponentsObject(cx))
michael@0 1151 return NS_ERROR_XPC_UNEXPECTED;
michael@0 1152
michael@0 1153 if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox))
michael@0 1154 return NS_ERROR_XPC_UNEXPECTED;
michael@0 1155
michael@0 1156 if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions))
michael@0 1157 return NS_ERROR_XPC_UNEXPECTED;
michael@0 1158
michael@0 1159 if (options.wantExportHelpers &&
michael@0 1160 (!JS_DefineFunction(cx, sandbox, "exportFunction", ExportFunction, 3, 0) ||
michael@0 1161 !JS_DefineFunction(cx, sandbox, "evalInWindow", EvalInWindow, 2, 0) ||
michael@0 1162 !JS_DefineFunction(cx, sandbox, "createObjectIn", CreateObjectIn, 2, 0) ||
michael@0 1163 !JS_DefineFunction(cx, sandbox, "cloneInto", CloneInto, 3, 0) ||
michael@0 1164 !JS_DefineFunction(cx, sandbox, "isProxy", IsProxy, 1, 0)))
michael@0 1165 return NS_ERROR_XPC_UNEXPECTED;
michael@0 1166
michael@0 1167 if (!options.globalProperties.Define(cx, sandbox))
michael@0 1168 return NS_ERROR_XPC_UNEXPECTED;
michael@0 1169 }
michael@0 1170
michael@0 1171
michael@0 1172 // We have this crazy behavior where wantXrays=false also implies that the
michael@0 1173 // returned sandbox is implicitly waived. We've stopped advertising it, but
michael@0 1174 // keep supporting it for now.
michael@0 1175 vp.setObject(*sandbox);
michael@0 1176 if (options.wantXrays && !JS_WrapValue(cx, vp))
michael@0 1177 return NS_ERROR_UNEXPECTED;
michael@0 1178 if (!options.wantXrays && !xpc::WrapperFactory::WaiveXrayAndWrap(cx, vp))
michael@0 1179 return NS_ERROR_UNEXPECTED;
michael@0 1180
michael@0 1181 // Set the location information for the new global, so that tools like
michael@0 1182 // about:memory may use that information
michael@0 1183 xpc::SetLocationForGlobal(sandbox, options.sandboxName);
michael@0 1184
michael@0 1185 xpc::SetSandboxMetadata(cx, sandbox, options.metadata);
michael@0 1186
michael@0 1187 JS_FireOnNewGlobalObject(cx, sandbox);
michael@0 1188
michael@0 1189 return NS_OK;
michael@0 1190 }
michael@0 1191
michael@0 1192 /* bool call(in nsIXPConnectWrappedNative wrapper,
michael@0 1193 * in JSContextPtr cx,
michael@0 1194 * in JSObjectPtr obj,
michael@0 1195 * in uint32_t argc,
michael@0 1196 * in JSValPtr argv,
michael@0 1197 * in JSValPtr vp);
michael@0 1198 */
michael@0 1199 NS_IMETHODIMP
michael@0 1200 nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
michael@0 1201 JSObject *objArg, const CallArgs &args, bool *_retval)
michael@0 1202 {
michael@0 1203 RootedObject obj(cx, objArg);
michael@0 1204 return CallOrConstruct(wrapper, cx, obj, args, _retval);
michael@0 1205 }
michael@0 1206
michael@0 1207 /* bool construct(in nsIXPConnectWrappedNative wrapper,
michael@0 1208 * in JSContextPtr cx,
michael@0 1209 * in JSObjectPtr obj,
michael@0 1210 * in uint32_t argc,
michael@0 1211 * in JSValPtr argv,
michael@0 1212 * in JSValPtr vp);
michael@0 1213 */
michael@0 1214 NS_IMETHODIMP
michael@0 1215 nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
michael@0 1216 JSObject *objArg, const CallArgs &args, bool *_retval)
michael@0 1217 {
michael@0 1218 RootedObject obj(cx, objArg);
michael@0 1219 return CallOrConstruct(wrapper, cx, obj, args, _retval);
michael@0 1220 }
michael@0 1221
michael@0 1222 /*
michael@0 1223 * For sandbox constructor the first argument can be a URI string in which case
michael@0 1224 * we use the related Codebase Principal for the sandbox.
michael@0 1225 */
michael@0 1226 bool
michael@0 1227 ParsePrincipal(JSContext *cx, HandleString codebase, nsIPrincipal **principal)
michael@0 1228 {
michael@0 1229 MOZ_ASSERT(principal);
michael@0 1230 MOZ_ASSERT(codebase);
michael@0 1231 nsCOMPtr<nsIURI> uri;
michael@0 1232 nsDependentJSString codebaseStr;
michael@0 1233 NS_ENSURE_TRUE(codebaseStr.init(cx, codebase), false);
michael@0 1234 nsresult rv = NS_NewURI(getter_AddRefs(uri), codebaseStr);
michael@0 1235 if (NS_FAILED(rv)) {
michael@0 1236 JS_ReportError(cx, "Creating URI from string failed");
michael@0 1237 return false;
michael@0 1238 }
michael@0 1239
michael@0 1240 nsCOMPtr<nsIScriptSecurityManager> secman =
michael@0 1241 do_GetService(kScriptSecurityManagerContractID);
michael@0 1242 NS_ENSURE_TRUE(secman, false);
michael@0 1243
michael@0 1244 // We could allow passing in the app-id and browser-element info to the
michael@0 1245 // sandbox constructor. But creating a sandbox based on a string is a
michael@0 1246 // deprecated API so no need to add features to it.
michael@0 1247 rv = secman->GetNoAppCodebasePrincipal(uri, principal);
michael@0 1248 if (NS_FAILED(rv) || !*principal) {
michael@0 1249 JS_ReportError(cx, "Creating Principal from URI failed");
michael@0 1250 return false;
michael@0 1251 }
michael@0 1252 return true;
michael@0 1253 }
michael@0 1254
michael@0 1255 /*
michael@0 1256 * For sandbox constructor the first argument can be a principal object or
michael@0 1257 * a script object principal (Document, Window).
michael@0 1258 */
michael@0 1259 static bool
michael@0 1260 GetPrincipalOrSOP(JSContext *cx, HandleObject from, nsISupports **out)
michael@0 1261 {
michael@0 1262 MOZ_ASSERT(out);
michael@0 1263 *out = nullptr;
michael@0 1264
michael@0 1265 nsXPConnect* xpc = nsXPConnect::XPConnect();
michael@0 1266 nsISupports* native = xpc->GetNativeOfWrapper(cx, from);
michael@0 1267
michael@0 1268 if (nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(native)) {
michael@0 1269 sop.forget(out);
michael@0 1270 return true;
michael@0 1271 }
michael@0 1272
michael@0 1273 nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(native);
michael@0 1274 principal.forget(out);
michael@0 1275 NS_ENSURE_TRUE(*out, false);
michael@0 1276
michael@0 1277 return true;
michael@0 1278 }
michael@0 1279
michael@0 1280 /*
michael@0 1281 * The first parameter of the sandbox constructor might be an array of principals, either in string
michael@0 1282 * format or actual objects (see GetPrincipalOrSOP)
michael@0 1283 */
michael@0 1284 static bool
michael@0 1285 GetExpandedPrincipal(JSContext *cx, HandleObject arrayObj, nsIExpandedPrincipal **out)
michael@0 1286 {
michael@0 1287 MOZ_ASSERT(out);
michael@0 1288 uint32_t length;
michael@0 1289
michael@0 1290 if (!JS_IsArrayObject(cx, arrayObj) ||
michael@0 1291 !JS_GetArrayLength(cx, arrayObj, &length) ||
michael@0 1292 !length)
michael@0 1293 {
michael@0 1294 // We need a whitelist of principals or uri strings to create an
michael@0 1295 // expanded principal, if we got an empty array or something else
michael@0 1296 // report error.
michael@0 1297 JS_ReportError(cx, "Expected an array of URI strings");
michael@0 1298 return false;
michael@0 1299 }
michael@0 1300
michael@0 1301 nsTArray< nsCOMPtr<nsIPrincipal> > allowedDomains(length);
michael@0 1302 allowedDomains.SetLength(length);
michael@0 1303 nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
michael@0 1304 NS_ENSURE_TRUE(ssm, false);
michael@0 1305
michael@0 1306 for (uint32_t i = 0; i < length; ++i) {
michael@0 1307 RootedValue allowed(cx);
michael@0 1308 if (!JS_GetElement(cx, arrayObj, i, &allowed))
michael@0 1309 return false;
michael@0 1310
michael@0 1311 nsresult rv;
michael@0 1312 nsCOMPtr<nsIPrincipal> principal;
michael@0 1313 if (allowed.isString()) {
michael@0 1314 // In case of string let's try to fetch a codebase principal from it.
michael@0 1315 RootedString str(cx, allowed.toString());
michael@0 1316 if (!ParsePrincipal(cx, str, getter_AddRefs(principal)))
michael@0 1317 return false;
michael@0 1318
michael@0 1319 } else if (allowed.isObject()) {
michael@0 1320 // In case of object let's see if it's a Principal or a ScriptObjectPrincipal.
michael@0 1321 nsCOMPtr<nsISupports> prinOrSop;
michael@0 1322 RootedObject obj(cx, &allowed.toObject());
michael@0 1323 if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)))
michael@0 1324 return false;
michael@0 1325
michael@0 1326 nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(prinOrSop));
michael@0 1327 principal = do_QueryInterface(prinOrSop);
michael@0 1328 if (sop)
michael@0 1329 principal = sop->GetPrincipal();
michael@0 1330 }
michael@0 1331 NS_ENSURE_TRUE(principal, false);
michael@0 1332
michael@0 1333 // We do not allow ExpandedPrincipals to contain any system principals.
michael@0 1334 bool isSystem;
michael@0 1335 rv = ssm->IsSystemPrincipal(principal, &isSystem);
michael@0 1336 NS_ENSURE_SUCCESS(rv, false);
michael@0 1337 if (isSystem) {
michael@0 1338 JS_ReportError(cx, "System principal is not allowed in an expanded principal");
michael@0 1339 return false;
michael@0 1340 }
michael@0 1341 allowedDomains[i] = principal;
michael@0 1342 }
michael@0 1343
michael@0 1344 nsCOMPtr<nsIExpandedPrincipal> result = new nsExpandedPrincipal(allowedDomains);
michael@0 1345 result.forget(out);
michael@0 1346 return true;
michael@0 1347 }
michael@0 1348
michael@0 1349 /*
michael@0 1350 * Helper that tries to get a property from the options object.
michael@0 1351 */
michael@0 1352 bool
michael@0 1353 OptionsBase::ParseValue(const char *name, MutableHandleValue prop, bool *aFound)
michael@0 1354 {
michael@0 1355 bool found;
michael@0 1356 bool ok = JS_HasProperty(mCx, mObject, name, &found);
michael@0 1357 NS_ENSURE_TRUE(ok, false);
michael@0 1358
michael@0 1359 if (aFound)
michael@0 1360 *aFound = found;
michael@0 1361
michael@0 1362 if (!found)
michael@0 1363 return true;
michael@0 1364
michael@0 1365 return JS_GetProperty(mCx, mObject, name, prop);
michael@0 1366 }
michael@0 1367
michael@0 1368 /*
michael@0 1369 * Helper that tries to get a boolean property from the options object.
michael@0 1370 */
michael@0 1371 bool
michael@0 1372 OptionsBase::ParseBoolean(const char *name, bool *prop)
michael@0 1373 {
michael@0 1374 MOZ_ASSERT(prop);
michael@0 1375 RootedValue value(mCx);
michael@0 1376 bool found;
michael@0 1377 bool ok = ParseValue(name, &value, &found);
michael@0 1378 NS_ENSURE_TRUE(ok, false);
michael@0 1379
michael@0 1380 if (!found)
michael@0 1381 return true;
michael@0 1382
michael@0 1383 if (!value.isBoolean()) {
michael@0 1384 JS_ReportError(mCx, "Expected a boolean value for property %s", name);
michael@0 1385 return false;
michael@0 1386 }
michael@0 1387
michael@0 1388 *prop = value.toBoolean();
michael@0 1389 return true;
michael@0 1390 }
michael@0 1391
michael@0 1392 /*
michael@0 1393 * Helper that tries to get an object property from the options object.
michael@0 1394 */
michael@0 1395 bool
michael@0 1396 OptionsBase::ParseObject(const char *name, MutableHandleObject prop)
michael@0 1397 {
michael@0 1398 RootedValue value(mCx);
michael@0 1399 bool found;
michael@0 1400 bool ok = ParseValue(name, &value, &found);
michael@0 1401 NS_ENSURE_TRUE(ok, false);
michael@0 1402
michael@0 1403 if (!found)
michael@0 1404 return true;
michael@0 1405
michael@0 1406 if (!value.isObject()) {
michael@0 1407 JS_ReportError(mCx, "Expected an object value for property %s", name);
michael@0 1408 return false;
michael@0 1409 }
michael@0 1410 prop.set(&value.toObject());
michael@0 1411 return true;
michael@0 1412 }
michael@0 1413
michael@0 1414 /*
michael@0 1415 * Helper that tries to get a string property from the options object.
michael@0 1416 */
michael@0 1417 bool
michael@0 1418 OptionsBase::ParseString(const char *name, nsCString &prop)
michael@0 1419 {
michael@0 1420 RootedValue value(mCx);
michael@0 1421 bool found;
michael@0 1422 bool ok = ParseValue(name, &value, &found);
michael@0 1423 NS_ENSURE_TRUE(ok, false);
michael@0 1424
michael@0 1425 if (!found)
michael@0 1426 return true;
michael@0 1427
michael@0 1428 if (!value.isString()) {
michael@0 1429 JS_ReportError(mCx, "Expected a string value for property %s", name);
michael@0 1430 return false;
michael@0 1431 }
michael@0 1432
michael@0 1433 char *tmp = JS_EncodeString(mCx, value.toString());
michael@0 1434 NS_ENSURE_TRUE(tmp, false);
michael@0 1435 prop.Adopt(tmp, strlen(tmp));
michael@0 1436 return true;
michael@0 1437 }
michael@0 1438
michael@0 1439 /*
michael@0 1440 * Helper that tries to get a string property from the options object.
michael@0 1441 */
michael@0 1442 bool
michael@0 1443 OptionsBase::ParseString(const char *name, nsString &prop)
michael@0 1444 {
michael@0 1445 RootedValue value(mCx);
michael@0 1446 bool found;
michael@0 1447 bool ok = ParseValue(name, &value, &found);
michael@0 1448 NS_ENSURE_TRUE(ok, false);
michael@0 1449
michael@0 1450 if (!found)
michael@0 1451 return true;
michael@0 1452
michael@0 1453 if (!value.isString()) {
michael@0 1454 JS_ReportError(mCx, "Expected a string value for property %s", name);
michael@0 1455 return false;
michael@0 1456 }
michael@0 1457
michael@0 1458 nsDependentJSString strVal;
michael@0 1459 strVal.init(mCx, value.toString());
michael@0 1460 prop = strVal;
michael@0 1461 return true;
michael@0 1462 }
michael@0 1463
michael@0 1464 /*
michael@0 1465 * Helper that tries to get jsid property from the options object.
michael@0 1466 */
michael@0 1467 bool
michael@0 1468 OptionsBase::ParseId(const char *name, MutableHandleId prop)
michael@0 1469 {
michael@0 1470 RootedValue value(mCx);
michael@0 1471 bool found;
michael@0 1472 bool ok = ParseValue(name, &value, &found);
michael@0 1473 NS_ENSURE_TRUE(ok, false);
michael@0 1474
michael@0 1475 if (!found)
michael@0 1476 return true;
michael@0 1477
michael@0 1478 return JS_ValueToId(mCx, value, prop);
michael@0 1479 }
michael@0 1480
michael@0 1481 /*
michael@0 1482 * Helper that tries to get a list of DOM constructors and other helpers from the options object.
michael@0 1483 */
michael@0 1484 bool
michael@0 1485 SandboxOptions::ParseGlobalProperties()
michael@0 1486 {
michael@0 1487 RootedValue value(mCx);
michael@0 1488 bool found;
michael@0 1489 bool ok = ParseValue("wantGlobalProperties", &value, &found);
michael@0 1490 NS_ENSURE_TRUE(ok, false);
michael@0 1491 if (!found)
michael@0 1492 return true;
michael@0 1493
michael@0 1494 if (!value.isObject()) {
michael@0 1495 JS_ReportError(mCx, "Expected an array value for wantGlobalProperties");
michael@0 1496 return false;
michael@0 1497 }
michael@0 1498
michael@0 1499 RootedObject ctors(mCx, &value.toObject());
michael@0 1500 if (!JS_IsArrayObject(mCx, ctors)) {
michael@0 1501 JS_ReportError(mCx, "Expected an array value for wantGlobalProperties");
michael@0 1502 return false;
michael@0 1503 }
michael@0 1504
michael@0 1505 return globalProperties.Parse(mCx, ctors);
michael@0 1506 }
michael@0 1507
michael@0 1508 /*
michael@0 1509 * Helper that parsing the sandbox options object (from) and sets the fields of the incoming options struct (options).
michael@0 1510 */
michael@0 1511 bool
michael@0 1512 SandboxOptions::Parse()
michael@0 1513 {
michael@0 1514 return ParseObject("sandboxPrototype", &proto) &&
michael@0 1515 ParseBoolean("wantXrays", &wantXrays) &&
michael@0 1516 ParseBoolean("wantComponents", &wantComponents) &&
michael@0 1517 ParseBoolean("wantExportHelpers", &wantExportHelpers) &&
michael@0 1518 ParseString("sandboxName", sandboxName) &&
michael@0 1519 ParseObject("sameZoneAs", &sameZoneAs) &&
michael@0 1520 ParseBoolean("invisibleToDebugger", &invisibleToDebugger) &&
michael@0 1521 ParseBoolean("discardSource", &discardSource) &&
michael@0 1522 ParseGlobalProperties() &&
michael@0 1523 ParseValue("metadata", &metadata);
michael@0 1524 }
michael@0 1525
michael@0 1526 static nsresult
michael@0 1527 AssembleSandboxMemoryReporterName(JSContext *cx, nsCString &sandboxName)
michael@0 1528 {
michael@0 1529 // Use a default name when the caller did not provide a sandboxName.
michael@0 1530 if (sandboxName.IsEmpty())
michael@0 1531 sandboxName = NS_LITERAL_CSTRING("[anonymous sandbox]");
michael@0 1532
michael@0 1533 nsXPConnect* xpc = nsXPConnect::XPConnect();
michael@0 1534 // Get the xpconnect native call context.
michael@0 1535 nsAXPCNativeCallContext *cc = nullptr;
michael@0 1536 xpc->GetCurrentNativeCallContext(&cc);
michael@0 1537 NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG);
michael@0 1538
michael@0 1539 // Get the current source info from xpc.
michael@0 1540 nsCOMPtr<nsIStackFrame> frame;
michael@0 1541 xpc->GetCurrentJSStack(getter_AddRefs(frame));
michael@0 1542
michael@0 1543 // Append the caller's location information.
michael@0 1544 if (frame) {
michael@0 1545 nsString location;
michael@0 1546 int32_t lineNumber = 0;
michael@0 1547 frame->GetFilename(location);
michael@0 1548 frame->GetLineNumber(&lineNumber);
michael@0 1549
michael@0 1550 sandboxName.AppendLiteral(" (from: ");
michael@0 1551 sandboxName.Append(NS_ConvertUTF16toUTF8(location));
michael@0 1552 sandboxName.AppendLiteral(":");
michael@0 1553 sandboxName.AppendInt(lineNumber);
michael@0 1554 sandboxName.AppendLiteral(")");
michael@0 1555 }
michael@0 1556
michael@0 1557 return NS_OK;
michael@0 1558 }
michael@0 1559
michael@0 1560 // static
michael@0 1561 nsresult
michael@0 1562 nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative *wrapper,
michael@0 1563 JSContext *cx, HandleObject obj,
michael@0 1564 const CallArgs &args, bool *_retval)
michael@0 1565 {
michael@0 1566 if (args.length() < 1)
michael@0 1567 return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval);
michael@0 1568
michael@0 1569 nsresult rv;
michael@0 1570 bool ok = false;
michael@0 1571
michael@0 1572 // Make sure to set up principals on the sandbox before initing classes.
michael@0 1573 nsCOMPtr<nsIPrincipal> principal;
michael@0 1574 nsCOMPtr<nsIExpandedPrincipal> expanded;
michael@0 1575 nsCOMPtr<nsISupports> prinOrSop;
michael@0 1576
michael@0 1577 if (args[0].isString()) {
michael@0 1578 RootedString str(cx, args[0].toString());
michael@0 1579 ok = ParsePrincipal(cx, str, getter_AddRefs(principal));
michael@0 1580 prinOrSop = principal;
michael@0 1581 } else if (args[0].isObject()) {
michael@0 1582 RootedObject obj(cx, &args[0].toObject());
michael@0 1583 if (JS_IsArrayObject(cx, obj)) {
michael@0 1584 ok = GetExpandedPrincipal(cx, obj, getter_AddRefs(expanded));
michael@0 1585 prinOrSop = expanded;
michael@0 1586 } else {
michael@0 1587 ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop));
michael@0 1588 }
michael@0 1589 }
michael@0 1590
michael@0 1591 if (!ok)
michael@0 1592 return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
michael@0 1593
michael@0 1594 bool calledWithOptions = args.length() > 1;
michael@0 1595 if (calledWithOptions && !args[1].isObject())
michael@0 1596 return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
michael@0 1597
michael@0 1598 RootedObject optionsObject(cx, calledWithOptions ? &args[1].toObject()
michael@0 1599 : nullptr);
michael@0 1600
michael@0 1601 SandboxOptions options(cx, optionsObject);
michael@0 1602 if (calledWithOptions && !options.Parse())
michael@0 1603 return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
michael@0 1604
michael@0 1605 if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName)))
michael@0 1606 return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
michael@0 1607
michael@0 1608 if (options.metadata.isNullOrUndefined()) {
michael@0 1609 // If the caller is running in a sandbox, inherit.
michael@0 1610 RootedObject callerGlobal(cx, CurrentGlobalOrNull(cx));
michael@0 1611 if (IsSandbox(callerGlobal)) {
michael@0 1612 rv = GetSandboxMetadata(cx, callerGlobal, &options.metadata);
michael@0 1613 if (NS_WARN_IF(NS_FAILED(rv)))
michael@0 1614 return rv;
michael@0 1615 }
michael@0 1616 }
michael@0 1617
michael@0 1618 rv = CreateSandboxObject(cx, args.rval(), prinOrSop, options);
michael@0 1619
michael@0 1620 if (NS_FAILED(rv))
michael@0 1621 return ThrowAndFail(rv, cx, _retval);
michael@0 1622
michael@0 1623 *_retval = true;
michael@0 1624 return NS_OK;
michael@0 1625 }
michael@0 1626
michael@0 1627 class ContextHolder : public nsIScriptObjectPrincipal
michael@0 1628 {
michael@0 1629 public:
michael@0 1630 ContextHolder(JSContext *aOuterCx, HandleObject aSandbox, nsIPrincipal *aPrincipal);
michael@0 1631 virtual ~ContextHolder();
michael@0 1632
michael@0 1633 JSContext * GetJSContext()
michael@0 1634 {
michael@0 1635 return mJSContext;
michael@0 1636 }
michael@0 1637
michael@0 1638 nsIPrincipal * GetPrincipal() { return mPrincipal; }
michael@0 1639
michael@0 1640 NS_DECL_ISUPPORTS
michael@0 1641
michael@0 1642 private:
michael@0 1643 JSContext* mJSContext;
michael@0 1644 nsCOMPtr<nsIPrincipal> mPrincipal;
michael@0 1645 };
michael@0 1646
michael@0 1647 NS_IMPL_ISUPPORTS(ContextHolder, nsIScriptObjectPrincipal)
michael@0 1648
michael@0 1649 ContextHolder::ContextHolder(JSContext *aOuterCx,
michael@0 1650 HandleObject aSandbox,
michael@0 1651 nsIPrincipal *aPrincipal)
michael@0 1652 : mJSContext(JS_NewContext(JS_GetRuntime(aOuterCx), 1024)),
michael@0 1653 mPrincipal(aPrincipal)
michael@0 1654 {
michael@0 1655 if (mJSContext) {
michael@0 1656 bool isChrome;
michael@0 1657 DebugOnly<nsresult> rv = XPCWrapper::GetSecurityManager()->
michael@0 1658 IsSystemPrincipal(mPrincipal, &isChrome);
michael@0 1659 MOZ_ASSERT(NS_SUCCEEDED(rv));
michael@0 1660
michael@0 1661 JS::ContextOptionsRef(mJSContext).setDontReportUncaught(true)
michael@0 1662 .setPrivateIsNSISupports(true);
michael@0 1663 js::SetDefaultObjectForContext(mJSContext, aSandbox);
michael@0 1664 JS_SetContextPrivate(mJSContext, this);
michael@0 1665 }
michael@0 1666 }
michael@0 1667
michael@0 1668 ContextHolder::~ContextHolder()
michael@0 1669 {
michael@0 1670 if (mJSContext)
michael@0 1671 JS_DestroyContextNoGC(mJSContext);
michael@0 1672 }
michael@0 1673
michael@0 1674 nsresult
michael@0 1675 xpc::EvalInSandbox(JSContext *cx, HandleObject sandboxArg, const nsAString& source,
michael@0 1676 const nsACString& filename, int32_t lineNo,
michael@0 1677 JSVersion jsVersion, bool returnStringOnly, MutableHandleValue rval)
michael@0 1678 {
michael@0 1679 JS_AbortIfWrongThread(JS_GetRuntime(cx));
michael@0 1680 rval.set(UndefinedValue());
michael@0 1681
michael@0 1682 bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg);
michael@0 1683 RootedObject sandbox(cx, js::CheckedUnwrap(sandboxArg));
michael@0 1684 if (!sandbox || js::GetObjectJSClass(sandbox) != &SandboxClass) {
michael@0 1685 return NS_ERROR_INVALID_ARG;
michael@0 1686 }
michael@0 1687
michael@0 1688 nsIScriptObjectPrincipal *sop =
michael@0 1689 (nsIScriptObjectPrincipal*)xpc_GetJSPrivate(sandbox);
michael@0 1690 MOZ_ASSERT(sop, "Invalid sandbox passed");
michael@0 1691 nsCOMPtr<nsIPrincipal> prin = sop->GetPrincipal();
michael@0 1692 NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE);
michael@0 1693
michael@0 1694 nsAutoCString filenameBuf;
michael@0 1695 if (!filename.IsVoid()) {
michael@0 1696 filenameBuf.Assign(filename);
michael@0 1697 } else {
michael@0 1698 // Default to the spec of the principal.
michael@0 1699 nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf);
michael@0 1700 lineNo = 1;
michael@0 1701 }
michael@0 1702
michael@0 1703 // We create a separate cx to do the sandbox evaluation. Scope it.
michael@0 1704 RootedValue v(cx, UndefinedValue());
michael@0 1705 RootedValue exn(cx, UndefinedValue());
michael@0 1706 bool ok = true;
michael@0 1707 {
michael@0 1708 // Make a special cx for the sandbox and push it.
michael@0 1709 // NB: As soon as the RefPtr goes away, the cx goes away. So declare
michael@0 1710 // it first so that it disappears last.
michael@0 1711 nsRefPtr<ContextHolder> sandcxHolder = new ContextHolder(cx, sandbox, prin);
michael@0 1712 JSContext *sandcx = sandcxHolder->GetJSContext();
michael@0 1713 if (!sandcx) {
michael@0 1714 JS_ReportError(cx, "Can't prepare context for evalInSandbox");
michael@0 1715 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1716 }
michael@0 1717 nsCxPusher pusher;
michael@0 1718 pusher.Push(sandcx);
michael@0 1719 JSAutoCompartment ac(sandcx, sandbox);
michael@0 1720
michael@0 1721 JS::CompileOptions options(sandcx);
michael@0 1722 options.setFileAndLine(filenameBuf.get(), lineNo);
michael@0 1723 if (jsVersion != JSVERSION_DEFAULT)
michael@0 1724 options.setVersion(jsVersion);
michael@0 1725 JS::RootedObject rootedSandbox(sandcx, sandbox);
michael@0 1726 ok = JS::Evaluate(sandcx, rootedSandbox, options,
michael@0 1727 PromiseFlatString(source).get(), source.Length(), &v);
michael@0 1728 if (ok && returnStringOnly && !v.isUndefined()) {
michael@0 1729 JSString *str = ToString(sandcx, v);
michael@0 1730 ok = !!str;
michael@0 1731 v = ok ? JS::StringValue(str) : JS::UndefinedValue();
michael@0 1732 }
michael@0 1733
michael@0 1734 // If the sandbox threw an exception, grab it off the context.
michael@0 1735 if (JS_GetPendingException(sandcx, &exn)) {
michael@0 1736 MOZ_ASSERT(!ok);
michael@0 1737 JS_ClearPendingException(sandcx);
michael@0 1738 if (returnStringOnly) {
michael@0 1739 // The caller asked for strings only, convert the
michael@0 1740 // exception into a string.
michael@0 1741 JSString *str = ToString(sandcx, exn);
michael@0 1742 exn = str ? JS::StringValue(str) : JS::UndefinedValue();
michael@0 1743 }
michael@0 1744 }
michael@0 1745 }
michael@0 1746
michael@0 1747 //
michael@0 1748 // Alright, we're back on the caller's cx. If an error occured, try to
michael@0 1749 // wrap and set the exception. Otherwise, wrap the return value.
michael@0 1750 //
michael@0 1751
michael@0 1752 if (!ok) {
michael@0 1753 // If we end up without an exception, it was probably due to OOM along
michael@0 1754 // the way, in which case we thow. Otherwise, wrap it.
michael@0 1755 if (exn.isUndefined() || !JS_WrapValue(cx, &exn))
michael@0 1756 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1757
michael@0 1758 // Set the exception on our caller's cx.
michael@0 1759 JS_SetPendingException(cx, exn);
michael@0 1760 return NS_ERROR_FAILURE;
michael@0 1761 }
michael@0 1762
michael@0 1763 // Transitively apply Xray waivers if |sb| was waived.
michael@0 1764 if (waiveXray) {
michael@0 1765 ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v);
michael@0 1766 } else {
michael@0 1767 ok = JS_WrapValue(cx, &v);
michael@0 1768 }
michael@0 1769 NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
michael@0 1770
michael@0 1771 // Whew!
michael@0 1772 rval.set(v);
michael@0 1773 return NS_OK;
michael@0 1774 }
michael@0 1775
michael@0 1776 static bool
michael@0 1777 NonCloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
michael@0 1778 {
michael@0 1779 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1780
michael@0 1781 RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
michael@0 1782 MOZ_ASSERT(v.isObject(), "weird function");
michael@0 1783
michael@0 1784 RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
michael@0 1785 if (!obj) {
michael@0 1786 return false;
michael@0 1787 }
michael@0 1788 return JS_CallFunctionValue(cx, obj, v, args, args.rval());
michael@0 1789 }
michael@0 1790
michael@0 1791 /*
michael@0 1792 * Forwards the call to the exported function. Clones all the non reflectors, ignores
michael@0 1793 * the |this| argument.
michael@0 1794 */
michael@0 1795 static bool
michael@0 1796 CloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
michael@0 1797 {
michael@0 1798 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1799
michael@0 1800 RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
michael@0 1801 NS_ASSERTION(v.isObject(), "weird function");
michael@0 1802 RootedObject origFunObj(cx, UncheckedUnwrap(&v.toObject()));
michael@0 1803 {
michael@0 1804 JSAutoCompartment ac(cx, origFunObj);
michael@0 1805 // Note: only the arguments are cloned not the |this| or the |callee|.
michael@0 1806 // Function forwarder does not use those.
michael@0 1807 for (unsigned i = 0; i < args.length(); i++) {
michael@0 1808 if (!CloneNonReflectors(cx, args[i])) {
michael@0 1809 return false;
michael@0 1810 }
michael@0 1811 }
michael@0 1812
michael@0 1813 // JS API does not support any JSObject to JSFunction conversion,
michael@0 1814 // so let's use JS_CallFunctionValue instead.
michael@0 1815 RootedValue functionVal(cx, ObjectValue(*origFunObj));
michael@0 1816
michael@0 1817 if (!JS_CallFunctionValue(cx, JS::NullPtr(), functionVal, args, args.rval()))
michael@0 1818 return false;
michael@0 1819 }
michael@0 1820
michael@0 1821 // Return value must be wrapped.
michael@0 1822 return JS_WrapValue(cx, args.rval());
michael@0 1823 }
michael@0 1824
michael@0 1825 bool
michael@0 1826 xpc::NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, bool doclone,
michael@0 1827 MutableHandleValue vp)
michael@0 1828 {
michael@0 1829 JSFunction *fun = js::NewFunctionByIdWithReserved(cx, doclone ? CloningFunctionForwarder :
michael@0 1830 NonCloningFunctionForwarder,
michael@0 1831 0,0, JS::CurrentGlobalOrNull(cx), id);
michael@0 1832
michael@0 1833 if (!fun)
michael@0 1834 return false;
michael@0 1835
michael@0 1836 JSObject *funobj = JS_GetFunctionObject(fun);
michael@0 1837 js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable));
michael@0 1838 vp.setObject(*funobj);
michael@0 1839 return true;
michael@0 1840 }
michael@0 1841
michael@0 1842 bool
michael@0 1843 xpc::NewFunctionForwarder(JSContext *cx, HandleObject callable, bool doclone,
michael@0 1844 MutableHandleValue vp)
michael@0 1845 {
michael@0 1846 RootedId emptyId(cx);
michael@0 1847 RootedValue emptyStringValue(cx, JS_GetEmptyStringValue(cx));
michael@0 1848 if (!JS_ValueToId(cx, emptyStringValue, &emptyId))
michael@0 1849 return false;
michael@0 1850
michael@0 1851 return NewFunctionForwarder(cx, emptyId, callable, doclone, vp);
michael@0 1852 }
michael@0 1853
michael@0 1854
michael@0 1855 nsresult
michael@0 1856 xpc::GetSandboxMetadata(JSContext *cx, HandleObject sandbox, MutableHandleValue rval)
michael@0 1857 {
michael@0 1858 MOZ_ASSERT(NS_IsMainThread());
michael@0 1859 MOZ_ASSERT(IsSandbox(sandbox));
michael@0 1860
michael@0 1861 RootedValue metadata(cx);
michael@0 1862 {
michael@0 1863 JSAutoCompartment ac(cx, sandbox);
michael@0 1864 metadata = JS_GetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT);
michael@0 1865 }
michael@0 1866
michael@0 1867 if (!JS_WrapValue(cx, &metadata))
michael@0 1868 return NS_ERROR_UNEXPECTED;
michael@0 1869
michael@0 1870 rval.set(metadata);
michael@0 1871 return NS_OK;
michael@0 1872 }
michael@0 1873
michael@0 1874 nsresult
michael@0 1875 xpc::SetSandboxMetadata(JSContext *cx, HandleObject sandbox, HandleValue metadataArg)
michael@0 1876 {
michael@0 1877 MOZ_ASSERT(NS_IsMainThread());
michael@0 1878 MOZ_ASSERT(IsSandbox(sandbox));
michael@0 1879
michael@0 1880 RootedValue metadata(cx);
michael@0 1881
michael@0 1882 JSAutoCompartment ac(cx, sandbox);
michael@0 1883 if (!JS_StructuredClone(cx, metadataArg, &metadata, nullptr, nullptr))
michael@0 1884 return NS_ERROR_UNEXPECTED;
michael@0 1885
michael@0 1886 JS_SetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT, metadata);
michael@0 1887
michael@0 1888 return NS_OK;
michael@0 1889 }

mercurial