Skip to content

Commit 99743ad

Browse files
mtrofinCommit bot
authored andcommitted
[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}
1 parent b1841ee commit 99743ad

6 files changed

Lines changed: 464 additions & 24 deletions

File tree

include/v8.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class Private;
108108
class Uint32;
109109
class Utils;
110110
class Value;
111+
class WasmCompiledModule;
111112
template <class T> class Local;
112113
template <class T>
113114
class MaybeLocal;
@@ -1709,6 +1710,8 @@ class V8_EXPORT ValueSerializer {
17091710
virtual Maybe<uint32_t> GetSharedArrayBufferId(
17101711
Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer);
17111712

1713+
virtual Maybe<uint32_t> GetWasmModuleTransferId(
1714+
Isolate* isolate, Local<WasmCompiledModule> module);
17121715
/*
17131716
* Allocates memory for the buffer of at least the size provided. The actual
17141717
* size (which may be greater or equal) is written to |actual_size|. If no
@@ -1819,6 +1822,13 @@ class V8_EXPORT ValueDeserializer {
18191822
* MaybeLocal<Object>() returned.
18201823
*/
18211824
virtual MaybeLocal<Object> ReadHostObject(Isolate* isolate);
1825+
1826+
/*
1827+
* Get a WasmCompiledModule given a transfer_id previously provided
1828+
* by ValueSerializer::GetWasmModuleTransferId
1829+
*/
1830+
virtual MaybeLocal<WasmCompiledModule> GetWasmModuleFromId(
1831+
Isolate* isolate, uint32_t transfer_id);
18221832
};
18231833

18241834
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size);
@@ -1861,6 +1871,11 @@ class V8_EXPORT ValueDeserializer {
18611871
*/
18621872
void SetSupportsLegacyWireFormat(bool supports_legacy_wire_format);
18631873

1874+
/*
1875+
* Expect inline wasm in the data stream (rather than in-memory transfer)
1876+
*/
1877+
void SetExpectInlineWasm(bool allow_inline_wasm);
1878+
18641879
/*
18651880
* Reads the underlying wire format version. Likely mostly to be useful to
18661881
* legacy code reading old wire format versions. Must be called after
@@ -3903,6 +3918,37 @@ class V8_EXPORT WasmCompiledModule : public Object {
39033918
typedef std::pair<std::unique_ptr<const uint8_t[]>, size_t> SerializedModule;
39043919
// A buffer that is owned by the caller.
39053920
typedef std::pair<const uint8_t*, size_t> CallerOwnedBuffer;
3921+
3922+
// An opaque, native heap object for transferring wasm modules. It
3923+
// supports move semantics, and does not support copy semantics.
3924+
class TransferrableModule final {
3925+
public:
3926+
TransferrableModule(TransferrableModule&& src) = default;
3927+
TransferrableModule(const TransferrableModule& src) = delete;
3928+
3929+
TransferrableModule& operator=(TransferrableModule&& src) = default;
3930+
TransferrableModule& operator=(const TransferrableModule& src) = delete;
3931+
3932+
private:
3933+
typedef std::pair<std::unique_ptr<const uint8_t[]>, size_t> OwnedBuffer;
3934+
friend class WasmCompiledModule;
3935+
TransferrableModule(OwnedBuffer&& code, OwnedBuffer&& bytes)
3936+
: compiled_code(std::move(code)), wire_bytes(std::move(bytes)) {}
3937+
3938+
OwnedBuffer compiled_code = {nullptr, 0};
3939+
OwnedBuffer wire_bytes = {nullptr, 0};
3940+
};
3941+
3942+
// Get an in-memory, non-persistable, and context-independent (meaning,
3943+
// suitable for transfer to another Isolate and Context) representation
3944+
// of this wasm compiled module.
3945+
TransferrableModule GetTransferrableModule();
3946+
3947+
// Efficiently re-create a WasmCompiledModule, without recompiling, from
3948+
// a TransferrableModule.
3949+
static MaybeLocal<WasmCompiledModule> FromTransferrableModule(
3950+
Isolate* isolate, const TransferrableModule&);
3951+
39063952
// Get the wasm-encoded bytes that were used to compile this module.
39073953
Local<String> GetWasmWireBytes();
39083954

@@ -3924,6 +3970,11 @@ class V8_EXPORT WasmCompiledModule : public Object {
39243970
static MaybeLocal<WasmCompiledModule> Compile(Isolate* isolate,
39253971
const uint8_t* start,
39263972
size_t length);
3973+
static CallerOwnedBuffer AsCallerOwned(
3974+
const TransferrableModule::OwnedBuffer& buff) {
3975+
return {buff.first.get(), buff.second};
3976+
}
3977+
39273978
WasmCompiledModule();
39283979
static void CheckCast(Value* obj);
39293980
};

src/api.cc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,6 +3125,11 @@ Maybe<uint32_t> ValueSerializer::Delegate::GetSharedArrayBufferId(
31253125
return Nothing<uint32_t>();
31263126
}
31273127

3128+
Maybe<uint32_t> ValueSerializer::Delegate::GetWasmModuleTransferId(
3129+
Isolate* v8_isolate, Local<WasmCompiledModule> module) {
3130+
return Nothing<uint32_t>();
3131+
}
3132+
31283133
void* ValueSerializer::Delegate::ReallocateBufferMemory(void* old_buffer,
31293134
size_t size,
31303135
size_t* actual_size) {
@@ -3213,6 +3218,15 @@ MaybeLocal<Object> ValueDeserializer::Delegate::ReadHostObject(
32133218
return MaybeLocal<Object>();
32143219
}
32153220

3221+
MaybeLocal<WasmCompiledModule> ValueDeserializer::Delegate::GetWasmModuleFromId(
3222+
Isolate* v8_isolate, uint32_t id) {
3223+
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
3224+
isolate->ScheduleThrow(*isolate->factory()->NewError(
3225+
isolate->error_function(),
3226+
i::MessageTemplate::kDataCloneDeserializationError));
3227+
return MaybeLocal<WasmCompiledModule>();
3228+
}
3229+
32163230
struct ValueDeserializer::PrivateData {
32173231
PrivateData(i::Isolate* i, i::Vector<const uint8_t> data, Delegate* delegate)
32183232
: isolate(i), deserializer(i, data, delegate) {}
@@ -3275,6 +3289,10 @@ void ValueDeserializer::SetSupportsLegacyWireFormat(
32753289
private_->supports_legacy_wire_format = supports_legacy_wire_format;
32763290
}
32773291

3292+
void ValueDeserializer::SetExpectInlineWasm(bool expect_inline_wasm) {
3293+
private_->deserializer.set_expect_inline_wasm(expect_inline_wasm);
3294+
}
3295+
32783296
uint32_t ValueDeserializer::GetWireFormatVersion() const {
32793297
CHECK(!private_->has_aborted);
32803298
return private_->deserializer.GetWireFormatVersion();
@@ -7506,6 +7524,36 @@ Local<String> WasmCompiledModule::GetWasmWireBytes() {
75067524
return Local<String>::Cast(Utils::ToLocal(wire_bytes));
75077525
}
75087526

7527+
// Currently, wasm modules are bound, both to Isolate and to
7528+
// the Context they were created in. The currently-supported means to
7529+
// decontextualize and then re-contextualize a module is via
7530+
// serialization/deserialization.
7531+
WasmCompiledModule::TransferrableModule
7532+
WasmCompiledModule::GetTransferrableModule() {
7533+
i::DisallowHeapAllocation no_gc;
7534+
WasmCompiledModule::SerializedModule compiled_part = Serialize();
7535+
7536+
Local<String> wire_bytes = GetWasmWireBytes();
7537+
size_t wire_size = static_cast<size_t>(wire_bytes->Length());
7538+
uint8_t* bytes = new uint8_t[wire_size];
7539+
wire_bytes->WriteOneByte(bytes, 0, wire_bytes->Length());
7540+
7541+
return TransferrableModule(
7542+
std::move(compiled_part),
7543+
std::make_pair(
7544+
std::unique_ptr<const uint8_t[]>(const_cast<const uint8_t*>(bytes)),
7545+
wire_size));
7546+
}
7547+
7548+
MaybeLocal<WasmCompiledModule> WasmCompiledModule::FromTransferrableModule(
7549+
Isolate* isolate,
7550+
const WasmCompiledModule::TransferrableModule& transferrable_module) {
7551+
MaybeLocal<WasmCompiledModule> ret =
7552+
Deserialize(isolate, AsCallerOwned(transferrable_module.compiled_code),
7553+
AsCallerOwned(transferrable_module.wire_bytes));
7554+
return ret;
7555+
}
7556+
75097557
WasmCompiledModule::SerializedModule WasmCompiledModule::Serialize() {
75107558
i::Handle<i::JSObject> obj =
75117559
i::Handle<i::JSObject>::cast(Utils::OpenHandle(this));

src/value-serializer.cc

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ enum class SerializationTag : uint8_t {
126126
// wasmWireByteLength:uint32_t, then raw data
127127
// compiledDataLength:uint32_t, then raw data
128128
kWasmModule = 'W',
129+
// A wasm module object transfer. next value is its index.
130+
kWasmModuleTransfer = 'w',
129131
// The delegate is responsible for processing all following data.
130132
// This "escapes" to whatever wire format the delegate chooses.
131133
kHostObject = '\\',
@@ -803,6 +805,19 @@ Maybe<bool> ValueSerializer::WriteJSArrayBufferView(JSArrayBufferView* view) {
803805
}
804806

805807
Maybe<bool> ValueSerializer::WriteWasmModule(Handle<JSObject> object) {
808+
if (delegate_ != nullptr) {
809+
Maybe<uint32_t> transfer_id = delegate_->GetWasmModuleTransferId(
810+
reinterpret_cast<v8::Isolate*>(isolate_),
811+
v8::Local<v8::WasmCompiledModule>::Cast(Utils::ToLocal(object)));
812+
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
813+
uint32_t id = 0;
814+
if (transfer_id.To(&id)) {
815+
WriteTag(SerializationTag::kWasmModuleTransfer);
816+
WriteVarint<uint32_t>(id);
817+
return Just(true);
818+
}
819+
}
820+
806821
Handle<WasmCompiledModule> compiled_part(
807822
WasmCompiledModule::cast(object->GetEmbedderField(0)), isolate_);
808823
WasmEncodingTag encoding_tag = WasmEncodingTag::kRawBytes;
@@ -1150,6 +1165,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
11501165
}
11511166
case SerializationTag::kWasmModule:
11521167
return ReadWasmModule();
1168+
case SerializationTag::kWasmModuleTransfer:
1169+
return ReadWasmModuleTransfer();
11531170
case SerializationTag::kHostObject:
11541171
return ReadHostObject();
11551172
default:
@@ -1595,8 +1612,32 @@ MaybeHandle<JSArrayBufferView> ValueDeserializer::ReadJSArrayBufferView(
15951612
return typed_array;
15961613
}
15971614

1615+
MaybeHandle<JSObject> ValueDeserializer::ReadWasmModuleTransfer() {
1616+
if (FLAG_wasm_disable_structured_cloning || expect_inline_wasm()) {
1617+
return MaybeHandle<JSObject>();
1618+
}
1619+
1620+
uint32_t transfer_id = 0;
1621+
Local<Value> module_value;
1622+
if (!ReadVarint<uint32_t>().To(&transfer_id) || delegate_ == nullptr ||
1623+
!delegate_
1624+
->GetWasmModuleFromId(reinterpret_cast<v8::Isolate*>(isolate_),
1625+
transfer_id)
1626+
.ToLocal(&module_value)) {
1627+
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject);
1628+
return MaybeHandle<JSObject>();
1629+
}
1630+
uint32_t id = next_id_++;
1631+
Handle<JSObject> module =
1632+
Handle<JSObject>::cast(Utils::OpenHandle(*module_value));
1633+
AddObjectWithID(id, module);
1634+
return module;
1635+
}
1636+
15981637
MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
1599-
if (FLAG_wasm_disable_structured_cloning) return MaybeHandle<JSObject>();
1638+
if (FLAG_wasm_disable_structured_cloning || !expect_inline_wasm()) {
1639+
return MaybeHandle<JSObject>();
1640+
}
16001641

16011642
Vector<const uint8_t> encoding_tag;
16021643
if (!ReadRawBytes(sizeof(WasmEncodingTag)).To(&encoding_tag) ||
@@ -1625,21 +1666,22 @@ MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
16251666
// Try to deserialize the compiled module first.
16261667
ScriptData script_data(compiled_bytes.start(), compiled_bytes.length());
16271668
Handle<FixedArray> compiled_part;
1669+
MaybeHandle<JSObject> result;
16281670
if (WasmCompiledModuleSerializer::DeserializeWasmModule(
16291671
isolate_, &script_data, wire_bytes)
16301672
.ToHandle(&compiled_part)) {
1631-
return WasmModuleObject::New(
1673+
result = WasmModuleObject::New(
16321674
isolate_, Handle<WasmCompiledModule>::cast(compiled_part));
1633-
}
1634-
1635-
// If that fails, recompile.
1636-
MaybeHandle<JSObject> result;
1637-
{
1675+
} else {
16381676
wasm::ErrorThrower thrower(isolate_, "ValueDeserializer::ReadWasmModule");
16391677
result = wasm::SyncCompile(isolate_, &thrower,
16401678
wasm::ModuleWireBytes(wire_bytes));
16411679
}
16421680
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject);
1681+
uint32_t id = next_id_++;
1682+
if (!result.is_null()) {
1683+
AddObjectWithID(id, result.ToHandleChecked());
1684+
}
16431685
return result;
16441686
}
16451687

src/value-serializer.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class JSValue;
3131
class Object;
3232
class Oddball;
3333
class Smi;
34+
class WasmModuleObject;
3435

3536
enum class SerializationTag : uint8_t;
3637

@@ -218,6 +219,9 @@ class ValueDeserializer {
218219
bool ReadUint64(uint64_t* value) WARN_UNUSED_RESULT;
219220
bool ReadDouble(double* value) WARN_UNUSED_RESULT;
220221
bool ReadRawBytes(size_t length, const void** data) WARN_UNUSED_RESULT;
222+
void set_expect_inline_wasm(bool expect_inline_wasm) {
223+
expect_inline_wasm_ = expect_inline_wasm;
224+
}
221225

222226
private:
223227
// Reading the wire format.
@@ -230,6 +234,7 @@ class ValueDeserializer {
230234
Maybe<T> ReadZigZag() WARN_UNUSED_RESULT;
231235
Maybe<double> ReadDouble() WARN_UNUSED_RESULT;
232236
Maybe<Vector<const uint8_t>> ReadRawBytes(int size) WARN_UNUSED_RESULT;
237+
bool expect_inline_wasm() const { return expect_inline_wasm_; }
233238

234239
// Reads a string if it matches the one provided.
235240
// Returns true if this was the case. Otherwise, nothing is consumed.
@@ -263,6 +268,7 @@ class ValueDeserializer {
263268
MaybeHandle<JSArrayBufferView> ReadJSArrayBufferView(
264269
Handle<JSArrayBuffer> buffer) WARN_UNUSED_RESULT;
265270
MaybeHandle<JSObject> ReadWasmModule() WARN_UNUSED_RESULT;
271+
MaybeHandle<JSObject> ReadWasmModuleTransfer() WARN_UNUSED_RESULT;
266272
MaybeHandle<JSObject> ReadHostObject() WARN_UNUSED_RESULT;
267273

268274
/*
@@ -285,6 +291,7 @@ class ValueDeserializer {
285291
PretenureFlag pretenure_;
286292
uint32_t version_ = 0;
287293
uint32_t next_id_ = 0;
294+
bool expect_inline_wasm_ = false;
288295

289296
// Always global handles.
290297
Handle<FixedArray> id_map_;

test/cctest/wasm/test-run-wasm-module.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,43 @@ TEST(BlockWasmCodeGenAtDeserialization) {
469469
Cleanup();
470470
}
471471

472+
TEST(TransferrableWasmModules) {
473+
v8::internal::AccountingAllocator allocator;
474+
Zone zone(&allocator, ZONE_NAME);
475+
476+
ZoneBuffer buffer(&zone);
477+
WasmSerializationTest::BuildWireBytes(&zone, &buffer);
478+
479+
Isolate* from_isolate = CcTest::InitIsolateOnce();
480+
ErrorThrower thrower(from_isolate, "");
481+
std::vector<v8::WasmCompiledModule::TransferrableModule> store;
482+
{
483+
HandleScope scope(from_isolate);
484+
testing::SetupIsolateForWasmModule(from_isolate);
485+
486+
MaybeHandle<WasmModuleObject> module_object = SyncCompile(
487+
from_isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()));
488+
v8::Local<v8::WasmCompiledModule> v8_module =
489+
v8::Local<v8::WasmCompiledModule>::Cast(v8::Utils::ToLocal(
490+
Handle<JSObject>::cast(module_object.ToHandleChecked())));
491+
store.push_back(v8_module->GetTransferrableModule());
492+
}
493+
494+
{
495+
v8::Isolate::CreateParams create_params;
496+
create_params.array_buffer_allocator =
497+
from_isolate->array_buffer_allocator();
498+
v8::Isolate* to_isolate = v8::Isolate::New(create_params);
499+
v8::HandleScope new_scope(to_isolate);
500+
v8::Local<v8::Context> deserialization_context =
501+
v8::Context::New(to_isolate);
502+
deserialization_context->Enter();
503+
v8::MaybeLocal<v8::WasmCompiledModule> mod =
504+
v8::WasmCompiledModule::FromTransferrableModule(to_isolate, store[0]);
505+
CHECK(!mod.IsEmpty());
506+
}
507+
}
508+
472509
TEST(MemorySize) {
473510
{
474511
// Initial memory size is 16, see wasm-module-builder.cc

0 commit comments

Comments
 (0)