Bug 994320 - Implement permitsAncestry CSP in C++. r=ckerschb,grobinson
☠☠ backed out by a7df548210d7 ☠ ☠
authorSid Stamm <sstamm@mozilla.com>
Tue, 20 May 2014 10:43:17 -0700
changeset 183996 f4964171a9c08b310baffc670cb0355cb3cbcfb8
parent 183995 a4655f5da435f4f7188517763e4be4040795f3d2
child 183997 e4e5b0502f930e2e9e16614a6580dc8272348ec9
push id26810
push usercbook@mozilla.com
push dateWed, 21 May 2014 11:46:36 +0000
treeherdermozilla-central@50fb8c4db2fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, grobinson
bugs994320
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 994320 - Implement permitsAncestry CSP in C++. r=ckerschb,grobinson
content/base/src/nsCSPContext.cpp
content/base/src/nsCSPUtils.cpp
content/base/src/nsCSPUtils.h
content/base/test/TestCSPParser.cpp
--- a/content/base/src/nsCSPContext.cpp
+++ b/content/base/src/nsCSPContext.cpp
@@ -21,16 +21,17 @@
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsIPrincipal.h"
 #include "nsIPropertyBag2.h"
 #include "nsIScriptError.h"
+#include "nsIWebNavigation.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsString.h"
 #include "prlog.h"
 
 using namespace mozilla;
 
 #if defined(PR_LOGGING)
 static PRLogModuleInfo *
@@ -434,21 +435,128 @@ nsCSPContext::SetRequestContext(nsIURI* 
   NS_ASSERTION(mSelfURI, "No mSelfURI in SetRequestContext, can not translate 'self' into actual URI");
 
   // aDocumentPrincipal will be removed in bug 994872
   // aReferrer will be used in the patch for bug 994322
 
   return NS_OK;
 }
 
+/**
+ * Based on the given docshell, determines if this CSP context allows the
+ * ancestry.
+ *
+ * In order to determine the URI of the parent document (one causing the load
+ * of this protected document), this function obtains the docShellTreeItem,
+ * then walks up the hierarchy until it finds a privileged (chrome) tree item.
+ * Getting the parent's URI looks like this in pseudocode:
+ *
+ * nsIDocShell->QI(nsIInterfaceRequestor)
+ *            ->GI(nsIDocShellTreeItem)
+ *            ->QI(nsIInterfaceRequestor)
+ *            ->GI(nsIWebNavigation)
+ *            ->GetCurrentURI();
+ *
+ * aDocShell is the docShell for the protected document.
+ */
 NS_IMETHODIMP
 nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell, bool* outPermitsAncestry)
 {
-  // For now, we allows permitsAncestry, this will be fixed in Bug 994320
+  nsresult rv;
+
+  // Can't check ancestry without a docShell.
+  if (aDocShell == nullptr) {
+    return NS_ERROR_FAILURE;
+  }
+
   *outPermitsAncestry = true;
+
+  // extract the ancestry as an array
+  nsCOMArray<nsIURI> ancestorsArray;
+
+  nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(aDocShell));
+  nsCOMPtr<nsIDocShellTreeItem> treeItem(do_GetInterface(ir));
+  nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+  nsCOMPtr<nsIWebNavigation> webNav;
+  nsCOMPtr<nsIURI> currentURI;
+  nsCOMPtr<nsIURI> uriClone;
+
+  // iterate through each docShell parent item
+  while (NS_SUCCEEDED(treeItem->GetParent(getter_AddRefs(parentTreeItem))) &&
+         parentTreeItem != nullptr) {
+    ir     = do_QueryInterface(parentTreeItem);
+    NS_ASSERTION(ir, "Could not QI docShellTreeItem to nsIInterfaceRequestor");
+
+    webNav = do_GetInterface(ir);
+    NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE);
+
+    rv = webNav->GetCurrentURI(getter_AddRefs(currentURI));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (currentURI) {
+      // stop when reaching chrome
+      bool isChrome = false;
+      rv = currentURI->SchemeIs("chrome", &isChrome);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (isChrome) { break; }
+
+      // delete the userpass from the URI.
+      rv = currentURI->CloneIgnoringRef(getter_AddRefs(uriClone));
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = uriClone->SetUserPass(EmptyCString());
+      NS_ENSURE_SUCCESS(rv, rv);
+#ifdef PR_LOGGING
+      {
+      nsAutoCString spec;
+      uriClone->GetSpec(spec);
+      CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, found ancestor: %s", spec.get()));
+      }
+#endif
+      ancestorsArray.AppendElement(uriClone);
+    }
+
+    // next ancestor
+    treeItem = parentTreeItem;
+  }
+
+  nsAutoString violatedDirective;
+
+  // Now that we've got the ancestry chain in ancestorsArray, time to check
+  // them against any CSP.
+  for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+    for (uint32_t a = 0; a < ancestorsArray.Length(); a++) {
+      // TODO(sid) the mapping from frame-ancestors context to TYPE_DOCUMENT is
+      // forced. while this works for now, we will implement something in
+      // bug 999656.
+#ifdef PR_LOGGING
+      {
+      nsAutoCString spec;
+      ancestorsArray[a]->GetSpec(spec);
+      CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s", spec.get()));
+      }
+#endif
+      if (!mPolicies[i]->permits(nsIContentPolicy::TYPE_DOCUMENT,
+                                 ancestorsArray[a],
+                                 EmptyString(), // no nonce
+                                 violatedDirective)) {
+        // Policy is violated
+        nsCOMPtr<nsIObserverService> observerService =
+          mozilla::services::GetObserverService();
+        NS_ENSURE_TRUE(observerService, NS_ERROR_NOT_AVAILABLE);
+
+        observerService->NotifyObservers(ancestorsArray[a],
+                                         CSP_VIOLATION_TOPIC,
+                                         violatedDirective.get());
+        // TODO(sid) generate violation reports and remove NotifyObservers
+        //           call. (in bug 994322)
+        // TODO(sid) implement logic for report-only (in bug 994322)
+        *outPermitsAncestry = false;
+      }
+    }
+  }
   return NS_OK;
 }
 
 /* ===== nsISerializable implementation ====== */
 
 NS_IMETHODIMP
 nsCSPContext::Read(nsIObjectInputStream* aStream)
 {
--- a/content/base/src/nsCSPUtils.cpp
+++ b/content/base/src/nsCSPUtils.cpp
@@ -631,16 +631,18 @@ CSP_DirectiveToContentType(enum CSPDirec
   switch (aDir) {
     case CSP_IMG_SRC:    return nsIContentPolicy::TYPE_IMAGE;
     case CSP_SCRIPT_SRC: return nsIContentPolicy::TYPE_SCRIPT;
     case CSP_STYLE_SRC:  return nsIContentPolicy::TYPE_STYLESHEET;
     case CSP_FONT_SRC:   return nsIContentPolicy::TYPE_FONT;
     case CSP_MEDIA_SRC:  return nsIContentPolicy::TYPE_MEDIA;
     case CSP_OBJECT_SRC: return nsIContentPolicy::TYPE_OBJECT;
     case CSP_FRAME_SRC:  return nsIContentPolicy::TYPE_SUBDOCUMENT;
+    // TODO(sid): fix this mapping to be more precise (bug 999656)
+    case CSP_FRAME_ANCESTORS: return nsIContentPolicy::TYPE_DOCUMENT;
 
     // Fall through to error for the following Directives:
     case CSP_DEFAULT_SRC:
     case CSP_CONNECT_SRC:
     case CSP_REPORT_URI:
     case CSP_LAST_DIRECTIVE_VALUE:
     default:
       NS_ASSERTION(false, "Can not convert CSPDirective into nsContentPolicyType");
--- a/content/base/src/nsCSPUtils.h
+++ b/content/base/src/nsCSPUtils.h
@@ -44,32 +44,34 @@ enum CSPDirective {
   CSP_OBJECT_SRC,
   CSP_STYLE_SRC,
   CSP_IMG_SRC,
   CSP_MEDIA_SRC,
   CSP_FRAME_SRC,
   CSP_FONT_SRC,
   CSP_CONNECT_SRC,
   CSP_REPORT_URI,
+  CSP_FRAME_ANCESTORS,
   // CSP_LAST_DIRECTIVE_VALUE always needs to be the last element in the enum
   // because we use it to calculate the size for the char* array.
   CSP_LAST_DIRECTIVE_VALUE
 };
 
 static const char* CSPStrDirectives[] = {
-  "default-src", // CSP_DEFAULT_SRC = 0
-  "script-src",  // CSP_SCRIPT_SRC
-  "object-src",  // CSP_OBJECT_SRC
-  "style-src",   // CSP_STYLE_SRC
-  "img-src",     // CSP_IMG_SRC
-  "media-src",   // CSP_MEDIA_SRC
-  "frame-src",   // CSP_FRAME_SRC
-  "font-src",    // CSP_FONT_SRC
-  "connect-src", // CSP_CONNECT_SRC
-  "report-uri",  // CSP_REPORT_URI
+  "default-src",    // CSP_DEFAULT_SRC = 0
+  "script-src",     // CSP_SCRIPT_SRC
+  "object-src",     // CSP_OBJECT_SRC
+  "style-src",      // CSP_STYLE_SRC
+  "img-src",        // CSP_IMG_SRC
+  "media-src",      // CSP_MEDIA_SRC
+  "frame-src",      // CSP_FRAME_SRC
+  "font-src",       // CSP_FONT_SRC
+  "connect-src",    // CSP_CONNECT_SRC
+  "report-uri",     // CSP_REPORT_URI
+  "frame-ancestors" // CSP_FRAME_ANCESTORS
 };
 
 inline const char* CSP_EnumToDirective(enum CSPDirective aDir)
 {
   // Make sure all elements in enum CSPDirective got added to CSPStrDirectives.
   static_assert((sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]) ==
                 static_cast<uint32_t>(CSP_LAST_DIRECTIVE_VALUE)),
                 "CSP_LAST_DIRECTIVE_VALUE does not match length of CSPStrDirectives");
--- a/content/base/test/TestCSPParser.cpp
+++ b/content/base/test/TestCSPParser.cpp
@@ -662,16 +662,38 @@ nsresult TestGoodGeneratedPolicies() {
     { "default-src 'self' HTTP://foo.com",
       "default-src http://www.selfuri.com http://foo.com" },
     { "default-src 'NONE'",
       "default-src 'none'" },
     { "script-src policy-uri ",
       "script-src http://policy-uri" },
     { "img-src 'self'; ",
       "img-src http://www.selfuri.com" },
+    { "frame-ancestors foo-bar.com",
+      "frame-ancestors http://foo-bar.com" },
+    { "frame-ancestors http://a.com",
+      "frame-ancestors http://a.com" },
+    { "frame-ancestors 'self'",
+      "frame-ancestors http://www.selfuri.com" },
+    { "frame-ancestors http://self.com:88",
+      "frame-ancestors http://self.com:88" },
+    { "frame-ancestors http://a.b.c.d.e.f.g.h.i.j.k.l.x.com",
+      "frame-ancestors http://a.b.c.d.e.f.g.h.i.j.k.l.x.com" },
+    { "frame-ancestors https://self.com:34",
+      "frame-ancestors https://self.com:34" },
+    { "default-src 'none'; frame-ancestors 'self'",
+      "default-src 'none'; frame-ancestors http://www.selfuri.com" },
+    { "frame-ancestors http://self:80",
+      "frame-ancestors http://self:80" },
+    { "frame-ancestors http://self.com/bar",
+      "frame-ancestors http://self.com" },
+    { "default-src 'self'; frame-ancestors 'self'",
+      "default-src http://www.selfuri.com; frame-ancestors http://www.selfuri.com" },
+    { "frame-ancestors http://bar.com/foo.png",
+      "frame-ancestors http://bar.com" },
   };
 
   uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
   return runTestSuite(policies, policyCount, 1);
 }
 
 // ============================= TestBadGeneratedPolicies ========================