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 607778 6506888c5f7b25b2a690117fb60fe79a90d274e6
parent 607777 f5687797261d69daf1a788d15275909be03c4c2f
child 607779 734640228fbc00212f96ea1a362247887d739b73
push id68110
push userbmo:nchen@mozilla.com
push dateWed, 12 Jul 2017 21:26:51 +0000
reviewersjduell
bugs1376459
milestone56.0a1
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))