LCOV - code coverage report
Current view: top level - dom/security - FramingChecker.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 23 161 14.3 %
Date: 2017-07-14 16:53:18 Functions: 2 4 50.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "FramingChecker.h"
       8             : #include "nsCharSeparatedTokenizer.h"
       9             : #include "nsCSPUtils.h"
      10             : #include "nsDocShell.h"
      11             : #include "nsIChannel.h"
      12             : #include "nsIConsoleService.h"
      13             : #include "nsIContentSecurityPolicy.h"
      14             : #include "nsIScriptError.h"
      15             : #include "nsNetUtil.h"
      16             : #include "nsQueryObject.h"
      17             : #include "mozilla/dom/nsCSPUtils.h"
      18             : 
      19             : using namespace mozilla;
      20             : 
      21             : /* static */ bool
      22           0 : FramingChecker::CheckOneFrameOptionsPolicy(nsIHttpChannel* aHttpChannel,
      23             :                                            const nsAString& aPolicy,
      24             :                                            nsIDocShell* aDocShell)
      25             : {
      26             :   static const char allowFrom[] = "allow-from";
      27           0 :   const uint32_t allowFromLen = ArrayLength(allowFrom) - 1;
      28             :   bool isAllowFrom =
      29           0 :     StringHead(aPolicy, allowFromLen).LowerCaseEqualsLiteral(allowFrom);
      30             : 
      31             :   // return early if header does not have one of the values with meaning
      32           0 :   if (!aPolicy.LowerCaseEqualsLiteral("deny") &&
      33           0 :       !aPolicy.LowerCaseEqualsLiteral("sameorigin") &&
      34           0 :       !isAllowFrom) {
      35           0 :     return true;
      36             :   }
      37             : 
      38           0 :   nsCOMPtr<nsIURI> uri;
      39           0 :   aHttpChannel->GetURI(getter_AddRefs(uri));
      40             : 
      41             :   // XXXkhuey when does this happen?  Is returning true safe here?
      42           0 :   if (!aDocShell) {
      43           0 :     return true;
      44             :   }
      45             : 
      46             :   // We need to check the location of this window and the location of the top
      47             :   // window, if we're not the top.  X-F-O: SAMEORIGIN requires that the
      48             :   // document must be same-origin with top window.  X-F-O: DENY requires that
      49             :   // the document must never be framed.
      50           0 :   nsCOMPtr<nsPIDOMWindowOuter> thisWindow = aDocShell->GetWindow();
      51             :   // If we don't have DOMWindow there is no risk of clickjacking
      52           0 :   if (!thisWindow) {
      53           0 :     return true;
      54             :   }
      55             : 
      56             :   // GetScriptableTop, not GetTop, because we want this to respect
      57             :   // <iframe mozbrowser> boundaries.
      58           0 :   nsCOMPtr<nsPIDOMWindowOuter> topWindow = thisWindow->GetScriptableTop();
      59             : 
      60             :   // if the document is in the top window, it's not in a frame.
      61           0 :   if (thisWindow == topWindow) {
      62           0 :     return true;
      63             :   }
      64             : 
      65             :   // Find the top docshell in our parent chain that doesn't have the system
      66             :   // principal and use it for the principal comparison.  Finding the top
      67             :   // content-type docshell doesn't work because some chrome documents are
      68             :   // loaded in content docshells (see bug 593387).
      69             :   nsCOMPtr<nsIDocShellTreeItem> thisDocShellItem(
      70           0 :     do_QueryInterface(static_cast<nsIDocShell*>(aDocShell)));
      71           0 :   nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem;
      72           0 :   nsCOMPtr<nsIDocShellTreeItem> curDocShellItem = thisDocShellItem;
      73           0 :   nsCOMPtr<nsIDocument> topDoc;
      74             :   nsresult rv;
      75             :   nsCOMPtr<nsIScriptSecurityManager> ssm =
      76           0 :     do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
      77           0 :   if (!ssm) {
      78           0 :     MOZ_CRASH();
      79             :   }
      80             : 
      81             :   // Traverse up the parent chain and stop when we see a docshell whose
      82             :   // parent has a system principal, or a docshell corresponding to
      83             :   // <iframe mozbrowser>.
      84           0 :   while (NS_SUCCEEDED(
      85           0 :            curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem))) &&
      86           0 :          parentDocShellItem) {
      87           0 :     nsCOMPtr<nsIDocShell> curDocShell = do_QueryInterface(curDocShellItem);
      88           0 :     if (curDocShell && curDocShell->GetIsMozBrowser()) {
      89           0 :       break;
      90             :     }
      91             : 
      92           0 :     bool system = false;
      93           0 :     topDoc = parentDocShellItem->GetDocument();
      94           0 :     if (topDoc) {
      95           0 :       if (NS_SUCCEEDED(
      96           0 :             ssm->IsSystemPrincipal(topDoc->NodePrincipal(), &system)) &&
      97             :           system) {
      98             :         // Found a system-principled doc: last docshell was top.
      99           0 :         break;
     100             :       }
     101             :     } else {
     102           0 :       return false;
     103             :     }
     104           0 :     curDocShellItem = parentDocShellItem;
     105             :   }
     106             : 
     107             :   // If this document has the top non-SystemPrincipal docshell it is not being
     108             :   // framed or it is being framed by a chrome document, which we allow.
     109           0 :   if (curDocShellItem == thisDocShellItem) {
     110           0 :     return true;
     111             :   }
     112             : 
     113             :   // If the value of the header is DENY, and the previous condition is
     114             :   // not met (current docshell is not the top docshell), prohibit the
     115             :   // load.
     116           0 :   if (aPolicy.LowerCaseEqualsLiteral("deny")) {
     117           0 :     ReportXFOViolation(curDocShellItem, uri, eDENY);
     118           0 :     return false;
     119             :   }
     120             : 
     121           0 :   topDoc = curDocShellItem->GetDocument();
     122           0 :   nsCOMPtr<nsIURI> topUri;
     123           0 :   topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri));
     124             : 
     125             :   // If the X-Frame-Options value is SAMEORIGIN, then the top frame in the
     126             :   // parent chain must be from the same origin as this document.
     127           0 :   if (aPolicy.LowerCaseEqualsLiteral("sameorigin")) {
     128           0 :     rv = ssm->CheckSameOriginURI(uri, topUri, true);
     129           0 :     if (NS_FAILED(rv)) {
     130           0 :       ReportXFOViolation(curDocShellItem, uri, eSAMEORIGIN);
     131           0 :       return false; /* wasn't same-origin */
     132             :     }
     133             :   }
     134             : 
     135             :   // If the X-Frame-Options value is "allow-from [uri]", then the top
     136             :   // frame in the parent chain must be from that origin
     137           0 :   if (isAllowFrom) {
     138           0 :     if (aPolicy.Length() == allowFromLen ||
     139           0 :         (aPolicy[allowFromLen] != ' ' &&
     140           0 :          aPolicy[allowFromLen] != '\t')) {
     141           0 :       ReportXFOViolation(curDocShellItem, uri, eALLOWFROM);
     142           0 :       return false;
     143             :     }
     144           0 :     rv = NS_NewURI(getter_AddRefs(uri), Substring(aPolicy, allowFromLen));
     145           0 :     if (NS_FAILED(rv)) {
     146           0 :       return false;
     147             :     }
     148             : 
     149           0 :     rv = ssm->CheckSameOriginURI(uri, topUri, true);
     150           0 :     if (NS_FAILED(rv)) {
     151           0 :       ReportXFOViolation(curDocShellItem, uri, eALLOWFROM);
     152           0 :       return false;
     153             :     }
     154             :   }
     155             : 
     156           0 :   return true;
     157             : }
     158             : 
     159             : // Ignore x-frame-options if CSP with frame-ancestors exists
     160             : static bool
     161           3 : ShouldIgnoreFrameOptions(nsIChannel* aChannel, nsIPrincipal* aPrincipal)
     162             : {
     163           3 :   NS_ENSURE_TRUE(aChannel, false);
     164           3 :   NS_ENSURE_TRUE(aPrincipal, false);
     165             : 
     166           6 :   nsCOMPtr<nsIContentSecurityPolicy> csp;
     167           3 :   aPrincipal->GetCsp(getter_AddRefs(csp));
     168           3 :   if (!csp) {
     169             :     // if there is no CSP, then there is nothing to do here
     170           3 :     return false;
     171             :   }
     172             : 
     173           0 :   bool enforcesFrameAncestors = false;
     174           0 :   csp->GetEnforcesFrameAncestors(&enforcesFrameAncestors);
     175           0 :   if (!enforcesFrameAncestors) {
     176             :     // if CSP does not contain frame-ancestors, then there
     177             :     // is nothing to do here.
     178           0 :     return false;
     179             :   }
     180             : 
     181             :   // log warning to console that xfo is ignored because of CSP
     182           0 :   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
     183           0 :   uint64_t innerWindowID = loadInfo ? loadInfo->GetInnerWindowID() : 0;
     184             :   const char16_t* params[] = { u"x-frame-options",
     185           0 :                                u"frame-ancestors" };
     186           0 :   CSP_LogLocalizedStr(u"IgnoringSrcBecauseOfDirective",
     187           0 :                       params, ArrayLength(params),
     188           0 :                       EmptyString(), // no sourcefile
     189           0 :                       EmptyString(), // no scriptsample
     190             :                       0,             // no linenumber
     191             :                       0,             // no columnnumber
     192             :                       nsIScriptError::warningFlag,
     193           0 :                       "CSP", innerWindowID);
     194             : 
     195           0 :   return true;
     196             : }
     197             : 
     198             : // Check if X-Frame-Options permits this document to be loaded as a subdocument.
     199             : // This will iterate through and check any number of X-Frame-Options policies
     200             : // in the request (comma-separated in a header, multiple headers, etc).
     201             : /* static */ bool
     202          25 : FramingChecker::CheckFrameOptions(nsIChannel* aChannel,
     203             :                                   nsIDocShell* aDocShell,
     204             :                                   nsIPrincipal* aPrincipal)
     205             : {
     206          25 :   if (!aChannel || !aDocShell) {
     207          22 :     return true;
     208             :   }
     209             : 
     210           3 :   if (ShouldIgnoreFrameOptions(aChannel, aPrincipal)) {
     211           0 :     return true;
     212             :   }
     213             : 
     214             :   nsresult rv;
     215           6 :   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
     216           3 :   if (!httpChannel) {
     217             :     // check if it is hiding in a multipart channel
     218           2 :     rv = nsDocShell::Cast(aDocShell)->GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
     219           2 :     if (NS_FAILED(rv)) {
     220           0 :       return false;
     221             :     }
     222             :   }
     223             : 
     224           3 :   if (!httpChannel) {
     225           2 :     return true;
     226             :   }
     227             : 
     228           2 :   nsAutoCString xfoHeaderCValue;
     229           3 :   Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
     230           2 :                                            xfoHeaderCValue);
     231           2 :   NS_ConvertUTF8toUTF16 xfoHeaderValue(xfoHeaderCValue);
     232             : 
     233             :   // if no header value, there's nothing to do.
     234           1 :   if (xfoHeaderValue.IsEmpty()) {
     235           1 :     return true;
     236             :   }
     237             : 
     238             :   // iterate through all the header values (usually there's only one, but can
     239             :   // be many.  If any want to deny the load, deny the load.
     240           0 :   nsCharSeparatedTokenizer tokenizer(xfoHeaderValue, ',');
     241           0 :   while (tokenizer.hasMoreTokens()) {
     242           0 :     const nsAString& tok = tokenizer.nextToken();
     243           0 :     if (!CheckOneFrameOptionsPolicy(httpChannel, tok, aDocShell)) {
     244             :       // cancel the load and display about:blank
     245           0 :       httpChannel->Cancel(NS_BINDING_ABORTED);
     246           0 :       if (aDocShell) {
     247           0 :         nsCOMPtr<nsIWebNavigation> webNav(do_QueryObject(aDocShell));
     248           0 :         if (webNav) {
     249           0 :           nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
     250             :           nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo
     251           0 :             ? loadInfo->TriggeringPrincipal()
     252           0 :             : nsContentUtils::GetSystemPrincipal();
     253           0 :           webNav->LoadURI(u"about:blank",
     254             :                           0, nullptr, nullptr, nullptr,
     255           0 :                           triggeringPrincipal);
     256             :         }
     257             :       }
     258           0 :       return false;
     259             :     }
     260             :   }
     261             : 
     262           0 :   return true;
     263             : }
     264             : 
     265             : /* static */ void
     266           0 : FramingChecker::ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem,
     267             :                                    nsIURI* aThisURI,
     268             :                                    XFOHeader aHeader)
     269             : {
     270           0 :   MOZ_ASSERT(aTopDocShellItem, "Need a top docshell");
     271             : 
     272           0 :   nsCOMPtr<nsPIDOMWindowOuter> topOuterWindow = aTopDocShellItem->GetWindow();
     273           0 :   if (!topOuterWindow) {
     274           0 :     return;
     275             :   }
     276             : 
     277           0 :   nsPIDOMWindowInner* topInnerWindow = topOuterWindow->GetCurrentInnerWindow();
     278           0 :   if (!topInnerWindow) {
     279           0 :     return;
     280             :   }
     281             : 
     282           0 :   nsCOMPtr<nsIURI> topURI;
     283             : 
     284           0 :   nsCOMPtr<nsIDocument> document = aTopDocShellItem->GetDocument();
     285           0 :   nsresult rv = document->NodePrincipal()->GetURI(getter_AddRefs(topURI));
     286           0 :   if (NS_FAILED(rv)) {
     287           0 :     return;
     288             :   }
     289             : 
     290           0 :   if (!topURI) {
     291           0 :     return;
     292             :   }
     293             : 
     294           0 :   nsCString topURIString;
     295           0 :   nsCString thisURIString;
     296             : 
     297           0 :   rv = topURI->GetSpec(topURIString);
     298           0 :   if (NS_FAILED(rv)) {
     299           0 :     return;
     300             :   }
     301             : 
     302           0 :   rv = aThisURI->GetSpec(thisURIString);
     303           0 :   if (NS_FAILED(rv)) {
     304           0 :     return;
     305             :   }
     306             : 
     307             :   nsCOMPtr<nsIConsoleService> consoleService =
     308           0 :     do_GetService(NS_CONSOLESERVICE_CONTRACTID);
     309             :   nsCOMPtr<nsIScriptError> errorObject =
     310           0 :     do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
     311             : 
     312           0 :   if (!consoleService || !errorObject) {
     313           0 :     return;
     314             :   }
     315             : 
     316           0 :   nsString msg = NS_LITERAL_STRING("Load denied by X-Frame-Options: ");
     317           0 :   msg.Append(NS_ConvertUTF8toUTF16(thisURIString));
     318             : 
     319           0 :   switch (aHeader) {
     320             :     case eDENY:
     321           0 :       msg.AppendLiteral(" does not permit framing.");
     322           0 :       break;
     323             :     case eSAMEORIGIN:
     324           0 :       msg.AppendLiteral(" does not permit cross-origin framing.");
     325           0 :       break;
     326             :     case eALLOWFROM:
     327           0 :       msg.AppendLiteral(" does not permit framing by ");
     328           0 :       msg.Append(NS_ConvertUTF8toUTF16(topURIString));
     329           0 :       msg.Append('.');
     330           0 :       break;
     331             :   }
     332             : 
     333           0 :   rv = errorObject->InitWithWindowID(msg, EmptyString(), EmptyString(), 0, 0,
     334             :                                      nsIScriptError::errorFlag,
     335             :                                      "X-Frame-Options",
     336           0 :                                      topInnerWindow->WindowID());
     337           0 :   if (NS_FAILED(rv)) {
     338           0 :     return;
     339             :   }
     340             : 
     341           0 :   consoleService->LogMessage(errorObject);
     342             : }

Generated by: LCOV version 1.13