Bug 734891 - part 2: Adding ExpandedPrincipal support
authorGabor Krizsanits <gkrizsanits@mozilla.com>
Sat, 09 Jun 2012 15:19:26 -0700
changeset 101590 6dd8897c873be53815061d699ef15feba809f837
parent 101589 e9814ed8d9748f0f8c721e94a400f45317a3106a
child 101591 0e53ca52d4f4162060ab964a4da0bd27223a775b
push id191
push userlsblakk@mozilla.com
push dateFri, 05 Oct 2012 17:12:53 +0000
treeherdermozilla-release@ddb22ac6c03b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs734891
milestone16.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 734891 - part 2: Adding ExpandedPrincipal support
caps/idl/nsIPrincipal.idl
caps/include/nsPrincipal.h
caps/src/nsPrincipal.cpp
caps/src/nsScriptSecurityManager.cpp
--- a/caps/idl/nsIPrincipal.idl
+++ b/caps/idl/nsIPrincipal.idl
@@ -5,23 +5,26 @@
 
 /* Defines the abstract interface for a principal. */
 
 #include "nsISerializable.idl"
 
 %{C++
 struct JSContext;
 struct JSPrincipals;
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
 %}
 
 interface nsIURI;
 interface nsIContentSecurityPolicy;
 
 [ptr] native JSContext(JSContext);
 [ptr] native JSPrincipals(JSPrincipals);
+[ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >);
 
 [scriptable, uuid(42ba6a38-d619-49ab-8248-3d247e959d5e)]
 interface nsIPrincipal : nsISerializable
 {
     /**
      * Values of capabilities for each principal. Order is
      * significant: if an operation is performed on a set
      * of capabilities, the minimum is computed.
@@ -205,8 +208,30 @@ interface nsIPrincipal : nsISerializable
      */
     readonly attribute nsISupports certificate;
 
     /**
      * A Content Security Policy associated with this principal.
      */
     [noscript] attribute nsIContentSecurityPolicy csp;
 };
+
+/**
+ * If nsSystemPrincipal is too risky to use, but we want a principal to access 
+ * more than one origin, nsExpandedPrincipals letting us define an array of 
+ * principals it subsumes. So script with an nsExpandedPrincipals will gain
+ * same origin access when at least one of its principals it contains gained 
+ * sameorigin acccess. An nsExpandedPrincipal will be subsumed by the system
+ * principal, and by another nsExpandedPrincipal that has all its principals.
+ * It is added for jetpack content-scripts to let them interact with the
+ * content and a well defined set of other domains, without the risk of 
+ * leaking out a system principal to the content. See: Bug 734891
+ */
+[uuid(f3e177Df-6a5e-489f-80a7-2dd1481471d8)]
+interface nsIExpandedPrincipal : nsISupports
+{
+  /**
+   * An array of principals that the expanded principal subsumes.
+   * Note: this list is not reference counted, it is shared, so 
+   * should not be changed and should only be used ephemerally.
+   */
+  [notxpcom] readonly attribute PrincipalArray whiteList;
+};
\ No newline at end of file
--- a/caps/include/nsPrincipal.h
+++ b/caps/include/nsPrincipal.h
@@ -11,16 +11,17 @@
 #include "nsVoidArray.h"
 #include "nsHashtable.h"
 #include "nsJSPrincipals.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 
 class nsIObjectInputStream;
 class nsIObjectOutputStream;
+class DomainPolicy; 
 
 class nsBasePrincipal : public nsJSPrincipals
 {
 public:
   nsBasePrincipal();
 
 protected:
   virtual ~nsBasePrincipal();
@@ -155,17 +156,53 @@ public:
   nsCOMPtr<nsIURI> mDomain;
   nsCOMPtr<nsIURI> mCodebase;
   // If mCodebaseImmutable is true, mCodebase is non-null and immutable
   bool mCodebaseImmutable;
   bool mDomainImmutable;
   bool mInitialized;
 };
 
+class nsExpandedPrincipal : public nsIExpandedPrincipal, public nsBasePrincipal
+{
+public:
+  nsExpandedPrincipal(nsTArray< nsCOMPtr<nsIPrincipal> > &aWhiteList);
+
+protected:
+  virtual ~nsExpandedPrincipal();
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIEXPANDEDPRINCIPAL
+  NS_DECL_NSISERIALIZABLE
+  NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval NS_OUTPARAM);
+  NS_IMETHOD EqualsIgnoringDomain(nsIPrincipal* other, bool* _retval NS_OUTPARAM);
+  NS_IMETHOD GetHashValue(PRUint32* aHashValue);
+  NS_IMETHOD GetURI(nsIURI** aURI);
+  NS_IMETHOD GetDomain(nsIURI** aDomain);
+  NS_IMETHOD SetDomain(nsIURI* aDomain);
+  NS_IMETHOD GetOrigin(char** aOrigin);
+  NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval NS_OUTPARAM);
+  NS_IMETHOD SubsumesIgnoringDomain(nsIPrincipal* other, bool* _retval NS_OUTPARAM);
+  NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report);
+#ifdef DEBUG
+  virtual void dumpImpl();
+#endif
+  
+  virtual void GetScriptLocation(nsACString &aStr) MOZ_OVERRIDE;
+
+private:
+  nsTArray< nsCOMPtr<nsIPrincipal> > mPrincipals;
+};
 
 #define NS_PRINCIPAL_CLASSNAME  "principal"
 #define NS_PRINCIPAL_CONTRACTID "@mozilla.org/principal;1"
 #define NS_PRINCIPAL_CID \
   { 0x36102b6b, 0x7b62, 0x451a, \
     { 0xa1, 0xc8, 0xa0, 0xd4, 0x56, 0xc9, 0x2d, 0xc5 }}
 
+#define NS_EXPANDEDPRINCIPAL_CLASSNAME  "expandedprincipal"
+#define NS_EXPANDEDPRINCIPAL_CONTRACTID "@mozilla.org/expandedprincipal;1"
+#define NS_EXPANDEDPRINCIPAL_CID \
+  { 0xb33a3807, 0xb76c, 0x44e5, \
+    { 0xb9, 0x9d, 0x95, 0x7e, 0xe9, 0xba, 0x6e, 0x39 }}
 
 #endif // nsPrincipal_h__
--- a/caps/src/nsPrincipal.cpp
+++ b/caps/src/nsPrincipal.cpp
@@ -1197,8 +1197,211 @@ nsPrincipal::Write(nsIObjectOutputStream
     return rv;
   }
 
   // mCodebaseImmutable and mDomainImmutable will be recomputed based
   // on the deserialized URIs in Read().
 
   return NS_OK;
 }
+
+
+/************************************************************************************************************************/
+
+static const char EXPANDED_PRINCIPAL_SPEC[] = "[Expanded Principal]";
+
+NS_IMPL_CLASSINFO(nsExpandedPrincipal, NULL, nsIClassInfo::MAIN_THREAD_ONLY,
+                  NS_EXPANDEDPRINCIPAL_CID)
+NS_IMPL_QUERY_INTERFACE2_CI(nsExpandedPrincipal,
+                            nsIPrincipal,
+                            nsIExpandedPrincipal)
+NS_IMPL_CI_INTERFACE_GETTER2(nsExpandedPrincipal,
+                             nsIPrincipal,
+                             nsIExpandedPrincipal)
+NS_IMPL_ADDREF_INHERITED(nsExpandedPrincipal, nsBasePrincipal);
+NS_IMPL_RELEASE_INHERITED(nsExpandedPrincipal, nsBasePrincipal);
+
+nsExpandedPrincipal::nsExpandedPrincipal(nsTArray<nsCOMPtr <nsIPrincipal> > &aWhiteList)
+{
+  mPrincipals.AppendElements(aWhiteList);
+}
+
+nsExpandedPrincipal::~nsExpandedPrincipal()
+{ }
+
+NS_IMETHODIMP 
+nsExpandedPrincipal::GetDomain(nsIURI** aDomain)
+{
+  *aDomain = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsExpandedPrincipal::SetDomain(nsIURI* aDomain)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsExpandedPrincipal::GetOrigin(char** aOrigin)
+{
+  *aOrigin = ToNewCString(NS_LITERAL_CSTRING(EXPANDED_PRINCIPAL_SPEC));
+  return *aOrigin ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+typedef nsresult (NS_STDCALL nsIPrincipal::*nsIPrincipalMemFn)(nsIPrincipal* aOther,
+                                                               bool* aResult);
+#define CALL_MEMBER_FUNCTION(THIS,MEM_FN)  ((THIS)->*(MEM_FN))
+
+// nsExpandedPrincipal::Equals and nsExpandedPrincipal::EqualsIgnoringDomain
+// shares the same logic. The difference only that Equals requires 'this' 
+// and 'aOther' to Subsume each other while EqualsIgnoringDomain requires 
+// bidirectional SubsumesIgnoringDomain.
+static nsresult 
+Equals(nsExpandedPrincipal* aThis, nsIPrincipalMemFn aFn, nsIPrincipal* aOther,
+       bool* aResult)
+{
+  // If (and only if) 'aThis' and 'aOther' both Subsume/SubsumesIgnoringDomain
+  // each other, then they are Equal.
+  *aResult = false;
+  // Calling the corresponding subsume function on this (aFn).
+  nsresult rv = CALL_MEMBER_FUNCTION(aThis, aFn)(aOther, aResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!*aResult)
+    return NS_OK;
+
+  // Calling the corresponding subsume function on aOther (aFn).
+  rv = CALL_MEMBER_FUNCTION(aOther, aFn)(aThis, aResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExpandedPrincipal::Equals(nsIPrincipal* aOther, bool* aResult)
+{
+  return ::Equals(this, &nsIPrincipal::Subsumes, aOther, aResult);
+}
+
+NS_IMETHODIMP
+nsExpandedPrincipal::EqualsIgnoringDomain(nsIPrincipal* aOther, bool* aResult)
+{
+  return ::Equals(this, &nsIPrincipal::SubsumesIgnoringDomain, aOther, aResult);
+}
+
+// nsExpandedPrincipal::Subsumes and nsExpandedPrincipal::SubsumesIgnoringDomain
+// shares the same logic. The difference only that Subsumes calls are replaced
+//with SubsumesIgnoringDomain calls in the second case.
+static nsresult 
+Subsumes(nsExpandedPrincipal* aThis, nsIPrincipalMemFn aFn, nsIPrincipal* aOther, 
+         bool* aResult)
+{
+  nsresult rv;
+  nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aOther);  
+  if (expanded) {
+    // If aOther is an ExpandedPrincipal too, check if all of its 
+    // principals are subsumed.
+    nsTArray< nsCOMPtr<nsIPrincipal> >* otherList;
+    expanded->GetWhiteList(&otherList);
+    for (uint32_t i = 0; i < otherList->Length(); ++i){
+      rv = CALL_MEMBER_FUNCTION(aThis, aFn)((*otherList)[i], aResult);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (!*aResult) {
+        // If we don't subsume at least one principal of aOther, return false.
+        return NS_OK;    
+      }
+    }
+  } else {
+    // For a regular aOther, one of our principals must subsume it.
+    nsTArray< nsCOMPtr<nsIPrincipal> >* list;
+    aThis->GetWhiteList(&list);
+    for (uint32_t i = 0; i < list->Length(); ++i){
+      rv = CALL_MEMBER_FUNCTION((*list)[i], aFn)(aOther, aResult);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (*aResult) {
+        // If one of our principal subsumes it, return true.
+        return NS_OK;
+      }
+    }
+  }
+  return NS_OK;
+}
+
+#undef CALL_MEMBER_FUNCTION
+
+NS_IMETHODIMP
+nsExpandedPrincipal::Subsumes(nsIPrincipal* aOther, bool* aResult)
+{
+  return ::Subsumes(this, &nsIPrincipal::Subsumes, aOther, aResult);
+}
+
+NS_IMETHODIMP
+nsExpandedPrincipal::SubsumesIgnoringDomain(nsIPrincipal* aOther, bool* aResult)
+{
+  return ::Subsumes(this, &nsIPrincipal::SubsumesIgnoringDomain, aOther, aResult);
+}
+
+NS_IMETHODIMP
+nsExpandedPrincipal::CheckMayLoad(nsIURI* uri, bool aReport)
+{
+  nsresult rv;
+  for (uint32_t i = 0; i < mPrincipals.Length(); ++i){
+    rv = mPrincipals[i]->CheckMayLoad(uri, aReport);
+    if (NS_SUCCEEDED(rv))
+      return rv;
+  }
+
+  return NS_ERROR_DOM_BAD_URI;
+}
+
+NS_IMETHODIMP
+nsExpandedPrincipal::GetHashValue(PRUint32* result)
+{
+  MOZ_NOT_REACHED("extended principal should never be used as key in a hash map");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsExpandedPrincipal::GetURI(nsIURI** aURI)
+{
+  *aURI = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsExpandedPrincipal::GetWhiteList(nsTArray<nsCOMPtr<nsIPrincipal> >** aWhiteList)
+{
+  *aWhiteList = &mPrincipals;
+  return NS_OK;
+}
+
+void
+nsExpandedPrincipal::GetScriptLocation(nsACString& aStr)
+{
+  if (mCert) {
+    aStr.Assign(mCert->fingerprint);
+  } else {
+    // Is that a good idea to list it's principals?
+    aStr.Assign(EXPANDED_PRINCIPAL_SPEC);
+  }
+}
+
+#ifdef DEBUG
+void nsExpandedPrincipal::dumpImpl()
+{
+  fprintf(stderr, "nsExpandedPrincipal (%p)\n", this);
+}
+#endif 
+
+//////////////////////////////////////////
+// Methods implementing nsISerializable //
+//////////////////////////////////////////
+
+NS_IMETHODIMP
+nsExpandedPrincipal::Read(nsIObjectInputStream* aStream)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsExpandedPrincipal::Write(nsIObjectOutputStream* aStream)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -1339,17 +1339,32 @@ nsScriptSecurityManager::CheckLoadURIWit
     if (aPrincipal == mSystemPrincipal) {
         // Allow access
         return NS_OK;
     }
 
     nsCOMPtr<nsIURI> sourceURI;
     aPrincipal->GetURI(getter_AddRefs(sourceURI));
     if (!sourceURI) {
-        NS_ERROR("Non-system principals passed to CheckLoadURIWithPrincipal "
+        nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aPrincipal);
+        if (expanded) {
+            nsTArray< nsCOMPtr<nsIPrincipal> > *whiteList;
+            expanded->GetWhiteList(&whiteList);
+            for (uint32_t i = 0; i < whiteList->Length(); ++i) {
+                nsresult rv = CheckLoadURIWithPrincipal((*whiteList)[i],
+                                                        aTargetURI,
+                                                        aFlags);
+                if (NS_SUCCEEDED(rv)) {
+                    // Allow access if it succeeded with one of the white listed principals
+                    return NS_OK;
+                }
+            }
+            return NS_OK;
+        }
+        NS_ERROR("Non-system principals or expanded principal passed to CheckLoadURIWithPrincipal "
                  "must have a URI!");
         return NS_ERROR_UNEXPECTED;
     }
     
     // Automatic loads are not allowed from certain protocols.
     if (aFlags & nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT) {
         nsresult rv =
             DenyAccessIfURIHasFlags(sourceURI,