Bug 369814: don't open jar: content unless served from a safe mime type. r=bz, sr=dveditz, ui-r=beltzner

This commit is contained in:
dcamp@mozilla.com 2007-11-26 21:32:23 -08:00
parent 5c633024c5
commit 1b9adf5c62
18 changed files with 390 additions and 8 deletions

View File

@ -53,6 +53,7 @@ deniedPortAccess=This address uses a network port which is normally used for pur
proxyResolveFailure=Firefox is configured to use a proxy server that can't be found.
proxyConnectFailure=Firefox is configured to use a proxy server that is refusing connections.
contentEncodingError=The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression. Please contact the website owners to inform them of this problem.
unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
externalProtocolTitle=External Protocol Request
externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
#LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined

View File

@ -66,6 +66,13 @@
</ul>
">
<!ENTITY unsafeContentType.title "Unsafe File Type">
<!ENTITY unsafeContentType.longDesc "
<ul>
<li>Please contact the website owners to inform them of this problem.</li>
</ul>
">
<!ENTITY netReset.title "The connection was reset">
<!ENTITY netReset.longDesc "&sharedLongDesc;">

View File

@ -85,6 +85,7 @@ REQUIRES = xpcom \
windowwatcher \
imglib2 \
mimetype \
jar \
$(NULL)
SDK_XPIDLSRCS = \

View File

@ -168,6 +168,8 @@
#include "nsITextToSubURI.h"
#include "nsIJARChannel.h"
#include "prlog.h"
#include "prmem.h"
@ -1300,12 +1302,37 @@ nsDocShell::SetDocumentCharsetInfo(nsIDocumentCharsetInfo *
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::GetChannelIsUnsafe(PRBool *aUnsafe)
{
*aUnsafe = PR_FALSE;
nsCOMPtr<nsIChannel> channel;
GetCurrentDocumentChannel(getter_AddRefs(channel));
if (!channel) {
return NS_OK;
}
nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
if (!jarChannel) {
return NS_OK;
}
return jarChannel->GetIsUnsafe(aUnsafe);
}
NS_IMETHODIMP
nsDocShell::GetAllowPlugins(PRBool * aAllowPlugins)
{
NS_ENSURE_ARG_POINTER(aAllowPlugins);
*aAllowPlugins = mAllowPlugins;
if (!mAllowPlugins) {
return NS_OK;
}
PRBool unsafe;
*aAllowPlugins = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
return NS_OK;
}
@ -1323,6 +1350,12 @@ nsDocShell::GetAllowJavascript(PRBool * aAllowJavascript)
NS_ENSURE_ARG_POINTER(aAllowJavascript);
*aAllowJavascript = mAllowJavascript;
if (!mAllowJavascript) {
return NS_OK;
}
PRBool unsafe;
*aAllowJavascript = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
return NS_OK;
}
@ -1338,6 +1371,12 @@ NS_IMETHODIMP nsDocShell::GetAllowMetaRedirects(PRBool * aReturn)
NS_ENSURE_ARG_POINTER(aReturn);
*aReturn = mAllowMetaRedirects;
if (!mAllowMetaRedirects) {
return NS_OK;
}
PRBool unsafe;
*aReturn = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
return NS_OK;
}
@ -3036,6 +3075,10 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI *aURI,
// Bad Content Encoding.
error.AssignLiteral("contentEncodingError");
break;
case NS_ERROR_UNSAFE_CONTENT_TYPE:
// Channel refused to load from an unrecognized content type.
error.AssignLiteral("unsafeContentType");
break;
}
}
@ -6553,6 +6596,25 @@ nsDocShell::InternalLoad(nsIURI * aURI,
(aFlags & INTERNAL_LOAD_FLAGS_INHERIT_OWNER) &&
NS_SUCCEEDED(URIInheritsSecurityContext(aURI, &inherits)) &&
inherits) {
// Don't allow loads that would inherit our security context
// if this document came from an unsafe channel.
nsCOMPtr<nsIDocShellTreeItem> treeItem = this;
do {
nsCOMPtr<nsIDocShell> itemDocShell =
do_QueryInterface(treeItem);
PRBool isUnsafe;
if (itemDocShell &&
NS_SUCCEEDED(itemDocShell->GetChannelIsUnsafe(&isUnsafe)) &&
isUnsafe) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsIDocShellTreeItem> parent;
treeItem->GetSameTypeParent(getter_AddRefs(parent));
parent.swap(treeItem);
} while (treeItem);
owner = GetInheritedPrincipal(PR_TRUE);
}
}

View File

@ -68,7 +68,7 @@ interface nsILayoutHistoryState;
interface nsISecureBrowserUI;
interface nsIDOMStorage;
[scriptable, uuid(10ed386d-8598-408c-b571-e75ad18edeb0)]
[scriptable, uuid(4b00222a-8d0a-46d7-a1fe-43bd89d19324)]
interface nsIDocShell : nsISupports
{
/**
@ -448,5 +448,12 @@ interface nsIDocShell : nsISupports
* document has been set up)
*/
readonly attribute boolean isInUnload;
/**
* Find out if the currently loaded document came from a suspicious channel
* (such as a JAR channel where the server-returned content type isn't a
* known JAR type).
*/
readonly attribute boolean channelIsUnsafe;
};

View File

@ -1196,6 +1196,7 @@ nsresult nsWebShell::EndPageLoad(nsIWebProgress *aProgress,
aStatus == NS_ERROR_NET_RESET ||
aStatus == NS_ERROR_MALWARE_URI ||
aStatus == NS_ERROR_PHISHING_URI ||
aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
DisplayLoadError(aStatus, url, nsnull, channel);
}

View File

@ -219,6 +219,7 @@
<h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
<h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
<h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
<h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
<h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
@ -240,6 +241,7 @@
<div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
<div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
<div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
<div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
<div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
<div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>

View File

@ -54,6 +54,8 @@ include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
test_bug344861.html \
test_bug369814.html \
bug369814.zip \
test_bug384014.html \
test_bug387979.html \
test_bug404548.html \

BIN
docshell/test/bug369814.zip Normal file

Binary file not shown.

View File

@ -0,0 +1,204 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=369814
-->
<head>
<title>Test for Bug 369814</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384014">Mozilla Bug 369814</a>
<p>
<iframe id="testFrame"></iframe>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Tests for Bug 369814 **/
SimpleTest.waitForExplicitFinish();
// Because child scripts won't be able to run to tell us they're done,
// we need to just wait for them. Wait this amount of time before checking
// the results.
const gLoadTimeout = 3000;
var Ci = Components.interfaces;
var gCurrentTest;
var gTargetWindow;
var gNumPokes;
var gPrefValue;
var gTestFrame = document.getElementById('testFrame');
/**
* Called by documents loaded from jar files to indicate that they can access
* this document.
*/
function poke(description) {
ok(false, gCurrentTest['name'] + ": got unexpected poke: " + description);
gNumPokes++;
}
function loadEvent(window, callback)
{
var fn = function() {
window.removeEventListener("load", fn, false);
callback();
};
window.addEventListener("load", fn, false);
}
function loadTestTarget(callback)
{
gTargetWindow = window.open("http://localhost:8888", "bug369814target");
loadEvent(gTargetWindow, callback);
}
function closeTestTarget()
{
gTargetWindow.close();
gTargetWindow = null;
}
function loadErrorTest(test)
{
gTestFrame.src = test['url'];
// Give the frame a chance to fail at loading
setTimeout(function() {
// XXX: There doesn't seem to be a reliable check for "got an error,"
// but reaching in to an error document will throw an exception
var errorPage;
try {
var item = gTestFrame.contentDocument.getElementById(gCurrentTest['data-iframe']);
errorPage = false;
} catch (e) {
errorPage = true;
}
ok(errorPage, gCurrentTest["name"] + ": should block a suspicious JAR load.");
finishTest();
}, gLoadTimeout);
}
function iframeTest(test) {
gTestFrame.src = test['url'];
loadEvent(gTestFrame, function() {
finishTest();
});
}
function refreshTest(test) {
gTestFrame.src = test['url'];
loadEvent(gTestFrame, function() {
// Wait for the frame to try and refresh
// XXX: a "blocked redirect" signal would be needed to get rid of
// this timeout.
setTimeout(function() {
finishTest();
}, gLoadTimeout);
});
}
function anchorTest(test) {
loadTestTarget(function() {
gTestFrame.src = test['url'];
loadEvent(gTestFrame, function() {
sendMouseEvent({type:'click'}, 'target', gTestFrame.contentWindow);
sendMouseEvent({type:'click'}, 'notarget', gTestFrame.contentWindow);
// Give the clicks a chance to load
setTimeout(function() {
closeTestTarget();
finishTest();
}, gLoadTimeout);
});
});
}
var gTests = [
{ "name" : "iframes.html loaded from non-jar type, pref disabled",
"url" : "jar:http://localhost:8888/tests/docshell/test/bug369814.zip!/iframes.html",
"pref" : false,
"pokes" : { },
"func" : loadErrorTest,
},
{ "name" : "refresh.html loaded from non-jar type, pref enabled",
"url" : "jar:http://localhost:8888/tests/docshell/test/bug369814.zip!/refresh.html",
"pref" : true,
"pokes" : { },
"func" : refreshTest,
},
{ "name" : "iframes.html loaded from non-jar type, pref enabled",
"url" : "jar:http://localhost:8888/tests/docshell/test/bug369814.zip!/iframes.html",
"pref" : true,
"pokes" : { },
"func" : iframeTest,
},
{ "name" : "anchors.html loaded from non-jar type, pref enabled",
"url" : "jar:http://localhost:8888/tests/docshell/test/bug369814.zip!/anchors.html",
"pref" : true,
"pokes" : { },
"func" : anchorTest,
},
];
var gNextTest = 0;
function runNextTest()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var prefs = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
if (gNextTest < gTests.length) {
gCurrentTest = gTests[gNextTest++];
gNumPokes = 0;
prefs.setBoolPref("network.jar.open-unsafe-types", gCurrentTest['pref']);
gCurrentTest['func'](gCurrentTest);
} else {
// Put back the pref value we had at test start
prefs.setBoolPref("network.jar.open-unsafe-types", gPrefValue);
SimpleTest.finish();
}
}
function finishTest()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var prefs = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
prefs.setBoolPref("network.jar.open-unsafe-types", false);
if (gNumPokes == 0) {
ok(true, gCurrentTest["name"] + ": no unexpected pokes");
}
runNextTest();
}
function startTests()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var prefs = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
gPrefValue = prefs.getBoolPref("network.jar.open-unsafe-types");
}
addLoadEvent(runNextTest);
</script>
</pre>
</body>
</html>

View File

@ -53,6 +53,7 @@ deniedPortAccess=Access to the port number given has been disabled for security
proxyResolveFailure=The proxy server you have configured could not be found. Please check your proxy settings and try again.
proxyConnectFailure=The connection was refused when attempting to contact the proxy server you have configured. Please check your proxy settings and try again.
contentEncodingError=The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression. Please contact the website owners to inform them of this problem.
unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
externalProtocolTitle=External Protocol Request
externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
#LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined

View File

@ -30,6 +30,13 @@
<!ENTITY contentEncodingError.title "Content Encoding Error">
<!ENTITY contentEncodingError.longDesc "<p>The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
<!ENTITY unsafeContentType.title "Unsafe File Type">
<!ENTITY unsafeContentType.longDesc "
<ul>
<li>Please contact the website owners to inform them of this problem.</li>
</ul>
">
<!ENTITY netReset.title "Connection Interrupted">
<!ENTITY netReset.longDesc "<p>The network link was interrupted while negotiating a connection. Please try again.</p>">

View File

@ -37,7 +37,14 @@
#include "nsIChannel.idl"
[scriptable, uuid(c7e410d1-85f2-11d3-9f63-006008a6efe9)]
[scriptable, uuid(6e6cc56d-51eb-4299-a795-dcfd1229ab3d)]
interface nsIJARChannel : nsIChannel
{
/**
* Returns TRUE if the JAR file is not safe (if the content type reported
* by the server for a remote JAR is not of an expected type). Scripting,
* redirects, and plugins should be disabled when loading from this
* channel.
*/
readonly attribute boolean isUnsafe;
};

View File

@ -44,6 +44,8 @@
#include "nsNetUtil.h"
#include "nsInt64.h"
#include "nsEscape.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIScriptSecurityManager.h"
#include "nsIPrincipal.h"
@ -222,6 +224,7 @@ nsJARChannel::nsJARChannel()
, mLoadFlags(LOAD_NORMAL)
, mStatus(NS_OK)
, mIsPending(PR_FALSE)
, mIsUnsafe(PR_TRUE)
, mJarInput(nsnull)
{
#if defined(PR_LOGGING)
@ -323,6 +326,8 @@ nsJARChannel::EnsureJarInput(PRBool blocking)
}
if (mJarFile) {
mIsUnsafe = PR_FALSE;
// NOTE: we do not need to deal with mSecurityInfo here,
// because we're loading from a local file
rv = CreateJarInput(gJarHandler->JarCache());
@ -337,7 +342,7 @@ nsJARChannel::EnsureJarInput(PRBool blocking)
if (NS_SUCCEEDED(rv))
rv = NS_OpenURI(mDownloader, nsnull, mJarBaseURI, nsnull,
mLoadGroup, mCallbacks,
mLoadFlags & ~LOAD_DOCUMENT_URI);
mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS));
}
return rv;
@ -644,6 +649,9 @@ nsJARChannel::Open(nsIInputStream **stream)
NS_ENSURE_TRUE(!mJarInput, NS_ERROR_IN_PROGRESS);
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
mJarFile = nsnull;
mIsUnsafe = PR_TRUE;
nsresult rv = EnsureJarInput(PR_TRUE);
if (NS_FAILED(rv)) return rv;
@ -666,6 +674,9 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
NS_ENSURE_ARG_POINTER(listener);
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
mJarFile = nsnull;
mIsUnsafe = PR_TRUE;
// Initialize mProgressSink
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
@ -690,6 +701,16 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsIJARChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsJARChannel::GetIsUnsafe(PRBool *isUnsafe)
{
*isUnsafe = mIsUnsafe;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsIDownloadObserver
//-----------------------------------------------------------------------------
@ -729,6 +750,47 @@ nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
}
status = rv;
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
// We only want to run scripts if the server really intended to
// send us a JAR file. Check the server-supplied content type for
// a JAR type.
nsCAutoString header;
httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
header);
nsCAutoString contentType;
nsCAutoString charset;
NS_ParseContentType(header, contentType, charset);
mIsUnsafe = !contentType.EqualsLiteral("application/java-archive") &&
!contentType.EqualsLiteral("application/x-jar");
} else {
nsCOMPtr<nsIJARChannel> innerJARChannel(do_QueryInterface(channel));
if (innerJARChannel) {
PRBool unsafe;
innerJARChannel->GetIsUnsafe(&unsafe);
mIsUnsafe = unsafe;
}
}
// XXX: THIS IS TEMPORARY
//mIsUnsafe = PR_FALSE;
}
if (mIsUnsafe) {
PRBool allowUnpack = PR_FALSE;
nsCOMPtr<nsIPrefBranch> prefs =
do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
prefs->GetBoolPref("network.jar.open-unsafe-types", &allowUnpack);
}
if (!allowUnpack) {
status = NS_ERROR_UNSAFE_CONTENT_TYPE;
}
}
if (NS_SUCCEEDED(status)) {
@ -745,6 +807,7 @@ nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
}
if (NS_FAILED(status)) {
mStatus = status;
OnStartRequest(nsnull, nsnull);
OnStopRequest(nsnull, nsnull, status);
}

View File

@ -97,7 +97,8 @@ private:
PRInt32 mContentLength;
PRUint32 mLoadFlags;
nsresult mStatus;
PRBool mIsPending;
PRPackedBool mIsPending;
PRPackedBool mIsUnsafe;
nsJARInputThunk *mJarInput;
nsCOMPtr<nsIStreamListener> mDownloader;

View File

@ -613,6 +613,11 @@ pref("network.http.pipelining.maxrequests" , 4);
// </http>
// If false, remote JAR files that are served with a content type other than
// application/java-archive or application/x-jar will not be opened
// by the jar channel.
pref("network.jar.open-unsafe-types", false);
// This preference controls whether or not internationalized domain names (IDN)
// are handled. IDN requires a nsIIDNService implementation.
pref("network.enableIDN", true);

View File

@ -232,6 +232,13 @@
#define NS_ERROR_REDIRECT_LOOP \
NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_NETWORK, 31)
/**
* The request failed because the content type returned by the server was
* not a type expected by the channel (for nested channels such as the JAR
* channel).
*/
#define NS_ERROR_UNSAFE_CONTENT_TYPE \
NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_NETWORK, 74)
/******************************************************************************
* FTP specific error codes:

View File

@ -15,20 +15,24 @@
*
* sendMouseEvent({type:'click'}, 'node');
*/
function sendMouseEvent(aEvent, aTarget) {
function sendMouseEvent(aEvent, aTarget, aWindow) {
if (['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
throw new Error("sendMouseEvent doesn't know about event type '"+aEvent.type+"'");
}
if (!aWindow) {
aWindow = window;
}
// For events to trigger the UA's default actions they need to be "trusted"
netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserWrite');
var event = document.createEvent('MouseEvent');
var event = aWindow.document.createEvent('MouseEvent');
var typeArg = aEvent.type;
var canBubbleArg = true;
var cancelableArg = true;
var viewArg = window;
var viewArg = aWindow;
var detailArg = aEvent.detail || (aEvent.type == 'click' ||
aEvent.type == 'mousedown' ||
aEvent.type == 'mouseup' ? 1 : 0);
@ -48,7 +52,7 @@ function sendMouseEvent(aEvent, aTarget) {
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
buttonArg, relatedTargetArg);
document.getElementById(aTarget).dispatchEvent(event);
aWindow.document.getElementById(aTarget).dispatchEvent(event);
}
/**