// Copyright (c) 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "source/fuzz/fuzzer.h" #include #include #include #include #include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass_add_access_chains.h" #include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h" #include "source/fuzz/fuzzer_pass_add_composite_extract.h" #include "source/fuzz/fuzzer_pass_add_composite_inserts.h" #include "source/fuzz/fuzzer_pass_add_composite_types.h" #include "source/fuzz/fuzzer_pass_add_copy_memory.h" #include "source/fuzz/fuzzer_pass_add_dead_blocks.h" #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" #include "source/fuzz/fuzzer_pass_add_dead_continues.h" #include "source/fuzz/fuzzer_pass_add_equation_instructions.h" #include "source/fuzz/fuzzer_pass_add_function_calls.h" #include "source/fuzz/fuzzer_pass_add_global_variables.h" #include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h" #include "source/fuzz/fuzzer_pass_add_loads.h" #include "source/fuzz/fuzzer_pass_add_local_variables.h" #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" #include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h" #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" #include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" #include "source/fuzz/fuzzer_pass_add_parameters.h" #include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" #include "source/fuzz/fuzzer_pass_add_stores.h" #include "source/fuzz/fuzzer_pass_add_synonyms.h" #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h" #include "source/fuzz/fuzzer_pass_adjust_branch_weights.h" #include "source/fuzz/fuzzer_pass_adjust_function_controls.h" #include "source/fuzz/fuzzer_pass_adjust_loop_controls.h" #include "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h" #include "source/fuzz/fuzzer_pass_adjust_selection_controls.h" #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h" #include "source/fuzz/fuzzer_pass_construct_composites.h" #include "source/fuzz/fuzzer_pass_copy_objects.h" #include "source/fuzz/fuzzer_pass_donate_modules.h" #include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h" #include "source/fuzz/fuzzer_pass_expand_vector_reductions.h" #include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h" #include "source/fuzz/fuzzer_pass_inline_functions.h" #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h" #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h" #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" #include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h" #include "source/fuzz/fuzzer_pass_merge_blocks.h" #include "source/fuzz/fuzzer_pass_merge_function_returns.h" #include "source/fuzz/fuzzer_pass_mutate_pointers.h" #include "source/fuzz/fuzzer_pass_obfuscate_constants.h" #include "source/fuzz/fuzzer_pass_outline_functions.h" #include "source/fuzz/fuzzer_pass_permute_blocks.h" #include "source/fuzz/fuzzer_pass_permute_function_parameters.h" #include "source/fuzz/fuzzer_pass_permute_instructions.h" #include "source/fuzz/fuzzer_pass_permute_phi_operands.h" #include "source/fuzz/fuzzer_pass_propagate_instructions_down.h" #include "source/fuzz/fuzzer_pass_propagate_instructions_up.h" #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" #include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" #include "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h" #include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h" #include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h" #include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h" #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" #include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" #include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h" #include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h" #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h" #include "source/fuzz/fuzzer_pass_split_blocks.h" #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h" #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h" #include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h" #include "source/fuzz/pass_management/repeated_pass_manager.h" #include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h" #include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h" #include "source/fuzz/pass_management/repeated_pass_manager_simple.h" #include "source/fuzz/pass_management/repeated_pass_recommender_standard.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation_context.h" #include "source/opt/build_module.h" #include "source/spirv_fuzzer_options.h" #include "source/util/make_unique.h" namespace spvtools { namespace fuzz { namespace { const uint32_t kIdBoundGap = 100; } // namespace Fuzzer::Fuzzer(spv_target_env target_env, MessageConsumer consumer, const std::vector& binary_in, const protobufs::FactSequence& initial_facts, const std::vector& donor_suppliers, std::unique_ptr random_generator, bool enable_all_passes, RepeatedPassStrategy repeated_pass_strategy, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options) : target_env_(target_env), consumer_(std::move(consumer)), binary_in_(binary_in), initial_facts_(initial_facts), donor_suppliers_(donor_suppliers), random_generator_(std::move(random_generator)), enable_all_passes_(enable_all_passes), repeated_pass_strategy_(repeated_pass_strategy), validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass), validator_options_(validator_options), num_repeated_passes_applied_(0), ir_context_(nullptr), fuzzer_context_(nullptr), transformation_context_(nullptr), transformation_sequence_out_() {} Fuzzer::~Fuzzer() = default; template void Fuzzer::MaybeAddRepeatedPass(uint32_t percentage_chance_of_adding_pass, RepeatedPassInstances* pass_instances, Args&&... extra_args) { if (enable_all_passes_ || fuzzer_context_->ChoosePercentage(percentage_chance_of_adding_pass)) { pass_instances->SetPass(MakeUnique( ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(), &transformation_sequence_out_, std::forward(extra_args)...)); } } template void Fuzzer::MaybeAddFinalPass(std::vector>* passes, Args&&... extra_args) { if (enable_all_passes_ || fuzzer_context_->ChooseEven()) { passes->push_back(MakeUnique( ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(), &transformation_sequence_out_, std::forward(extra_args)...)); } } bool Fuzzer::ApplyPassAndCheckValidity(FuzzerPass* pass) const { pass->Apply(); return !validate_after_each_fuzzer_pass_ || fuzzerutil::IsValidAndWellFormed(ir_context_.get(), validator_options_, consumer_); } Fuzzer::FuzzerResult Fuzzer::Run() { // Check compatibility between the library version being linked with and the // header files being used. GOOGLE_PROTOBUF_VERIFY_VERSION; assert(ir_context_ == nullptr && fuzzer_context_ == nullptr && transformation_context_ == nullptr && transformation_sequence_out_.transformation_size() == 0 && "'Run' must not be invoked more than once."); spvtools::SpirvTools tools(target_env_); tools.SetMessageConsumer(consumer_); if (!tools.IsValid()) { consumer_(SPV_MSG_ERROR, nullptr, {}, "Failed to create SPIRV-Tools interface; stopping."); return {Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface, std::vector(), protobufs::TransformationSequence()}; } // Initial binary should be valid. if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) { consumer_(SPV_MSG_ERROR, nullptr, {}, "Initial binary is invalid; stopping."); return {Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid, std::vector(), protobufs::TransformationSequence()}; } // Build the module from the input binary. ir_context_ = BuildModule(target_env_, consumer_, binary_in_.data(), binary_in_.size()); assert(ir_context_); // The fuzzer will introduce new ids into the module. The module's id bound // gives the smallest id that can be used for this purpose. We add an offset // to this so that there is a sizeable gap between the ids used in the // original module and the ids used for fuzzing, as a readability aid. // // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the // case where the maximum id bound is reached. auto minimum_fresh_id = ir_context_->module()->id_bound() + kIdBoundGap; fuzzer_context_ = MakeUnique(random_generator_.get(), minimum_fresh_id); transformation_context_ = MakeUnique( MakeUnique(ir_context_.get()), validator_options_); transformation_context_->GetFactManager()->AddInitialFacts(consumer_, initial_facts_); RepeatedPassInstances pass_instances{}; // The following passes are likely to be very useful: many other passes // introduce synonyms, irrelevant ids and constants that these passes can work // with. We thus enable them with high probability. MaybeAddRepeatedPass(90, &pass_instances); MaybeAddRepeatedPass(90, &pass_instances); MaybeAddRepeatedPass(90, &pass_instances); do { // Each call to MaybeAddRepeatedPass randomly decides whether the given pass // should be enabled, and adds an instance of the pass to |pass_instances| // if it is enabled. MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances, donor_suppliers_); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass(&pass_instances); MaybeAddRepeatedPass( &pass_instances); MaybeAddRepeatedPass(&pass_instances); // There is a theoretical possibility that no pass instances were created // until now; loop again if so. } while (pass_instances.GetPasses().empty()); RepeatedPassRecommenderStandard pass_recommender(&pass_instances, fuzzer_context_.get()); std::unique_ptr repeated_pass_manager = nullptr; switch (repeated_pass_strategy_) { case RepeatedPassStrategy::kSimple: repeated_pass_manager = MakeUnique( fuzzer_context_.get(), &pass_instances); break; case RepeatedPassStrategy::kLoopedWithRecommendations: repeated_pass_manager = MakeUnique( fuzzer_context_.get(), &pass_instances, &pass_recommender); break; case RepeatedPassStrategy::kRandomWithRecommendations: repeated_pass_manager = MakeUnique( fuzzer_context_.get(), &pass_instances, &pass_recommender); break; } do { if (!ApplyPassAndCheckValidity( repeated_pass_manager->ChoosePass(transformation_sequence_out_))) { return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule, std::vector(), protobufs::TransformationSequence()}; } } while (ShouldContinueFuzzing()); // Now apply some passes that it does not make sense to apply repeatedly, // as they do not unlock other passes. std::vector> final_passes; MaybeAddFinalPass(&final_passes); MaybeAddFinalPass(&final_passes); MaybeAddFinalPass(&final_passes); MaybeAddFinalPass(&final_passes); MaybeAddFinalPass(&final_passes); MaybeAddFinalPass(&final_passes); MaybeAddFinalPass( &final_passes); MaybeAddFinalPass(&final_passes); MaybeAddFinalPass(&final_passes); MaybeAddFinalPass(&final_passes); MaybeAddFinalPass(&final_passes); for (auto& pass : final_passes) { if (!ApplyPassAndCheckValidity(pass.get())) { return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule, std::vector(), protobufs::TransformationSequence()}; } } // Encode the module as a binary. std::vector binary_out; ir_context_->module()->ToBinary(&binary_out, false); return {Fuzzer::FuzzerResultStatus::kComplete, std::move(binary_out), std::move(transformation_sequence_out_)}; } bool Fuzzer::ShouldContinueFuzzing() { // There's a risk that fuzzing could get stuck, if none of the enabled fuzzer // passes are able to apply any transformations. To guard against this we // count the number of times some repeated pass has been applied and ensure // that fuzzing stops if the number of repeated passes hits the limit on the // number of transformations that can be applied. assert( num_repeated_passes_applied_ <= fuzzer_context_->GetTransformationLimit() && "The number of repeated passes applied must not exceed its upper limit."); if (ir_context_->module()->id_bound() >= fuzzer_context_->GetIdBoundLimit()) { return false; } if (num_repeated_passes_applied_ == fuzzer_context_->GetTransformationLimit()) { // Stop because fuzzing has got stuck. return false; } auto transformations_applied_so_far = static_cast(transformation_sequence_out_.transformation_size()); if (transformations_applied_so_far >= fuzzer_context_->GetTransformationLimit()) { // Stop because we have reached the transformation limit. return false; } // If we have applied T transformations so far, and the limit on the number of // transformations to apply is L (where T < L), the chance that we will // continue fuzzing is: // // 1 - T/(2*L) // // That is, the chance of continuing decreases as more transformations are // applied. Using 2*L instead of L increases the number of transformations // that are applied on average. auto chance_of_continuing = static_cast( 100.0 * (1.0 - (static_cast(transformations_applied_so_far) / (2.0 * static_cast( fuzzer_context_->GetTransformationLimit()))))); if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) { // We have probabilistically decided to stop. return false; } // Continue fuzzing! num_repeated_passes_applied_++; return true; } } // namespace fuzz } // namespace spvtools