[wasm-gc][bug] call_indirect should check for null table entries

This was not happening when there was no need to typecheck the entry.

Additional changes:
- Add tests with null table entries for typed and untyped function
  tables.
- Allow AddIndirectFunctionTable in wasm-run-utils to specify table
  type.
- Add possibility to define tables in test-gc.cc.
- Merge trapTableOutOfBounds with trapInvalidFunc.
- Use trapTableOutOfBounds in call_indirect as appropriate.
- Fix emission of table types in wasm-module-builder.cc.

Bug: v8:9495
Change-Id: I4a857ff4378e5a87dc0646d94b4c75635a43c55b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2442622
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70311}
This commit is contained in:
Manos Koukoutos 2020-10-02 05:31:50 +00:00 committed by Commit Bot
parent bee5b996aa
commit cdb3da7f5f
20 changed files with 86 additions and 44 deletions

View File

@ -326,7 +326,6 @@ extern enum MessageTemplate {
kWasmTrapDivUnrepresentable,
kWasmTrapRemByZero,
kWasmTrapFloatUnrepresentable,
kWasmTrapFuncInvalid,
kWasmTrapFuncSigMismatch,
kWasmTrapDataSegmentDropped,
kWasmTrapElemSegmentDropped,

View File

@ -385,10 +385,6 @@ builtin ThrowWasmTrapFloatUnrepresentable(): JSAny {
tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapFloatUnrepresentable));
}
builtin ThrowWasmTrapFuncInvalid(): JSAny {
tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapFuncInvalid));
}
builtin ThrowWasmTrapFuncSigMismatch(): JSAny {
tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapFuncSigMismatch));
}

View File

@ -1598,7 +1598,6 @@ enum class LoadSensitivity {
V(TrapDivUnrepresentable) \
V(TrapRemByZero) \
V(TrapFloatUnrepresentable) \
V(TrapFuncInvalid) \
V(TrapFuncSigMismatch) \
V(TrapDataSegmentDropped) \
V(TrapElemSegmentDropped) \

View File

@ -553,13 +553,12 @@ namespace internal {
T(WasmTrapDivUnrepresentable, "divide result unrepresentable") \
T(WasmTrapRemByZero, "remainder by zero") \
T(WasmTrapFloatUnrepresentable, "float unrepresentable in integer range") \
T(WasmTrapFuncInvalid, "invalid index into function table") \
T(WasmTrapTableOutOfBounds, "table index is out of bounds") \
T(WasmTrapFuncSigMismatch, "function signature mismatch") \
T(WasmTrapMultiReturnLengthMismatch, "multi-return length mismatch") \
T(WasmTrapTypeError, "wasm function signature contains illegal type") \
T(WasmTrapDataSegmentDropped, "data segment has been dropped") \
T(WasmTrapElemSegmentDropped, "element segment has been dropped") \
T(WasmTrapTableOutOfBounds, "table access out of bounds") \
T(WasmTrapBrOnExnNull, "br_on_exn on null value") \
T(WasmTrapRethrowNull, "rethrowing null value") \
T(WasmTrapNullDereference, "dereferencing a null pointer") \

View File

@ -569,7 +569,7 @@ IfValueParameters const& IfValueParametersOf(const Operator* op) {
V(TrapDivUnrepresentable) \
V(TrapRemByZero) \
V(TrapFloatUnrepresentable) \
V(TrapFuncInvalid) \
V(TrapTableOutOfBounds) \
V(TrapFuncSigMismatch)
#define CACHED_PARAMETER_LIST(V) \

View File

@ -2672,7 +2672,7 @@ Node* WasmGraphBuilder::BuildWasmCall(const wasm::FunctionSig* sig,
wasm::WasmCodePosition position,
Node* instance_node,
UseRetpoline use_retpoline) {
auto call_descriptor =
CallDescriptor* call_descriptor =
GetWasmCallDescriptor(mcgraph()->zone(), sig, use_retpoline);
const Operator* op = mcgraph()->common()->Call(call_descriptor);
Node* call = BuildCallNode(sig, args, position, instance_node, op);
@ -2699,7 +2699,7 @@ Node* WasmGraphBuilder::BuildWasmReturnCall(const wasm::FunctionSig* sig,
wasm::WasmCodePosition position,
Node* instance_node,
UseRetpoline use_retpoline) {
auto call_descriptor =
CallDescriptor* call_descriptor =
GetWasmCallDescriptor(mcgraph()->zone(), sig, use_retpoline);
const Operator* op = mcgraph()->common()->TailCall(call_descriptor);
Node* call = BuildCallNode(sig, args, position, instance_node, op);
@ -2878,7 +2878,7 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
// Bounds check against the table size.
Node* in_bounds = graph()->NewNode(machine->Uint32LessThan(), key, ift_size);
TrapIfFalse(wasm::kTrapFuncInvalid, in_bounds, position);
TrapIfFalse(wasm::kTrapTableOutOfBounds, in_bounds, position);
// Mask the key to prevent SSCA.
if (untrusted_code_mitigations_) {
@ -2896,20 +2896,27 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
Node* int32_scaled_key = Uint32ToUintptr(
graph()->NewNode(machine->Word32Shl(), key, Int32Constant(2)));
Node* loaded_sig = SetEffect(
graph()->NewNode(machine->Load(MachineType::Int32()), ift_sig_ids,
int32_scaled_key, effect(), control()));
// Check that the dynamic type of the function is a subtype of its static
// (table) type. Currently, the only subtyping between function types is
// $t <: funcref for all $t: function_type.
// TODO(7748): Expand this with function subtyping.
if (env_->module->tables[table_index].type == wasm::kWasmFuncRef) {
const bool needs_typechecking =
env_->module->tables[table_index].type == wasm::kWasmFuncRef;
if (needs_typechecking) {
int32_t expected_sig_id = env_->module->signature_ids[sig_index];
Node* loaded_sig = SetEffect(
graph()->NewNode(machine->Load(MachineType::Int32()), ift_sig_ids,
int32_scaled_key, effect(), control()));
Node* sig_match = graph()->NewNode(machine->WordEqual(), loaded_sig,
Node* sig_match = graph()->NewNode(machine->Word32Equal(), loaded_sig,
Int32Constant(expected_sig_id));
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
} else {
// We still have to check that the entry is initialized.
// TODO(9495): Skip this check for non-nullable tables when they are
// allowed.
Node* function_is_null =
graph()->NewNode(machine->Word32Equal(), loaded_sig, Int32Constant(-1));
TrapIfTrue(wasm::kTrapNullDereference, function_is_null, position);
}
Node* tagged_scaled_key;

View File

@ -3916,7 +3916,7 @@ class LiftoffCompiler {
// Bounds check against the table size.
Label* invalid_func_label = AddOutOfLineTrap(
decoder->position(), WasmCode::kThrowWasmTrapFuncInvalid);
decoder->position(), WasmCode::kThrowWasmTrapTableOutOfBounds);
uint32_t canonical_sig_num = env_->module->signature_ids[imm.sig_index];
DCHECK_GE(canonical_sig_num, 0);

View File

@ -597,7 +597,7 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
size_t start = EmitSection(kTableSectionCode, buffer);
buffer->write_size(tables_.size());
for (const WasmTable& table : tables_) {
buffer->write_u8(table.type.value_type_code());
WriteValueType(buffer, table.type);
buffer->write_u8(table.has_maximum ? kWithMaximum : kNoMaximum);
buffer->write_size(table.min_size);
if (table.has_maximum) buffer->write_size(table.max_size);

View File

@ -88,6 +88,10 @@ class WasmGCTester {
byte DefineSignature(FunctionSig* sig) { return builder_.AddSignature(sig); }
byte DefineTable(ValueType type, uint32_t min_size, uint32_t max_size) {
return builder_.AddTable(type, min_size, max_size);
}
void CompileModule() {
ZoneBuffer buffer(&zone);
builder_.WriteTo(&buffer);
@ -1048,6 +1052,21 @@ TEST(GlobalInitReferencingGlobal) {
tester.CheckResult(func, 42);
}
TEST(IndirectNullSetManually) {
WasmGCTester tester;
byte sig_index = tester.DefineSignature(tester.sigs.i_i());
tester.DefineTable(ValueType::Ref(sig_index, kNullable), 1, 1);
byte func_index = tester.DefineFunction(
tester.sigs.i_i(), {},
{WASM_TABLE_SET(0, WASM_I32V(0), WASM_REF_NULL(sig_index)),
WASM_CALL_INDIRECT(sig_index, WASM_I32V(0), WASM_GET_LOCAL(0)),
kExprEnd});
tester.CompileModule();
tester.CheckHasThrown(func_index, 42);
}
TEST(JsAccess) {
WasmGCTester tester;
const byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});

View File

@ -3490,6 +3490,29 @@ WASM_EXEC_TEST(IfInsideUnreachable) {
CHECK_EQ(17, r.Call());
}
WASM_EXEC_TEST(IndirectNull) {
WasmRunner<int32_t> r(execution_tier);
FunctionSig sig(1, 0, &kWasmI32);
byte sig_index = r.builder().AddSignature(&sig);
r.builder().AddIndirectFunctionTable(nullptr, 1);
BUILD(r, WASM_CALL_INDIRECT(sig_index, WASM_I32V(0)));
CHECK_TRAP(r.Call());
}
WASM_EXEC_TEST(IndirectNullTyped) {
WasmRunner<int32_t> r(execution_tier);
FunctionSig sig(1, 0, &kWasmI32);
byte sig_index = r.builder().AddSignature(&sig);
r.builder().AddIndirectFunctionTable(nullptr, 1,
ValueType::Ref(sig_index, kNullable));
BUILD(r, WASM_CALL_INDIRECT(sig_index, WASM_I32V(0)));
CHECK_TRAP(r.Call());
}
// This test targets binops in Liftoff.
// Initialize a number of local variables to force them into different
// registers, then perform a binary operation on two of the locals.

View File

@ -170,7 +170,8 @@ Handle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) {
}
void TestingModuleBuilder::AddIndirectFunctionTable(
const uint16_t* function_indexes, uint32_t table_size) {
const uint16_t* function_indexes, uint32_t table_size,
ValueType table_type) {
Handle<WasmInstanceObject> instance = instance_object();
uint32_t table_index = static_cast<uint32_t>(test_module_->tables.size());
test_module_->tables.emplace_back();
@ -178,7 +179,7 @@ void TestingModuleBuilder::AddIndirectFunctionTable(
table.initial_size = table_size;
table.maximum_size = table_size;
table.has_maximum_size = true;
table.type = kWasmFuncRef;
table.type = table_type;
{
// Allocate the indirect function table.

View File

@ -201,7 +201,8 @@ class TestingModuleBuilder {
// If function_indexes is {nullptr}, the contents of the table will be
// initialized with null functions.
void AddIndirectFunctionTable(const uint16_t* function_indexes,
uint32_t table_size);
uint32_t table_size,
ValueType table_type = kWasmFuncRef);
uint32_t AddBytes(Vector<const byte> bytes);

View File

@ -3273,7 +3273,7 @@ class WasmInterpreterInternals {
code = result.interpreter_code;
continue; // Do not bump pc.
case CallResult::INVALID_FUNC:
return DoTrap(kTrapFuncInvalid, pc);
return DoTrap(kTrapTableOutOfBounds, pc);
case CallResult::SIGNATURE_MISMATCH:
return DoTrap(kTrapFuncSigMismatch, pc);
}
@ -3319,7 +3319,7 @@ class WasmInterpreterInternals {
continue; // Do not bump pc.
}
case CallResult::INVALID_FUNC:
return DoTrap(kTrapFuncInvalid, pc);
return DoTrap(kTrapTableOutOfBounds, pc);
case CallResult::SIGNATURE_MISMATCH:
return DoTrap(kTrapFuncSigMismatch, pc);
}

View File

@ -79,11 +79,11 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(v1, instance.exports.call1(0));
assertEquals(v2, instance.exports.call1(1));
assertEquals(v3, instance.exports.call1(2));
assertTraps(kTrapFuncInvalid, () => instance.exports.call1(3));
assertTraps(kTrapTableOutOfBounds, () => instance.exports.call1(3));
assertEquals(v1, instance.exports.return_call1(0));
assertEquals(v2, instance.exports.return_call1(1));
assertEquals(v3, instance.exports.return_call1(2));
assertTraps(kTrapFuncInvalid, () => instance.exports.return_call1(3));
assertTraps(kTrapTableOutOfBounds, () => instance.exports.return_call1(3));
// Try to call through the uninitialized table entry.
assertTraps(kTrapFuncSigMismatch, () => instance.exports.call2(0));

View File

@ -55,7 +55,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
print(" --z1--");
assertTraps(kTrapFuncSigMismatch, () => module.exports.main(2, 12, 33));
print(" --w1--");
assertTraps(kTrapFuncInvalid, () => module.exports.main(3, 12, 33));
assertTraps(kTrapTableOutOfBounds, () => module.exports.main(3, 12, 33));
})();
(function Test2() {
@ -99,7 +99,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
print(" --q2--");
assertTraps(kTrapFuncSigMismatch, () => module.exports.main(3, 12, 33));
print(" --t2--");
assertTraps(kTrapFuncInvalid, () => module.exports.main(4, 12, 33));
assertTraps(kTrapTableOutOfBounds, () => module.exports.main(4, 12, 33));
})();
@ -151,7 +151,7 @@ function AddFunctions(builder) {
assertEquals(35, module.exports.main(2, 1));
assertEquals(32, module.exports.main(1, 2));
assertEquals(31, module.exports.main(2, 2));
assertTraps(kTrapFuncInvalid, () => module.exports.main(12, 3));
assertTraps(kTrapTableOutOfBounds, () => module.exports.main(12, 3));
})();
(function ConstBaseTest() {
@ -187,7 +187,7 @@ function AddFunctions(builder) {
assertEquals(31, main(2, i + 1));
assertEquals(33, main(1, i + 2));
assertEquals(66, main(2, i + 2));
assertTraps(kTrapFuncInvalid, () => main(12, 10));
assertTraps(kTrapTableOutOfBounds, () => main(12, 10));
}
})();
@ -224,6 +224,6 @@ function AddFunctions(builder) {
assertEquals(35, main(2, i + 1));
assertEquals(32, main(1, i + 2));
assertEquals(31, main(2, i + 2));
assertTraps(kTrapFuncInvalid, () => main(12, 10));
assertTraps(kTrapTableOutOfBounds, () => main(12, 10));
}
})();

View File

@ -333,8 +333,8 @@ function js_div(a, b) { return (a / b) | 0; }
assertTraps(kTrapFuncSigMismatch, () => i1.exports.main(2));
assertTraps(kTrapFuncSigMismatch, () => i2.exports.main(2));
assertTraps(kTrapFuncInvalid, () => i1.exports.main(3));
assertTraps(kTrapFuncInvalid, () => i2.exports.main(3));
assertTraps(kTrapTableOutOfBounds, () => i1.exports.main(3));
assertTraps(kTrapTableOutOfBounds, () => i2.exports.main(3));
})();
(function MismatchedTableSize() {

View File

@ -93,7 +93,7 @@ function testGrowInternalAnyFuncTable(table_index) {
assertTraps(kTrapFuncSigMismatch, () => instance.exports.call(size - 2));
function growAndCheck(element, grow_by) {
assertEquals(size, instance.exports.size());
assertTraps(kTrapFuncInvalid, () => instance.exports.call(size));
assertTraps(kTrapTableOutOfBounds, () => instance.exports.call(size));
assertEquals(size, instance.exports.grow(dummy_func(element), grow_by));
for (let i = 0; i < grow_by; ++i) {
assertEquals(element, instance.exports.call(size + i));

View File

@ -289,7 +289,7 @@ let id = (() => { // identity exported function
assertInvalidFunction = function(s) {
assertThrows(
() => instances[i].exports.main(s), WebAssembly.RuntimeError,
kTrapMsgs[kTrapFuncInvalid]);
kTrapMsgs[kTrapTableOutOfBounds]);
}
assertInvalidFunction(size);
assertInvalidFunction(size + 1);

View File

@ -32,7 +32,7 @@ function testTrapLocations(instance, expected_stack_length) {
testWasmTrap(0, kTrapDivByZero, 14);
testWasmTrap(1, kTrapMemOutOfBounds, 15);
testWasmTrap(2, kTrapUnreachable, 28);
testWasmTrap(3, kTrapFuncInvalid, 32);
testWasmTrap(3, kTrapTableOutOfBounds, 32);
}
var builder = new WasmModuleBuilder();

View File

@ -678,15 +678,14 @@ let kTrapDivByZero = 2;
let kTrapDivUnrepresentable = 3;
let kTrapRemByZero = 4;
let kTrapFloatUnrepresentable = 5;
let kTrapFuncInvalid = 6;
let kTrapTableOutOfBounds = 6;
let kTrapFuncSigMismatch = 7;
let kTrapTypeError = 8;
let kTrapUnalignedAccess = 9;
let kTrapDataSegmentDropped = 10;
let kTrapElemSegmentDropped = 11;
let kTrapTableOutOfBounds = 12;
let kTrapBrOnExnNull = 13;
let kTrapRethrowNull = 14;
let kTrapBrOnExnNull = 12;
let kTrapRethrowNull = 13;
let kTrapMsgs = [
"unreachable",
@ -695,13 +694,12 @@ let kTrapMsgs = [
"divide result unrepresentable",
"remainder by zero",
"float unrepresentable in integer range",
"invalid index into function table",
"table index is out of bounds",
"function signature mismatch",
"wasm function signature contains illegal type",
"operation does not support unaligned accesses",
"data segment has been dropped",
"element segment has been dropped",
"table access out of bounds",
"br_on_exn on null value",
"rethrowing null value"
];