Bug 789776: Log an error message when X-Frame-Options vetoes a load. r=jst
authorKyle Huey <khuey@kylehuey.com>
Thu, 20 Sep 2012 19:47:18 -0700
changeset 107812 eed7b92769c36585304e3c8eb3d80315f66d315d
parent 107811 7ffcbd67d18d72b45380f0cc3a48123bbe8f85a5
child 107813 4050caa0b9c5684cbabfb37c55c788da955d071f
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersjst
bugs789776
milestone18.0a1
Bug 789776: Log an error message when X-Frame-Options vetoes a load. r=jst
docshell/base/nsDSURIContentListener.cpp
docshell/base/nsDSURIContentListener.h
--- a/docshell/base/nsDSURIContentListener.cpp
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -13,16 +13,18 @@
 #include "nsIDOMWindow.h"
 #include "nsNetUtil.h"
 #include "nsAutoPtr.h"
 #include "nsIHttpChannel.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsError.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "mozilla/Preferences.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
 
 using namespace mozilla;
 
 //*****************************************************************************
 //***    nsDSURIContentListener: Object Management
 //*****************************************************************************
 
 nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell)
@@ -270,115 +272,123 @@ bool nsDSURIContentListener::CheckOneFra
         !isAllowFrom)
         return true;
 
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
     if (!httpChannel) {
         return true;
     }
 
-    if (mDocShell) {
-        // We need to check the location of this window and the location of the top
-        // window, if we're not the top.  X-F-O: SAMEORIGIN requires that the
-        // document must be same-origin with top window.  X-F-O: DENY requires that
-        // the document must never be framed.
-        nsCOMPtr<nsIDOMWindow> thisWindow = do_GetInterface(static_cast<nsIDocShell*>(mDocShell));
-        // If we don't have DOMWindow there is no risk of clickjacking
-        if (!thisWindow)
-            return true;
+    nsCOMPtr<nsIURI> uri;
+    httpChannel->GetURI(getter_AddRefs(uri));
+
+    // XXXkhuey when does this happen?  Is returning true safe here?
+    if (!mDocShell) {
+        return true;
+    }
 
-        // GetScriptableTop, not GetTop, because we want this to respect
-        // <iframe mozbrowser> boundaries.
-        nsCOMPtr<nsIDOMWindow> topWindow;
-        thisWindow->GetScriptableTop(getter_AddRefs(topWindow));
+    // We need to check the location of this window and the location of the top
+    // window, if we're not the top.  X-F-O: SAMEORIGIN requires that the
+    // document must be same-origin with top window.  X-F-O: DENY requires that
+    // the document must never be framed.
+    nsCOMPtr<nsIDOMWindow> thisWindow = do_GetInterface(static_cast<nsIDocShell*>(mDocShell));
+    // If we don't have DOMWindow there is no risk of clickjacking
+    if (!thisWindow)
+        return true;
+
+    // GetScriptableTop, not GetTop, because we want this to respect
+    // <iframe mozbrowser> boundaries.
+    nsCOMPtr<nsIDOMWindow> topWindow;
+    thisWindow->GetScriptableTop(getter_AddRefs(topWindow));
+
+    // if the document is in the top window, it's not in a frame.
+    if (thisWindow == topWindow)
+        return true;
 
-        // if the document is in the top window, it's not in a frame.
-        if (thisWindow == topWindow)
-            return true;
+    // Find the top docshell in our parent chain that doesn't have the system
+    // principal and use it for the principal comparison.  Finding the top
+    // content-type docshell doesn't work because some chrome documents are
+    // loaded in content docshells (see bug 593387).
+    nsCOMPtr<nsIDocShellTreeItem> thisDocShellItem(do_QueryInterface(
+                                                   static_cast<nsIDocShell*> (mDocShell)));
+    nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem,
+                                  curDocShellItem = thisDocShellItem;
+    nsCOMPtr<nsIDocument> topDoc;
+    nsresult rv;
+    nsCOMPtr<nsIScriptSecurityManager> ssm =
+        do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+    if (!ssm) {
+        MOZ_CRASH();
+    }
 
-        // Find the top docshell in our parent chain that doesn't have the system
-        // principal and use it for the principal comparison.  Finding the top
-        // content-type docshell doesn't work because some chrome documents are
-        // loaded in content docshells (see bug 593387).
-        nsCOMPtr<nsIDocShellTreeItem> thisDocShellItem(do_QueryInterface(
-                                                       static_cast<nsIDocShell*> (mDocShell)));
-        nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem,
-                                      curDocShellItem = thisDocShellItem;
-        nsCOMPtr<nsIDocument> topDoc;
-        nsresult rv;
-        nsCOMPtr<nsIScriptSecurityManager> ssm =
-            do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
-        if (!ssm) {
-            NS_ASSERTION(ssm, "Failed to get the ScriptSecurityManager.");
-            return false;
+    // Traverse up the parent chain and stop when we see a docshell whose
+    // parent has a system principal, or a docshell corresponding to
+    // <iframe mozbrowser>.
+    while (NS_SUCCEEDED(curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem))) &&
+           parentDocShellItem) {
+
+        nsCOMPtr<nsIDocShell> curDocShell = do_QueryInterface(curDocShellItem);
+        if (curDocShell && curDocShell->GetIsContentBoundary()) {
+          break;
         }
 
-        // Traverse up the parent chain and stop when we see a docshell whose
-        // parent has a system principal, or a docshell corresponding to
-        // <iframe mozbrowser>.
-        while (NS_SUCCEEDED(curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem))) &&
-               parentDocShellItem) {
-
-            nsCOMPtr<nsIDocShell> curDocShell = do_QueryInterface(curDocShellItem);
-            if (curDocShell && curDocShell->GetIsContentBoundary()) {
-              break;
+        bool system = false;
+        topDoc = do_GetInterface(parentDocShellItem);
+        if (topDoc) {
+            if (NS_SUCCEEDED(ssm->IsSystemPrincipal(topDoc->NodePrincipal(),
+                                                    &system)) && system) {
+                // Found a system-principled doc: last docshell was top.
+                break;
             }
-
-            bool system = false;
-            topDoc = do_GetInterface(parentDocShellItem);
-            if (topDoc) {
-                if (NS_SUCCEEDED(ssm->IsSystemPrincipal(topDoc->NodePrincipal(),
-                                                        &system)) && system) {
-                    // Found a system-principled doc: last docshell was top.
-                    break;
-                }
-            }
-            else {
-                return false;
-            }
-            curDocShellItem = parentDocShellItem;
         }
-
-        // If this document has the top non-SystemPrincipal docshell it is not being
-        // framed or it is being framed by a chrome document, which we allow.
-        if (curDocShellItem == thisDocShellItem)
-            return true;
-
-        // If the value of the header is DENY, and the previous condition is
-        // not met (current docshell is not the top docshell), prohibit the
-        // load.
-        if (policy.LowerCaseEqualsLiteral("deny")) {
+        else {
             return false;
         }
+        curDocShellItem = parentDocShellItem;
+    }
 
-        topDoc = do_GetInterface(curDocShellItem);
-        nsCOMPtr<nsIURI> topUri;
-        topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri));
-        nsCOMPtr<nsIURI> uri;
+    // If this document has the top non-SystemPrincipal docshell it is not being
+    // framed or it is being framed by a chrome document, which we allow.
+    if (curDocShellItem == thisDocShellItem)
+        return true;
+
+    // If the value of the header is DENY, and the previous condition is
+    // not met (current docshell is not the top docshell), prohibit the
+    // load.
+    if (policy.LowerCaseEqualsLiteral("deny")) {
+        ReportXFOViolation(curDocShellItem, uri, eDENY);
+        return false;
+    }
+
+    topDoc = do_GetInterface(curDocShellItem);
+    nsCOMPtr<nsIURI> topUri;
+    topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri));
 
-        // If the X-Frame-Options value is SAMEORIGIN, then the top frame in the
-        // parent chain must be from the same origin as this document.
-        if (policy.LowerCaseEqualsLiteral("sameorigin")) {
-            httpChannel->GetURI(getter_AddRefs(uri));
-            rv = ssm->CheckSameOriginURI(uri, topUri, true);
-            if (NS_FAILED(rv))
-                return false; /* wasn't same-origin */
+    // If the X-Frame-Options value is SAMEORIGIN, then the top frame in the
+    // parent chain must be from the same origin as this document.
+    if (policy.LowerCaseEqualsLiteral("sameorigin")) {
+        rv = ssm->CheckSameOriginURI(uri, topUri, true);
+        if (NS_FAILED(rv)) {
+            ReportXFOViolation(curDocShellItem, uri, eSAMEORIGIN);
+            return false; /* wasn't same-origin */
         }
+    }
 
-        // If the X-Frame-Options value is "allow-from [uri]", then the top
-        // frame in the parent chain must be from that origin
-        if (isAllowFrom) {
-            rv = NS_NewURI(getter_AddRefs(uri),
-                           Substring(policy, allowFromLen));
-            if (NS_FAILED(rv))
-              return false;
+    // If the X-Frame-Options value is "allow-from [uri]", then the top
+    // frame in the parent chain must be from that origin
+    if (isAllowFrom) {
+        rv = NS_NewURI(getter_AddRefs(uri),
+                       Substring(policy, allowFromLen));
+        if (NS_FAILED(rv))
+          return false;
 
-            rv = ssm->CheckSameOriginURI(uri, topUri, true);
-            if (NS_FAILED(rv))
-                return false;
+        rv = ssm->CheckSameOriginURI(uri, topUri, true);
+        if (NS_FAILED(rv)) {
+            ReportXFOViolation(curDocShellItem, uri, eALLOWFROM);
+            return false;
         }
     }
 
     return true;
 }
 
 // Check if X-Frame-Options permits this document to be loaded as a subdocument.
 // This will iterate through and check any number of X-Frame-Options policies
@@ -415,8 +425,82 @@ bool nsDSURIContentListener::CheckFrameO
                 }
             }
             return false;
         }
     }
 
     return true;
 }
+
+void
+nsDSURIContentListener::ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem,
+                                           nsIURI* aThisURI,
+                                           XFOHeader aHeader)
+{
+    nsresult rv = NS_OK;
+
+    nsCOMPtr<nsPIDOMWindow> topOuterWindow = do_GetInterface(aTopDocShellItem);
+    if (!topOuterWindow)
+        return;
+
+    NS_ASSERTION(topOuterWindow->IsOuterWindow(), "Huh?");
+    nsPIDOMWindow* topInnerWindow = topOuterWindow->GetCurrentInnerWindow();
+    if (!topInnerWindow)
+        return;
+
+    nsCOMPtr<nsIURI> topURI;
+
+    nsCOMPtr<nsIDocument> document;
+
+    document = do_GetInterface(aTopDocShellItem);
+    rv = document->NodePrincipal()->GetURI(getter_AddRefs(topURI));
+    if (NS_FAILED(rv))
+        return;
+
+    if (!topURI)
+        return;
+
+    nsCString topURIString;
+    nsCString thisURIString;
+
+    rv = topURI->GetSpec(topURIString);
+    if (NS_FAILED(rv))
+        return;
+
+    rv = aThisURI->GetSpec(thisURIString);
+    if (NS_FAILED(rv))
+        return;
+
+    nsCOMPtr<nsIConsoleService> consoleService =
+      do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+    nsCOMPtr<nsIScriptError> errorObject =
+      do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+
+    if (!consoleService || !errorObject)
+        return;
+
+    nsString msg = NS_LITERAL_STRING("Load denied by X-Frame-Options: ");
+    msg.Append(NS_ConvertUTF8toUTF16(thisURIString));
+
+    switch (aHeader) {
+        case eDENY:
+            msg.AppendLiteral(" does not permit framing.");
+            break;
+        case eSAMEORIGIN:
+            msg.AppendLiteral(" does not permit cross-origin framing.");
+            break;
+        case eALLOWFROM:
+            msg.AppendLiteral(" does not permit framing by ");
+            msg.Append(NS_ConvertUTF8toUTF16(topURIString));
+            msg.AppendLiteral(".");
+            break;
+    }
+
+    rv = errorObject->InitWithWindowID(msg, EmptyString(), EmptyString(), 0, 0,
+                                       nsIScriptError::errorFlag,
+                                       "X-Frame-Options",
+                                       topInnerWindow->WindowID());
+    if (NS_FAILED(rv))
+        return;
+
+    consoleService->LogMessage(errorObject);
+}
--- a/docshell/base/nsDSURIContentListener.h
+++ b/docshell/base/nsDSURIContentListener.h
@@ -36,16 +36,25 @@ protected:
     }
 
     // Determine if X-Frame-Options allows content to be framed
     // as a subdocument
     bool CheckFrameOptions(nsIRequest* request);
     bool CheckOneFrameOptionsPolicy(nsIRequest* request,
                                     const nsAString& policy);
 
+    enum XFOHeader {
+      eDENY,
+      eSAMEORIGIN,
+      eALLOWFROM
+    };
+
+    void ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem,
+                            nsIURI* aThisURI,
+                            XFOHeader aHeader);
 protected:
     nsDocShell*                      mDocShell;
 
     // Store the parent listener in either of these depending on
     // if supports weak references or not. Proper weak refs are
     // preferred and encouraged!
     nsWeakPtr                        mWeakParentContentListener;
     nsIURIContentListener*           mParentContentListener;