Fixing bug 397791. Prevent document principals from ever changing, and make us not use XOWs for same origin document objects. r=jonas@sickin.cc, sr=bzbarsky@mit.edu
authorjst@mozilla.org
Fri, 25 Jan 2008 13:49:11 -0800
changeset 10689 3d55a154134b2bc15cb0a2fb16f449fcefc5cbfe
parent 10688 569aca45def7eed66cb1c0820f37924b2f701e14
child 10690 1cfbda3c3f57884e9ce1484983aad4c082188c94
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjonas, bzbarsky
bugs397791
milestone1.9b3pre
Fixing bug 397791. Prevent document principals from ever changing, and make us not use XOWs for same origin document objects. r=jonas@sickin.cc, sr=bzbarsky@mit.edu
content/html/document/src/nsHTMLDocument.cpp
content/xml/document/src/nsXMLDocument.cpp
content/xml/document/src/nsXMLDocument.h
js/src/xpconnect/src/XPCWrapper.h
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -2078,27 +2078,49 @@ nsHTMLDocument::OpenCommon(const nsACStr
   // Grab a reference to the calling documents security info (if any)
   // and principal as it may be lost in the call to Reset().
   nsCOMPtr<nsISupports> securityInfo;
   if (callerDoc) {
     securityInfo = callerDoc->GetSecurityInfo();
   }
 
   nsCOMPtr<nsIPrincipal> callerPrincipal;
-  nsContentUtils::GetSecurityManager()->
-    GetSubjectPrincipal(getter_AddRefs(callerPrincipal));
+  nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
+
+  secMan->GetSubjectPrincipal(getter_AddRefs(callerPrincipal));
+
+  if (!callerPrincipal) {
+    // If we're called from C++ we can't do a document.open w/o
+    // changing the principal of the document to something like
+    // about:blank (as that's the only sane thing to do when we don't
+    // know the origin of this call), and since we can't change the
+    // principals of a document for security reasons we'll have to
+    // refuse to go ahead with this call.
+
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  // We're called from script. Make sure the script is from the same
+  // origin, not just that the caller can access the document. This is
+  // needed to keep document principals from ever changing, which is
+  // needed because of the way we use our XOW code, and is a sane
+  // thing to do anyways.
+
+  PRBool equals = PR_FALSE;
+  if (NS_FAILED(callerPrincipal->Equals(NodePrincipal(), &equals)) ||
+      !equals) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
 
   // The URI for the document after this call. Get it from the calling
   // principal (if available), or set it to "about:blank" if no
   // principal is reachable.
   nsCOMPtr<nsIURI> uri;
-
-  if (callerPrincipal) {
-    callerPrincipal->GetURI(getter_AddRefs(uri));
-  }
+  callerPrincipal->GetURI(getter_AddRefs(uri));
+
   if (!uri) {
     rv = NS_NewURI(getter_AddRefs(uri),
                    NS_LITERAL_CSTRING("about:blank"));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<nsIDocShell> docshell = do_QueryReferent(mDocumentContainer);
 
@@ -2146,25 +2168,16 @@ nsHTMLDocument::OpenCommon(const nsACStr
   nsCOMPtr<nsIDOMDocument> kungFuDeathGrip =
     do_QueryInterface((nsIHTMLDocument*)this);
 
   nsPIDOMWindow *window = GetInnerWindow();
   if (window) {
     // Remember the old scope in case the call to SetNewDocument changes it.
     nsCOMPtr<nsIScriptGlobalObject> oldScope(do_QueryReferent(mScopeObject));
 
-    // If callerPrincipal doesn't match our principal. make sure that
-    // SetNewDocument gives us a new inner window and clears our scope.
-    PRBool samePrincipal;
-    if (!callerPrincipal ||
-        NS_FAILED(callerPrincipal->Equals(NodePrincipal(), &samePrincipal)) ||
-        !samePrincipal) {
-      SetIsInitialDocument(PR_FALSE);
-    }
-
     rv = window->SetNewDocument(this, nsnull, PR_FALSE);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Now make sure we're not flagged as the initial document anymore, now
     // that we've had stuff done to us.  From now on, if anyone tries to
     // document.open() us, they get a new inner window.
     SetIsInitialDocument(PR_FALSE);
 
--- a/content/xml/document/src/nsXMLDocument.cpp
+++ b/content/xml/document/src/nsXMLDocument.cpp
@@ -188,24 +188,18 @@ nsXMLDocument::nsXMLDocument(const char*
 }
 
 nsXMLDocument::~nsXMLDocument()
 {
   // XXX We rather crash than hang
   mLoopingForSyncLoad = PR_FALSE;
 }
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLDocument)
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXMLDocument, nsDocument)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptContext)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
 // QueryInterface implementation for nsXMLDocument
-NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXMLDocument)
+NS_INTERFACE_TABLE_HEAD(nsXMLDocument)
   NS_INTERFACE_TABLE_INHERITED3(nsXMLDocument,
                                 nsIInterfaceRequestor,
                                 nsIChannelEventSink,
                                 nsIDOMXMLDocument)
   NS_INTERFACE_TABLE_TO_MAP_SEGUE
   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(XMLDocument)
 NS_INTERFACE_MAP_END_INHERITING(nsDocument)
 
@@ -222,30 +216,28 @@ nsXMLDocument::Init()
 
   return rv;
 }
 
 void
 nsXMLDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
 {
   nsDocument::Reset(aChannel, aLoadGroup);
-
-  mScriptContext = nsnull;
 }
 
 void
 nsXMLDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
                           nsIPrincipal* aPrincipal)
 {
   if (mChannelIsPending) {
     StopDocumentLoad();
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannelIsPending = nsnull;
   }
-  
+
   nsDocument::ResetToURI(aURI, aLoadGroup, aPrincipal);
 }
 
 /////////////////////////////////////////////////////
 // nsIInterfaceRequestor methods:
 //
 NS_IMETHODIMP
 nsXMLDocument::GetInterface(const nsIID& aIID, void** aSink)
@@ -284,37 +276,29 @@ nsXMLDocument::OnChannelRedirect(nsIChan
 
   nsCOMPtr<nsIURI> newLocation;
   nsresult rv = aNewChannel->GetURI(getter_AddRefs(newLocation)); // The redirected URI
   if (NS_FAILED(rv)) 
     return rv;
 
   nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
 
-  if (mScriptContext && !mCrossSiteAccessEnabled) {
-    nsCOMPtr<nsIJSContextStack> stack(do_GetService("@mozilla.org/js/xpc/ContextStack;1", & rv));
-    if (NS_FAILED(rv))
-      return rv;
-
-    JSContext *cx = (JSContext *)mScriptContext->GetNativeContext();
-    if (!cx)
-      return NS_ERROR_UNEXPECTED;
-
-    stack->Push(cx);
+  nsCOMPtr<nsIURI> oldURI;
+  rv = aOldChannel->GetURI(getter_AddRefs(oldURI));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = secMan->CheckSameOrigin(nsnull, newLocation);
+  nsCOMPtr<nsIURI> newURI;
+  rv = aNewChannel->GetURI(getter_AddRefs(newURI));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    stack->Pop(&cx);
-  
-    if (NS_FAILED(rv)) {
-      // The security manager set a pending exception.  Since we're
-      // running under the event loop, we need to report it.
-      ::JS_ReportPendingException(cx);
-      return rv;
-    }
+  rv = nsContentUtils::GetSecurityManager()->
+    CheckSameOriginURI(oldURI, newURI, PR_TRUE);
+
+  if (NS_FAILED(rv)) {
+    return rv;
   }
 
   // XXXbz Shouldn't we look at the owner on the new channel at some point?
   // It's not gonna be right here, but eventually it will....
   nsCOMPtr<nsIPrincipal> principal;
   rv = secMan->GetCodebasePrincipal(newLocation, getter_AddRefs(principal));
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -355,136 +339,100 @@ nsXMLDocument::GetAsync(PRBool *aAsync)
 
 NS_IMETHODIMP
 nsXMLDocument::SetAsync(PRBool aAsync)
 {
   mAsync = aAsync;
   return NS_OK;
 }
 
-nsresult 
-nsXMLDocument::GetLoadGroup(nsILoadGroup **aLoadGroup)
-{
-  NS_ENSURE_ARG_POINTER(aLoadGroup);
-  *aLoadGroup = nsnull;
-
-  if (mScriptContext) {
-    nsCOMPtr<nsIDOMWindow> window =
-      do_QueryInterface(mScriptContext->GetGlobalObject());
-
-    if (window) {
-      nsCOMPtr<nsIDOMDocument> domdoc;
-      window->GetDocument(getter_AddRefs(domdoc));
-      nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
-      if (doc) {
-        *aLoadGroup = doc->GetDocumentLoadGroup().get(); // already_AddRefed
-      }
-    }
-  }
-
-  return NS_OK;
-}
-
-
 NS_IMETHODIMP
 nsXMLDocument::Load(const nsAString& aUrl, PRBool *aReturn)
 {
   NS_ENSURE_ARG_POINTER(aReturn);
   *aReturn = PR_FALSE;
 
-  nsIScriptContext *callingContext = nsnull;
-
-  nsCOMPtr<nsIJSContextStack> stack =
-    do_GetService("@mozilla.org/js/xpc/ContextStack;1");
-  if (stack) {
-    JSContext *cx;
-    if (NS_SUCCEEDED(stack->Peek(&cx)) && cx) {
-      callingContext = nsJSUtils::GetDynamicScriptContext(cx);
-    }
-  }
+  nsCOMPtr<nsIDocument> callingDoc =
+    do_QueryInterface(nsContentUtils::GetDocumentFromContext());
 
   nsIURI *baseURI = mDocumentURI;
   nsCAutoString charset;
 
-  if (callingContext) {
-    nsCOMPtr<nsIDOMWindow> window =
-      do_QueryInterface(callingContext->GetGlobalObject());
-
-    if (window) {
-      nsCOMPtr<nsIDOMDocument> dom_doc;
-      window->GetDocument(getter_AddRefs(dom_doc));
-      nsCOMPtr<nsIDocument> doc(do_QueryInterface(dom_doc));
-
-      if (doc) {
-        baseURI = doc->GetBaseURI();
-        charset = doc->GetDocumentCharacterSet();
-      }
-    }
+  if (callingDoc) {
+    baseURI = callingDoc->GetBaseURI();
+    charset = callingDoc->GetDocumentCharacterSet();
   }
 
   // Create a new URI
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, charset.get(), baseURI);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  nsCOMPtr<nsIURI> codebase;
+  NodePrincipal()->GetURI(getter_AddRefs(codebase));
+
+  // Get security manager, check to see whether the current document
+  // is allowed to load this URI. It's important to use the current
+  // document's principal for this check so that we don't end up in a
+  // case where code with elevated privileges is calling us and
+  // changing the principal of this document.
+  nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
+
+  if (codebase) {
+    rv = secMan->CheckSameOriginURI(codebase, uri, PR_FALSE);
+
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else {
+    // We're called from chrome, check to make sure the URI we're
+    // about to load is also chrome.
+
+    PRBool isChrome = PR_FALSE;
+    if (NS_FAILED(uri->SchemeIs("chrome", &isChrome)) || !isChrome) {
+      return NS_ERROR_DOM_SECURITY_ERR;
+    }
+  }
+
+  rv = secMan->CheckConnect(nsnull, uri, "XMLDocument", "load");
+  if (NS_FAILED(rv)) {
+    // We need to return success here so that JS will get a proper
+    // exception thrown later. Native calls should always result in
+    // CheckConnect() succeeding, but in case JS calls C++ which calls
+    // this code the exception might be lost.
+    return NS_OK;
+  }
+
   // Partial Reset, need to restore principal for security reasons and
   // event listener manager so that load listeners etc. will
   // remain. This should be done before the security check is done to
   // ensure that the document is reset even if the new document can't
   // be loaded.  Note that we need to hold a strong ref to |principal|
   // here, because ResetToURI will null out our node principal before
   // setting the new one.
   nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
   nsCOMPtr<nsIEventListenerManager> elm(mListenerManager);
   mListenerManager = nsnull;
 
-  ResetToURI(uri, nsnull, principal);
+  // When we are called from JS we can find the load group for the page,
+  // and add ourselves to it. This way any pending requests
+  // will be automatically aborted if the user leaves the page.
+
+  nsCOMPtr<nsILoadGroup> loadGroup;
+  if (callingDoc) {
+    loadGroup = callingDoc->GetDocumentLoadGroup();
+  }
+
+  ResetToURI(uri, loadGroup, principal);
 
   mListenerManager = elm;
 
-  // Get security manager, check to see if we're allowed to load this URI
-  nsCOMPtr<nsIScriptSecurityManager> secMan = 
-           do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  rv = secMan->CheckConnect(nsnull, uri, "XMLDocument", "load");
-  if (NS_FAILED(rv)) {
-    // We need to return success here so that JS will get a proper
-    // exception thrown later. Native calls should always result in
-    // CheckConnect() succeeding, but in case JS calls C++ which calls
-    // this code the exception might be lost.
-    return NS_OK;
-  }
-
-  // Store script context, if any, in case we encounter redirect
-  // (because we need it there)
-
-  mScriptContext = callingContext;
-
-  // Find out if UniversalBrowserRead privileges are enabled - we will
-  // need this in case of a redirect
-  PRBool crossSiteAccessEnabled;
-  rv = secMan->IsCapabilityEnabled("UniversalBrowserRead",
-                                   &crossSiteAccessEnabled);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  mCrossSiteAccessEnabled = crossSiteAccessEnabled;
-
   // Create a channel
-  // When we are called from JS we can find the load group for the page,
-  // and add ourselves to it. This way any pending requests
-  // will be automatically aborted if the user leaves the page.
-  nsCOMPtr<nsILoadGroup> loadGroup;
-  GetLoadGroup(getter_AddRefs(loadGroup));
 
   nsCOMPtr<nsIChannel> channel;
   // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active,
   // which in turn keeps STOP button from becoming active  
   rv = NS_NewChannel(getter_AddRefs(channel), uri, nsnull, loadGroup, this, 
                      nsIRequest::LOAD_BACKGROUND);
   if (NS_FAILED(rv)) {
     return rv;
--- a/content/xml/document/src/nsXMLDocument.h
+++ b/content/xml/document/src/nsXMLDocument.h
@@ -90,31 +90,24 @@ public:
 
   // nsIDOMXMLDocument
   NS_DECL_NSIDOMXMLDOCUMENT
 
   virtual nsresult Init();
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsXMLDocument, nsDocument)
-
   void SetLoadedAsData(PRBool aLoadedAsData) { mLoadedAsData = aLoadedAsData; }
 protected:
-  virtual nsresult GetLoadGroup(nsILoadGroup **aLoadGroup);
-
-  nsCOMPtr<nsIScriptContext> mScriptContext;
-
   // mChannelIsPending indicates whether we're currently asynchronously loading
   // data from mChannel (via document.load() or normal load).  It's set to true
   // when we first find out about the channel (StartDocumentLoad) and set to
   // false in EndLoad or if ResetToURI() is called.  In the latter case our
   // mChannel is also cancelled.  Note that if this member is true, mChannel
   // cannot be null.
   PRPackedBool mChannelIsPending;
-  PRPackedBool mCrossSiteAccessEnabled;
   PRPackedBool mLoadedAsInteractiveData;
   PRPackedBool mAsync;
   PRPackedBool mLoopingForSyncLoad;
 };
 
 
 #endif // nsXMLDocument_h___
--- a/js/src/xpconnect/src/XPCWrapper.h
+++ b/js/src/xpconnect/src/XPCWrapper.h
@@ -82,17 +82,16 @@ nsresult
 IsWrapperSameOrigin(JSContext *cx, JSObject *wrappedObj);
 
 inline JSBool
 XPC_XOW_ClassNeedsXOW(const char *name)
 {
   // TODO Make a perfect hash of these and use that?
   return !strcmp(name, "Window")            ||
          !strcmp(name, "Location")          ||
-         !strcmp(name, "HTMLDocument")      ||
          !strcmp(name, "HTMLIFrameElement") ||
          !strcmp(name, "HTMLFrameElement");
 }
 
 extern JSExtendedClass sXPC_XOW_JSClass;
 
 // This class wraps some common functionality between the three existing
 // wrappers. Its main purpose is to allow XPCCrossOriginWrapper to act both