From 92e156858b13636981194acbb32a2918f65f1f53 Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Mon, 27 Feb 2012 13:58:00 -0800 Subject: [PATCH] Bug 406541 - Ensure we agree with java on applet codebase, and run security checks for file: codebase URIs. r=jst --- .../base/public/nsIObjectLoadingContent.idl | 11 ++- content/base/src/nsObjectLoadingContent.cpp | 99 ++++++++++++++++--- content/base/src/nsObjectLoadingContent.h | 14 ++- dom/plugins/base/nsPluginInstanceOwner.cpp | 36 +++++++ 4 files changed, 137 insertions(+), 23 deletions(-) diff --git a/content/base/public/nsIObjectLoadingContent.idl b/content/base/public/nsIObjectLoadingContent.idl index e49e6da21221..92a15adf1cd4 100644 --- a/content/base/public/nsIObjectLoadingContent.idl +++ b/content/base/public/nsIObjectLoadingContent.idl @@ -42,6 +42,7 @@ interface nsIObjectFrame; interface nsIPluginTag; interface nsIDOMElement; interface nsIDOMClientRect; +interface nsIURI; %{C++ #include "nsNPAPIPluginInstance.h" @@ -51,7 +52,7 @@ interface nsIDOMClientRect; /** * This interface represents a content node that loads objects. */ -[scriptable, uuid(6D8914C7-0E22-4452-8962-11B69BBE84D7)] +[scriptable, uuid(3FF07AB3-5BAC-4D98-9549-5BD15CCEBCD3)] interface nsIObjectLoadingContent : nsISupports { const unsigned long TYPE_LOADING = 0; @@ -79,6 +80,14 @@ interface nsIObjectLoadingContent : nsISupports */ unsigned long getContentTypeForMIMEType(in AUTF8String aMimeType); + /** + * Gets the base URI to be used for this object. This differs from + * nsIContent::GetBaseURI in that it takes codebase attributes into + * account. The MIME type is required as some plugins (java) calculate + * this differently. + */ + nsIURI GetObjectBaseURI(in ACString aMimeType); + /** * Returns the plugin instance if it has already been instantiated. This * will never instantiate the plugin and so is safe to call even when diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index fc75f4709328..576d3f4a4871 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -598,7 +598,7 @@ nsObjectLoadingContent::InstantiatePluginInstance(const char* aMimeType, nsIURI* if (!aURI) { // We need some URI. If we have nothing else, use the base URI. // XXX(biesi): The code used to do this. Not sure why this is correct... - GetObjectBaseURI(thisContent, getter_AddRefs(baseURI)); + GetObjectBaseURI(nsCString(aMimeType), getter_AddRefs(baseURI)); aURI = baseURI; } @@ -1163,7 +1163,7 @@ nsObjectLoadingContent::LoadObject(const nsAString& aURI, nsIDocument* doc = thisContent->OwnerDoc(); nsCOMPtr baseURI; - GetObjectBaseURI(thisContent, getter_AddRefs(baseURI)); + GetObjectBaseURI(aTypeHint, getter_AddRefs(baseURI)); nsCOMPtr uri; nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), @@ -1194,6 +1194,51 @@ nsObjectLoadingContent::UpdateFallbackState(nsIContent* aContent, } } +bool +nsObjectLoadingContent::IsFileCodebaseAllowable(nsIURI* aBaseURI, nsIURI* aOriginURI) +{ + nsCOMPtr baseFileURL(do_QueryInterface(aBaseURI)); + nsCOMPtr originFileURL(do_QueryInterface(aOriginURI)); + + // get IFile handles and normalize + nsCOMPtr originFile; + nsCOMPtr baseFile; + if (!originFileURL || !baseFileURL || + NS_FAILED(originFileURL->GetFile(getter_AddRefs(originFile))) || + NS_FAILED(baseFileURL->GetFile(getter_AddRefs(baseFile))) || + NS_FAILED(baseFile->Normalize()) || + NS_FAILED(originFile->Normalize())) { + return false; + } + + // If the origin is a directory, it should contain/equal baseURI + // Otherwise, its parent directory should contain/equal baseURI + bool origin_is_dir; + bool contained = false; + nsresult rv = originFile->IsDirectory(&origin_is_dir); + NS_ENSURE_SUCCESS(rv, false); + + if (origin_is_dir) { + // originURI is a directory, ensure it contains the baseURI + rv = originFile->Contains(baseFile, true, &contained); + if (NS_SUCCEEDED(rv) && !contained) { + rv = originFile->Equals(baseFile, &contained); + } + } else { + // originURI is a file, ensure its parent contains the baseURI + nsCOMPtr originParent; + rv = originFile->GetParent(getter_AddRefs(originParent)); + if (NS_SUCCEEDED(rv) && originParent) { + rv = originParent->Contains(baseFile, true, &contained); + if (NS_SUCCEEDED(rv) && !contained) { + rv = originParent->Equals(baseFile, &contained); + } + } + } + + return NS_SUCCEEDED(rv) && contained; +} + nsresult nsObjectLoadingContent::LoadObject(nsIURI* aURI, bool aNotify, @@ -1291,6 +1336,28 @@ nsObjectLoadingContent::LoadObject(nsIURI* aURI, HandleBeingBlockedByContentPolicy(rv, shouldLoad); return NS_OK; } + + // If this is a file:// URI, require that the codebase (baseURI) + // is contained within the same folder as the document origin (originURI) + // or within the document origin, if it is a folder. + // No originURI implies chrome, which bypasses the check + // -- bug 406541 + nsCOMPtr originURI; + nsCOMPtr baseURI; + GetObjectBaseURI(aTypeHint, getter_AddRefs(baseURI)); + rv = thisContent->NodePrincipal()->GetURI(getter_AddRefs(originURI)); + if (NS_FAILED(rv)) { + Fallback(aNotify); + return NS_OK; + } + if (originURI) { + bool isfile; + if (NS_FAILED(originURI->SchemeIs("file", &isfile)) || + (isfile && !IsFileCodebaseAllowable(baseURI, originURI))) { + Fallback(aNotify); + return NS_OK; + } + } } nsresult rv = NS_ERROR_UNEXPECTED; @@ -1408,7 +1475,7 @@ nsObjectLoadingContent::LoadObject(nsIURI* aURI, // XXX(biesi). The plugin instantiation code used to pass the base URI // here instead of the plugin URI for instantiation via class ID, so I // continue to do so. Why that is, no idea... - GetObjectBaseURI(thisContent, getter_AddRefs(mURI)); + GetObjectBaseURI(mContentType, getter_AddRefs(mURI)); if (!mURI) { mURI = aURI; } @@ -1785,25 +1852,29 @@ nsObjectLoadingContent::TypeForClassID(const nsAString& aClassID, return NS_ERROR_NOT_AVAILABLE; } -void -nsObjectLoadingContent::GetObjectBaseURI(nsIContent* thisContent, nsIURI** aURI) +NS_IMETHODIMP +nsObjectLoadingContent::GetObjectBaseURI(const nsACString & aMimeType, nsIURI** aURI) { - // We want to use swap(); since this is just called from this file, - // we can assert this (callers use comptrs) - NS_PRECONDITION(*aURI == nsnull, "URI must be inited to zero"); + nsCOMPtr thisContent = + do_QueryInterface(static_cast(this)); // For plugins, the codebase attribute is the base URI nsCOMPtr baseURI = thisContent->GetBaseURI(); nsAutoString codebase; thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::codebase, codebase); - if (!codebase.IsEmpty()) { - nsContentUtils::NewURIWithDocumentCharset(aURI, codebase, - thisContent->OwnerDoc(), - baseURI); - } else { - baseURI.swap(*aURI); + + if (codebase.IsEmpty() && aMimeType.Equals("application/x-java-vm")) { + // bug 406541 + // Java resolves codebase="" as "/" -- so we replicate that quirk, to ensure + // we run security checks against the same path. + codebase.AssignLiteral("/"); } + + nsContentUtils::NewURIWithDocumentCharset(aURI, codebase, + thisContent->OwnerDoc(), + baseURI); + return NS_OK; } nsObjectFrame* diff --git a/content/base/src/nsObjectLoadingContent.h b/content/base/src/nsObjectLoadingContent.h index 89cdb18bd7b4..720e9ca8bb95 100644 --- a/content/base/src/nsObjectLoadingContent.h +++ b/content/base/src/nsObjectLoadingContent.h @@ -253,6 +253,12 @@ class nsObjectLoadingContent : public nsImageLoadingContent */ static bool IsSuccessfulRequest(nsIRequest* aRequest); + /** + * Check if the given baseURI is contained in the same directory as the + * aOriginURI (or a child thereof) + */ + static bool IsFileCodebaseAllowable(nsIURI* aBaseURI, nsIURI* aOriginURI); + /** * Check whether the URI can be handled internally. */ @@ -299,14 +305,6 @@ class nsObjectLoadingContent : public nsImageLoadingContent */ nsresult TypeForClassID(const nsAString& aClassID, nsACString& aType); - /** - * Gets the base URI to be used for this object. This differs from - * nsIContent::GetBaseURI in that it takes codebase attributes into - * account. - */ - void GetObjectBaseURI(nsIContent* thisContent, nsIURI** aURI); - - /** * Gets the frame that's associated with this content node. * Does not flush. diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index d0e88852481f..418f3330a827 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -95,6 +95,7 @@ using mozilla::DefaultXDisplay; #include "nsIDOMDragEvent.h" #include "nsIScrollableFrame.h" #include "nsIImageLoadingContent.h" +#include "nsIObjectLoadingContent.h" #include "nsContentCID.h" #include "nsWidgetsCID.h" @@ -1195,6 +1196,31 @@ nsresult nsPluginInstanceOwner::EnsureCachedAttrParamArrays() mNumCachedAttrs++; } + // Some plugins (java, bug 406541) don't canonicalize the 'codebase' attribute + // in a standard way - codebase="" results in / (domain root), but + // codebase="blah" results in ./blah; codebase="file:" results in "file:///". + // We canonicalize codebase here to ensure the codebase we run security checks + // against is the same codebase java uses. + // Note that GetObjectBaseURI mimics some of java's quirks for maximal + // compatibility. + const char* mime = nsnull; + bool addCodebase = false; + nsCAutoString codebaseSpec; + if (mInstance && NS_SUCCEEDED(mInstance->GetMIMEType(&mime)) && mime && + strcmp(mime, "application/x-java-vm") == 0) { + addCodebase = true; + nsCOMPtr objlContent = do_QueryInterface(mContent); + nsCOMPtr codebaseURI; + objlContent->GetObjectBaseURI(nsCString(mime), getter_AddRefs(codebaseURI)); + nsresult rv = codebaseURI->GetSpec(codebaseSpec); + NS_ENSURE_SUCCESS(rv, rv); + + // Make space if codebase isn't already set + if (!mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::codebase)) { + mNumCachedAttrs++; + } + } + mCachedAttrParamNames = (char**)NS_Alloc(sizeof(char*) * (mNumCachedAttrs + 1 + mNumCachedParams)); NS_ENSURE_TRUE(mCachedAttrParamNames, NS_ERROR_OUT_OF_MEMORY); mCachedAttrParamValues = (char**)NS_Alloc(sizeof(char*) * (mNumCachedAttrs + 1 + mNumCachedParams)); @@ -1248,12 +1274,22 @@ nsresult nsPluginInstanceOwner::EnsureCachedAttrParamArrays() mNumCachedAttrs--; wmodeSet = true; } + } else if (addCodebase && 0 == PL_strcasecmp(mCachedAttrParamNames[nextAttrParamIndex], "codebase")) { + mCachedAttrParamValues[nextAttrParamIndex] = ToNewUTF8String(NS_ConvertUTF8toUTF16(codebaseSpec)); + addCodebase = false; } else { mCachedAttrParamValues[nextAttrParamIndex] = ToNewUTF8String(value); } nextAttrParamIndex++; } + // Pontentially add CODEBASE attribute + if (addCodebase) { + mCachedAttrParamNames [nextAttrParamIndex] = ToNewUTF8String(NS_LITERAL_STRING("codebase")); + mCachedAttrParamValues[nextAttrParamIndex] = ToNewUTF8String(NS_ConvertUTF8toUTF16(codebaseSpec)); + nextAttrParamIndex++; + } + // Potentially add WMODE attribute. if (!wmodeType.IsEmpty() && !wmodeSet) { mCachedAttrParamNames [nextAttrParamIndex] = ToNewUTF8String(NS_LITERAL_STRING("wmode"));