Bug 969273: Implement a toy principal type for the JS shell, for testing. r=mrbkap r=waldo

This commit is contained in:
Jim Blandy 2014-03-07 11:50:41 -08:00
parent 582f932872
commit 304f606645
2 changed files with 140 additions and 11 deletions

View File

@ -0,0 +1,52 @@
// Test the JS shell's toy principals.
var count = 0;
// Given a string of letters |expected|, say "abc", assert that the stack
// contains calls to a series of functions named by the next letter from
// the string, say a, b, and then c. Younger frames appear earlier in
// |expected| than older frames.
function check(expected, stack) {
print("check(" + uneval(expected) + ") against:\n" + stack);
count++;
// Extract only the function names from the stack trace. Omit the frames
// for the top-level evaluation, if it is present.
var split = stack.split(/(.)?@.*\n/).slice(0, -1);
if (split[split.length - 1] === undefined)
split = split.slice(0, -2);
// Check the function names against the expected sequence.
assertEq(split.length, expected.length * 2);
for (var i = 0; i < expected.length; i++)
assertEq(split[i * 2 + 1], expected[i]);
}
var low = newGlobal({ principal: 0 });
var mid = newGlobal({ principal: 0xffff });
var high = newGlobal({ principal: 0xfffff });
eval('function a() { check("a", Error().stack); b(); }');
low .eval('function b() { check("b", Error().stack); c(); }');
mid .eval('function c() { check("cba", Error().stack); d(); }');
high.eval('function d() { check("dcba", Error().stack); e(); }');
eval('function e() { check("edcba", Error().stack); f(); }'); // no principal, so checks skipped
low .eval('function f() { check("fb", Error().stack); g(); }');
mid .eval('function g() { check("gfecba", Error().stack); h(); }');
high.eval('function h() { check("hgfedcba", Error().stack); }');
// Make everyone's functions visible to each other, as needed.
b = low .b;
low .c = mid .c;
mid .d = high.d;
high.e = e;
f = low .f;
low .g = mid .g;
mid .h = high.h;
low.check = mid.check = high.check = check;
// Kick the whole process off.
a();
assertEq(count, 8);

View File

@ -190,11 +190,65 @@ static void
DestroyContext(JSContext *cx, bool withGC);
static JSObject *
NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options);
NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options,
JSPrincipals *principals);
static const JSErrorFormatString *
my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber);
/*
* A toy principals type for the shell.
*
* In the shell, a principal is simply a 32-bit mask: P subsumes Q if the
* set bits in P are a superset of those in Q. Thus, the principal 0 is
* subsumed by everything, and the principal ~0 subsumes everything.
*
* As a special case, a null pointer as a principal is treated like 0xffff.
*
* The 'newGlobal' function takes an option indicating which principal the
* new global should have; 'evaluate' does for the new code.
*/
class ShellPrincipals: public JSPrincipals {
uint32_t bits;
static uint32_t getBits(JSPrincipals *p) {
if (!p)
return 0xffff;
return static_cast<ShellPrincipals *>(p)->bits;
}
public:
ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) {
this->refcount = refcount;
}
static void destroy(JSPrincipals *principals) {
MOZ_ASSERT(principals != &fullyTrusted);
MOZ_ASSERT(principals->refcount == 0);
js_free(static_cast<ShellPrincipals *>(principals));
}
static bool subsumes(JSPrincipals *first, JSPrincipals *second) {
uint32_t firstBits = getBits(first);
uint32_t secondBits = getBits(second);
return (firstBits | secondBits) == firstBits;
}
static JSSecurityCallbacks securityCallbacks;
// Fully-trusted principals singleton.
static ShellPrincipals fullyTrusted;
};
JSSecurityCallbacks ShellPrincipals::securityCallbacks = {
nullptr, // contentSecurityPolicyAllows
subsumes
};
// The fully-trusted principal subsumes all other principals.
ShellPrincipals ShellPrincipals::fullyTrusted(-1, 1);
#ifdef EDITLINE
extern "C" {
extern JS_EXPORT_API(char *) readline(const char *prompt);
@ -2829,7 +2883,7 @@ WorkerMain(void *arg)
JS::CompartmentOptions compartmentOptions;
compartmentOptions.setVersion(JSVERSION_LATEST);
RootedObject global(cx, NewGlobalObject(cx, compartmentOptions));
RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr));
if (!global)
break;
@ -4104,6 +4158,7 @@ WrapWithProto(JSContext *cx, unsigned argc, jsval *vp)
static bool
NewGlobal(JSContext *cx, unsigned argc, jsval *vp)
{
JSPrincipals *principals = nullptr;
JS::CompartmentOptions options;
options.setVersion(JSVERSION_LATEST);
@ -4121,9 +4176,23 @@ NewGlobal(JSContext *cx, unsigned argc, jsval *vp)
return false;
if (v.isBoolean())
options.setInvisibleToDebugger(v.toBoolean());
if (!JS_GetProperty(cx, opts, "principal", &v))
return false;
if (!v.isUndefined()) {
uint32_t bits;
if (!ToUint32(cx, v, &bits))
return false;
principals = cx->new_<ShellPrincipals>(bits);
if (!principals)
return false;
JS_HoldPrincipals(principals);
}
}
RootedObject global(cx, NewGlobalObject(cx, options));
RootedObject global(cx, NewGlobalObject(cx, options, principals));
if (principals)
JS_DropPrincipals(cx->runtime(), principals);
if (!global)
return false;
@ -4601,7 +4670,15 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
" Return a new global object in a new compartment. If options\n"
" is given, it may have any of the following properties:\n"
" sameZoneAs: the compartment will be in the same zone as the given object (defaults to a new zone)\n"
" invisibleToDebugger: the global will be invisible to the debugger (default false)"),
" invisibleToDebugger: the global will be invisible to the debugger (default false)\n"
" principal: if present, its value converted to a number must be an\n"
" integer that fits in 32 bits; use that as the new compartment's\n"
" principal. Shell principals are toys, meant only for testing; one\n"
" shell principal subsumes another if its set bits are a superset of\n"
" the other's. Thus, a principal of 0 subsumes nothing, while a\n"
" principals of ~0 subsumes all other principals. The absence of a\n"
" principal is treated as if its bits were 0xffff, for subsumption\n"
" purposes. If this property is omitted, supply no principal."),
JS_FN_HELP("enableStackWalkingAssertion", EnableStackWalkingAssertion, 1, 0,
"enableStackWalkingAssertion(enabled)",
@ -5520,9 +5597,10 @@ DestroyContext(JSContext *cx, bool withGC)
}
static JSObject *
NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options)
NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options,
JSPrincipals *principals)
{
RootedObject glob(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
RootedObject glob(cx, JS_NewGlobalObject(cx, &global_class, principals,
JS::DontFireOnNewGlobalHook, options));
if (!glob)
return nullptr;
@ -5849,7 +5927,7 @@ Shell(JSContext *cx, OptionParser *op, char **envp)
RootedObject glob(cx);
JS::CompartmentOptions options;
options.setVersion(JSVERSION_LATEST);
glob = NewGlobalObject(cx, options);
glob = NewGlobalObject(cx, options, nullptr);
if (!glob)
return 1;
@ -6103,11 +6181,10 @@ main(int argc, char **argv, char **envp)
if (availMem > 0)
JS_SetGCParametersBasedOnAvailableMemory(rt, availMem);
/* Set the initial counter to 1 so the principal will never be destroyed. */
JSPrincipals shellTrustedPrincipals;
shellTrustedPrincipals.refcount = 1;
JS_SetTrustedPrincipals(rt, &ShellPrincipals::fullyTrusted);
JS_SetSecurityCallbacks(rt, &ShellPrincipals::securityCallbacks);
JS_InitDestroyPrincipalsCallback(rt, ShellPrincipals::destroy);
JS_SetTrustedPrincipals(rt, &shellTrustedPrincipals);
JS_SetOperationCallback(rt, ShellOperationCallback);
JS::SetAsmJSCacheOps(rt, &asmJSCacheOps);