ebe9419228
When v8 fails an access check, it invokes a helper to try to see if it can service the request via an access check interceptor. Invoking the access check interceptor can throw an exception (e.g. a SecurityError). Unfortunately, the failed access check property helpers and the interceptor helpers don't agree on how to propagate the exception: if the interceptor helper detects a scheduled exception, it promotes the exception to a pending exception and returns to the failed access check property helper. The failed access check property helper also has an early return in case of a scheduled exception. However, this doesn't work, as the previously thrown exception is no longer scheduled, as it's been promoted to a pending exception. Thus, the failed access check property helper always end up calling the failed access check callback as well. Since Blink's implementation of the failed access check callback also throws an exception, this conflicts with the previously-thrown, already-pending exception. With this patch, the failed access check property helpers check for a pending exception rather than a scheduled exception after invoking the interceptor, so the exception can be propagated correctly. BUG=v8:5715 R=yangguo@chromium.org,jochen@chromium.org Review-Url: https://codereview.chromium.org/2550423002 Cr-Commit-Position: refs/heads/master@{#41556}
405 lines
14 KiB
C++
405 lines
14 KiB
C++
// Copyright 2016 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "test/cctest/cctest.h"
|
|
|
|
namespace {
|
|
|
|
int32_t g_cross_context_int = 0;
|
|
|
|
bool g_expect_interceptor_call = false;
|
|
|
|
void NamedGetter(v8::Local<v8::Name> property,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
if (property->Equals(context, v8_str("cross_context_int")).FromJust())
|
|
info.GetReturnValue().Set(g_cross_context_int);
|
|
}
|
|
|
|
void NamedSetter(v8::Local<v8::Name> property, v8::Local<v8::Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
if (!property->Equals(context, v8_str("cross_context_int")).FromJust())
|
|
return;
|
|
if (value->IsInt32()) {
|
|
g_cross_context_int = value->ToInt32(context).ToLocalChecked()->Value();
|
|
}
|
|
info.GetReturnValue().Set(value);
|
|
}
|
|
|
|
void NamedQuery(v8::Local<v8::Name> property,
|
|
const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
if (!property->Equals(context, v8_str("cross_context_int")).FromJust())
|
|
return;
|
|
info.GetReturnValue().Set(v8::DontDelete);
|
|
}
|
|
|
|
void NamedDeleter(v8::Local<v8::Name> property,
|
|
const v8::PropertyCallbackInfo<v8::Boolean>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
if (!property->Equals(context, v8_str("cross_context_int")).FromJust())
|
|
return;
|
|
info.GetReturnValue().Set(false);
|
|
}
|
|
|
|
void NamedEnumerator(const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
v8::Local<v8::Array> names = v8::Array::New(isolate, 1);
|
|
names->Set(context, 0, v8_str("cross_context_int")).FromJust();
|
|
info.GetReturnValue().Set(names);
|
|
}
|
|
|
|
void IndexedGetter(uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
if (index == 7) info.GetReturnValue().Set(g_cross_context_int);
|
|
}
|
|
|
|
void IndexedSetter(uint32_t index, v8::Local<v8::Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
if (index != 7) return;
|
|
if (value->IsInt32()) {
|
|
g_cross_context_int = value->ToInt32(context).ToLocalChecked()->Value();
|
|
}
|
|
info.GetReturnValue().Set(value);
|
|
}
|
|
|
|
void IndexedQuery(uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Integer>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
if (index == 7) info.GetReturnValue().Set(v8::DontDelete);
|
|
}
|
|
|
|
void IndexedDeleter(uint32_t index,
|
|
const v8::PropertyCallbackInfo<v8::Boolean>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
if (index == 7) info.GetReturnValue().Set(false);
|
|
}
|
|
|
|
void IndexedEnumerator(const v8::PropertyCallbackInfo<v8::Array>& info) {
|
|
CHECK(g_expect_interceptor_call);
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
v8::Local<v8::Array> names = v8::Array::New(isolate, 1);
|
|
names->Set(context, 0, v8_str("7")).FromJust();
|
|
info.GetReturnValue().Set(names);
|
|
}
|
|
|
|
void NamedGetterThrowsException(
|
|
v8::Local<v8::Name> property,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetIsolate()->ThrowException(v8_str("exception"));
|
|
}
|
|
|
|
void NamedSetterThrowsException(
|
|
v8::Local<v8::Name> property, v8::Local<v8::Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetIsolate()->ThrowException(v8_str("exception"));
|
|
}
|
|
|
|
void IndexedGetterThrowsException(
|
|
uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetIsolate()->ThrowException(v8_str("exception"));
|
|
}
|
|
|
|
void IndexedSetterThrowsException(
|
|
uint32_t index, v8::Local<v8::Value> value,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetIsolate()->ThrowException(v8_str("exception"));
|
|
}
|
|
|
|
bool AccessCheck(v8::Local<v8::Context> accessing_context,
|
|
v8::Local<v8::Object> accessed_object,
|
|
v8::Local<v8::Value> data) {
|
|
return false;
|
|
}
|
|
|
|
void GetCrossContextInt(v8::Local<v8::String> property,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
CHECK(!g_expect_interceptor_call);
|
|
info.GetReturnValue().Set(g_cross_context_int);
|
|
}
|
|
|
|
void SetCrossContextInt(v8::Local<v8::String> property,
|
|
v8::Local<v8::Value> value,
|
|
const v8::PropertyCallbackInfo<void>& info) {
|
|
CHECK(!g_expect_interceptor_call);
|
|
v8::Isolate* isolate = info.GetIsolate();
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
if (value->IsInt32()) {
|
|
g_cross_context_int = value->ToInt32(context).ToLocalChecked()->Value();
|
|
}
|
|
}
|
|
|
|
void Return42(v8::Local<v8::String> property,
|
|
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
info.GetReturnValue().Set(42);
|
|
}
|
|
|
|
void CheckCanRunScriptInContext(v8::Isolate* isolate,
|
|
v8::Local<v8::Context> context) {
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
g_expect_interceptor_call = false;
|
|
g_cross_context_int = 0;
|
|
|
|
// Running script in this context should work.
|
|
CompileRunChecked(isolate, "this.foo = 42; this[23] = true;");
|
|
ExpectInt32("this.all_can_read", 42);
|
|
CompileRunChecked(isolate, "this.cross_context_int = 23");
|
|
CHECK_EQ(g_cross_context_int, 23);
|
|
ExpectInt32("this.cross_context_int", 23);
|
|
}
|
|
|
|
void CheckCrossContextAccess(v8::Isolate* isolate,
|
|
v8::Local<v8::Context> accessing_context,
|
|
v8::Local<v8::Object> accessed_object) {
|
|
v8::HandleScope handle_scope(isolate);
|
|
accessing_context->Global()
|
|
->Set(accessing_context, v8_str("other"), accessed_object)
|
|
.FromJust();
|
|
v8::Context::Scope context_scope(accessing_context);
|
|
|
|
g_expect_interceptor_call = true;
|
|
g_cross_context_int = 23;
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(CompileRun(accessing_context, "this.other.foo").IsEmpty());
|
|
}
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(CompileRun(accessing_context, "this.other[23]").IsEmpty());
|
|
}
|
|
|
|
// AllCanRead properties are also inaccessible.
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CHECK(CompileRun(accessing_context, "this.other.all_can_read").IsEmpty());
|
|
}
|
|
|
|
// Intercepted properties are accessible, however.
|
|
ExpectInt32("this.other.cross_context_int", 23);
|
|
CompileRunChecked(isolate, "this.other.cross_context_int = 42");
|
|
ExpectInt32("this.other[7]", 42);
|
|
ExpectString("JSON.stringify(Object.getOwnPropertyNames(this.other))",
|
|
"[\"7\",\"cross_context_int\"]");
|
|
}
|
|
|
|
void CheckCrossContextAccessWithException(
|
|
v8::Isolate* isolate, v8::Local<v8::Context> accessing_context,
|
|
v8::Local<v8::Object> accessed_object) {
|
|
v8::HandleScope handle_scope(isolate);
|
|
accessing_context->Global()
|
|
->Set(accessing_context, v8_str("other"), accessed_object)
|
|
.FromJust();
|
|
v8::Context::Scope context_scope(accessing_context);
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("this.other.should_throw");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->IsString());
|
|
CHECK(v8_str("exception")
|
|
->Equals(accessing_context, try_catch.Exception())
|
|
.FromJust());
|
|
}
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("this.other.should_throw = 8");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->IsString());
|
|
CHECK(v8_str("exception")
|
|
->Equals(accessing_context, try_catch.Exception())
|
|
.FromJust());
|
|
}
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("this.other[42]");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->IsString());
|
|
CHECK(v8_str("exception")
|
|
->Equals(accessing_context, try_catch.Exception())
|
|
.FromJust());
|
|
}
|
|
|
|
{
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("this.other[42] = 8");
|
|
CHECK(try_catch.HasCaught());
|
|
CHECK(try_catch.Exception()->IsString());
|
|
CHECK(v8_str("exception")
|
|
->Equals(accessing_context, try_catch.Exception())
|
|
.FromJust());
|
|
}
|
|
}
|
|
|
|
void Ctor(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
CHECK(info.IsConstructCall());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(AccessCheckWithInterceptor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
global_template->SetAccessCheckCallbackAndHandler(
|
|
AccessCheck,
|
|
v8::NamedPropertyHandlerConfiguration(
|
|
NamedGetter, NamedSetter, NamedQuery, NamedDeleter, NamedEnumerator),
|
|
v8::IndexedPropertyHandlerConfiguration(IndexedGetter, IndexedSetter,
|
|
IndexedQuery, IndexedDeleter,
|
|
IndexedEnumerator));
|
|
global_template->SetNativeDataProperty(
|
|
v8_str("cross_context_int"), GetCrossContextInt, SetCrossContextInt);
|
|
global_template->SetNativeDataProperty(
|
|
v8_str("all_can_read"), Return42, nullptr, v8::Local<v8::Value>(),
|
|
v8::None, v8::Local<v8::AccessorSignature>(), v8::ALL_CAN_READ);
|
|
|
|
v8::Local<v8::Context> context0 =
|
|
v8::Context::New(isolate, nullptr, global_template);
|
|
CheckCanRunScriptInContext(isolate, context0);
|
|
|
|
// Create another context.
|
|
v8::Local<v8::Context> context1 =
|
|
v8::Context::New(isolate, nullptr, global_template);
|
|
CheckCrossContextAccess(isolate, context1, context0->Global());
|
|
}
|
|
|
|
TEST(AccessCheckWithExceptionThrowingInterceptor) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
isolate->SetFailedAccessCheckCallbackFunction([](v8::Local<v8::Object> target,
|
|
v8::AccessType type,
|
|
v8::Local<v8::Value> data) {
|
|
CHECK(false); // This should never be called.
|
|
});
|
|
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
global_template->SetAccessCheckCallbackAndHandler(
|
|
AccessCheck, v8::NamedPropertyHandlerConfiguration(
|
|
NamedGetterThrowsException, NamedSetterThrowsException),
|
|
v8::IndexedPropertyHandlerConfiguration(IndexedGetterThrowsException,
|
|
IndexedSetterThrowsException));
|
|
|
|
// Create two contexts.
|
|
v8::Local<v8::Context> context0 =
|
|
v8::Context::New(isolate, nullptr, global_template);
|
|
v8::Local<v8::Context> context1 =
|
|
v8::Context::New(isolate, nullptr, global_template);
|
|
|
|
CheckCrossContextAccessWithException(isolate, context1, context0->Global());
|
|
}
|
|
|
|
TEST(NewRemoteContext) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::ObjectTemplate> global_template =
|
|
v8::ObjectTemplate::New(isolate);
|
|
global_template->SetAccessCheckCallbackAndHandler(
|
|
AccessCheck,
|
|
v8::NamedPropertyHandlerConfiguration(
|
|
NamedGetter, NamedSetter, NamedQuery, NamedDeleter, NamedEnumerator),
|
|
v8::IndexedPropertyHandlerConfiguration(IndexedGetter, IndexedSetter,
|
|
IndexedQuery, IndexedDeleter,
|
|
IndexedEnumerator));
|
|
global_template->SetNativeDataProperty(
|
|
v8_str("cross_context_int"), GetCrossContextInt, SetCrossContextInt);
|
|
global_template->SetNativeDataProperty(
|
|
v8_str("all_can_read"), Return42, nullptr, v8::Local<v8::Value>(),
|
|
v8::None, v8::Local<v8::AccessorSignature>(), v8::ALL_CAN_READ);
|
|
|
|
v8::Local<v8::Object> global0 =
|
|
v8::Context::NewRemoteContext(isolate, global_template).ToLocalChecked();
|
|
|
|
// Create a real context.
|
|
{
|
|
v8::HandleScope other_scope(isolate);
|
|
v8::Local<v8::Context> context1 =
|
|
v8::Context::New(isolate, nullptr, global_template);
|
|
|
|
CheckCrossContextAccess(isolate, context1, global0);
|
|
}
|
|
|
|
// Create a context using the detached global.
|
|
{
|
|
v8::HandleScope other_scope(isolate);
|
|
v8::Local<v8::Context> context2 =
|
|
v8::Context::New(isolate, nullptr, global_template, global0);
|
|
|
|
CheckCanRunScriptInContext(isolate, context2);
|
|
}
|
|
|
|
// Turn a regular context into a remote context.
|
|
{
|
|
v8::HandleScope other_scope(isolate);
|
|
v8::Local<v8::Context> context3 =
|
|
v8::Context::New(isolate, nullptr, global_template);
|
|
|
|
CheckCanRunScriptInContext(isolate, context3);
|
|
|
|
// Turn the global object into a remote context, and try to access it.
|
|
v8::Local<v8::Object> context3_global = context3->Global();
|
|
context3->DetachGlobal();
|
|
v8::Local<v8::Object> global3 =
|
|
v8::Context::NewRemoteContext(isolate, global_template, context3_global)
|
|
.ToLocalChecked();
|
|
v8::Local<v8::Context> context4 =
|
|
v8::Context::New(isolate, nullptr, global_template);
|
|
|
|
CheckCrossContextAccess(isolate, context4, global3);
|
|
|
|
// Turn it back into a regular context.
|
|
v8::Local<v8::Context> context5 =
|
|
v8::Context::New(isolate, nullptr, global_template, global3);
|
|
|
|
CheckCanRunScriptInContext(isolate, context5);
|
|
}
|
|
}
|
|
|
|
TEST(NewRemoteInstance) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::FunctionTemplate> tmpl =
|
|
v8::FunctionTemplate::New(isolate, Ctor);
|
|
v8::Local<v8::ObjectTemplate> instance = tmpl->InstanceTemplate();
|
|
instance->SetAccessCheckCallbackAndHandler(
|
|
AccessCheck,
|
|
v8::NamedPropertyHandlerConfiguration(
|
|
NamedGetter, NamedSetter, NamedQuery, NamedDeleter, NamedEnumerator),
|
|
v8::IndexedPropertyHandlerConfiguration(IndexedGetter, IndexedSetter,
|
|
IndexedQuery, IndexedDeleter,
|
|
IndexedEnumerator));
|
|
tmpl->SetNativeDataProperty(
|
|
v8_str("all_can_read"), Return42, nullptr, v8::Local<v8::Value>(),
|
|
v8::None, v8::Local<v8::AccessorSignature>(), v8::ALL_CAN_READ);
|
|
|
|
v8::Local<v8::Object> obj = tmpl->NewRemoteInstance().ToLocalChecked();
|
|
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
CheckCrossContextAccess(isolate, context, obj);
|
|
}
|