Skip to content

Commit

Permalink
Propagate exceptions thrown by access check interceptors.
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
zetafunction authored and Commit bot committed Dec 7, 2016
1 parent b5f146a commit ebe9419
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 6 deletions.
13 changes: 7 additions & 6 deletions src/objects.cc
Expand Up @@ -1811,11 +1811,13 @@ MaybeHandle<Object> JSObject::GetPropertyWithFailedAccessCheck(
GetPropertyWithInterceptor(it, &done), Object);
if (done) return result;
}

} else {
MaybeHandle<Object> result;
Handle<Object> result;
bool done;
result = GetPropertyWithInterceptorInternal(it, interceptor, &done);
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
GetPropertyWithInterceptorInternal(it, interceptor, &done), Object);
if (done) return result;
}

Expand Down Expand Up @@ -1851,7 +1853,7 @@ Maybe<PropertyAttributes> JSObject::GetPropertyAttributesWithFailedAccessCheck(
} else {
Maybe<PropertyAttributes> result =
GetPropertyAttributesWithInterceptorInternal(it, interceptor);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<PropertyAttributes>());
if (isolate->has_pending_exception()) return Nothing<PropertyAttributes>();
if (result.FromMaybe(ABSENT) != ABSENT) return result;
}
isolate->ReportFailedAccessCheck(checked);
Expand Down Expand Up @@ -1887,10 +1889,9 @@ Maybe<bool> JSObject::SetPropertyWithFailedAccessCheck(
} else {
Maybe<bool> result = SetPropertyWithInterceptorInternal(
it, interceptor, should_throw, value);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
if (isolate->has_pending_exception()) return Nothing<bool>();
if (result.IsJust()) return result;
}

isolate->ReportFailedAccessCheck(checked);
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>());
return Just(true);
Expand Down
99 changes: 99 additions & 0 deletions test/cctest/test-access-checks.cc
Expand Up @@ -102,6 +102,29 @@ void IndexedEnumerator(const v8::PropertyCallbackInfo<v8::Array>& info) {
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) {
Expand Down Expand Up @@ -181,6 +204,56 @@ void CheckCrossContextAccess(v8::Isolate* isolate,
"[\"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());
}
Expand Down Expand Up @@ -215,6 +288,32 @@ TEST(AccessCheckWithInterceptor) {
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);
Expand Down

0 comments on commit ebe9419

Please sign in to comment.