Bug 1376459 - Prevent Offline Application Cache fallback namespace and fallback page be from a parent directory relative to the manifest. r=jduell
authorHonza Bambas <honzab.moz@firemni.cz>
Wed, 12 Jul 2017 16:58:19 -0400
changeset 419775 6506888c5f7b25b2a690117fb60fe79a90d274e6
parent 419774 f5687797261d69daf1a788d15275909be03c4c2f
child 419776 734640228fbc00212f96ea1a362247887d739b73
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs1376459
milestone56.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 1376459 - Prevent Offline Application Cache fallback namespace and fallback page be from a parent directory relative to the manifest. r=jduell
netwerk/protocol/http/nsHttpChannel.cpp
uriloader/prefetch/nsOfflineCacheUpdate.cpp
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -191,16 +191,60 @@ 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);
+
+    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()) &&
@@ -3498,16 +3542,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;
@@ -4597,16 +4647,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;
 }
 
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -796,16 +796,47 @@ nsOfflineManifestItem::AddNamespace(uint
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mNamespaces->AppendElement(ns, false);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
+static nsresult
+GetURIDirectory(nsIURI* uri, nsACString &directory)
+{
+  nsresult rv;
+
+  nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = url->GetDirectory(directory);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+static nsresult
+CheckFileContainedInPath(nsIURI* file, nsACString const &masterDirectory)
+{
+  nsresult rv;
+
+  nsAutoCString directory;
+  rv = GetURIDirectory(file, directory);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool contains = StringBeginsWith(directory, masterDirectory);
+  if (!contains) {
+      return NS_ERROR_DOM_BAD_URI;
+  }
+
+  return NS_OK;
+}
+
 nsresult
 nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin,
                                           const nsCString::const_iterator &aEnd)
 {
     nsCString::const_iterator begin = aBegin;
     nsCString::const_iterator end = aEnd;
 
     // all lines ignore trailing spaces and tabs
@@ -940,16 +971,35 @@ nsOfflineManifestItem::HandleManifestLin
         if (NS_FAILED(rv))
             break;
         if (NS_FAILED(DropReferenceFromURL(fallbackURI)))
             break;
         rv = fallbackURI->GetAsciiSpec(fallbackSpec);
         if (NS_FAILED(rv))
             break;
 
+        // The following set of checks is preventing a website under
+        // a subdirectory to add fallback pages for the whole origin
+        // (or a parent directory) to prevent fallback attacks.
+        nsAutoCString manifestDirectory;
+        rv = GetURIDirectory(mURI, manifestDirectory);
+        if (NS_FAILED(rv)) {
+            break;
+        }
+
+        rv = CheckFileContainedInPath(namespaceURI, manifestDirectory);
+        if (NS_FAILED(rv)) {
+            break;
+        }
+
+        rv = CheckFileContainedInPath(fallbackURI, manifestDirectory);
+        if (NS_FAILED(rv)) {
+            break;
+        }
+
         // Manifest and namespace must be same origin
         if (!NS_SecurityCompareURIs(mURI, namespaceURI,
                                     mStrictFileOriginPolicy))
             break;
 
         // Fallback and namespace must be same origin
         if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI,
                                     mStrictFileOriginPolicy))