Index: third_party/WebKit/Source/core/frame/LocalFrame.cpp |
diff --git a/third_party/WebKit/Source/core/frame/LocalFrame.cpp b/third_party/WebKit/Source/core/frame/LocalFrame.cpp |
index dc18be7d650e04d21e1cc0a47fcbbd81c2ae5b36..3eefa4fb1b4e989b32a932a7221951863416ce44 100644 |
--- a/third_party/WebKit/Source/core/frame/LocalFrame.cpp |
+++ b/third_party/WebKit/Source/core/frame/LocalFrame.cpp |
@@ -80,6 +80,7 @@ |
#include "core/svg/SVGDocumentExtensions.h" |
#include "core/timing/Performance.h" |
#include "platform/DragImage.h" |
+#include "platform/Histogram.h" |
#include "platform/PluginScriptForbiddenScope.h" |
#include "platform/RuntimeEnabledFeatures.h" |
#include "platform/ScriptForbiddenScope.h" |
@@ -919,6 +920,213 @@ void LocalFrame::ScheduleVisualUpdateUnlessThrottled() { |
GetPage()->Animator().ScheduleVisualUpdate(this); |
} |
+bool LocalFrame::CanNavigate(const Frame& target_frame) { |
+ String error_reason; |
+ const bool is_allowed_navigation = |
+ CanNavigateWithoutFramebusting(target_frame, error_reason); |
+ const bool sandboxed = |
+ GetSecurityContext()->GetSandboxFlags() != kSandboxNone; |
+ const bool has_user_gesture = HasReceivedUserGesture(); |
+ |
+ // Top navigation in sandbox with or w/o 'allow-top-navigation'. |
+ if (target_frame != this && sandboxed && target_frame == Tree().Top()) { |
+ UseCounter::Count(this, UseCounter::kTopNavInSandbox); |
+ if (!has_user_gesture) { |
+ UseCounter::Count(this, UseCounter::kTopNavInSandboxWithoutGesture); |
+ } |
+ } |
+ |
+ // Top navigation w/o sandbox or in sandbox with 'allow-top-navigation'. |
+ if (target_frame != this && |
+ !GetSecurityContext()->IsSandboxed(kSandboxTopNavigation) && |
+ target_frame == Tree().Top()) { |
+ DEFINE_STATIC_LOCAL(EnumerationHistogram, framebust_histogram, |
+ ("WebCore.Framebust", 4)); |
+ const unsigned kUserGestureBit = 0x1; |
+ const unsigned kAllowedBit = 0x2; |
+ unsigned framebust_params = 0; |
+ |
+ if (has_user_gesture) |
+ framebust_params |= kUserGestureBit; |
+ |
+ UseCounter::Count(this, UseCounter::kTopNavigationFromSubFrame); |
+ if (sandboxed) { // Sandboxed with 'allow-top-navigation'. |
+ UseCounter::Count(this, UseCounter::kTopNavInSandboxWithPerm); |
+ if (!has_user_gesture) { |
+ UseCounter::Count(this, |
+ UseCounter::kTopNavInSandboxWithPermButNoGesture); |
+ } |
+ } |
+ |
+ if (is_allowed_navigation) |
+ framebust_params |= kAllowedBit; |
+ framebust_histogram.Count(framebust_params); |
+ if (has_user_gesture || is_allowed_navigation) |
+ return true; |
+ // Frame-busting used to be generally allowed in most situations, but may |
+ // now blocked if the document initiating the navigation has never received |
+ // a user gesture. |
+ if (!RuntimeEnabledFeatures:: |
+ framebustingNeedsSameOriginOrUserGestureEnabled()) { |
+ String target_frame_description = |
+ target_frame.IsLocalFrame() ? "with URL '" + |
+ ToLocalFrame(target_frame) |
+ .GetDocument() |
+ ->Url() |
+ .GetString() + |
+ "'" |
+ : "with origin '" + |
+ target_frame.GetSecurityContext() |
+ ->GetSecurityOrigin() |
+ ->ToString() + |
+ "'"; |
+ String message = "Frame with URL '" + GetDocument()->Url().GetString() + |
+ "' attempted to navigate its top-level window " + |
+ target_frame_description + |
+ ". Navigating the top-level window from a cross-origin " |
+ "iframe will soon require that the iframe has received " |
+ "a user gesture. See " |
+ "https://www.chromestatus.com/features/" |
+ "5851021045661696."; |
+ PrintNavigationWarning(message); |
+ return true; |
+ } |
+ error_reason = |
+ "The frame attempting navigation is targeting its top-level window, " |
+ "but is neither same-origin with its target nor has it received a " |
+ "user gesture. See " |
+ "https://www.chromestatus.com/features/5851021045661696."; |
+ PrintNavigationErrorMessage(target_frame, error_reason.Latin1().data()); |
+ GetNavigationScheduler().SchedulePageBlock(GetDocument(), |
+ ResourceError::ACCESS_DENIED); |
+ return false; |
+ } |
+ if (!is_allowed_navigation && !error_reason.IsNull()) |
+ PrintNavigationErrorMessage(target_frame, error_reason.Latin1().data()); |
+ return is_allowed_navigation; |
+} |
+ |
+static bool CanAccessAncestor(const SecurityOrigin& active_security_origin, |
+ const Frame* target_frame) { |
+ // targetFrame can be 0 when we're trying to navigate a top-level frame |
+ // that has a 0 opener. |
+ if (!target_frame) |
+ return false; |
+ |
+ const bool is_local_active_origin = active_security_origin.IsLocal(); |
+ for (const Frame* ancestor_frame = target_frame; ancestor_frame; |
+ ancestor_frame = ancestor_frame->Tree().Parent()) { |
+ const SecurityOrigin* ancestor_security_origin = |
+ ancestor_frame->GetSecurityContext()->GetSecurityOrigin(); |
+ if (active_security_origin.CanAccess(ancestor_security_origin)) |
+ return true; |
+ |
+ // Allow file URL descendant navigation even when |
+ // allowFileAccessFromFileURLs is false. |
+ // FIXME: It's a bit strange to special-case local origins here. Should we |
+ // be doing something more general instead? |
+ if (is_local_active_origin && ancestor_security_origin->IsLocal()) |
+ return true; |
+ } |
+ |
+ return false; |
+} |
+ |
+bool LocalFrame::CanNavigateWithoutFramebusting(const Frame& target_frame, |
+ String& reason) { |
+ if (&target_frame == this) |
+ return true; |
+ |
+ if (GetSecurityContext()->IsSandboxed(kSandboxNavigation)) { |
+ if (!target_frame.Tree().IsDescendantOf(this) && |
+ !target_frame.IsMainFrame()) { |
+ reason = |
+ "The frame attempting navigation is sandboxed, and is therefore " |
+ "disallowed from navigating its ancestors."; |
+ return false; |
+ } |
+ |
+ // Sandboxed frames can also navigate popups, if the |
+ // 'allow-sandbox-escape-via-popup' flag is specified, or if |
+ // 'allow-popups' flag is specified, or if the |
+ if (target_frame.IsMainFrame() && target_frame != Tree().Top() && |
+ GetSecurityContext()->IsSandboxed( |
+ kSandboxPropagatesToAuxiliaryBrowsingContexts) && |
+ (GetSecurityContext()->IsSandboxed(kSandboxPopups) || |
+ target_frame.Client()->Opener() != this)) { |
+ reason = |
+ "The frame attempting navigation is sandboxed and is trying " |
+ "to navigate a popup, but is not the popup's opener and is not " |
+ "set to propagate sandboxing to popups."; |
+ return false; |
+ } |
+ |
+ // Top navigation is forbidden unless opted-in. allow-top-navigation or |
+ // allow-top-navigation-by-user-activation will also skips origin checks. |
+ if (target_frame == Tree().Top()) { |
+ if (GetSecurityContext()->IsSandboxed(kSandboxTopNavigation) && |
+ GetSecurityContext()->IsSandboxed( |
+ kSandboxTopNavigationByUserActivation)) { |
+ reason = |
+ "The frame attempting navigation of the top-level window is " |
+ "sandboxed, but the flag of 'allow-top-navigation' or " |
+ "'allow-top-navigation-by-user-activation' is not set."; |
+ return false; |
+ } |
+ if (GetSecurityContext()->IsSandboxed(kSandboxTopNavigation) && |
+ !GetSecurityContext()->IsSandboxed( |
+ kSandboxTopNavigationByUserActivation) && |
+ !UserGestureIndicator::ProcessingUserGesture()) { |
+ // With only 'allow-top-navigation-by-user-activation' (but not |
+ // 'allow-top-navigation'), top navigation requires a user gesture. |
+ reason = |
+ "The frame attempting navigation of the top-level window is " |
+ "sandboxed with the 'allow-top-navigation-by-user-activation' " |
+ "flag, but has no user activation (aka gesture). See " |
+ "https://www.chromestatus.com/feature/5629582019395584."; |
+ return false; |
+ } |
+ return true; |
+ } |
+ } |
+ |
+ DCHECK(GetSecurityContext()->GetSecurityOrigin()); |
+ SecurityOrigin& origin = *GetSecurityContext()->GetSecurityOrigin(); |
+ |
+ // This is the normal case. A document can navigate its decendant frames, |
+ // or, more generally, a document can navigate a frame if the document is |
+ // in the same origin as any of that frame's ancestors (in the frame |
+ // hierarchy). |
+ // |
+ // See http://www.adambarth.com/papers/2008/barth-jackson-mitchell.pdf for |
+ // historical information about this security check. |
+ if (CanAccessAncestor(origin, &target_frame)) |
+ return true; |
+ |
+ // Top-level frames are easier to navigate than other frames because they |
+ // display their URLs in the address bar (in most browsers). However, there |
+ // are still some restrictions on navigation to avoid nuisance attacks. |
+ // Specifically, a document can navigate a top-level frame if that frame |
+ // opened the document or if the document is the same-origin with any of |
+ // the top-level frame's opener's ancestors (in the frame hierarchy). |
+ // |
+ // In both of these cases, the document performing the navigation is in |
+ // some way related to the frame being navigate (e.g., by the "opener" |
+ // and/or "parent" relation). Requiring some sort of relation prevents a |
+ // document from navigating arbitrary, unrelated top-level frames. |
+ if (!target_frame.Tree().Parent()) { |
+ if (target_frame == Client()->Opener()) |
+ return true; |
+ if (CanAccessAncestor(origin, target_frame.Client()->Opener())) |
+ return true; |
+ } |
+ |
+ reason = |
+ "The frame attempting navigation is neither same-origin with the target, " |
+ "nor is it the target's parent or opener."; |
+ return false; |
+} |
+ |
LocalFrameClient* LocalFrame::Client() const { |
return static_cast<LocalFrameClient*>(Frame::Client()); |
} |