diff --git a/caps/src/nsCodebasePrincipal.cpp b/caps/src/nsCodebasePrincipal.cpp index 5509d2526372..a4ae15d4dc31 100644 --- a/caps/src/nsCodebasePrincipal.cpp +++ b/caps/src/nsCodebasePrincipal.cpp @@ -177,7 +177,8 @@ nsCodebasePrincipal::Equals(nsIPrincipal *other, PRBool *result) // All file: urls are considered to have the same origin. *result = PR_TRUE; } else if (PL_strcmp(scheme1, "imap") == 0 || - PL_strcmp(scheme1, "mailbox") == 0) + PL_strcmp(scheme1, "mailbox") == 0 || + PL_strcmp(scheme1, "news") == 0) { // Each message is a distinct trust domain; use the // whole spec for comparison diff --git a/content/html/document/src/nsHTMLDocument.cpp b/content/html/document/src/nsHTMLDocument.cpp index ce6cd661b630..1eb79fc214dc 100644 --- a/content/html/document/src/nsHTMLDocument.cpp +++ b/content/html/document/src/nsHTMLDocument.cpp @@ -286,6 +286,7 @@ nsHTMLDocument::nsHTMLDocument() //NS_WITH_SERVICE(nsIRDFService, gRDF, kRDFServiceCID, &rv); } + mDomainSet = PR_FALSE; // Bug 13871: Frameset spoofing } nsHTMLDocument::~nsHTMLDocument() @@ -1809,7 +1810,22 @@ nsHTMLDocument::SetDomain(const nsAReadableString& aDomain) NS_ASSERTION(NS_SUCCEEDED(rv), "Principal not an aggregate."); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; - return agg->SetCodebase(newCodebase); + + rv = agg->SetCodebase(newCodebase); + + // Bug 13871: Frameset spoofing - note that document.domain was set + if (NS_SUCCEEDED(rv)) + mDomainSet = PR_TRUE; + + return rv; +} + +NS_IMETHODIMP +nsHTMLDocument::GetDomainSet(PRBool* aValue) +{ + NS_ENSURE_ARG_POINTER(aValue); + *aValue = mDomainSet; + return NS_OK; } NS_IMETHODIMP diff --git a/content/html/document/src/nsHTMLDocument.h b/content/html/document/src/nsHTMLDocument.h index 103b3b3cb257..93a46cbaafd4 100644 --- a/content/html/document/src/nsHTMLDocument.h +++ b/content/html/document/src/nsHTMLDocument.h @@ -193,6 +193,11 @@ protected: PRBool mShouldMatchCase; + /* + * Bug 13871: Frameset spoofing - find out if document.domain was set + */ + PRBool mDomainSet; + protected: void RegisterNamedItems(nsIContent *aContent, PRBool aInForm); void UnregisterNamedItems(nsIContent *aContent, PRBool aInForm); diff --git a/dom/public/html/nsIDOMNSHTMLDocument.h b/dom/public/html/nsIDOMNSHTMLDocument.h index 4785fc3c1251..fca73ab8f996 100644 --- a/dom/public/html/nsIDOMNSHTMLDocument.h +++ b/dom/public/html/nsIDOMNSHTMLDocument.h @@ -63,6 +63,8 @@ public: NS_IMETHOD GetEmbeds(nsIDOMHTMLCollection** aEmbeds)=0; + NS_IMETHOD GetDomainSet(PRBool* aDomainSet)=0; + NS_IMETHOD GetSelection(nsAWritableString& aReturn)=0; NS_IMETHOD NamedItem(JSContext* cx, jsval* argv, PRUint32 argc, jsval* aReturn)=0; @@ -98,6 +100,7 @@ public: NS_IMETHOD SetFgColor(const nsAReadableString& aFgColor); \ NS_IMETHOD GetLastModified(nsAWritableString& aLastModified); \ NS_IMETHOD GetEmbeds(nsIDOMHTMLCollection** aEmbeds); \ + NS_IMETHOD GetDomainSet(PRBool* aDomainSet); \ NS_IMETHOD GetSelection(nsAWritableString& aReturn); \ NS_IMETHOD NamedItem(JSContext* cx, jsval* argv, PRUint32 argc, jsval* aReturn); \ NS_IMETHOD Open(JSContext* cx, jsval* argv, PRUint32 argc); \ @@ -125,6 +128,7 @@ public: NS_IMETHOD SetFgColor(const nsAReadableString& aFgColor) { return _to SetFgColor(aFgColor); } \ NS_IMETHOD GetLastModified(nsAWritableString& aLastModified) { return _to GetLastModified(aLastModified); } \ NS_IMETHOD GetEmbeds(nsIDOMHTMLCollection** aEmbeds) { return _to GetEmbeds(aEmbeds); } \ + NS_IMETHOD GetDomainSet(PRBool* aDomainSet) { return _to GetDomainSet(aDomainSet); } \ NS_IMETHOD GetSelection(nsAWritableString& aReturn) { return _to GetSelection(aReturn); } \ NS_IMETHOD NamedItem(JSContext* cx, jsval* argv, PRUint32 argc, jsval* aReturn) { return _to NamedItem(cx, argv, argc, aReturn); } \ NS_IMETHOD Open(JSContext* cx, jsval* argv, PRUint32 argc) { return _to Open(cx, argv, argc); } \ diff --git a/dom/public/idl/html/HTMLDocument.idl b/dom/public/idl/html/HTMLDocument.idl index 081d31a336ad..8d4e22558233 100644 --- a/dom/public/idl/html/HTMLDocument.idl +++ b/dom/public/idl/html/HTMLDocument.idl @@ -47,4 +47,6 @@ void captureEvents(in long eventFlags); void releaseEvents(in long eventFlags); void routeEvent(in Event evt); + + readonly attribute boolean domainSet; }; diff --git a/dom/public/nsDOMPropEnums.h b/dom/public/nsDOMPropEnums.h index 176302e2d653..cb68c02c27cd 100644 --- a/dom/public/nsDOMPropEnums.h +++ b/dom/public/nsDOMPropEnums.h @@ -785,6 +785,7 @@ enum nsDOMProp { NS_DOM_PROP_NSHTMLDOCUMENT_BGCOLOR, NS_DOM_PROP_NSHTMLDOCUMENT_CAPTUREEVENTS, NS_DOM_PROP_NSHTMLDOCUMENT_CLEAR, + NS_DOM_PROP_NSHTMLDOCUMENT_DOMAINSET, NS_DOM_PROP_NSHTMLDOCUMENT_EMBEDS, NS_DOM_PROP_NSHTMLDOCUMENT_FGCOLOR, NS_DOM_PROP_NSHTMLDOCUMENT_GETSELECTION, diff --git a/dom/public/nsDOMPropNames.h b/dom/public/nsDOMPropNames.h index 067a6dfb903f..f1603bd406ad 100644 --- a/dom/public/nsDOMPropNames.h +++ b/dom/public/nsDOMPropNames.h @@ -783,6 +783,7 @@ "nshtmldocument.bgcolor", \ "nshtmldocument.captureevents", \ "nshtmldocument.clear", \ + "nshtmldocument.domainset", \ "nshtmldocument.embeds", \ "nshtmldocument.fgcolor", \ "nshtmldocument.getselection", \ diff --git a/dom/src/html/nsJSHTMLDocument.cpp b/dom/src/html/nsJSHTMLDocument.cpp index fbfff0a7e816..05b41a3e87b8 100644 --- a/dom/src/html/nsJSHTMLDocument.cpp +++ b/dom/src/html/nsJSHTMLDocument.cpp @@ -75,7 +75,8 @@ enum HTMLDocument_slots { NSHTMLDOCUMENT_BGCOLOR = -17, NSHTMLDOCUMENT_FGCOLOR = -18, NSHTMLDOCUMENT_LASTMODIFIED = -19, - NSHTMLDOCUMENT_EMBEDS = -20 + NSHTMLDOCUMENT_EMBEDS = -20, + NSHTMLDOCUMENT_DOMAINSET = -21 }; /***********************************************************************/ @@ -410,6 +411,25 @@ GetHTMLDocumentProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) } break; } + case NSHTMLDOCUMENT_DOMAINSET: + { + rv = secMan->CheckScriptAccess(cx, obj, NS_DOM_PROP_NSHTMLDOCUMENT_DOMAINSET, PR_FALSE); + if (NS_SUCCEEDED(rv)) { + PRBool prop; + nsIDOMNSHTMLDocument* b; + if (NS_OK == a->QueryInterface(kINSHTMLDocumentIID, (void **)&b)) { + rv = b->GetDomainSet(&prop); + if(NS_SUCCEEDED(rv)) { + *vp = BOOLEAN_TO_JSVAL(prop); + } + NS_RELEASE(b); + } + else { + rv = NS_ERROR_DOM_WRONG_TYPE_ERR; + } + } + break; + } default: checkNamedItem = PR_TRUE; } @@ -652,6 +672,7 @@ static JSPropertySpec HTMLDocumentProperties[] = {"fgColor", NSHTMLDOCUMENT_FGCOLOR, JSPROP_ENUMERATE}, {"lastModified", NSHTMLDOCUMENT_LASTMODIFIED, JSPROP_ENUMERATE | JSPROP_READONLY}, {"embeds", NSHTMLDOCUMENT_EMBEDS, JSPROP_ENUMERATE | JSPROP_READONLY}, + {"domainSet", NSHTMLDOCUMENT_DOMAINSET, JSPROP_ENUMERATE | JSPROP_READONLY}, {0} }; diff --git a/layout/html/document/src/nsHTMLDocument.cpp b/layout/html/document/src/nsHTMLDocument.cpp index ce6cd661b630..1eb79fc214dc 100644 --- a/layout/html/document/src/nsHTMLDocument.cpp +++ b/layout/html/document/src/nsHTMLDocument.cpp @@ -286,6 +286,7 @@ nsHTMLDocument::nsHTMLDocument() //NS_WITH_SERVICE(nsIRDFService, gRDF, kRDFServiceCID, &rv); } + mDomainSet = PR_FALSE; // Bug 13871: Frameset spoofing } nsHTMLDocument::~nsHTMLDocument() @@ -1809,7 +1810,22 @@ nsHTMLDocument::SetDomain(const nsAReadableString& aDomain) NS_ASSERTION(NS_SUCCEEDED(rv), "Principal not an aggregate."); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; - return agg->SetCodebase(newCodebase); + + rv = agg->SetCodebase(newCodebase); + + // Bug 13871: Frameset spoofing - note that document.domain was set + if (NS_SUCCEEDED(rv)) + mDomainSet = PR_TRUE; + + return rv; +} + +NS_IMETHODIMP +nsHTMLDocument::GetDomainSet(PRBool* aValue) +{ + NS_ENSURE_ARG_POINTER(aValue); + *aValue = mDomainSet; + return NS_OK; } NS_IMETHODIMP diff --git a/layout/html/document/src/nsHTMLDocument.h b/layout/html/document/src/nsHTMLDocument.h index 103b3b3cb257..93a46cbaafd4 100644 --- a/layout/html/document/src/nsHTMLDocument.h +++ b/layout/html/document/src/nsHTMLDocument.h @@ -193,6 +193,11 @@ protected: PRBool mShouldMatchCase; + /* + * Bug 13871: Frameset spoofing - find out if document.domain was set + */ + PRBool mDomainSet; + protected: void RegisterNamedItems(nsIContent *aContent, PRBool aInForm); void UnregisterNamedItems(nsIContent *aContent, PRBool aInForm); diff --git a/uriloader/base/nsURILoader.cpp b/uriloader/base/nsURILoader.cpp index 83774108ce5e..fe473ed79cb8 100644 --- a/uriloader/base/nsURILoader.cpp +++ b/uriloader/base/nsURILoader.cpp @@ -53,6 +53,14 @@ #include "nsCExternalHandlerService.h" // contains contractids for the helper app service +// Following are for Bug 13871: Prevent frameset spoofing +#include "nsIPref.h" +#include "nsIWebNavigation.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsICodebasePrincipal.h" +#include "nsIDOMNSHTMLDocument.h" + static NS_DEFINE_CID(kURILoaderCID, NS_URI_LOADER_CID); static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID); @@ -463,6 +471,13 @@ nsURILoader::nsURILoader() { NS_INIT_ISUPPORTS(); m_listeners = new nsVoidArray(); + + // Check pref to see if we should prevent frameset spoofing + mValidateOrigin = PR_TRUE; // secure by default, pref disables check + nsCOMPtr prefs = do_GetService(NS_PREF_CONTRACTID); + NS_ASSERTION(prefs,"nsURILoader: could not get prefs service!\n"); + if (prefs) + prefs->GetBoolPref("browser.frame.validate_origin", &mValidateOrigin); } nsURILoader::~nsURILoader() @@ -506,7 +521,143 @@ NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel * aChannel, return OpenURIVia(aChannel, aCommand, aWindowTarget, aWindowContext, 0 /* ip address */); } -NS_IMETHODIMP nsURILoader::GetTarget(nsIChannel * aChannel, const char * aWindowTarget, +// +// Bug 13871: Prevent frameset spoofing +// Check if origin document uri is the equivalent to target's principal. +// This takes into account subdomain checking if document.domain is set for +// Nav 4.x compatability. +// +// The following was derived from nsCodeBasePrincipal::Equals but in addition +// to the host PL_strcmp, it accepts a subdomain (nsHTMLDocument::SetDomain) +// if the document.domain was set. +// +static +PRBool SameOrSubdomainOfTarget(nsIURI* aOriginURI, nsIURI* aTargetURI, PRBool aDocumentDomainSet) +{ + nsXPIDLCString targetScheme; + nsresult rv = aTargetURI->GetScheme(getter_Copies(targetScheme)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetScheme, PR_TRUE); + + nsXPIDLCString originScheme; + rv = aOriginURI->GetScheme(getter_Copies(originScheme)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && originScheme, PR_TRUE); + + if (PL_strcmp(targetScheme, originScheme)) + return PR_FALSE; // Different schemes - check fails + + if (! PL_strcmp(targetScheme, "file")) + return PR_TRUE; // All file: urls are considered to have the same origin. + + if (! PL_strcmp(targetScheme, "imap") || + ! PL_strcmp(targetScheme, "mailbox") || + ! PL_strcmp(targetScheme, "news")) + { + + // Each message is a distinct trust domain; use the whole spec for comparison + nsXPIDLCString targetSpec; + rv =aTargetURI->GetSpec(getter_Copies(targetSpec)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetSpec, PR_TRUE); + + nsXPIDLCString originSpec; + rv = aOriginURI->GetSpec(getter_Copies(originSpec)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && originSpec, PR_TRUE); + + return (! PL_strcmp(targetSpec, originSpec)); // True if full spec is same, false otherwise + } + + // Compare ports. + int targetPort, originPort; + rv = aTargetURI->GetPort(&targetPort); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv), PR_TRUE); + + rv = aOriginURI->GetPort(&originPort); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv), PR_TRUE); + + if (targetPort != originPort) + return PR_FALSE; // Different port - check fails + + // Need to check the hosts + nsXPIDLCString targetHost; + rv = aTargetURI->GetHost(getter_Copies(targetHost)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetHost, PR_TRUE); + + nsXPIDLCString originHost; + rv = aOriginURI->GetHost(getter_Copies(originHost)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && originHost, PR_TRUE); + + if (!PL_strcmp(targetHost, originHost)) + return PR_TRUE; // Hosts are the same - check passed + + // If document.domain was set, do the relaxed check + // Right align hostnames and compare - ensure preceeding char is . or / + if (aDocumentDomainSet) + { + int targetHostLen = PL_strlen(targetHost); + int originHostLen = PL_strlen(originHost); + int prefixChar = originHostLen-targetHostLen-1; + + return ((originHostLen > targetHostLen) && + (! PL_strcmp((originHost+prefixChar+1), targetHost)) && + (originHost[prefixChar] == '.' || originHost[prefixChar] == '/')); + } + + return PR_FALSE; // document.domain not set and hosts not same - check failed +} + +// +// Bug 13871: Prevent frameset spoofing +// +// This routine answers: 'Is origin's document from same domain as target's document?' +// Be optimistic that domain is same - error cases all answer 'yes'. +// +// We have to compare the URI of the actual document loaded in the origin, +// ignoring any document.domain that was set, with the principal URI of the +// target (including any document.domain that was set). This puts control +// of loading in the hands of the target, which is more secure. (per Nav 4.x) +// +static +PRBool ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem, nsIDocShellTreeItem* aTargetTreeItem) +{ + // Get origin document uri (ignoring document.domain) + nsCOMPtr originWebNav(do_QueryInterface(aOriginTreeItem)); + NS_ENSURE_TRUE(originWebNav, PR_TRUE); + + nsCOMPtr originDocumentURI; + nsresult rv = originWebNav->GetCurrentURI(getter_AddRefs(originDocumentURI)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && originDocumentURI, PR_TRUE); + + // Get target principal uri (including document.domain) + nsCOMPtr targetDOMDocument(do_GetInterface(aTargetTreeItem)); + NS_ENSURE_TRUE(targetDOMDocument, NS_ERROR_FAILURE); + + nsCOMPtr targetDocument(do_QueryInterface(targetDOMDocument)); + NS_ENSURE_TRUE(targetDocument, NS_ERROR_FAILURE); + + nsCOMPtr targetPrincipal; + rv = targetDocument->GetPrincipal(getter_AddRefs(targetPrincipal)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetPrincipal, rv); + + nsCOMPtr targetCodebasePrincipal(do_QueryInterface(targetPrincipal)); + NS_ENSURE_TRUE(targetCodebasePrincipal, PR_TRUE); + + nsCOMPtr targetPrincipalURI; + rv = targetCodebasePrincipal->GetURI(getter_AddRefs(targetPrincipalURI)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && targetPrincipalURI, PR_TRUE); + + // Find out if document.domain was set + nsCOMPtr targetDOMNSHTMLDocument(do_QueryInterface(targetDocument)); + NS_ENSURE_TRUE(targetDOMNSHTMLDocument, NS_ERROR_FAILURE); + + PRBool documentDomainSet; + rv = targetDOMNSHTMLDocument->GetDomainSet(&documentDomainSet); + NS_ENSURE_SUCCESS(rv, rv); + + // Is origin same principal or a subdomain of target's document.domain + // Compare actual URI of origin document, not origin principal's URI. (Per Nav 4.x) + return SameOrSubdomainOfTarget(originDocumentURI, targetPrincipalURI, documentDomainSet); +} + +NS_IMETHODIMP nsURILoader::GetTarget(nsIChannel * aChannel, nsCString &aWindowTarget, nsISupports * aWindowContext, nsISupports ** aRetargetedWindowContext) { @@ -549,6 +700,33 @@ NS_IMETHODIMP nsURILoader::GetTarget(nsIChannel * aChannel, const char * aWindow else { windowCtxtAsTreeItem->FindItemWithName(name.GetUnicode(), nsnull, getter_AddRefs(treeItem)); + + // Bug 13871: Prevent frameset spoofing + // See BugSplat 336170, 338737 and XP_FindNamedContextInList in the classic codebase + // Per Nav's behaviour: + // - pref controlled: "browser.frame.validate_origin" (mValidateOrigin) + // - allow load if host of target or target's parent is same as host of origin + // - allow load if target is a top level window + + // Check to see if pref is true + if (mValidateOrigin && windowCtxtAsTreeItem && treeItem) { + + // Is origin frame from the same domain as target frame? + if (! ValidateOrigin(windowCtxtAsTreeItem, treeItem)) { + + // No. Is origin frame from the same domain as target's parent? + nsCOMPtr targetParentTreeItem; + nsresult rv = treeItem->GetSameTypeParent(getter_AddRefs(targetParentTreeItem)); + if (NS_SUCCEEDED(rv) && targetParentTreeItem) { + if (! ValidateOrigin(windowCtxtAsTreeItem, targetParentTreeItem)) { + + // Neither is from the origin domain, send load to a new window (_blank) + *aRetargetedWindowContext = aWindowContext; + aWindowTarget.Assign("_blank"); + } // else (target's parent from origin domain) allow this load + } // else (no parent) allow this load since shell is a toplevel window + } // else (target from origin domain) allow this load + } // else (pref is false) allow this load } nsCOMPtr treeItemCtxt (do_QueryInterface(treeItem)); @@ -592,8 +770,9 @@ NS_IMETHODIMP nsURILoader::OpenURIVia(nsIChannel * aChannel, } } + nsCAutoString windowTarget(aWindowTarget); nsCOMPtr retargetedWindowContext; - NS_ENSURE_SUCCESS(GetTarget(aChannel, aWindowTarget, aOriginalWindowContext, getter_AddRefs(retargetedWindowContext)), NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(GetTarget(aChannel, windowTarget, aOriginalWindowContext, getter_AddRefs(retargetedWindowContext)), NS_ERROR_FAILURE); NS_NEWXPCOM(loader, nsDocumentOpenInfo); if (!loader) return NS_ERROR_OUT_OF_MEMORY; @@ -605,7 +784,7 @@ NS_IMETHODIMP nsURILoader::OpenURIVia(nsIChannel * aChannel, loader->Init(retargetedWindowContext, aOriginalWindowContext); // Extra Info // now instruct the loader to go ahead and open the url - rv = loader->Open(aChannel, aCommand, aWindowTarget, retargetedWindowContext); + rv = loader->Open(aChannel, aCommand, windowTarget, retargetedWindowContext); NS_RELEASE(loader); return rv; diff --git a/uriloader/base/nsURILoader.h b/uriloader/base/nsURILoader.h index 8edc07936d53..e5a138554608 100644 --- a/uriloader/base/nsURILoader.h +++ b/uriloader/base/nsURILoader.h @@ -27,6 +27,7 @@ #include "nsISupportsUtils.h" #include "nsCOMPtr.h" #include "nsIInterfaceRequestor.h" +#include "nsString.h" class nsVoidArray; @@ -45,6 +46,9 @@ protected: // when they go away. nsVoidArray * m_listeners; + // If set, we will try to prevent frame spoofing (set by pref in constructor) + PRBool mValidateOrigin; + // prepare the load cookie for the window context nsresult SetupLoadCookie(nsISupports * aWindowContext, nsIInterfaceRequestor ** aLoadCookie); @@ -55,7 +59,7 @@ protected: const char * aWindowTarget, char ** aContentTypeToUse); - NS_IMETHOD GetTarget(nsIChannel * aChannel, const char * aWindowTarget, + NS_IMETHOD GetTarget(nsIChannel * aChannel, nsCString &aWindowTarget, nsISupports * aWindowContext, nsISupports ** aRetargetedWindowContext); };