Bug 1521907 part 1. Add a version of CheckedUnwrap that can do a dynamic security check. r=jandem

We're going to need this because we will have multiple Realms in the same
compartment which want different CheckedUnwrap behavior in some cases.  So we
need to be able to check which Realm we're in.

Differential Revision: https://phabricator.services.mozilla.com/D17881

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Boris Zbarsky 2019-01-30 17:33:49 +00:00
Родитель c936f4be1a
Коммит 9927e55b7e
2 изменённых файлов: 89 добавлений и 4 удалений

Просмотреть файл

@ -131,6 +131,16 @@ class JS_FRIEND_API Wrapper : public ForwardingProxyHandler {
virtual bool finalizeInBackground(const Value& priv) const override;
/**
* A hook subclasses can override to implement CheckedUnwrapDynamic
* behavior. The JSContext represents the "who is trying to unwrap?" Realm.
* The JSObject is the wrapper that the caller is trying to unwrap.
*/
virtual bool dynamicCheckedUnwrapAllowed(JSObject* obj, JSContext* cx) const {
MOZ_ASSERT(hasSecurityPolicy(), "Why are you asking?");
return false;
}
using BaseProxyHandler::Action;
enum Flags { CROSS_COMPARTMENT = 1 << 0, LAST_USED_FLAG = CROSS_COMPARTMENT };
@ -387,14 +397,28 @@ JS_FRIEND_API JSObject* UncheckedUnwrap(JSObject* obj,
bool stopAtWindowProxy = true,
unsigned* flagsp = nullptr);
// Given a JSObject, returns that object stripped of wrappers. At each stage,
// the security wrapper has the opportunity to veto the unwrap. If
// stopAtWindowProxy is true, then this returns the WindowProxy if it was
// previously wrapped.
// Given a JSObject, returns that object stripped of wrappers, except
// WindowProxy wrappers. At each stage, the wrapper has the opportunity to veto
// the unwrap. Null is returned if there are security wrappers that can't be
// unwrapped.
//
// This does a static-only unwrap check: it basically checks whether _all_
// globals in the wrapper's source compartment should be able to access the
// wrapper target. This won't necessarily return the right thing for the HTML
// spec's cross-origin objects (WindowProxy and Location), but is fine to use
// when failure to unwrap one of those objects wouldn't be a problem. For
// example, if you want to test whether your target object is a specific class
// that's not WindowProxy or Location, you can use this.
//
// ExposeToActiveJS is called on wrapper targets to allow gray marking
// assertions to work while an incremental GC is in progress, but this means
// that this cannot be called from the GC or off the main thread.
JS_FRIEND_API JSObject* CheckedUnwrapStatic(JSObject* obj);
// Old CheckedUnwrap API that we would like to remove once we convert all
// callers to CheckedUnwrapStatic or CheckedUnwrapDynamic. If stopAtWindowProxy
// is true, then this returns the WindowProxy if a WindowProxy is encountered;
// otherwise it will unwrap the WindowProxy and return a Window.
JS_FRIEND_API JSObject* CheckedUnwrap(JSObject* obj,
bool stopAtWindowProxy = true);
@ -403,6 +427,27 @@ JS_FRIEND_API JSObject* CheckedUnwrap(JSObject* obj,
JS_FRIEND_API JSObject* UnwrapOneChecked(JSObject* obj,
bool stopAtWindowProxy = true);
// Given a JSObject, returns that object stripped of wrappers. At each stage,
// the security wrapper has the opportunity to veto the unwrap. If
// stopAtWindowProxy is true, then this returns the WindowProxy if it was
// previously wrapped. Null is returned if there are security wrappers that
// can't be unwrapped.
//
// ExposeToActiveJS is called on wrapper targets to allow gray marking
// assertions to work while an incremental GC is in progress, but this means
// that this cannot be called from the GC or off the main thread.
//
// The JSContext argument will be used for dynamic checks (needed by WindowProxy
// and Location) and should represent the Realm doing the unwrapping. It is not
// used to throw exceptions; this function never throws.
JS_FRIEND_API JSObject* CheckedUnwrapDynamic(JSObject* obj, JSContext* cx,
bool stopAtWindowProxy = true);
// Unwrap only the outermost security wrapper, with the same semantics as
// above. This is the checked version of Wrapper::wrappedObject.
JS_FRIEND_API JSObject* UnwrapOneCheckedDynamic(JSObject* obj, JSContext* cx,
bool stopAtWindowProxy = true);
// Given a JSObject, returns that object stripped of wrappers. This returns the
// WindowProxy if it was previously wrapped.
//

Просмотреть файл

@ -355,6 +355,12 @@ JS_FRIEND_API JSObject* js::UncheckedUnwrap(JSObject* wrapped,
return wrapped;
}
JS_FRIEND_API JSObject* js::CheckedUnwrapStatic(JSObject* obj) {
// For now, just forward to the old API. Once we remove it, we can
// inline it here, without the stopAtWindowProxy bits.
return CheckedUnwrap(obj);
}
JS_FRIEND_API JSObject* js::CheckedUnwrap(JSObject* obj,
bool stopAtWindowProxy) {
while (true) {
@ -380,6 +386,40 @@ JS_FRIEND_API JSObject* js::UnwrapOneChecked(JSObject* obj,
return handler->hasSecurityPolicy() ? nullptr : Wrapper::wrappedObject(obj);
}
JS_FRIEND_API JSObject* js::CheckedUnwrapDynamic(JSObject* obj, JSContext* cx,
bool stopAtWindowProxy) {
while (true) {
JSObject* wrapper = obj;
obj = UnwrapOneCheckedDynamic(obj, cx, stopAtWindowProxy);
if (!obj || obj == wrapper) {
return obj;
}
}
}
JS_FRIEND_API JSObject* js::UnwrapOneCheckedDynamic(JSObject* obj,
JSContext* cx,
bool stopAtWindowProxy) {
MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
MOZ_ASSERT(CurrentThreadCanAccessRuntime(obj->runtimeFromAnyThread()));
// We should know who's asking.
MOZ_ASSERT(cx);
MOZ_ASSERT(cx->realm());
if (!obj->is<WrapperObject>() ||
MOZ_UNLIKELY(stopAtWindowProxy && IsWindowProxy(obj))) {
return obj;
}
const Wrapper* handler = Wrapper::wrapperHandler(obj);
if (!handler->hasSecurityPolicy() ||
handler->dynamicCheckedUnwrapAllowed(obj, cx)) {
return Wrapper::wrappedObject(obj);
}
return nullptr;
}
void js::ReportAccessDenied(JSContext* cx) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_OBJECT_ACCESS_DENIED);