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
--- 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