TEST(PerIsolateState) {
HandleScope scope(CcTest::isolate());
LocalContext context1(CcTest::isolate());
+
+ Local<Value> foo = v8_str("foo");
+ context1->SetSecurityToken(foo);
+
CompileRun(
"var count = 0;"
"var calls = 0;"
Handle<Value> notify_fun2;
{
LocalContext context2(CcTest::isolate());
+ context2->SetSecurityToken(foo);
context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
obj);
notify_fun2 = CompileRun(
Handle<Value> notify_fun3;
{
LocalContext context3(CcTest::isolate());
+ context3->SetSecurityToken(foo);
context3->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
obj);
notify_fun3 = CompileRun(
}
{
LocalContext context4(CcTest::isolate());
+ context4->SetSecurityToken(foo);
context4->Global()->Set(
String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun1"),
}
-TEST(GlobalObjectObservation) {
- LocalContext context(CcTest::isolate());
- HandleScope scope(CcTest::isolate());
- Handle<Object> global_proxy = context->Global();
- CompileRun(
- "var records = [];"
- "var global = this;"
- "Object.observe(global, function(r) { [].push.apply(records, r) });"
- "global.foo = 'hello';");
- CHECK_EQ(1, CompileRun("records.length")->Int32Value());
- CHECK(global_proxy->StrictEquals(CompileRun("records[0].object")));
-
- // Detached, mutating the proxy has no effect.
- context->DetachGlobal();
- CompileRun("global.bar = 'goodbye';");
- CHECK_EQ(1, CompileRun("records.length")->Int32Value());
- CompileRun("this.baz = 'goodbye';");
- CHECK_EQ(1, CompileRun("records.length")->Int32Value());
-
- // Attached to a different context, should not leak mutations
- // to the old context.
- context->DetachGlobal();
- {
- LocalContext context2(CcTest::isolate());
- CompileRun(
- "var records2 = [];"
- "var global = this;"
- "Object.observe(this, function(r) { [].push.apply(records2, r) });"
- "this.v1 = 'context2';");
- context2->DetachGlobal();
- CompileRun(
- "global.v2 = 'context2';"
- "this.v3 = 'context2';");
- CHECK_EQ(1, CompileRun("records2.length")->Int32Value());
- }
- CHECK_EQ(1, CompileRun("records.length")->Int32Value());
-
- // Attaching by passing to Context::New
- {
- // Delegates to Context::New
- LocalContext context3(
- CcTest::isolate(), NULL, Handle<ObjectTemplate>(), global_proxy);
- CompileRun(
- "var records3 = [];"
- "Object.observe(this, function(r) { [].push.apply(records3, r) });"
- "this.qux = 'context3';");
- CHECK_EQ(1, CompileRun("records3.length")->Int32Value());
- CHECK(global_proxy->StrictEquals(CompileRun("records3[0].object")));
- }
- CHECK_EQ(1, CompileRun("records.length")->Int32Value());
-}
-
-
struct RecordExpectation {
Handle<Value> object;
const char* type;
}
-static bool NamedAccessAlwaysAllowed(Local<Object>, Local<Value>, AccessType,
- Local<Value>) {
- return true;
+static int TestObserveSecurity(Handle<Context> observer_context,
+ Handle<Context> object_context,
+ Handle<Context> mutation_context) {
+ Context::Scope observer_scope(observer_context);
+ CompileRun("var records = null;"
+ "var observer = function(r) { records = r };");
+ Handle<Value> observer = CompileRun("observer");
+ {
+ Context::Scope object_scope(object_context);
+ object_context->Global()->Set(
+ String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
+ CompileRun("var obj = {};"
+ "obj.length = 0;"
+ "Object.observe(obj, observer,"
+ "['add', 'update', 'delete','reconfigure','splice']"
+ ");");
+ Handle<Value> obj = CompileRun("obj");
+ {
+ Context::Scope mutation_scope(mutation_context);
+ mutation_context->Global()->Set(
+ String::NewFromUtf8(CcTest::isolate(), "obj"), obj);
+ CompileRun("obj.foo = 'bar';"
+ "obj.foo = 'baz';"
+ "delete obj.foo;"
+ "Object.defineProperty(obj, 'bar', {value: 'bot'});"
+ "Array.prototype.push.call(obj, 1, 2, 3);"
+ "Array.prototype.splice.call(obj, 1, 2, 2, 4);"
+ "Array.prototype.pop.call(obj);"
+ "Array.prototype.shift.call(obj);");
+ }
+ }
+ return CompileRun("records ? records.length : 0")->Int32Value();
}
-static bool IndexedAccessAlwaysAllowed(Local<Object>, uint32_t, AccessType,
- Local<Value>) {
- return true;
+TEST(ObserverSecurityAAA) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<Context> contextA = Context::New(isolate);
+ CHECK_EQ(8, TestObserveSecurity(contextA, contextA, contextA));
}
-static AccessType g_access_block_type = ACCESS_GET;
-static const uint32_t kBlockedContextIndex = 1337;
+TEST(ObserverSecurityA1A2A3) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+
+ v8::Local<Context> contextA1 = Context::New(isolate);
+ v8::Local<Context> contextA2 = Context::New(isolate);
+ v8::Local<Context> contextA3 = Context::New(isolate);
+ Local<Value> foo = v8_str("foo");
+ contextA1->SetSecurityToken(foo);
+ contextA2->SetSecurityToken(foo);
+ contextA3->SetSecurityToken(foo);
-static bool NamedAccessAllowUnlessBlocked(Local<Object> host,
- Local<Value> key,
- AccessType type,
- Local<Value> data) {
- if (type != g_access_block_type) return true;
- v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(
- Utils::OpenHandle(*host)->GetIsolate());
- Handle<Object> global = isolate->GetCurrentContext()->Global();
- if (!global->Has(kBlockedContextIndex)) return true;
- return !key->IsString() || !key->Equals(data);
+ CHECK_EQ(8, TestObserveSecurity(contextA1, contextA2, contextA3));
}
-static bool IndexedAccessAllowUnlessBlocked(Local<Object> host,
- uint32_t index,
- AccessType type,
- Local<Value> data) {
- if (type != g_access_block_type) return true;
- v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(
- Utils::OpenHandle(*host)->GetIsolate());
- Handle<Object> global = isolate->GetCurrentContext()->Global();
- if (!global->Has(kBlockedContextIndex)) return true;
- return index != data->Uint32Value();
+TEST(ObserverSecurityAAB) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<Context> contextA = Context::New(isolate);
+ v8::Local<Context> contextB = Context::New(isolate);
+ CHECK_EQ(0, TestObserveSecurity(contextA, contextA, contextB));
}
-static bool BlockAccessKeys(Local<Object> host, Local<Value> key,
- AccessType type, Local<Value>) {
- v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(
- Utils::OpenHandle(*host)->GetIsolate());
- Handle<Object> global = isolate->GetCurrentContext()->Global();
- return type != ACCESS_KEYS || !global->Has(kBlockedContextIndex);
+TEST(ObserverSecurityA1A2B) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+
+ v8::Local<Context> contextA1 = Context::New(isolate);
+ v8::Local<Context> contextA2 = Context::New(isolate);
+ v8::Local<Context> contextB = Context::New(isolate);
+
+ Local<Value> foo = v8_str("foo");
+ contextA1->SetSecurityToken(foo);
+ contextA2->SetSecurityToken(foo);
+
+ CHECK_EQ(0, TestObserveSecurity(contextA1, contextA2, contextB));
}
-static Handle<Object> CreateAccessCheckedObject(
- v8::Isolate* isolate,
- NamedSecurityCallback namedCallback,
- IndexedSecurityCallback indexedCallback,
- Handle<Value> data = Handle<Value>()) {
- Handle<ObjectTemplate> tmpl = ObjectTemplate::New(isolate);
- tmpl->SetAccessCheckCallbacks(namedCallback, indexedCallback, data);
- Handle<Object> instance = tmpl->NewInstance();
- Handle<Object> global = instance->CreationContext()->Global();
- global->Set(String::NewFromUtf8(isolate, "obj"), instance);
- global->Set(kBlockedContextIndex, v8::True(isolate));
- return instance;
+TEST(ObserverSecurityABA) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<Context> contextA = Context::New(isolate);
+ v8::Local<Context> contextB = Context::New(isolate);
+ CHECK_EQ(0, TestObserveSecurity(contextA, contextB, contextA));
}
-TEST(NamedAccessCheck) {
- const AccessType types[] = { ACCESS_GET, ACCESS_HAS };
- for (size_t i = 0; i < ARRAY_SIZE(types); ++i) {
- HandleScope scope(CcTest::isolate());
- LocalContext context(CcTest::isolate());
- g_access_block_type = types[i];
- Handle<Object> instance = CreateAccessCheckedObject(
- CcTest::isolate(),
- NamedAccessAllowUnlessBlocked,
- IndexedAccessAlwaysAllowed,
- String::NewFromUtf8(CcTest::isolate(), "foo"));
- CompileRun("var records = null;"
- "var objNoCheck = {};"
- "var observer = function(r) { records = r };"
- "Object.observe(obj, observer);"
- "Object.observe(objNoCheck, observer);");
- Handle<Value> obj_no_check = CompileRun("objNoCheck");
- {
- LocalContext context2(CcTest::isolate());
- context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
- instance);
- context2->Global()->Set(
- String::NewFromUtf8(CcTest::isolate(), "objNoCheck"),
- obj_no_check);
- CompileRun("var records2 = null;"
- "var observer2 = function(r) { records2 = r };"
- "Object.observe(obj, observer2);"
- "Object.observe(objNoCheck, observer2);"
- "obj.foo = 'bar';"
- "Object.defineProperty(obj, 'foo', {value: 5});"
- "Object.defineProperty(obj, 'foo', {get: function(){}});"
- "obj.bar = 'baz';"
- "objNoCheck.baz = 'quux'");
- const RecordExpectation expected_records2[] = {
- { instance, "add", "foo", Handle<Value>() },
- { instance, "update", "foo",
- String::NewFromUtf8(CcTest::isolate(), "bar") },
- { instance, "reconfigure", "foo",
- Number::New(CcTest::isolate(), 5) },
- { instance, "add", "bar", Handle<Value>() },
- { obj_no_check, "add", "baz", Handle<Value>() },
- };
- EXPECT_RECORDS(CompileRun("records2"), expected_records2);
- }
- const RecordExpectation expected_records[] = {
- { instance, "add", "bar", Handle<Value>() },
- { obj_no_check, "add", "baz", Handle<Value>() }
- };
- EXPECT_RECORDS(CompileRun("records"), expected_records);
- }
+TEST(ObserverSecurityA1BA2) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<Context> contextA1 = Context::New(isolate);
+ v8::Local<Context> contextA2 = Context::New(isolate);
+ v8::Local<Context> contextB = Context::New(isolate);
+
+ Local<Value> foo = v8_str("foo");
+ contextA1->SetSecurityToken(foo);
+ contextA2->SetSecurityToken(foo);
+
+ CHECK_EQ(0, TestObserveSecurity(contextA1, contextB, contextA2));
}
-TEST(IndexedAccessCheck) {
- const AccessType types[] = { ACCESS_GET, ACCESS_HAS };
- for (size_t i = 0; i < ARRAY_SIZE(types); ++i) {
- HandleScope scope(CcTest::isolate());
- LocalContext context(CcTest::isolate());
- g_access_block_type = types[i];
- Handle<Object> instance = CreateAccessCheckedObject(
- CcTest::isolate(), NamedAccessAlwaysAllowed,
- IndexedAccessAllowUnlessBlocked, Number::New(CcTest::isolate(), 7));
- CompileRun("var records = null;"
- "var objNoCheck = {};"
- "var observer = function(r) { records = r };"
- "Object.observe(obj, observer);"
- "Object.observe(objNoCheck, observer);");
- Handle<Value> obj_no_check = CompileRun("objNoCheck");
- {
- LocalContext context2(CcTest::isolate());
- context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
- instance);
- context2->Global()->Set(
- String::NewFromUtf8(CcTest::isolate(), "objNoCheck"),
- obj_no_check);
- CompileRun("var records2 = null;"
- "var observer2 = function(r) { records2 = r };"
- "Object.observe(obj, observer2);"
- "Object.observe(objNoCheck, observer2);"
- "obj[7] = 'foo';"
- "Object.defineProperty(obj, '7', {value: 5});"
- "Object.defineProperty(obj, '7', {get: function(){}});"
- "obj[8] = 'bar';"
- "objNoCheck[42] = 'quux'");
- const RecordExpectation expected_records2[] = {
- { instance, "add", "7", Handle<Value>() },
- { instance, "update", "7",
- String::NewFromUtf8(CcTest::isolate(), "foo") },
- { instance, "reconfigure", "7", Number::New(CcTest::isolate(), 5) },
- { instance, "add", "8", Handle<Value>() },
- { obj_no_check, "add", "42", Handle<Value>() }
- };
- EXPECT_RECORDS(CompileRun("records2"), expected_records2);
- }
- const RecordExpectation expected_records[] = {
- { instance, "add", "8", Handle<Value>() },
- { obj_no_check, "add", "42", Handle<Value>() }
- };
- EXPECT_RECORDS(CompileRun("records"), expected_records);
- }
+TEST(ObserverSecurityBAA) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<Context> contextA = Context::New(isolate);
+ v8::Local<Context> contextB = Context::New(isolate);
+ CHECK_EQ(0, TestObserveSecurity(contextB, contextA, contextA));
}
-TEST(SpliceAccessCheck) {
- HandleScope scope(CcTest::isolate());
- LocalContext context(CcTest::isolate());
- g_access_block_type = ACCESS_GET;
- Handle<Object> instance = CreateAccessCheckedObject(
- CcTest::isolate(), NamedAccessAlwaysAllowed,
- IndexedAccessAllowUnlessBlocked, Number::New(CcTest::isolate(), 1));
- CompileRun("var records = null;"
- "obj[1] = 'foo';"
- "obj.length = 2;"
- "var objNoCheck = {1: 'bar', length: 2};"
- "observer = function(r) { records = r };"
- "Array.observe(obj, observer);"
- "Array.observe(objNoCheck, observer);");
- Handle<Value> obj_no_check = CompileRun("objNoCheck");
+TEST(ObserverSecurityBA1A2) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<Context> contextA1 = Context::New(isolate);
+ v8::Local<Context> contextA2 = Context::New(isolate);
+ v8::Local<Context> contextB = Context::New(isolate);
+
+ Local<Value> foo = v8_str("foo");
+ contextA1->SetSecurityToken(foo);
+ contextA2->SetSecurityToken(foo);
+
+ CHECK_EQ(0, TestObserveSecurity(contextB, contextA1, contextA2));
+}
+
+
+TEST(ObserverSecurityNotify) {
+ v8::Isolate* isolate = CcTest::isolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<Context> contextA = Context::New(isolate);
+ v8::Local<Context> contextB = Context::New(isolate);
+
+ Context::Scope scopeA(contextA);
+ CompileRun("var obj = {};"
+ "var recordsA = null;"
+ "var observerA = function(r) { recordsA = r };"
+ "Object.observe(obj, observerA);");
+ Handle<Value> obj = CompileRun("obj");
+
{
- LocalContext context2(CcTest::isolate());
- context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
- instance);
- context2->Global()->Set(
- String::NewFromUtf8(CcTest::isolate(), "objNoCheck"), obj_no_check);
- CompileRun("var records2 = null;"
- "var observer2 = function(r) { records2 = r };"
- "Array.observe(obj, observer2);"
- "Array.observe(objNoCheck, observer2);"
- // No one should hear about this: no splice records are emitted
- // for access-checked objects
- "[].push.call(obj, 5);"
- "[].splice.call(obj, 1, 1);"
- "[].pop.call(obj);"
- "[].pop.call(objNoCheck);");
- // TODO(adamk): Extend EXPECT_RECORDS to be able to assert more things
- // about splice records. For this test it's not so important since
- // we just want to guarantee the machinery is in operation at all.
- const RecordExpectation expected_records2[] = {
- { obj_no_check, "splice", "", Handle<Value>() }
- };
- EXPECT_RECORDS(CompileRun("records2"), expected_records2);
+ Context::Scope scopeB(contextB);
+ contextB->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"), obj);
+ CompileRun("var recordsB = null;"
+ "var observerB = function(r) { recordsB = r };"
+ "Object.observe(obj, observerB);");
+ }
+
+ CompileRun("var notifier = Object.getNotifier(obj);"
+ "notifier.notify({ type: 'update' });");
+ CHECK_EQ(1, CompileRun("recordsA ? recordsA.length : 0")->Int32Value());
+
+ {
+ Context::Scope scopeB(contextB);
+ CHECK_EQ(0, CompileRun("recordsB ? recordsB.length : 0")->Int32Value());
}
- const RecordExpectation expected_records[] = {
- { obj_no_check, "splice", "", Handle<Value>() }
- };
- EXPECT_RECORDS(CompileRun("records"), expected_records);
}
-TEST(DisallowAllForAccessKeys) {
+TEST(HiddenPropertiesLeakage) {
HandleScope scope(CcTest::isolate());
LocalContext context(CcTest::isolate());
- Handle<Object> instance = CreateAccessCheckedObject(
- CcTest::isolate(), BlockAccessKeys, IndexedAccessAlwaysAllowed);
- CompileRun("var records = null;"
- "var objNoCheck = {};"
+ CompileRun("var obj = {};"
+ "var records = null;"
"var observer = function(r) { records = r };"
- "Object.observe(obj, observer);"
- "Object.observe(objNoCheck, observer);");
- Handle<Value> obj_no_check = CompileRun("objNoCheck");
+ "Object.observe(obj, observer);");
+ Handle<Value> obj =
+ context->Global()->Get(String::NewFromUtf8(CcTest::isolate(), "obj"));
+ Handle<Object>::Cast(obj)
+ ->SetHiddenValue(String::NewFromUtf8(CcTest::isolate(), "foo"),
+ Null(CcTest::isolate()));
+ CompileRun(""); // trigger delivery
+ CHECK(CompileRun("records")->IsNull());
+}
+
+
+TEST(GetNotifierFromOtherContext) {
+ HandleScope scope(CcTest::isolate());
+ LocalContext context(CcTest::isolate());
+ CompileRun("var obj = {};");
+ Handle<Value> instance = CompileRun("obj");
{
LocalContext context2(CcTest::isolate());
context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
instance);
- context2->Global()->Set(
- String::NewFromUtf8(CcTest::isolate(), "objNoCheck"), obj_no_check);
- CompileRun("var records2 = null;"
- "var observer2 = function(r) { records2 = r };"
- "Object.observe(obj, observer2);"
- "Object.observe(objNoCheck, observer2);"
- "obj.foo = 'bar';"
- "obj[5] = 'baz';"
- "objNoCheck.baz = 'quux'");
- const RecordExpectation expected_records2[] = {
- { instance, "add", "foo", Handle<Value>() },
- { instance, "add", "5", Handle<Value>() },
- { obj_no_check, "add", "baz", Handle<Value>() },
- };
- EXPECT_RECORDS(CompileRun("records2"), expected_records2);
+ CHECK(CompileRun("Object.getNotifier(obj)")->IsNull());
}
- const RecordExpectation expected_records[] = {
- { obj_no_check, "add", "baz", Handle<Value>() }
- };
- EXPECT_RECORDS(CompileRun("records"), expected_records);
}
-TEST(AccessCheckDisallowApiModifications) {
+TEST(GetNotifierFromOtherOrigin) {
HandleScope scope(CcTest::isolate());
+ Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
+ Handle<Value> bar = String::NewFromUtf8(CcTest::isolate(), "bar");
LocalContext context(CcTest::isolate());
- Handle<Object> instance = CreateAccessCheckedObject(
- CcTest::isolate(), BlockAccessKeys, IndexedAccessAlwaysAllowed);
- CompileRun("var records = null;"
- "var observer = function(r) { records = r };"
- "Object.observe(obj, observer);");
+ context->SetSecurityToken(foo);
+ CompileRun("var obj = {};");
+ Handle<Value> instance = CompileRun("obj");
{
LocalContext context2(CcTest::isolate());
+ context2->SetSecurityToken(bar);
context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
instance);
- CompileRun("var records2 = null;"
- "var observer2 = function(r) { records2 = r };"
- "Object.observe(obj, observer2);");
- instance->Set(5, String::NewFromUtf8(CcTest::isolate(), "bar"));
- instance->Set(String::NewFromUtf8(CcTest::isolate(), "foo"),
- String::NewFromUtf8(CcTest::isolate(), "bar"));
- CompileRun(""); // trigger delivery
- const RecordExpectation expected_records2[] = {
- { instance, "add", "5", Handle<Value>() },
- { instance, "add", "foo", Handle<Value>() }
- };
- EXPECT_RECORDS(CompileRun("records2"), expected_records2);
+ CHECK(CompileRun("Object.getNotifier(obj)")->IsNull());
}
- CHECK(CompileRun("records")->IsNull());
}
-TEST(HiddenPropertiesLeakage) {
+TEST(GetNotifierFromSameOrigin) {
HandleScope scope(CcTest::isolate());
+ Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
LocalContext context(CcTest::isolate());
- CompileRun("var obj = {};"
- "var records = null;"
- "var observer = function(r) { records = r };"
- "Object.observe(obj, observer);");
- Handle<Value> obj =
- context->Global()->Get(String::NewFromUtf8(CcTest::isolate(), "obj"));
- Handle<Object>::Cast(obj)
- ->SetHiddenValue(String::NewFromUtf8(CcTest::isolate(), "foo"),
- Null(CcTest::isolate()));
- CompileRun(""); // trigger delivery
- CHECK(CompileRun("records")->IsNull());
+ context->SetSecurityToken(foo);
+ CompileRun("var obj = {};");
+ Handle<Value> instance = CompileRun("obj");
+ {
+ LocalContext context2(CcTest::isolate());
+ context2->SetSecurityToken(foo);
+ context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
+ instance);
+ CHECK(CompileRun("Object.getNotifier(obj)")->IsObject());
+ }
}