Fixing bug 351370. Make javascript: URLs evaluate in a sandbox when principals don't match. r=mrbkap@gmail.com, sr=bzbarsky@mit.edu.

This commit is contained in:
jst%mozilla.jstenback.com 2006-09-23 17:06:58 +00:00
Родитель 03427afba9
Коммит 3961fcdb53
6 изменённых файлов: 326 добавлений и 115 удалений

Просмотреть файл

@ -62,6 +62,8 @@ REQUIRES = xpcom \
jsconsole \
uconv \
docshell \
content \
pref \
$(NULL)
XPIDLSRCS = \
@ -72,6 +74,10 @@ CPPSRCS = nsJSProtocolHandler.cpp
EXPORTS = $(srcdir)/nsJSProtocolHandler.h
LOCAL_INCLUDES += \
-I$(srcdir) \
-I$(srcdir)/../base \
EXTRA_DSO_LDOPTS = \
$(MOZ_COMPONENT_LIBS) \
$(MOZ_JS_LIBS) \

Просмотреть файл

@ -69,6 +69,9 @@
#include "nsIWebNavigation.h"
#include "nsIDocShell.h"
#include "nsIContentViewer.h"
#include "nsIXPConnect.h"
#include "nsContentUtils.h"
#include "nsJSUtils.h"
class nsJSThunk : public nsIInputStream
@ -194,6 +197,8 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel)
if (NS_FAILED(rv))
return rv;
PRBool useSandbox = PR_TRUE;
if (owner) {
principal = do_QueryInterface(owner, &rv);
NS_ASSERTION(principal, "Channel's owner is not a principal");
@ -215,49 +220,84 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel)
if (principal != systemPrincipal) {
rv = securityManager->CheckSameOriginPrincipal(principal,
objectPrincipal);
if (NS_FAILED(rv)) {
nsCOMPtr<nsIConsoleService> console =
do_GetService("@mozilla.org/consoleservice;1");
if (console) {
// XXX Localize me!
console->LogStringMessage(
NS_LITERAL_STRING("Attempt to load a javascript: URL from one host\nin a window displaying content from another host\nwas blocked by the security manager.").get());
}
return NS_ERROR_DOM_RETVAL_UNDEFINED;
if (NS_SUCCEEDED(rv)) {
useSandbox = PR_FALSE;
}
} else {
useSandbox = PR_FALSE;
}
}
else {
// No owner from channel, use the null principal for lack of anything
// better. Note that we do not use the object principal here because
// that would give the javascript: URL the principals of whatever page
// we might be remotely associated with, which is a good recipe for XSS
// issues.
principal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
if (NS_FAILED(rv) || !principal) {
return NS_ERROR_FAILURE;
}
}
nsString result;
PRBool isUndefined;
// Finally, we have everything needed to evaluate the expression.
nsString result;
PRBool bIsUndefined;
rv = scriptContext->EvaluateString(NS_ConvertUTF8toUTF16(script),
globalJSObject, // obj
principal,
url.get(), // url
1, // line no
nsnull,
&result,
&bIsUndefined);
if (useSandbox) {
// No owner from channel, or we have a principal
// mismatch. Evaluate the javascript URL in a sandbox to
// prevent it from accessing data it doesn't have permissions
// to access.
nsIXPConnect *xpc = nsContentUtils::XPConnect();
JSContext *cx = (JSContext*)scriptContext->GetNativeContext();
nsCOMPtr<nsIXPConnectJSObjectHolder> sandbox;
rv = xpc->CreateSandbox(cx, principal, getter_AddRefs(sandbox));
NS_ENSURE_SUCCESS(rv, rv);
jsval rval = JSVAL_VOID;
nsAutoGCRoot root(&rval, &rv);
if (NS_FAILED(rv)) {
return rv;
}
rv = xpc->EvalInSandboxObject(NS_ConvertUTF8toUTF16(script), cx,
sandbox, &rval);
// Propagate and report exceptions that happened in the
// sandbox.
if (JS_IsExceptionPending(cx)) {
JS_ReportPendingException(cx);
}
isUndefined = rval == JSVAL_VOID;
if (!isUndefined && NS_SUCCEEDED(rv)) {
JSAutoRequest ar(cx);
JSString *str = JS_ValueToString(cx, rval);
if (!str) {
// Report any pending exceptions.
if (JS_IsExceptionPending(cx)) {
JS_ReportPendingException(cx);
}
// We don't know why this failed, so just use a
// generic error code. It'll be translated to a
// different one below anyways.
rv = NS_ERROR_FAILURE;
} else {
result = nsDependentJSString(str);
}
}
} else {
// No need to use the sandbox, evaluate the script directly in
// the given scope.
rv = scriptContext->EvaluateString(NS_ConvertUTF8toUTF16(script),
globalJSObject, // obj
principal,
url.get(), // url
1, // line no
nsnull,
&result,
&isUndefined);
}
if (NS_FAILED(rv)) {
rv = NS_ERROR_MALFORMED_URI;
}
else if (bIsUndefined) {
else if (isUndefined) {
rv = NS_ERROR_DOM_RETVAL_UNDEFINED;
}
else {
@ -270,6 +310,7 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel)
else
rv = NS_ERROR_OUT_OF_MEMORY;
}
return rv;
}

Просмотреть файл

@ -147,12 +147,7 @@ interface nsIXPConnect;
interface nsIXPConnectWrappedNative;
interface nsIInterfaceInfo;
interface nsIXPCSecurityManager;
%{C++
#ifndef XPCONNECT_STANDALONE
class nsIPrincipal;
#endif
%}
interface nsIPrincipal;
/***************************************************************************/
[uuid(8916a320-d118-11d3-8f3a-0010a4e73d9a)]
@ -464,7 +459,7 @@ interface nsIXPCFunctionThisTranslator : nsISupports
{ 0xbd, 0xd6, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } }
%}
[uuid(038ce87f-c178-4d3a-9824-f71effc63aef)]
[uuid(0f2f96c2-0a66-4677-a3d8-ebb907e9b780)]
interface nsIXPConnect : nsISupports
{
%{ C++
@ -705,4 +700,32 @@ interface nsIXPConnect : nsISupports
in JSObjectPtr aScope,
in nsIClassInfo aClassInfo,
in nsIXPConnectJSObjectHolder aPrototype);
/**
* Create a sandbox for evaluating code in isolation using
* evalInSandboxObject().
*
* @param cx A context to use when creating the sandbox object.
* @param principal The principal (or NULL to use the null principal)
* to use when evaluating code in this sandbox.
*/
[noscript] nsIXPConnectJSObjectHolder createSandbox(in JSContextPtr cx,
in nsIPrincipal principal);
/**
* Evaluate script in a sandbox, completely isolated from all
* other running scripts.
*
* @param source The source of the script to evaluate.
* @param cx The context to use when setting up the evaluation of
* the script. The actual evaluation will happen on a new
* temporary context.
* @param sandbox The sandbox object to evaluate the script in.
* @return The result of the evaluation as a jsval. If the caller
* intends to use the return value from this call the caller
* is responsible for rooting the jsval before making a call
* to this method.
*/
[noscript] JSVal evalInSandboxObject(in AString source, in JSContextPtr cx,
in nsIXPConnectJSObjectHolder sandbox);
};

Просмотреть файл

@ -1182,6 +1182,57 @@ nsXPConnect::RestoreWrappedNativePrototype(JSContext * aJSContext,
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::CreateSandbox(JSContext *cx, nsIPrincipal *principal,
nsIXPConnectJSObjectHolder **_retval)
{
#ifdef XPCONNECT_STANDALONE
return NS_ERROR_NOT_AVAILABLE;
#else /* XPCONNECT_STANDALONE */
XPCCallContext ccx(NATIVE_CALLER, cx);
if(!ccx.IsValid())
return UnexpectedFailure(NS_ERROR_FAILURE);
*_retval = nsnull;
jsval rval = JSVAL_VOID;
AUTO_MARK_JSVAL(ccx, &rval);
nsresult rv = xpc_CreateSandboxObject(cx, &rval, principal);
NS_ASSERTION(NS_FAILED(rv) || !JSVAL_IS_PRIMITIVE(rval),
"Bad return value from xpc_CreateSandboxObject()!");
if (NS_SUCCEEDED(rv) && !JSVAL_IS_PRIMITIVE(rval)) {
*_retval = XPCJSObjectHolder::newHolder(cx, JSVAL_TO_OBJECT(rval));
NS_ENSURE_TRUE(*_retval, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(*_retval);
}
return rv;
#endif /* XPCONNECT_STANDALONE */
}
NS_IMETHODIMP
nsXPConnect::EvalInSandboxObject(const nsAString& source, JSContext *cx,
nsIXPConnectJSObjectHolder *sandbox,
jsval *rval)
{
#ifdef XPCONNECT_STANDALONE
return NS_ERROR_NOT_AVAILABLE;
#else /* XPCONNECT_STANDALONE */
if (!sandbox)
return NS_ERROR_INVALID_ARG;
JSObject *obj;
nsresult rv = sandbox->GetJSObject(&obj);
NS_ENSURE_SUCCESS(rv, rv);
return xpc_EvalInSandbox(cx, obj, source,
NS_ConvertUTF16toUTF8(source).get(), 1, rval);
#endif /* XPCONNECT_STANDALONE */
}
/* nsIXPConnectJSObjectHolder getWrappedNativePrototype (in JSContextPtr aJSContext, in JSObjectPtr aScope, in nsIClassInfo aClassInfo); */
NS_IMETHODIMP
nsXPConnect::GetWrappedNativePrototype(JSContext * aJSContext,

Просмотреть файл

@ -2666,6 +2666,9 @@ public:
virtual ~nsXPCComponents_utils_Sandbox();
private:
// XXXjst: This method (and other CallOrConstruct()'s in this
// file) doesn't need to be virtual, could even be a static
// method!
NS_METHOD CallOrConstruct(nsIXPConnectWrappedNative *wrapper,
JSContext * cx, JSObject * obj,
PRUint32 argc, jsval * argv,
@ -3145,6 +3148,77 @@ NS_IMPL_THREADSAFE_RELEASE(nsXPCComponents_utils_Sandbox)
#define XPC_MAP_FLAGS 0
#include "xpc_map_end.h" /* This #undef's the above. */
#ifndef XPCONNECT_STANDALONE
nsresult
xpc_CreateSandboxObject(JSContext * cx, jsval * vp, nsISupports *prinOrSop)
{
// Create the sandbox global object
nsresult rv;
nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
if(NS_FAILED(rv))
return NS_ERROR_XPC_UNEXPECTED;
XPCAutoJSContext tempcx(JS_NewContext(JS_GetRuntime(cx), 1024), PR_FALSE);
if (!tempcx)
return NS_ERROR_OUT_OF_MEMORY;
AutoJSRequestWithNoCallContext req(tempcx);
JSObject *sandbox = JS_NewObject(tempcx, &SandboxClass, nsnull, nsnull);
if (!sandbox)
return NS_ERROR_XPC_UNEXPECTED;
JS_SetGlobalObject(tempcx, sandbox);
nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(prinOrSop));
if (!sop) {
nsCOMPtr<nsIPrincipal> principal(do_QueryInterface(prinOrSop));
if (!principal) {
principal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
NS_ASSERTION(NS_FAILED(rv) || principal,
"Bad return from do_CreateInstance");
if (!principal || NS_FAILED(rv)) {
if (NS_SUCCEEDED(rv))
rv = NS_ERROR_FAILURE;
return rv;
}
}
sop = new PrincipalHolder(principal);
if (!sop)
return NS_ERROR_OUT_OF_MEMORY;
}
// Pass on ownership of sop to |sandbox|.
{
nsIScriptObjectPrincipal *tmp = sop;
if (!JS_SetPrivate(cx, sandbox, tmp)) {
return NS_ERROR_XPC_UNEXPECTED;
}
NS_ADDREF(tmp);
}
rv = xpc->InitClasses(cx, sandbox);
if (NS_SUCCEEDED(rv) &&
!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) {
rv = NS_ERROR_FAILURE;
}
if (NS_FAILED(rv))
return NS_ERROR_XPC_UNEXPECTED;
if (vp)
*vp = OBJECT_TO_JSVAL(sandbox);
return NS_OK;
}
#endif /* !XPCONNECT_STANDALONE */
/* PRBool call(in nsIXPConnectWrappedNative wrapper,
in JSContextPtr cx,
in JSObjectPtr obj,
@ -3191,29 +3265,16 @@ nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative *wrappe
{
#ifdef XPCONNECT_STANDALONE
return NS_ERROR_NOT_AVAILABLE;
#else
#else /* XPCONNECT_STANDALONE */
if (argc < 1)
return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval);
// Create the sandbox global object
nsresult rv;
nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
if(NS_FAILED(rv))
return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
XPCAutoJSContext tempcx(JS_NewContext(JS_GetRuntime(cx), 1024), PR_FALSE);
if (!tempcx)
return ThrowAndFail(NS_ERROR_OUT_OF_MEMORY, cx, _retval);
AutoJSRequestWithNoCallContext req(tempcx);
JSObject *sandbox = JS_NewObject(tempcx, &SandboxClass, nsnull, nsnull);
if (!sandbox)
return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
JS_SetGlobalObject(tempcx, sandbox);
// Make sure to set up principals on the sandbox before initing classes
nsIScriptObjectPrincipal *sop = nsnull;
nsCOMPtr<nsIScriptObjectPrincipal> sop;
nsCOMPtr<nsIPrincipal> principal;
nsISupports *prinOrSop = nsnull;
if (JSVAL_IS_STRING(argv[0])) {
JSString *codebasestr = JSVAL_TO_STRING(argv[0]);
nsCAutoString codebase(JS_GetStringBytes(codebasestr),
@ -3224,13 +3285,12 @@ nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative *wrappe
if (!stdUrl ||
NS_FAILED(rv = stdUrl->Init(nsIStandardURL::URLTYPE_STANDARD, 80,
codebase, nsnull, nsnull)) ||
!(iURL = do_QueryInterface(stdUrl, &rv))) {
!(iURL = do_QueryInterface(stdUrl, &rv))) {
if (NS_SUCCEEDED(rv))
rv = NS_ERROR_FAILURE;
return ThrowAndFail(rv, cx, _retval);
}
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIScriptSecurityManager> secman =
do_GetService(kScriptSecurityManagerContractID);
if (!secman ||
@ -3241,52 +3301,37 @@ nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative *wrappe
return ThrowAndFail(rv, cx, _retval);
}
sop = new PrincipalHolder(principal);
if (!sop)
return ThrowAndFail(NS_ERROR_OUT_OF_MEMORY, cx, _retval);
NS_ADDREF(sop);
prinOrSop = principal;
} else {
if (!JSVAL_IS_PRIMITIVE(argv[0])) {
nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID()));
if(!xpc)
return NS_ERROR_XPC_UNEXPECTED;
nsCOMPtr<nsIXPConnectWrappedNative> wrapper;
xpc->GetWrappedNativeOfJSObject(cx, JSVAL_TO_OBJECT(argv[0]),
getter_AddRefs(wrapper));
if (wrapper) {
nsCOMPtr<nsIDOMWindow> win = do_QueryWrappedNative(wrapper);
if (win)
CallQueryInterface(win, &sop);
sop = do_QueryWrappedNative(wrapper);
prinOrSop = sop;
}
}
if (!sop)
if (!prinOrSop)
return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
// Note: if we're here, then sop has been AddRef'd by CallQueryInterface
}
if (!JS_SetPrivate(cx, sandbox, sop)) {
NS_RELEASE(sop);
return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
rv = xpc_CreateSandboxObject(cx, vp, prinOrSop);
if (NS_FAILED(rv)) {
return ThrowAndFail(rv, cx, _retval);
}
// After this point |sop| will be released when |sandbox| is
// finalized, so no need to worry about it from now on.
*_retval = PR_TRUE;
rv = xpc->InitClasses(cx, sandbox);
if (NS_SUCCEEDED(rv) &&
!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) {
rv = NS_ERROR_FAILURE;
}
if (NS_FAILED(rv))
return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval);
if (vp)
*vp = OBJECT_TO_JSVAL(sandbox);
*_retval = JS_TRUE;
return NS_OK;
#endif
return rv;
#endif /* XPCONNECT_STANDALONE */
}
class ContextHolder : public nsISupports
@ -3327,7 +3372,7 @@ nsXPCComponents_Utils::EvalInSandbox(const nsAString &source)
{
#ifdef XPCONNECT_STANDALONE
return NS_ERROR_NOT_AVAILABLE;
#else
#else /* XPCONNECT_STANDALONE */
nsresult rv;
nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
@ -3369,6 +3414,38 @@ nsXPCComponents_Utils::EvalInSandbox(const nsAString &source)
if (JSVAL_IS_PRIMITIVE(argv[1]))
return NS_ERROR_INVALID_ARG;
JSObject *sandbox = JSVAL_TO_OBJECT(argv[1]);
// Get the current source info from xpc.
nsXPIDLCString filename;
PRInt32 lineNo = 0;
{
nsCOMPtr<nsIStackFrame> frame;
xpc->GetCurrentJSStack(getter_AddRefs(frame));
if (frame) {
frame->GetFilename(getter_Copies(filename));
frame->GetLineNumber(&lineNo);
}
}
rv = xpc_EvalInSandbox(cx, sandbox, source, filename.get(), lineNo, rval);
if (NS_SUCCEEDED(rv)) {
if (JS_IsExceptionPending(cx)) {
cc->SetExceptionWasThrown(PR_TRUE);
} else {
cc->SetReturnValueWasSet(PR_TRUE);
}
}
return rv;
#endif /* XPCONNECT_STANDALONE */
}
#ifndef XPCONNECT_STANDALONE
nsresult
xpc_EvalInSandbox(JSContext *cx, JSObject *sandbox, const nsAString& source,
const char *filename, PRInt32 lineNo, jsval *rval)
{
if (JS_GetClass(cx, sandbox) != &SandboxClass)
return NS_ERROR_INVALID_ARG;
@ -3393,8 +3470,7 @@ nsXPCComponents_Utils::EvalInSandbox(const nsAString &source)
}
XPCPerThreadData *data = XPCPerThreadData::GetData();
XPCJSContextStack *stack;
PRBool popContext = PR_FALSE;
XPCJSContextStack *stack = nsnull;
if (data && (stack = data->GetJSContextStack())) {
if (NS_FAILED(stack->Push(sandcx->GetJSContext()))) {
JS_ReportError(cx,
@ -3402,55 +3478,44 @@ nsXPCComponents_Utils::EvalInSandbox(const nsAString &source)
JSPRINCIPALS_DROP(cx, jsPrincipals);
return NS_ERROR_FAILURE;
}
popContext = PR_TRUE;
}
// Get the current source info from xpc. Use the codebase as a fallback,
// though.
nsXPIDLCString filename;
PRInt32 lineNo = 0;
{
nsCOMPtr<nsIStackFrame> frame;
xpc->GetCurrentJSStack(getter_AddRefs(frame));
if (frame) {
frame->GetFilename(getter_Copies(filename));
frame->GetLineNumber(&lineNo);
} else {
filename.Assign(jsPrincipals->codebase);
lineNo = 1;
}
if (!filename) {
// Default the filename to the codebase.
filename = jsPrincipals->codebase;
lineNo = 1;
}
nsresult rv = NS_OK;
AutoJSRequestWithNoCallContext req(sandcx->GetJSContext());
if (!JS_EvaluateUCScriptForPrincipals(sandcx->GetJSContext(), sandbox,
jsPrincipals,
NS_REINTERPRET_CAST(const jschar *,
PromiseFlatString(source).get()),
source.Length(), filename.get(),
lineNo, rval)) {
source.Length(), filename, lineNo,
rval)) {
jsval exn;
if (JS_GetPendingException(sandcx->GetJSContext(), &exn)) {
AutoJSSuspendRequestWithNoCallContext sus(sandcx->GetJSContext());
AutoJSRequestWithNoCallContext cxreq(cx);
JS_SetPendingException(cx, exn);
cc->SetExceptionWasThrown(PR_TRUE);
} else {
rv = NS_ERROR_OUT_OF_MEMORY;
}
} else {
cc->SetReturnValueWasSet(PR_TRUE);
}
if (popContext) {
if (stack) {
stack->Pop(nsnull);
}
JSPRINCIPALS_DROP(cx, jsPrincipals);
return rv;
#endif /* !XPCONNECT_STANDALONE */
}
#endif /* !XPCONNECT_STANDALONE */
#ifdef XPC_USE_SECURITY_CHECKED_COMPONENT
/* string canCreateWrapper (in nsIIDPtr iid); */

Просмотреть файл

@ -3615,6 +3615,31 @@ JSBool xpc_IsReportableErrorCode(nsresult code);
JSObject* xpc_CloneJSFunction(XPCCallContext &ccx, JSObject *funobj,
JSObject *parent);
#ifndef XPCONNECT_STANDALONE
// Helper for creating a sandbox object to use for evaluating
// untrusted code completely separated from all other code in the
// system using xpc_EvalInSandbox(). Takes the JSContext on which to
// do setup etc on, puts the sandbox object in *vp (which must be
// rooted by the caller), and uses the principal that's either
// directly passed in prinOrSop or indirectly as an
// nsIScriptObjectPrincipal holding the principal. If no principal is
// reachable through prinOrSop, a new null principal will be created
// and used.
nsresult
xpc_CreateSandboxObject(JSContext * cx, jsval * vp, nsISupports *prinOrSop);
// Helper for evaluating scripts in a sandbox object created with
// xpc_CreateSandboxObject(). The caller is responsible of ensuring
// that *rval doesn't get collected during the call or usage after the
// call. This helper will use filename and lineNo for error reporting,
// and if no filename is provided it will use the codebase from the
// principal and line number 1 as a fallback.
nsresult
xpc_EvalInSandbox(JSContext *cx, JSObject *sandbox, const nsAString& source,
const char *filename, PRInt32 lineNo, jsval *rval);
#endif /* !XPCONNECT_STANDALONE */
/***************************************************************************/
// Inlined utilities.