Landing followup fix for bug 402983 and re-enabling the new stricter file URI security policies. r+sr=bzbarsky@mit.edu
authorjst@mozilla.org
Sat, 22 Mar 2008 09:50:47 -0700
changeset 13471 1c9d050ca983a13b4f1a63bcff889f6bfb2d0545
parent 13470 f4a63be595bd8411b93b99ccefa4690bc167ed7a
child 13472 56db9dd439950fd21c3d3e1441dc01e637a034ce
push idunknown
push userunknown
push dateunknown
bugs402983
milestone1.9b5pre
Landing followup fix for bug 402983 and re-enabling the new stricter file URI security policies. r+sr=bzbarsky@mit.edu
caps/include/nsScriptSecurityManager.h
caps/src/nsPrincipal.cpp
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
content/base/src/nsObjectLoadingContent.cpp
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
layout/style/nsCSSLoader.cpp
modules/libpref/src/init/all.js
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -405,16 +405,23 @@ public:
 
     static nsresult 
     ReportError(JSContext* cx, const nsAString& messageTag,
                 nsIURI* aSource, nsIURI* aTarget);
     static nsresult
     CheckSameOriginPrincipal(nsIPrincipal* aSubject,
                              nsIPrincipal* aObject,
                              PRBool aIsCheckConnect);
+
+    static PRBool
+    GetStrictFileOriginPolicy()
+    {
+        return sStrictFileOriginPolicy;
+    }
+
 private:
 
     // GetScriptSecurityManager is the only call that can make one
     nsScriptSecurityManager();
     virtual ~nsScriptSecurityManager();
 
     static JSBool JS_DLL_CALLBACK
     CheckObjectAccess(JSContext *cx, JSObject *obj,
--- a/caps/src/nsPrincipal.cpp
+++ b/caps/src/nsPrincipal.cpp
@@ -40,16 +40,18 @@
 
 #include "nscore.h"
 #include "nsScriptSecurityManager.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "plstr.h"
 #include "nsCRT.h"
 #include "nsIURI.h"
+#include "nsIFileURL.h"
+#include "nsIProtocolHandler.h"
 #include "nsNetUtil.h"
 #include "nsJSPrincipals.h"
 #include "nsVoidArray.h"
 #include "nsHashtable.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
@@ -299,20 +301,102 @@ nsPrincipal::Equals(nsIPrincipal *aOther
 }
 
 NS_IMETHODIMP
 nsPrincipal::Subsumes(nsIPrincipal *aOther, PRBool *aResult)
 {
   return Equals(aOther, aResult);
 }
 
+static PRBool
+URIIsLocalFile(nsIURI *aURI)
+{
+  PRBool isFile;
+  nsCOMPtr<nsINetUtil> util = do_GetIOService();
+
+  return util && NS_SUCCEEDED(util->ProtocolHasFlags(aURI,
+                                nsIProtocolHandler::URI_IS_LOCAL_FILE,
+                                &isFile)) &&
+         isFile;
+}
+
 NS_IMETHODIMP
 nsPrincipal::CheckMayLoad(nsIURI* aURI, PRBool aReport)
 {
   if (!nsScriptSecurityManager::SecurityCompareURIs(mCodebase, aURI)) {
+    if (nsScriptSecurityManager::GetStrictFileOriginPolicy() &&
+        URIIsLocalFile(aURI)) {
+      nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(aURI));
+
+      if (!URIIsLocalFile(mCodebase)) {
+        // If the codebase is not also a file: uri then forget it
+        // (don't want resource: principals in a file: doc)
+        //
+        // note: we're not de-nesting jar: uris here, we want to
+        // keep archive content bottled up in its own little island
+
+        if (aReport) {
+          nsScriptSecurityManager::ReportError(
+            nsnull, NS_LITERAL_STRING("CheckSameOriginError"), mCodebase, aURI);
+        }
+
+        return NS_ERROR_DOM_BAD_URI;
+      }
+
+      //
+      // pull out the internal files
+      //
+      nsCOMPtr<nsIFileURL> codebaseFileURL(do_QueryInterface(mCodebase));
+      nsCOMPtr<nsIFile> targetFile;
+      nsCOMPtr<nsIFile> codebaseFile;
+      PRBool targetIsDir;
+
+      // Make sure targetFile is not a directory (bug 209234)
+      // and that it exists w/out unescaping (bug 395343)
+
+      if (!codebaseFileURL || !fileURL ||
+          NS_FAILED(fileURL->GetFile(getter_AddRefs(targetFile))) ||
+          NS_FAILED(codebaseFileURL->GetFile(getter_AddRefs(codebaseFile))) ||
+          !targetFile || !codebaseFile ||
+          NS_FAILED(targetFile->Normalize()) ||
+          NS_FAILED(codebaseFile->Normalize()) ||
+          NS_FAILED(targetFile->IsDirectory(&targetIsDir)) ||
+          targetIsDir) {
+        if (aReport) {
+          nsScriptSecurityManager::ReportError(
+            nsnull, NS_LITERAL_STRING("CheckSameOriginError"), mCodebase, aURI);
+        }
+
+        return NS_ERROR_DOM_BAD_URI;
+      }
+
+      //
+      // If the file to be loaded is in a subdirectory of the codebase
+      // (or same-dir if codebase is not a directory) then it will
+      // inherit its codebase principal and be scriptable by that codebase.
+      //
+      PRBool codebaseIsDir;
+      PRBool contained = PR_FALSE;
+      nsresult rv = codebaseFile->IsDirectory(&codebaseIsDir);
+      if (NS_SUCCEEDED(rv) && codebaseIsDir) {
+        rv = codebaseFile->Contains(targetFile, PR_TRUE, &contained);
+      }
+      else {
+        nsCOMPtr<nsIFile> codebaseParent;
+        rv = codebaseFile->GetParent(getter_AddRefs(codebaseParent));
+        if (NS_SUCCEEDED(rv) && codebaseParent) {
+          rv = codebaseParent->Contains(targetFile, PR_TRUE, &contained);
+        }
+      }
+
+      if (NS_SUCCEEDED(rv) && contained) {
+        return NS_OK;
+      }
+    }
+
     if (aReport) {
       nsScriptSecurityManager::ReportError(
         nsnull, NS_LITERAL_STRING("CheckSameOriginError"), mCodebase, aURI);
     }
     
     return NS_ERROR_DOM_BAD_URI;
   }
 
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1159,16 +1159,21 @@ public:
 
   /**
    * Hide any XUL popups associated with aDocument, including any documents
    * displayed in child frames.
    */
   static void HidePopupsInDocument(nsIDocument* aDocument);
 
   /**
+   * Return true if aURI is a local file URI (i.e. file://).
+   */
+  static PRBool URIIsLocalFile(nsIURI *aURI);
+
+  /**
    * Get the application manifest URI for this context.  The manifest URI
    * is specified in the manifest= attribute of the root element of the
    * toplevel window.
    *
    * @param aWindow The context to check.
    * @param aURI The manifest URI.
    */
   static void GetOfflineAppManifest(nsIDOMWindow *aWindow, nsIURI **aURI);
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -4061,13 +4061,26 @@ nsContentUtils::HidePopupsInDocument(nsI
     nsCOMPtr<nsIDocShellTreeItem> docShellToHide = do_QueryInterface(container);
     if (docShellToHide)
       pm->HidePopupsInDocShell(docShellToHide);
   }
 #endif
 }
 
 /* static */
+PRBool
+nsContentUtils::URIIsLocalFile(nsIURI *aURI)
+{
+  PRBool isFile;
+  nsCOMPtr<nsINetUtil> util = do_QueryInterface(sIOService);
+
+  return util && NS_SUCCEEDED(util->ProtocolHasFlags(aURI,
+                                nsIProtocolHandler::URI_IS_LOCAL_FILE,
+                                &isFile)) &&
+         isFile;
+}
+
+/* static */
 void
 nsAutoGCRoot::Shutdown()
 {
   NS_IF_RELEASE(sJSRuntimeService);
 }
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -1240,17 +1240,20 @@ nsObjectLoadingContent::LoadObject(nsIUR
   }
 
   // Set up the channel's principal and such, like nsDocShell::DoURILoad does
   PRBool inheritPrincipal;
   rv = NS_URIChainHasFlags(aURI,
                            nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
                            &inheritPrincipal);
   NS_ENSURE_SUCCESS(rv, rv);
-  if (inheritPrincipal || IsAboutBlank(aURI)) {
+  if (inheritPrincipal || IsAboutBlank(aURI) ||
+      (nsContentUtils::URIIsLocalFile(aURI) &&
+       NS_SUCCEEDED(thisContent->NodePrincipal()->CheckMayLoad(aURI,
+                                                               PR_FALSE)))) {
     chan->SetOwner(thisContent->NodePrincipal());
   }
 
   nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(chan);
   if (scriptChannel) {
     // Allow execution against our context if the principals match
     scriptChannel->
       SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -285,17 +285,16 @@ nsDocShell::nsDocShell():
     mAllowImages(PR_TRUE),
     mFocusDocFirst(PR_FALSE),
     mHasFocus(PR_FALSE),
     mCreatingDocument(PR_FALSE),
     mUseErrorPages(PR_FALSE),
     mObserveErrorPages(PR_TRUE),
     mAllowAuth(PR_TRUE),
     mAllowKeywordFixup(PR_FALSE),
-    mStrictFilePolicy(PR_TRUE),
     mFiredUnloadEvent(PR_FALSE),
     mEODForCurrentDocument(PR_FALSE),
     mURIResultedInDocument(PR_FALSE),
     mIsBeingDestroyed(PR_FALSE),
     mIsExecutingOnLoadHandler(PR_FALSE),
     mIsPrintingOrPP(PR_FALSE),
     mSavingOldViewer(PR_FALSE),
     mAppType(nsIDocShell::APP_TYPE_UNKNOWN),
@@ -3588,20 +3587,16 @@ nsDocShell::Create()
         rv = mPrefs->GetBoolPref("browser.frame.validate_origin", &tmpbool);
         if (NS_SUCCEEDED(rv)) {
             gValidateOrigin = tmpbool;
         } else {
             gValidateOrigin = PR_TRUE;
         }
     }
 
-    rv = mPrefs->GetBoolPref("security.fileuri.strict_origin_policy", &tmpbool);
-    if (NS_SUCCEEDED(rv))
-        mStrictFilePolicy = tmpbool;
-
     // Should we use XUL error pages instead of alerts if possible?
     rv = mPrefs->GetBoolPref("browser.xul.error_pages.enabled", &tmpbool);
     if (NS_SUCCEEDED(rv))
         mUseErrorPages = tmpbool;
 
     nsCOMPtr<nsIPrefBranch2> prefs(do_QueryInterface(mPrefs, &rv));
     if (NS_SUCCEEDED(rv) && mObserveErrorPages) {
         prefs->AddObserver("browser.xul.error_pages.enabled", this, PR_FALSE);
@@ -7352,76 +7347,20 @@ nsDocShell::DoURILoad(nsIURI * aURI,
     //
     // file: uri special-casing
     //
     // If this is a file: load opened from another file: then it may need
     // to inherit the owner from the referrer so they can script each other.
     // If we don't set the owner explicitly then each file: gets an owner
     // based on its own codebase later.
     //
-    if (mStrictFilePolicy && URIIsLocalFile(aURI)) {
-        nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(aURI));
-        nsCOMPtr<nsIPrincipal> ownerPrincipal(do_QueryInterface(aOwner));
-        nsCOMPtr<nsIURI> ownerURI;
-        if (ownerPrincipal) {
-             ownerPrincipal->GetURI(getter_AddRefs(ownerURI));
-        }
-
-        if (!URIIsLocalFile(ownerURI)) {
-            // If the owner is not also a file: uri then forget it
-            // (don't want resource: principals in a file: doc)
-            //
-            // note: we're not de-nesting jar: uris here, we want to
-            // keep archive content bottled up in its own little island
-            ownerURI = nsnull;
-        }
-
-        //
-        // pull out the internal files
-        //
-        nsCOMPtr<nsIFileURL> ownerFileURL(do_QueryInterface(ownerURI));
-        nsCOMPtr<nsIFile> targetFile;
-        nsCOMPtr<nsIFile> ownerFile;
-        if (ownerFileURL &&
-            NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(targetFile))) &&
-            NS_SUCCEEDED(ownerFileURL->GetFile(getter_AddRefs(ownerFile)))) {
-            //
-            // Make sure targetFile is not a directory (bug 209234)
-            // and that it exists w/out unescaping (bug 395343)
-            //
-            PRBool targetIsDir;
-            if (targetFile && ownerFile && 
-                NS_SUCCEEDED(targetFile->Normalize()) &&
-                NS_SUCCEEDED(ownerFile->Normalize()) &&
-                NS_SUCCEEDED(targetFile->IsDirectory(&targetIsDir)) &&
-                !targetIsDir) {
-                //
-                // If the file to be loaded is in a subdirectory of the owner
-                // (or same-dir if owner is not a directory) then it will
-                // inherit its owner principal and be scriptable by that owner.
-                //
-                PRBool ownerIsDir;
-                PRBool contained = PR_FALSE;
-                rv = ownerFile->IsDirectory(&ownerIsDir);
-                if (NS_SUCCEEDED(rv) && ownerIsDir) {
-                    rv = ownerFile->Contains(targetFile, PR_TRUE, &contained);
-                }
-                else {
-                    nsCOMPtr<nsIFile> ownerParent;
-                    rv = ownerFile->GetParent(getter_AddRefs(ownerParent));
-                    if (NS_SUCCEEDED(rv) && ownerParent) {
-                        rv = ownerParent->Contains(targetFile, PR_TRUE, &contained);
-                    }
-                }
-
-                if (NS_SUCCEEDED(rv) && contained) {
-                    channel->SetOwner(aOwner);
-                }
-            }
-        }
+    nsCOMPtr<nsIPrincipal> ownerPrincipal(do_QueryInterface(aOwner));
+    if (URIIsLocalFile(aURI) && ownerPrincipal &&
+        NS_SUCCEEDED(ownerPrincipal->CheckMayLoad(aURI, PR_FALSE))) {
+        channel->SetOwner(aOwner);
     }
 
     nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel);
     if (scriptChannel) {
         // Allow execution against our context if the principals match
         scriptChannel->
             SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
     }
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -546,17 +546,16 @@ protected:
     PRPackedBool               mAllowImages;
     PRPackedBool               mFocusDocFirst;
     PRPackedBool               mHasFocus;
     PRPackedBool               mCreatingDocument; // (should be) debugging only
     PRPackedBool               mUseErrorPages;
     PRPackedBool               mObserveErrorPages;
     PRPackedBool               mAllowAuth;
     PRPackedBool               mAllowKeywordFixup;
-    PRPackedBool               mStrictFilePolicy;
 
     // This boolean is set to true right before we fire pagehide and generally
     // unset when we embed a new content viewer.  While it's true no navigation
     // is allowed in this docshell.
     PRPackedBool               mFiredUnloadEvent;
 
     // this flag is for bug #21358. a docshell may load many urls
     // which don't result in new documents being created (i.e. a new
--- a/layout/style/nsCSSLoader.cpp
+++ b/layout/style/nsCSSLoader.cpp
@@ -1431,27 +1431,29 @@ CSSLoaderImpl::LoadSheet(SheetLoadData* 
   // this before opening it, so it's only treated as a hint.
   channel->SetContentType(NS_LITERAL_CSTRING("text/css"));
 
   if (aLoadData->mLoaderPrincipal) {
     PRBool inherit;
     rv = NS_URIChainHasFlags(aLoadData->mURI,
                              nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
                              &inherit);
-    if (NS_SUCCEEDED(rv) && inherit) {
+    if ((NS_SUCCEEDED(rv) && inherit) ||
+        (nsContentUtils::URIIsLocalFile(aLoadData->mURI) &&
+         NS_SUCCEEDED(aLoadData->mLoaderPrincipal->
+                      CheckMayLoad(aLoadData->mURI, PR_FALSE)))) {
       channel->SetOwner(aLoadData->mLoaderPrincipal);
     }
   }
 
   // We don't have to hold on to the stream loader.  The ownership
   // model is: Necko owns the stream loader, which owns the load data,
   // which owns us
   nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
-  rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader),
-                                 aLoadData);
+  rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
 
   if (NS_SUCCEEDED(rv))
     rv = channel->AsyncOpen(streamLoader, nsnull);
 
 #ifdef DEBUG
   mSyncCallback = PR_FALSE;
 #endif
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -496,17 +496,17 @@ pref("javascript.options.strict",       
 pref("javascript.options.relimit",          false);
 
 // advanced prefs
 pref("security.enable_java",                true);
 pref("advanced.mailftp",                    false);
 pref("image.animation_mode",                "normal");
 
 // Same-origin policy for file URIs, "false" is traditional
-pref("security.fileuri.strict_origin_policy", false);
+pref("security.fileuri.strict_origin_policy", true);
 
 // If there is ever a security firedrill that requires
 // us to block certian ports global, this is the pref 
 // to use.  Is is a comma delimited list of port numbers
 // for example:
 //   pref("network.security.ports.banned", "1,2,3,4,5");
 // prevents necko connecting to ports 1-5 unless the protocol
 // overrides.