Commit 1719ecb9 authored by Karl Schimpf's avatar Karl Schimpf Committed by Commit Bot

Add capability to handle CSP 'wasm-eval' in V8

Like CSP flag 'unsafe-eval', which communicates if both JS source
files and WASM binary files may be compiled, this CL adds a similar
flag for the compilation of WASM binary files.

That is, a WASM binary file will be compiled only if the new flag is
defined, or the flag for 'unsafe-eval' allows it. These flags are
implemented as callback functions on the isolate. The callbacks get a
(CSP) context, and a string, and returns the corresponding value of
the flag.

Both callbacks are initialized with the nullptr, and is used to
communicate that no CSP policy is defined. This allows this concept to
work, independent of it running in Chrome.

It also does a small clean up in api.cc to use macro CALLER_SETTERS,
instead of explicit code when appropriate.

Bug: v8:7041
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng
Change-Id: Idb3356574ae2a298057e6b7bccbd3492831952ae
Reviewed-on: https://chromium-review.googlesource.com/759162Reviewed-by: 's avatarBill Budge <bbudge@chromium.org>
Reviewed-by: 's avatarEric Holk <eholk@chromium.org>
Commit-Queue: Karl Schimpf <kschimpf@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49243}
parent 1ea3fd2e
......@@ -6443,6 +6443,9 @@ typedef bool (*AllowCodeGenerationFromStringsCallback)(Local<Context> context,
// --- WebAssembly compilation callbacks ---
typedef bool (*ExtensionCallback)(const FunctionCallbackInfo<Value>&);
typedef bool (*AllowWasmCodeGenerationCallback)(Local<Context> context,
Local<String> source);
// --- Callback for APIs defined on v8-supported objects, but implemented
// by the embedder. Example: WebAssembly.{compile|instantiate}Streaming ---
typedef void (*ApiImplementationCallback)(const FunctionCallbackInfo<Value>&);
......@@ -7739,6 +7742,13 @@ class V8_EXPORT Isolate {
void SetAllowCodeGenerationFromStringsCallback(
AllowCodeGenerationFromStringsCallback callback);
/**
* Set the callback to invoke to check if wasm code generation should
* be allowed.
*/
void SetAllowWasmCodeGenerationCallback(
AllowWasmCodeGenerationCallback callback);
/**
* Embedder over{ride|load} injection points for wasm APIs. The expectation
* is that the embedder sets them at most once.
......
......@@ -9186,28 +9186,19 @@ void Isolate::GetCodeRange(void** start, size_t* length_in_bytes) {
}
void Isolate::SetFatalErrorHandler(FatalErrorCallback that) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->set_exception_behavior(that);
}
void Isolate::SetOOMErrorHandler(OOMErrorCallback that) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->set_oom_behavior(that);
}
void Isolate::SetAllowCodeGenerationFromStringsCallback(
AllowCodeGenerationFromStringsCallback callback) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->set_allow_code_gen_callback(callback);
}
#define CALLBACK_SETTER(ExternalName, Type, InternalName) \
void Isolate::Set##ExternalName(Type callback) { \
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this); \
isolate->set_##InternalName(callback); \
}
CALLBACK_SETTER(FatalErrorHandler, FatalErrorCallback, exception_behavior)
CALLBACK_SETTER(OOMErrorHandler, OOMErrorCallback, oom_behavior)
CALLBACK_SETTER(AllowCodeGenerationFromStringsCallback,
AllowCodeGenerationFromStringsCallback, allow_code_gen_callback)
CALLBACK_SETTER(AllowWasmCodeGenerationCallback,
AllowWasmCodeGenerationCallback, allow_wasm_code_gen_callback)
CALLBACK_SETTER(WasmModuleCallback, ExtensionCallback, wasm_module_callback)
CALLBACK_SETTER(WasmInstanceCallback, ExtensionCallback, wasm_instance_callback)
......
......@@ -197,6 +197,7 @@ enum ContextLookupFlags {
V(ACCESSOR_PROPERTY_DESCRIPTOR_MAP_INDEX, Map, \
accessor_property_descriptor_map) \
V(ALLOW_CODE_GEN_FROM_STRINGS_INDEX, Object, allow_code_gen_from_strings) \
V(ALLOW_WASM_EVAL_INDEX, Object, allow_wasm_eval) \
V(ARRAY_BUFFER_FUN_INDEX, JSFunction, array_buffer_fun) \
V(ARRAY_BUFFER_MAP_INDEX, Map, array_buffer_map) \
V(ARRAY_BUFFER_NOINIT_FUN_INDEX, JSFunction, array_buffer_noinit_fun) \
......
......@@ -420,6 +420,7 @@ typedef std::vector<HeapObject*> DebugObjectCache;
V(OOMErrorCallback, oom_behavior, nullptr) \
V(LogEventCallback, event_logger, nullptr) \
V(AllowCodeGenerationFromStringsCallback, allow_code_gen_callback, nullptr) \
V(AllowWasmCodeGenerationCallback, allow_wasm_code_gen_callback, nullptr) \
V(ExtensionCallback, wasm_module_callback, &NoExtension) \
V(ExtensionCallback, wasm_instance_callback, &NoExtension) \
V(ApiImplementationCallback, wasm_compile_streaming_callback, nullptr) \
......
......@@ -855,12 +855,18 @@ RUNTIME_FUNCTION(Runtime_DisallowCodegenFromStrings) {
DCHECK_EQ(1, args.length());
CONVERT_BOOLEAN_ARG_CHECKED(flag, 0);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
if (flag) {
v8_isolate->SetAllowCodeGenerationFromStringsCallback(
DisallowCodegenFromStringsCallback);
} else {
v8_isolate->SetAllowCodeGenerationFromStringsCallback(nullptr);
}
v8_isolate->SetAllowCodeGenerationFromStringsCallback(
flag ? DisallowCodegenFromStringsCallback : nullptr);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DisallowWasmCodegen) {
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
CONVERT_BOOLEAN_ARG_CHECKED(flag, 0);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
v8_isolate->SetAllowWasmCodeGenerationCallback(
flag ? DisallowCodegenFromStringsCallback : nullptr);
return isolate->heap()->undefined_value();
}
......
......@@ -624,6 +624,7 @@ namespace internal {
F(IsWasmTrapHandlerEnabled, 0, 1) \
F(GetWasmRecoveredTrapCount, 0, 1) \
F(DisallowCodegenFromStrings, 1, 1) \
F(DisallowWasmCodegen, 1, 1) \
F(ValidateWasmInstancesChain, 2, 1) \
F(ValidateWasmModuleState, 1, 1) \
F(ValidateWasmOrphanedInstance, 1, 1) \
......
......@@ -167,8 +167,14 @@ bool IsWasmCodegenAllowed(Isolate* isolate, Handle<Context> context) {
// separate callback that includes information about the module about to be
// compiled. For the time being, pass an empty string as placeholder for the
// sources.
return isolate->allow_code_gen_callback() == nullptr ||
isolate->allow_code_gen_callback()(
if (auto wasm_codegen_callback = isolate->allow_wasm_code_gen_callback()) {
return wasm_codegen_callback(
v8::Utils::ToLocal(context),
v8::Utils::ToLocal(isolate->factory()->empty_string()));
}
auto codegen_callback = isolate->allow_code_gen_callback();
return codegen_callback == nullptr ||
codegen_callback(
v8::Utils::ToLocal(context),
v8::Utils::ToLocal(isolate->factory()->empty_string()));
}
......
......@@ -228,6 +228,7 @@ v8_source_set("cctest_sources") {
"wasm/test-run-wasm.cc",
"wasm/test-streaming-compilation.cc",
"wasm/test-wasm-breakpoints.cc",
"wasm/test-wasm-codegen.cc",
"wasm/test-wasm-interpreter-entry.cc",
"wasm/test-wasm-stack.cc",
"wasm/test-wasm-trap-position.cc",
......
......@@ -218,6 +218,7 @@
'wasm/test-run-wasm-relocation.cc',
'wasm/test-run-wasm-simd.cc',
'wasm/test-wasm-breakpoints.cc',
"wasm/test-wasm-codegen.cc",
'wasm/test-wasm-interpreter-entry.cc',
'wasm/test-wasm-stack.cc',
'wasm/test-wasm-trap-position.cc',
......
// Copyright 2017 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.
// Tests effects of (CSP) "unsafe-eval" and "wasm-eval" callback functions.
//
// Note: These tests are in a separate test file because the tests dynamically
// change the isolate in terms of callbacks allow_code_gen_callback and
// allow_wasm_code_gen_callback.
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
// Possible values for callback pointers.
enum TestValue {
kTestUsingNull, // no callback.
kTestUsingFalse, // callback returning false.
kTestUsingTrue, // callbacl returning true.
};
constexpr int kNumTestValues = 3;
const char* TestValueName[kNumTestValues] = {"null", "false", "true"};
// Defined to simplify iterating over TestValues;
const TestValue AllTestValues[kNumTestValues] = {
kTestUsingNull, kTestUsingFalse, kTestUsingTrue};
// This matrix holds the results of setting allow_code_gen_callback
// (first index) and allow_wasm_code_gen_callback (second index) using
// TestValue's. The value in the matrix is true if compilation is
// allowed, and false otherwise.
const bool ExpectedResults[kNumTestValues][kNumTestValues] = {
{true, false, true}, {false, false, true}, {true, false, true}};
bool TrueCallback(Local<v8::Context>, Local<v8::String>) { return true; }
bool FalseCallback(Local<v8::Context>, Local<v8::String>) { return false; }
typedef bool (*CallbackFn)(Local<v8::Context>, Local<v8::String>);
// Defines the Callback to use for the corresponding TestValue.
CallbackFn Callback[kNumTestValues] = {nullptr, FalseCallback, TrueCallback};
void BuildTrivialModule(Zone* zone, ZoneBuffer* buffer) {
WasmModuleBuilder* builder = new (zone) WasmModuleBuilder(zone);
builder->WriteTo(*buffer);
}
bool TestModule(Isolate* isolate,
v8::WasmCompiledModule::CallerOwnedBuffer wire_bytes) {
HandleScope scope(isolate);
v8::WasmCompiledModule::CallerOwnedBuffer serialized_module(nullptr, 0);
MaybeLocal<v8::WasmCompiledModule> module =
v8::WasmCompiledModule::DeserializeOrCompile(
reinterpret_cast<v8::Isolate*>(isolate), serialized_module,
wire_bytes);
return !module.IsEmpty();
}
} // namespace
TEST(PropertiesOfCodegenCallbacks) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
ZoneBuffer buffer(&zone);
BuildTrivialModule(&zone, &buffer);
v8::WasmCompiledModule::CallerOwnedBuffer wire_bytes = {buffer.begin(),
buffer.size()};
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
for (TestValue codegen : AllTestValues) {
for (TestValue wasm_codegen : AllTestValues) {
fprintf(stderr, "Test codegen = %s, wasm_codegen = %s\n",
TestValueName[codegen], TestValueName[wasm_codegen]);
isolate->set_allow_code_gen_callback(Callback[codegen]);
isolate->set_allow_wasm_code_gen_callback(Callback[wasm_codegen]);
bool found = TestModule(isolate, wire_bytes);
bool expected = ExpectedResults[codegen][wasm_codegen];
CHECK_EQ(expected, found);
CcTest::CollectAllAvailableGarbage();
}
}
}
} // namespace wasm
} // namespace internal
} // namespace v8
......@@ -20,10 +20,12 @@ let buffer = (function CreateBuffer() {
})();
%DisallowCodegenFromStrings(true);
%DisallowWasmCodegen(true);
async function SyncTestOk() {
print('sync module compile (ok)...');
%DisallowCodegenFromStrings(false);
%DisallowWasmCodegen(false);
let module = new WebAssembly.Module(buffer);
assertInstanceof(module, WebAssembly.Module);
}
......@@ -31,6 +33,20 @@ async function SyncTestOk() {
async function SyncTestFail() {
print('sync module compile (fail)...');
%DisallowCodegenFromStrings(true);
%DisallowWasmCodegen(false);
try {
let module = new WebAssembly.Module(buffer);
assertUnreachable();
} catch (e) {
print(" " + e);
assertInstanceof(e, WebAssembly.CompileError);
}
}
async function SyncTestWasmFail(disallow_codegen) {
print('sync wasm module compile (fail)...');
%DisallowCodegenFromStrings(disallow_codegen);
%DisallowWasmCodegen(true);
try {
let module = new WebAssembly.Module(buffer);
assertUnreachable();
......@@ -43,6 +59,7 @@ async function SyncTestFail() {
async function AsyncTestOk() {
print('async module compile (ok)...');
%DisallowCodegenFromStrings(false);
%DisallowWasmCodegen(false);
let promise = WebAssembly.compile(buffer);
assertPromiseResult(
promise, module => assertInstanceof(module, WebAssembly.Module));
......@@ -51,6 +68,20 @@ async function AsyncTestOk() {
async function AsyncTestFail() {
print('async module compile (fail)...');
%DisallowCodegenFromStrings(true);
%DisallowWasmCodegen(false);
try {
let m = await WebAssembly.compile(buffer);
assertUnreachable();
} catch (e) {
print(" " + e);
assertInstanceof(e, WebAssembly.CompileError);
}
}
async function AsyncTestWasmFail(disallow_codegen) {
print('async wasm module compile (fail)...');
%DisallowCodegenFromStrings(disallow_codegen);
%DisallowWasmCodegen(true);
try {
let m = await WebAssembly.compile(buffer);
assertUnreachable();
......@@ -65,6 +96,7 @@ async function StreamingTestOk() {
// TODO(titzer): compileStreaming must be supplied by embedder.
// (and it takes a response, not a buffer)
%DisallowCodegenFromStrings(false);
%DisallowWasmCodegen(false);
if ("Function" != typeof WebAssembly.compileStreaming) {
print(" no embedder for streaming compilation");
return;
......@@ -77,6 +109,27 @@ async function StreamingTestOk() {
async function StreamingTestFail() {
print('streaming module compile (fail)...');
%DisallowCodegenFromStrings(true);
%DisallowWasmCodegen(false);
// TODO(titzer): compileStreaming must be supplied by embedder.
// (and it takes a response, not a buffer)
if ("Function" != typeof WebAssembly.compileStreaming) {
print(" no embedder for streaming compilation");
return;
}
try {
let m = await WebAssembly.compileStreaming(buffer);
assertUnreachable();
} catch (e) {
print(" " + e);
assertInstanceof(e, WebAssembly.CompileError);
}
}
async function StreamingTestWasmFail(disallow_codegen) {
print('streaming wasm module compile (fail)...');
%DisallowCodegenFromStrings(disallow_codegen);
%DisallowWasmCodegen(true);
// TODO(titzer): compileStreaming must be supplied by embedder.
// (and it takes a response, not a buffer)
if ("Function" != typeof WebAssembly.compileStreaming) {
......@@ -99,6 +152,14 @@ async function RunAll() {
await AsyncTestFail();
await StreamingTestOk();
await StreamingTestFail();
disallow_codegen = false;
for (count = 0; count < 2; ++count) {
SyncTestWasmFail(disallow_codegen);
AsyncTestWasmFail(disallow_codegen);
StreamingTestWasmFail(disallow_codegen)
disallow_codegen = true;
}
}
assertPromiseResult(RunAll());
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment