Skip to content

Commit 72362c9

Browse files
shaoboyan091Dawn LUCI CQ
authored andcommitted
[Core] Fix Writer Fuzz by Validating Immediate Blocks
This CL add validations about immediate block. It ensures immediate block members offsets are valid and make sure there is at most one user-declared immediate data. Apply this validation rules to all backends. Bug: 366291600,442260935 Change-Id: I0713491dc3f968627a5aaec3cbd7a2e80c3b4f58 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/261414 Reviewed-by: dan sinclair <[email protected]> Commit-Queue: Shaobo Yan <[email protected]>
1 parent 7e9df77 commit 72362c9

File tree

10 files changed

+349
-134
lines changed

10 files changed

+349
-134
lines changed

src/tint/lang/core/ir/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ cc_library(
240240
"validator_call_test.cc",
241241
"validator_flow_control_test.cc",
242242
"validator_function_test.cc",
243+
"validator_immediate_test.cc",
243244
"validator_test.cc",
244245
"validator_test.h",
245246
"validator_type_test.cc",

src/tint/lang/core/ir/BUILD.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ tint_add_target(tint_lang_core_ir_test test
246246
lang/core/ir/validator_call_test.cc
247247
lang/core/ir/validator_flow_control_test.cc
248248
lang/core/ir/validator_function_test.cc
249+
lang/core/ir/validator_immediate_test.cc
249250
lang/core/ir/validator_test.cc
250251
lang/core/ir/validator_test.h
251252
lang/core/ir/validator_type_test.cc

src/tint/lang/core/ir/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ if (tint_build_unittests) {
241241
"validator_call_test.cc",
242242
"validator_flow_control_test.cc",
243243
"validator_function_test.cc",
244+
"validator_immediate_test.cc",
244245
"validator_test.cc",
245246
"validator_test.h",
246247
"validator_type_test.cc",

src/tint/lang/core/ir/validator.cc

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <string>
3434
#include <string_view>
3535
#include <utility>
36+
#include <vector>
3637

3738
#include "src/tint/lang/core/intrinsic/table.h"
3839
#include "src/tint/lang/core/ir/access.h"
@@ -4306,6 +4307,74 @@ Result<SuccessType> ValidateAndDumpIfNeeded([[maybe_unused]] const Module& ir,
43064307
return Success;
43074308
}
43084309

4310+
Result<uint32_t> ValidateSingleUserImmediate(const Module& ir) {
4311+
uint32_t user_immediate_size = 0;
4312+
for (auto* inst : *ir.root_block) {
4313+
auto* var = inst->As<core::ir::Var>();
4314+
if (!var) {
4315+
continue;
4316+
}
4317+
auto* ptr = var->Result()->Type()->As<core::type::Pointer>();
4318+
if (!ptr) {
4319+
continue;
4320+
}
4321+
if (ptr->AddressSpace() == core::AddressSpace::kImmediate) {
4322+
if (user_immediate_size > 0) {
4323+
return Failure("module contains multiple user-declared immediate data");
4324+
}
4325+
user_immediate_size = tint::RoundUp(4u, ptr->StoreType()->Size());
4326+
}
4327+
}
4328+
return user_immediate_size; // 0 if none found
4329+
}
4330+
4331+
Result<SuccessType> ValidateInternalImmediateOffset(uint32_t max_immediate_block_size,
4332+
uint32_t user_immediate_data_size,
4333+
const std::vector<ImmediateInfo>& immediates) {
4334+
struct Range {
4335+
uint32_t offset;
4336+
uint32_t size;
4337+
};
4338+
std::vector<Range> ranges;
4339+
ranges.reserve(immediates.size());
4340+
for (size_t i = 0; i < immediates.size(); ++i) {
4341+
const auto& info = immediates[i];
4342+
if (info.size == 0) {
4343+
return Failure("immediate data #" + std::to_string(i) + " has zero size");
4344+
}
4345+
if (info.offset & 0x3) {
4346+
return Failure("immediate data #" + std::to_string(i) +
4347+
" offset is not 4-byte aligned");
4348+
}
4349+
if (info.offset < user_immediate_data_size) {
4350+
return Failure("immediate data #" + std::to_string(i) +
4351+
" overlaps user-declared immediate data region");
4352+
}
4353+
if (info.offset > max_immediate_block_size) {
4354+
return Failure("immediate data #" + std::to_string(i) +
4355+
" offset exceeds maximum immediate block size");
4356+
}
4357+
if (info.size > max_immediate_block_size ||
4358+
info.offset > max_immediate_block_size - info.size) {
4359+
return Failure("immediate data #" + std::to_string(i) +
4360+
" (offset + size) exceeds maximum immediate block size");
4361+
}
4362+
ranges.push_back(Range{info.offset, info.size});
4363+
}
4364+
std::sort(ranges.begin(), ranges.end(),
4365+
[](const Range& a, const Range& b) { return a.offset < b.offset; });
4366+
for (size_t i = 1; i < ranges.size(); ++i) {
4367+
const auto& prev = ranges[i - 1];
4368+
const auto& cur = ranges[i];
4369+
if (cur.offset < prev.offset + prev.size) {
4370+
return Failure("immediate data ranges overlap (offset " + std::to_string(prev.offset) +
4371+
" size " + std::to_string(prev.size) + " and offset " +
4372+
std::to_string(cur.offset) + " size " + std::to_string(cur.size) + ")");
4373+
}
4374+
}
4375+
return Success;
4376+
}
4377+
43094378
} // namespace tint::core::ir
43104379

43114380
namespace std {

src/tint/lang/core/ir/validator.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#ifndef SRC_TINT_LANG_CORE_IR_VALIDATOR_H_
2929
#define SRC_TINT_LANG_CORE_IR_VALIDATOR_H_
3030

31+
#include <vector>
3132
#include "src/tint/utils/containers/enum_set.h"
3233
#include "src/tint/utils/result.h"
3334

@@ -105,6 +106,26 @@ Result<SuccessType> ValidateAndDumpIfNeeded(const Module& ir,
105106
Capabilities capabilities = {},
106107
std::string_view timing = "before");
107108

109+
// Scans the module root block for user-declared immediate data (module-scope
110+
// `var<immediate>` declarations). Returns Success if there is at most one.
111+
// On success, the returned uint32_t is the 4-byte rounded-up size of the
112+
// user-declared immediate data (or 0 if none present). Fails if multiple
113+
// immediates are declared.
114+
Result<uint32_t> ValidateSingleUserImmediate(const Module& ir);
115+
116+
// Immediate data validation helpers
117+
struct ImmediateInfo {
118+
uint32_t offset = 0;
119+
uint32_t size = 0;
120+
};
121+
122+
// Validates internal (implementation-provided) immediates. Offsets must not overlap each other
123+
// or the user-declared immediate data (if present). `user_immediate_data_size` is the 4-byte
124+
// rounded size of the user immediate block, or 0 if none exists.
125+
Result<SuccessType> ValidateInternalImmediateOffset(uint32_t max_immediate_block_size,
126+
uint32_t user_immediate_data_size,
127+
const std::vector<ImmediateInfo>& immediates);
128+
108129
} // namespace tint::core::ir
109130

110131
#endif // SRC_TINT_LANG_CORE_IR_VALIDATOR_H_
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright 2025 The Dawn & Tint Authors
2+
//
3+
// Redistribution and use in source and binary forms, with or without
4+
// modification, are permitted provided that the following conditions are met:
5+
//
6+
// 1. Redistributions of source code must retain the above copyright notice, this
7+
// list of conditions and the following disclaimer.
8+
//
9+
// 2. Redistributions in binary form must reproduce the above copyright notice,
10+
// this list of conditions and the following disclaimer in the documentation
11+
// and/or other materials provided with the distribution.
12+
//
13+
// 3. Neither the name of the copyright holder nor the names of its
14+
// contributors may be used to endorse or promote products derived from
15+
// this software without specific prior written permission.
16+
//
17+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23+
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
28+
#include "src/tint/lang/core/ir/validator_test.h"
29+
30+
#include "gtest/gtest.h"
31+
32+
#include "src/tint/lang/core/ir/builder.h"
33+
#include "src/tint/lang/core/ir/validator.h"
34+
#include "src/tint/lang/core/type/pointer.h"
35+
#include "src/tint/lang/core/type/struct.h"
36+
37+
namespace tint::core::ir {
38+
39+
using namespace tint::core::fluent_types; // NOLINT
40+
41+
// Helper to make a module-scope immediate variable with given store type.
42+
static Var* MakeImmediate(Builder& b,
43+
Module& mod,
44+
const char* name,
45+
const core::type::Type* store_ty) {
46+
auto* v = b.Var(name, core::AddressSpace::kImmediate, store_ty);
47+
mod.root_block->Append(v);
48+
return v;
49+
}
50+
51+
class IR_ValidatorImmediateTest : public IR_ValidatorTest {};
52+
53+
TEST_F(IR_ValidatorImmediateTest, ValidateSingleUserImmediate_None) {
54+
// Empty module (no immediates)
55+
auto res = ValidateSingleUserImmediate(mod);
56+
ASSERT_EQ(res, Success) << res.Failure();
57+
EXPECT_EQ(res.Get(), 0u);
58+
}
59+
60+
TEST_F(IR_ValidatorImmediateTest, ValidateSingleUserImmediate_One) {
61+
// Single immediate variable with size 12 -> rounded to 12 (already multiple of 4)
62+
auto* s = ty.Struct(mod.symbols.New("S"), {{mod.symbols.New("a"), ty.array<i32, 3>()}});
63+
MakeImmediate(b, mod, "immediate_data", s);
64+
auto res = ValidateSingleUserImmediate(mod);
65+
ASSERT_EQ(res, Success) << res.Failure();
66+
EXPECT_EQ(res.Get(), 12u); // 3 * 4 bytes
67+
}
68+
69+
// Round-up behavior is implicitly covered by using a struct whose size is already a multiple of 4.
70+
71+
TEST_F(IR_ValidatorImmediateTest, ValidateSingleUserImmediate_Multiple) {
72+
auto* s = ty.Struct(mod.symbols.New("S"), {{mod.symbols.New("a"), ty.i32()}});
73+
MakeImmediate(b, mod, "immediate0", s);
74+
MakeImmediate(b, mod, "immediate1", s);
75+
auto res = ValidateSingleUserImmediate(mod);
76+
ASSERT_NE(res, Success);
77+
EXPECT_THAT(res.Failure().reason,
78+
::testing::HasSubstr("multiple user-declared immediate data"));
79+
}
80+
81+
TEST_F(IR_ValidatorImmediateTest, ValidateInternalImmediateOffset_BasicSuccess) {
82+
// No user immediate, two internal ranges non-overlapping
83+
std::vector<ImmediateInfo> immediates = {
84+
{0u, 16u}, // starts after user (none), size 16
85+
{32u, 8u},
86+
};
87+
auto res = ValidateInternalImmediateOffset(0x1000u, 0u, immediates);
88+
ASSERT_EQ(res, Success) << res.Failure();
89+
}
90+
91+
TEST_F(IR_ValidatorImmediateTest, ValidateInternalImmediateOffset_OverlapUser) {
92+
// User immediate occupies first 32 bytes
93+
std::vector<ImmediateInfo> immediates = {
94+
{16u, 8u},
95+
};
96+
auto res = ValidateInternalImmediateOffset(0x1000u, 32u, immediates);
97+
ASSERT_NE(res, Success);
98+
EXPECT_THAT(res.Failure().reason,
99+
::testing::HasSubstr("overlaps user-declared immediate data"));
100+
}
101+
102+
TEST_F(IR_ValidatorImmediateTest, ValidateInternalImmediateOffset_ZeroSize) {
103+
std::vector<ImmediateInfo> immediates = {
104+
{64u, 0u},
105+
};
106+
auto res = ValidateInternalImmediateOffset(0x1000u, 0u, immediates);
107+
ASSERT_NE(res, Success);
108+
EXPECT_THAT(res.Failure().reason, ::testing::HasSubstr("zero size"));
109+
}
110+
111+
TEST_F(IR_ValidatorImmediateTest, ValidateInternalImmediateOffset_Misaligned) {
112+
std::vector<ImmediateInfo> immediates = {
113+
{10u, 4u},
114+
};
115+
auto res = ValidateInternalImmediateOffset(0x1000u, 0u, immediates);
116+
ASSERT_NE(res, Success);
117+
EXPECT_THAT(res.Failure().reason, ::testing::HasSubstr("not 4-byte aligned"));
118+
}
119+
120+
TEST_F(IR_ValidatorImmediateTest, ValidateInternalImmediateOffset_ExceedsMax) {
121+
std::vector<ImmediateInfo> immediates = {
122+
{0x2000u, 4u},
123+
};
124+
auto res = ValidateInternalImmediateOffset(0x1000u, 0u, immediates);
125+
ASSERT_NE(res, Success);
126+
EXPECT_THAT(res.Failure().reason, ::testing::HasSubstr("offset exceeds"));
127+
}
128+
129+
TEST_F(IR_ValidatorImmediateTest, ValidateInternalImmediateOffset_OffsetPlusSizeExceeds) {
130+
std::vector<ImmediateInfo> immediates = {
131+
{0x0FFCu, 16u}, // 0x0FFC + 16 > 0x1000
132+
};
133+
auto res = ValidateInternalImmediateOffset(0x1000u, 0u, immediates);
134+
ASSERT_NE(res, Success);
135+
EXPECT_THAT(res.Failure().reason, ::testing::HasSubstr("(offset + size)"));
136+
}
137+
138+
TEST_F(IR_ValidatorImmediateTest, ValidateInternalImmediateOffset_RangesOverlap) {
139+
std::vector<ImmediateInfo> immediates = {
140+
{64u, 32u}, {80u, 16u}, // overlaps previous (64..96) vs (80..96)
141+
};
142+
auto res = ValidateInternalImmediateOffset(0x1000u, 0u, immediates);
143+
ASSERT_NE(res, Success);
144+
EXPECT_THAT(res.Failure().reason, ::testing::HasSubstr("ranges overlap"));
145+
}
146+
147+
} // namespace tint::core::ir

src/tint/lang/glsl/writer/writer.cc

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727

2828
#include "src/tint/lang/glsl/writer/writer.h"
2929

30+
#include <vector>
3031
#include "src/tint/lang/core/ir/core_builtin_call.h"
3132
#include "src/tint/lang/core/ir/module.h"
33+
#include "src/tint/lang/core/ir/validator.h"
3234
#include "src/tint/lang/core/ir/var.h"
3335
#include "src/tint/lang/core/type/binding_array.h"
3436
#include "src/tint/lang/core/type/pointer.h"
@@ -63,9 +65,8 @@ Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& optio
6365
// Make sure that every texture variable is in the texture_builtins_from_uniform binding list,
6466
// otherwise TextureBuiltinsFromUniform will fail.
6567
// TODO(https://issues.chromium.org/427172887) Be more precise for the
66-
// texture_builtins_from_uniform checks. Also make sure there is at most one user-declared
67-
// immediate, and make a note of its size.
68-
uint32_t user_immediate_size = 0;
68+
// texture_builtins_from_uniform checks. Also ensure there is at most one user-declared
69+
// immediate.
6970
for (auto* inst : *ir.root_block) {
7071
auto* var = inst->As<core::ir::Var>();
7172

@@ -125,15 +126,16 @@ Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& optio
125126
}
126127
}
127128

128-
if (ptr->AddressSpace() == core::AddressSpace::kImmediate) {
129-
if (user_immediate_size > 0) {
130-
// We've already seen a user-declared immediate data.
131-
return Failure("multiple user-declared immediate data");
132-
}
133-
user_immediate_size = tint::RoundUp(4u, ptr->StoreType()->Size());
134-
}
129+
// user-declared immediate validation handled later by helper.
130+
}
131+
132+
auto user_immediate_res = core::ir::ValidateSingleUserImmediate(ir);
133+
if (user_immediate_res != Success) {
134+
return user_immediate_res.Failure();
135135
}
136136

137+
uint32_t user_immediate_size = user_immediate_res.Get();
138+
137139
// Check for calls to unsupported builtin functions.
138140
for (auto* inst : ir.Instructions()) {
139141
auto* call = inst->As<core::ir::CoreBuiltinCall>();
@@ -184,40 +186,22 @@ Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& optio
184186
}
185187
}
186188

187-
static constexpr uint32_t kMaxOffset = 0x1000;
188-
Hashset<uint32_t, 4> immediate_word_offsets;
189-
auto check_immediate_offset = [&](uint32_t offset) {
190-
// Excessive values can cause OOM / timeouts when padding structures in the printer.
191-
if (offset > kMaxOffset) {
192-
return false;
193-
}
194-
// Offset must be 4-byte aligned.
195-
if (offset & 0x3) {
196-
return false;
189+
{
190+
std::vector<core::ir::ImmediateInfo> immediates;
191+
if (options.first_instance_offset) {
192+
immediates.push_back({*options.first_instance_offset, 4u});
197193
}
198-
// Offset must not have already been used.
199-
if (!immediate_word_offsets.Add(offset >> 2)) {
200-
return false;
194+
if (options.first_vertex_offset) {
195+
immediates.push_back({*options.first_vertex_offset, 4u});
201196
}
202-
// Offset must be after the user-defined immediate data.
203-
if (offset < user_immediate_size) {
204-
return false;
197+
if (options.depth_range_offsets) {
198+
immediates.push_back({options.depth_range_offsets->max, 4u});
199+
immediates.push_back({options.depth_range_offsets->min, 4u});
205200
}
206-
return true;
207-
};
208-
209-
if (options.first_instance_offset && !check_immediate_offset(*options.first_instance_offset)) {
210-
return Failure("invalid offset for first_instance_offset immediate data");
211-
}
212-
213-
if (options.first_vertex_offset && !check_immediate_offset(*options.first_vertex_offset)) {
214-
return Failure("invalid offset for first_vertex_offset immediate data");
215-
}
216-
217-
if (options.depth_range_offsets) {
218-
if (!check_immediate_offset(options.depth_range_offsets->max) ||
219-
!check_immediate_offset(options.depth_range_offsets->min)) {
220-
return Failure("invalid offsets for depth range immediate data");
201+
if (auto res =
202+
core::ir::ValidateInternalImmediateOffset(0x1000, user_immediate_size, immediates);
203+
res != Success) {
204+
return res.Failure();
221205
}
222206
}
223207

0 commit comments

Comments
 (0)