Commit d9bc0ffb 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-Commit-Position: refs/heads/master@{#43295}
parent 18ad0f13
......@@ -5947,6 +5947,18 @@ 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)(Local<Value> source, bool as_promise);
typedef bool (*AllowWasmInstantiateCallback)(Local<WasmCompiledModule> module,
Local<Value> ffi, bool as_promise);
// --- Garbage Collection Callbacks ---
/**
......@@ -7200,6 +7212,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.
......
......@@ -8707,6 +8707,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,27 @@
#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;
} g_WasmCompileControls;
bool IsWasmCompileAllowed(v8::Local<v8::Value> value, bool is_async) {
return (is_async && g_WasmCompileControls.AllowAnySizeForAsync) ||
(v8::Local<v8::ArrayBuffer>::Cast(value)->ByteLength() <=
g_WasmCompileControls.MaxWasmBufferSize);
}
// Use the compile controls for instantiation, too
bool IsWasmInstantiateAllowed(v8::Local<v8::WasmCompiledModule> module,
v8::Local<v8::Value> ffi, bool is_async) {
return (is_async && g_WasmCompileControls.AllowAnySizeForAsync) ||
(static_cast<uint32_t>(module->GetWasmWireBytes()->Length()) <=
g_WasmCompileControls.MaxWasmBufferSize);
}
} // namespace
namespace v8 {
namespace internal {
......@@ -429,6 +450,25 @@ RUNTIME_FUNCTION(Runtime_CheckWasmWrapperElision) {
return isolate->heap()->ToBoolean(count == 1);
}
RUNTIME_FUNCTION(Runtime_SetWasmCompileControls) {
HandleScope scope(isolate);
CHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(Smi, block_size, 0);
CONVERT_BOOLEAN_ARG_CHECKED(allow_async, 1);
g_WasmCompileControls.AllowAnySizeForAsync = allow_async;
g_WasmCompileControls.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());
......
......@@ -609,6 +609,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) \
......
......@@ -79,9 +79,18 @@ RawBuffer GetRawBufferSource(
static i::MaybeHandle<i::WasmModuleObject> CreateModuleObject(
v8::Isolate* isolate, const v8::Local<v8::Value> source,
ErrorThrower* thrower) {
ErrorThrower* thrower, bool async) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i::MaybeHandle<i::JSObject> nothing;
i::MaybeHandle<i::WasmModuleObject> nothing;
AllowWasmCompileCallback callback =
reinterpret_cast<i::Isolate*>(isolate)->allow_wasm_compile_callback();
if (callback != nullptr && !callback(source, async)) {
thrower->RangeError(
"Wasm compilation exceeds internal limits in this context for provided "
"arguments");
return nothing;
}
RawBuffer buffer = GetRawBufferSource(source, thrower);
if (buffer.start == nullptr) return i::MaybeHandle<i::WasmModuleObject>();
......@@ -139,7 +148,7 @@ void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) {
return;
}
i::MaybeHandle<i::JSObject> module_obj =
CreateModuleObject(isolate, args[0], &thrower);
CreateModuleObject(isolate, args[0], &thrower, true);
if (thrower.error()) {
resolver->Reject(context, Utils::ToLocal(thrower.Reify()));
......@@ -180,7 +189,7 @@ void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
i::MaybeHandle<i::JSObject> module_obj =
CreateModuleObject(isolate, args[0], &thrower);
CreateModuleObject(isolate, args[0], &thrower, false);
if (module_obj.is_null()) return;
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
......@@ -189,7 +198,8 @@ void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) {
MaybeLocal<Value> InstantiateModuleImpl(
i::Isolate* i_isolate, i::Handle<i::WasmModuleObject> i_module_obj,
const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower) {
const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower,
bool as_promise) {
// It so happens that in both the WebAssembly.instantiate, as well as
// WebAssembly.Instance ctor, the positions of the ffi object and memory
// are the same. If that changes later, we refactor the consts into
......@@ -210,6 +220,17 @@ MaybeLocal<Value> InstantiateModuleImpl(
Local<Object> obj = Local<Object>::Cast(args[kFfiOffset]);
ffi = i::Handle<i::JSReceiver>::cast(v8::Utils::OpenHandle(*obj));
}
AllowWasmInstantiateCallback allow_instantiate =
i_isolate->allow_wasm_instantiate_callback();
if (allow_instantiate != nullptr &&
!allow_instantiate(Local<WasmCompiledModule>::Cast(Utils::ToLocal(
i::Handle<i::JSObject>::cast(i_module_obj))),
Utils::ToLocal(ffi), as_promise)) {
thrower->RangeError(
"Wasm instantiation exceeds internal limits in this context for "
"provided arguments");
return nothing;
}
i::MaybeHandle<i::JSObject> instance =
i::wasm::WasmModule::Instantiate(i_isolate, thrower, i_module_obj, ffi);
......@@ -316,7 +337,7 @@ void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) {
if (!maybe_module.is_null()) {
MaybeLocal<Value> instance = InstantiateModuleImpl(
i_isolate, maybe_module.ToHandleChecked(), args, &thrower);
i_isolate, maybe_module.ToHandleChecked(), args, &thrower, false);
if (instance.IsEmpty()) {
DCHECK(thrower.error());
......@@ -361,7 +382,7 @@ void WebAssemblyInstantiate(const v8::FunctionCallbackInfo<v8::Value>& args) {
i::Handle<i::WasmModuleObject> module_obj;
if (want_pair) {
i::MaybeHandle<i::WasmModuleObject> maybe_module_obj =
CreateModuleObject(isolate, args[0], &thrower);
CreateModuleObject(isolate, args[0], &thrower, true);
if (!maybe_module_obj.ToHandle(&module_obj)) {
DCHECK(thrower.error());
resolver->Reject(context, Utils::ToLocal(thrower.Reify()));
......@@ -372,7 +393,7 @@ void WebAssemblyInstantiate(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
DCHECK(!module_obj.is_null());
MaybeLocal<Value> instance =
InstantiateModuleImpl(i_isolate, module_obj, args, &thrower);
InstantiateModuleImpl(i_isolate, module_obj, args, &thrower, true);
if (instance.IsEmpty()) {
DCHECK(thrower.error());
resolver->Reject(context, Utils::ToLocal(thrower.Reify()));
......
// 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);
// not even async compile works.
var ex;
%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);
// 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