Commit 99743ad4 authored by mtrofin's avatar mtrofin Committed by Commit bot

[wasm] Transferrable modules

We want to restrict structured cloning in Chrome to:
- postMessage senders and receivers that are co-located
in the same process
- indexedDB (just https).

For context, on the Chrome side, we will achieve the postMessage part
by using a mechanism similar to transferrables: the
SerializedScriptValue will have a list of wasm modules, separate from
the serialized data stream; and this list won't be copied cross
process boundaries. The IDB part is achieved by explicitly opting in
reading/writing to the serialization stream. To block attack vectors
in IPC cases, the default for deserialization will be to expect data
in the wasm transfers list.

This change is the V8 side necessary to enabling this design. We
introduce TransferrableModule, an opaque datatype exposed to the
embedder. Internally, TransferrableModules are just serialized data,
because we don't have a better mechanism, at the moment, for
de-contextualizing/re-contextualizing wasm modules (wrt Isolate and
Context).

The chrome defaults will be implemented in the
serialization/deserialization delegates on that side. For the v8 side
of things, in the absence of a serialization delegate, the V8
serializer will write to serialization stream. In the absence of a
deserialization delegate, the deserializer won't work. This asymmetry
is intentional - it communicates to the embedder the need to make a
policy decision, otherwise wasm serialization/deserialization won't
work "out of the box".

BUG=v8:6079

Review-Url: https://codereview.chromium.org/2748473004
Cr-Commit-Position: refs/heads/master@{#43955}
parent b1841eec
......@@ -108,6 +108,7 @@ class Private;
class Uint32;
class Utils;
class Value;
class WasmCompiledModule;
template <class T> class Local;
template <class T>
class MaybeLocal;
......@@ -1709,6 +1710,8 @@ class V8_EXPORT ValueSerializer {
virtual Maybe<uint32_t> GetSharedArrayBufferId(
Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer);
virtual Maybe<uint32_t> GetWasmModuleTransferId(
Isolate* isolate, Local<WasmCompiledModule> module);
/*
* Allocates memory for the buffer of at least the size provided. The actual
* size (which may be greater or equal) is written to |actual_size|. If no
......@@ -1819,6 +1822,13 @@ class V8_EXPORT ValueDeserializer {
* MaybeLocal<Object>() returned.
*/
virtual MaybeLocal<Object> ReadHostObject(Isolate* isolate);
/*
* Get a WasmCompiledModule given a transfer_id previously provided
* by ValueSerializer::GetWasmModuleTransferId
*/
virtual MaybeLocal<WasmCompiledModule> GetWasmModuleFromId(
Isolate* isolate, uint32_t transfer_id);
};
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size);
......@@ -1861,6 +1871,11 @@ class V8_EXPORT ValueDeserializer {
*/
void SetSupportsLegacyWireFormat(bool supports_legacy_wire_format);
/*
* Expect inline wasm in the data stream (rather than in-memory transfer)
*/
void SetExpectInlineWasm(bool allow_inline_wasm);
/*
* Reads the underlying wire format version. Likely mostly to be useful to
* legacy code reading old wire format versions. Must be called after
......@@ -3903,6 +3918,37 @@ class V8_EXPORT WasmCompiledModule : public Object {
typedef std::pair<std::unique_ptr<const uint8_t[]>, size_t> SerializedModule;
// A buffer that is owned by the caller.
typedef std::pair<const uint8_t*, size_t> CallerOwnedBuffer;
// An opaque, native heap object for transferring wasm modules. It
// supports move semantics, and does not support copy semantics.
class TransferrableModule final {
public:
TransferrableModule(TransferrableModule&& src) = default;
TransferrableModule(const TransferrableModule& src) = delete;
TransferrableModule& operator=(TransferrableModule&& src) = default;
TransferrableModule& operator=(const TransferrableModule& src) = delete;
private:
typedef std::pair<std::unique_ptr<const uint8_t[]>, size_t> OwnedBuffer;
friend class WasmCompiledModule;
TransferrableModule(OwnedBuffer&& code, OwnedBuffer&& bytes)
: compiled_code(std::move(code)), wire_bytes(std::move(bytes)) {}
OwnedBuffer compiled_code = {nullptr, 0};
OwnedBuffer wire_bytes = {nullptr, 0};
};
// Get an in-memory, non-persistable, and context-independent (meaning,
// suitable for transfer to another Isolate and Context) representation
// of this wasm compiled module.
TransferrableModule GetTransferrableModule();
// Efficiently re-create a WasmCompiledModule, without recompiling, from
// a TransferrableModule.
static MaybeLocal<WasmCompiledModule> FromTransferrableModule(
Isolate* isolate, const TransferrableModule&);
// Get the wasm-encoded bytes that were used to compile this module.
Local<String> GetWasmWireBytes();
......@@ -3924,6 +3970,11 @@ class V8_EXPORT WasmCompiledModule : public Object {
static MaybeLocal<WasmCompiledModule> Compile(Isolate* isolate,
const uint8_t* start,
size_t length);
static CallerOwnedBuffer AsCallerOwned(
const TransferrableModule::OwnedBuffer& buff) {
return {buff.first.get(), buff.second};
}
WasmCompiledModule();
static void CheckCast(Value* obj);
};
......
......@@ -3125,6 +3125,11 @@ Maybe<uint32_t> ValueSerializer::Delegate::GetSharedArrayBufferId(
return Nothing<uint32_t>();
}
Maybe<uint32_t> ValueSerializer::Delegate::GetWasmModuleTransferId(
Isolate* v8_isolate, Local<WasmCompiledModule> module) {
return Nothing<uint32_t>();
}
void* ValueSerializer::Delegate::ReallocateBufferMemory(void* old_buffer,
size_t size,
size_t* actual_size) {
......@@ -3213,6 +3218,15 @@ MaybeLocal<Object> ValueDeserializer::Delegate::ReadHostObject(
return MaybeLocal<Object>();
}
MaybeLocal<WasmCompiledModule> ValueDeserializer::Delegate::GetWasmModuleFromId(
Isolate* v8_isolate, uint32_t id) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
isolate->ScheduleThrow(*isolate->factory()->NewError(
isolate->error_function(),
i::MessageTemplate::kDataCloneDeserializationError));
return MaybeLocal<WasmCompiledModule>();
}
struct ValueDeserializer::PrivateData {
PrivateData(i::Isolate* i, i::Vector<const uint8_t> data, Delegate* delegate)
: isolate(i), deserializer(i, data, delegate) {}
......@@ -3275,6 +3289,10 @@ void ValueDeserializer::SetSupportsLegacyWireFormat(
private_->supports_legacy_wire_format = supports_legacy_wire_format;
}
void ValueDeserializer::SetExpectInlineWasm(bool expect_inline_wasm) {
private_->deserializer.set_expect_inline_wasm(expect_inline_wasm);
}
uint32_t ValueDeserializer::GetWireFormatVersion() const {
CHECK(!private_->has_aborted);
return private_->deserializer.GetWireFormatVersion();
......@@ -7506,6 +7524,36 @@ Local<String> WasmCompiledModule::GetWasmWireBytes() {
return Local<String>::Cast(Utils::ToLocal(wire_bytes));
}
// Currently, wasm modules are bound, both to Isolate and to
// the Context they were created in. The currently-supported means to
// decontextualize and then re-contextualize a module is via
// serialization/deserialization.
WasmCompiledModule::TransferrableModule
WasmCompiledModule::GetTransferrableModule() {
i::DisallowHeapAllocation no_gc;
WasmCompiledModule::SerializedModule compiled_part = Serialize();
Local<String> wire_bytes = GetWasmWireBytes();
size_t wire_size = static_cast<size_t>(wire_bytes->Length());
uint8_t* bytes = new uint8_t[wire_size];
wire_bytes->WriteOneByte(bytes, 0, wire_bytes->Length());
return TransferrableModule(
std::move(compiled_part),
std::make_pair(
std::unique_ptr<const uint8_t[]>(const_cast<const uint8_t*>(bytes)),
wire_size));
}
MaybeLocal<WasmCompiledModule> WasmCompiledModule::FromTransferrableModule(
Isolate* isolate,
const WasmCompiledModule::TransferrableModule& transferrable_module) {
MaybeLocal<WasmCompiledModule> ret =
Deserialize(isolate, AsCallerOwned(transferrable_module.compiled_code),
AsCallerOwned(transferrable_module.wire_bytes));
return ret;
}
WasmCompiledModule::SerializedModule WasmCompiledModule::Serialize() {
i::Handle<i::JSObject> obj =
i::Handle<i::JSObject>::cast(Utils::OpenHandle(this));
......
......@@ -126,6 +126,8 @@ enum class SerializationTag : uint8_t {
// wasmWireByteLength:uint32_t, then raw data
// compiledDataLength:uint32_t, then raw data
kWasmModule = 'W',
// A wasm module object transfer. next value is its index.
kWasmModuleTransfer = 'w',
// The delegate is responsible for processing all following data.
// This "escapes" to whatever wire format the delegate chooses.
kHostObject = '\\',
......@@ -803,6 +805,19 @@ Maybe<bool> ValueSerializer::WriteJSArrayBufferView(JSArrayBufferView* view) {
}
Maybe<bool> ValueSerializer::WriteWasmModule(Handle<JSObject> object) {
if (delegate_ != nullptr) {
Maybe<uint32_t> transfer_id = delegate_->GetWasmModuleTransferId(
reinterpret_cast<v8::Isolate*>(isolate_),
v8::Local<v8::WasmCompiledModule>::Cast(Utils::ToLocal(object)));
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
uint32_t id = 0;
if (transfer_id.To(&id)) {
WriteTag(SerializationTag::kWasmModuleTransfer);
WriteVarint<uint32_t>(id);
return Just(true);
}
}
Handle<WasmCompiledModule> compiled_part(
WasmCompiledModule::cast(object->GetEmbedderField(0)), isolate_);
WasmEncodingTag encoding_tag = WasmEncodingTag::kRawBytes;
......@@ -1150,6 +1165,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
}
case SerializationTag::kWasmModule:
return ReadWasmModule();
case SerializationTag::kWasmModuleTransfer:
return ReadWasmModuleTransfer();
case SerializationTag::kHostObject:
return ReadHostObject();
default:
......@@ -1595,8 +1612,32 @@ MaybeHandle<JSArrayBufferView> ValueDeserializer::ReadJSArrayBufferView(
return typed_array;
}
MaybeHandle<JSObject> ValueDeserializer::ReadWasmModuleTransfer() {
if (FLAG_wasm_disable_structured_cloning || expect_inline_wasm()) {
return MaybeHandle<JSObject>();
}
uint32_t transfer_id = 0;
Local<Value> module_value;
if (!ReadVarint<uint32_t>().To(&transfer_id) || delegate_ == nullptr ||
!delegate_
->GetWasmModuleFromId(reinterpret_cast<v8::Isolate*>(isolate_),
transfer_id)
.ToLocal(&module_value)) {
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject);
return MaybeHandle<JSObject>();
}
uint32_t id = next_id_++;
Handle<JSObject> module =
Handle<JSObject>::cast(Utils::OpenHandle(*module_value));
AddObjectWithID(id, module);
return module;
}
MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
if (FLAG_wasm_disable_structured_cloning) return MaybeHandle<JSObject>();
if (FLAG_wasm_disable_structured_cloning || !expect_inline_wasm()) {
return MaybeHandle<JSObject>();
}
Vector<const uint8_t> encoding_tag;
if (!ReadRawBytes(sizeof(WasmEncodingTag)).To(&encoding_tag) ||
......@@ -1625,21 +1666,22 @@ MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
// Try to deserialize the compiled module first.
ScriptData script_data(compiled_bytes.start(), compiled_bytes.length());
Handle<FixedArray> compiled_part;
MaybeHandle<JSObject> result;
if (WasmCompiledModuleSerializer::DeserializeWasmModule(
isolate_, &script_data, wire_bytes)
.ToHandle(&compiled_part)) {
return WasmModuleObject::New(
result = WasmModuleObject::New(
isolate_, Handle<WasmCompiledModule>::cast(compiled_part));
}
// If that fails, recompile.
MaybeHandle<JSObject> result;
{
} else {
wasm::ErrorThrower thrower(isolate_, "ValueDeserializer::ReadWasmModule");
result = wasm::SyncCompile(isolate_, &thrower,
wasm::ModuleWireBytes(wire_bytes));
}
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject);
uint32_t id = next_id_++;
if (!result.is_null()) {
AddObjectWithID(id, result.ToHandleChecked());
}
return result;
}
......
......@@ -31,6 +31,7 @@ class JSValue;
class Object;
class Oddball;
class Smi;
class WasmModuleObject;
enum class SerializationTag : uint8_t;
......@@ -218,6 +219,9 @@ class ValueDeserializer {
bool ReadUint64(uint64_t* value) WARN_UNUSED_RESULT;
bool ReadDouble(double* value) WARN_UNUSED_RESULT;
bool ReadRawBytes(size_t length, const void** data) WARN_UNUSED_RESULT;
void set_expect_inline_wasm(bool expect_inline_wasm) {
expect_inline_wasm_ = expect_inline_wasm;
}
private:
// Reading the wire format.
......@@ -230,6 +234,7 @@ class ValueDeserializer {
Maybe<T> ReadZigZag() WARN_UNUSED_RESULT;
Maybe<double> ReadDouble() WARN_UNUSED_RESULT;
Maybe<Vector<const uint8_t>> ReadRawBytes(int size) WARN_UNUSED_RESULT;
bool expect_inline_wasm() const { return expect_inline_wasm_; }
// Reads a string if it matches the one provided.
// Returns true if this was the case. Otherwise, nothing is consumed.
......@@ -263,6 +268,7 @@ class ValueDeserializer {
MaybeHandle<JSArrayBufferView> ReadJSArrayBufferView(
Handle<JSArrayBuffer> buffer) WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadWasmModule() WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadWasmModuleTransfer() WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadHostObject() WARN_UNUSED_RESULT;
/*
......@@ -285,6 +291,7 @@ class ValueDeserializer {
PretenureFlag pretenure_;
uint32_t version_ = 0;
uint32_t next_id_ = 0;
bool expect_inline_wasm_ = false;
// Always global handles.
Handle<FixedArray> id_map_;
......
......@@ -469,6 +469,43 @@ TEST(BlockWasmCodeGenAtDeserialization) {
Cleanup();
}
TEST(TransferrableWasmModules) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
ZoneBuffer buffer(&zone);
WasmSerializationTest::BuildWireBytes(&zone, &buffer);
Isolate* from_isolate = CcTest::InitIsolateOnce();
ErrorThrower thrower(from_isolate, "");
std::vector<v8::WasmCompiledModule::TransferrableModule> store;
{
HandleScope scope(from_isolate);
testing::SetupIsolateForWasmModule(from_isolate);
MaybeHandle<WasmModuleObject> module_object = SyncCompile(
from_isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()));
v8::Local<v8::WasmCompiledModule> v8_module =
v8::Local<v8::WasmCompiledModule>::Cast(v8::Utils::ToLocal(
Handle<JSObject>::cast(module_object.ToHandleChecked())));
store.push_back(v8_module->GetTransferrableModule());
}
{
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
from_isolate->array_buffer_allocator();
v8::Isolate* to_isolate = v8::Isolate::New(create_params);
v8::HandleScope new_scope(to_isolate);
v8::Local<v8::Context> deserialization_context =
v8::Context::New(to_isolate);
deserialization_context->Enter();
v8::MaybeLocal<v8::WasmCompiledModule> mod =
v8::WasmCompiledModule::FromTransferrableModule(to_isolate, store[0]);
CHECK(!mod.IsEmpty());
}
}
TEST(MemorySize) {
{
// Initial memory size is 16, see wasm-module-builder.cc
......
This diff is collapsed.
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