# HG changeset patch # User pbz # Date 1600689290 0 # Mon Sep 21 11:54:50 2020 +0000 # Node ID efcefed227f304781326e7c8a52633559a79b6adlist oniguruma.spec # Parent 32d03662a363850006f648c22e825b3e886b29bc Bug 1314912 - Rate limit calls to History and Location interfaces. r=smaug9.0-3 This adds a rate limit to methods and setters of the History and Location for non-system callers. The rate limit is counted per BrowsingContext and can be controlled by prefs. This patch is based on the original rate limit patch by :freesamael. Differential Revision: https://phabricator.services.mozilla.com/D90136 diff -r 32d03662a363 -r efcefed227f3 docshell/base/BrowsingContext.cpp --- a/docshell/base/BrowsingContext.cpp 2020-07-21 06:49:37.000000000 +0800 +++ b/docshell/base/BrowsingContext.cpp 2021-01-06 10:22:57.966851379 +0800 @@ -2459,6 +2459,56 @@ bool BrowsingContext::CanSet(FieldIndex< return GetBrowserId() == 0 && IsTop() && Children().IsEmpty(); } +nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) { + // We only rate limit non system callers + if (aCallerType == CallerType::System) { + return NS_OK; + } + + // Fetch rate limiting preferences + uint32_t limitCount = + StaticPrefs::dom_navigation_locationChangeRateLimit_count(); + uint32_t timeSpanSeconds = + StaticPrefs::dom_navigation_locationChangeRateLimit_timespan(); + + // Disable throttling if either of the preferences is set to 0. + if (limitCount == 0 || timeSpanSeconds == 0) { + return NS_OK; + } + + TimeDuration throttleSpan = TimeDuration::FromSeconds(timeSpanSeconds); + + if (mLocationChangeRateLimitSpanStart.IsNull() || + ((TimeStamp::Now() - mLocationChangeRateLimitSpanStart) > throttleSpan)) { + // Initial call or timespan exceeded, reset counter and timespan. + mLocationChangeRateLimitSpanStart = TimeStamp::Now(); + mLocationChangeRateLimitCount = 1; + return NS_OK; + } + + if (mLocationChangeRateLimitCount >= limitCount) { + // Rate limit reached + + Document* doc = GetDocument(); + if (doc) { + nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,NS_LITERAL_CSTRING("DOM"), doc, + nsContentUtils::eDOM_PROPERTIES, + "LocChangeFloodingPrevented"); + } + + return NS_ERROR_DOM_SECURITY_ERR; + } + + mLocationChangeRateLimitCount++; + return NS_OK; +} + +void BrowsingContext::ResetLocationChangeRateLimit() { + // Resetting the timestamp object will cause the check function to + // init again and reset the rate limit. + mLocationChangeRateLimitSpanStart = TimeStamp(); +} + } // namespace dom namespace ipc { diff -r 32d03662a363 -r efcefed227f3 docshell/base/BrowsingContext.h --- a/docshell/base/BrowsingContext.h 2020-07-21 06:49:37.000000000 +0800 +++ b/docshell/base/BrowsingContext.h 2021-01-06 10:22:57.954851198 +0800 @@ -652,6 +652,16 @@ class BrowsingContext : public nsILoadCo bool CrossOriginIsolated(); + // Checks if we reached the rate limit for calls to Location and History API. + // The rate limit is controlled by the + // "dom.navigation.locationChangeRateLimit" prefs. + // Rate limit applies per BrowsingContext. + // Returns NS_OK if we are below the rate limit and increments the counter. + // Returns NS_ERROR_DOM_SECURITY_ERR if limit is reached. + nsresult CheckLocationChangeRateLimit(CallerType aCallerType); + + void ResetLocationChangeRateLimit(); + protected: virtual ~BrowsingContext(); BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup, @@ -932,6 +942,11 @@ class BrowsingContext : public nsILoadCo RefPtr mSessionStorageManager; RefPtr mChildSessionHistory; + + // Counter and time span for rate limiting Location and History API calls. + // Used by CheckLocationChangeRateLimit. Do not apply cross-process. + uint32_t mLocationChangeRateLimitCount; + mozilla::TimeStamp mLocationChangeRateLimitSpanStart; }; /** diff -r 32d03662a363 -r efcefed227f3 docshell/shistory/ChildSHistory.cpp --- a/docshell/shistory/ChildSHistory.cpp 2020-07-21 06:49:37.000000000 +0800 +++ b/docshell/shistory/ChildSHistory.cpp 2021-01-06 10:22:58.058852764 +0800 @@ -105,7 +105,14 @@ void ChildSHistory::Go(int32_t aOffset, } } -void ChildSHistory::AsyncGo(int32_t aOffset, bool aRequireUserInteraction) { +void ChildSHistory::AsyncGo(int32_t aOffset, bool aRequireUserInteraction, + CallerType aCallerType, ErrorResult& aRv) { + nsresult rv = mBrowsingContext->CheckLocationChangeRateLimit(aCallerType); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + if (!CanGo(aOffset)) { return; } diff -r 32d03662a363 -r efcefed227f3 docshell/shistory/ChildSHistory.h --- a/docshell/shistory/ChildSHistory.h 2020-07-21 06:49:37.000000000 +0800 +++ b/docshell/shistory/ChildSHistory.h 2021-01-06 10:22:58.058852764 +0800 @@ -64,8 +64,8 @@ class ChildSHistory : public nsISupports */ bool CanGo(int32_t aOffset); void Go(int32_t aOffset, bool aRequireUserInteraction, ErrorResult& aRv); - void AsyncGo(int32_t aOffset, bool aRequireUserInteraction); - + void AsyncGo(int32_t aOffset, bool aRequireUserInteraction, + CallerType aCallerType, ErrorResult& aRv); void RemovePendingHistoryNavigations(); /** diff -r 32d03662a363 -r efcefed227f3 dom/base/LocationBase.cpp --- a/dom/base/LocationBase.cpp 2020-07-21 04:53:13.000000000 +0800 +++ b/dom/base/LocationBase.cpp 2021-01-06 10:22:46.030671698 +0800 @@ -116,6 +116,16 @@ void LocationBase::SetURI(nsIURI* aURI, return; } + CallerType callerType = aSubjectPrincipal.IsSystemPrincipal() + ? CallerType::System + : CallerType::NonSystem; + + nsresult rv = bc->CheckLocationChangeRateLimit(callerType); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + RefPtr loadState = CheckURL(aURI, aSubjectPrincipal, aRv); if (aRv.Failed()) { @@ -141,7 +151,7 @@ void LocationBase::SetURI(nsIURI* aURI, loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE); loadState->SetFirstParty(true); - nsresult rv = bc->LoadURI(loadState); + rv = bc->LoadURI(loadState); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); } diff -r 32d03662a363 -r efcefed227f3 dom/base/nsHistory.cpp --- a/dom/base/nsHistory.cpp 2020-07-21 06:49:37.000000000 +0800 +++ b/dom/base/nsHistory.cpp 2021-01-06 10:22:46.030671698 +0800 @@ -135,7 +135,7 @@ void nsHistory::GetState(JSContext* aCx, aResult.setNull(); } -void nsHistory::Go(int32_t aDelta, ErrorResult& aRv) { +void nsHistory::Go(int32_t aDelta, CallerType aCallerType, ErrorResult& aRv) { nsCOMPtr win(do_QueryReferent(mInnerWindow)); if (!win || !win->HasActiveDocument()) { return aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); @@ -157,15 +157,17 @@ void nsHistory::Go(int32_t aDelta, Error // Ignore the return value from Go(), since returning errors from Go() can // lead to exceptions and a possible leak of history length + // AsyncGo throws if we hit the location change rate limit. if (StaticPrefs::dom_window_history_async()) { - session_history->AsyncGo(aDelta, /* aRequireUserInteraction = */ false); + session_history->AsyncGo(aDelta, /* aRequireUserInteraction = */ false, + aCallerType, aRv); } else { session_history->Go(aDelta, /* aRequireUserInteraction = */ false, IgnoreErrors()); } } -void nsHistory::Back(ErrorResult& aRv) { +void nsHistory::Back(CallerType aCallerType, ErrorResult& aRv) { nsCOMPtr win(do_QueryReferent(mInnerWindow)); if (!win || !win->HasActiveDocument()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); @@ -181,13 +183,14 @@ void nsHistory::Back(ErrorResult& aRv) { } if (StaticPrefs::dom_window_history_async()) { - sHistory->AsyncGo(-1, /* aRequireUserInteraction = */ false); + sHistory->AsyncGo(-1, /* aRequireUserInteraction = */ false, aCallerType, + aRv); } else { sHistory->Go(-1, /* aRequireUserInteraction = */ false, IgnoreErrors()); } } -void nsHistory::Forward(ErrorResult& aRv) { +void nsHistory::Forward(CallerType aCallerType, ErrorResult& aRv) { nsCOMPtr win(do_QueryReferent(mInnerWindow)); if (!win || !win->HasActiveDocument()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); @@ -203,7 +206,8 @@ void nsHistory::Forward(ErrorResult& aRv } if (StaticPrefs::dom_window_history_async()) { - sHistory->AsyncGo(1, /* aRequireUserInteraction = */ false); + sHistory->AsyncGo(1, /* aRequireUserInteraction = */ false, aCallerType, + aRv); } else { sHistory->Go(1, /* aRequireUserInteraction = */ false, IgnoreErrors()); } @@ -211,19 +215,20 @@ void nsHistory::Forward(ErrorResult& aRv void nsHistory::PushState(JSContext* aCx, JS::Handle aData, const nsAString& aTitle, const nsAString& aUrl, - ErrorResult& aRv) { - PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, false); + CallerType aCallerType, ErrorResult& aRv) { + PushOrReplaceState(aCx, aData, aTitle, aUrl, aCallerType, aRv, false); } void nsHistory::ReplaceState(JSContext* aCx, JS::Handle aData, const nsAString& aTitle, const nsAString& aUrl, - ErrorResult& aRv) { - PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, true); + CallerType aCallerType, ErrorResult& aRv) { + PushOrReplaceState(aCx, aData, aTitle, aUrl, aCallerType, aRv, true); } void nsHistory::PushOrReplaceState(JSContext* aCx, JS::Handle aData, const nsAString& aTitle, - const nsAString& aUrl, ErrorResult& aRv, + const nsAString& aUrl, + CallerType aCallerType, ErrorResult& aRv, bool aReplace) { nsCOMPtr win(do_QueryReferent(mInnerWindow)); if (!win) { @@ -238,6 +243,15 @@ void nsHistory::PushOrReplaceState(JSCon return; } + BrowsingContext* bc = win->GetBrowsingContext(); + if (bc) { + nsresult rv = bc->CheckLocationChangeRateLimit(aCallerType); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + } + // AddState might run scripts, so we need to hold a strong reference to the // docShell here to keep it from going away. nsCOMPtr docShell = win->GetDocShell(); diff -r 32d03662a363 -r efcefed227f3 dom/base/nsHistory.h --- a/dom/base/nsHistory.h 2020-07-21 04:53:13.000000000 +0800 +++ b/dom/base/nsHistory.h 2021-01-06 10:22:46.030671698 +0800 @@ -42,14 +42,17 @@ class nsHistory final : public nsISuppor mozilla::ErrorResult& aRv); void GetState(JSContext* aCx, JS::MutableHandle aResult, mozilla::ErrorResult& aRv) const; - void Go(int32_t aDelta, mozilla::ErrorResult& aRv); - void Back(mozilla::ErrorResult& aRv); - void Forward(mozilla::ErrorResult& aRv); + void Go(int32_t aDelta, mozilla::dom::CallerType aCallerType, + mozilla::ErrorResult& aRv); + void Back(mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv); + void Forward(mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv); void PushState(JSContext* aCx, JS::Handle aData, const nsAString& aTitle, const nsAString& aUrl, + mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv); void ReplaceState(JSContext* aCx, JS::Handle aData, const nsAString& aTitle, const nsAString& aUrl, + mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv); protected: @@ -59,6 +62,7 @@ class nsHistory final : public nsISuppor void PushOrReplaceState(JSContext* aCx, JS::Handle aData, const nsAString& aTitle, const nsAString& aUrl, + mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv, bool aReplace); already_AddRefed GetSessionHistory() const; diff -r 32d03662a363 -r efcefed227f3 dom/chrome-webidl/BrowsingContext.webidl --- a/dom/chrome-webidl/BrowsingContext.webidl 2020-07-21 06:49:37.000000000 +0800 +++ b/dom/chrome-webidl/BrowsingContext.webidl 2021-01-06 10:22:42.362616481 +0800 @@ -120,6 +120,9 @@ interface BrowsingContext { * under the new browser element. */ attribute unsigned long long browserId; + + // Resets the location change rate limit. Used for testing. + void resetLocationChangeRateLimit(); }; BrowsingContext includes LoadContextMixin; diff -r 32d03662a363 -r efcefed227f3 dom/locales/en-US/chrome/dom/dom.properties --- a/dom/locales/en-US/chrome/dom/dom.properties 2020-07-21 06:49:37.000000000 +0800 +++ b/dom/locales/en-US/chrome/dom/dom.properties 2021-01-06 10:22:42.418617324 +0800 @@ -393,3 +393,5 @@ UnknownProtocolNavigationPrevented=Preve PostMessageSharedMemoryObjectToCrossOriginWarning=Cannot post message containing a shared memory object to a cross-origin window. # LOCALIZATION NOTE: %S is the URL of the resource in question UnusedLinkPreloadPending=The resource at ā€œ%Sā€ preloaded with link preload was not used within a few seconds. Make sure all attributes of the preload tag are set correctly. +# LOCALIZATION NOTE: Do not translate "Location" and "History". +LocChangeFloodingPrevented=Too many calls to Location or History APIs within a short timeframe. diff -r 32d03662a363 -r efcefed227f3 dom/webidl/History.webidl --- a/dom/webidl/History.webidl 2020-07-21 04:53:19.000000000 +0800 +++ b/dom/webidl/History.webidl 2021-01-06 10:22:42.298615518 +0800 @@ -21,14 +21,14 @@ interface History { attribute ScrollRestoration scrollRestoration; [Throws] readonly attribute any state; - [Throws] + [Throws, NeedsCallerType] void go(optional long delta = 0); - [Throws] + [Throws, NeedsCallerType] void back(); - [Throws] + [Throws, NeedsCallerType] void forward(); - [Throws] + [Throws, NeedsCallerType] void pushState(any data, DOMString title, optional DOMString? url = null); - [Throws] + [Throws, NeedsCallerType] void replaceState(any data, DOMString title, optional DOMString? url = null); }; diff -r 32d03662a363 -r efcefed227f3 modules/libpref/init/StaticPrefList.yaml --- a/modules/libpref/init/StaticPrefList.yaml 2021-01-06 10:25:09.272827991 +0800 +++ b/modules/libpref/init/StaticPrefList.yaml 2021-01-06 10:22:36.458527604 +0800 @@ -2181,6 +2181,19 @@ value: true mirror: always +# Limit of location change caused by content scripts in a time span per +# BrowsingContext. This includes calls to History and Location APIs. +- name: dom.navigation.locationChangeRateLimit.count + type: uint32_t + value: 200 + mirror: always + +# Time span in seconds for location change rate limit. +- name: dom.navigation.locationChangeRateLimit.timespan + type: uint32_t + value: 10 + mirror: always + # Network Information API - name: dom.netinfo.enabled type: RelaxedAtomicBool