Commit message: None
CVE: CVE-2013-2857
CWE: CWE-416
Vulnerability description: Use-after-free vulnerability in Google Chrome before 27.0.1453.110 allows remote attackers to cause a denial of service or possibly have unspecified other impact via vectors related to the handling of images.

Project: Chrome

ImageLoader::ImageLoader(Element* element)
    : m_element(element)
    , m_image(0)
    , m_derefElementTimer(this, &ImageLoader::timerFired)
    , m_hasPendingBeforeLoadEvent(false)
    , m_hasPendingLoadEvent(false)
    , m_hasPendingErrorEvent(false)
    , m_imageComplete(true)
    , m_loadManually(false)
    , m_elementIsProtected(false)
{
}

Project: Chrome

static ImageEventSender& beforeLoadEventSender()
{
    DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().beforeloadEvent));
    return sender;
}

Project: Chrome

    static void checkConsistency(const WebCore::ImageLoader* p)
    {
        if (!p)
            return;
        ASSERT(p->element());
        ValueCheck<WebCore::Element*>::checkConsistency(p->element());
    }

Project: Chrome

inline void ImageLoader::clearFailedLoadURL()
{
    m_failedLoadURL = AtomicString();
}

Project: Chrome

void ImageLoader::dispatchPendingBeforeLoadEvent()
{
    if (!m_hasPendingBeforeLoadEvent)
        return;
    if (!m_image)
        return;
    if (!m_element->document()->attached())
        return;
    m_hasPendingBeforeLoadEvent = false;
    if (m_element->dispatchBeforeLoadEvent(m_image->url())) {
        updateRenderer();
        return;
    }
    if (m_image) {
        m_image->removeClient(this);
        m_image = 0;
    }

    loadEventSender().cancelEvent(this);
    m_hasPendingLoadEvent = false;
    
    if (m_element->hasTagName(HTMLNames::objectTag))
        static_cast<HTMLObjectElement*>(m_element)->renderFallbackContent();

    updatedHasPendingEvent();
}

Project: Chrome

void ImageLoader::dispatchPendingBeforeLoadEvents()
{
    beforeLoadEventSender().dispatchPendingEvents();
}

Project: Chrome

void ImageLoader::dispatchPendingErrorEvent()
{
    if (!m_hasPendingErrorEvent)
        return;
    m_hasPendingErrorEvent = false;
    if (element()->document()->attached())
        element()->dispatchEvent(Event::create(eventNames().errorEvent, false, false));

    updatedHasPendingEvent();
}

Project: Chrome

void ImageLoader::dispatchPendingErrorEvents()
{
    errorEventSender().dispatchPendingEvents();
}

Project: Chrome

void ImageLoader::dispatchPendingEvent(ImageEventSender* eventSender)
{
    ASSERT(eventSender == &beforeLoadEventSender() || eventSender == &loadEventSender() || eventSender == &errorEventSender());
    const AtomicString& eventType = eventSender->eventType();
    if (eventType == eventNames().beforeloadEvent)
        dispatchPendingBeforeLoadEvent();
    if (eventType == eventNames().loadEvent)
        dispatchPendingLoadEvent();
    if (eventType == eventNames().errorEvent)
        dispatchPendingErrorEvent();
}

Project: Chrome

void ImageLoader::dispatchPendingLoadEvent()
{
    if (!m_hasPendingLoadEvent)
        return;
    if (!m_image)
        return;
    m_hasPendingLoadEvent = false;
    if (element()->document()->attached())
        dispatchLoadEvent();

    updatedHasPendingEvent();
}

Project: Chrome

void ImageLoader::dispatchPendingLoadEvents()
{
    loadEventSender().dispatchPendingEvents();
}

Project: Chrome

void ImageLoader::elementDidMoveToNewDocument()
{
    clearFailedLoadURL();
    setImage(0);
}

Project: Chrome

static ImageEventSender& loadEventSender()
{
    DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().loadEvent));
    return sender;
}

Project: Chrome

void ImageLoader::notifyFinished(CachedResource* resource)
{
    ASSERT(m_failedLoadURL.isEmpty());
    ASSERT(resource == m_image.get());

    m_imageComplete = true;
    if (!hasPendingBeforeLoadEvent())
        updateRenderer();

    if (!m_hasPendingLoadEvent)
        return;

    if (m_element->fastHasAttribute(HTMLNames::crossoriginAttr)
        && !m_element->document()->securityOrigin()->canRequest(image()->response().url())
        && !resource->passesAccessControlCheck(m_element->document()->securityOrigin())) {

        setImageWithoutConsideringPendingLoadEvent(0);

        m_hasPendingErrorEvent = true;
        errorEventSender().dispatchEventSoon(this);

        DEFINE_STATIC_LOCAL(String, consoleMessage, (ASCIILiteral("Cross-origin image load denied by Cross-Origin Resource Sharing policy.")));
        m_element->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, consoleMessage);

        ASSERT(!m_hasPendingLoadEvent);

        updatedHasPendingEvent();
        return;
    }

    if (resource->wasCanceled()) {
        m_hasPendingLoadEvent = false;
        updatedHasPendingEvent();
        return;
    }

    loadEventSender().dispatchEventSoon(this);
}

Project: Chrome

static inline bool pageIsBeingDismissed(Document* document)
{
    Frame* frame = document->frame();
    return frame && frame->loader()->pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal;
}

Project: Chrome

void ImageLoader::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
    MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::Image);
    info.addMember(m_element, "element");
    info.addMember(m_image.get(), "image", WTF::RetainingPointer);
    info.addMember(m_derefElementTimer, "derefElementTimer");
    info.addMember(m_failedLoadURL, "failedLoadURL");
}

Project: Chrome

void ImageLoader::setImage(CachedImage* newImage)
{
    setImageWithoutConsideringPendingLoadEvent(newImage);

    updatedHasPendingEvent();
}

Project: Chrome

void ImageLoader::setImageWithoutConsideringPendingLoadEvent(CachedImage* newImage)
{
    ASSERT(m_failedLoadURL.isEmpty());
    CachedImage* oldImage = m_image.get();
    if (newImage != oldImage) {
        m_image = newImage;
        if (m_hasPendingBeforeLoadEvent) {
            beforeLoadEventSender().cancelEvent(this);
            m_hasPendingBeforeLoadEvent = false;
        }
        if (m_hasPendingLoadEvent) {
            loadEventSender().cancelEvent(this);
            m_hasPendingLoadEvent = false;
        }
        if (m_hasPendingErrorEvent) {
            errorEventSender().cancelEvent(this);
            m_hasPendingErrorEvent = false;
        }
        m_imageComplete = true;
        if (newImage)
            newImage->addClient(this);
        if (oldImage)
            oldImage->removeClient(this);
    }

    if (RenderImageResource* imageResource = renderImageResource())
        imageResource->resetAnimation();
}

Project: Chrome

void ImageLoader::timerFired(Timer<ImageLoader>*)
{
    m_element->deref();
}

Project: Chrome

void ImageLoader::updateFromElementIgnoringPreviousError()
{
    clearFailedLoadURL();
    updateFromElement();
}

Project: Chrome

void ImageLoader::updateRenderer()
{
    RenderImageResource* imageResource = renderImageResource();

    if (!imageResource)
        return;

    CachedImage* cachedImage = imageResource->cachedImage();
    if (m_image != cachedImage && (m_imageComplete || !cachedImage))
        imageResource->setCachedImage(m_image.get());
}

Project: Chrome

void ImageLoader::updatedHasPendingEvent()
{
    bool wasProtected = m_elementIsProtected;
    m_elementIsProtected = m_hasPendingLoadEvent || m_hasPendingErrorEvent;
    if (wasProtected == m_elementIsProtected)
        return;

    if (m_elementIsProtected) {
        if (m_derefElementTimer.isActive())
            m_derefElementTimer.stop();
        else
            m_element->ref();
    } else {
        ASSERT(!m_derefElementTimer.isActive());
        m_derefElementTimer.startOneShot(0);
    }   
}

Project: Chrome

ImageLoader::~ImageLoader()
{
    if (m_image)
        m_image->removeClient(this);

    ASSERT(m_hasPendingBeforeLoadEvent || !beforeLoadEventSender().hasPendingEvents(this));
    if (m_hasPendingBeforeLoadEvent)
        beforeLoadEventSender().cancelEvent(this);

    ASSERT(m_hasPendingLoadEvent || !loadEventSender().hasPendingEvents(this));
    if (m_hasPendingLoadEvent)
        loadEventSender().cancelEvent(this);

    ASSERT(m_hasPendingErrorEvent || !errorEventSender().hasPendingEvents(this));
    if (m_hasPendingErrorEvent)
        errorEventSender().cancelEvent(this);

    if (m_elementIsProtected)
        m_element->deref();
}