Commit b40d44ec authored by mtrofin's avatar mtrofin Committed by Commit bot

[wasm] Embedder can control what buffers wasm compilation works on.

Two controls, one for instantiation and one for compilation. They allow
the embedder (e.g. Chrome) check properties of the parameters of those
two operations, and decide if they are allowed to continue.

For example, Chrome may now decline compilation of certain size buffers,
in synchronous cases; same for instantiation (where the buffer size
refers to the size of the buffer containing wasm wire bytes)

BUG=v8:5981

Review-Url: https://codereview.chromium.org/2699843003
Cr-Original-Commit-Position: refs/heads/master@{#43295}
Committed: https://chromium.googlesource.com/v8/v8/+/d9bc0ffb16e633d52d7bcfd547a6125f0e4dfb87
Review-Url: https://codereview.chromium.org/2699843003
Cr-Commit-Position: refs/heads/master@{#43336}
parent 2b9840d8
......@@ -5947,6 +5947,21 @@ typedef void (*FailedAccessCheckCallback)(Local<Object> target,
*/
typedef bool (*AllowCodeGenerationFromStringsCallback)(Local<Context> context);
// --- WASM compilation callbacks ---
/**
* Callback to check if a buffer source may be compiled to WASM, given
* the compilation is attempted as a promise or not.
*/
typedef bool (*AllowWasmCompileCallback)(Isolate* isolate, Local<Value> source,
bool as_promise);
typedef bool (*AllowWasmInstantiateCallback)(Isolate* isolate,
Local<Value> module_or_bytes,
MaybeLocal<Value> ffi,
bool as_promise);
// --- Garbage Collection Callbacks ---
/**
......@@ -7200,6 +7215,16 @@ class V8_EXPORT Isolate {
void SetAllowCodeGenerationFromStringsCallback(
AllowCodeGenerationFromStringsCallback callback);
/**
* Set the callback to invoke to check if wasm compilation from
* the specified object is allowed. By default, wasm compilation
* is allowed.
*
* Similar for instantiate.
*/
void SetAllowWasmCompileCallback(AllowWasmCompileCallback callback);
void SetAllowWasmInstantiateCallback(AllowWasmInstantiateCallback callback);
/**
* Check if V8 is dead and therefore unusable. This is the case after
* fatal errors such as out-of-memory situations.
......
......@@ -8703,6 +8703,16 @@ void Isolate::SetAllowCodeGenerationFromStringsCallback(
isolate->set_allow_code_gen_callback(callback);
}
void Isolate::SetAllowWasmCompileCallback(AllowWasmCompileCallback callback) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->set_allow_wasm_compile_callback(callback);
}
void Isolate::SetAllowWasmInstantiateCallback(
AllowWasmInstantiateCallback callback) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->set_allow_wasm_instantiate_callback(callback);
}
bool Isolate::IsDead() {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
......
......@@ -394,6 +394,8 @@ typedef List<HeapObject*> DebugObjectCache;
V(OOMErrorCallback, oom_behavior, nullptr) \
V(LogEventCallback, event_logger, nullptr) \
V(AllowCodeGenerationFromStringsCallback, allow_code_gen_callback, nullptr) \
V(AllowWasmCompileCallback, allow_wasm_compile_callback, nullptr) \
V(AllowWasmInstantiateCallback, allow_wasm_instantiate_callback, nullptr) \
V(ExternalReferenceRedirectorPointer*, external_reference_redirector, \
nullptr) \
/* State for Relocatable. */ \
......
......@@ -19,6 +19,45 @@
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects.h"
namespace {
struct WasmCompileControls {
uint32_t MaxWasmBufferSize = std::numeric_limits<uint32_t>::max();
bool AllowAnySizeForAsync = true;
};
// We need per-isolate controls, because we sometimes run tests in multiple
// isolates
// concurrently.
// To avoid upsetting the static initializer count, we lazy initialize this.
v8::base::LazyInstance<std::map<v8::Isolate*, WasmCompileControls>>::type
g_PerIsolateWasmControls = LAZY_INSTANCE_INITIALIZER;
bool IsWasmCompileAllowed(v8::Isolate* isolate, v8::Local<v8::Value> value,
bool is_async) {
DCHECK_GT(g_PerIsolateWasmControls.Get().count(isolate), 0);
const WasmCompileControls& ctrls = g_PerIsolateWasmControls.Get().at(isolate);
return (is_async && ctrls.AllowAnySizeForAsync) ||
(v8::Local<v8::ArrayBuffer>::Cast(value)->ByteLength() <=
ctrls.MaxWasmBufferSize);
}
// Use the compile controls for instantiation, too
bool IsWasmInstantiateAllowed(v8::Isolate* isolate,
v8::Local<v8::Value> module_or_bytes,
v8::MaybeLocal<v8::Value> ffi, bool is_async) {
DCHECK_GT(g_PerIsolateWasmControls.Get().count(isolate), 0);
const WasmCompileControls& ctrls = g_PerIsolateWasmControls.Get().at(isolate);
if (is_async && ctrls.AllowAnySizeForAsync) return true;
if (!module_or_bytes->IsWebAssemblyCompiledModule()) {
return IsWasmCompileAllowed(isolate, module_or_bytes, is_async);
}
v8::Local<v8::WasmCompiledModule> module =
v8::Local<v8::WasmCompiledModule>::Cast(module_or_bytes);
return static_cast<uint32_t>(module->GetWasmWireBytes()->Length()) <=
ctrls.MaxWasmBufferSize;
}
} // namespace
namespace v8 {
namespace internal {
......@@ -429,6 +468,26 @@ RUNTIME_FUNCTION(Runtime_CheckWasmWrapperElision) {
return isolate->heap()->ToBoolean(count == 1);
}
RUNTIME_FUNCTION(Runtime_SetWasmCompileControls) {
HandleScope scope(isolate);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
CHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(Smi, block_size, 0);
CONVERT_BOOLEAN_ARG_CHECKED(allow_async, 1);
WasmCompileControls& ctrl = (*g_PerIsolateWasmControls.Pointer())[v8_isolate];
ctrl.AllowAnySizeForAsync = allow_async;
ctrl.MaxWasmBufferSize = static_cast<uint32_t>(block_size->value());
isolate->set_allow_wasm_compile_callback(IsWasmCompileAllowed);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_SetWasmInstantiateControls) {
HandleScope scope(isolate);
CHECK(args.length() == 0);
isolate->set_allow_wasm_instantiate_callback(IsWasmInstantiateAllowed);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_NotifyContextDisposed) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());
......
......@@ -599,6 +599,8 @@ namespace internal {
F(ValidateWasmInstancesChain, 2, 1) \
F(ValidateWasmModuleState, 1, 1) \
F(ValidateWasmOrphanedInstance, 1, 1) \
F(SetWasmCompileControls, 2, 1) \
F(SetWasmInstantiateControls, 0, 1) \
F(Verify, 1, 1)
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
......
......@@ -31,6 +31,11 @@ using v8::internal::wasm::ErrorThrower;
namespace v8 {
namespace {
#define RANGE_ERROR_MSG \
"Wasm compilation exceeds internal limits in this context for the provided " \
"arguments"
// TODO(wasm): move brand check to the respective types, and don't throw
// in it, rather, use a provided ErrorThrower, or let caller handle it.
static bool HasBrand(i::Handle<i::Object> value, i::Handle<i::Symbol> sym) {
......@@ -73,6 +78,44 @@ i::MaybeHandle<i::WasmModuleObject> GetFirstArgumentAsModule(
v8::Utils::OpenHandle(*module_obj));
}
bool IsCompilationAllowed(i::Isolate* isolate, ErrorThrower* thrower,
v8::Local<v8::Value> source, bool is_async) {
// Allow caller to do one final check on thrower state, rather than
// one at each step. No information is lost - failure reason is captured
// in the thrower state.
if (thrower->error()) return false;
AllowWasmCompileCallback callback = isolate->allow_wasm_compile_callback();
if (callback != nullptr &&
!callback(reinterpret_cast<v8::Isolate*>(isolate), source, is_async)) {
thrower->RangeError(RANGE_ERROR_MSG);
return false;
}
return true;
}
bool IsInstantiationAllowed(i::Isolate* isolate, ErrorThrower* thrower,
v8::Local<v8::Value> module_or_bytes,
i::MaybeHandle<i::JSReceiver> ffi, bool is_async) {
// Allow caller to do one final check on thrower state, rather than
// one at each step. No information is lost - failure reason is captured
// in the thrower state.
if (thrower->error()) return false;
v8::MaybeLocal<v8::Value> v8_ffi;
if (!ffi.is_null()) {
v8_ffi = v8::Local<v8::Value>::Cast(Utils::ToLocal(ffi.ToHandleChecked()));
}
AllowWasmInstantiateCallback callback =
isolate->allow_wasm_instantiate_callback();
if (callback != nullptr &&
!callback(reinterpret_cast<v8::Isolate*>(isolate), module_or_bytes,
v8_ffi, is_async)) {
thrower->RangeError(RANGE_ERROR_MSG);
return false;
}
return true;
}
i::wasm::ModuleWireBytes GetFirstArgumentAsBytes(
const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower) {
if (args.Length() < 1) {
......@@ -139,10 +182,11 @@ void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) {
return_value.Set(resolver->GetPromise());
auto bytes = GetFirstArgumentAsBytes(args, &thrower);
if (thrower.error()) {
if (!IsCompilationAllowed(i_isolate, &thrower, args[0], true)) {
resolver->Reject(context, Utils::ToLocal(thrower.Reify()));
return;
}
DCHECK(!thrower.error());
i::Handle<i::JSPromise> promise = Utils::OpenHandle(*resolver->GetPromise());
i::wasm::AsyncCompile(i_isolate, promise, bytes);
}
......@@ -175,8 +219,9 @@ void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) {
ErrorThrower thrower(i_isolate, "WebAssembly.Module()");
auto bytes = GetFirstArgumentAsBytes(args, &thrower);
if (thrower.error()) return;
if (!IsCompilationAllowed(i_isolate, &thrower, args[0], false)) return;
DCHECK(!thrower.error());
i::MaybeHandle<i::Object> module_obj =
i::wasm::SyncCompile(i_isolate, &thrower, bytes);
if (module_obj.is_null()) return;
......@@ -251,7 +296,11 @@ void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) {
if (thrower.error()) return;
auto maybe_imports = GetSecondArgumentAsImports(args, &thrower);
if (thrower.error()) return;
if (!IsInstantiationAllowed(i_isolate, &thrower, args[0], maybe_imports,
false)) {
return;
}
DCHECK(!thrower.error());
i::MaybeHandle<i::Object> instance_object = i::wasm::SyncInstantiate(
i_isolate, &thrower, maybe_module.ToHandleChecked(), maybe_imports,
......@@ -299,8 +348,12 @@ void WebAssemblyInstantiate(const v8::FunctionCallbackInfo<v8::Value>& args) {
resolver->Reject(context, Utils::ToLocal(thrower.Reify()));
return;
}
if (!IsInstantiationAllowed(i_isolate, &thrower, args[0], maybe_imports,
true)) {
resolver->Reject(context, Utils::ToLocal(thrower.Reify()));
return;
}
i::Handle<i::JSPromise> promise = Utils::OpenHandle(*resolver->GetPromise());
if (HasBrand(first_arg, i::Handle<i::Symbol>(i_context->wasm_module_sym()))) {
// WebAssembly.instantiate(module, imports) -> WebAssembly.Instance
auto module_object = GetFirstArgumentAsModule(args, &thrower);
......
// 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.
// Flags: --allow-natives-syntax --validate-asm
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
let buffer = (() => {
var builder = new WasmModuleBuilder();
builder.addFunction("f", kSig_i_v)
.addBody([kExprI32Const, 42])
.exportAs("f");
return builder.toBuffer();
})();
// allow up to the buffer size
%SetWasmCompileControls(buffer.byteLength, true);
%SetWasmInstantiateControls();
var m = new WebAssembly.Module(buffer);
var i = new WebAssembly.Instance(m);
assertEquals(i.exports.f(), 42);
// the buffer can't compile synchronously, but we allow async compile
%SetWasmCompileControls(buffer.byteLength - 1, true);
// test first that we can't sync-instantiate this module anymore
try {
i = new WebAssembly.Instance(m);
} catch (e) {
assertTrue(e instanceof RangeError);
}
//...but we can async-instantiate it
WebAssembly.instantiate(m)
.then(instance => i = instance,
assertUnreachable);
%RunMicrotasks();
assertTrue(i instanceof WebAssembly.Instance);
try {
m = new WebAssembly.Module(buffer);
assertUnreachable();
} catch (e) {
assertTrue(e instanceof RangeError);
}
WebAssembly.compile(buffer)
.then(res => m = res,
m = null);
%RunMicrotasks();
assertTrue(m instanceof WebAssembly.Module)
WebAssembly.instantiate(buffer)
.then(res => m = res.module,
m = null);
%RunMicrotasks();
assertTrue(m instanceof WebAssembly.Module);
// Async compile works, but using the sync instantiate doesn't
i = undefined;
m = undefined;
var ex = undefined;
WebAssembly.compile(buffer)
.then(mod => {
m = mod;
try {
i = new WebAssembly.Instance(m);
} catch (e) {
ex = e;
}
},
e => ex = e);
%RunMicrotasks();
assertTrue(ex instanceof RangeError);
assertEquals(i, undefined);
assertTrue(m instanceof WebAssembly.Module);
// Now block async compile works.
%SetWasmCompileControls(buffer.byteLength - 1, false);
WebAssembly.compile(buffer)
.then(ex = null,
e => ex = e);
%RunMicrotasks();
assertTrue(ex instanceof RangeError);
WebAssembly.instantiate(buffer)
.then(ex = null,
e => ex = e);
%RunMicrotasks();
assertTrue(ex instanceof RangeError);
// Verify that, for asm-wasm, these controls are ignored.
%SetWasmCompileControls(0, false);
function assertValidAsm(func) {
assertTrue(%IsAsmWasmCode(func));
}
function assertWasm(expected, func) {
assertEquals(
expected, func(undefined, undefined, new ArrayBuffer(1024)).caller());
assertValidAsm(func);
}
function TestAsmWasmIsUnaffected() {
"use asm";
function caller() {
return 43;
}
return {caller: caller};
}
assertWasm(43, TestAsmWasmIsUnaffected);
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