Stop evals and Script object calls/execs that cross trust domains (289074, r=shaver, sr=jst, a=drivers).

This commit is contained in:
brendan%mozilla.org 2005-04-07 02:22:24 +00:00
parent fc3153b467
commit b02c276f35
6 changed files with 134 additions and 45 deletions

View File

@ -367,6 +367,12 @@ private:
const char* aClassName, jsval aProperty,
void** aCachedClassPolicy);
void
ThrowAccessDeniedException(JSContext *cx,
PRUint32 aAction,
const char *aClassName,
jsval aProperty);
nsresult
CheckSameOriginPrincipalInternal(nsIPrincipal* aSubject,
nsIPrincipal* aObject,

View File

@ -444,6 +444,8 @@ nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSObject *obj,
// a different trust domain.
// 2. A user-defined getter or setter function accessible on another
// trust domain's window or document object.
// 3. An eval or Script.prototype.exec attempt from trusted content where
// the eval or exec method comes from untrusted content.
// If *vp is not a primitive, some new JS engine call to this hook was
// added, but we can handle that case too -- if a primitive value in a
// property of obj is being accessed, we should use obj as the target
@ -451,6 +453,36 @@ nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSObject *obj,
NS_ASSERTION(!JSVAL_IS_PRIMITIVE(*vp), "unexpected target property value");
JSObject* target = JSVAL_IS_PRIMITIVE(*vp) ? obj : JSVAL_TO_OBJECT(*vp);
NS_ASSERTION(!(mode & JSACC_WRITE), "unexpected write access check");
if (mode & JSACC_EXEC)
{
nsresult rv;
nsCOMPtr<nsIPrincipal> subjectPrincipal;
rv = ssm->GetSubjectPrincipal(cx, getter_AddRefs(subjectPrincipal));
if (NS_FAILED(rv))
return JS_FALSE;
nsCOMPtr<nsIPrincipal> objectPrincipal;
rv = ssm->doGetObjectPrincipal(cx, target,
getter_AddRefs(objectPrincipal));
if (NS_FAILED(rv))
return JS_FALSE;
// If any eval or exec call is crossing a trust boundary, whether
// chrome to content, content to chrome, or between two different
// origins for content, stop it cold here.
if (subjectPrincipal != objectPrincipal)
{
ssm->ThrowAccessDeniedException(cx,
nsIXPCSecurityManager::ACCESS_CALL_METHOD,
JS_GetClass(cx, obj)->name,
id);
return JS_FALSE;
}
return JS_TRUE;
}
// Do the same-origin check -- this sets a JS exception if the check fails.
// Pass the parent object's class name, as we have no class-info for it.
nsresult rv =
@ -765,51 +797,59 @@ nsScriptSecurityManager::CheckPropertyAccessImpl(PRUint32 aAction,
printf("CheckXPCPerms DENIED.\n");
#endif
if (NS_FAILED(rv)) //-- Security tests failed, access is denied, report error
if (NS_FAILED(rv))
{
nsAutoString stringName;
switch(aAction)
{
case nsIXPCSecurityManager::ACCESS_GET_PROPERTY:
stringName.AssignLiteral("GetPropertyDenied");
break;
case nsIXPCSecurityManager::ACCESS_SET_PROPERTY:
stringName.AssignLiteral("SetPropertyDenied");
break;
case nsIXPCSecurityManager::ACCESS_CALL_METHOD:
stringName.AssignLiteral("CallMethodDenied");
}
NS_ConvertUTF8toUTF16 className(classInfoData.GetName());
const PRUnichar *formatStrings[] =
{
className.get(),
JSValIDToString(cx, aProperty)
};
nsXPIDLString errorMsg;
// We need to keep our existing failure rv and not override it
// with a likely success code from the following string bundle
// call in order to throw the correct security exception later.
nsresult rv2 = sStrBundle->FormatStringFromName(stringName.get(),
formatStrings,
NS_ARRAY_LENGTH(formatStrings),
getter_Copies(errorMsg));
NS_ENSURE_SUCCESS(rv2, rv2);
SetPendingException(cx, errorMsg.get());
if (sXPConnect)
{
nsCOMPtr<nsIXPCNativeCallContext> xpcCallContext;
sXPConnect->GetCurrentNativeCallContext(getter_AddRefs(xpcCallContext));
if (xpcCallContext)
xpcCallContext->SetExceptionWasThrown(PR_TRUE);
}
//-- Security tests failed, access is denied, report error
ThrowAccessDeniedException(cx, aAction, classInfoData.GetName(),
aProperty);
}
return rv;
}
void
nsScriptSecurityManager::ThrowAccessDeniedException(JSContext *cx,
PRUint32 aAction,
const char *aClassName,
jsval aProperty)
{
nsAutoString stringName;
switch(aAction)
{
case nsIXPCSecurityManager::ACCESS_GET_PROPERTY:
stringName.AssignLiteral("GetPropertyDenied");
break;
case nsIXPCSecurityManager::ACCESS_SET_PROPERTY:
stringName.AssignLiteral("SetPropertyDenied");
break;
case nsIXPCSecurityManager::ACCESS_CALL_METHOD:
stringName.AssignLiteral("CallMethodDenied");
}
NS_ConvertUTF8toUTF16 className(aClassName);
const PRUnichar *formatStrings[] =
{
className.get(),
JSValIDToString(cx, aProperty)
};
// If FormatStringFromName fails, we'll still throw an empty string
// as an exception, and fail the caller.
nsXPIDLString errorMsg;
sStrBundle->FormatStringFromName(stringName.get(),
formatStrings,
NS_ARRAY_LENGTH(formatStrings),
getter_Copies(errorMsg));
SetPendingException(cx, errorMsg.get());
if (sXPConnect)
{
nsCOMPtr<nsIXPCNativeCallContext> xpcCallContext;
sXPConnect->GetCurrentNativeCallContext(getter_AddRefs(xpcCallContext));
if (xpcCallContext)
xpcCallContext->SetExceptionWasThrown(PR_TRUE);
}
}
nsresult
nsScriptSecurityManager::CheckSameOriginPrincipalInternal(nsIPrincipal* aSubject,
@ -2957,8 +2997,8 @@ nsScriptSecurityManager::InitPolicies()
nsCAutoString sitesPrefName(
NS_LITERAL_CSTRING(sPolicyPrefix) +
nsDependentCString(nameBegin) +
NS_LITERAL_CSTRING(".sites"));
nsDependentCString(nameBegin) +
NS_LITERAL_CSTRING(".sites"));
nsXPIDLCString domainList;
rv = mSecurityPref->SecurityGetCharPref(sitesPrefName.get(),
getter_Copies(domainList));

View File

@ -217,7 +217,12 @@ struct JSRuntime {
/*
* Check property accessibility for objects of arbitrary class. Used at
* present to check f.caller accessibility for any function object f.
* present to check whether:
* 1. f.caller should be accessible for any function object f.
* 2. a getter or setter on another trust domain's global object should
* be accessible to a given script.
* 3. eval or eval-like primitives (Script objects) should be accessible
* to trusted scripts.
*/
JSCheckAccessOp checkObjectAccess;

View File

@ -1001,6 +1001,7 @@ obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
JSStackFrame *fp, *caller;
JSBool indirectCall;
JSRuntime *rt;
JSObject *scopeobj;
JSString *str;
const char *file;
@ -1027,6 +1028,20 @@ obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
return JS_FALSE;
}
/*
* Check whether the caller (native or scripted) should trust this
* eval function object.
*/
rt = cx->runtime;
if (rt->checkObjectAccess) {
*rval = argv[-2];
if (!rt->checkObjectAccess(cx, obj,
ATOM_KEY(rt->atomState.evalAtom),
JSACC_EXEC, rval)) {
return JS_FALSE;
}
}
if (!JSVAL_IS_STRING(argv[0])) {
*rval = argv[0];
return JS_TRUE;

View File

@ -89,7 +89,10 @@ typedef enum JSType {
JSTYPE_LIMIT
} JSType;
/* JSObjectOps.checkAccess mode enumeration. */
/*
* JSObjectOps.checkAccess mode enumeration.
* XXXbe these began as consecutive values but turned into flags -- revisit
*/
typedef enum JSAccessMode {
JSACC_PROTO = 0, /* XXXbe redundant w.r.t. id */
JSACC_PARENT = 1, /* XXXbe redundant w.r.t. id */
@ -97,10 +100,11 @@ typedef enum JSAccessMode {
JSACC_WATCH = 3, /* a watchpoint on object foo for id 'bar' */
JSACC_READ = 4, /* a "get" of foo.bar */
JSACC_WRITE = 8, /* a "set" of foo.bar = baz */
JSACC_EXEC = 16, /* eval or Script.prototype.exec attempt */
JSACC_LIMIT
} JSAccessMode;
#define JSACC_TYPEMASK (JSACC_WRITE - 1)
#define JSACC_TYPEMASK (JSACC_EXEC - 1)
/*
* This enum type is used to control the behavior of a JSObject property

View File

@ -236,6 +236,7 @@ static JSBool
script_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
JSScript *script;
JSRuntime *rt;
JSObject *scopeobj, *parent;
JSStackFrame *fp, *caller;
@ -245,6 +246,24 @@ script_exec(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
if (!script)
return JS_TRUE;
/*
* Check whether the caller (native or scripted) should trust this
* Script.prototype.exec function object.
*/
rt = cx->runtime;
if (rt->checkObjectAccess) {
JSString *exec_str;
jsval exec_idval;
exec_str = JS_InternString(cx, "exec");
if (!exec_str)
return JS_FALSE;
exec_idval = STRING_TO_JSVAL(exec_str);
*rval = argv[-2];
if (!rt->checkObjectAccess(cx, obj, exec_idval, JSACC_EXEC, rval))
return JS_FALSE;
}
scopeobj = NULL;
if (argc) {
if (!js_ValueToObject(cx, argv[0], &scopeobj))