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