Reland "[asmjs] Properly validate asm.js heap sizes"

This is a reland of 5c3092718e
(the CL was reverted because of a Chromium test that is now fixed)

Original change's description:
> Reland "[asmjs] Properly validate asm.js heap sizes"
>
> This is a reland of 5d69010e26
>
> Original change's description:
> > [asmjs] Properly validate asm.js heap sizes
> >
> > Enforce both engine limitations and spec (http://asmjs.org/spec/latest/)
> > limitations on the size of asm.js heaps.
> >
> > R=clemensh@chromium.org
> > CC=​mstarzinger@chromium.org
> >
> > Bug: chromium:873600
> > Change-Id: I104c23bbd0a9a7c494f97f8f9e83ac5a37496dfd
> > Reviewed-on: https://chromium-review.googlesource.com/1174411
> > Commit-Queue: Ben Titzer <titzer@chromium.org>
> > Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
> > Cr-Commit-Position: refs/heads/master@{#55163}
>
> Bug: chromium:873600
> Change-Id: Id24070bda3aafb9e1a32af0732a1b18f633ef932
> Reviewed-on: https://chromium-review.googlesource.com/1179681
> Commit-Queue: Ben Titzer <titzer@chromium.org>
> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#55193}

Bug: chromium:873600
Change-Id: I6eca2a89589070837b109278f964fc8e9a0fd6f1
Reviewed-on: https://chromium-review.googlesource.com/1183081
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Commit-Queue: Ben Titzer <titzer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55249}
This commit is contained in:
Ben L. Titzer 2018-08-17 14:05:25 +02:00 committed by Commit Bot
parent c4d1a00873
commit 438e7ec6dc
14 changed files with 212 additions and 37 deletions

View File

@ -23,6 +23,7 @@
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-result.h"
@ -329,6 +330,28 @@ UnoptimizedCompilationJob* AsmJs::NewCompilationJob(
return new AsmJsCompilationJob(parse_info, literal, allocator);
}
namespace {
inline bool IsValidAsmjsMemorySize(size_t size) {
// Enforce asm.js spec minimum size.
if (size < (1u << 12u)) return false;
// Enforce engine-limited maximum allocation size.
if (size > wasm::kV8MaxWasmMemoryBytes) return false;
// Enforce flag-limited maximum allocation size.
if (size > (FLAG_wasm_max_mem_pages * uint64_t{wasm::kWasmPageSize})) {
return false;
}
// Enforce power-of-2 sizes for 2^12 - 2^24.
if (size < (1u << 24u)) {
uint32_t size32 = static_cast<uint32_t>(size);
return base::bits::IsPowerOfTwo(size32);
}
// Enforce multiple of 2^24 for sizes >= 2^24
if ((size % (1u << 24u)) != 0) return false;
// All checks passed!
return true;
}
} // namespace
MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate,
Handle<SharedFunctionInfo> shared,
Handle<FixedArray> wasm_data,
@ -369,15 +392,9 @@ MaybeHandle<Object> AsmJs::InstantiateAsmWasm(Isolate* isolate,
}
memory->set_is_growable(false);
size_t size = NumberToSize(memory->byte_length());
// TODO(mstarzinger): We currently only limit byte length of the buffer to
// be a multiple of 8, we should enforce the stricter spec limits here.
if (size % FixedTypedArrayBase::kMaxElementSize != 0) {
ReportInstantiationFailure(script, position, "Unexpected heap size");
return MaybeHandle<Object>();
}
// Currently WebAssembly only supports heap sizes within the uint32_t range.
if (size > std::numeric_limits<uint32_t>::max()) {
ReportInstantiationFailure(script, position, "Unexpected heap size");
// Check the asm.js heap size against the valid limits.
if (!IsValidAsmjsMemorySize(size)) {
ReportInstantiationFailure(script, position, "Invalid heap size");
return MaybeHandle<Object>();
}
} else {

View File

@ -2793,9 +2793,6 @@ bool Shell::SetOptions(int argc, char* argv[]) {
strcmp(argv[i], "--no-stress-background-compile") == 0) {
options.stress_background_compile = false;
argv[i] = nullptr;
} else if (strcmp(argv[i], "--mock-arraybuffer-allocator") == 0) {
options.mock_arraybuffer_allocator = true;
argv[i] = nullptr;
} else if (strcmp(argv[i], "--noalways-opt") == 0 ||
strcmp(argv[i], "--no-always-opt") == 0) {
// No support for stressing if we can't use --always-opt.
@ -2910,6 +2907,7 @@ bool Shell::SetOptions(int argc, char* argv[]) {
}
v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
options.mock_arraybuffer_allocator = i::FLAG_mock_arraybuffer_allocator;
// Set up isolated source groups.
options.isolate_sources = new SourceGroup[options.num_isolates];

View File

@ -978,7 +978,6 @@ DEFINE_INT(heap_snapshot_string_limit, 1024,
DEFINE_BOOL(sampling_heap_profiler_suppress_randomness, false,
"Use constant sample intervals to eliminate test flakiness")
// v8.cc
DEFINE_BOOL(use_idle_notification, true,
"Use idle notification to reduce memory footprint.")
@ -1145,6 +1144,8 @@ DEFINE_BOOL(use_external_strings, false, "Use external strings for source code")
DEFINE_STRING(map_counters, "", "Map counters to a file")
DEFINE_ARGS(js_arguments,
"Pass all remaining arguments to the script. Alias for \"--\".")
DEFINE_BOOL(mock_arraybuffer_allocator, false,
"Use a mock ArrayBuffer allocator for testing.")
//
// GDB JIT integration flags.

View File

@ -951,14 +951,17 @@ void SetInstanceMemory(Handle<WasmInstanceObject> instance,
instance->SetRawMemory(reinterpret_cast<byte*>(buffer->backing_store()),
buffer->byte_length()->Number());
#if DEBUG
// To flush out bugs earlier, in DEBUG mode, check that all pages of the
// memory are accessible by reading and writing one byte on each page.
byte* mem_start = instance->memory_start();
size_t mem_size = instance->memory_size();
for (size_t offset = 0; offset < mem_size; offset += wasm::kWasmPageSize) {
byte val = mem_start[offset];
USE(val);
mem_start[offset] = val;
if (!FLAG_mock_arraybuffer_allocator) {
// To flush out bugs earlier, in DEBUG mode, check that all pages of the
// memory are accessible by reading and writing one byte on each page.
// Don't do this if the mock ArrayBuffer allocator is enabled.
byte* mem_start = instance->memory_start();
size_t mem_size = instance->memory_size();
for (size_t offset = 0; offset < mem_size; offset += wasm::kWasmPageSize) {
byte val = mem_start[offset];
USE(val);
mem_start[offset] = val;
}
}
#endif
}

View File

@ -26424,7 +26424,7 @@ TEST(TurboAsmDisablesNeuter) {
" function load() { return MEM32[0] | 0; }"
" return { load: load };"
"}"
"var buffer = new ArrayBuffer(1024);"
"var buffer = new ArrayBuffer(4096);"
"var module = Module(this, {}, buffer);"
"%OptimizeFunctionOnNextCall(module.load);"
"module.load();"
@ -26440,7 +26440,7 @@ TEST(TurboAsmDisablesNeuter) {
" function store() { MEM32[0] = 0; }"
" return { store: store };"
"}"
"var buffer = new ArrayBuffer(1024);"
"var buffer = new ArrayBuffer(4096);"
"var module = Module(this, {}, buffer);"
"%OptimizeFunctionOnNextCall(module.store);"
"module.store();"

View File

@ -2,4 +2,4 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
*%(basename)s:7: Linking failure in asm.js: Unexpected heap size
*%(basename)s:7: Linking failure in asm.js: Invalid heap size

View File

@ -0,0 +1,99 @@
// Copyright 2018 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: --validate-asm --allow-natives-syntax --expose-gc --mock-arraybuffer-allocator
let gCounter = 1000;
let gMinHeap = new ArrayBuffer(1 << 12);
let gStdlib = {Uint8Array: Uint8Array};
// The template of asm.js modules used in this test.
function Template(stdlib, ffi, heap) {
"use asm";
var MEM8 = new stdlib.Uint8Array(heap);
function foo() { return VAL; }
return { foo: foo };
}
// Create a fresh module each time.
function NewModule() {
// Use eval() to get a unique module each time.
let val = gCounter++;
let string = (Template + "; Template").replace("VAL", "" + val);
// print(string);
let module = eval(string);
// print(module);
module(gStdlib, {}, gMinHeap);
assertTrue(%IsAsmWasmCode(module));
return {module: module, val: val};
}
(function TestValid_PowerOfTwo() {
print("TestValid_PowerOfTwo...");
let r = NewModule();
for (let i = 12; i <= 24; i++) {
gc(); // Likely OOM otherwise.
let size = 1 << i;
print(" size=" + size);
let heap = new ArrayBuffer(size);
var instance = r.module(gStdlib, {}, heap);
assertTrue(%IsAsmWasmCode(r.module));
assertEquals(r.val, instance.foo());
}
})();
(function TestValid_Multiple() {
print("TestValid_Multiple...");
let r = NewModule();
for (let i = 1; i < 47; i += 7) {
gc(); // Likely OOM otherwise.
let size = i * (1 << 24);
print(" size=" + size);
let heap = new ArrayBuffer(size);
var instance = r.module(gStdlib, {}, heap);
assertTrue(%IsAsmWasmCode(r.module));
assertEquals(r.val, instance.foo());
}
})();
(function TestInvalid_TooSmall() {
print("TestInvalid_TooSmall...");
for (let i = 1; i < 12; i++) {
let size = 1 << i;
print(" size=" + size);
let r = NewModule();
let heap = new ArrayBuffer(size);
var instance = r.module(gStdlib, {}, heap);
assertFalse(%IsAsmWasmCode(r.module));
assertEquals(r.val, instance.foo());
}
})();
(function TestInValid_NonPowerOfTwo() {
print("TestInvalid_NonPowerOfTwo...");
for (let i = 12; i <= 24; i++) {
gc(); // Likely OOM otherwise.
let size = 1 + (1 << i);
print(" size=" + size);
let r = NewModule();
let heap = new ArrayBuffer(size);
var instance = r.module(gStdlib, {}, heap);
assertFalse(%IsAsmWasmCode(r.module));
assertEquals(r.val, instance.foo());
}
})();
(function TestInValid_NonMultiple() {
print("TestInvalid_NonMultiple...");
for (let i = (1 << 24); i < (1 << 25); i += (1 << 22)) {
gc(); // Likely OOM otherwise.
let size = i + (1 << 20);
print(" size=" + size);
let r = NewModule();
let heap = new ArrayBuffer(size);
var instance = r.module(gStdlib, {}, heap);
assertFalse(%IsAsmWasmCode(r.module));
assertEquals(r.val, instance.foo());
}
})();

View File

@ -154,6 +154,9 @@
'asm/poppler/*': [PASS, SLOW, NO_VARIANTS],
'asm/sqlite3/*': [PASS, SLOW, NO_VARIANTS],
# OOM flakes in isolates tests because too many largish heaps are created.
'asm/asm-heap': [PASS, NO_VARIANTS, ['isolates', SKIP]],
# Slow tests.
'copy-on-write-assert': [PASS, SLOW],
'es6/typedarray-construct-offset-not-smi': [PASS, SLOW],

View File

@ -4,6 +4,8 @@
// Flags: --allow-natives-syntax
let kMinHeapSize = 4096;
(function TestLeftRight() {
function Module(stdlib, foreign, heap) {
"use asm";
@ -14,7 +16,7 @@
}
return { f:f }
}
var buffer = new ArrayBuffer(1024);
var buffer = new ArrayBuffer(kMinHeapSize);
var module = new Module(this, {}, buffer);
assertTrue(%IsAsmWasmCode(Module));
new Int32Array(buffer)[42] = 23;
@ -31,7 +33,7 @@
}
return { f:f }
}
var buffer = new ArrayBuffer(1024);
var buffer = new ArrayBuffer(kMinHeapSize);
var module = new Module(this, {}, buffer)
assertTrue(%IsAsmWasmCode(Module));
new Int32Array(buffer)[42 >> 4] = 23;
@ -48,7 +50,7 @@
}
return { f:f }
}
var buffer = new ArrayBuffer(1024);
var buffer = new ArrayBuffer(kMinHeapSize);
var module = new Module(this, {}, buffer)
assertFalse(%IsAsmWasmCode(Module));
new Int32Array(buffer)[42 & 0xfc] = 23;
@ -65,7 +67,7 @@
}
return { f:f }
}
var buffer = new ArrayBuffer(1024);
var buffer = new ArrayBuffer(kMinHeapSize);
var module = new Module(this, {}, buffer)
assertFalse(%IsAsmWasmCode(Module));
new Int32Array(buffer)[42 >> 3] = 23;
@ -82,7 +84,7 @@
}
return { f:f }
}
var buffer = new ArrayBuffer(1024);
var buffer = new ArrayBuffer(kMinHeapSize);
var module = new Module(this, {}, buffer)
assertFalse(%IsAsmWasmCode(Module));
new Int32Array(buffer)[42 << 2] = 23;

View File

@ -13,7 +13,7 @@ function Module(stdlib, env, heap) {
return { f: f };
}
function instantiate() {
var buffer = new ArrayBuffer(0);
var buffer = new ArrayBuffer(4096);
Module(this, {}, buffer).f();
try {} finally {}
gc();

View File

@ -16,7 +16,7 @@ function module(stdlib,foreign,buffer) {
var global = {Uint32Array:Uint32Array};
var env = {};
memory = new WebAssembly.Memory({initial:200});
memory = new WebAssembly.Memory({initial:128});
var buffer = memory.buffer;
evil_f = module(global,env,buffer);

View File

@ -0,0 +1,50 @@
// Copyright 2018 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.
(function DoTest() {
var stdlib = this;
try {
var buffer = new ArrayBuffer((2097120) * 1024);
} catch (e) {
// Out of memory: soft pass because 2GiB is actually a lot!
print("OOM: soft pass");
return;
}
var foreign = {}
var m = (function Module(stdlib, foreign, heap) {
"use asm";
var MEM16 = new stdlib.Int16Array(heap);
function load(i) {
i = i|0;
i = MEM16[i >> 1]|0;
return i | 0;
}
function store(i, v) {
i = i|0;
v = v|0;
MEM16[i >> 1] = v;
}
function load8(i) {
i = i|0;
i = MEM16[i + 8 >> 1]|0;
return i | 0;
}
function store8(i, v) {
i = i|0;
v = v|0;
MEM16[i + 8 >> 1] = v;
}
return { load: load, store: store, load8: load8, store8: store8 };
})(stdlib, foreign, buffer);
assertEquals(0, m.load(-8));
assertEquals(0, m.load8(-16));
m.store(2014, 2, 30, 1, 0);
assertEquals(0, m.load8(-8));
m.store8(-8, 99);
assertEquals(99, m.load(0));
assertEquals(99, m.load8(-8));
})();

View File

@ -5,6 +5,7 @@
// Flags: --validate-asm --allow-natives-syntax
var stdlib = this;
let kMinHeapSize = 4096;
function assertValidAsm(func) {
assertTrue(%IsAsmWasmCode(func), "must be valid asm code");
@ -13,7 +14,7 @@ function assertValidAsm(func) {
function assertWasm(expected, func, ffi) {
print("Testing " + func.name + "...");
assertEquals(
expected, func(stdlib, ffi, new ArrayBuffer(1024)).caller());
expected, func(stdlib, ffi, new ArrayBuffer(kMinHeapSize)).caller());
assertValidAsm(func);
}
@ -38,7 +39,7 @@ assertWasm(7, TestInt32HeapAccess);
function TestInt32HeapAccessExternal() {
var memory = new ArrayBuffer(1024);
var memory = new ArrayBuffer(kMinHeapSize);
var memory_int32 = new Int32Array(memory);
var module_decl = eval('(' + TestInt32HeapAccess.toString() + ')');
var module = module_decl(stdlib, null, memory);
@ -63,7 +64,7 @@ function TestHeapAccessIntTypes() {
var code = TestInt32HeapAccess.toString();
code = code.replace('Int32Array', types[i][1]);
code = code.replace(/>> 2/g, types[i][2]);
var memory = new ArrayBuffer(1024);
var memory = new ArrayBuffer(kMinHeapSize);
var memory_view = new types[i][0](memory);
var module_decl = eval('(' + code + ')');
var module = module_decl(stdlib, null, memory);
@ -102,7 +103,7 @@ assertWasm(1, TestFloatHeapAccess);
function TestFloatHeapAccessExternal() {
var memory = new ArrayBuffer(1024);
var memory = new ArrayBuffer(kMinHeapSize);
var memory_float64 = new Float64Array(memory);
var module_decl = eval('(' + TestFloatHeapAccess.toString() + ')');
var module = module_decl(stdlib, null, memory);
@ -146,7 +147,7 @@ TestFloatHeapAccessExternal();
return {load: load, iload: iload, store: store, storeb: storeb};
}
var memory = new ArrayBuffer(1024);
var memory = new ArrayBuffer(kMinHeapSize);
var module_decl = eval('(' + TestByteHeapAccessCompat.toString() + ')');
var m = module_decl(stdlib, null, memory);
assertValidAsm(module_decl);

View File

@ -5,6 +5,7 @@
// Flags: --validate-asm --allow-natives-syntax
var stdlib = this;
let kMinHeapSize = 4096;
function assertValidAsm(func) {
assertTrue(%IsAsmWasmCode(func), "must be valid asm code");
@ -13,7 +14,7 @@ function assertValidAsm(func) {
function assertWasm(expected, func, ffi) {
print("Testing " + func.name + "...");
assertEquals(
expected, func(stdlib, ffi, new ArrayBuffer(1024)).caller());
expected, func(stdlib, ffi, new ArrayBuffer(kMinHeapSize)).caller());
assertValidAsm(func);
}