diff --git a/content/base/src/nsCSPContext.cpp b/content/base/src/nsCSPContext.cpp index 09685f1f2001..91e5ffe5c814 100644 --- a/content/base/src/nsCSPContext.cpp +++ b/content/base/src/nsCSPContext.cpp @@ -26,6 +26,7 @@ #include "nsIPrincipal.h" #include "nsIPropertyBag2.h" #include "nsIScriptError.h" +#include "nsIWebNavigation.h" #include "nsIWritablePropertyBag2.h" #include "nsString.h" #include "prlog.h" @@ -439,11 +440,118 @@ nsCSPContext::SetRequestContext(nsIURI* aSelfURI, 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 ancestorsArray; + + nsCOMPtr ir(do_QueryInterface(aDocShell)); + nsCOMPtr treeItem(do_GetInterface(ir)); + nsCOMPtr parentTreeItem; + nsCOMPtr webNav; + nsCOMPtr currentURI; + nsCOMPtr 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 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; } diff --git a/content/base/src/nsCSPUtils.cpp b/content/base/src/nsCSPUtils.cpp index 6d4493a3461b..85c5e33c5008 100644 --- a/content/base/src/nsCSPUtils.cpp +++ b/content/base/src/nsCSPUtils.cpp @@ -636,6 +636,8 @@ CSP_DirectiveToContentType(enum CSPDirective aDir) 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: diff --git a/content/base/src/nsCSPUtils.h b/content/base/src/nsCSPUtils.h index 50be4b21337b..a0aeb41832bb 100644 --- a/content/base/src/nsCSPUtils.h +++ b/content/base/src/nsCSPUtils.h @@ -49,22 +49,24 @@ enum CSPDirective { 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) diff --git a/content/base/test/TestCSPParser.cpp b/content/base/test/TestCSPParser.cpp index 533ac37a5730..7e29b1f97b80 100644 --- a/content/base/test/TestCSPParser.cpp +++ b/content/base/test/TestCSPParser.cpp @@ -667,6 +667,28 @@ nsresult TestGoodGeneratedPolicies() { "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);