# HG changeset patch # User Birunthan Mohanathas # Parent 81b1a342005694cfe2e1084d1c9e47350d45f6d4 Bug 903966 - Stop blocking 'http://127.0.0.1/' as mixed content. r?ckerschb According to the spec, content from loopback addresses should no longer be treated as mixed content even in secure origins. See: - https://github.com/w3c/webappsec-mixed-content/commit/349501cdaa4b4dc1e2a8aacb216ced58fd316165 - https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy Note that we only whitelist '127.0.0.1' and '::1' to match Chrome 53 and later. See: - https://chromium.googlesource.com/chromium/src.git/+/130ee686fa00b617bfc001ceb3bb49782da2cb4e It is unclear if HTTPS origins should be able to use workers and WebSocket connections through a loopback HTTP address. They are not supported in Chrome (whether this is intentional or not is uncertain) so lets just ignore them for now. Tests for this are being reviewed at: https://github.com/w3c/web-platform-tests/pull/5304 diff --git a/browser/base/content/test/siteIdentity/browser.ini b/browser/base/content/test/siteIdentity/browser.ini --- a/browser/base/content/test/siteIdentity/browser.ini +++ b/browser/base/content/test/siteIdentity/browser.ini @@ -85,8 +85,13 @@ support-files = tags = mcb support-files = test_no_mcb_on_http_site_img.html test_no_mcb_on_http_site_img.css test_no_mcb_on_http_site_font.html test_no_mcb_on_http_site_font.css test_no_mcb_on_http_site_font2.html test_no_mcb_on_http_site_font2.css +[browser_no_mcb_for_loopback.js] +tags = mcb +support-files = + ../general/moz.png + test_no_mcb_for_loopback.html diff --git a/browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js b/browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js new file mode 100644 --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// The test loads a HTTPS web page with active content from HTTP loopback URLs +// and makes sure that the mixed content flags on the docshell are not set. +// +// Note that the URLs referenced within the test page intentionally use the +// unassigned port 8 because we don't want to actually load anything, we just +// want to check that the URLs are not blocked. + +const TEST_URI = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "test_no_mcb_for_loopback.html"; + +const PREF_DISPLAY = "security.mixed_content.block_display_content"; +const PREF_ACTIVE = "security.mixed_content.block_active_content"; + +registerCleanupFunction(function() { + Services.prefs.clearUserPref(PREF_DISPLAY); + Services.prefs.clearUserPref(PREF_ACTIVE); + gBrowser.removeCurrentTab(); +}); + +add_task(function* allowLoopbackMixedContent() { + Services.prefs.setBoolPref(PREF_DISPLAY, true); + Services.prefs.setBoolPref(PREF_ACTIVE, true); + + const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI); + const browser = gBrowser.getBrowserForTab(tab); + + yield ContentTask.spawn(browser, null, function() { + is(docShell.hasMixedDisplayContentBlocked, false, "hasMixedDisplayContentBlocked not set"); + is(docShell.hasMixedActiveContentBlocked, false, "hasMixedActiveContentBlocked not set"); + }); + + // Check that loopback content served from the cache is not blocked. + yield ContentTask.spawn(browser, null, function* () { + const doc = content.document; + const img = doc.createElement("img"); + const promiseImgLoaded = ContentTaskUtils.waitForEvent(img, "load", false); + img.src = "http://127.0.0.1:8888/browser/browser/base/content/test/siteIdentity/moz.png"; + Assert.ok(!img.complete, "loopback image not yet loaded"); + doc.body.appendChild(img); + yield promiseImgLoaded; + + const cachedImg = doc.createElement("img"); + cachedImg.src = img.src; + Assert.ok(cachedImg.complete, "loopback image loaded from cache"); + }); + + assertMixedContentBlockingState(browser, { + activeBlocked: false, + activeLoaded: false, + passiveLoaded: false, + }); +}); diff --git a/browser/base/content/test/siteIdentity/test_no_mcb_for_loopback.html b/browser/base/content/test/siteIdentity/test_no_mcb_for_loopback.html new file mode 100644 --- /dev/null +++ b/browser/base/content/test/siteIdentity/test_no_mcb_for_loopback.html @@ -0,0 +1,50 @@ + + + + + + Bug 903966 + + + + + +
test
+
test
+ + + + + + + + + + + + + + + + diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp --- a/dom/html/HTMLFormElement.cpp +++ b/dom/html/HTMLFormElement.cpp @@ -9,16 +9,17 @@ #include "jsapi.h" #include "mozilla/ContentEvents.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "mozilla/EventStates.h" #include "mozilla/dom/AutocompleteErrorEvent.h" #include "mozilla/dom/nsCSPUtils.h" #include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/nsMixedContentBlocker.h" #include "mozilla/dom/HTMLFormControlsCollection.h" #include "mozilla/dom/HTMLFormElementBinding.h" #include "mozilla/Move.h" #include "nsIHTMLDocument.h" #include "nsGkAtoms.h" #include "nsStyleConsts.h" #include "nsPresContext.h" #include "nsIDocument.h" @@ -902,16 +903,20 @@ HTMLFormElement::DoSecureToInsecureSubmi if (NS_FAILED(rv)) { return rv; } if (!formIsHTTPS || actionIsHTTPS || actionIsJS) { return NS_OK; } + if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aActionURL)) { + return NS_OK; + } + nsCOMPtr window = OwnerDoc()->GetWindow(); if (!window) { return NS_ERROR_FAILURE; } nsCOMPtr docShell = window->GetDocShell(); if (!docShell) { return NS_ERROR_FAILURE; } diff --git a/dom/html/moz.build b/dom/html/moz.build --- a/dom/html/moz.build +++ b/dom/html/moz.build @@ -227,17 +227,18 @@ EXTRA_COMPONENTS += [ include('/ipc/chromium/chromium-config.mozbuild') LOCAL_INCLUDES += [ '/caps', '/docshell/base', '/dom/base', '/dom/canvas', - '/dom/media/', + '/dom/media', + '/dom/security', '/dom/xbl', '/dom/xul', '/editor/txmgr', '/image', '/layout/forms', '/layout/generic', '/layout/style', '/layout/tables', diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -422,16 +422,28 @@ nsMixedContentBlocker::ShouldLoad(uint32 aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal, aDecision); return rv; } +bool +nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(nsIURI* aURL) { + nsAutoCString host; + nsresult rv = aURL->GetHost(host); + NS_ENSURE_SUCCESS(rv, false); + + // We could also allow 'localhost' (if we can guarantee that it resolves + // to a loopback address), but Chrome doesn't support it as of writing. For + // web compat, lets only allow what Chrome allows. + return host.Equals("127.0.0.1") || host.Equals("::1"); +} + /* Static version of ShouldLoad() that contains all the Mixed Content Blocker * logic. Called from non-static ShouldLoad(). */ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, uint32_t aContentType, nsIURI* aContentLocation, nsIURI* aRequestingLocation, @@ -724,30 +736,39 @@ nsMixedContentBlocker::ShouldLoad(bool a rv = innerContentLocation->SchemeIs("https", &isHttpsScheme); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(!isHttpsScheme); #endif *aDecision = REJECT_REQUEST; return NS_OK; } + bool isHttpScheme = false; + rv = innerContentLocation->SchemeIs("http", &isHttpScheme); + NS_ENSURE_SUCCESS(rv, rv); + + // Loopback origins are not considered mixed content even over HTTP. See: + // https://w3c.github.io/webappsec-mixed-content/#should-block-fetch + if (isHttpScheme && + IsPotentiallyTrustworthyLoopbackURL(innerContentLocation)) { + *aDecision = ACCEPT; + return NS_OK; + } + // The page might have set the CSP directive 'upgrade-insecure-requests'. In such // a case allow the http: load to succeed with the promise that the channel will // get upgraded to https before fetching any data from the netwerk. // Please see: nsHttpChannel::Connect() // // Please note that the CSP directive 'upgrade-insecure-requests' only applies to // http: and ws: (for websockets). Websockets are not subject to mixed content // blocking since insecure websockets are not allowed within secure pages. Hence, // we only have to check against http: here. Skip mixed content blocking if the // subresource load uses http: and the CSP directive 'upgrade-insecure-requests' // is present on the page. - bool isHttpScheme = false; - rv = innerContentLocation->SchemeIs("http", &isHttpScheme); - NS_ENSURE_SUCCESS(rv, rv); nsIDocument* document = docShell->GetDocument(); MOZ_ASSERT(document, "Expected a document"); if (isHttpScheme && document->GetUpgradeInsecureRequests(isPreload)) { *aDecision = ACCEPT; return NS_OK; } // The page might have set the CSP directive 'block-all-mixed-content' which diff --git a/dom/security/nsMixedContentBlocker.h b/dom/security/nsMixedContentBlocker.h --- a/dom/security/nsMixedContentBlocker.h +++ b/dom/security/nsMixedContentBlocker.h @@ -40,16 +40,20 @@ private: public: NS_DECL_ISUPPORTS NS_DECL_NSICONTENTPOLICY NS_DECL_NSICHANNELEVENTSINK nsMixedContentBlocker(); + // See: + // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy + static bool IsPotentiallyTrustworthyLoopbackURL(nsIURI* aURL); + /* Static version of ShouldLoad() that contains all the Mixed Content Blocker * logic. Called from non-static ShouldLoad(). * Called directly from imageLib when an insecure redirect exists in a cached * image load. * @param aHadInsecureImageRedirect * boolean flag indicating that an insecure redirect through http * occured when this image was initially loaded and cached. * Remaining parameters are from nsIContentPolicy::ShouldLoad(). diff --git a/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js b/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js --- a/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js +++ b/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js @@ -9,17 +9,17 @@ //jscs:disable add_task(function*() { //jscs:enable Observer.add_observers(Services); registerCleanupFunction(do_cleanup); // add the top-level server test_servers['localhost-ip'] = { - host: '127.0.0.1', + host: '127.0.0.2', response: true, id: 'localhost-ip', }; test_settings.block_active.result['localhost-ip'] = 'blocked'; let which = "block_active"; SetupPrefTestEnvironment(which);