Bug 518991 - Implement access controls in COWs. For now, this is opt-in default unsafe. r=jst

This commit is contained in:
Blake Kaplan 2009-09-30 19:38:57 -07:00
parent c68af46729
commit 1b993d3f38
4 changed files with 216 additions and 14 deletions

View File

@ -48,6 +48,167 @@
// This file implements a wrapper around trusted objects that allows them to
// be safely injected into untrusted code.
namespace {
const PRUint32 sPropIsReadable = 0x1;
const PRUint32 sPropIsWritable = 0x2;
const PRUint32 sExposedPropsSlot = XPCWrapper::sNumSlots;
class AutoIdArray {
public:
AutoIdArray(JSContext *cx, JSIdArray *ida) : cx(cx), ida(ida) {
}
~AutoIdArray() {
if (ida) {
JS_DestroyIdArray(cx, ida);
}
}
JSIdArray *array() {
return ida;
}
private:
JSContext *cx;
JSIdArray *ida;
};
JSBool
GetExposedProperties(JSContext *cx, JSObject *obj, jsval *rval)
{
jsid exposedPropsId = GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS);
JSBool found = JS_FALSE;
if (!JS_HasPropertyById(cx, obj, exposedPropsId, &found))
return JS_FALSE;
if (!found) {
*rval = JSVAL_VOID;
return JS_TRUE;
}
*rval = JSVAL_NULL;
jsval exposedProps;
if (!JS_LookupPropertyById(cx, obj, exposedPropsId, &exposedProps))
return JS_FALSE;
if (JSVAL_IS_VOID(exposedProps) || JSVAL_IS_NULL(exposedProps))
return JS_TRUE;
if (!JSVAL_IS_OBJECT(exposedProps)) {
JS_ReportError(cx,
"__exposedProps__ must be undefined, null, or an Object");
return JS_FALSE;
}
obj = JSVAL_TO_OBJECT(exposedProps);
AutoIdArray guard(cx, JS_Enumerate(cx, obj));
JSIdArray *props = guard.array();
if (!props)
return JS_FALSE;
if (props->length == 0)
return JS_TRUE;
JSObject *info = JS_NewObjectWithGivenProto(cx, NULL, NULL, obj);
if (!info)
return JS_FALSE;
*rval = OBJECT_TO_JSVAL(info);
for (int i = 0; i < props->length; i++) {
jsid propId = props->vector[i];
jsval propVal;
if (!JS_LookupPropertyById(cx, obj, propId, &propVal))
return JS_FALSE;
if (!JSVAL_IS_STRING(propVal)) {
JS_ReportError(cx, "property must be a string");
return JS_FALSE;
}
JSString *str = JSVAL_TO_STRING(propVal);
const jschar *chars = JS_GetStringChars(str);
size_t length = JS_GetStringLength(str);
int32 propPerms = 0;
for (size_t i = 0; i < length; ++i) {
switch (chars[i]) {
case 'r':
if (propPerms & sPropIsReadable) {
JS_ReportError(cx, "duplicate 'readable' property flag");
return JS_FALSE;
}
propPerms |= sPropIsReadable;
break;
case 'w':
if (propPerms & sPropIsWritable) {
JS_ReportError(cx, "duplicate 'writable' property flag");
return JS_FALSE;
}
propPerms |= sPropIsWritable;
break;
default:
JS_ReportError(cx, "properties can only be readable or read and writable");
return JS_FALSE;
}
}
if (propPerms == 0) {
JS_ReportError(cx, "specified properties must have a permission bit set");
return JS_FALSE;
}
if (!JS_DefinePropertyById(cx, info, propId, INT_TO_JSVAL(propPerms),
NULL, NULL,
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) {
return JS_FALSE;
}
}
return JS_TRUE;
}
JSBool
CanTouchProperty(JSContext *cx, JSObject *wrapperObj, jsid id, JSBool isSet,
JSBool *allowedp)
{
jsval exposedProps;
if (!JS_GetReservedSlot(cx, wrapperObj, sExposedPropsSlot, &exposedProps)) {
return JS_FALSE;
}
if (JSVAL_IS_PRIMITIVE(exposedProps)) {
// TODO For now, if the object doesn't ask for security, provide full
// access. In the future, we want to default to false here.
// NB: We differentiate between void (no __exposedProps__ property at all)
// and null (__exposedProps__ exists but didn't specify any properties)
// here.
*allowedp = JSVAL_IS_VOID(exposedProps);
return JS_TRUE;
}
JSObject *hash = JSVAL_TO_OBJECT(exposedProps);
jsval allowedval;
if (!JS_LookupPropertyById(cx, hash, id, &allowedval)) {
return JS_FALSE;
}
const PRUint32 wanted = isSet ? sPropIsWritable : sPropIsReadable;
// We test JSVAL_IS_INT to protect against unknown ids.
*allowedp = JSVAL_IS_INT(allowedval) &&
(JSVAL_TO_INT(allowedval) & wanted) != 0;
return JS_TRUE;
}
}
static JSBool
XPC_COW_AddProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
@ -87,7 +248,7 @@ JSExtendedClass sXPC_COW_JSClass = {
// JSClass (JSExtendedClass.base) initialization
{ "ChromeObjectWrapper",
JSCLASS_NEW_RESOLVE | JSCLASS_IS_EXTENDED |
JSCLASS_HAS_RESERVED_SLOTS(XPCWrapper::sNumSlots),
JSCLASS_HAS_RESERVED_SLOTS(XPCWrapper::sNumSlots + 1),
XPC_COW_AddProperty, XPC_COW_DelProperty,
XPC_COW_GetProperty, XPC_COW_SetProperty,
XPC_COW_Enumerate, (JSResolveOp)XPC_COW_NewResolve,
@ -189,10 +350,9 @@ XPC_COW_FunctionWrapper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
wrappedObj = obj;
}
JSObject *funObj = JSVAL_TO_OBJECT(argv[-2]);
jsval funToCall;
if (!JS_GetReservedSlot(cx, funObj, XPCWrapper::eWrappedFunctionSlot,
&funToCall)) {
if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(argv[-2]),
XPCWrapper::eWrappedFunctionSlot, &funToCall)) {
return JS_FALSE;
}
@ -370,18 +530,28 @@ XPC_COW_GetOrSetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp,
return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
}
if (id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_PROTO) ||
id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_PARENT)) {
jsid interned_id;
if (!JS_ValueToId(cx, id, &interned_id)) {
return JS_FALSE;
}
if (interned_id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_PROTO) ||
interned_id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_PARENT) ||
interned_id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_EXPOSEDPROPS)) {
// No getting or setting __proto__ or __parent__ on my object.
return ThrowException(NS_ERROR_INVALID_ARG, cx); // XXX better error message
}
if (!XPC_COW_RewrapForChrome(cx, obj, vp)) {
JSBool canTouch;
if (!CanTouchProperty(cx, obj, interned_id, isSet, &canTouch)) {
return JS_FALSE;
}
jsid interned_id;
if (!JS_ValueToId(cx, id, &interned_id)) {
if (!canTouch) {
return ThrowException(NS_ERROR_XPC_SECURITY_MANAGER_VETO, cx);
}
if (!XPC_COW_RewrapForChrome(cx, obj, vp)) {
return JS_FALSE;
}
@ -426,7 +596,7 @@ XPC_COW_Enumerate(JSContext *cx, JSObject *obj)
}
static JSBool
XPC_COW_NewResolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
XPC_COW_NewResolve(JSContext *cx, JSObject *obj, jsval idval, uintN flags,
JSObject **objp)
{
obj = GetWrapper(obj);
@ -443,6 +613,18 @@ XPC_COW_NewResolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
return ThrowException(NS_ERROR_FAILURE, cx);
}
jsid id;
JSBool canTouch;
if (!JS_ValueToId(cx, idval, &id) ||
!CanTouchProperty(cx, obj, id, (flags & JSRESOLVE_ASSIGNING) != 0,
&canTouch)) {
return JS_FALSE;
}
if (!canTouch) {
return ThrowException(NS_ERROR_XPC_SECURITY_MANAGER_VETO, cx);
}
return XPCWrapper::NewResolve(cx, obj, JS_TRUE, wrappedObj, id, flags, objp);
}
@ -568,9 +750,18 @@ XPC_COW_WrapObject(JSContext *cx, JSObject *parent, jsval v, jsval *vp)
}
*vp = OBJECT_TO_JSVAL(wrapperObj);
jsval exposedProps = JSVAL_VOID;
JSAutoTempValueRooter tvr(cx, 1, &exposedProps);
if (!GetExposedProperties(cx, JSVAL_TO_OBJECT(v), &exposedProps)) {
return JS_FALSE;
}
if (!JS_SetReservedSlot(cx, wrapperObj, XPCWrapper::sWrappedObjSlot, v) ||
!JS_SetReservedSlot(cx, wrapperObj, XPCWrapper::sFlagsSlot,
JSVAL_ZERO)) {
JSVAL_ZERO) ||
!JS_SetReservedSlot(cx, wrapperObj, sExposedPropsSlot, exposedProps)) {
return JS_FALSE;
}

View File

@ -757,11 +757,17 @@ xpc_SameScope(XPCWrappedNativeScope *objectscope, XPCWrappedNativeScope *xpcscop
return JS_FALSE;
}
inline jsid
GetRTIdByIndex(JSContext *cx, uintN index)
{
XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
return rt->GetStringID(index);
}
inline jsval
GetRTStringByIndex(JSContext *cx, uintN index)
{
XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
return ID_TO_VALUE(rt->GetStringID(index));
return ID_TO_VALUE(GetRTIdByIndex(cx, index));
}
inline

View File

@ -62,7 +62,8 @@ const char* XPCJSRuntime::mStrings[] = {
"item", // IDX_ITEM
"__proto__", // IDX_PROTO
"__iterator__", // IDX_ITERATOR
"__parent__" // IDX_PARENT
"__parent__", // IDX_PARENT
"__exposedProps__" // IDX_EXPOSEDPROPS
};
/***************************************************************************/

View File

@ -704,6 +704,7 @@ public:
IDX_PROTO ,
IDX_ITERATOR ,
IDX_PARENT ,
IDX_EXPOSEDPROPS ,
IDX_TOTAL_COUNT // just a count of the above
};
@ -4298,6 +4299,9 @@ xpc_EvalInSandbox(JSContext *cx, JSObject *sandbox, const nsAString& source,
inline JSBool
xpc_ForcePropertyResolve(JSContext* cx, JSObject* obj, jsval idval);
inline jsid
GetRTIdByIndex(JSContext *cx, uintN index);
inline jsval
GetRTStringByIndex(JSContext *cx, uintN index);