mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 518991 - Implement access controls in COWs. For now, this is opt-in default unsafe. r=jst
This commit is contained in:
parent
c68af46729
commit
1b993d3f38
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
/***************************************************************************/
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user