Bug 1376459 - Prevent Offline Application Cache fallback namespace and fallback page be from a parent directory relative to the manifest. r=jduell, a=jcristau
authorHonza Bambas <honzab.moz@firemni.cz>
Mon, 31 Jul 2017 07:57:53 -0400
changeset 414495 c5cb6429eb36cf2c3110d464f95b8760ae1c524a
parent 414494 66bd0b4c3b50686ca081aafcc9a8c1a26b77df00
child 414496 2cd8c7f13e6c5ace6955da85a4d95f6e65caad2e
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell, jcristau
bugs1376459
milestone55.0
Bug 1376459 - Prevent Offline Application Cache fallback namespace and fallback page be from a parent directory relative to the manifest. r=jduell, a=jcristau
netwerk/protocol/http/nsHttpChannel.cpp
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -189,16 +189,68 @@ Hash(const char *buf, nsACString &hash)
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = hasher->Finish(true, hash);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
+bool
+IsInSubpathOfAppCacheManifest(nsIApplicationCache *cache, nsACString const& uriSpec)
+{
+    MOZ_ASSERT(cache);
+
+    static bool sForbid = true;
+    static nsresult once = Preferences::AddBoolVarCache(&sForbid, "network.appcache.forbid-fallback-outside-manifest-path", true);
+    Unused << once;
+
+    if (!sForbid) {
+      return true;
+    }
+
+    nsresult rv;
+
+    nsCOMPtr<nsIURI> uri;
+    rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+
+    nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+
+    nsAutoCString directory;
+    rv = url->GetDirectory(directory);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+
+    nsCOMPtr<nsIURI> manifestURI;
+    rv = cache->GetManifestURI(getter_AddRefs(manifestURI));
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+
+    nsCOMPtr<nsIURL> manifestURL(do_QueryInterface(manifestURI, &rv));
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+
+    nsAutoCString manifestDirectory;
+    rv = manifestURL->GetDirectory(manifestDirectory);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+
+    return StringBeginsWith(directory, manifestDirectory);
+}
+
 } // unnamed namespace
 
 // We only treat 3xx responses as redirects if they have a Location header and
 // the status code is in a whitelist.
 bool
 nsHttpChannel::WillRedirect(nsHttpResponseHead * response)
 {
     return IsRedirectStatus(response->Status()) &&
@@ -3438,16 +3490,22 @@ nsHttpChannel::ProcessFallback(bool *wai
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
         // This cache points to a fallback that refers to a different
         // manifest.  Refuse to fall back.
         return NS_OK;
     }
 
+    if (!IsInSubpathOfAppCacheManifest(mApplicationCache, mFallbackKey)) {
+        // Refuse to fallback if the fallback key is not contained in the same
+        // path as the cache manifest.
+        return NS_OK;
+    }
+
     MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
                "Fallback entry not marked correctly!");
 
     // Kill any offline cache entry, and disable offline caching for the
     // fallback.
     if (mOfflineCacheEntry) {
         mOfflineCacheEntry->AsyncDoom(nullptr);
         mOfflineCacheEntry = nullptr;
@@ -4514,16 +4572,27 @@ nsHttpChannel::OnOfflineCacheEntryAvaila
 
             // ... and if there were an application cache entry,
             // we would have found it earlier.
             return NS_ERROR_CACHE_KEY_NOT_FOUND;
         }
 
         if (namespaceType &
             nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
+
+            nsAutoCString namespaceSpec;
+            rv = namespaceEntry->GetNamespaceSpec(namespaceSpec);
+            NS_ENSURE_SUCCESS(rv, rv);
+
+            // This prevents fallback attacks injected by an insecure subdirectory
+            // for the whole origin (or a parent directory).
+            if (!IsInSubpathOfAppCacheManifest(mApplicationCache, namespaceSpec)) {
+                return NS_OK;
+            }
+
             rv = namespaceEntry->GetData(mFallbackKey);
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     return NS_OK;
 }