Commit message: None
CVE: CVE-2016-1697
CWE: CWE-284
Vulnerability description: The FrameLoader::startLoad function in WebKit/Source/core/loader/FrameLoader.cpp in Blink, as used in Google Chrome before 51.0.2704.79, does not prevent frame navigations during DocumentLoader detach operations, which allows remote attackers to bypass the Same Origin Policy via crafted JavaScript code.

Project: Chrome

void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
{
    dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
}

Project: Chrome

static void appendServerMapMousePosition(StringBuilder& url, Event* event)
{
    if (!event->isMouseEvent())
        return;

    ASSERT(event->target());
    Node* target = event->target()->toNode();
    ASSERT(target);
    if (!isHTMLImageElement(*target))
        return;

    HTMLImageElement& imageElement = toHTMLImageElement(*target);
    if (!imageElement.isServerMap())
        return;

    LayoutObject* layoutObject = imageElement.layoutObject();
    if (!layoutObject || !layoutObject->isBox())
        return;

    LayoutPoint mapPoint(layoutObject->absoluteToLocal(FloatPoint(toMouseEvent(event)->absoluteLocation()), UseTransforms));

    mapPoint -= toLayoutBox(layoutObject)->contentBoxOffset();

    float scaleFactor = 1 / layoutObject->style()->effectiveZoom();
    mapPoint.scale(scaleFactor, scaleFactor);

    IntPoint clampedPoint(roundedIntPoint(mapPoint));
    clampedPoint.clampNegativeToZero();

    url.append('?');
    url.appendNumber(clampedPoint.x());
    url.append(',');
    url.appendNumber(clampedPoint.y());
}

Project: Chrome

bool HTMLAnchorElement::canStartSelection() const
{
    if (!isLink())
        return HTMLElement::canStartSelection();
    return hasEditableStyle();
}

Project: Chrome

HTMLAnchorElement* HTMLAnchorElement::create(Document& document)
{
    return new HTMLAnchorElement(aTag, document);
}

Project: Chrome

void HTMLAnchorElement::defaultEventHandler(Event* event)
{
    if (isLink()) {
        if (focused() && isEnterKeyKeydownEvent(event) && isLiveLink()) {
            event->setDefaultHandled();
            dispatchSimulatedClick(event);
            return;
        }

        if (isLinkClick(event) && isLiveLink()) {
            handleClick(event);
            return;
        }
    }

    HTMLElement::defaultEventHandler(event);
}

Project: Chrome

void HTMLAnchorElement::dispatchBlurEvent(Element* newFocusedElement, WebFocusType type, InputDeviceCapabilities* sourceCapabilities)
{
    if (type != WebFocusTypePage)
        m_wasFocusedByMouse = false;
    HTMLElement::dispatchBlurEvent(newFocusedElement, type, sourceCapabilities);
}

Project: Chrome

void HTMLAnchorElement::dispatchFocusEvent(Element* oldFocusedElement, WebFocusType type, InputDeviceCapabilities* sourceCapabilities)
{
    if (type != WebFocusTypePage)
        m_wasFocusedByMouse = type == WebFocusTypeMouse;
    HTMLElement::dispatchFocusEvent(oldFocusedElement, type, sourceCapabilities);
}

Project: Chrome

bool HTMLAnchorElement::draggable() const
{
    const AtomicString& value = getAttribute(draggableAttr);
    if (equalIgnoringCase(value, "true"))
        return true;
    if (equalIgnoringCase(value, "false"))
        return false;
    return hasAttribute(hrefAttr);
}

Project: Chrome

bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
{
    return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
}

Project: Chrome

bool HTMLAnchorElement::hasRel(uint32_t relation) const
{
    return m_linkRelations & relation;
}

Project: Chrome

KURL HTMLAnchorElement::href() const
{
    return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
}

Project: Chrome

String HTMLAnchorElement::input() const
{
    return getAttribute(hrefAttr);
}

Project: Chrome

Node::InsertionNotificationRequest HTMLAnchorElement::insertedInto(ContainerNode* insertionPoint)
{
    InsertionNotificationRequest request = HTMLElement::insertedInto(insertionPoint);
    logAddElementIfIsolatedWorldAndInDocument("a", hrefAttr);
    return request;
}

Project: Chrome

bool isEnterKeyKeydownEvent(Event* event)
{
    return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter" && !toKeyboardEvent(event)->repeat();
}

Project: Chrome

bool HTMLAnchorElement::isInteractiveContent() const
{
    return isLink();
}

Project: Chrome

bool isLinkClick(Event* event)
{
    return event->type() == EventTypeNames::click && (!event->isMouseEvent() || (toMouseEvent(event)->button() != RightButton && toMouseEvent(event)->detail() <= 1));
}

Project: Chrome

bool HTMLAnchorElement::isLiveLink() const
{
    return isLink() && !hasEditableStyle();
}

Project: Chrome

bool HTMLAnchorElement::isMouseFocusable() const
{
    if (isLink())
        return supportsFocus();

    return HTMLElement::isMouseFocusable();
}

Project: Chrome

bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
{
    return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
}

Project: Chrome

bool HTMLAnchorElement::matchesEnabledPseudoClass() const
{
    return isLink();
}

Project: Chrome

const AtomicString& HTMLAnchorElement::name() const
{
    return getNameAttribute();
}

Project: Chrome

void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& value)
{
    if (name == hrefAttr) {
        bool wasLink = isLink();
        setIsLink(!value.isNull());
        if (wasLink || isLink()) {
            pseudoStateChanged(CSSSelector::PseudoLink);
            pseudoStateChanged(CSSSelector::PseudoVisited);
            pseudoStateChanged(CSSSelector::PseudoAnyLink);
        }
        if (wasLink && !isLink() && treeScope().adjustedFocusedElement() == this) {
            document().setNeedsFocusedElementCheck();
        }
        if (isLink()) {
            String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
            if (document().isDNSPrefetchEnabled()) {
                if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
                    prefetchDNS(document().completeURL(parsedURL).host());
            }
        }
        invalidateCachedVisitedLinkHash();
        logUpdateAttributeIfIsolatedWorldAndInDocument("a", hrefAttr, oldValue, value);
    } else if (name == nameAttr || name == titleAttr) {
    } else if (name == relAttr) {
        setRel(value);
    } else {
        HTMLElement::parseAttribute(name, oldValue, value);
    }
}

Project: Chrome

void HTMLAnchorElement::setActive(bool down)
{
    if (hasEditableStyle())
        return;

    ContainerNode::setActive(down);
}

Project: Chrome

void HTMLAnchorElement::setHref(const AtomicString& value)
{
    setAttribute(hrefAttr, value);
}

Project: Chrome

void HTMLAnchorElement::setRel(const AtomicString& value)
{
    m_linkRelations = 0;
    SpaceSplitString newLinkRelations(value, SpaceSplitString::ShouldFoldCase);
    if (newLinkRelations.contains("noreferrer"))
        m_linkRelations |= RelationNoReferrer;
    if (newLinkRelations.contains("noopener"))
        m_linkRelations |= RelationNoOpener;
}

Project: Chrome

void HTMLAnchorElement::setURL(const KURL& url)
{
    setHref(AtomicString(url.getString()));
}

Project: Chrome

bool HTMLAnchorElement::supportsFocus() const
{
    if (hasEditableStyle())
        return HTMLElement::supportsFocus();
    return isLink() || HTMLElement::supportsFocus();
}

Project: Chrome

short HTMLAnchorElement::tabIndex() const
{
    return Element::tabIndex();
}

Project: Chrome

KURL HTMLAnchorElement::url() const
{
    return href();
}

Project: Chrome

bool HTMLAnchorElement::willRespondToMouseClickEvents()
{
    return isLink() || HTMLElement::willRespondToMouseClickEvents();
}

Project: Chrome

HTMLAnchorElement::~HTMLAnchorElement()
{
}

Project: Chrome

void FrameLoader::clear()
{
    if (m_stateMachine.creatingInitialEmptyDocument())
        return;

    m_frame->editor().clear();
    m_frame->document()->removeFocusedElementOfSubtree(m_frame->document());
    m_frame->selection().prepareForDestruction();
    m_frame->eventHandler().clear();
    if (m_frame->view())
        m_frame->view()->clear();

    m_frame->script().enableEval();

    m_frame->navigationScheduler().cancel();

    m_checkTimer.stop();

    if (m_stateMachine.isDisplayingInitialEmptyDocument())
        m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad);

    takeObjectSnapshot();
}

Project: Chrome

SecurityContext::InsecureRequestsPolicy FrameLoader::getInsecureRequestsPolicy() const
{
    Frame* parentFrame = m_frame->tree().parent();
    if (!parentFrame)
        return SecurityContext::InsecureRequestsDoNotUpgrade;

    if (!parentFrame->isLocalFrame())
        return SecurityContext::InsecureRequestsDoNotUpgrade;

    ASSERT(toLocalFrame(parentFrame)->document());
    return toLocalFrame(parentFrame)->document()->getInsecureRequestsPolicy();
}

Project: Chrome

void FrameLoader::load(const FrameLoadRequest& passedRequest, FrameLoadType frameLoadType,
    HistoryItem* historyItem, HistoryLoadType historyLoadType)
{
    ASSERT(m_frame->document());

    if (!m_frame->isNavigationAllowed())
        return;

    if (m_inStopAllLoaders)
        return;

    if (m_frame->page()->defersLoading() && isBackForwardLoadType(frameLoadType)) {
        m_deferredHistoryLoad = DeferredHistoryLoad::create(passedRequest.resourceRequest(), historyItem, frameLoadType, historyLoadType);
        return;
    }

    FrameLoadRequest request(passedRequest);
    request.resourceRequest().setHasUserGesture(UserGestureIndicator::processingUserGesture());

    if (!prepareRequestForThisFrame(request))
        return;

    Frame* targetFrame = request.form() ? nullptr : m_frame->findFrameForNavigation(AtomicString(request.frameName()), *m_frame);

    if (isBackForwardLoadType(frameLoadType)) {
        ASSERT(historyItem);
        m_provisionalItem = historyItem;
    }

    if (targetFrame && targetFrame != m_frame) {
        bool wasInSamePage = targetFrame->page() == m_frame->page();

        request.setFrameName("_self");
        targetFrame->navigate(request);
        Page* page = targetFrame->page();
        if (!wasInSamePage && page)
            page->chromeClient().focus();
        return;
    }

    setReferrerForFrameRequest(request.resourceRequest(), request.getShouldSendReferrer(), request.originDocument());

    FrameLoadType newLoadType = (frameLoadType == FrameLoadTypeStandard) ?
        determineFrameLoadType(request) : frameLoadType;
    NavigationPolicy policy = navigationPolicyForRequest(request);
    if (shouldOpenInNewWindow(targetFrame, request, policy)) {
        if (policy == NavigationPolicyDownload) {
            client()->loadURLExternally(request.resourceRequest(), NavigationPolicyDownload, String(), false);
        } else {
            request.resourceRequest().setFrameType(WebURLRequest::FrameTypeAuxiliary);
            createWindowForRequest(request, *m_frame, policy, request.getShouldSendReferrer(), request.getShouldSetOpener());
        }
        return;
    }

    const KURL& url = request.resourceRequest().url();
    bool sameDocumentHistoryNavigation =
        isBackForwardLoadType(newLoadType) && historyLoadType == HistorySameDocumentLoad;
    bool sameDocumentNavigation = policy == NavigationPolicyCurrentTab
        && shouldPerformFragmentNavigation(
            request.form(), request.resourceRequest().httpMethod(), newLoadType, url);

    if (sameDocumentHistoryNavigation || sameDocumentNavigation) {
        ASSERT(historyItem || !sameDocumentHistoryNavigation);
        RefPtr<SerializedScriptValue> stateObject = sameDocumentHistoryNavigation ?
            historyItem->stateObject() : nullptr;

        if (!sameDocumentHistoryNavigation) {
            m_documentLoader->setNavigationType(determineNavigationType(
                newLoadType, false, request.triggeringEvent()));
            if (shouldTreatURLAsSameAsCurrent(url))
                newLoadType = FrameLoadTypeReplaceCurrentItem;
        }

        loadInSameDocument(url, stateObject, newLoadType, historyLoadType, request.clientRedirect(), request.originDocument());
        return;
    }

    startLoad(request, newLoadType, policy);
}

Project: Chrome

void FrameLoader::setReferrerForFrameRequest(ResourceRequest& request, ShouldSendReferrer shouldSendReferrer, Document* originDocument)
{
    if (!originDocument)
        return;
    if (request.didSetHTTPReferrer())
        return;
    if (shouldSendReferrer == NeverSendReferrer)
        return;

    Referrer referrer = SecurityPolicy::generateReferrer(originDocument->getReferrerPolicy(), request.url(), originDocument->outgoingReferrer());

    request.setHTTPReferrer(referrer);
    RefPtr<SecurityOrigin> referrerOrigin = SecurityOrigin::createFromString(referrer.referrer);
    request.addHTTPOriginIfNeeded(referrerOrigin);
}

Project: Chrome

bool FrameLoader::shouldClose(bool isReload)
{
    Page* page = m_frame->page();
    if (!page || !page->chromeClient().canOpenBeforeUnloadConfirmPanel())
        return true;

    HeapVector<Member<LocalFrame>> targetFrames;
    targetFrames.append(m_frame);
    for (Frame* child = m_frame->tree().firstChild(); child; child = child->tree().traverseNext(m_frame)) {
        if (child->isLocalFrame())
            targetFrames.append(toLocalFrame(child));
    }

    bool shouldClose = false;
    {
        NavigationDisablerForBeforeUnload navigationDisabler;
        size_t i;

        bool didAllowNavigation = false;
        for (i = 0; i < targetFrames.size(); i++) {
            if (!targetFrames[i]->tree().isDescendantOf(m_frame))
                continue;
            if (!targetFrames[i]->document()->dispatchBeforeUnloadEvent(page->chromeClient(), isReload, didAllowNavigation))
                break;
        }

        if (i == targetFrames.size())
            shouldClose = true;
    }
    return shouldClose;
}

Project: Chrome

bool FrameLoader::shouldContinueForNavigationPolicy(const ResourceRequest& request, const SubstituteData& substituteData,
    DocumentLoader* loader, ContentSecurityPolicyDisposition shouldCheckMainWorldContentSecurityPolicy,
    NavigationType type, NavigationPolicy policy, bool replacesCurrentHistoryItem, bool isClientRedirect)
{
    if (request.url().isEmpty() || substituteData.isValid())
        return true;

    if (shouldCheckMainWorldContentSecurityPolicy == CheckContentSecurityPolicy) {
        Frame* parentFrame = m_frame->tree().parent();
        if (parentFrame) {
            ContentSecurityPolicy* parentPolicy = parentFrame->securityContext()->contentSecurityPolicy();
            ContentSecurityPolicy::RedirectStatus redirectStatus = request.followedRedirect()
                ? ContentSecurityPolicy::DidRedirect
                : ContentSecurityPolicy::DidNotRedirect;
            if (!parentPolicy->allowChildFrameFromSource(request.url(), redirectStatus)) {
                m_frame->document()->enforceSandboxFlags(SandboxOrigin);
                m_frame->owner()->dispatchLoad();
                return false;
            }
        }
    }

    bool isFormSubmission = type == NavigationTypeFormSubmitted || type == NavigationTypeFormResubmitted;
    if (isFormSubmission && !m_frame->document()->contentSecurityPolicy()->allowFormAction(request.url()))
        return false;

    policy = client()->decidePolicyForNavigation(request, loader, type, policy, replacesCurrentHistoryItem, isClientRedirect);
    if (policy == NavigationPolicyCurrentTab)
        return true;
    if (policy == NavigationPolicyIgnore)
        return false;
    if (policy == NavigationPolicyHandledByClient) {
        m_progressTracker->progressStarted();
        return false;
    }
    if (!LocalDOMWindow::allowPopUp(*m_frame) && !UserGestureIndicator::utilizeUserGesture())
        return false;
    client()->loadURLExternally(request, policy, String(), replacesCurrentHistoryItem);
    return false;
}

Project: Chrome

bool FrameLoader::shouldEnforceStrictMixedContentChecking() const
{
    Frame* parentFrame = m_frame->tree().parent();
    if (!parentFrame)
        return false;

    return parentFrame->securityContext()->shouldEnforceStrictMixedContentChecking();
}