Bug 1913000 - Add more rate limiting to History interface. r=emilio a=RyanVM

Original Revision: https://phabricator.services.mozilla.com/D221255

Differential Revision: https://phabricator.services.mozilla.com/D224233
This commit is contained in:
William Wen 2024-10-01 18:13:16 +00:00
parent fd7c0e3c40
commit 73a06167f9
14 changed files with 48 additions and 35 deletions

View file

@ -3772,7 +3772,7 @@ bool BrowsingContext::ShouldUpdateSessionHistory(uint32_t aLoadType) {
(IsForceReloadType(aLoadType) && IsSubframe())); (IsForceReloadType(aLoadType) && IsSubframe()));
} }
nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) { nsresult BrowsingContext::CheckNavigationRateLimit(CallerType aCallerType) {
// We only rate limit non system callers // We only rate limit non system callers
if (aCallerType == CallerType::System) { if (aCallerType == CallerType::System) {
return NS_OK; return NS_OK;
@ -3780,9 +3780,9 @@ nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) {
// Fetch rate limiting preferences // Fetch rate limiting preferences
uint32_t limitCount = uint32_t limitCount =
StaticPrefs::dom_navigation_locationChangeRateLimit_count(); StaticPrefs::dom_navigation_navigationRateLimit_count();
uint32_t timeSpanSeconds = uint32_t timeSpanSeconds =
StaticPrefs::dom_navigation_locationChangeRateLimit_timespan(); StaticPrefs::dom_navigation_navigationRateLimit_timespan();
// Disable throttling if either of the preferences is set to 0. // Disable throttling if either of the preferences is set to 0.
if (limitCount == 0 || timeSpanSeconds == 0) { if (limitCount == 0 || timeSpanSeconds == 0) {
@ -3791,15 +3791,15 @@ nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) {
TimeDuration throttleSpan = TimeDuration::FromSeconds(timeSpanSeconds); TimeDuration throttleSpan = TimeDuration::FromSeconds(timeSpanSeconds);
if (mLocationChangeRateLimitSpanStart.IsNull() || if (mNavigationRateLimitSpanStart.IsNull() ||
((TimeStamp::Now() - mLocationChangeRateLimitSpanStart) > throttleSpan)) { ((TimeStamp::Now() - mNavigationRateLimitSpanStart) > throttleSpan)) {
// Initial call or timespan exceeded, reset counter and timespan. // Initial call or timespan exceeded, reset counter and timespan.
mLocationChangeRateLimitSpanStart = TimeStamp::Now(); mNavigationRateLimitSpanStart = TimeStamp::Now();
mLocationChangeRateLimitCount = 1; mNavigationRateLimitCount = 1;
return NS_OK; return NS_OK;
} }
if (mLocationChangeRateLimitCount >= limitCount) { if (mNavigationRateLimitCount >= limitCount) {
// Rate limit reached // Rate limit reached
Document* doc = GetDocument(); Document* doc = GetDocument();
@ -3812,14 +3812,14 @@ nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) {
return NS_ERROR_DOM_SECURITY_ERR; return NS_ERROR_DOM_SECURITY_ERR;
} }
mLocationChangeRateLimitCount++; mNavigationRateLimitCount++;
return NS_OK; return NS_OK;
} }
void BrowsingContext::ResetLocationChangeRateLimit() { void BrowsingContext::ResetNavigationRateLimit() {
// Resetting the timestamp object will cause the check function to // Resetting the timestamp object will cause the check function to
// init again and reset the rate limit. // init again and reset the rate limit.
mLocationChangeRateLimitSpanStart = TimeStamp(); mNavigationRateLimitSpanStart = TimeStamp();
} }
void BrowsingContext::LocationCreated(dom::Location* aLocation) { void BrowsingContext::LocationCreated(dom::Location* aLocation) {

View file

@ -898,13 +898,13 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
// Checks if we reached the rate limit for calls to Location and History API. // Checks if we reached the rate limit for calls to Location and History API.
// The rate limit is controlled by the // The rate limit is controlled by the
// "dom.navigation.locationChangeRateLimit" prefs. // "dom.navigation.navigationRateLimit" prefs.
// Rate limit applies per BrowsingContext. // Rate limit applies per BrowsingContext.
// Returns NS_OK if we are below the rate limit and increments the counter. // Returns NS_OK if we are below the rate limit and increments the counter.
// Returns NS_ERROR_DOM_SECURITY_ERR if limit is reached. // Returns NS_ERROR_DOM_SECURITY_ERR if limit is reached.
nsresult CheckLocationChangeRateLimit(CallerType aCallerType); nsresult CheckNavigationRateLimit(CallerType aCallerType);
void ResetLocationChangeRateLimit(); void ResetNavigationRateLimit();
mozilla::dom::DisplayMode DisplayMode() { return Top()->GetDisplayMode(); } mozilla::dom::DisplayMode DisplayMode() { return Top()->GetDisplayMode(); }
@ -1432,9 +1432,9 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
nsTArray<std::function<void(uint64_t)>> mDiscardListeners; nsTArray<std::function<void(uint64_t)>> mDiscardListeners;
// Counter and time span for rate limiting Location and History API calls. // Counter and time span for rate limiting Location and History API calls.
// Used by CheckLocationChangeRateLimit. Do not apply cross-process. // Used by CheckNavigationRateLimit. Do not apply cross-process.
uint32_t mLocationChangeRateLimitCount; uint32_t mNavigationRateLimitCount;
mozilla::TimeStamp mLocationChangeRateLimitSpanStart; mozilla::TimeStamp mNavigationRateLimitSpanStart;
mozilla::LinkedList<dom::Location> mLocations; mozilla::LinkedList<dom::Location> mLocations;
}; };

View file

@ -177,7 +177,7 @@ void ChildSHistory::AsyncGo(int32_t aOffset, bool aRequireUserInteraction,
MOZ_LOG(gSHLog, LogLevel::Debug, MOZ_LOG(gSHLog, LogLevel::Debug,
("ChildSHistory::AsyncGo(%d), current index = %d", aOffset, ("ChildSHistory::AsyncGo(%d), current index = %d", aOffset,
index.value())); index.value()));
nsresult rv = mBrowsingContext->CheckLocationChangeRateLimit(aCallerType); nsresult rv = mBrowsingContext->CheckNavigationRateLimit(aCallerType);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
MOZ_LOG(gSHLog, LogLevel::Debug, ("Rejected")); MOZ_LOG(gSHLog, LogLevel::Debug, ("Rejected"));
aRv.Throw(rv); aRv.Throw(rv);

View file

@ -17,8 +17,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1314912
async function setup() { async function setup() {
await SpecialPowers.pushPrefEnv({set: [ await SpecialPowers.pushPrefEnv({set: [
["dom.navigation.locationChangeRateLimit.count", RATE_LIMIT_COUNT], ["dom.navigation.navigationRateLimit.count", RATE_LIMIT_COUNT],
["dom.navigation.locationChangeRateLimit.timespan", RATE_LIMIT_TIME_SPAN]]}); ["dom.navigation.navigationRateLimit.timespan", RATE_LIMIT_TIME_SPAN]]});
} }
let inc = 0; let inc = 0;
@ -26,6 +26,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1314912
const rateLimitedFunctions = (win) => ({ const rateLimitedFunctions = (win) => ({
"history.replaceState": () => win.history.replaceState(null, "test", `${win.location.href}#${inc++}`), "history.replaceState": () => win.history.replaceState(null, "test", `${win.location.href}#${inc++}`),
"history.pushState": () => win.history.pushState(null, "test", `${win.location.href}#${inc++}`), "history.pushState": () => win.history.pushState(null, "test", `${win.location.href}#${inc++}`),
"history.SetScrollRestoration": () => win.history.scrollRestoration = "auto",
"history.back": () => win.history.back(), "history.back": () => win.history.back(),
"history.forward": () => win.history.forward(), "history.forward": () => win.history.forward(),
"history.go": () => win.history.go(-1), "history.go": () => win.history.go(-1),
@ -53,7 +54,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1314912
Object.entries(rateLimitedFunctions(win)).forEach(([name, fn]) => { Object.entries(rateLimitedFunctions(win)).forEach(([name, fn]) => {
// Reset the rate limit for the next run. // Reset the rate limit for the next run.
info("Reset rate limit."); info("Reset rate limit.");
SpecialPowers.wrap(win).browsingContext.resetLocationChangeRateLimit(); SpecialPowers.wrap(win).browsingContext.resetNavigationRateLimit();
info(`Calling ${name} ${RATE_LIMIT_COUNT} times to reach the rate limit.`); info(`Calling ${name} ${RATE_LIMIT_COUNT} times to reach the rate limit.`);
for(let i = 0; i< RATE_LIMIT_COUNT; i++) { for(let i = 0; i< RATE_LIMIT_COUNT; i++) {
@ -83,7 +84,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1314912
// Cleanup // Cleanup
win.close(); win.close();
SpecialPowers.wrap(win).browsingContext.resetLocationChangeRateLimit(); SpecialPowers.wrap(win).browsingContext.resetNavigationRateLimit();
SimpleTest.finish(); SimpleTest.finish();
} }

View file

@ -562,7 +562,7 @@ void Location::Reload(bool aForceget, nsIPrincipal& aSubjectPrincipal,
? CallerType::System ? CallerType::System
: CallerType::NonSystem; : CallerType::NonSystem;
nsresult rv = bc->CheckLocationChangeRateLimit(callerType); nsresult rv = bc->CheckNavigationRateLimit(callerType);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
aRv.Throw(rv); aRv.Throw(rv);
return; return;

View file

@ -125,7 +125,7 @@ void LocationBase::SetURI(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal,
? CallerType::System ? CallerType::System
: CallerType::NonSystem; : CallerType::NonSystem;
nsresult rv = bc->CheckLocationChangeRateLimit(callerType); nsresult rv = bc->CheckNavigationRateLimit(callerType);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
aRv.Throw(rv); aRv.Throw(rv);
return; return;

View file

@ -72,7 +72,7 @@ uint32_t nsHistory::GetLength(ErrorResult& aRv) const {
return len >= 0 ? len : 0; return len >= 0 ? len : 0;
} }
ScrollRestoration nsHistory::GetScrollRestoration(mozilla::ErrorResult& aRv) { ScrollRestoration nsHistory::GetScrollRestoration(mozilla::dom::CallerType aCallerType, mozilla::ErrorResult& aRv) {
nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow)); nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
if (!win || !win->HasActiveDocument() || !win->GetDocShell()) { if (!win || !win->HasActiveDocument() || !win->GetDocShell()) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
@ -88,6 +88,7 @@ ScrollRestoration nsHistory::GetScrollRestoration(mozilla::ErrorResult& aRv) {
} }
void nsHistory::SetScrollRestoration(mozilla::dom::ScrollRestoration aMode, void nsHistory::SetScrollRestoration(mozilla::dom::ScrollRestoration aMode,
mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aRv) { mozilla::ErrorResult& aRv) {
nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow)); nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
if (!win || !win->HasActiveDocument() || !win->GetDocShell()) { if (!win || !win->HasActiveDocument() || !win->GetDocShell()) {
@ -95,6 +96,15 @@ void nsHistory::SetScrollRestoration(mozilla::dom::ScrollRestoration aMode,
return; return;
} }
BrowsingContext* bc = win->GetBrowsingContext();
if (bc) {
nsresult rv = bc->CheckNavigationRateLimit(aCallerType);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return;
}
}
win->GetDocShell()->SetCurrentScrollRestorationIsManual( win->GetDocShell()->SetCurrentScrollRestorationIsManual(
aMode == mozilla::dom::ScrollRestoration::Manual); aMode == mozilla::dom::ScrollRestoration::Manual);
} }
@ -152,7 +162,7 @@ void nsHistory::Go(int32_t aDelta, nsIPrincipal& aSubjectPrincipal,
? CallerType::System ? CallerType::System
: CallerType::NonSystem; : CallerType::NonSystem;
// AsyncGo throws if we hit the location change rate limit. // AsyncGo throws if we hit the navigation rate limit.
session_history->AsyncGo(aDelta, /* aRequireUserInteraction = */ false, session_history->AsyncGo(aDelta, /* aRequireUserInteraction = */ false,
userActivation, callerType, aRv); userActivation, callerType, aRv);
} }
@ -237,7 +247,7 @@ void nsHistory::PushOrReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
BrowsingContext* bc = win->GetBrowsingContext(); BrowsingContext* bc = win->GetBrowsingContext();
if (bc) { if (bc) {
nsresult rv = bc->CheckLocationChangeRateLimit(aCallerType); nsresult rv = bc->CheckNavigationRateLimit(aCallerType);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
aRv.Throw(rv); aRv.Throw(rv);
return; return;

View file

@ -40,8 +40,10 @@ class nsHistory final : public nsISupports, public nsWrapperCache {
uint32_t GetLength(mozilla::ErrorResult& aRv) const; uint32_t GetLength(mozilla::ErrorResult& aRv) const;
mozilla::dom::ScrollRestoration GetScrollRestoration( mozilla::dom::ScrollRestoration GetScrollRestoration(
mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aRv); mozilla::ErrorResult& aRv);
void SetScrollRestoration(mozilla::dom::ScrollRestoration aMode, void SetScrollRestoration(mozilla::dom::ScrollRestoration aMode,
mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aRv); mozilla::ErrorResult& aRv);
void GetState(JSContext* aCx, JS::MutableHandle<JS::Value> aResult, void GetState(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
mozilla::ErrorResult& aRv) const; mozilla::ErrorResult& aRv) const;

View file

@ -264,7 +264,7 @@ interface BrowsingContext {
readonly attribute ChildSHistory? childSessionHistory; readonly attribute ChildSHistory? childSessionHistory;
// Resets the location change rate limit. Used for testing. // Resets the location change rate limit. Used for testing.
undefined resetLocationChangeRateLimit(); undefined resetNavigationRateLimit();
readonly attribute long childOffset; readonly attribute long childOffset;
}; };

View file

@ -17,7 +17,7 @@ enum ScrollRestoration { "auto", "manual" };
interface History { interface History {
[Throws] [Throws]
readonly attribute unsigned long length; readonly attribute unsigned long length;
[Throws] [Throws, NeedsCallerType]
attribute ScrollRestoration scrollRestoration; attribute ScrollRestoration scrollRestoration;
[Throws] [Throws]
readonly attribute any state; readonly attribute any state;

View file

@ -3165,13 +3165,13 @@
# Limit of location change caused by content scripts in a time span per # Limit of location change caused by content scripts in a time span per
# BrowsingContext. This includes calls to History and Location APIs. # BrowsingContext. This includes calls to History and Location APIs.
- name: dom.navigation.locationChangeRateLimit.count - name: dom.navigation.navigationRateLimit.count
type: uint32_t type: uint32_t
value: 200 value: 200
mirror: always mirror: always
# Time span in seconds for location change rate limit. # Time span in seconds for location change rate limit.
- name: dom.navigation.locationChangeRateLimit.timespan - name: dom.navigation.navigationRateLimit.timespan
type: uint32_t type: uint32_t
value: 10 value: 10
mirror: always mirror: always

View file

@ -222,7 +222,7 @@ const COMMON_PREFERENCES = new Map([
["dom.max_script_run_time", 0], ["dom.max_script_run_time", 0],
// Disable location change rate limitation // Disable location change rate limitation
["dom.navigation.locationChangeRateLimit.count", 0], ["dom.navigation.navigationRateLimit.count", 0],
// DOM Push // DOM Push
["dom.push.connection.enabled", false], ["dom.push.connection.enabled", false],

View file

@ -82,8 +82,8 @@ class GeckoInstance(object):
# No slow script dialogs # No slow script dialogs
"dom.max_chrome_script_run_time": 0, "dom.max_chrome_script_run_time": 0,
"dom.max_script_run_time": 0, "dom.max_script_run_time": 0,
# Disable location change rate limitation # Disable navigation change rate limitation
"dom.navigation.locationChangeRateLimit.count": 0, "dom.navigation.navigationRateLimit.count": 0,
# DOM Push # DOM Push
"dom.push.connection.enabled": False, "dom.push.connection.enabled": False,
# Screen Orientation API # Screen Orientation API

View file

@ -5168,7 +5168,7 @@ interface BrowsingContext extends LoadContextMixin {
watchedByDevTools: boolean; watchedByDevTools: boolean;
readonly window: WindowProxy | null; readonly window: WindowProxy | null;
getAllBrowsingContextsInSubtree(): BrowsingContext[]; getAllBrowsingContextsInSubtree(): BrowsingContext[];
resetLocationChangeRateLimit(): void; resetNavigationRateLimit(): void;
setRDMPaneMaxTouchPoints(maxTouchPoints: number): void; setRDMPaneMaxTouchPoints(maxTouchPoints: number): void;
setRDMPaneOrientation(type: OrientationType, rotationAngle: number): void; setRDMPaneOrientation(type: OrientationType, rotationAngle: number): void;
} }