bug 515443 CSP no-eval support. r=mrbkap,brendan

This commit is contained in:
Sid Stamm 2010-03-08 00:24:50 -08:00
parent 1335f2114e
commit 47a3291b46
10 changed files with 139 additions and 7 deletions

View File

@ -431,6 +431,10 @@ private:
jsval id, JSAccessMode mode, jsval id, JSAccessMode mode,
jsval *vp); jsval *vp);
// Decides, based on CSP, whether or not eval() and stuff can be executed.
static JSBool
ContentSecurityPolicyPermitsJSAction(JSContext *cx);
// Returns null if a principal cannot be found; generally callers // Returns null if a principal cannot be found; generally callers
// should error out at that point. // should error out at that point.
static nsIPrincipal* static nsIPrincipal*

View File

@ -93,6 +93,7 @@
#include "nsCDefaultURIFixup.h" #include "nsCDefaultURIFixup.h"
#include "nsIChromeRegistry.h" #include "nsIChromeRegistry.h"
#include "nsPrintfCString.h" #include "nsPrintfCString.h"
#include "nsIContentSecurityPolicy.h"
static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
@ -513,6 +514,50 @@ NS_IMPL_ISUPPORTS5(nsScriptSecurityManager,
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
///////////////// Security Checks ///////////////// ///////////////// Security Checks /////////////////
JSBool
nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx)
{
// Get the security manager
nsScriptSecurityManager *ssm =
nsScriptSecurityManager::GetScriptSecurityManager();
NS_ASSERTION(ssm, "Failed to get security manager service");
if (!ssm)
return JS_FALSE;
nsresult rv;
nsIPrincipal* subjectPrincipal = ssm->GetSubjectPrincipal(cx, &rv);
NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get nsIPrincipal from js context");
if (NS_FAILED(rv))
return JS_FALSE; // Not just absence of principal, but failure.
if (!subjectPrincipal)
return JS_FALSE;
nsCOMPtr<nsIContentSecurityPolicy> csp;
rv = subjectPrincipal->GetCsp(getter_AddRefs(csp));
NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get CSP from principal.");
// don't do anything unless there's a CSP
if (!csp)
return JS_TRUE;
PRBool evalOK = PR_TRUE;
// this call will send violation reports as warranted (and return true if
// reportOnly is set).
rv = csp->GetAllowsEval(&evalOK);
if (NS_FAILED(rv))
{
NS_WARNING("CSP: failed to get allowsEval");
return JS_TRUE; // fail open to not break sites.
}
return evalOK;
}
JSBool JSBool
nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSObject *obj, nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSObject *obj,
jsval id, JSAccessMode mode, jsval id, JSAccessMode mode,
@ -3361,9 +3406,10 @@ nsresult nsScriptSecurityManager::Init()
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
static JSSecurityCallbacks securityCallbacks = { static JSSecurityCallbacks securityCallbacks = {
CheckObjectAccess, CheckObjectAccess,
NULL, NULL,
NULL NULL,
ContentSecurityPolicyPermitsJSAction
}; };
#ifdef DEBUG #ifdef DEBUG

View File

@ -351,6 +351,10 @@ _TEST_FILES = test_bug5141.html \
test_CSP_inlinescript.html \ test_CSP_inlinescript.html \
file_CSP_inlinescript_main.html \ file_CSP_inlinescript_main.html \
file_CSP_inlinescript_main.html^headers^ \ file_CSP_inlinescript_main.html^headers^ \
test_CSP_evalscript.html \
file_CSP_evalscript.sjs \
file_CSP_evalscript_main.html \
file_CSP_evalscript_main.js \
test_bug540854.html \ test_bug540854.html \
bug540854.sjs \ bug540854.sjs \
$(NULL) $(NULL)

View File

@ -50,6 +50,7 @@
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
#include "nsDOMError.h" #include "nsDOMError.h"
#include "nsGlobalWindow.h" #include "nsGlobalWindow.h"
#include "nsIContentSecurityPolicy.h"
static const char kSetIntervalStr[] = "setInterval"; static const char kSetIntervalStr[] = "setInterval";
static const char kSetTimeoutStr[] = "setTimeout"; static const char kSetTimeoutStr[] = "setTimeout";
@ -203,7 +204,7 @@ nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, PRBool *aIsInterval,
JSAutoRequest ar(cx); JSAutoRequest ar(cx);
if (argc < 1) { if (argc < 1) {
::JS_ReportError(cx, "Function %s requires at least 1 parameter", ::JS_ReportError(cx, "Function %s requires at least 2 parameter",
*aIsInterval ? kSetIntervalStr : kSetTimeoutStr); *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
return NS_ERROR_DOM_TYPE_ERR; return NS_ERROR_DOM_TYPE_ERR;
} }
@ -243,6 +244,32 @@ nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, PRBool *aIsInterval,
} }
if (expr) { if (expr) {
// if CSP is enabled, and setTimeout/setInterval was called with a string
// or object, disable the registration and log an error
nsCOMPtr<nsIDocument> doc = do_QueryInterface(aWindow->GetExtantDocument());
if (doc) {
nsCOMPtr<nsIContentSecurityPolicy> csp;
nsresult rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp));
NS_ENSURE_SUCCESS(rv, rv);
if (csp) {
PRBool allowsEval;
// this call will send violation reports as warranted (and return true if
// reportOnly is set).
rv = csp->GetAllowsEval(&allowsEval);
NS_ENSURE_SUCCESS(rv, rv);
if (!allowsEval) {
::JS_ReportError(cx, "call to %s blocked by CSP",
*aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
// Note: Our only caller knows to turn NS_ERROR_DOM_TYPE_ERR into NS_OK.
return NS_ERROR_DOM_TYPE_ERR;
}
}
} // if there's no document, we don't have to do anything.
rv = NS_HOLD_JS_OBJECTS(this, nsJSScriptTimeoutHandler); rv = NS_HOLD_JS_OBJECTS(this, nsJSScriptTimeoutHandler);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);

View File

@ -326,3 +326,4 @@ MSG_DEF(JSMSG_CANT_DEFINE_ARRAY_INDEX,243, 0, JSEXN_TYPEERR, "can't define array
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_INDEX, 244, 0, JSEXN_ERR, "invalid or out-of-range index") MSG_DEF(JSMSG_TYPED_ARRAY_BAD_INDEX, 244, 0, JSEXN_ERR, "invalid or out-of-range index")
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG, 245, 1, JSEXN_ERR, "argument {0} must be >= 0") MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG, 245, 1, JSEXN_ERR, "argument {0} must be >= 0")
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 246, 0, JSEXN_ERR, "invalid arguments") MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 246, 0, JSEXN_ERR, "invalid arguments")
MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION, 247, 0, JSEXN_ERR, "call to Function() blocked by CSP")

View File

@ -2015,9 +2015,10 @@ JS_DropPrincipals(JSContext *cx, JSPrincipals *principals);
struct JSSecurityCallbacks { struct JSSecurityCallbacks {
JSCheckAccessOp checkObjectAccess; JSCheckAccessOp checkObjectAccess;
JSPrincipalsTranscoder principalsTranscoder; JSPrincipalsTranscoder principalsTranscoder;
JSObjectPrincipalsFinder findObjectPrincipals; JSObjectPrincipalsFinder findObjectPrincipals;
JSCSPEvalChecker contentSecurityPolicyAllows;
}; };
extern JS_PUBLIC_API(JSSecurityCallbacks *) extern JS_PUBLIC_API(JSSecurityCallbacks *)

View File

@ -2218,6 +2218,17 @@ Function(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
return JS_FALSE; return JS_FALSE;
} }
/*
* CSP check: whether new Function() is allowed at all.
* Report errors via CSP is done in the script security manager.
* js_CheckContentSecurityPolicy is defined in jsobj.cpp
*/
if (!js_CheckContentSecurityPolicy(cx)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_CSP_BLOCKED_FUNCTION);
return JS_FALSE;
}
n = argc ? argc - 1 : 0; n = argc ? argc - 1 : 0;
if (n > 0) { if (n > 0) {
enum { OK, BAD, BAD_FORMAL } state; enum { OK, BAD, BAD_FORMAL } state;

View File

@ -1118,6 +1118,26 @@ Object_p_valueOf(JSContext* cx, JSObject* obj, JSString *hint)
} }
#endif #endif
/*
* Check if CSP allows new Function() or eval() to run in the current
* principals.
*/
JSBool
js_CheckContentSecurityPolicy(JSContext *cx)
{
JSSecurityCallbacks *callbacks;
callbacks = JS_GetSecurityCallbacks(cx);
// if there are callbacks, make sure that the CSP callback is installed and
// that it permits eval().
if (callbacks) {
return callbacks->contentSecurityPolicyAllows &&
callbacks->contentSecurityPolicyAllows(cx);
}
return JS_TRUE;
}
/* /*
* Check whether principals subsumes scopeobj's principals, and return true * Check whether principals subsumes scopeobj's principals, and return true
* if so (or if scopeobj has no principals, for backward compatibility with * if so (or if scopeobj has no principals, for backward compatibility with
@ -1394,6 +1414,13 @@ obj_eval(JSContext *cx, uintN argc, jsval *vp)
if (!result) if (!result)
return JS_FALSE; return JS_FALSE;
// CSP check: is eval() allowed at all?
// report errors via CSP is done in the script security mgr.
if (!js_CheckContentSecurityPolicy(cx)) {
JS_ReportError(cx, "call to eval() blocked by CSP");
return JS_FALSE;
}
JSObject *callee = JSVAL_TO_OBJECT(vp[0]); JSObject *callee = JSVAL_TO_OBJECT(vp[0]);
JSPrincipals *principals = js_EvalFramePrincipals(cx, callee, caller); JSPrincipals *principals = js_EvalFramePrincipals(cx, callee, caller);
uintN line; uintN line;

View File

@ -1100,6 +1100,10 @@ extern JSBool
js_CheckPrincipalsAccess(JSContext *cx, JSObject *scopeobj, js_CheckPrincipalsAccess(JSContext *cx, JSObject *scopeobj,
JSPrincipals *principals, JSAtom *caller); JSPrincipals *principals, JSAtom *caller);
/* For CSP -- checks if eval() and friends are allowed to run. */
extern JSBool
js_CheckContentSecurityPolicy(JSContext *cx);
/* Infallible -- returns its argument if there is no wrapped object. */ /* Infallible -- returns its argument if there is no wrapped object. */
extern JSObject * extern JSObject *
js_GetWrappedObject(JSContext *cx, JSObject *obj); js_GetWrappedObject(JSContext *cx, JSObject *obj);

View File

@ -584,6 +584,13 @@ typedef JSBool
typedef JSPrincipals * typedef JSPrincipals *
(* JSObjectPrincipalsFinder)(JSContext *cx, JSObject *obj); (* JSObjectPrincipalsFinder)(JSContext *cx, JSObject *obj);
/*
* Used to check if a CSP instance wants to disable eval() and friends.
* See js_CheckCSPPermitsJSAction() in jsobj.
*/
typedef JSBool
(* JSCSPEvalChecker)(JSContext *cx);
JS_END_EXTERN_C JS_END_EXTERN_C
#endif /* jspubtd_h___ */ #endif /* jspubtd_h___ */