Bug 568698 - Instead of fixing two globals in the jetpack process, allow jetpack to create sandboxes in which to load user code and implementation modules, r=bent

This commit is contained in:
Benjamin Smedberg
2010-07-08 09:40:07 -07:00
parent 122361efed
commit 174fb9fbc8
9 changed files with 172 additions and 143 deletions

View File

@@ -71,7 +71,7 @@ public:
> MapType; > MapType;
OpaqueSeenType() { OpaqueSeenType() {
NS_ASSERTION(map.init(1), "Failed to initialize map"); (void) map.init(1);
} }
bool ok() { return map.initialized(); } bool ok() { return map.initialized(); }
@@ -452,24 +452,18 @@ JetpackActorCommon::RecvMessage(JSContext* cx,
JSObject* implGlobal = JS_GetGlobalObject(cx); JSObject* implGlobal = JS_GetGlobalObject(cx);
js::AutoValueRooter rval(cx); js::AutoValueRooter rval(cx);
const uint32 savedOptions =
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_DONT_REPORT_UNCAUGHT);
for (PRUint32 i = 0; i < snapshot.Length(); ++i) { for (PRUint32 i = 0; i < snapshot.Length(); ++i) {
Variant* vp = results ? results->AppendElement() : NULL; Variant* vp = results ? results->AppendElement() : NULL;
rval.set(JSVAL_VOID); rval.set(JSVAL_VOID);
if (!JS_CallFunctionValue(cx, implGlobal, snapshot[i], argc, argv, if (!JS_CallFunctionValue(cx, implGlobal, snapshot[i], argc, argv,
rval.addr())) { rval.addr())) {
// If a receiver throws, we drop the exception on the floor. (void) JS_ReportPendingException(cx);
JS_ClearPendingException(cx);
if (vp) if (vp)
*vp = void_t(); *vp = void_t();
} else if (vp && !jsval_to_Variant(cx, rval.value(), vp)) } else if (vp && !jsval_to_Variant(cx, rval.value(), vp))
*vp = void_t(); *vp = void_t();
} }
JS_SetOptions(cx, savedOptions);
return true; return true;
} }

View File

@@ -44,6 +44,8 @@
#include "jsarray.h" #include "jsarray.h"
#include <stdio.h>
namespace mozilla { namespace mozilla {
namespace jetpack { namespace jetpack {
@@ -55,18 +57,6 @@ JetpackChild::~JetpackChild()
{ {
} }
#define IMPL_PROP_FLAGS (JSPROP_SHARED | \
JSPROP_ENUMERATE | \
JSPROP_READONLY | \
JSPROP_PERMANENT)
const JSPropertySpec
JetpackChild::sImplProperties[] = {
{ "jetpack", 0, IMPL_PROP_FLAGS, UserJetpackGetter, NULL },
{ 0, 0, 0, NULL, NULL }
};
#undef IMPL_PROP_FLAGS
#define IMPL_METHOD_FLAGS (JSFUN_FAST_NATIVE | \ #define IMPL_METHOD_FLAGS (JSFUN_FAST_NATIVE | \
JSPROP_ENUMERATE | \ JSPROP_ENUMERATE | \
JSPROP_READONLY | \ JSPROP_READONLY | \
@@ -80,6 +70,8 @@ JetpackChild::sImplMethods[] = {
JS_FN("unregisterReceivers", UnregisterReceivers, 1, IMPL_METHOD_FLAGS), JS_FN("unregisterReceivers", UnregisterReceivers, 1, IMPL_METHOD_FLAGS),
JS_FN("wrap", Wrap, 1, IMPL_METHOD_FLAGS), JS_FN("wrap", Wrap, 1, IMPL_METHOD_FLAGS),
JS_FN("createHandle", CreateHandle, 0, IMPL_METHOD_FLAGS), JS_FN("createHandle", CreateHandle, 0, IMPL_METHOD_FLAGS),
JS_FN("createSandbox", CreateSandbox, 0, IMPL_METHOD_FLAGS),
JS_FN("evalInSandbox", EvalInSandbox, 2, IMPL_METHOD_FLAGS),
JS_FS_END JS_FS_END
}; };
@@ -92,7 +84,21 @@ JetpackChild::sGlobalClass = {
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS JSCLASS_NO_OPTIONAL_MEMBERS
}; };
static void
ReportJetpackErrors(JSContext* cx, const char* message, JSErrorReport* report)
{
const char* filename = "<unknown>";
if (report && report->filename)
filename = report->filename;
int lineno = -1;
if (report)
lineno = report->lineno;
fprintf(stderr, "Jetpack JavaScript Error: %s:%i, %s\n",
filename, lineno, message);
}
bool bool
JetpackChild::Init(base::ProcessHandle aParentProcessHandle, JetpackChild::Init(base::ProcessHandle aParentProcessHandle,
MessageLoop* aIOLoop, MessageLoop* aIOLoop,
@@ -102,31 +108,20 @@ JetpackChild::Init(base::ProcessHandle aParentProcessHandle,
return false; return false;
if (!(mRuntime = JS_NewRuntime(32L * 1024L * 1024L)) || if (!(mRuntime = JS_NewRuntime(32L * 1024L * 1024L)) ||
!(mImplCx = JS_NewContext(mRuntime, 8192)) || !(mCx = JS_NewContext(mRuntime, 8192)))
!(mUserCx = JS_NewContext(mRuntime, 8192)))
return false; return false;
{ JS_SetErrorReporter(mCx, ReportJetpackErrors);
JSAutoRequest request(mImplCx);
JS_SetContextPrivate(mImplCx, this);
JSObject* implGlobal =
JS_NewGlobalObject(mImplCx, const_cast<JSClass*>(&sGlobalClass));
if (!implGlobal ||
!JS_InitStandardClasses(mImplCx, implGlobal) ||
!JS_DefineProperties(mImplCx, implGlobal,
const_cast<JSPropertySpec*>(sImplProperties)) ||
!JS_DefineFunctions(mImplCx, implGlobal,
const_cast<JSFunctionSpec*>(sImplMethods)))
return false;
}
{ {
JSAutoRequest request(mUserCx); JSAutoRequest request(mCx);
JS_SetContextPrivate(mUserCx, this); JS_SetContextPrivate(mCx, this);
JSObject* userGlobal = JSObject* implGlobal =
JS_NewGlobalObject(mUserCx, const_cast<JSClass*>(&sGlobalClass)); JS_NewGlobalObject(mCx, const_cast<JSClass*>(&sGlobalClass));
if (!userGlobal || if (!implGlobal ||
!JS_InitStandardClasses(mUserCx, userGlobal)) !JS_InitStandardClasses(mCx, implGlobal) ||
!JS_DefineFunctions(mCx, implGlobal,
const_cast<JSFunctionSpec*>(sImplMethods)))
return false; return false;
} }
@@ -137,8 +132,7 @@ void
JetpackChild::CleanUp() JetpackChild::CleanUp()
{ {
ClearReceivers(); ClearReceivers();
JS_DestroyContext(mUserCx); JS_DestroyContext(mCx);
JS_DestroyContext(mImplCx);
JS_DestroyRuntime(mRuntime); JS_DestroyRuntime(mRuntime);
JS_ShutDown(); JS_ShutDown();
} }
@@ -153,32 +147,21 @@ bool
JetpackChild::RecvSendMessage(const nsString& messageName, JetpackChild::RecvSendMessage(const nsString& messageName,
const nsTArray<Variant>& data) const nsTArray<Variant>& data)
{ {
JSAutoRequest request(mImplCx); JSAutoRequest request(mCx);
return JetpackActorCommon::RecvMessage(mImplCx, messageName, data, NULL); return JetpackActorCommon::RecvMessage(mCx, messageName, data, NULL);
} }
static bool bool
Evaluate(JSContext* cx, const nsCString& code) JetpackChild::RecvEvalScript(const nsString& code)
{ {
JSAutoRequest request(cx); JSAutoRequest request(mCx);
js::AutoValueRooter ignored(cx);
JS_EvaluateScript(cx, JS_GetGlobalObject(cx), code.get(), js::AutoValueRooter ignored(mCx);
code.Length(), "", 1, ignored.addr()); (void) JS_EvaluateUCScript(mCx, JS_GetGlobalObject(mCx), code.get(),
code.Length(), "", 1, ignored.addr());
return true; return true;
} }
bool
JetpackChild::RecvLoadImplementation(const nsCString& code)
{
return Evaluate(mImplCx, code);
}
bool
JetpackChild::RecvLoadUserScript(const nsCString& code)
{
return Evaluate(mUserCx, code);
}
PHandleChild* PHandleChild*
JetpackChild::AllocPHandle() JetpackChild::AllocPHandle()
{ {
@@ -197,20 +180,10 @@ JetpackChild::GetThis(JSContext* cx)
{ {
JetpackChild* self = JetpackChild* self =
static_cast<JetpackChild*>(JS_GetContextPrivate(cx)); static_cast<JetpackChild*>(JS_GetContextPrivate(cx));
JS_ASSERT(cx == self->mImplCx || JS_ASSERT(cx == self->mCx);
cx == self->mUserCx);
return self; return self;
} }
JSBool
JetpackChild::UserJetpackGetter(JSContext* cx, JSObject* obj, jsval id,
jsval* vp)
{
JSObject* userGlobal = JS_GetGlobalObject(GetThis(cx)->mUserCx);
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(userGlobal));
return JS_TRUE;
}
struct MessageResult { struct MessageResult {
nsString msgName; nsString msgName;
nsTArray<Variant> data; nsTArray<Variant> data;
@@ -428,5 +401,49 @@ JetpackChild::CreateHandle(JSContext* cx, uintN argc, jsval* vp)
return JS_TRUE; return JS_TRUE;
} }
JSBool
JetpackChild::CreateSandbox(JSContext* cx, uintN argc, jsval* vp)
{
if (argc > 0) {
JS_ReportError(cx, "createSandbox takes zero arguments");
return JS_FALSE;
}
JSObject* obj = JS_NewGlobalObject(cx, const_cast<JSClass*>(&sGlobalClass));
if (!obj)
return JS_FALSE;
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
return JS_InitStandardClasses(cx, obj);
}
JSBool
JetpackChild::EvalInSandbox(JSContext* cx, uintN argc, jsval* vp)
{
if (argc != 2) {
JS_ReportError(cx, "evalInSandbox takes two arguments");
return JS_FALSE;
}
jsval* argv = JS_ARGV(cx, vp);
JSObject* obj;
if (!JSVAL_IS_OBJECT(argv[0]) ||
!(obj = JSVAL_TO_OBJECT(argv[0])) ||
&sGlobalClass != JS_GetClass(cx, obj) ||
obj == JS_GetGlobalObject(cx)) {
JS_ReportError(cx, "The first argument to evalInSandbox must be a global object created using createSandbox.");
return JS_FALSE;
}
JSString* str = JS_ValueToString(cx, argv[1]);
if (!str)
return JS_FALSE;
js::AutoValueRooter ignored(cx);
return JS_EvaluateUCScript(cx, obj, JS_GetStringChars(str), JS_GetStringLength(str), "", 1,
ignored.addr());
}
} // namespace jetpack } // namespace jetpack
} // namespace mozilla } // namespace mozilla

View File

@@ -69,22 +69,17 @@ protected:
NS_OVERRIDE virtual bool RecvSendMessage(const nsString& messageName, NS_OVERRIDE virtual bool RecvSendMessage(const nsString& messageName,
const nsTArray<Variant>& data); const nsTArray<Variant>& data);
NS_OVERRIDE virtual bool RecvLoadImplementation(const nsCString& code); NS_OVERRIDE virtual bool RecvEvalScript(const nsString& script);
NS_OVERRIDE virtual bool RecvLoadUserScript(const nsCString& code);
NS_OVERRIDE virtual PHandleChild* AllocPHandle(); NS_OVERRIDE virtual PHandleChild* AllocPHandle();
NS_OVERRIDE virtual bool DeallocPHandle(PHandleChild* actor); NS_OVERRIDE virtual bool DeallocPHandle(PHandleChild* actor);
private: private:
JSRuntime* mRuntime; JSRuntime* mRuntime;
JSContext *mImplCx, *mUserCx; JSContext *mCx;
static JetpackChild* GetThis(JSContext* cx); static JetpackChild* GetThis(JSContext* cx);
static const JSPropertySpec sImplProperties[];
static JSBool UserJetpackGetter(JSContext* cx, JSObject* obj, jsval idval,
jsval* vp);
static const JSFunctionSpec sImplMethods[]; static const JSFunctionSpec sImplMethods[];
static JSBool SendMessage(JSContext* cx, uintN argc, jsval *vp); static JSBool SendMessage(JSContext* cx, uintN argc, jsval *vp);
static JSBool CallMessage(JSContext* cx, uintN argc, jsval *vp); static JSBool CallMessage(JSContext* cx, uintN argc, jsval *vp);
@@ -93,6 +88,8 @@ private:
static JSBool UnregisterReceivers(JSContext* cx, uintN argc, jsval *vp); static JSBool UnregisterReceivers(JSContext* cx, uintN argc, jsval *vp);
static JSBool Wrap(JSContext* cx, uintN argc, jsval *vp); static JSBool Wrap(JSContext* cx, uintN argc, jsval *vp);
static JSBool CreateHandle(JSContext* cx, uintN argc, jsval *vp); static JSBool CreateHandle(JSContext* cx, uintN argc, jsval *vp);
static JSBool CreateSandbox(JSContext* cx, uintN argc, jsval *vp);
static JSBool EvalInSandbox(JSContext* cx, uintN argc, jsval *vp);
static const JSClass sGlobalClass; static const JSClass sGlobalClass;

View File

@@ -63,38 +63,6 @@ JetpackParent::~JetpackParent()
NS_IMPL_ISUPPORTS1(JetpackParent, nsIJetpack) NS_IMPL_ISUPPORTS1(JetpackParent, nsIJetpack)
static nsresult
ReadFromURI(const nsAString& aURI,
nsCString* content)
{
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri),
NS_ConvertUTF16toUTF8(aURI));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIChannel> channel;
NS_NewChannel(getter_AddRefs(channel), uri);
NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
nsCOMPtr<nsIInputStream> input;
rv = channel->Open(getter_AddRefs(input));
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(input, "Channel opened successfully but stream was null?");
char buffer[256];
PRUint32 avail = 0;
input->Available(&avail);
if (avail) {
PRUint32 read = 0;
while (NS_SUCCEEDED(input->Read(buffer, sizeof(buffer), &read)) && read) {
content->Append(buffer, read);
read = 0;
}
}
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
JetpackParent::SendMessage(const nsAString& aMessageName) JetpackParent::SendMessage(const nsAString& aMessageName)
{ {
@@ -159,31 +127,12 @@ JetpackParent::UnregisterReceivers(const nsAString& aMessageName)
} }
NS_IMETHODIMP NS_IMETHODIMP
JetpackParent::LoadImplementation(const nsAString& aURI) JetpackParent::EvalScript(const nsAString& aScript)
{ {
nsCString code; if (!SendEvalScript(nsString(aScript)))
nsresult rv = ReadFromURI(aURI, &code); return NS_ERROR_FAILURE;
NS_ENSURE_SUCCESS(rv, rv);
if (!code.IsEmpty() && return NS_OK;
!SendLoadImplementation(code))
rv = NS_ERROR_FAILURE;
return rv;
}
NS_IMETHODIMP
JetpackParent::LoadUserScript(const nsAString& aURI)
{
nsCString code;
nsresult rv = ReadFromURI(aURI, &code);
NS_ENSURE_SUCCESS(rv, rv);
if (!code.IsEmpty() &&
!SendLoadUserScript(code))
rv = NS_ERROR_FAILURE;
return rv;
} }
bool bool

View File

@@ -78,8 +78,7 @@ both:
async PHandle(); async PHandle();
child: child:
async LoadImplementation(nsCString code); async EvalScript(nsString code);
async LoadUserScript(nsCString code);
parent: parent:
sync CallMessage(nsString messageName, sync CallMessage(nsString messageName,

View File

@@ -53,8 +53,7 @@ interface nsIJetpack : nsISupports
in jsval aReceiver); in jsval aReceiver);
void unregisterReceivers(in AString aMessageName); void unregisterReceivers(in AString aMessageName);
void loadImplementation(in AString aURI); void evalScript(in AString aScript);
void loadUserScript(in AString aURI);
nsIVariant createHandle(); nsIVariant createHandle();

View File

@@ -41,3 +41,43 @@ registerReceiver("drop methods", echo);
registerReceiver("exception coping", echo); registerReceiver("exception coping", echo);
registerReceiver("duplicate receivers", echo); registerReceiver("duplicate receivers", echo);
function ok(c, msg)
{
sendMessage("test result", c, msg);
}
registerReceiver("test sandbox", function() {
var addon = createSandbox();
ok(typeof(addon) == "object", "typeof(addon)");
ok("Date" in addon, "addon.Date exists");
ok(addon.Date !== Date, "Date objects are different");
var fn = "var x; var c = 3; function doit() { x = 12; return 4; }";
evalInSandbox(addon, fn);
ok(addon.x === undefined, "x is undefined");
ok(addon.c == 3, "c is 3");
ok(addon.doit() == 4, "doit called successfully");
ok(addon.x == 12, "x is now 12");
var fn2 = "let function barbar{}";
try {
evalInSandbox(addon, fn2);
ok(false, "bad syntax should throw");
}
catch(e) {
ok(true, "bad syntax should throw");
}
var fn3 = "throw new Error('just kidding')";
try {
evalInSandbox(addon, fn3);
ok(false, "thrown error should be caught");
}
catch(e) {
ok(true, "thrown error should be caught");
}
sendMessage("sandbox done");
});

View File

@@ -1,3 +1,6 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
var jps = Components.classes["@mozilla.org/jetpack/service;1"] var jps = Components.classes["@mozilla.org/jetpack/service;1"]
.getService(Components.interfaces.nsIJetpackService); .getService(Components.interfaces.nsIJetpackService);
var jetpack = null; var jetpack = null;
@@ -7,11 +10,32 @@ function createHandle() {
return jetpack.createHandle(); return jetpack.createHandle();
} }
const PR_RDONLY = 0x1;
function read_file(f)
{
var fis = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
fis.init(f, PR_RDONLY, 0444, Ci.nsIFileInputStream.CLOSE_ON_EOF);
var lis = Cc["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Ci.nsIConverterInputStream);
lis.init(fis, "UTF-8", 1024, 0);
var data = "";
var r = {};
while (lis.readString(0x0FFFFFFF, r))
data += r.value;
return data;
}
function run_test() { function run_test() {
jetpack = jps.createJetpack(); jetpack = jps.createJetpack();
run_handle_tests(); run_handle_tests();
jetpack.loadImplementation("file://" + do_get_file("impl.js").path); jetpack.evalScript(read_file(do_get_file("impl.js")));
var circ1 = {}, var circ1 = {},
circ2 = {}, circ2 = {},
@@ -162,6 +186,13 @@ function run_test() {
jetpack.registerReceiver("duplicate receivers", jetpack.registerReceiver("duplicate receivers",
function() { do_test_finished() }); function() { do_test_finished() });
jetpack.registerReceiver("test result", function(name, c, msg) {
dump("TEST-INFO | test_jetpack.js | remote check '" + msg + "' result: " + c + "\n");
do_check_true(c);
});
jetpack.registerReceiver("sandbox done", do_test_finished);
do_test_pending();
do_test_pending(); do_test_pending();
do_test_pending(); do_test_pending();
do_test_pending(); do_test_pending();
@@ -183,8 +214,11 @@ function run_test() {
undefined, null, true, false, 1, 2, 999, 1/4, "oyez"); undefined, null, true, false, 1, 2, 999, 1/4, "oyez");
jetpack.sendMessage("drop methods", drop); jetpack.sendMessage("drop methods", drop);
jetpack.sendMessage("exception coping"); jetpack.sendMessage("exception coping");
jetpack.sendMessage("duplicate receivers"); jetpack.sendMessage("duplicate receivers");
jetpack.sendMessage("test sandbox");
do_register_cleanup(function() { do_register_cleanup(function() {
jetpack.destroy(); jetpack.destroy();
}); });

View File

@@ -175,7 +175,7 @@ class XPCShellTests(object):
'-e', 'print("To start the test, type |_execute_test();|.");', '-e', 'print("To start the test, type |_execute_test();|.");',
'-i'] '-i']
else: else:
self.xpcsRunArgs = ['-e', '_execute_test();'] self.xpcsRunArgs = ['-e', '_execute_test(); quit(0);']
def getPipes(self): def getPipes(self):
""" """