Landing fix for bug 402983. Make security checks on file:// URIs symmetric. Patch by dveditz@cruzio.com, r=jonas@sicking.cc,bzbarsky@mit.edu. jst@mozilla.org
authorjst@mozilla.org
Thu, 20 Mar 2008 21:39:08 -0700
changeset 13414 095e70787c44ac2251627a29a195c4a6a78ecec6
parent 13413 78f89dcd38603318ab1e30a6c01fc6ab3d21223c
child 13415 4d031706c37751463a586231b45a2d1ec0033f7d
push idunknown
push userunknown
push dateunknown
reviewersjonas
bugs402983
milestone1.9b5pre
Landing fix for bug 402983. Make security checks on file:// URIs symmetric. Patch by dveditz@cruzio.com, r=jonas@sicking.cc,bzbarsky@mit.edu. jst@mozilla.org
caps/include/nsScriptSecurityManager.h
caps/src/nsScriptSecurityManager.cpp
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
modules/libpref/src/init/all.js
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -541,20 +541,16 @@ private:
     InitDomainPolicy(JSContext* cx, const char* aPolicyName,
                      DomainPolicy* aDomainPolicy);
 
     nsresult
     InitPrincipals(PRUint32 prefCount, const char** prefNames,
                    nsISecurityPref* securityPref);
 
 
-    /* encapsulate the file comparison rules */
-    static PRBool SecurityCompareFileURIs(nsIURI* aSourceURI,
-                                          nsIURI* aTargetURI);
-
 #ifdef XPC_IDISPATCH_SUPPORT
     // While this header is included outside of caps, this class isn't 
     // referenced so this should be fine.
     nsresult
     CheckComponentPermissions(JSContext *cx, const nsCID &aCID);
 #endif
 #ifdef DEBUG_CAPS_HACKER
     void
@@ -585,29 +581,17 @@ private:
     PRPackedBool mIsMailJavaScriptEnabled;
     PRPackedBool mIsWritingPrefs;
     PRPackedBool mPolicyPrefsChanged;
 #ifdef XPC_IDISPATCH_SUPPORT    
     PRPackedBool mXPCDefaultGrantAll;
     static const char sXPCDefaultGrantAllName[];
 #endif
 
-    static PRInt32 sFileURIOriginPolicy;
+    static PRBool sStrictFileOriginPolicy;
 
     static nsIIOService    *sIOService;
     static nsIXPConnect    *sXPConnect;
     static nsIStringBundle *sStrBundle;
     static JSRuntime       *sRuntime;
 };
 
-// Levels for file: URI same-origin policy:
-//   self:        same-origin only with itself
-//   samedir:     same-origin with files having the same path
-//   subdir:      same-origin with files having longer paths (asymetric)
-//   anyfile:     same-origin with any other file: URI (but not directories)
-//   traditional: any local file, any directory
-#define FILEURI_SOP_SELF        0
-#define FILEURI_SOP_SAMEDIR     1
-#define FILEURI_SOP_SUBDIR      2
-#define FILEURI_SOP_ANYFILE     3
-#define FILEURI_SOP_TRADITIONAL 4
-
 #endif // nsScriptSecurityManager_h__
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -20,16 +20,17 @@
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Norris Boyd
  *   Mitch Stoltz
  *   Steve Morse
  *   Christopher A. Aillon
  *   Giorgio Maone
+ *   Daniel Veditz
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -93,17 +94,17 @@
 #include "nsCDefaultURIFixup.h"
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 nsIIOService    *nsScriptSecurityManager::sIOService = nsnull;
 nsIXPConnect    *nsScriptSecurityManager::sXPConnect = nsnull;
 nsIStringBundle *nsScriptSecurityManager::sStrBundle = nsnull;
 JSRuntime       *nsScriptSecurityManager::sRuntime   = 0;
-PRInt32 nsScriptSecurityManager::sFileURIOriginPolicy = FILEURI_SOP_SELF;
+PRBool nsScriptSecurityManager::sStrictFileOriginPolicy = PR_TRUE;
 
 // Info we need about the JSClasses used by XPConnects wrapped
 // natives, to avoid having to QI to nsIXPConnectWrappedNative all the
 // time when doing security checks.
 static const JSClass *sXPCWrappedNativeJSClass;
 static JSGetObjectOps sXPCWrappedNativeGetObjOps1;
 static JSGetObjectOps sXPCWrappedNativeGetObjOps2;
 
@@ -308,17 +309,26 @@ nsScriptSecurityManager::SecurityCompare
         !sameScheme)
     {
         // Not same-origin if schemes differ
         return PR_FALSE;
     }
 
     // special handling for file: URIs
     if (targetScheme.EqualsLiteral("file"))
-        return SecurityCompareFileURIs( sourceBaseURI, targetBaseURI );
+    {
+        // in traditional unsafe behavior all files are the same origin
+        if (!sStrictFileOriginPolicy)
+            return PR_TRUE;
+
+         // Otherwise they had better match
+         PRBool filesAreEqual = PR_FALSE;
+         nsresult rv = sourceBaseURI->Equals(targetBaseURI, &filesAreEqual);
+         return NS_SUCCEEDED(rv) && filesAreEqual;
+    }
 
     // Special handling for mailnews schemes
     if (targetScheme.EqualsLiteral("imap") ||
         targetScheme.EqualsLiteral("mailbox") ||
         targetScheme.EqualsLiteral("news"))
     {
         // Each message is a distinct trust domain; use the 
         // whole spec for comparison
@@ -364,93 +374,16 @@ nsScriptSecurityManager::SecurityCompare
         else if (targetPort == -1)
             targetPort = defaultPort;
         result = targetPort == sourcePort;
     }
 
     return result;
 }
 
-// helper function for SecurityCompareURIs
-PRBool
-nsScriptSecurityManager::SecurityCompareFileURIs(nsIURI* aSourceURI,
-                                                 nsIURI* aTargetURI)
-{
-    // in traditional unsafe behavior all files are the same origin
-    if (sFileURIOriginPolicy == FILEURI_SOP_TRADITIONAL)
-        return PR_TRUE;
-
-
-    // Check simplest and default FILEURI_SOP_SELF case first:
-    // If they're equal or if the policy says they must be, we're done
-    PRBool filesAreEqual = PR_FALSE;
-    if (NS_FAILED( aSourceURI->Equals(aTargetURI, &filesAreEqual) ))
-        return PR_FALSE;
-    if (filesAreEqual || sFileURIOriginPolicy == FILEURI_SOP_SELF)
-        return filesAreEqual;
-
-
-    // disallow access to directory listings (bug 209234)
-    PRBool targetIsDir = PR_TRUE;
-    nsCOMPtr<nsIFile> targetFile;
-    nsCOMPtr<nsIFileURL> targetFileURL( do_QueryInterface(aTargetURI) );
-
-    if (!targetFileURL ||
-        NS_FAILED( targetFileURL->GetFile(getter_AddRefs(targetFile)) ) ||
-        NS_FAILED( targetFile->IsDirectory(&targetIsDir) ) ||
-        targetIsDir)
-    {
-        return PR_FALSE;
-    }
-
-
-    // For policy ANYFILE we're done
-    if (sFileURIOriginPolicy == FILEURI_SOP_ANYFILE)
-        return PR_TRUE;
-
-
-    // source parent directory is needed for remaining policies
-    nsCOMPtr<nsIFile> sourceFile;
-    nsCOMPtr<nsIFile> sourceParent;
-    nsCOMPtr<nsIFileURL> sourceFileURL( do_QueryInterface(aSourceURI) );
-
-    if (!sourceFileURL ||
-        NS_FAILED( sourceFileURL->GetFile(getter_AddRefs(sourceFile)) ) ||
-        NS_FAILED( sourceFile->GetParent(getter_AddRefs(sourceParent)) ) ||
-        !sourceParent)
-    {
-        // unexpected error
-        return PR_FALSE;
-    }
-
-    // check remaining policies
-    if (sFileURIOriginPolicy == FILEURI_SOP_SAMEDIR)
-    {
-        // file: URIs in the same directory have the same origin
-        PRBool sameParent = PR_FALSE;
-        nsCOMPtr<nsIFile> targetParent;
-        if (NS_FAILED( targetFile->GetParent(getter_AddRefs(targetParent)) ) ||
-            NS_FAILED( sourceParent->Equals(targetParent, &sameParent) ))
-            return PR_FALSE;
-        return sameParent;
-    }
-
-    if (sFileURIOriginPolicy == FILEURI_SOP_SUBDIR)
-    {
-        // file: URIs can access files in the same or lower directories
-        PRBool isChild = PR_FALSE;
-        if (NS_FAILED( sourceParent->Contains(targetFile, PR_TRUE, &isChild) ))
-            return PR_FALSE;
-        return isChild;
-    }
-
-    NS_NOTREACHED("invalid file uri policy setting");
-    return PR_FALSE;
-}
-
 NS_IMETHODIMP
 nsScriptSecurityManager::GetChannelPrincipal(nsIChannel* aChannel,
                                              nsIPrincipal** aPrincipal)
 {
     NS_PRECONDITION(aChannel, "Must have channel!");
     nsCOMPtr<nsISupports> owner;
     aChannel->GetOwner(getter_AddRefs(owner));
     if (owner) {
@@ -3887,17 +3820,17 @@ nsScriptSecurityManager::InitPrincipals(
     return NS_OK;
 }
 
 const char nsScriptSecurityManager::sJSEnabledPrefName[] =
     "javascript.enabled";
 const char nsScriptSecurityManager::sJSMailEnabledPrefName[] =
     "javascript.allow.mailnews";
 const char nsScriptSecurityManager::sFileOriginPolicyPrefName[] =
-    "security.fileuri.origin_policy";
+    "security.fileuri.strict_origin_policy";
 #ifdef XPC_IDISPATCH_SUPPORT
 const char nsScriptSecurityManager::sXPCDefaultGrantAllName[] =
     "security.classID.allowByDefault";
 #endif
 
 inline void
 nsScriptSecurityManager::ScriptSecurityPrefChanged()
 {
@@ -3905,19 +3838,18 @@ nsScriptSecurityManager::ScriptSecurityP
     nsresult rv = mSecurityPref->SecurityGetBoolPref(sJSEnabledPrefName, &temp);
     // JavaScript defaults to enabled in failure cases.
     mIsJavaScriptEnabled = NS_FAILED(rv) || temp;
 
     rv = mSecurityPref->SecurityGetBoolPref(sJSMailEnabledPrefName, &temp);
     // JavaScript in Mail defaults to disabled in failure cases.
     mIsMailJavaScriptEnabled = NS_SUCCEEDED(rv) && temp;
 
-    PRInt32 policy;
-    rv = mSecurityPref->SecurityGetIntPref(sFileOriginPolicyPrefName, &policy);
-    sFileURIOriginPolicy = NS_SUCCEEDED(rv) ? policy : FILEURI_SOP_SELF;
+    rv = mSecurityPref->SecurityGetBoolPref(sFileOriginPolicyPrefName, &temp);
+    sStrictFileOriginPolicy = NS_SUCCEEDED(rv) && temp;
 
 #ifdef XPC_IDISPATCH_SUPPORT
     rv = mSecurityPref->SecurityGetBoolPref(sXPCDefaultGrantAllName, &temp);
     // Granting XPC Priveleges defaults to disabled in failure cases.
     mXPCDefaultGrantAll = NS_SUCCEEDED(rv) && temp;
 #endif
 }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -128,16 +128,17 @@
 // Interfaces Needed
 #include "nsIUploadChannel.h"
 #include "nsIProgressEventSink.h"
 #include "nsIWebProgress.h"
 #include "nsILayoutHistoryState.h"
 #include "nsITimer.h"
 #include "nsISHistoryInternal.h"
 #include "nsIPrincipal.h"
+#include "nsIFileURL.h"
 #include "nsIHistoryEntry.h"
 #include "nsISHistoryListener.h"
 #include "nsIWindowWatcher.h"
 #include "nsIPromptFactory.h"
 #include "nsIObserver.h"
 #include "nsINestedURI.h"
 #include "nsITransportSecurityInfo.h"
 #include "nsINSSErrorsService.h"
@@ -284,16 +285,17 @@ 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),
@@ -3586,32 +3588,36 @@ 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);
     }
 
     nsCOMPtr<nsIObserverService> serv = do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
     if (serv) {
         const char* msg = mItemType == typeContent ?
             NS_WEBNAVIGATION_CREATE : NS_CHROME_WEBNAVIGATION_CREATE;
         serv->NotifyObservers(GetAsSupports(this), msg, nsnull);
-    }    
+    }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::Destroy()
 {
     NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome,
@@ -7338,16 +7344,86 @@ nsDocShell::DoURILoad(nsIURI * aURI,
     PRBool inherit;
     // We expect URIInheritsSecurityContext to return success for an
     // about:blank URI, so don't call IsAboutBlank() if this call fails.
     rv = URIInheritsSecurityContext(aURI, &inherit);
     if (NS_SUCCEEDED(rv) && (inherit || IsAboutBlank(aURI))) {
         channel->SetOwner(aOwner);
     }
 
+    //
+    // 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<nsIScriptChannel> scriptChannel = do_QueryInterface(channel);
     if (scriptChannel) {
         // Allow execution against our context if the principals match
         scriptChannel->
             SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
     }
 
     if (aIsNewWindowTarget) {
@@ -7355,17 +7431,17 @@ nsDocShell::DoURILoad(nsIURI * aURI,
         if (props) {
             props->SetPropertyAsBool(
                 NS_LITERAL_STRING("docshell.newWindowTarget"),
                 PR_TRUE);
         }
     }
 
     rv = DoChannelLoad(channel, uriLoader, aBypassClassifier);
-    
+
     //
     // If the channel load failed, we failed and nsIWebProgress just ain't
     // gonna happen.
     //
     if (NS_SUCCEEDED(rv)) {
         if (aDocShell) {
           *aDocShell = this;
           NS_ADDREF(*aDocShell);
@@ -9311,16 +9387,29 @@ nsDocShell::URIInheritsSecurityContext(n
     // current document, which is what this function tests for...
     return NS_URIChainHasFlags(aURI,
                                nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
                                aResult);
 }
 
 /* static */
 PRBool
+nsDocShell::URIIsLocalFile(nsIURI *aURI)
+{
+    PRBool isFile;
+    nsCOMPtr<nsINetUtil> util = do_GetIOService();
+
+    return util && NS_SUCCEEDED(util->ProtocolHasFlags(aURI,
+                                    nsIProtocolHandler::URI_IS_LOCAL_FILE,
+                                    &isFile)) &&
+           isFile;
+}
+
+/* static */
+PRBool
 nsDocShell::IsAboutBlank(nsIURI* aURI)
 {
     NS_PRECONDITION(aURI, "Must have URI");
     
     // GetSpec can be expensive for some URIs, so check the scheme first.
     PRBool isAbout = PR_FALSE;
     if (NS_FAILED(aURI->SchemeIs("about", &isAbout)) || !isAbout) {
         return PR_FALSE;
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -509,16 +509,19 @@ protected:
 
     // Method to get our current position and size without flushing
     void DoGetPositionAndSize(PRInt32 * x, PRInt32 * y, PRInt32 * cx,
                               PRInt32 * cy);
     
     // Check whether aURI should inherit our security context
     static nsresult URIInheritsSecurityContext(nsIURI* aURI, PRBool* aResult);
 
+    // Check whether aURI is a URI_IS_LOCAL_FILE or not
+    static PRBool URIIsLocalFile(nsIURI *aURI);
+
     // Check whether aURI is about:blank
     static PRBool IsAboutBlank(nsIURI* aURI);
 
     // Call this when a URI load is handed to us (via OnLinkClick or
     // InternalLoad).  This makes sure that we're not inside unload, or that if
     // we are it's still OK to load this URI.
     PRBool IsOKToLoadURI(nsIURI* aURI);
     
@@ -543,16 +546,17 @@ 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/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -495,18 +495,18 @@ pref("javascript.allow.mailnews",       
 pref("javascript.options.strict",           false);
 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: 0=self, 1=samedir, 2=subdir, 3=anyfile
-pref("security.fileuri.origin_policy", 2);
+// Same-origin policy for file URIs, "false" is traditional
+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.