[tint][ir][fuzz] Prevent fuzzer from crashing on malformed accesses
- Adds support for variable length operands to checking logic
Fixes: 353259704
Change-Id: I22dd9d03419bff9ae551b284f0d72ea1be13a754
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/198560
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/tint/lang/core/ir/access.h b/src/tint/lang/core/ir/access.h
index 55a332c..0df6ee4 100644
--- a/src/tint/lang/core/ir/access.h
+++ b/src/tint/lang/core/ir/access.h
@@ -44,6 +44,12 @@
/// The base offset in Operands() for the access indices
static constexpr size_t kIndicesOperandOffset = 1;
+ /// The fixed number of results returned by this instruction
+ static constexpr size_t kNumResults = 1;
+
+ /// The minimum number of operands used by this instruction
+ static constexpr size_t kMinNumOperands = 1;
+
/// Constructor (no results, no operands)
Access();
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index d3b7ad0..e8b1694 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -316,6 +316,16 @@
/// @returns true if the operand is not null
bool CheckOperandNotNull(const ir::Instruction* inst, size_t idx);
+ /// Checks the number of operands provided to @p inst and that none of them are null.
+ /// @param inst the instruction
+ /// @param min_count the minimum number of operands to expect
+ /// @param max_count the maximum number of operands to expect, if not set, than only the minimum
+ /// number is checked.
+ /// @returns true if the number of operands is in the expected range and none are null
+ bool CheckOperands(const ir::Instruction* inst,
+ size_t min_count,
+ std::optional<size_t> max_count);
+
/// Checks the number of operands for @p inst are exactly equal to @p count and that none of
/// them are null.
/// @param inst the instruction
@@ -323,6 +333,19 @@
/// @returns true if the operands count is as expected and none are null
bool CheckOperands(const ir::Instruction* inst, size_t count);
+ /// Checks the number of results for @p inst are exactly equal to @p num_results and the number
+ /// of operands is correctly. Both results and operands are confirmed to be non-null.
+ /// @param inst the instruction
+ /// @param num_results expected number of results for the instruction
+ /// @param min_operands the minimum number of operands to expect
+ /// @param max_operands the maximum number of operands to expect, if not set, than only the
+ /// minimum number is checked.
+ /// @returns true if the result and operand counts are as expected and none are null
+ bool CheckResultsAndOperandRange(const ir::Instruction* inst,
+ size_t num_results,
+ size_t min_operands,
+ std::optional<size_t> max_operands);
+
/// Checks the number of results and operands for @p inst are exactly equal to num_results
/// and num_operands, respectively, and that none of them are null.
/// @param inst the instruction
@@ -802,6 +825,35 @@
return true;
}
+bool Validator::CheckOperands(const ir::Instruction* inst,
+ size_t min_count,
+ std::optional<size_t> max_count) {
+ if (TINT_UNLIKELY(inst->Operands().Length() < min_count)) {
+ if (max_count.has_value()) {
+ AddError(inst) << "expected between " << min_count << " and " << max_count.value()
+ << " operands, got " << inst->Operands().Length();
+ } else {
+ AddError(inst) << "expected at least " << min_count << " operands, got "
+ << inst->Operands().Length();
+ }
+ return false;
+ }
+
+ if (TINT_UNLIKELY(max_count.has_value() && inst->Operands().Length() > max_count.value())) {
+ AddError(inst) << "expected between " << min_count << " and " << max_count.value()
+ << " operands, got " << inst->Operands().Length();
+ return false;
+ }
+
+ bool passed = true;
+ for (size_t i = 0; i < inst->Operands().Length(); i++) {
+ if (TINT_UNLIKELY(!CheckOperandNotNull(inst, i))) {
+ passed = false;
+ }
+ }
+ return passed;
+}
+
bool Validator::CheckOperands(const ir::Instruction* inst, size_t count) {
if (TINT_UNLIKELY(inst->Operands().Length() != count)) {
AddError(inst) << "expected exactly " << count << " operands, got "
@@ -818,6 +870,16 @@
return passed;
}
+bool Validator::CheckResultsAndOperandRange(const ir::Instruction* inst,
+ size_t num_results,
+ size_t min_operands,
+ std::optional<size_t> max_operands = {}) {
+ // Intentionally avoiding short-circuiting here
+ bool results_passed = CheckResults(inst, num_results);
+ bool operands_passed = CheckOperands(inst, min_operands, max_operands);
+ return results_passed && operands_passed;
+}
+
bool Validator::CheckResultsAndOperands(const ir::Instruction* inst,
size_t num_results,
size_t num_operands) {
@@ -1200,8 +1262,7 @@
}
void Validator::CheckAccess(const Access* a) {
- if (!a->Object()) {
- AddError(a, Access::kObjectOperandOffset) << "null object";
+ if (!CheckResultsAndOperandRange(a, Access::kNumResults, Access::kMinNumOperands)) {
return;
}
@@ -1670,7 +1731,7 @@
}
void Validator::CheckLoad(const Load* l) {
- if (TINT_UNLIKELY(!CheckResultsAndOperands(l, Load::kNumResults, Load::kNumOperands))) {
+ if (!CheckResultsAndOperands(l, Load::kNumResults, Load::kNumOperands)) {
return;
}
@@ -1690,7 +1751,7 @@
}
void Validator::CheckStore(const Store* s) {
- if (TINT_UNLIKELY(!CheckResultsAndOperands(s, Store::kNumResults, Store::kNumOperands))) {
+ if (!CheckResultsAndOperands(s, Store::kNumResults, Store::kNumOperands)) {
return;
}
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index dc96213..8aa1830 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -849,22 +849,23 @@
)");
}
-TEST_F(IR_ValidatorTest, Access_NoObject) {
+TEST_F(IR_ValidatorTest, Access_NoOperands) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.vec3<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
- b.Access(ty.f32(), nullptr);
+ auto* access = b.Access(ty.f32(), obj, 0_i);
+ access->ClearOperands();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
- R"(:3:21 error: access: null object
- %3:f32 = access undef
- ^^^^^
+ R"(:3:14 error: access: expected at least 1 operands, got 0
+ %3:f32 = access
+ ^^^^^^
:2:3 note: in block
$B1: {
@@ -873,7 +874,98 @@
note: # Disassembly
%my_func = func(%2:vec3<f32>):void {
$B1: {
- %3:f32 = access undef
+ %3:f32 = access
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Access_NoResults) {
+ auto* f = b.Function("my_func", ty.void_());
+ auto* obj = b.FunctionParam(ty.vec3<f32>());
+ f->SetParams({obj});
+
+ b.Append(f->Block(), [&] {
+ auto* access = b.Access(ty.f32(), obj, 0_i);
+ access->ClearResults();
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:3:13 error: access: expected exactly 1 results, got 0
+ undef = access %2, 0i
+ ^^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func(%2:vec3<f32>):void {
+ $B1: {
+ undef = access %2, 0i
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Access_NullObject) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ b.Access(ty.f32(), nullptr);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:3:21 error: access: operand is undefined
+ %2:f32 = access undef
+ ^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ %2:f32 = access undef
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Access_NullIndex) {
+ auto* f = b.Function("my_func", ty.void_());
+ auto* obj = b.FunctionParam(ty.vec3<f32>());
+ f->SetParams({obj});
+
+ b.Append(f->Block(), [&] {
+ b.Access(ty.f32(), obj, nullptr);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:3:25 error: access: operand is undefined
+ %3:f32 = access %2, undef
+ ^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func(%2:vec3<f32>):void {
+ $B1: {
+ %3:f32 = access %2, undef
ret
}
}