Commit 8d76f0e3 authored by titzer's avatar titzer Committed by Commit bot

[wasm] Enforce memory and table limits during instantiation.

R=rossberg@chromium.org
BUG=chromium:575167

Review-Url: https://codereview.chromium.org/2636173002
Cr-Commit-Position: refs/heads/master@{#42426}
parent cfa6ce32
......@@ -319,11 +319,10 @@ class ModuleDecoder : public Decoder {
}
case kExternalMemory: {
// ===== Imported memory =========================================
bool has_max = false;
consume_resizable_limits("memory", "pages", kV8MaxWasmMemoryPages,
&module->min_mem_pages, &has_max,
kSpecMaxWasmMemoryPages,
&module->max_mem_pages);
consume_resizable_limits(
"memory", "pages", kV8MaxWasmMemoryPages,
&module->min_mem_pages, &module->has_max_mem,
kSpecMaxWasmMemoryPages, &module->max_mem_pages);
SetHasMemory(module);
break;
}
......@@ -394,10 +393,10 @@ class ModuleDecoder : public Decoder {
uint32_t memory_count = consume_count("memory count", kV8MaxWasmMemories);
for (uint32_t i = 0; ok() && i < memory_count; i++) {
bool has_max = false;
consume_resizable_limits(
"memory", "pages", kV8MaxWasmMemoryPages, &module->min_mem_pages,
&has_max, kSpecMaxWasmMemoryPages, &module->max_mem_pages);
consume_resizable_limits("memory", "pages", kV8MaxWasmMemoryPages,
&module->min_mem_pages, &module->has_max_mem,
kSpecMaxWasmMemoryPages,
&module->max_mem_pages);
}
SetHasMemory(module);
section_iter.advance();
......
......@@ -354,22 +354,22 @@ void WebAssemblyInstantiate(const v8::FunctionCallbackInfo<v8::Value>& args) {
bool GetIntegerProperty(v8::Isolate* isolate, ErrorThrower* thrower,
Local<Context> context, Local<v8::Object> object,
Local<String> property, int* result, int lower_bound,
int upper_bound) {
Local<String> property, int* result,
int64_t lower_bound, uint64_t upper_bound) {
v8::MaybeLocal<v8::Value> maybe = object->Get(context, property);
v8::Local<v8::Value> value;
if (maybe.ToLocal(&value)) {
int64_t number;
if (!value->IntegerValue(context).To(&number)) return false;
if (number < static_cast<int64_t>(lower_bound)) {
if (number < lower_bound) {
thrower->RangeError("Property value %" PRId64
" is below the lower bound %d",
" is below the lower bound %" PRIx64,
number, lower_bound);
return false;
}
if (number > static_cast<int64_t>(upper_bound)) {
thrower->RangeError("Property value %" PRId64
" is above the upper bound %d",
" is above the upper bound %" PRIu64,
number, upper_bound);
return false;
}
......@@ -379,8 +379,6 @@ bool GetIntegerProperty(v8::Isolate* isolate, ErrorThrower* thrower,
return false;
}
const int max_table_size = 1 << 26;
void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
......@@ -408,28 +406,23 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
}
// The descriptor's 'initial'.
int initial;
int initial = 0;
if (!GetIntegerProperty(isolate, &thrower, context, descriptor,
v8_str(isolate, "initial"), &initial, 0,
max_table_size)) {
i::wasm::kV8MaxWasmTableSize)) {
return;
}
// The descriptor's 'maximum'.
int maximum = 0;
int maximum = -1;
Local<String> maximum_key = v8_str(isolate, "maximum");
Maybe<bool> has_maximum = descriptor->Has(context, maximum_key);
if (has_maximum.IsNothing()) {
// There has been an exception, just return.
return;
}
if (has_maximum.FromJust()) {
if (!has_maximum.IsNothing() && has_maximum.FromJust()) {
if (!GetIntegerProperty(isolate, &thrower, context, descriptor, maximum_key,
&maximum, initial, max_table_size)) {
&maximum, initial,
i::wasm::kSpecMaxWasmTableSize)) {
return;
}
} else {
maximum = static_cast<int>(i::wasm::kV8MaxWasmTableSize);
}
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
......@@ -452,23 +445,21 @@ void WebAssemblyMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = args[0]->ToObject(context).ToLocalChecked();
// The descriptor's 'initial'.
int initial;
int initial = 0;
if (!GetIntegerProperty(isolate, &thrower, context, descriptor,
v8_str(isolate, "initial"), &initial, 0, 65536)) {
v8_str(isolate, "initial"), &initial, 0,
i::wasm::kV8MaxWasmMemoryPages)) {
return;
}
// The descriptor's 'maximum'.
int maximum = 0;
int maximum = -1;
Local<String> maximum_key = v8_str(isolate, "maximum");
Maybe<bool> has_maximum = descriptor->Has(context, maximum_key);
if (has_maximum.IsNothing()) {
// There has been an exception, just return.
return;
}
if (has_maximum.FromJust()) {
if (!has_maximum.IsNothing() && has_maximum.FromJust()) {
if (!GetIntegerProperty(isolate, &thrower, context, descriptor, maximum_key,
&maximum, initial, 65536)) {
&maximum, initial,
i::wasm::kSpecMaxWasmMemoryPages)) {
return;
}
}
......@@ -481,8 +472,8 @@ void WebAssemblyMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
thrower.RangeError("could not allocate memory");
return;
}
i::Handle<i::JSObject> memory_obj = i::WasmMemoryObject::New(
i_isolate, buffer, has_maximum.FromJust() ? maximum : -1);
i::Handle<i::JSObject> memory_obj =
i::WasmMemoryObject::New(i_isolate, buffer, maximum);
args.GetReturnValue().Set(Utils::ToLocal(memory_obj));
}
......@@ -523,7 +514,13 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
new_size64 += old_size;
if (new_size64 < old_size || new_size64 > receiver->maximum_length()) {
int64_t max_size64 = receiver->maximum_length();
if (max_size64 < 0 ||
max_size64 > static_cast<int64_t>(i::wasm::kV8MaxWasmTableSize)) {
max_size64 = i::wasm::kV8MaxWasmTableSize;
}
if (new_size64 < old_size || new_size64 > max_size64) {
v8::Local<v8::Value> e = v8::Exception::RangeError(
v8_str(isolate, new_size64 < old_size ? "trying to shrink table"
: "maximum table size exceeded"));
......
......@@ -31,6 +31,7 @@ const size_t kV8MaxWasmTables = 1;
const size_t kV8MaxWasmMemories = 1;
const size_t kSpecMaxWasmMemoryPages = 65536;
const size_t kSpecMaxWasmTableSize = 0xFFFFFFFFu;
const uint64_t kWasmMaxHeapOffset =
static_cast<uint64_t>(
......
......@@ -1669,16 +1669,34 @@ class WasmInstanceBuilder {
table_instance.js_wrappers = Handle<FixedArray>(
table_instance.table_object->functions(), isolate_);
// TODO(titzer): import table size must match exactly for now.
int table_size = table_instance.js_wrappers->length();
if (table_size != static_cast<int>(table.min_size)) {
int imported_cur_size = table_instance.js_wrappers->length();
if (imported_cur_size < static_cast<int>(table.min_size)) {
thrower_->LinkError(
"table import %d is wrong size (%d), expected %u", index,
table_size, table.min_size);
"table import %d is smaller than minimum %d, got %u", index,
table.min_size, imported_cur_size);
return -1;
}
if (table.has_max) {
int64_t imported_max_size =
table_instance.table_object->maximum_length();
if (imported_max_size < 0) {
thrower_->LinkError(
"table import %d has no maximum length, expected %d", index,
table.max_size);
return -1;
}
if (imported_max_size > table.max_size) {
thrower_->LinkError(
"table import %d has maximum larger than maximum %d, "
"got %" PRIx64,
index, table.max_size, imported_max_size);
return -1;
}
}
// Allocate a new dispatch table and signature table.
int table_size = imported_cur_size;
table_instance.function_table =
isolate_->factory()->NewFixedArray(table_size);
table_instance.signature_table =
......@@ -1720,6 +1738,29 @@ class WasmInstanceBuilder {
DCHECK(WasmJs::IsWasmMemoryObject(isolate_, memory));
instance->set_memory_object(*memory);
memory_ = Handle<JSArrayBuffer>(memory->buffer(), isolate_);
uint32_t imported_cur_pages = static_cast<uint32_t>(
memory_->byte_length()->Number() / WasmModule::kPageSize);
if (imported_cur_pages < module_->min_mem_pages) {
thrower_->LinkError(
"memory import %d is smaller than maximum %u, got %u", index,
module_->min_mem_pages, imported_cur_pages);
}
int32_t imported_max_pages = memory->maximum_pages();
if (module_->has_max_mem) {
if (imported_max_pages < 0) {
thrower_->LinkError(
"memory import %d has no maximum limit, expected at most %u",
index, imported_max_pages);
return -1;
}
if (static_cast<uint32_t>(imported_max_pages) >
module_->max_mem_pages) {
thrower_->LinkError(
"memory import %d has larger maximum than maximum %u, got %d",
index, module_->max_mem_pages, imported_max_pages);
return -1;
}
}
break;
}
case kExternalGlobal: {
......@@ -2212,13 +2253,14 @@ int32_t wasm::GetInstanceMemorySize(Isolate* isolate,
}
}
uint32_t GetMaxInstanceMemorySize(Isolate* isolate,
Handle<WasmInstanceObject> instance) {
uint32_t GetMaxInstanceMemoryPages(Isolate* isolate,
Handle<WasmInstanceObject> instance) {
if (instance->has_memory_object()) {
Handle<WasmMemoryObject> memory_object(instance->memory_object(), isolate);
int maximum = memory_object->maximum_pages();
if (maximum > 0) return static_cast<uint32_t>(maximum);
if (memory_object->has_maximum_pages()) {
uint32_t maximum = static_cast<uint32_t>(memory_object->maximum_pages());
if (maximum < kV8MaxWasmMemoryPages) return maximum;
}
}
uint32_t compiled_max_pages = instance->compiled_module()->max_mem_pages();
isolate->counters()->wasm_max_mem_pages_count()->AddSample(
......@@ -2294,7 +2336,7 @@ int32_t wasm::GrowWebAssemblyMemory(Isolate* isolate, Handle<Object> receiver,
Handle<WasmInstanceObject> instance = instance_wrapper->instance_object();
DCHECK(IsWasmInstance(*instance));
if (pages == 0) return GetInstanceMemorySize(isolate, instance);
uint32_t max_pages = GetMaxInstanceMemorySize(isolate, instance);
uint32_t max_pages = GetMaxInstanceMemoryPages(isolate, instance);
// Grow memory object buffer and update instances associated with it.
MaybeHandle<JSArrayBuffer> memory_buffer = handle(memory_object->buffer());
......@@ -2342,7 +2384,7 @@ int32_t wasm::GrowMemory(Isolate* isolate, Handle<WasmInstanceObject> instance,
old_size = old_buffer->byte_length()->Number();
old_mem_start = static_cast<Address>(old_buffer->backing_store());
}
uint32_t max_pages = GetMaxInstanceMemorySize(isolate, instance_obj);
uint32_t max_pages = GetMaxInstanceMemoryPages(isolate, instance_obj);
Handle<JSArrayBuffer> buffer =
GrowMemoryBuffer(isolate, instance_buffer, pages, max_pages);
if (buffer.is_null()) return -1;
......
......@@ -179,6 +179,7 @@ struct V8_EXPORT_PRIVATE WasmModule {
Zone* owned_zone;
uint32_t min_mem_pages = 0; // minimum size of the memory in 64k pages
uint32_t max_mem_pages = 0; // maximum size of the memory in 64k pages
bool has_max_mem = false; // try if a maximum memory size exists
bool has_memory = false; // true if the memory was defined or imported
bool mem_export = false; // true if the memory is exported
// TODO(wasm): reconcile start function index being an int with
......
......@@ -122,7 +122,7 @@ DEFINE_OBJ_GETTER(WasmModuleObject, compiled_module, kCompiledModule,
WasmCompiledModule)
Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial,
uint32_t maximum,
int64_t maximum,
Handle<FixedArray>* js_functions) {
Handle<JSFunction> table_ctor(
isolate->native_context()->wasm_table_constructor());
......@@ -133,8 +133,8 @@ Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate, uint32_t initial,
(*js_functions)->set(i, null);
}
table_obj->SetInternalField(kFunctions, *(*js_functions));
table_obj->SetInternalField(kMaximum,
static_cast<Object*>(Smi::FromInt(maximum)));
Handle<Object> max = isolate->factory()->NewNumber(maximum);
table_obj->SetInternalField(kMaximum, *max);
Handle<FixedArray> dispatch_tables = isolate->factory()->NewFixedArray(0);
table_obj->SetInternalField(kDispatchTables, *dispatch_tables);
......@@ -176,8 +176,12 @@ DEFINE_OBJ_ACCESSORS(WasmTableObject, functions, kFunctions, FixedArray)
uint32_t WasmTableObject::current_length() { return functions()->length(); }
uint32_t WasmTableObject::maximum_length() {
return SafeUint32(GetInternalField(kMaximum));
bool WasmTableObject::has_maximum_length() {
return GetInternalField(kMaximum)->Number() >= 0;
}
int64_t WasmTableObject::maximum_length() {
return static_cast<int64_t>(GetInternalField(kMaximum)->Number());
}
WasmTableObject* WasmTableObject::cast(Object* object) {
......@@ -195,14 +199,14 @@ void WasmTableObject::Grow(Isolate* isolate, Handle<WasmTableObject> table,
Handle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate,
Handle<JSArrayBuffer> buffer,
int maximum) {
int32_t maximum) {
Handle<JSFunction> memory_ctor(
isolate->native_context()->wasm_memory_constructor());
Handle<JSObject> memory_obj =
isolate->factory()->NewJSObject(memory_ctor, TENURED);
memory_obj->SetInternalField(kArrayBuffer, *buffer);
memory_obj->SetInternalField(kMaximum,
static_cast<Object*>(Smi::FromInt(maximum)));
Handle<Object> max = isolate->factory()->NewNumber(maximum);
memory_obj->SetInternalField(kMaximum, *max);
Handle<Symbol> memory_sym(isolate->native_context()->wasm_memory_sym());
Object::SetProperty(memory_obj, memory_sym, memory_obj, STRICT).Check();
return Handle<WasmMemoryObject>::cast(memory_obj);
......@@ -216,8 +220,12 @@ uint32_t WasmMemoryObject::current_pages() {
return SafeUint32(buffer()->byte_length()) / wasm::WasmModule::kPageSize;
}
bool WasmMemoryObject::has_maximum_pages() {
return GetInternalField(kMaximum)->Number() >= 0;
}
int32_t WasmMemoryObject::maximum_pages() {
return SafeInt32(GetInternalField(kMaximum));
return static_cast<int32_t>(GetInternalField(kMaximum)->Number());
}
WasmMemoryObject* WasmMemoryObject::cast(Object* object) {
......
......@@ -9,6 +9,7 @@
#include "src/objects-inl.h"
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/managed.h"
#include "src/wasm/wasm-limits.h"
namespace v8 {
namespace internal {
......@@ -60,10 +61,11 @@ class WasmTableObject : public JSObject {
FixedArray* dispatch_tables();
uint32_t current_length();
uint32_t maximum_length();
bool has_maximum_length();
int64_t maximum_length(); // Returns < 0 if no maximum.
static Handle<WasmTableObject> New(Isolate* isolate, uint32_t initial,
uint32_t maximum,
int64_t maximum,
Handle<FixedArray>* js_functions);
static void Grow(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t count);
......@@ -86,11 +88,12 @@ class WasmMemoryObject : public JSObject {
void AddInstance(Isolate* isolate, Handle<WasmInstanceObject> object);
void ResetInstancesLink(Isolate* isolate);
uint32_t current_pages();
int32_t maximum_pages(); // returns < 0 if there is no maximum
bool has_maximum_pages();
int32_t maximum_pages(); // Returns < 0 if there is no maximum.
static Handle<WasmMemoryObject> New(Isolate* isolate,
Handle<JSArrayBuffer> buffer,
int maximum);
int32_t maximum);
static bool Grow(Isolate* isolate, Handle<WasmMemoryObject> memory,
uint32_t count);
......
......@@ -9,7 +9,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
(function SerializeAndDeserializeModule() {
var builder = new WasmModuleBuilder();
builder.addImportedMemory("", "memory", 1,1);
builder.addImportedMemory("", "memory", 1);
var kSig_v_i = makeSig([kWasmI32], []);
var signature = builder.addType(kSig_v_i);
builder.addImport("", "some_value", kSig_i_v);
......
......@@ -382,3 +382,18 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(3, instance.exports.mem_size());
assertEquals(3*kPageSize, instance.exports.exported_mem.buffer.byteLength);
})();
(function TestImportTooLarge() {
print("TestImportTooLarge");
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "m", 1, 2);
// initial size is too large
assertThrows(() => builder.instantiate({m: {m: new WebAssembly.Memory({initial: 3, maximum: 3})}}));
// maximum size is too large
assertThrows(() => builder.instantiate({m: {m: new WebAssembly.Memory({initial: 1, maximum: 4})}}));
// no maximum
assertThrows(() => builder.instantiate({m: {m: new WebAssembly.Memory({initial: 1})}}));
})();
......@@ -199,7 +199,8 @@ function js_div(a, b) { return (a / b) | 0; }
for (let i = 0; i < 5; i++) {
print(" base = " + i);
let table = new WebAssembly.Table({element: "anyfunc",
initial: kTableSize});
initial: kTableSize,
maximum: kTableSize});
assertEquals(10, table.length);
let i2 = new WebAssembly.Instance(m2, {q: {base: i, table: table,
js_div: js_div}});
......@@ -242,7 +243,8 @@ function js_div(a, b) { return (a / b) | 0; }
print("CumulativeTest...");
let kTableSize = 10;
let table = new WebAssembly.Table({element: "anyfunc", initial: 10});
let table = new WebAssembly.Table(
{element: "anyfunc", initial: kTableSize, maximum: kTableSize});
var builder = new WasmModuleBuilder();
......@@ -457,3 +459,22 @@ function js_div(a, b) { return (a / b) | 0; }
}
assertThrows(() => table.grow(11));
})();
(function TestImportTooLarge() {
print("TestImportTooLarge...");
let builder = new WasmModuleBuilder();
builder.addImportedTable("t", "t", 1, 2);
// initial size is too large
assertThrows(() => builder.instantiate({t: {t: new WebAssembly.Table(
{element: "anyfunc", initial: 3, maximum: 3})}}));
// maximum size is too large
assertThrows(() => builder.instantiate({t: {t: new WebAssembly.Table(
{element: "anyfunc", initial: 1, maximum: 4})}}));
// no maximum
assertThrows(() => builder.instantiate({t: {t: new WebAssembly.Table(
{element: "anyfunc", initial: 1})}}));
})();
......@@ -126,7 +126,7 @@ assertFalse(WebAssembly.validate(bytes(88, 88, 88, 88, 88, 88, 88, 88)));
(function InstancesAreIsolatedFromEachother() {
print("InstancesAreIsolatedFromEachother...");
var builder = new WasmModuleBuilder();
builder.addImportedMemory("", "memory", 1,1);
builder.addImportedMemory("", "memory", 1);
var kSig_v_i = makeSig([kWasmI32], []);
var signature = builder.addType(kSig_v_i);
builder.addImport("m", "some_value", kSig_i_v);
......@@ -207,7 +207,7 @@ assertFalse(WebAssembly.validate(bytes(88, 88, 88, 88, 88, 88, 88, 88)));
(function InstanceMemoryIsIsolated() {
print("InstanceMemoryIsIsolated...");
var builder = new WasmModuleBuilder();
builder.addImportedMemory("", "memory", 1,1);
builder.addImportedMemory("", "memory", 1);
builder.addFunction("f", kSig_i_v)
.addBody([
......
......@@ -12,7 +12,7 @@ var kMemSize = 65536;
function genModule(memory) {
var builder = new WasmModuleBuilder();
builder.addImportedMemory("", "memory", 1, 1);
builder.addImportedMemory("", "memory", 1);
builder.exportMemoryAs("memory");
builder.addFunction("main", kSig_i_i)
.addBody([
......
......@@ -13,6 +13,8 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
var outOfUint32RangeValue = 1e12;
var int32ButOob = 1073741824;
var kMaxUint32 = (4 * 1024 * 1024 * 1024) - 1;
var kV8MaxWasmTableSize = 10000000;
function assertTableIsValid(table) {
assertSame(WebAssembly.Table.prototype, table.__proto__);
......@@ -41,14 +43,17 @@ function assertTableIsValid(table) {
assertThrows(() => new WebAssembly.Table({element: 0, initial: 10}), TypeError);
assertThrows(() => new WebAssembly.Table({element: "any", initial: 10}), TypeError);
assertThrows(() => new WebAssembly.Table({element: "anyfunc", initial: -1}), RangeError);
assertThrows(() => new WebAssembly.Table({element: "anyfunc", initial: outOfUint32RangeValue}), RangeError);
assertThrows(() => new WebAssembly.Table(
{element: "anyfunc", initial: -1}), RangeError);
assertThrows(() => new WebAssembly.Table(
{element: "anyfunc", initial: outOfUint32RangeValue}), RangeError);
assertThrows(() => new WebAssembly.Table({element: "anyfunc", initial: 10, maximum: -1}), RangeError);
assertThrows(() => new WebAssembly.Table({element: "anyfunc", initial: 10, maximum: outOfUint32RangeValue}), RangeError);
assertThrows(() => new WebAssembly.Table({element: "anyfunc", initial: 10, maximum: 9}), RangeError);
assertThrows(() => new WebAssembly.Table({element: "anyfunc", initial: 0, maximum: int32ButOob}));
assertThrows(() => new WebAssembly.Table(
{element: "anyfunc", initial: 10, maximum: -1}), RangeError);
assertThrows(() => new WebAssembly.Table(
{element: "anyfunc", initial: 10, maximum: outOfUint32RangeValue}), RangeError);
assertThrows(() => new WebAssembly.Table(
{element: "anyfunc", initial: 10, maximum: 9}), RangeError);
let table;
table = new WebAssembly.Table({element: "anyfunc", initial: 1});
......@@ -94,6 +99,14 @@ function assertTableIsValid(table) {
table = new WebAssembly.Table({element: "anyfunc", initial: 0, maximum: undefined});
assertTableIsValid(table);
assertEquals(0, table.length);
table = new WebAssembly.Table({element: "anyfunc", maximum: kMaxUint32});
assertTableIsValid(table);
assertEquals(0, table.length);
table = new WebAssembly.Table({element: "anyfunc", maximum: kV8MaxWasmTableSize + 1});
assertTableIsValid(table);
assertEquals(0, table.length);
})();
(function TestMaximumIsReadOnce() {
......@@ -260,4 +273,9 @@ function assertTableIsValid(table) {
assertThrows(() => table.grow(-10), RangeError);
assertThrows(() => WebAssembly.Table.prototype.grow.call([], 0), TypeError);
table = new WebAssembly.Table(
{element: "anyfunc", initial: 0, maximum: kV8MaxWasmTableSize});
table.grow(kV8MaxWasmTableSize);
assertThrows(() => table.grow(1), RangeError);
})();
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