bug 515443 CSP no-eval support. r=mrbkap,brendan
authorSid Stamm <sstamm@mozilla.com>
Mon, 08 Mar 2010 00:24:50 -0800
changeset 39061 9b3422abc2f8532971e68ac1c8c7aa1e59a26c56
parent 39060 8997036f34970cafe811eb8c4837affa909bb9dc
child 39062 0780abb1f72350abeafba94dce43299008095342
push idunknown
push userunknown
push dateunknown
reviewersmrbkap, brendan
bugs515443
milestone1.9.3a3pre
bug 515443 CSP no-eval support. r=mrbkap,brendan
caps/include/nsScriptSecurityManager.h
caps/src/nsScriptSecurityManager.cpp
content/base/test/Makefile.in
dom/base/nsJSTimeoutHandler.cpp
js/src/js.msg
js/src/jsapi.h
js/src/jsfun.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jspubtd.h
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -426,16 +426,20 @@ private:
     nsScriptSecurityManager();
     virtual ~nsScriptSecurityManager();
 
     static JSBool
     CheckObjectAccess(JSContext *cx, JSObject *obj,
                       jsval id, JSAccessMode mode,
                       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
     // should error out at that point.
     static nsIPrincipal*
     doGetObjectPrincipal(JSObject *obj
 #ifdef DEBUG
                          , PRBool aAllowShortCircuit = PR_TRUE
 #endif
                          );
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -88,16 +88,17 @@
 #include "nsAutoPtr.h"
 #include "nsDOMJSUtils.h"
 #include "nsAboutProtocolUtils.h"
 #include "nsIClassInfo.h"
 #include "nsIURIFixup.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIChromeRegistry.h"
 #include "nsPrintfCString.h"
+#include "nsIContentSecurityPolicy.h"
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 nsIIOService    *nsScriptSecurityManager::sIOService = nsnull;
 nsIXPConnect    *nsScriptSecurityManager::sXPConnect = nsnull;
 nsIThreadJSContextStack *nsScriptSecurityManager::sJSContextStack = nsnull;
 nsIStringBundle *nsScriptSecurityManager::sStrBundle = nsnull;
 JSRuntime       *nsScriptSecurityManager::sRuntime   = 0;
@@ -509,16 +510,60 @@ NS_IMPL_ISUPPORTS5(nsScriptSecurityManag
                    nsIObserver)
 
 ///////////////////////////////////////////////////
 // Methods implementing nsIScriptSecurityManager //
 ///////////////////////////////////////////////////
 
 ///////////////// 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
 nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSObject *obj,
                                            jsval id, JSAccessMode mode,
                                            jsval *vp)
 {
     // Get the security manager
     nsScriptSecurityManager *ssm =
         nsScriptSecurityManager::GetScriptSecurityManager();
 
@@ -3356,19 +3401,20 @@ nsresult nsScriptSecurityManager::Init()
     nsCOMPtr<nsIJSRuntimeService> runtimeService =
         do_GetService("@mozilla.org/js/xpc/RuntimeService;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = runtimeService->GetRuntime(&sRuntime);
     NS_ENSURE_SUCCESS(rv, rv);
 
     static JSSecurityCallbacks securityCallbacks = {
-      CheckObjectAccess,
-      NULL,
-      NULL
+        CheckObjectAccess,
+        NULL,
+        NULL,
+        ContentSecurityPolicyPermitsJSAction
     };
 
 #ifdef DEBUG
     JSSecurityCallbacks *oldcallbacks =
 #endif
     JS_SetRuntimeSecurityCallbacks(sRuntime, &securityCallbacks);
     NS_ASSERTION(!oldcallbacks, "Someone else set security callbacks!");
 
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -346,16 +346,20 @@ include $(topsrcdir)/config/rules.mk
 		file_CSP_main.js \
  		test_CSP_frameancestors.html \
  		file_CSP_frameancestors.sjs \
  		file_CSP_frameancestors_main.html \
  		file_CSP_frameancestors_main.js \
  		test_CSP_inlinescript.html \
  		file_CSP_inlinescript_main.html \
  		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 \
 		bug540854.sjs \
 		$(NULL)
 
 # Disabled; see bug 492181
 #		test_plugin_freezing.html
 
 # Disabled for now. Mochitest isn't reliable enough for these.
--- a/dom/base/nsJSTimeoutHandler.cpp
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -45,16 +45,17 @@
 #include "nsIJSRuntimeService.h"
 #include "nsJSUtils.h"
 #include "nsDOMJSUtils.h"
 #include "nsContentUtils.h"
 #include "nsJSEnvironment.h"
 #include "nsServiceManagerUtils.h"
 #include "nsDOMError.h"
 #include "nsGlobalWindow.h"
+#include "nsIContentSecurityPolicy.h"
 
 static const char kSetIntervalStr[] = "setInterval";
 static const char kSetTimeoutStr[] = "setTimeout";
 
 // Our JS nsIScriptTimeoutHandler implementation.
 class nsJSScriptTimeoutHandler: public nsIScriptTimeoutHandler
 {
 public:
@@ -198,17 +199,17 @@ nsJSScriptTimeoutHandler::Init(nsGlobalW
 
   JSString *expr = nsnull;
   JSObject *funobj = nsnull;
   int32 interval = 0;
 
   JSAutoRequest ar(cx);
 
   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);
     return NS_ERROR_DOM_TYPE_ERR;
   }
 
   if (argc > 1 && !::JS_ValueToECMAInt32(cx, argv[1], &interval)) {
     ::JS_ReportError(cx,
                      "Second argument to %s must be a millisecond interval",
                      aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
@@ -238,16 +239,42 @@ nsJSScriptTimeoutHandler::Init(nsGlobalW
     ::JS_ReportError(cx, "useless %s call (missing quotes around argument?)",
                      *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
 
     // Return an error that nsGlobalWindow can recognize and turn into NS_OK.
     return NS_ERROR_DOM_TYPE_ERR;
   }
 
   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);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mExpr = expr;
 
     nsIPrincipal *prin = aWindow->GetPrincipal();
 
     // Get the calling location.
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -321,8 +321,9 @@ MSG_DEF(JSMSG_INVALID_DESCRIPTOR,     23
 MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE,  239, 0, JSEXN_TYPEERR, "object is not extensible")
 MSG_DEF(JSMSG_CANT_REDEFINE_UNCONFIGURABLE_PROP, 240, 1, JSEXN_TYPEERR, "can't redefine non-configurable property '{0}'")
 MSG_DEF(JSMSG_CANT_APPEND_PROPERTIES_TO_UNWRITABLE_LENGTH_ARRAY, 241, 0, JSEXN_TYPEERR, "Can't add elements past the end of an array if its length property is unwritable")
 MSG_DEF(JSMSG_DEFINE_ARRAY_LENGTH_UNSUPPORTED, 242, 0, JSEXN_INTERNALERR, "defining the length property on an array is not currently supported")
 MSG_DEF(JSMSG_CANT_DEFINE_ARRAY_INDEX,243, 0, JSEXN_TYPEERR, "can't define array index property")
 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_BAD_ARGS,   246, 0, JSEXN_ERR, "invalid arguments")
+MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION,   247, 0, JSEXN_ERR, "call to Function() blocked by CSP")
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2010,19 +2010,20 @@ JS_DropPrincipals(JSContext *cx, JSPrinc
 #define JSPRINCIPALS_DROP(cx, principals)                                     \
     ((--(principals)->refcount == 0)                                          \
      ? ((*(principals)->destroy)((cx), (principals)), 0)                      \
      : (principals)->refcount)
 #endif
 
 
 struct JSSecurityCallbacks {
-  JSCheckAccessOp            checkObjectAccess;
-  JSPrincipalsTranscoder     principalsTranscoder;
-  JSObjectPrincipalsFinder   findObjectPrincipals;
+    JSCheckAccessOp            checkObjectAccess;
+    JSPrincipalsTranscoder     principalsTranscoder;
+    JSObjectPrincipalsFinder   findObjectPrincipals;
+    JSCSPEvalChecker           contentSecurityPolicyAllows;
 };
 
 extern JS_PUBLIC_API(JSSecurityCallbacks *)
 JS_SetRuntimeSecurityCallbacks(JSRuntime *rt, JSSecurityCallbacks *callbacks);
 
 extern JS_PUBLIC_API(JSSecurityCallbacks *)
 JS_GetRuntimeSecurityCallbacks(JSRuntime *rt);
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -2213,16 +2213,27 @@ Function(JSContext *cx, JSObject *obj, u
     }
 
     /* Belt-and-braces: check that the caller has access to parent. */
     if (!js_CheckPrincipalsAccess(cx, parent, principals,
                                   CLASS_ATOM(cx, Function))) {
         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;
     if (n > 0) {
         enum { OK, BAD, BAD_FORMAL } state;
 
         /*
          * Collect the function-argument arguments into one string, separated
          * by commas, then make a tokenstream from that string, and scan it to
          * get the arguments.  We need to throw the full scanner at the
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1114,16 +1114,36 @@ obj_valueOf(JSContext *cx, uintN argc, j
 static jsval FASTCALL
 Object_p_valueOf(JSContext* cx, JSObject* obj, JSString *hint)
 {
     return OBJECT_TO_JSVAL(obj);
 }
 #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
  * if so (or if scopeobj has no principals, for backward compatibility with
  * the JS API, which does not require principals), and false otherwise.
  */
 JSBool
 js_CheckPrincipalsAccess(JSContext *cx, JSObject *scopeobj,
                          JSPrincipals *principals, JSAtom *caller)
 {
@@ -1389,16 +1409,23 @@ obj_eval(JSContext *cx, uintN argc, jsva
     }
 
     /* Ensure we compile this eval with the right object in the scope chain. */
     JSObject *result = js_CheckScopeChainValidity(cx, scopeobj, js_eval_str);
     JS_ASSERT_IF(result, result == scopeobj);
     if (!result)
         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]);
     JSPrincipals *principals = js_EvalFramePrincipals(cx, callee, caller);
     uintN line;
     const char *file = js_ComputeFilename(cx, caller, principals, &line);
 
     JSString *str = JSVAL_TO_STRING(argv[0]);
     JSScript *script = NULL;
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1095,16 +1095,20 @@ js_ReallocSlots(JSContext *cx, JSObject 
 
 extern JSObject *
 js_CheckScopeChainValidity(JSContext *cx, JSObject *scopeobj, const char *caller);
 
 extern JSBool
 js_CheckPrincipalsAccess(JSContext *cx, JSObject *scopeobj,
                          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. */
 extern JSObject *
 js_GetWrappedObject(JSContext *cx, JSObject *obj);
 
 /* NB: Infallible. */
 extern const char *
 js_ComputeFilename(JSContext *cx, JSStackFrame *caller,
                    JSPrincipals *principals, uintN *linenop);
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -579,11 +579,18 @@ typedef JSBool
  * a window object in the DOM level 0).  If there are no principals associated
  * with obj, return null.  Therefore null does not mean an error was reported;
  * in no event should an error be reported or an exception be thrown by this
  * callback's implementation.
  */
 typedef JSPrincipals *
 (* 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
 
 #endif /* jspubtd_h___ */