Bug 580128 - Always wrap Location objects in wrappers, even for same origin. r=mrbkap/gal

This commit is contained in:
Peter Van der Beken 2010-09-29 10:00:52 -07:00
parent 2ec85d518c
commit e036314b75
12 changed files with 169 additions and 16 deletions

View File

@ -2465,10 +2465,19 @@ nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj
jsClass = aObj->getClass();
} while (1);
NS_ASSERTION(!aAllowShortCircuit ||
result == doGetObjectPrincipal(origObj, PR_FALSE),
"Principal mismatch. Not good");
#ifdef DEBUG
if (aAllowShortCircuit) {
nsIPrincipal *principal = doGetObjectPrincipal(origObj, PR_FALSE);
// Location is always wrapped (even for same-compartment), so we can
// loosen the check to same-origin instead of same-principal.
NS_ASSERTION(strcmp(jsClass->name, "Location") == 0 ?
NS_SUCCEEDED(CheckSameOriginPrincipal(result, principal)) :
result == principal,
"Principal mismatch. Not good");
}
#endif
return result;
}

View File

@ -237,7 +237,7 @@ private:
class nsOuterWindowProxy : public JSWrapper
{
public:
nsOuterWindowProxy() : JSWrapper(0) {}
nsOuterWindowProxy() : JSWrapper((uintN)0) {}
virtual bool isOuterWindow() {
return true;

View File

@ -35,26 +35,35 @@ function runTest() {
is(lastlocation.iframeprop, 42, 'can read the new prop');
ok(firstlocation !== lastlocation, 'got a new location object');
// firstlocation should still work.
ok(firstlocation.href.indexOf('file_location.html'), 'can read location.href');
firstlocation.href = 'http://example.com/tests/dom/tests/mochitest/dom-level0/file_location.html';
return;
}
if (count == 3) {
var permissionDenied = false;
try {
isnot($('ifr').contentWindow.location.iframeprop, 42, "shouldn't see this");
var foo = $('ifr').contentWindow.location.href == '';
} catch (e) {
ok(/Permission denied/.test(e), "correctly threw a security error");
permissionDenied = /Permission denied/.test(e.message);
}
ok(permissionDenied, 'correctly threw a permission denied security error when reading location.href');
// XXX this doesn't work - file a bug on me!
//firstlocation.href = 'http://mochi.test:8888/tests/dom/tests/mochitest/dom-level0/file_location.html';
todo(false, 'can use old same-origin location object to set cross-origin frame');
$('ifr').contentWindow.location = 'file_location.html';
permissionDenied = false;
try {
var foo = $('ifr').contentWindow.location.iframeprop == 42;
} catch (e) {
permissionDenied = /Permission denied/.test(e.message);
}
ok(permissionDenied, 'correctly threw a permission denied security error an expando on location');
firstlocation.href = 'http://mochi.test:8888/tests/dom/tests/mochitest/dom-level0/file_location.html';
return;
}
is(lastlocation.iframeprop, 42, 'can still read old values of the location object');
ok(lastlocation !== $('ifr').contentWindow.location, 'location objects are distinct');
ok(firstlocation.href.indexOf('file_location.html'), 'can read location.href');
SimpleTest.finish();
}

View File

@ -85,6 +85,10 @@ JSObject::unwrap(uintN *flagsp)
return wrapped;
}
JSWrapper::JSWrapper(void *family) : JSProxyHandler(&sWrapperFamily), mFlags(0)
{
}
JSWrapper::JSWrapper(uintN flags) : JSProxyHandler(&sWrapperFamily), mFlags(flags)
{
}
@ -278,7 +282,7 @@ JSWrapper::leave(JSContext *cx, JSObject *wrapper)
{
}
JSWrapper JSWrapper::singleton(0);
JSWrapper JSWrapper::singleton((uintN)0);
JSObject *
JSWrapper::New(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent,
@ -360,7 +364,7 @@ AutoCompartment::leave()
/* Cross compartment wrappers. */
JSCrossCompartmentWrapper::JSCrossCompartmentWrapper(void *family) : JSWrapper(0)
JSCrossCompartmentWrapper::JSCrossCompartmentWrapper(void *family) : JSWrapper((uintN)0)
{
}

View File

@ -50,6 +50,10 @@ JS_BEGIN_EXTERN_C
/* No-op wrapper handler base class. */
class JSWrapper : public js::JSProxyHandler {
uintN mFlags;
protected:
// XXX Hack to let wrappers derive from either cross compartment
// wrappers or JSProxyHandlers.
JS_FRIEND_API(JSWrapper(void *family));
public:
uintN flags() const { return mFlags; }

View File

@ -49,6 +49,7 @@
#include "XPCWrapper.h"
#include "nsJSPrincipals.h"
#include "nsWrapperCache.h"
#include "WrapperFactory.h"
//#define STRICT_CHECK_OF_UNICODE
#ifdef STRICT_CHECK_OF_UNICODE
@ -1345,6 +1346,30 @@ XPCConvert::NativeInterface2JSObject(XPCLazyCallContext& lccx,
JSObject *original = flat;
if(!JS_WrapObject(ccx, &flat))
return JS_FALSE;
// If the object was not wrapped, we are same compartment and don't need
// to enforce any cross origin policies, except in case of the location
// object, which always needs a wrapper in between.
if(original == flat)
{
if(xpc::WrapperFactory::IsLocationObject(flat))
{
JSObject *locationWrapper = wrapper->GetWrapper();
if(!locationWrapper)
{
locationWrapper = xpc::WrapperFactory::WrapLocationObject(cx, flat);
if(!locationWrapper)
return JS_FALSE;
// Cache the location wrapper to ensure that we maintain
// the identity of window/document.location.
wrapper->SetWrapper(locationWrapper);
}
flat = locationWrapper;
}
}
*d = OBJECT_TO_JSVAL(flat);
if(dest)

View File

@ -63,6 +63,22 @@ AccessCheck::isSameOrigin(JSCompartment *a, JSCompartment *b)
cond;
}
bool
AccessCheck::isLocationObjectSameOrigin(JSContext *cx, JSObject *obj)
{
JSCompartment *compartment = obj->compartment();
obj = obj->unwrap()->getParent();
if (!obj->getClass()->ext.innerObject) {
obj = obj->unwrap();
JS_ASSERT(obj->getClass()->ext.innerObject);
}
OBJ_TO_INNER_OBJECT(cx, obj);
if (!obj)
return false;
return isSameOrigin(compartment, obj->compartment());
}
bool
AccessCheck::isChrome(JSCompartment *compartment)
{

View File

@ -52,6 +52,7 @@ class AccessCheck {
static bool isCrossOriginAccessPermitted(JSContext *cx, JSObject *obj, jsid id,
JSWrapper::Action act);
static bool isSystemOnlyAccessPermitted(JSContext *cx);
static bool isLocationObjectSameOrigin(JSContext *cx, JSObject *obj);
static bool needsSystemOnlyWrapper(JSObject *obj);
@ -99,6 +100,20 @@ struct CrossOriginAccessiblePropertiesOnly : public Policy {
}
};
// This policy only permits access to properties that are safe to be used
// across origins.
struct SameOriginOrCrossOriginAccessiblePropertiesOnly : public Policy {
static bool check(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act,
Permission &perm) {
perm = DenyAccess;
if (AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act) ||
AccessCheck::isLocationObjectSameOrigin(cx, wrapper)) {
perm = PermitPropertyAccess;
}
return true;
}
};
// This policy only permits access to properties if they appear in the
// objects exposed properties list.
struct ExposedPropertiesOnly : public Policy {

View File

@ -153,15 +153,23 @@ FilteringWrapper<Base, Policy>::enter(JSContext *cx, JSObject *wrapper, jsid id,
#define XOW FilteringWrapper<XrayWrapper<JSCrossCompartmentWrapper, CrossCompartmentXray>, \
CrossOriginAccessiblePropertiesOnly>
#define NNXOW FilteringWrapper<JSCrossCompartmentWrapper, CrossOriginAccessiblePropertiesOnly>
#define LW FilteringWrapper<XrayWrapper<JSWrapper, SameCompartmentXray>, \
SameOriginOrCrossOriginAccessiblePropertiesOnly>
#define XLW FilteringWrapper<XrayWrapper<JSCrossCompartmentWrapper, CrossCompartmentXray>, \
SameOriginOrCrossOriginAccessiblePropertiesOnly>
template<> SOW SOW::singleton(0);
template<> COW COW::singleton(0);
template<> XOW XOW::singleton(0);
template<> NNXOW NNXOW::singleton(0);
template<> LW LW::singleton(0);
template<> XLW XLW::singleton(0);
template class SOW;
template class COW;
template class XOW;
template class NNXOW;
template class LW;
template class XLW;
}

View File

@ -126,8 +126,17 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO
CrossOriginAccessiblePropertiesOnly>::singleton;
} else {
typedef XrayWrapper<JSCrossCompartmentWrapper, CrossCompartmentXray> Xray;
wrapper = &FilteringWrapper<Xray,
CrossOriginAccessiblePropertiesOnly>::singleton;
// Location objects can become same origin after navigation, so we might
// have to grant transparent access later on.
if (IsLocationObject(obj)) {
wrapper = &FilteringWrapper<Xray,
SameOriginOrCrossOriginAccessiblePropertiesOnly>::singleton;
} else {
wrapper= &FilteringWrapper<Xray,
CrossOriginAccessiblePropertiesOnly>::singleton;
}
xrayHolder = Xray::createHolder(cx, obj, parent);
if (!xrayHolder)
return nsnull;
@ -142,4 +151,28 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO
return wrapperObj;
}
typedef FilteringWrapper<XrayWrapper<JSWrapper, SameCompartmentXray>,
SameOriginOrCrossOriginAccessiblePropertiesOnly> LW;
bool
WrapperFactory::IsLocationObject(JSObject *obj)
{
const char *name = obj->getClass()->name;
return name[0] == 'L' && !strcmp(name, "Location");
}
JSObject *
WrapperFactory::WrapLocationObject(JSContext *cx, JSObject *obj)
{
JSObject *xrayHolder = LW::createHolder(cx, obj, obj->getParent());
if (!xrayHolder)
return NULL;
JSObject *wrapperObj = JSWrapper::New(cx, obj, obj->getProto(), NULL, &LW::singleton);
if (!wrapperObj)
return NULL;
wrapperObj->setProxyExtra(js::ObjectValue(*xrayHolder));
xrayHolder->setSlot(XrayUtils::JSSLOT_PROXY_OBJ, js::ObjectValue(*wrapperObj));
return wrapperObj;
}
}

View File

@ -59,6 +59,12 @@ class WrapperFactory {
JSObject *wrappedProto,
JSObject *parent,
uintN flags);
// Return true if this is a location object.
static bool IsLocationObject(JSObject *obj);
// Wrap a location object.
static JSObject *WrapLocationObject(JSContext *cx, JSObject *obj);
};
}

View File

@ -354,11 +354,35 @@ XrayWrapper<Base, Policy>::~XrayWrapper()
{
}
template <typename Base, typename Policy>
class AutoLeaveHelper
{
public:
AutoLeaveHelper(XrayWrapper<Base, Policy> &xray, JSContext *cx, JSObject *wrapper)
: xray(xray), cx(cx), wrapper(wrapper)
{
}
~AutoLeaveHelper()
{
xray.leave(cx, wrapper);
}
private:
XrayWrapper<Base, Policy> &xray;
JSContext *cx;
JSObject *wrapper;
};
template <typename Base, typename Policy>
bool
XrayWrapper<Base, Policy>::getPropertyDescriptor(JSContext *cx, JSObject *wrapper, jsid id,
bool set, PropertyDescriptor *desc_in)
{
if (!this->enter(cx, wrapper, id, set ? JSWrapper::SET : JSWrapper::GET))
return false;
AutoLeaveHelper<Base, Policy> helper(*this, cx, wrapper);
JSPropertyDescriptor *desc = Jsvalify(desc_in);
if (id == nsXPConnect::GetRuntimeInstance()->GetStringID(XPCJSRuntime::IDX_WRAPPED_JSOBJECT)) {
@ -543,7 +567,7 @@ CrossCompartmentXray::leave(JSContext *cx, JSObject *wrapper, void *priv)
}
#define XPCNW XrayWrapper<JSCrossCompartmentWrapper, CrossCompartmentXray>
#define SCNW XrayWrapper<JSProxyHandler, SameCompartmentXray>
#define SCNW XrayWrapper<JSWrapper, SameCompartmentXray>
template <> XPCNW XPCNW::singleton(0);
template <> SCNW SCNW::singleton(0);