| 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());
|
| }
|
|
|