Bug 1228457 create pref to allow blocking .onion at dns level rfc 7687 r=valentin.gosu
authorPatrick McManus <mcmanus@ducksong.com>
Fri, 11 Dec 2015 14:28:21 -0500
changeset 310363 cb59c073476446b2ebf006b450d9a17d1055315e
parent 310362 283333d47b1b1b2b55029e47c6e2d7cb4b0b9eb8
child 310364 51d23f0cb661b264a79c789fabaf156d74f03cfe
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvalentin
bugs1228457
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1228457 create pref to allow blocking .onion at dns level rfc 7687 r=valentin.gosu
modules/libpref/init/all.js
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsDNSService2.h
netwerk/test/unit/test_dns_onion.js
netwerk/test/unit/xpcshell.ini
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1690,16 +1690,23 @@ pref("network.dns.get-ttl", true);
 
 // The grace period allows the DNS cache to use expired entries, while kicking off
 // a revalidation in the background.
 pref("network.dnsCacheExpirationGracePeriod", 60);
 
 // This preference can be used to turn off DNS prefetch.
 pref("network.dns.disablePrefetch", false);
 
+// This preference controls whether .onion hostnames are
+// rejected before being given to DNS. RFC 7686
+pref("network.dns.blockDotOnion", true);
+
+// These domains are treated as localhost equivalent
+pref("network.dns.localDomains", "");
+
 // Contols whether or not "localhost" should resolve when offline
 pref("network.dns.offline-localhost", true);
 
 // This preference controls whether or not URLs with UTF-8 characters are
 // escaped.  Set this preference to TRUE for strict RFC2396 conformance.
 pref("network.standard-url.escape-utf8", true);
 
 // This preference controls whether or not URLs are always encoded and sent as
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -45,16 +45,17 @@ using namespace mozilla;
 using namespace mozilla::net;
 
 static const char kPrefDnsCacheEntries[]     = "network.dnsCacheEntries";
 static const char kPrefDnsCacheExpiration[]  = "network.dnsCacheExpiration";
 static const char kPrefDnsCacheGrace[]       = "network.dnsCacheExpirationGracePeriod";
 static const char kPrefIPv4OnlyDomains[]     = "network.dns.ipv4OnlyDomains";
 static const char kPrefDisableIPv6[]         = "network.dns.disableIPv6";
 static const char kPrefDisablePrefetch[]     = "network.dns.disablePrefetch";
+static const char kPrefBlockDotOnion[]       = "network.dns.blockDotOnion";
 static const char kPrefDnsLocalDomains[]     = "network.dns.localDomains";
 static const char kPrefDnsOfflineLocalhost[] = "network.dns.offline-localhost";
 static const char kPrefDnsNotifyResolution[] = "network.dns.notifyResolution";
 
 //-----------------------------------------------------------------------------
 
 class nsDNSRecord : public nsIDNSRecord
 {
@@ -538,16 +539,17 @@ nsDNSService::Init()
     NS_ENSURE_TRUE(!mResolver, NS_ERROR_ALREADY_INITIALIZED);
     // prefs
     uint32_t maxCacheEntries  = 400;
     uint32_t defaultCacheLifetime = 120; // seconds
     uint32_t defaultGracePeriod = 60; // seconds
     bool     disableIPv6      = false;
     bool     offlineLocalhost = true;
     bool     disablePrefetch  = false;
+    bool     blockDotOnion    = true;
     int      proxyType        = nsIProtocolProxyService::PROXYCONFIG_DIRECT;
     bool     notifyResolution = false;
 
     nsAdoptingCString ipv4OnlyDomains;
     nsAdoptingCString localDomains;
 
     // read prefs
     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
@@ -561,16 +563,17 @@ nsDNSService::Init()
             defaultGracePeriod = val;
 
         // ASSUMPTION: pref branch does not modify out params on failure
         prefs->GetBoolPref(kPrefDisableIPv6, &disableIPv6);
         prefs->GetCharPref(kPrefIPv4OnlyDomains, getter_Copies(ipv4OnlyDomains));
         prefs->GetCharPref(kPrefDnsLocalDomains, getter_Copies(localDomains));
         prefs->GetBoolPref(kPrefDnsOfflineLocalhost, &offlineLocalhost);
         prefs->GetBoolPref(kPrefDisablePrefetch, &disablePrefetch);
+        prefs->GetBoolPref(kPrefBlockDotOnion, &blockDotOnion);
 
         // If a manual proxy is in use, disable prefetch implicitly
         prefs->GetIntPref("network.proxy.type", &proxyType);
         prefs->GetBoolPref(kPrefDnsNotifyResolution, &notifyResolution);
     }
 
     if (mFirstTime) {
         mFirstTime = false;
@@ -580,16 +583,17 @@ nsDNSService::Init()
             prefs->AddObserver(kPrefDnsCacheEntries, this, false);
             prefs->AddObserver(kPrefDnsCacheExpiration, this, false);
             prefs->AddObserver(kPrefDnsCacheGrace, this, false);
             prefs->AddObserver(kPrefIPv4OnlyDomains, this, false);
             prefs->AddObserver(kPrefDnsLocalDomains, this, false);
             prefs->AddObserver(kPrefDisableIPv6, this, false);
             prefs->AddObserver(kPrefDnsOfflineLocalhost, this, false);
             prefs->AddObserver(kPrefDisablePrefetch, this, false);
+            prefs->AddObserver(kPrefBlockDotOnion, this, false);
             prefs->AddObserver(kPrefDnsNotifyResolution, this, false);
 
             // Monitor these to see if there is a change in proxy configuration
             // If a manual proxy is in use, disable prefetch implicitly
             prefs->AddObserver("network.proxy.type", this, false);
         }
 
         nsCOMPtr<nsIObserverService> observerService =
@@ -613,16 +617,17 @@ nsDNSService::Init()
     if (NS_SUCCEEDED(rv)) {
         // now, set all of our member variables while holding the lock
         MutexAutoLock lock(mLock);
         mResolver = res;
         mIDN = idn;
         mIPv4OnlyDomains = ipv4OnlyDomains; // exchanges buffer ownership
         mOfflineLocalhost = offlineLocalhost;
         mDisableIPv6 = disableIPv6;
+        mBlockDotOnion = blockDotOnion;
 
         // Disable prefetching either by explicit preference or if a manual proxy is configured 
         mDisablePrefetch = disablePrefetch || (proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL);
 
         mLocalDomains.Clear();
         if (localDomains) {
             nsCCharSeparatedTokenizer tokenizer(localDomains, ',',
                                                 nsCCharSeparatedTokenizer::SEPARATOR_OPTIONAL);
@@ -693,32 +698,42 @@ nsDNSService::GetPrefetchEnabled(bool *o
 NS_IMETHODIMP
 nsDNSService::SetPrefetchEnabled(bool inVal)
 {
     MutexAutoLock lock(mLock);
     mDisablePrefetch = !inVal;
     return NS_OK;
 }
 
-static inline bool PreprocessHostname(bool              aLocalDomain,
-                                      const nsACString &aInput,
-                                      nsIIDNService    *aIDN,
-                                      nsACString       &aACE)
+nsresult
+nsDNSService::PreprocessHostname(bool              aLocalDomain,
+                                 const nsACString &aInput,
+                                 nsIIDNService    *aIDN,
+                                 nsACString       &aACE)
 {
+    // Enforce RFC 7686
+    if (mBlockDotOnion &&
+        StringEndsWith(aInput, NS_LITERAL_CSTRING(".onion"))) {
+        return NS_ERROR_UNKNOWN_HOST;
+    }
+
     if (aLocalDomain) {
         aACE.AssignLiteral("localhost");
-        return true;
+        return NS_OK;
     }
 
     if (!aIDN || IsASCII(aInput)) {
         aACE = aInput;
-        return true;
+        return NS_OK;
     }
 
-    return IsUTF8(aInput) && NS_SUCCEEDED(aIDN->ConvertUTF8toACE(aInput, aACE));
+    if (!(IsUTF8(aInput) && NS_SUCCEEDED(aIDN->ConvertUTF8toACE(aInput, aACE)))) {
+        return NS_ERROR_FAILURE;
+    }
+    return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDNSService::AsyncResolve(const nsACString  &aHostname,
                            uint32_t           flags,
                            nsIDNSListener    *listener,
                            nsIEventTarget    *target_,
                            nsICancelable    **result)
@@ -755,18 +770,20 @@ nsDNSService::AsyncResolveExtended(const
     if (mNotifyResolution) {
         NS_DispatchToMainThread(new NotifyDNSResolution(aHostname));
     }
 
     if (!res)
         return NS_ERROR_OFFLINE;
 
     nsCString hostname;
-    if (!PreprocessHostname(localDomain, aHostname, idn, hostname))
-        return NS_ERROR_FAILURE;
+    nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
 
     if (mOffline &&
         (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) {
         flags |= RESOLVE_OFFLINE;
     }
 
     // make sure JS callers get notification on the main thread
     nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
@@ -786,19 +803,18 @@ nsDNSService::AsyncResolveExtended(const
         new nsDNSAsyncRequest(res, hostname, listener, flags, af,
                               aNetworkInterface);
     if (!req)
         return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(*result = req);
 
     // addref for resolver; will be released when OnLookupComplete is called.
     NS_ADDREF(req);
-    nsresult rv = res->ResolveHost(req->mHost.get(), flags, af,
-                                   req->mNetworkInterface.get(),
-                                   req);
+    rv = res->ResolveHost(req->mHost.get(), flags, af,
+                          req->mNetworkInterface.get(), req);
     if (NS_FAILED(rv)) {
         NS_RELEASE(req);
         NS_RELEASE(*result);
     }
     return rv;
 }
 
 NS_IMETHODIMP
@@ -832,18 +848,20 @@ nsDNSService::CancelAsyncResolveExtended
         res = mResolver;
         idn = mIDN;
         localDomain = mLocalDomains.GetEntry(aHostname);
     }
     if (!res)
         return NS_ERROR_OFFLINE;
 
     nsCString hostname;
-    if (!PreprocessHostname(localDomain, aHostname, idn, hostname))
-        return NS_ERROR_FAILURE;
+    nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
 
     uint16_t af = GetAFForLookup(hostname, aFlags);
 
     res->CancelAsyncRequest(hostname.get(), aFlags, af,
                             nsPromiseFlatCString(aNetworkInterface).get(), aListener,
                             aReason);
     return NS_OK;
 }
@@ -867,18 +885,20 @@ nsDNSService::Resolve(const nsACString &
 
     if (mNotifyResolution) {
         NS_DispatchToMainThread(new NotifyDNSResolution(aHostname));
     }
 
     NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE);
 
     nsCString hostname;
-    if (!PreprocessHostname(localDomain, aHostname, idn, hostname))
-        return NS_ERROR_FAILURE;
+    nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
 
     if (mOffline &&
         (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) {
         flags |= RESOLVE_OFFLINE;
     }
 
     //
     // sync resolve: since the host resolver only works asynchronously, we need
@@ -892,17 +912,17 @@ nsDNSService::Resolve(const nsACString &
     if (!mon)
         return NS_ERROR_OUT_OF_MEMORY;
 
     PR_EnterMonitor(mon);
     nsDNSSyncRequest syncReq(mon);
 
     uint16_t af = GetAFForLookup(hostname, flags);
 
-    nsresult rv = res->ResolveHost(hostname.get(), flags, af, "", &syncReq);
+    rv = res->ResolveHost(hostname.get(), flags, af, "", &syncReq);
     if (NS_SUCCEEDED(rv)) {
         // wait for result
         while (!syncReq.mDone)
             PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
 
         if (NS_FAILED(syncReq.mStatus))
             rv = syncReq.mStatus;
         else {
--- a/netwerk/dns/nsDNSService2.h
+++ b/netwerk/dns/nsDNSService2.h
@@ -38,28 +38,34 @@ public:
 
 private:
     ~nsDNSService();
 
     static nsDNSService* GetSingleton();
 
     uint16_t GetAFForLookup(const nsACString &host, uint32_t flags);
 
+    nsresult PreprocessHostname(bool              aLocalDomain,
+                                const nsACString &aInput,
+                                nsIIDNService    *aIDN,
+                                nsACString       &aACE);
+
     RefPtr<nsHostResolver>  mResolver;
     nsCOMPtr<nsIIDNService>   mIDN;
 
     // mLock protects access to mResolver and mIPv4OnlyDomains
     mozilla::Mutex            mLock;
 
     // mIPv4OnlyDomains is a comma-separated list of domains for which only
     // IPv4 DNS lookups are performed. This allows the user to disable IPv6 on
     // a per-domain basis and work around broken DNS servers. See bug 68796.
     nsAdoptingCString                         mIPv4OnlyDomains;
     bool                                      mDisableIPv6;
     bool                                      mDisablePrefetch;
+    bool                                      mBlockDotOnion;
     bool                                      mFirstTime;
     bool                                      mOffline;
     bool                                      mNotifyResolution;
     bool                                      mOfflineLocalhost;
     nsTHashtable<nsCStringHashKey>            mLocalDomains;
 };
 
 #endif //nsDNSService2_h__
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_dns_onion.js
@@ -0,0 +1,70 @@
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+var mainThread = threadManager.currentThread;
+
+var onionPref;
+var localdomainPref;
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+// check that we don't lookup .onion
+var listenerBlock = {
+  onLookupComplete: function(inRequest, inRecord, inStatus) {
+    do_check_false(Components.isSuccessCode(inStatus));
+    do_test_dontBlock();
+  },
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIDNSListener) ||
+        aIID.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+// check that we do lookup .onion (via pref)
+var listenerDontBlock = {
+  onLookupComplete: function(inRequest, inRecord, inStatus) {
+    var answer = inRecord.getNextAddrAsString();
+    do_check_true(answer == "127.0.0.1" || answer == "::1");
+    all_done();
+  },
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIDNSListener) ||
+        aIID.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+function do_test_dontBlock() {
+  prefs.setBoolPref("network.dns.blockDotOnion", false);
+  dns.asyncResolve("private.onion", 0, listenerDontBlock, mainThread);
+}
+
+function do_test_block() {
+  prefs.setBoolPref("network.dns.blockDotOnion", true);
+  try {
+    dns.asyncResolve("private.onion", 0, listenerBlock, mainThread);
+  } catch (e) {
+    // it is ok for this negative test to fail fast
+    do_check_true(true);
+    do_test_dontBlock();
+  }
+}
+
+function all_done() {
+  // reset locally modified prefs
+  prefs.setCharPref("network.dns.localDomains", localdomainPref);
+  prefs.setBoolPref("network.dns.blockDotOnion", onionPref);
+  do_test_finished();
+}
+
+function run_test() {
+  onionPref = prefs.getBoolPref("network.dns.blockDotOnion");
+  localdomainPref = prefs.getCharPref("network.dns.localDomains");
+  prefs.setCharPref("network.dns.localDomains", "private.onion");
+  do_test_block();
+  do_test_pending();
+}
+
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -182,16 +182,17 @@ skip-if = bits != 32
 [test_cookie_header.js]
 [test_cookiejars.js]
 [test_cookiejars_safebrowsing.js]
 [test_dns_cancel.js]
 [test_dns_per_interface.js]
 [test_data_protocol.js]
 [test_dns_service.js]
 [test_dns_offline.js]
+[test_dns_onion.js]
 [test_dns_localredirect.js]
 [test_dns_proxy_bypass.js]
 [test_duplicate_headers.js]
 [test_chunked_responses.js]
 [test_content_length_underrun.js]
 [test_event_sink.js]
 [test_extract_charset_from_content_type.js]
 [test_fallback_no-cache-entry_canceled.js]