1 // Copyright (c) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "source/fuzz/fuzzer.h"
16
17 #include <cassert>
18 #include <memory>
19 #include <sstream>
20
21 #include "fuzzer_pass_adjust_memory_operands_masks.h"
22 #include "source/fuzz/fact_manager.h"
23 #include "source/fuzz/fuzzer_context.h"
24 #include "source/fuzz/fuzzer_pass_add_access_chains.h"
25 #include "source/fuzz/fuzzer_pass_add_composite_types.h"
26 #include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
27 #include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
28 #include "source/fuzz/fuzzer_pass_add_dead_continues.h"
29 #include "source/fuzz/fuzzer_pass_add_equation_instructions.h"
30 #include "source/fuzz/fuzzer_pass_add_function_calls.h"
31 #include "source/fuzz/fuzzer_pass_add_global_variables.h"
32 #include "source/fuzz/fuzzer_pass_add_loads.h"
33 #include "source/fuzz/fuzzer_pass_add_local_variables.h"
34 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
35 #include "source/fuzz/fuzzer_pass_add_stores.h"
36 #include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
37 #include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
38 #include "source/fuzz/fuzzer_pass_adjust_loop_controls.h"
39 #include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
40 #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
41 #include "source/fuzz/fuzzer_pass_construct_composites.h"
42 #include "source/fuzz/fuzzer_pass_copy_objects.h"
43 #include "source/fuzz/fuzzer_pass_donate_modules.h"
44 #include "source/fuzz/fuzzer_pass_merge_blocks.h"
45 #include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
46 #include "source/fuzz/fuzzer_pass_outline_functions.h"
47 #include "source/fuzz/fuzzer_pass_permute_blocks.h"
48 #include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
49 #include "source/fuzz/fuzzer_pass_split_blocks.h"
50 #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
51 #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h"
52 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
53 #include "source/fuzz/pseudo_random_generator.h"
54 #include "source/fuzz/transformation_context.h"
55 #include "source/opt/build_module.h"
56 #include "source/spirv_fuzzer_options.h"
57 #include "source/util/make_unique.h"
58
59 namespace spvtools {
60 namespace fuzz {
61
62 namespace {
63 const uint32_t kIdBoundGap = 100;
64
65 const uint32_t kTransformationLimit = 500;
66
67 const uint32_t kChanceOfApplyingAnotherPass = 85;
68
69 // A convenience method to add a fuzzer pass to |passes| with probability 0.5.
70 // All fuzzer passes take |ir_context|, |transformation_context|,
71 // |fuzzer_context| and |transformation_sequence_out| as parameters. Extra
72 // arguments can be provided via |extra_args|.
73 template <typename T, typename... Args>
MaybeAddPass(std::vector<std::unique_ptr<FuzzerPass>> * passes,opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformation_sequence_out,Args &&...extra_args)74 void MaybeAddPass(
75 std::vector<std::unique_ptr<FuzzerPass>>* passes,
76 opt::IRContext* ir_context, TransformationContext* transformation_context,
77 FuzzerContext* fuzzer_context,
78 protobufs::TransformationSequence* transformation_sequence_out,
79 Args&&... extra_args) {
80 if (fuzzer_context->ChooseEven()) {
81 passes->push_back(MakeUnique<T>(ir_context, transformation_context,
82 fuzzer_context, transformation_sequence_out,
83 std::forward<Args>(extra_args)...));
84 }
85 }
86
87 } // namespace
88
89 struct Fuzzer::Impl {
Implspvtools::fuzz::Fuzzer::Impl90 Impl(spv_target_env env, uint32_t random_seed, bool validate_after_each_pass,
91 spv_validator_options options)
92 : target_env(env),
93 seed(random_seed),
94 validate_after_each_fuzzer_pass(validate_after_each_pass),
95 validator_options(options) {}
96
97 bool ApplyPassAndCheckValidity(FuzzerPass* pass,
98 const opt::IRContext& ir_context,
99 const spvtools::SpirvTools& tools) const;
100
101 const spv_target_env target_env; // Target environment.
102 MessageConsumer consumer; // Message consumer.
103 const uint32_t seed; // Seed for random number generator.
104 bool validate_after_each_fuzzer_pass; // Determines whether the validator
105 // should be invoked after every fuzzer
106 // pass.
107 spv_validator_options validator_options; // Options to control validation.
108 };
109
Fuzzer(spv_target_env env,uint32_t seed,bool validate_after_each_fuzzer_pass,spv_validator_options validator_options)110 Fuzzer::Fuzzer(spv_target_env env, uint32_t seed,
111 bool validate_after_each_fuzzer_pass,
112 spv_validator_options validator_options)
113 : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass,
114 validator_options)) {}
115
116 Fuzzer::~Fuzzer() = default;
117
SetMessageConsumer(MessageConsumer c)118 void Fuzzer::SetMessageConsumer(MessageConsumer c) {
119 impl_->consumer = std::move(c);
120 }
121
ApplyPassAndCheckValidity(FuzzerPass * pass,const opt::IRContext & ir_context,const spvtools::SpirvTools & tools) const122 bool Fuzzer::Impl::ApplyPassAndCheckValidity(
123 FuzzerPass* pass, const opt::IRContext& ir_context,
124 const spvtools::SpirvTools& tools) const {
125 pass->Apply();
126 if (validate_after_each_fuzzer_pass) {
127 std::vector<uint32_t> binary_to_validate;
128 ir_context.module()->ToBinary(&binary_to_validate, false);
129 if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
130 validator_options)) {
131 consumer(SPV_MSG_INFO, nullptr, {},
132 "Binary became invalid during fuzzing (set a breakpoint to "
133 "inspect); stopping.");
134 return false;
135 }
136 }
137 return true;
138 }
139
Run(const std::vector<uint32_t> & binary_in,const protobufs::FactSequence & initial_facts,const std::vector<fuzzerutil::ModuleSupplier> & donor_suppliers,std::vector<uint32_t> * binary_out,protobufs::TransformationSequence * transformation_sequence_out) const140 Fuzzer::FuzzerResultStatus Fuzzer::Run(
141 const std::vector<uint32_t>& binary_in,
142 const protobufs::FactSequence& initial_facts,
143 const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
144 std::vector<uint32_t>* binary_out,
145 protobufs::TransformationSequence* transformation_sequence_out) const {
146 // Check compatibility between the library version being linked with and the
147 // header files being used.
148 GOOGLE_PROTOBUF_VERIFY_VERSION;
149
150 spvtools::SpirvTools tools(impl_->target_env);
151 tools.SetMessageConsumer(impl_->consumer);
152 if (!tools.IsValid()) {
153 impl_->consumer(SPV_MSG_ERROR, nullptr, {},
154 "Failed to create SPIRV-Tools interface; stopping.");
155 return Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface;
156 }
157
158 // Initial binary should be valid.
159 if (!tools.Validate(&binary_in[0], binary_in.size(),
160 impl_->validator_options)) {
161 impl_->consumer(SPV_MSG_ERROR, nullptr, {},
162 "Initial binary is invalid; stopping.");
163 return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid;
164 }
165
166 // Build the module from the input binary.
167 std::unique_ptr<opt::IRContext> ir_context = BuildModule(
168 impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
169 assert(ir_context);
170
171 // Make a PRNG from the seed passed to the fuzzer on creation.
172 PseudoRandomGenerator random_generator(impl_->seed);
173
174 // The fuzzer will introduce new ids into the module. The module's id bound
175 // gives the smallest id that can be used for this purpose. We add an offset
176 // to this so that there is a sizeable gap between the ids used in the
177 // original module and the ids used for fuzzing, as a readability aid.
178 //
179 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the
180 // case where the maximum id bound is reached.
181 auto minimum_fresh_id = ir_context->module()->id_bound() + kIdBoundGap;
182 FuzzerContext fuzzer_context(&random_generator, minimum_fresh_id);
183
184 FactManager fact_manager;
185 fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
186 TransformationContext transformation_context(&fact_manager,
187 impl_->validator_options);
188
189 // Add some essential ingredients to the module if they are not already
190 // present, such as boolean constants.
191 FuzzerPassAddUsefulConstructs add_useful_constructs(
192 ir_context.get(), &transformation_context, &fuzzer_context,
193 transformation_sequence_out);
194 if (!impl_->ApplyPassAndCheckValidity(&add_useful_constructs, *ir_context,
195 tools)) {
196 return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
197 }
198
199 // Apply some semantics-preserving passes.
200 std::vector<std::unique_ptr<FuzzerPass>> passes;
201 while (passes.empty()) {
202 MaybeAddPass<FuzzerPassAddAccessChains>(
203 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
204 transformation_sequence_out);
205 MaybeAddPass<FuzzerPassAddCompositeTypes>(
206 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
207 transformation_sequence_out);
208 MaybeAddPass<FuzzerPassAddDeadBlocks>(
209 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
210 transformation_sequence_out);
211 MaybeAddPass<FuzzerPassAddDeadBreaks>(
212 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
213 transformation_sequence_out);
214 MaybeAddPass<FuzzerPassAddDeadContinues>(
215 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
216 transformation_sequence_out);
217 MaybeAddPass<FuzzerPassAddEquationInstructions>(
218 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
219 transformation_sequence_out);
220 MaybeAddPass<FuzzerPassAddFunctionCalls>(
221 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
222 transformation_sequence_out);
223 MaybeAddPass<FuzzerPassAddGlobalVariables>(
224 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
225 transformation_sequence_out);
226 MaybeAddPass<FuzzerPassAddLoads>(&passes, ir_context.get(),
227 &transformation_context, &fuzzer_context,
228 transformation_sequence_out);
229 MaybeAddPass<FuzzerPassAddLocalVariables>(
230 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
231 transformation_sequence_out);
232 MaybeAddPass<FuzzerPassAddStores>(&passes, ir_context.get(),
233 &transformation_context, &fuzzer_context,
234 transformation_sequence_out);
235 MaybeAddPass<FuzzerPassApplyIdSynonyms>(
236 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
237 transformation_sequence_out);
238 MaybeAddPass<FuzzerPassConstructComposites>(
239 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
240 transformation_sequence_out);
241 MaybeAddPass<FuzzerPassCopyObjects>(
242 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
243 transformation_sequence_out);
244 MaybeAddPass<FuzzerPassDonateModules>(
245 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
246 transformation_sequence_out, donor_suppliers);
247 MaybeAddPass<FuzzerPassMergeBlocks>(
248 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
249 transformation_sequence_out);
250 MaybeAddPass<FuzzerPassObfuscateConstants>(
251 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
252 transformation_sequence_out);
253 MaybeAddPass<FuzzerPassOutlineFunctions>(
254 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
255 transformation_sequence_out);
256 MaybeAddPass<FuzzerPassPermuteBlocks>(
257 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
258 transformation_sequence_out);
259 MaybeAddPass<FuzzerPassPermuteFunctionParameters>(
260 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
261 transformation_sequence_out);
262 MaybeAddPass<FuzzerPassSplitBlocks>(
263 &passes, ir_context.get(), &transformation_context, &fuzzer_context,
264 transformation_sequence_out);
265 }
266
267 bool is_first = true;
268 while (static_cast<uint32_t>(
269 transformation_sequence_out->transformation_size()) <
270 kTransformationLimit &&
271 (is_first ||
272 fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) {
273 is_first = false;
274 if (!impl_->ApplyPassAndCheckValidity(
275 passes[fuzzer_context.RandomIndex(passes)].get(), *ir_context,
276 tools)) {
277 return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
278 }
279 }
280
281 // Now apply some passes that it does not make sense to apply repeatedly,
282 // as they do not unlock other passes.
283 std::vector<std::unique_ptr<FuzzerPass>> final_passes;
284 MaybeAddPass<FuzzerPassAdjustFunctionControls>(
285 &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
286 transformation_sequence_out);
287 MaybeAddPass<FuzzerPassAdjustLoopControls>(
288 &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
289 transformation_sequence_out);
290 MaybeAddPass<FuzzerPassAdjustMemoryOperandsMasks>(
291 &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
292 transformation_sequence_out);
293 MaybeAddPass<FuzzerPassAdjustSelectionControls>(
294 &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
295 transformation_sequence_out);
296 MaybeAddPass<FuzzerPassAddNoContractionDecorations>(
297 &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
298 transformation_sequence_out);
299 MaybeAddPass<FuzzerPassSwapCommutableOperands>(
300 &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
301 transformation_sequence_out);
302 MaybeAddPass<FuzzerPassToggleAccessChainInstruction>(
303 &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
304 transformation_sequence_out);
305 for (auto& pass : final_passes) {
306 if (!impl_->ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) {
307 return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
308 }
309 }
310
311 // Encode the module as a binary.
312 ir_context->module()->ToBinary(binary_out, false);
313
314 return Fuzzer::FuzzerResultStatus::kComplete;
315 }
316
317 } // namespace fuzz
318 } // namespace spvtools
319