[utils] Add IsInBounds(index, size, max) helper

This CL adds a helper function that simplifies a bounds check pattern
that appears repeatedly in the code.

R=clemensh@chromium.org

Change-Id: I8c617515b34eb2d262d58a239a29c1515de2d92d
Reviewed-on: https://chromium-review.googlesource.com/c/1417611
Commit-Queue: Ben Titzer <titzer@chromium.org>
Reviewed-by: Clemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#58892}
This commit is contained in:
Ben L. Titzer 2019-01-17 15:29:43 +01:00 committed by Commit Bot
parent 073063d794
commit e254ec915b
7 changed files with 95 additions and 20 deletions

View File

@ -3313,9 +3313,7 @@ Node* WasmGraphBuilder::BoundsCheckMem(uint8_t access_size, Node* index,
return index; return index;
} }
const bool statically_oob = access_size > env_->max_memory_size || if (!IsInBounds(offset, access_size, env_->max_memory_size)) {
offset > env_->max_memory_size - access_size;
if (statically_oob) {
// The access will be out of bounds, even for the largest memory. // The access will be out of bounds, even for the largest memory.
TrapIfEq32(wasm::kTrapMemOutOfBounds, Int32Constant(0), 0, position); TrapIfEq32(wasm::kTrapMemOutOfBounds, Int32Constant(0), 0, position);
return mcgraph()->IntPtrConstant(0); return mcgraph()->IntPtrConstant(0);

View File

@ -73,6 +73,12 @@ inline constexpr bool IsInRange(T value, U lower_limit, U higher_limit) {
static_cast<unsigned_T>(lower_limit)); static_cast<unsigned_T>(lower_limit));
} }
// Checks if [index, index+length) is in range [0, max). Note that this check
// works even if {index+length} would wrap around.
inline constexpr bool IsInBounds(size_t index, size_t length, size_t max) {
return length <= max && index <= (max - length);
}
// X must be a power of 2. Returns the number of trailing zeros. // X must be a power of 2. Returns the number of trailing zeros.
template <typename T, template <typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type> typename = typename std::enable_if<std::is_integral<T>::value>::type>

View File

@ -14,6 +14,7 @@
#include "src/macro-assembler-inl.h" #include "src/macro-assembler-inl.h"
#include "src/objects/smi.h" #include "src/objects/smi.h"
#include "src/tracing/trace-event.h" #include "src/tracing/trace-event.h"
#include "src/utils.h"
#include "src/wasm/baseline/liftoff-assembler.h" #include "src/wasm/baseline/liftoff-assembler.h"
#include "src/wasm/function-body-decoder-impl.h" #include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/function-compiler.h" #include "src/wasm/function-compiler.h"
@ -1393,8 +1394,8 @@ class LiftoffCompiler {
// (a jump to the trap was generated then); return false otherwise. // (a jump to the trap was generated then); return false otherwise.
bool BoundsCheckMem(FullDecoder* decoder, uint32_t access_size, bool BoundsCheckMem(FullDecoder* decoder, uint32_t access_size,
uint32_t offset, Register index, LiftoffRegList pinned) { uint32_t offset, Register index, LiftoffRegList pinned) {
const bool statically_oob = access_size > env_->max_memory_size || const bool statically_oob =
offset > env_->max_memory_size - access_size; !IsInBounds(offset, access_size, env_->max_memory_size);
if (!statically_oob && if (!statically_oob &&
(FLAG_wasm_no_bounds_checks || env_->use_trap_handler)) { (FLAG_wasm_no_bounds_checks || env_->use_trap_handler)) {

View File

@ -5,6 +5,7 @@
#include "src/wasm/module-instantiate.h" #include "src/wasm/module-instantiate.h"
#include "src/asmjs/asm-js.h" #include "src/asmjs/asm-js.h"
#include "src/property-descriptor.h" #include "src/property-descriptor.h"
#include "src/utils.h"
#include "src/wasm/js-to-wasm-wrapper-cache-inl.h" #include "src/wasm/js-to-wasm-wrapper-cache-inl.h"
#include "src/wasm/module-compiler.h" #include "src/wasm/module-compiler.h"
#include "src/wasm/wasm-import-wrapper-cache-inl.h" #include "src/wasm/wasm-import-wrapper-cache-inl.h"
@ -24,10 +25,6 @@ namespace {
byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) { byte* raw_buffer_ptr(MaybeHandle<JSArrayBuffer> buffer, int offset) {
return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset; return static_cast<byte*>(buffer.ToHandleChecked()->backing_store()) + offset;
} }
bool in_bounds(uint32_t offset, size_t size, size_t upper) {
return offset + size <= upper && offset + size >= offset;
}
} // namespace } // namespace
// A helper class to simplify instantiating a module from a module object. // A helper class to simplify instantiating a module from a module object.
@ -432,7 +429,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
DCHECK(elem_segment.table_index < table_instances_.size()); DCHECK(elem_segment.table_index < table_instances_.size());
uint32_t base = EvalUint32InitExpr(elem_segment.offset); uint32_t base = EvalUint32InitExpr(elem_segment.offset);
size_t table_size = table_instances_[elem_segment.table_index].table_size; size_t table_size = table_instances_[elem_segment.table_index].table_size;
if (!in_bounds(base, elem_segment.entries.size(), table_size)) { if (!IsInBounds(base, elem_segment.entries.size(), table_size)) {
thrower_->LinkError("table initializer is out of bounds"); thrower_->LinkError("table initializer is out of bounds");
return {}; return {};
} }
@ -444,7 +441,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
for (const WasmDataSegment& seg : module_->data_segments) { for (const WasmDataSegment& seg : module_->data_segments) {
if (!seg.active) continue; if (!seg.active) continue;
uint32_t base = EvalUint32InitExpr(seg.dest_addr); uint32_t base = EvalUint32InitExpr(seg.dest_addr);
if (!in_bounds(base, seg.source.length(), instance->memory_size())) { if (!IsInBounds(base, seg.source.length(), instance->memory_size())) {
thrower_->LinkError("data segment is out of bounds"); thrower_->LinkError("data segment is out of bounds");
return {}; return {};
} }
@ -623,7 +620,7 @@ void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> instance) {
// Passive segments are not copied during instantiation. // Passive segments are not copied during instantiation.
if (!segment.active) continue; if (!segment.active) continue;
uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr); uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr);
DCHECK(in_bounds(dest_offset, source_size, instance->memory_size())); DCHECK(IsInBounds(dest_offset, source_size, instance->memory_size()));
byte* dest = instance->memory_start() + dest_offset; byte* dest = instance->memory_start() + dest_offset;
const byte* src = wire_bytes.start() + segment.source.offset(); const byte* src = wire_bytes.start() + segment.source.offset();
memcpy(dest, src, source_size); memcpy(dest, src, source_size);
@ -1464,7 +1461,7 @@ void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) {
uint32_t num_entries = static_cast<uint32_t>(elem_segment.entries.size()); uint32_t num_entries = static_cast<uint32_t>(elem_segment.entries.size());
uint32_t index = elem_segment.table_index; uint32_t index = elem_segment.table_index;
TableInstance& table_instance = table_instances_[index]; TableInstance& table_instance = table_instances_[index];
DCHECK(in_bounds(base, num_entries, table_instance.table_size)); DCHECK(IsInBounds(base, num_entries, table_instance.table_size));
for (uint32_t i = 0; i < num_entries; ++i) { for (uint32_t i = 0; i < num_entries; ++i) {
uint32_t func_index = elem_segment.entries[i]; uint32_t func_index = elem_segment.entries[i];
const WasmFunction* function = &module_->functions[func_index]; const WasmFunction* function = &module_->functions[func_index];

View File

@ -1402,14 +1402,18 @@ class ThreadImpl {
template <typename mtype> template <typename mtype>
inline Address BoundsCheckMem(uint32_t offset, uint32_t index) { inline Address BoundsCheckMem(uint32_t offset, uint32_t index) {
size_t mem_size = instance_object_->memory_size(); uint32_t effective_index = offset + index;
if (sizeof(mtype) > mem_size) return kNullAddress; if (effective_index < index) {
if (offset > (mem_size - sizeof(mtype))) return kNullAddress; return kNullAddress; // wraparound => oob
if (index > (mem_size - sizeof(mtype) - offset)) return kNullAddress; }
if (!IsInBounds(effective_index, sizeof(mtype),
instance_object_->memory_size())) {
return kNullAddress; // oob
}
// Compute the effective address of the access, making sure to condition // Compute the effective address of the access, making sure to condition
// the index even in the in-bounds case. // the index even in the in-bounds case.
return reinterpret_cast<Address>(instance_object_->memory_start()) + return reinterpret_cast<Address>(instance_object_->memory_start()) +
offset + (index & instance_object_->memory_mask()); (effective_index & instance_object_->memory_mask());
} }
template <typename ctype, typename mtype> template <typename ctype, typename mtype>

View File

@ -1415,6 +1415,7 @@ Address WasmInstanceObject::GetCallTarget(uint32_t func_index) {
namespace { namespace {
void CopyTableEntriesImpl(Handle<WasmInstanceObject> instance, uint32_t dst, void CopyTableEntriesImpl(Handle<WasmInstanceObject> instance, uint32_t dst,
uint32_t src, uint32_t count) { uint32_t src, uint32_t count) {
DCHECK(IsInBounds(dst, count, instance->indirect_function_table_size()));
if (src < dst) { if (src < dst) {
for (uint32_t i = count; i > 0; i--) { for (uint32_t i = count; i > 0; i--) {
auto to_entry = IndirectFunctionTableEntry(instance, dst + i - 1); auto to_entry = IndirectFunctionTableEntry(instance, dst + i - 1);
@ -1439,8 +1440,8 @@ bool WasmInstanceObject::CopyTableEntries(Isolate* isolate,
CHECK_EQ(0, table_index); // TODO(titzer): multiple tables in TableCopy CHECK_EQ(0, table_index); // TODO(titzer): multiple tables in TableCopy
if (count == 0) return true; // no-op if (count == 0) return true; // no-op
auto max = instance->indirect_function_table_size(); auto max = instance->indirect_function_table_size();
if (dst > max || count > (max - dst)) return false; // out-of-bounds if (!IsInBounds(dst, count, max)) return false;
if (src > max || count > (max - src)) return false; // out-of-bounds if (!IsInBounds(src, count, max)) return false;
if (dst == src) return true; // no-op if (dst == src) return true; // no-op
if (!instance->has_table_object()) { if (!instance->has_table_object()) {

View File

@ -132,5 +132,73 @@ TYPED_TEST(UtilsTest, PassesFilterTest) {
EXPECT_FALSE(PassesFilter(CStrVector(""), CStrVector("a"))); EXPECT_FALSE(PassesFilter(CStrVector(""), CStrVector("a")));
} }
TEST(UtilsTest, IsInBounds) {
// for column consistency and terseness
#define INB(x, y, z) EXPECT_TRUE(IsInBounds(x, y, z))
#define OOB(x, y, z) EXPECT_FALSE(IsInBounds(x, y, z))
INB(0, 0, 1);
INB(0, 1, 1);
INB(1, 0, 1);
OOB(0, 2, 1);
OOB(2, 0, 1);
INB(0, 0, 2);
INB(0, 1, 2);
INB(0, 2, 2);
INB(0, 0, 2);
INB(1, 0, 2);
INB(2, 0, 2);
OOB(0, 3, 2);
OOB(3, 0, 2);
INB(0, 1, 2);
INB(1, 1, 2);
OOB(1, 2, 2);
OOB(2, 1, 2);
const size_t max = std::numeric_limits<size_t>::max();
const size_t half = max / 2;
// limit cases.
INB(0, 0, max);
INB(0, 1, max);
INB(1, 0, max);
INB(max, 0, max);
INB(0, max, max);
INB(max - 1, 0, max);
INB(0, max - 1, max);
INB(max - 1, 1, max);
INB(1, max - 1, max);
INB(half, half, max);
INB(half + 1, half, max);
INB(half, half + 1, max);
OOB(max, 0, 0);
OOB(0, max, 0);
OOB(max, 0, 1);
OOB(0, max, 1);
OOB(max, 0, 2);
OOB(0, max, 2);
OOB(max, 0, max - 1);
OOB(0, max, max - 1);
// wraparound cases.
OOB(max, 1, max);
OOB(1, max, max);
OOB(max - 1, 2, max);
OOB(2, max - 1, max);
OOB(half + 1, half + 1, max);
OOB(half + 1, half + 1, max);
#undef INB
#undef OOB
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8