Changeset 195625 in webkit


Ignore:
Timestamp:
Jan 26, 2016 2:48:15 PM (8 years ago)
Author:
beidson@apple.com
Message:

History.pushState causes intense memory pressure.
https://bugs.webkit.org/show_bug.cgi?id=153435

Reviewed by Sam Weinig, Oliver Hunt, and Geoff Garen.

Source/WebCore:

Tests: fast/loader/stateobjects/pushstate-frequency-iframe.html

fast/loader/stateobjects/pushstate-frequency-with-user-gesture.html
fast/loader/stateobjects/pushstate-frequency.html
fast/loader/stateobjects/replacestate-frequency-iframe.html
fast/loader/stateobjects/replacestate-frequency-with-user-gesture.html
fast/loader/stateobjects/replacestate-frequency.html
loader/stateobjects/pushstate-size-iframe.html
loader/stateobjects/pushstate-size.html
loader/stateobjects/replacestate-size-iframe.html
loader/stateobjects/replacestate-size.html

Add restrictions on how frequently push/replaceState can be called,
as well as how much of a cumulative payload they can deliver.

  • bindings/js/JSHistoryCustom.cpp:

(WebCore::JSHistory::pushState):
(WebCore::JSHistory::replaceState):

  • page/History.cpp:

(WebCore::History::stateObjectAdded):

  • page/History.h:

LayoutTests:

  • fast/loader/stateobjects/pushstate-frequency-expected.txt: Added.
  • fast/loader/stateobjects/pushstate-frequency-iframe-expected.txt: Added.
  • fast/loader/stateobjects/pushstate-frequency-iframe.html: Added.
  • fast/loader/stateobjects/pushstate-frequency-with-user-gesture-expected.txt: Added.
  • fast/loader/stateobjects/pushstate-frequency-with-user-gesture.html: Added.
  • fast/loader/stateobjects/pushstate-frequency.html: Added.
  • fast/loader/stateobjects/replacestate-frequency-expected.txt: Added.
  • fast/loader/stateobjects/replacestate-frequency-iframe-expected.txt: Added.
  • fast/loader/stateobjects/replacestate-frequency-iframe.html: Added.
  • fast/loader/stateobjects/replacestate-frequency-with-user-gesture-expected.txt: Added.
  • fast/loader/stateobjects/replacestate-frequency-with-user-gesture.html: Added.
  • fast/loader/stateobjects/replacestate-frequency.html: Added.
  • fast/loader/stateobjects/resources/pushstate-iframe.html: Added.
  • fast/loader/stateobjects/resources/replacestate-iframe.html: Added.
  • loader/stateobjects/pushstate-size-expected.txt: Added.
  • loader/stateobjects/pushstate-size-iframe-expected.txt: Added.
  • loader/stateobjects/pushstate-size-iframe.html: Added.
  • loader/stateobjects/pushstate-size.html: Added.
  • loader/stateobjects/replacestate-size-expected.txt: Added.
  • loader/stateobjects/replacestate-size-iframe-expected.txt: Added.
  • loader/stateobjects/replacestate-size-iframe.html: Added.
  • loader/stateobjects/replacestate-size.html: Added.
  • loader/stateobjects/resources/pushstate-iframe.html: Added.
  • loader/stateobjects/resources/replacestate-iframe.html: Added.
Location:
trunk
Files:
26 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r195624 r195625  
     12016-01-26  Brady Eidson  <beidson@apple.com>
     2
     3        History.pushState causes intense memory pressure.
     4        https://bugs.webkit.org/show_bug.cgi?id=153435
     5
     6        Reviewed by Sam Weinig, Oliver Hunt, and Geoff Garen.
     7
     8        * TestExpectations: Mark some of the new tests as slow.
     9       
     10        * fast/loader/stateobjects/pushstate-frequency-expected.txt: Added.
     11        * fast/loader/stateobjects/pushstate-frequency-iframe-expected.txt: Added.
     12        * fast/loader/stateobjects/pushstate-frequency-iframe.html: Added.
     13        * fast/loader/stateobjects/pushstate-frequency-with-user-gesture-expected.txt: Added.
     14        * fast/loader/stateobjects/pushstate-frequency-with-user-gesture.html: Added.
     15        * fast/loader/stateobjects/pushstate-frequency.html: Added.
     16        * fast/loader/stateobjects/replacestate-frequency-expected.txt: Added.
     17        * fast/loader/stateobjects/replacestate-frequency-iframe-expected.txt: Added.
     18        * fast/loader/stateobjects/replacestate-frequency-iframe.html: Added.
     19        * fast/loader/stateobjects/replacestate-frequency-with-user-gesture-expected.txt: Added.
     20        * fast/loader/stateobjects/replacestate-frequency-with-user-gesture.html: Added.
     21        * fast/loader/stateobjects/replacestate-frequency.html: Added.
     22        * fast/loader/stateobjects/resources/pushstate-iframe.html: Added.
     23        * fast/loader/stateobjects/resources/replacestate-iframe.html: Added.
     24        * loader/stateobjects/pushstate-size-expected.txt: Added.
     25        * loader/stateobjects/pushstate-size-iframe-expected.txt: Added.
     26        * loader/stateobjects/pushstate-size-iframe.html: Added.
     27        * loader/stateobjects/pushstate-size.html: Added.
     28        * loader/stateobjects/replacestate-size-expected.txt: Added.
     29        * loader/stateobjects/replacestate-size-iframe-expected.txt: Added.
     30        * loader/stateobjects/replacestate-size-iframe.html: Added.
     31        * loader/stateobjects/replacestate-size.html: Added.
     32        * loader/stateobjects/resources/pushstate-iframe.html: Added.
     33        * loader/stateobjects/resources/replacestate-iframe.html: Added.
     34       
    1352016-01-26  Ryan Haddad  <ryanhaddad@apple.com>
    236
  • trunk/LayoutTests/TestExpectations

    r195394 r195625  
    848848http/tests/security/contentSecurityPolicy/1.1/scripthash-default-src.html # Needs expected file.
    849849http/tests/security/contentSecurityPolicy/1.1/stylehash-default-src.html # Needs expected file.
     850
     851# These state object tests purposefully stress a resource limit, and take multiple seconds to run.
     852loader/stateobjects/pushstate-size-iframe.html [ Slow ]
     853loader/stateobjects/pushstate-size.html [ Slow ]
     854loader/stateobjects/replacestate-size-iframe.html [ Slow ]
     855loader/stateobjects/replacestate-size.html [ Slow ]
  • trunk/Source/WebCore/ChangeLog

    r195616 r195625  
     12016-01-26  Brady Eidson  <beidson@apple.com>
     2
     3        History.pushState causes intense memory pressure.
     4        https://bugs.webkit.org/show_bug.cgi?id=153435
     5
     6        Reviewed by Sam Weinig, Oliver Hunt, and Geoff Garen.
     7
     8        Tests: fast/loader/stateobjects/pushstate-frequency-iframe.html
     9               fast/loader/stateobjects/pushstate-frequency-with-user-gesture.html
     10               fast/loader/stateobjects/pushstate-frequency.html
     11               fast/loader/stateobjects/replacestate-frequency-iframe.html
     12               fast/loader/stateobjects/replacestate-frequency-with-user-gesture.html
     13               fast/loader/stateobjects/replacestate-frequency.html
     14               loader/stateobjects/pushstate-size-iframe.html
     15               loader/stateobjects/pushstate-size.html
     16               loader/stateobjects/replacestate-size-iframe.html
     17               loader/stateobjects/replacestate-size.html
     18
     19        Add restrictions on how frequently push/replaceState can be called,
     20        as well as how much of a cumulative payload they can deliver.
     21       
     22        * bindings/js/JSHistoryCustom.cpp:
     23        (WebCore::JSHistory::pushState):
     24        (WebCore::JSHistory::replaceState):
     25       
     26        * page/History.cpp:
     27        (WebCore::History::stateObjectAdded):
     28        * page/History.h:
     29
    1302016-01-26  Anders Carlsson  <andersca@apple.com>
    231
  • trunk/Source/WebCore/bindings/js/JSHistoryCustom.cpp

    r191887 r195625  
    3030#include "JSHistory.h"
    3131
     32#include "ExceptionCode.h"
    3233#include "Frame.h"
    3334#include "JSDOMBinding.h"
     
    140141    }
    141142
    142     ExceptionCode ec = 0;
     143    ExceptionCodeWithMessage ec;
    143144    wrapped().stateObjectAdded(historyState.release(), title, url, History::StateObjectType::Push, ec);
    144145    setDOMException(&state, ec);
     
    169170    }
    170171
    171     ExceptionCode ec = 0;
     172    ExceptionCodeWithMessage ec;
    172173    wrapped().stateObjectAdded(historyState.release(), title, url, History::StateObjectType::Replace, ec);
    173174    setDOMException(&state, ec);
  • trunk/Source/WebCore/page/History.cpp

    r185231 r195625  
    3535#include "HistoryController.h"
    3636#include "HistoryItem.h"
     37#include "MainFrame.h"
    3738#include "Page.h"
     39#include "ScriptController.h"
    3840#include "SecurityOrigin.h"
    3941#include "SerializedScriptValue.h"
     42#include <wtf/CheckedArithmetic.h>
    4043#include <wtf/MainThread.h>
    4144
     
    137140}
    138141
    139 void History::stateObjectAdded(PassRefPtr<SerializedScriptValue> data, const String& title, const String& urlString, StateObjectType stateObjectType, ExceptionCode& ec)
    140 {
     142void History::stateObjectAdded(PassRefPtr<SerializedScriptValue> data, const String& title, const String& urlString, StateObjectType stateObjectType, ExceptionCodeWithMessage& ec)
     143{
     144    // Each unique main-frame document is only allowed to send 64mb of state object payload to the UI client/process.
     145    static uint32_t totalStateObjectPayloadLimit = 0x4000000;
     146    static unsigned perUserGestureStateObjectLimit = 100;
     147
    141148    if (!m_frame || !m_frame->page())
    142149        return;
    143    
     150
    144151    URL fullURL = urlForState(urlString);
    145152    if (!fullURL.isValid() || !m_frame->document()->securityOrigin()->canRequest(fullURL)) {
    146         ec = SECURITY_ERR;
    147         return;
    148     }
     153        ec.code = SECURITY_ERR;
     154        return;
     155    }
     156
     157    Document* mainDocument = m_frame->page()->mainFrame().document();
     158    History* mainHistory = nullptr;
     159    if (mainDocument) {
     160        if (auto* mainDOMWindow = mainDocument->domWindow())
     161            mainHistory = mainDOMWindow->history();
     162    }
     163
     164    if (!mainHistory)
     165        return;
     166
     167    bool processingUserGesture = ScriptController::processingUserGesture();
     168    if (!processingUserGesture && mainHistory->m_nonUserGestureObjectsAdded >= perUserGestureStateObjectLimit) {
     169        ec.code = SECURITY_ERR;
     170        if (stateObjectType == StateObjectType::Replace)
     171            ec.message = String::format("Attempt to use history.replaceState() more than %u times without a user gesture", perUserGestureStateObjectLimit);
     172        else
     173            ec.message = String::format("Attempt to use history.pushState() more than %u times without a user gesture", perUserGestureStateObjectLimit);
     174        return;
     175    }
     176
     177    double userGestureTimestamp = mainDocument->lastHandledUserGestureTimestamp();
     178    if (processingUserGesture) {
     179        if (mainHistory->m_currentUserGestureTimestamp < userGestureTimestamp) {
     180            mainHistory->m_currentUserGestureTimestamp = userGestureTimestamp;
     181            mainHistory->m_currentUserGestureObjectsAdded = 0;
     182        }
     183
     184        if (mainHistory->m_currentUserGestureObjectsAdded >= perUserGestureStateObjectLimit) {
     185            ec.code = SECURITY_ERR;
     186            if (stateObjectType == StateObjectType::Replace)
     187                ec.message = String::format("Attempt to use history.replaceState() more than %u times per gesture", perUserGestureStateObjectLimit);
     188            else
     189                ec.message = String::format("Attempt to use history.pushState() more than %u times per user gesture", perUserGestureStateObjectLimit);
     190            return;
     191        }
     192    }
     193
     194    Checked<unsigned> titleSize = title.length();
     195    titleSize *= 2;
     196
     197    Checked<unsigned> urlSize = fullURL.string().length();
     198    urlSize *= 2;
     199
     200    Checked<uint64_t> payloadSize = titleSize;
     201    payloadSize += urlSize;
     202    payloadSize += data ? data->data().size() : 0;
     203
     204    Checked<uint64_t> newTotalUsage = mainHistory->m_totalStateObjectUsage;
     205
     206    if (stateObjectType == StateObjectType::Replace)
     207        newTotalUsage -= m_mostRecentStateObjectUsage;
     208    newTotalUsage += payloadSize;
     209
     210    if (newTotalUsage > totalStateObjectPayloadLimit) {
     211        ec.code = QUOTA_EXCEEDED_ERR;
     212        if (stateObjectType == StateObjectType::Replace)
     213            ec.message = ASCIILiteral("Attempt to store more data than allowed using history.replaceState()");
     214        else
     215            ec.message = ASCIILiteral("Attempt to store more data than allowed using history.pushState()");
     216        return;
     217    }
     218
     219    m_mostRecentStateObjectUsage = payloadSize.unsafeGet();
     220
     221    mainHistory->m_totalStateObjectUsage = newTotalUsage.unsafeGet();
     222    if (processingUserGesture)
     223        ++mainHistory->m_currentUserGestureObjectsAdded;
     224    else
     225        ++mainHistory->m_nonUserGestureObjectsAdded;
    149226
    150227    if (!urlString.isEmpty())
  • trunk/Source/WebCore/page/History.h

    r184066 r195625  
    3939class Frame;
    4040class ScriptExecutionContext;
     41struct ExceptionCodeWithMessage;
    4142typedef int ExceptionCode;
    4243
     
    6263        Replace
    6364    };
    64     void stateObjectAdded(PassRefPtr<SerializedScriptValue>, const String& title, const String& url, StateObjectType, ExceptionCode&);
     65    void stateObjectAdded(PassRefPtr<SerializedScriptValue>, const String& title, const String& url, StateObjectType, ExceptionCodeWithMessage&);
    6566
    6667private:
     
    7273
    7374    RefPtr<SerializedScriptValue> m_lastStateObjectRequested;
     75
     76    unsigned m_nonUserGestureObjectsAdded { 0 };
     77    unsigned m_currentUserGestureObjectsAdded { 0 };
     78    double m_currentUserGestureTimestamp { 0 };
     79
     80    // For the main frame's History object to keep track of all state object usage.
     81    uint64_t m_totalStateObjectUsage { 0 };
     82
     83    // For each individual History object to keep track of the most recent state object added.
     84    uint64_t m_mostRecentStateObjectUsage { 0 };
    7485};
    7586
Note: See TracChangeset for help on using the changeset viewer.