diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 93966140c..10d431181 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3259,6 +3259,10 @@ function BrowserReloadWithFlags(reloadFlags) {
return;
}
+ // Reset temporary permissions on the current tab. This is done here
+ // because we only want to reset permissions on user reload.
+ delete gBrowser.selectedBrowser.canceledAuthenticationPromptCounter;
+
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
index 25e54d70b..7aba77445 100644
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2679,6 +2679,7 @@ let remote = false;
diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml
index e684b0b00..4f42e93fc 100644
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -417,6 +417,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// occurs in a new tab, we want focus to be restored to the content
// area when the current tab is re-selected.
gBrowser.selectedBrowser.focus();
+ delete gBrowser.selectedBrowser.canceledAuthenticationPromptCounter;
let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index 58fdff12d..b18e1d4b2 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -84,6 +84,7 @@
#include "nsWhitespaceTokenizer.h"
#include "nsICookieService.h"
#include "nsIConsoleReportCollector.h"
+#include "nsILoginManagerPrompter.h"
// we want to explore making the document own the load group
// so we can associate the document URI with the load group.
@@ -13169,6 +13170,35 @@ nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID,
// Get the an auth prompter for our window so that the parenting
// of the dialogs works as it should when using tabs.
+ // Since we don't go through E10S, we need to set the browser element
+ // manually (a la dom/ipc/TabParent) so that we can check elements
+ // that may be set on it, such as HTTP Auth DOS (TenFourFox issue 547).
+
+ NS_ASSERTION(mScriptGlobal, "We don't have a script global");
+ if (MOZ_LIKELY(mScriptGlobal)) {
+ nsCOMPtr frameElement = mScriptGlobal->GetFrameElementInternal();
+ if (MOZ_LIKELY(frameElement)) {
+ nsCOMPtr frame = do_QueryInterface(frameElement);
+ if (MOZ_LIKELY(frame)) {
+ nsCOMPtr prompt;
+ nsCOMPtr window = do_QueryInterface(frame->OwnerDoc()->GetWindow());
+ if (MOZ_LIKELY(window) &&
+ NS_SUCCEEDED(wwatch->GetPrompt(window, aIID,
+ getter_AddRefs(prompt))) &&
+ MOZ_LIKELY(prompt)) {
+ nsCOMPtr browser = do_QueryInterface(frameElement);
+ if (MOZ_LIKELY(browser)) {
+ nsCOMPtr prompter = do_QueryInterface(prompt);
+ if (MOZ_LIKELY(prompter))
+ prompter->SetE10sData(browser, nullptr);
+ }
+ *aResult = prompt.forget().take();
+ return NS_OK;
+ }
+ }
+ }
+ NS_WARNING("Unable to connect browser to auth prompt, falling back");
+ }
return wwatch->GetPrompt(mScriptGlobal, aIID,
reinterpret_cast(aResult));
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 85975fdc2..88839b013 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5151,6 +5151,12 @@ pref("toolkit.pageThumbs.screenSizeDivisor", 7);
pref("toolkit.pageThumbs.minWidth", 0);
pref("toolkit.pageThumbs.minHeight", 0);
+// When a user cancels this number of authentication dialogs coming from
+// a single web page in a row, all following authentication dialogs will
+// be blocked (automatically canceled) for that page. The counter resets
+// when the page is reloaded. To turn this feature off, just set the limit to 0.
+pref("prompts.authentication_dialog_abuse_limit", 3);
+
pref("tenfourfox.adblock.enabled", false);
pref("tenfourfox.adblock.logging.enabled", false);
pref("tenfourfox.dom.forms.date", true);
diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh
index abad41380..250e3a581 100644
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -117,6 +117,7 @@ struct HttpChannelOpenArgs
OptionalCorsPreflightArgs preflightArgs;
uint32_t initialRwin;
bool suspendAfterSynthesizeResponse;
+ uint64_t contentWindowId;
};
struct HttpChannelConnectArgs
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
index dd4dc6e29..89784819e 100644
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -48,8 +48,10 @@
#include "LoadInfo.h"
#include "nsIXULRuntime.h"
#include "nsPIDOMWindow.h"
+#include "nsIDOMWindowUtils.h"
#include
+#include "HttpBaseChannel.h"
namespace mozilla {
namespace net {
@@ -100,6 +102,7 @@ HttpBaseChannel::HttpBaseChannel()
, mTransferSize(0)
, mDecodedBodySize(0)
, mEncodedBodySize(0)
+ , mContentWindowId(0)
, mRequireCORSPreflight(false)
, mReportCollector(new ConsoleReportCollector())
, mForceMainDocumentChannel(false)
@@ -1100,6 +1103,30 @@ HttpBaseChannel::nsContentEncodings::PrepareForNext(void)
// HttpBaseChannel::nsIHttpChannel
//-----------------------------------------------------------------------------
+NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+ if (!mContentWindowId) {
+ nsCOMPtr loadContext;
+ GetCallback(loadContext);
+ if (loadContext) {
+ nsCOMPtr topWindow;
+ loadContext->GetTopWindow(getter_AddRefs(topWindow));
+ nsCOMPtr windowUtils = do_GetInterface(topWindow);
+ if (windowUtils) {
+ windowUtils->GetCurrentInnerWindowID(&mContentWindowId);
+ }
+ }
+ }
+ *aWindowId = mContentWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+ mContentWindowId = aWindowId;
+ return NS_OK;
+}
+
NS_IMETHODIMP
HttpBaseChannel::GetTransferSize(uint64_t *aTransferSize)
{
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
index 54b7939c9..11fdfb4c6 100644
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -178,6 +178,8 @@ public:
NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override;
NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override;
NS_IMETHOD GetProtocolVersion(nsACString & aProtocolVersion) override;
+ NS_IMETHOD GetTopLevelContentWindowId(uint64_t *aContentWindowId) override;
+ NS_IMETHOD SetTopLevelContentWindowId(uint64_t aContentWindowId) override;
// nsIHttpChannelInternal
NS_IMETHOD GetDocumentURI(nsIURI **aDocumentURI) override;
@@ -496,6 +498,10 @@ protected:
nsID mSchedulingContextID;
bool EnsureSchedulingContextID();
+ // ID of the top-level document's inner window this channel is being
+ // originated from.
+ uint64_t mContentWindowId;
+
bool mRequireCORSPreflight;
nsTArray mUnsafeHeaders;
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp
index 429934a49..75e3adbc9 100644
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -36,6 +36,8 @@
#include "nsContentSecurityManager.h"
#include "nsIDeprecationWarner.h"
#include "nsICompressConvStats.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindowUtils.h"
#ifdef OS_POSIX
#include "chrome/common/file_descriptor_set_posix.h"
@@ -1870,6 +1872,18 @@ HttpChannelChild::ContinueAsyncOpen()
return NS_ERROR_ILLEGAL_VALUE;
}
+ // This id identifies the inner window's top-level document,
+ // which changes on every new load or navigation.
+ uint64_t contentWindowId = 0;
+ if (tabChild) {
+ MOZ_ASSERT(tabChild->WebNavigation());
+ nsCOMPtr document = tabChild->GetDocument();
+ if (document) {
+ contentWindowId = document->InnerWindowID();
+ }
+ }
+ SetTopLevelContentWindowId(contentWindowId);
+
HttpChannelOpenArgs openArgs;
// No access to HttpChannelOpenArgs members, but they each have a
// function with the struct name that returns a ref.
@@ -1970,6 +1984,8 @@ HttpChannelChild::ContinueAsyncOpen()
mSchedulingContextID.ToProvidedString(scid);
openArgs.schedulingContextID().AssignASCII(scid);
+ openArgs.contentWindowId() = contentWindowId;
+
// The socket transport in the chrome process now holds a logical ref to us
// until OnStopRequest, or we do a redirect, or we hit an IPDL error.
AddIPDLReference();
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
index abb8d0de6..9cd132b55 100644
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -133,7 +133,8 @@ HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs)
a.loadInfo(), a.synthesizedResponseHead(),
a.synthesizedSecurityInfoSerialization(),
a.cacheKey(), a.schedulingContextID(), a.preflightArgs(),
- a.initialRwin(), a.suspendAfterSynthesizeResponse());
+ a.initialRwin(), a.suspendAfterSynthesizeResponse(),
+ a.contentWindowId());
}
case HttpChannelCreationArgs::THttpChannelConnectArgs:
{
@@ -386,7 +387,8 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
const nsCString& aSchedulingContextID,
const OptionalCorsPreflightArgs& aCorsPreflightArgs,
const uint32_t& aInitialRwin,
- const bool& aSuspendAfterSynthesizeResponse)
+ const bool& aSuspendAfterSynthesizeResponse,
+ const uint64_t& aContentWindowId)
{
nsCOMPtr uri = DeserializeURI(aURI);
if (!uri) {
@@ -440,6 +442,7 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
return SendFailedAsyncOpen(rv);
mChannel = static_cast(channel.get());
+ mChannel->SetTopLevelContentWindowId(aContentWindowId);
mChannel->SetWarningReporter(this);
mChannel->SetTimingEnabled(true);
if (mPBOverride != kPBOverride_Unset) {
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
index f75fc1b92..2d3077202 100644
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -129,7 +129,8 @@ protected:
const nsCString& aSchedulingContextID,
const OptionalCorsPreflightArgs& aCorsPreflightArgs,
const uint32_t& aInitialRwin,
- const bool& aSuspendAfterSynthesizeResponse);
+ const bool& aSuspendAfterSynthesizeResponse,
+ const uint64_t& aContentWindowId);
virtual bool RecvSetPriority(const uint16_t& priority) override;
virtual bool RecvSetClassOfService(const uint32_t& cos) override;
diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp
index 39a9dd76b..eaa02b0ad 100644
--- a/netwerk/protocol/http/NullHttpChannel.cpp
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -56,6 +56,18 @@ NullHttpChannel::Init(nsIURI *aURI,
// NullHttpChannel::nsIHttpChannel
//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+NullHttpChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
NS_IMETHODIMP
NullHttpChannel::GetTransferSize(uint64_t *aTransferSize)
{
diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl
index 220ff10ed..7e1158c55 100644
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -384,4 +384,10 @@ interface nsIHttpChannel : nsIChannel
* Identifies the scheduling context for this load.
*/
[noscript] attribute nsID schedulingContextID;
+
+ /**
+ * ID of the top-level document's inner window. Identifies the content
+ * this channel is being loaded in.
+ */
+ attribute uint64_t topLevelContentWindowId;
};
diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
index 5ae50719c..987acea6e 100644
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -721,6 +721,21 @@ nsViewSourceChannel::OnDataAvailable(nsIRequest *aRequest, nsISupports* aContext
// We want to forward most of nsIHttpChannel over to mHttpChannel, but we want
// to override GetRequestHeader and VisitHeaders. The reason is that we don't
// want various headers like Link: and Refresh: applying to view-source.
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->GetTopLevelContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
+{
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+ mHttpChannel->SetTopLevelContentWindowId(aWindowId);
+}
+
NS_IMETHODIMP
nsViewSourceChannel::GetRequestMethod(nsACString & aRequestMethod)
{
diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
index eb534443e..e5f0b8407 100644
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -95,30 +95,64 @@ LoginManagerPromptFactory.prototype = {
return;
}
- this._asyncPromptInProgress = true;
- prompt.inProgress = true;
+ // Set up a counter for ensuring that the basic auth prompt can not
+ // be abused for DOS-style attacks. With this counter, each eTLD+1
+ // per browser will get a limited number of times a user can
+ // cancel the prompt until we stop showing it.
+ let browser = prompter._browser;
+ let baseDomain = null;
+ if (browser) {
+ try {
+ baseDomain = Services.eTLD.getBaseDomainFromHost(hostname);
+ } catch (e) {
+ baseDomain = hostname;
+ }
+
+ if (!browser.canceledAuthenticationPromptCounter) {
+ browser.canceledAuthenticationPromptCounter = {};
+ }
+
+ if (!browser.canceledAuthenticationPromptCounter[baseDomain]) {
+ browser.canceledAuthenticationPromptCounter[baseDomain] = 0;
+ }
+ }
var self = this;
var runnable = {
+ cancel: false,
run : function() {
var ok = false;
- try {
- self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
- ok = prompter.promptAuth(prompt.channel,
- prompt.level,
- prompt.authInfo);
- } catch (e if (e instanceof Components.Exception) &&
- e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
- self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
- } catch (e) {
- Components.utils.reportError("LoginManagerPrompter: " +
- "_doAsyncPrompt:run: " + e + "\n");
- }
+ if (!this.cancel) {
+ try {
+ self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
+ ok = prompter.promptAuth(prompt.channel,
+ prompt.level,
+ prompt.authInfo);
+ } catch (e) {
+ if (e instanceof Components.Exception &&
+ e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
+ } else {
+ Components.utils.reportError("LoginManagerPrompter: " +
+ "_doAsyncPrompt:run: " + e + "\n");
+ }
+ }
- delete self._asyncPrompts[hashKey];
- prompt.inProgress = false;
- self._asyncPromptInProgress = false;
+ delete self._asyncPrompts[hashKey];
+ prompt.inProgress = false;
+ self._asyncPromptInProgress = false;
+
+ if (browser) {
+ // Reset the counter state if the user replied to a prompt and actually
+ // tried to login (vs. simply clicking any button to get out).
+ if (ok && (prompt.authInfo.username || prompt.authInfo.password)) {
+ browser.canceledAuthenticationPromptCounter[baseDomain] = 0;
+ } else {
+ browser.canceledAuthenticationPromptCounter[baseDomain] += 1;
+ }
+ }
+ }
for (var consumer of prompt.consumers) {
if (!consumer.callback)
@@ -128,16 +162,37 @@ LoginManagerPromptFactory.prototype = {
self.log("Calling back to " + consumer.callback + " ok=" + ok);
try {
- if (ok)
+ if (ok) {
consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
- else
- consumer.callback.onAuthCancelled(consumer.context, true);
+ } else {
+ consumer.callback.onAuthCancelled(consumer.context, !this.cancel);
+ }
} catch (e) { /* Throw away exceptions caused by callback */ }
}
self._doAsyncPrompt();
}
};
+ var cancelDialogLimit = Services.prefs.getIntPref("prompts.authentication_dialog_abuse_limit");
+
+ if (browser) {
+ let cancelationCounter = browser.canceledAuthenticationPromptCounter[baseDomain];
+ this.log("cancelationCounter ="+ cancelationCounter);
+ if (cancelDialogLimit && cancelationCounter >= cancelDialogLimit) {
+ this.log("Blocking auth dialog, due to exceeding dialog bloat limit");
+ delete this._asyncPrompts[hashKey];
+
+ // just make the runnable cancel all consumers
+ runnable.cancel = true;
+ } else {
+ this._asyncPromptInProgress = true;
+ prompt.inProgress = true;
+ }
+ } else {
+ this._asyncPromptInProgress = true;
+ prompt.inProgress = true;
+ }
+
Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
this.log("_doAsyncPrompt:run dispatched");
},