Bug 1370788 - Move XFO out of nsDSURIContentListener.cpp into dom/security. r=smaug
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Mon, 19 Jun 2017 06:59:44 +0200
changeset 400424 f55086b153eb88ec9620c26f278384dad26343c0
parent 400423 07071624cc92f889943c93ebdfb5091d043a1ec7
child 400425 859d9b3a95b2f3552c2543ba931f4275372b045c
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs1370788
milestone56.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 1370788 - Move XFO out of nsDSURIContentListener.cpp into dom/security. r=smaug
docshell/base/nsDSURIContentListener.cpp
docshell/base/nsDSURIContentListener.h
docshell/base/nsDocShell.h
dom/base/nsDocument.cpp
dom/security/FramingChecker.cpp
dom/security/FramingChecker.h
dom/security/moz.build
--- a/docshell/base/nsDSURIContentListener.cpp
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -7,28 +7,20 @@
 #include "nsDocShell.h"
 #include "nsDSURIContentListener.h"
 #include "nsIChannel.h"
 #include "nsServiceManagerUtils.h"
 #include "nsDocShellCID.h"
 #include "nsIWebNavigationInfo.h"
 #include "nsIDocument.h"
 #include "nsIDOMWindow.h"
-#include "nsNetUtil.h"
-#include "nsQueryObject.h"
 #include "nsIHttpChannel.h"
-#include "nsIScriptSecurityManager.h"
 #include "nsError.h"
-#include "nsCharSeparatedTokenizer.h"
-#include "nsIConsoleService.h"
-#include "nsIScriptError.h"
 #include "nsDocShellLoadTypes.h"
 #include "nsIMultiPartChannel.h"
-#include "nsContentUtils.h"
-#include "mozilla/dom/nsCSPUtils.h"
 
 using namespace mozilla;
 
 nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell)
   : mDocShell(aDocShell)
   , mExistingJPEGRequest(nullptr)
   , mParentContentListener(nullptr)
 {
@@ -253,331 +245,8 @@ nsDSURIContentListener::SetParentContent
       mParentContentListener = aParentListener;
     }
   } else {
     mWeakParentContentListener = nullptr;
     mParentContentListener = nullptr;
   }
   return NS_OK;
 }
-
-/* static */ bool
-nsDSURIContentListener::CheckOneFrameOptionsPolicy(nsIHttpChannel* aHttpChannel,
-                                                   const nsAString& aPolicy,
-                                                   nsIDocShell* aDocShell)
-{
-  static const char allowFrom[] = "allow-from";
-  const uint32_t allowFromLen = ArrayLength(allowFrom) - 1;
-  bool isAllowFrom =
-    StringHead(aPolicy, allowFromLen).LowerCaseEqualsLiteral(allowFrom);
-
-  // return early if header does not have one of the values with meaning
-  if (!aPolicy.LowerCaseEqualsLiteral("deny") &&
-      !aPolicy.LowerCaseEqualsLiteral("sameorigin") &&
-      !isAllowFrom) {
-    return true;
-  }
-
-  nsCOMPtr<nsIURI> uri;
-  aHttpChannel->GetURI(getter_AddRefs(uri));
-
-  // XXXkhuey when does this happen?  Is returning true safe here?
-  if (!aDocShell) {
-    return true;
-  }
-
-  // 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<nsPIDOMWindowOuter> thisWindow = aDocShell->GetWindow();
-  // 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<nsPIDOMWindowOuter> topWindow = thisWindow->GetScriptableTop();
-
-  // 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*>(aDocShell)));
-  nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem;
-  nsCOMPtr<nsIDocShellTreeItem> curDocShellItem = thisDocShellItem;
-  nsCOMPtr<nsIDocument> topDoc;
-  nsresult rv;
-  nsCOMPtr<nsIScriptSecurityManager> ssm =
-    do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
-  if (!ssm) {
-    MOZ_CRASH();
-  }
-
-  // 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->GetIsMozBrowser()) {
-      break;
-    }
-
-    bool system = false;
-    topDoc = parentDocShellItem->GetDocument();
-    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 (aPolicy.LowerCaseEqualsLiteral("deny")) {
-    ReportXFOViolation(curDocShellItem, uri, eDENY);
-    return false;
-  }
-
-  topDoc = curDocShellItem->GetDocument();
-  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 (aPolicy.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) {
-    if (aPolicy.Length() == allowFromLen ||
-        (aPolicy[allowFromLen] != ' ' &&
-         aPolicy[allowFromLen] != '\t')) {
-      ReportXFOViolation(curDocShellItem, uri, eALLOWFROM);
-      return false;
-    }
-    rv = NS_NewURI(getter_AddRefs(uri), Substring(aPolicy, allowFromLen));
-    if (NS_FAILED(rv)) {
-      return false;
-    }
-
-    rv = ssm->CheckSameOriginURI(uri, topUri, true);
-    if (NS_FAILED(rv)) {
-      ReportXFOViolation(curDocShellItem, uri, eALLOWFROM);
-      return false;
-    }
-  }
-
-  return true;
-}
-
-// Ignore x-frame-options if CSP with frame-ancestors exists
-static bool
-ShouldIgnoreFrameOptions(nsIChannel* aChannel, nsIPrincipal* aPrincipal)
-{
-  NS_ENSURE_TRUE(aChannel, false);
-  NS_ENSURE_TRUE(aPrincipal, false);
-
-  nsCOMPtr<nsIContentSecurityPolicy> csp;
-  aPrincipal->GetCsp(getter_AddRefs(csp));
-  if (!csp) {
-    // if there is no CSP, then there is nothing to do here
-    return false;
-  }
-
-  bool enforcesFrameAncestors = false;
-  csp->GetEnforcesFrameAncestors(&enforcesFrameAncestors);
-  if (!enforcesFrameAncestors) {
-    // if CSP does not contain frame-ancestors, then there
-    // is nothing to do here.
-    return false;
-  }
-
-  // log warning to console that xfo is ignored because of CSP
-  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
-  uint64_t innerWindowID = loadInfo ? loadInfo->GetInnerWindowID() : 0;
-  const char16_t* params[] = { u"x-frame-options",
-                               u"frame-ancestors" };
-  CSP_LogLocalizedStr(u"IgnoringSrcBecauseOfDirective",
-                      params, ArrayLength(params),
-                      EmptyString(), // no sourcefile
-                      EmptyString(), // no scriptsample
-                      0,             // no linenumber
-                      0,             // no columnnumber
-                      nsIScriptError::warningFlag,
-                      "CSP", innerWindowID);
-
-  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
-// in the request (comma-separated in a header, multiple headers, etc).
-/* static */ bool
-nsDSURIContentListener::CheckFrameOptions(nsIChannel* aChannel,
-                                          nsIDocShell* aDocShell,
-                                          nsIPrincipal* aPrincipal)
-{
-  if (!aChannel || !aDocShell) {
-    return true;
-  }
-
-  if (ShouldIgnoreFrameOptions(aChannel, aPrincipal)) {
-    return true;
-  }
-
-  nsresult rv;
-  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
-  if (!httpChannel) {
-    // check if it is hiding in a multipart channel
-    rv = nsDocShell::Cast(aDocShell)->GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
-    if (NS_FAILED(rv)) {
-      return false;
-    }
-  }
-
-  if (!httpChannel) {
-    return true;
-  }
-
-  nsAutoCString xfoHeaderCValue;
-  Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
-                                           xfoHeaderCValue);
-  NS_ConvertUTF8toUTF16 xfoHeaderValue(xfoHeaderCValue);
-
-  // if no header value, there's nothing to do.
-  if (xfoHeaderValue.IsEmpty()) {
-    return true;
-  }
-
-  // iterate through all the header values (usually there's only one, but can
-  // be many.  If any want to deny the load, deny the load.
-  nsCharSeparatedTokenizer tokenizer(xfoHeaderValue, ',');
-  while (tokenizer.hasMoreTokens()) {
-    const nsSubstring& tok = tokenizer.nextToken();
-    if (!CheckOneFrameOptionsPolicy(httpChannel, tok, aDocShell)) {
-      // cancel the load and display about:blank
-      httpChannel->Cancel(NS_BINDING_ABORTED);
-      if (aDocShell) {
-        nsCOMPtr<nsIWebNavigation> webNav(do_QueryObject(aDocShell));
-        if (webNav) {
-          nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
-          nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo
-            ? loadInfo->TriggeringPrincipal()
-            : nsContentUtils::GetSystemPrincipal();
-          webNav->LoadURI(u"about:blank",
-                          0, nullptr, nullptr, nullptr,
-                          triggeringPrincipal);
-        }
-      }
-      return false;
-    }
-  }
-
-  return true;
-}
-
-/* static */ void
-nsDSURIContentListener::ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem,
-                                           nsIURI* aThisURI,
-                                           XFOHeader aHeader)
-{
-  MOZ_ASSERT(aTopDocShellItem, "Need a top docshell");
-
-  nsCOMPtr<nsPIDOMWindowOuter> topOuterWindow = aTopDocShellItem->GetWindow();
-  if (!topOuterWindow) {
-    return;
-  }
-
-  nsPIDOMWindowInner* topInnerWindow = topOuterWindow->GetCurrentInnerWindow();
-  if (!topInnerWindow) {
-    return;
-  }
-
-  nsCOMPtr<nsIURI> topURI;
-
-  nsCOMPtr<nsIDocument> document = aTopDocShellItem->GetDocument();
-  nsresult 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.Append('.');
-      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
@@ -8,62 +8,40 @@
 #define nsDSURIContentListener_h__
 
 #include "nsCOMPtr.h"
 #include "nsIURIContentListener.h"
 #include "nsWeakReference.h"
 
 class nsDocShell;
 class nsIWebNavigationInfo;
-class nsIHttpChannel;
-class nsAString;
 
 class nsDSURIContentListener final
   : public nsIURIContentListener
   , public nsSupportsWeakReference
 {
   friend class nsDocShell;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIURICONTENTLISTENER
 
   nsresult Init();
 
-  // Determine if X-Frame-Options allows content to be framed
-  // as a subdocument
-  static bool CheckFrameOptions(nsIChannel* aChannel,
-                                nsIDocShell* aDocShell,
-                                nsIPrincipal* aPrincipal);
-
 protected:
   explicit nsDSURIContentListener(nsDocShell* aDocShell);
   virtual ~nsDSURIContentListener();
 
   void DropDocShellReference()
   {
     mDocShell = nullptr;
     mExistingJPEGRequest = nullptr;
     mExistingJPEGStreamListener = nullptr;
   }
 
-  static bool CheckOneFrameOptionsPolicy(nsIHttpChannel* aHttpChannel,
-                                         const nsAString& aPolicy,
-                                         nsIDocShell* aDocShell);
-  enum XFOHeader
-  {
-    eDENY,
-    eSAMEORIGIN,
-    eALLOWFROM
-  };
-
-  static void ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem,
-                                 nsIURI* aThisURI,
-                                 XFOHeader aHeader);
-
 protected:
   nsDocShell* mDocShell;
   // Hack to handle multipart images without creating a new viewer
   nsCOMPtr<nsIStreamListener> mExistingJPEGStreamListener;
   nsCOMPtr<nsIChannel> mExistingJPEGRequest;
 
   // Store the parent listener in either of these depending on
   // if supports weak references or not. Proper weak refs are
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -90,16 +90,17 @@ class nsIMutableArray;
 class nsIPrompt;
 class nsISHistory;
 class nsISecureBrowserUI;
 class nsIStringBundle;
 class nsIURIFixup;
 class nsIURILoader;
 class nsIWebBrowserFind;
 class nsIWidget;
+class FramingChecker;
 
 /* internally used ViewMode types */
 enum ViewMode
 {
   viewNormal = 0x0,
   viewSource = 0x1
 };
 
@@ -148,16 +149,17 @@ class nsDocShell final
   , public nsILinkHandler
   , public nsIClipboardCommands
   , public nsIDOMStorageManager
   , public nsINetworkInterceptController
   , public nsIDeprecationWarner
   , public mozilla::SupportsWeakPtr<nsDocShell>
 {
   friend class nsDSURIContentListener;
+  friend class FramingChecker;
 
 public:
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsDocShell)
 
   nsDocShell();
 
   virtual nsresult Init() override;
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -57,20 +57,20 @@
 #include "nsIDOMNodeFilter.h"
 
 #include "nsIDOMStyleSheet.h"
 #include "mozilla/dom/Attr.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsIDOMDOMImplementation.h"
 #include "nsIDOMDocumentXBL.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/FramingChecker.h"
 #include "nsGenericHTMLElement.h"
 #include "mozilla/dom/CDATASection.h"
 #include "mozilla/dom/ProcessingInstruction.h"
-#include "nsDSURIContentListener.h"
 #include "nsDOMString.h"
 #include "nsNodeUtils.h"
 #include "nsLayoutUtils.h" // for GetFrameForPoint
 #include "nsIFrame.h"
 #include "nsITabChild.h"
 
 #include "nsRange.h"
 #include "nsIDOMText.h"
@@ -2573,17 +2573,17 @@ nsDocument::StartDocumentLoad(const char
   // If this is not a data document, set CSP.
   if (!mLoadedAsData) {
     nsresult rv = InitCSP(aChannel);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // XFO needs to be checked after CSP because it is ignored if
   // the CSP defines frame-ancestors.
-  if (!nsDSURIContentListener::CheckFrameOptions(aChannel, docShell, NodePrincipal())) {
+  if (!FramingChecker::CheckFrameOptions(aChannel, docShell, NodePrincipal())) {
     MOZ_LOG(gCspPRLog, LogLevel::Debug,
             ("XFO doesn't like frame's ancestry, not loading."));
     // stop!  ERROR page!
     aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
   }
 
   return NS_OK;
 }
new file mode 100644
--- /dev/null
+++ b/dom/security/FramingChecker.cpp
@@ -0,0 +1,342 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FramingChecker.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsCSPUtils.h"
+#include "nsDocShell.h"
+#include "nsIChannel.h"
+#include "nsIConsoleService.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIScriptError.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "mozilla/dom/nsCSPUtils.h"
+
+using namespace mozilla;
+
+/* static */ bool
+FramingChecker::CheckOneFrameOptionsPolicy(nsIHttpChannel* aHttpChannel,
+                                           const nsAString& aPolicy,
+                                           nsIDocShell* aDocShell)
+{
+  static const char allowFrom[] = "allow-from";
+  const uint32_t allowFromLen = ArrayLength(allowFrom) - 1;
+  bool isAllowFrom =
+    StringHead(aPolicy, allowFromLen).LowerCaseEqualsLiteral(allowFrom);
+
+  // return early if header does not have one of the values with meaning
+  if (!aPolicy.LowerCaseEqualsLiteral("deny") &&
+      !aPolicy.LowerCaseEqualsLiteral("sameorigin") &&
+      !isAllowFrom) {
+    return true;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  aHttpChannel->GetURI(getter_AddRefs(uri));
+
+  // XXXkhuey when does this happen?  Is returning true safe here?
+  if (!aDocShell) {
+    return true;
+  }
+
+  // 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<nsPIDOMWindowOuter> thisWindow = aDocShell->GetWindow();
+  // 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<nsPIDOMWindowOuter> topWindow = thisWindow->GetScriptableTop();
+
+  // 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*>(aDocShell)));
+  nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem;
+  nsCOMPtr<nsIDocShellTreeItem> curDocShellItem = thisDocShellItem;
+  nsCOMPtr<nsIDocument> topDoc;
+  nsresult rv;
+  nsCOMPtr<nsIScriptSecurityManager> ssm =
+    do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+  if (!ssm) {
+    MOZ_CRASH();
+  }
+
+  // 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->GetIsMozBrowser()) {
+      break;
+    }
+
+    bool system = false;
+    topDoc = parentDocShellItem->GetDocument();
+    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 (aPolicy.LowerCaseEqualsLiteral("deny")) {
+    ReportXFOViolation(curDocShellItem, uri, eDENY);
+    return false;
+  }
+
+  topDoc = curDocShellItem->GetDocument();
+  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 (aPolicy.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) {
+    if (aPolicy.Length() == allowFromLen ||
+        (aPolicy[allowFromLen] != ' ' &&
+         aPolicy[allowFromLen] != '\t')) {
+      ReportXFOViolation(curDocShellItem, uri, eALLOWFROM);
+      return false;
+    }
+    rv = NS_NewURI(getter_AddRefs(uri), Substring(aPolicy, allowFromLen));
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+
+    rv = ssm->CheckSameOriginURI(uri, topUri, true);
+    if (NS_FAILED(rv)) {
+      ReportXFOViolation(curDocShellItem, uri, eALLOWFROM);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// Ignore x-frame-options if CSP with frame-ancestors exists
+static bool
+ShouldIgnoreFrameOptions(nsIChannel* aChannel, nsIPrincipal* aPrincipal)
+{
+  NS_ENSURE_TRUE(aChannel, false);
+  NS_ENSURE_TRUE(aPrincipal, false);
+
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  aPrincipal->GetCsp(getter_AddRefs(csp));
+  if (!csp) {
+    // if there is no CSP, then there is nothing to do here
+    return false;
+  }
+
+  bool enforcesFrameAncestors = false;
+  csp->GetEnforcesFrameAncestors(&enforcesFrameAncestors);
+  if (!enforcesFrameAncestors) {
+    // if CSP does not contain frame-ancestors, then there
+    // is nothing to do here.
+    return false;
+  }
+
+  // log warning to console that xfo is ignored because of CSP
+  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+  uint64_t innerWindowID = loadInfo ? loadInfo->GetInnerWindowID() : 0;
+  const char16_t* params[] = { u"x-frame-options",
+                               u"frame-ancestors" };
+  CSP_LogLocalizedStr(u"IgnoringSrcBecauseOfDirective",
+                      params, ArrayLength(params),
+                      EmptyString(), // no sourcefile
+                      EmptyString(), // no scriptsample
+                      0,             // no linenumber
+                      0,             // no columnnumber
+                      nsIScriptError::warningFlag,
+                      "CSP", innerWindowID);
+
+  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
+// in the request (comma-separated in a header, multiple headers, etc).
+/* static */ bool
+FramingChecker::CheckFrameOptions(nsIChannel* aChannel,
+                                  nsIDocShell* aDocShell,
+                                  nsIPrincipal* aPrincipal)
+{
+  if (!aChannel || !aDocShell) {
+    return true;
+  }
+
+  if (ShouldIgnoreFrameOptions(aChannel, aPrincipal)) {
+    return true;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+  if (!httpChannel) {
+    // check if it is hiding in a multipart channel
+    rv = nsDocShell::Cast(aDocShell)->GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+  }
+
+  if (!httpChannel) {
+    return true;
+  }
+
+  nsAutoCString xfoHeaderCValue;
+  Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
+                                           xfoHeaderCValue);
+  NS_ConvertUTF8toUTF16 xfoHeaderValue(xfoHeaderCValue);
+
+  // if no header value, there's nothing to do.
+  if (xfoHeaderValue.IsEmpty()) {
+    return true;
+  }
+
+  // iterate through all the header values (usually there's only one, but can
+  // be many.  If any want to deny the load, deny the load.
+  nsCharSeparatedTokenizer tokenizer(xfoHeaderValue, ',');
+  while (tokenizer.hasMoreTokens()) {
+    const nsSubstring& tok = tokenizer.nextToken();
+    if (!CheckOneFrameOptionsPolicy(httpChannel, tok, aDocShell)) {
+      // cancel the load and display about:blank
+      httpChannel->Cancel(NS_BINDING_ABORTED);
+      if (aDocShell) {
+        nsCOMPtr<nsIWebNavigation> webNav(do_QueryObject(aDocShell));
+        if (webNav) {
+          nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
+          nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo
+            ? loadInfo->TriggeringPrincipal()
+            : nsContentUtils::GetSystemPrincipal();
+          webNav->LoadURI(u"about:blank",
+                          0, nullptr, nullptr, nullptr,
+                          triggeringPrincipal);
+        }
+      }
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/* static */ void
+FramingChecker::ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem,
+                                   nsIURI* aThisURI,
+                                   XFOHeader aHeader)
+{
+  MOZ_ASSERT(aTopDocShellItem, "Need a top docshell");
+
+  nsCOMPtr<nsPIDOMWindowOuter> topOuterWindow = aTopDocShellItem->GetWindow();
+  if (!topOuterWindow) {
+    return;
+  }
+
+  nsPIDOMWindowInner* topInnerWindow = topOuterWindow->GetCurrentInnerWindow();
+  if (!topInnerWindow) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> topURI;
+
+  nsCOMPtr<nsIDocument> document = aTopDocShellItem->GetDocument();
+  nsresult 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.Append('.');
+      break;
+  }
+
+  rv = errorObject->InitWithWindowID(msg, EmptyString(), EmptyString(), 0, 0,
+                                     nsIScriptError::errorFlag,
+                                     "X-Frame-Options",
+                                     topInnerWindow->WindowID());
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  consoleService->LogMessage(errorObject);
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/FramingChecker.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FramingChecker_h
+#define mozilla_dom_FramingChecker_h
+
+class nsIDocShell;
+class nsIChannel;
+class nsIHttpChannel;
+class nsIDocShellTreeItem;
+class nsIURI;
+class nsIPrincipal;
+
+class FramingChecker {
+
+public:
+  // Determine if X-Frame-Options allows content to be framed
+  // as a subdocument
+  static bool CheckFrameOptions(nsIChannel* aChannel,
+                                nsIDocShell* aDocShell,
+                                nsIPrincipal* aPrincipal);
+
+protected:
+  enum XFOHeader
+  {
+    eDENY,
+    eSAMEORIGIN,
+    eALLOWFROM
+  };
+
+  static bool CheckOneFrameOptionsPolicy(nsIHttpChannel* aHttpChannel,
+                                         const nsAString& aPolicy,
+                                         nsIDocShell* aDocShell);
+
+  static void ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem,
+                                 nsIURI* aThisURI,
+                                 XFOHeader aHeader);
+};
+
+#endif /* mozilla_dom_FramingChecker_h */
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -6,16 +6,17 @@
 
 with Files('*'):
     BUG_COMPONENT = ('Core', 'DOM: Security')
 
 TEST_DIRS += ['test']
 
 EXPORTS.mozilla.dom += [
     'ContentVerifier.h',
+    'FramingChecker.h',
     'nsContentSecurityManager.h',
     'nsCSPContext.h',
     'nsCSPService.h',
     'nsCSPUtils.h',
     'nsMixedContentBlocker.h',
     'SRICheck.h',
     'SRILogHelper.h',
     'SRIMetadata.h',
@@ -23,29 +24,31 @@ EXPORTS.mozilla.dom += [
 
 EXPORTS += [
     'nsContentSecurityManager.h',
     'nsMixedContentBlocker.h',
 ]
 
 UNIFIED_SOURCES += [
     'ContentVerifier.cpp',
+    'FramingChecker.cpp',
     'nsContentSecurityManager.cpp',
     'nsCSPContext.cpp',
     'nsCSPParser.cpp',
     'nsCSPService.cpp',
     'nsCSPUtils.cpp',
     'nsMixedContentBlocker.cpp',
     'SRICheck.cpp',
     'SRIMetadata.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/caps',
+    '/docshell/base',  # for nsDocShell.h
     '/netwerk/base',
 ]
 
 if CONFIG['GNU_CC']:
     CFLAGS += ['-Wformat-security']
     CXXFLAGS += ['-Wformat-security']