// Copyright 2021 The Tint Authors. // // 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 #include #include #include #include "fuzzers/random_generator.h" #include "fuzzers/tint_common_fuzzer.h" #include "fuzzers/tint_spirv_tools_fuzzer/cli.h" #include "fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h" #include "fuzzers/tint_spirv_tools_fuzzer/override_cli_params.h" #include "fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h" #include "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h" #include "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h" #include "fuzzers/tint_spirv_tools_fuzzer/util.h" #include "spirv-tools/libspirv.hpp" namespace tint { namespace fuzzers { namespace spvtools_fuzzer { namespace { struct Context { FuzzerCliParams params; std::unique_ptr mutator_cache; }; Context* context = nullptr; extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { auto params = ParseFuzzerCliParams(argc, *argv); auto mutator_cache = params.mutator_cache_size ? std::make_unique(params.mutator_cache_size) : nullptr; context = new Context{std::move(params), std::move(mutator_cache)}; OverrideCliParams(context->params); return 0; } std::unique_ptr CreateMutator(const std::vector& binary, unsigned seed) { std::vector types; types.reserve(3); // Determine which mutator we will be using for `binary` at random. auto cli_mutator_type = context->params.mutator_type; if ((MutatorType::kFuzz & cli_mutator_type) == MutatorType::kFuzz) { types.push_back(MutatorType::kFuzz); } if ((MutatorType::kReduce & cli_mutator_type) == MutatorType::kReduce) { types.push_back(MutatorType::kReduce); } if ((MutatorType::kOpt & cli_mutator_type) == MutatorType::kOpt) { types.push_back(MutatorType::kOpt); } assert(!types.empty() && "At least one mutator type must be specified"); RandomGenerator generator(seed); auto mutator_type = types[generator.GetUInt64(types.size())]; const auto& mutator_params = context->params.mutator_params; switch (mutator_type) { case MutatorType::kFuzz: return std::make_unique( mutator_params.target_env, binary, seed, mutator_params.donors, mutator_params.enable_all_fuzzer_passes, mutator_params.repeated_pass_strategy, mutator_params.validate_after_each_fuzzer_pass, mutator_params.transformation_batch_size); case MutatorType::kReduce: return std::make_unique( mutator_params.target_env, binary, seed, mutator_params.reduction_batch_size, mutator_params.enable_all_reduce_passes, mutator_params.validate_after_each_reduce_pass); case MutatorType::kOpt: return std::make_unique( mutator_params.target_env, seed, binary, mutator_params.validate_after_each_opt_pass, mutator_params.opt_batch_size); default: assert(false && "All mutator types must be handled above"); return nullptr; } } void CLIMessageConsumer(spv_message_level_t level, const char*, const spv_position_t& position, const char* message) { switch (level) { case SPV_MSG_FATAL: case SPV_MSG_INTERNAL_ERROR: case SPV_MSG_ERROR: std::cerr << "error: line " << position.index << ": " << message << std::endl; break; case SPV_MSG_WARNING: std::cout << "warning: line " << position.index << ": " << message << std::endl; break; case SPV_MSG_INFO: std::cout << "info: line " << position.index << ": " << message << std::endl; break; default: break; } } bool IsValid(const std::vector& binary) { spvtools::SpirvTools tools(context->params.mutator_params.target_env); tools.SetMessageConsumer(CLIMessageConsumer); return tools.IsValid() && tools.Validate(binary.data(), binary.size(), spvtools::ValidatorOptions()); } extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned seed) { if ((size % sizeof(uint32_t)) != 0) { // A valid SPIR-V binary's size must be a multiple of the size of a 32-bit // word, and the SPIR-V Tools fuzzer is only designed to work with valid // binaries. return 0; } std::vector binary(size / sizeof(uint32_t)); std::memcpy(binary.data(), data, size); MutatorCache dummy_cache(1); auto* mutator_cache = context->mutator_cache.get(); if (!mutator_cache) { // Use a placeholder cache if the user has decided not to use a real cache. // The placeholder cache will be destroyed when we return from this function // but it will save us from writing all the `if (mutator_cache)` below. mutator_cache = &dummy_cache; } if (!mutator_cache->Get(binary)) { // This is an unknown binary, so its validity must be checked before // proceeding. if (!IsValid(binary)) { return 0; } // Assign a mutator to the binary if it doesn't have one yet. mutator_cache->Put(binary, CreateMutator(binary, seed)); } auto* mutator = mutator_cache->Get(binary); assert(mutator && "Mutator must be present in the cache"); auto result = mutator->Mutate(); if (result.GetStatus() == Mutator::Status::kInvalid) { // The binary is invalid - log the error and remove the mutator from the // cache. util::LogMutatorError(*mutator, context->params.error_dir); mutator_cache->Remove(binary); return 0; } if (!result.IsChanged()) { // The mutator didn't change the binary this time. This could be due to the // fact that we've reached the number of mutations we can apply (e.g. the // number of transformations in spirv-fuzz) or the mutator was just unlucky. // Either way, there is no harm in destroying mutator and maybe trying again // later (i.e. if libfuzzer decides to do so). mutator_cache->Remove(binary); return 0; } // At this point the binary is valid and was changed by the mutator. auto mutated = mutator->GetBinary(); auto mutated_bytes_size = mutated.size() * sizeof(uint32_t); if (mutated_bytes_size > max_size) { // The binary is too big. It's unlikely that we'll reduce its size by // applying the mutator one more time. mutator_cache->Remove(binary); return 0; } if (result.GetStatus() == Mutator::Status::kComplete) { // Reassign the mutator to the mutated binary in the cache so that we can // access later. mutator_cache->Put(mutated, mutator_cache->Remove(binary)); } else { // If the binary is valid and was changed but is not `kComplete`, then the // mutator has reached some limit on the number of mutations. mutator_cache->Remove(binary); } std::memcpy(data, mutated.data(), mutated_bytes_size); return mutated_bytes_size; } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { if (size == 0) { return 0; } if ((size % sizeof(uint32_t)) != 0) { // The SPIR-V Tools fuzzer has been designed to work with valid // SPIR-V binaries, whose sizes should be multiples of the size of a 32-bit // word. return 0; } CommonFuzzer spv_to_wgsl(InputFormat::kSpv, OutputFormat::kWGSL); spv_to_wgsl.EnableInspector(); spv_to_wgsl.Run(data, size); if (spv_to_wgsl.HasErrors()) { auto error = spv_to_wgsl.Diagnostics().str(); util::LogSpvError(error, data, size, context ? context->params.error_dir : ""); return 0; } const auto& wgsl = spv_to_wgsl.GetGeneratedWgsl(); std::pair targets[] = { {FuzzingTarget::kHlsl, OutputFormat::kHLSL}, {FuzzingTarget::kMsl, OutputFormat::kMSL}, {FuzzingTarget::kSpv, OutputFormat::kSpv}, {FuzzingTarget::kWgsl, OutputFormat::kWGSL}}; for (auto target : targets) { if ((target.first & context->params.fuzzing_target) != target.first) { continue; } CommonFuzzer fuzzer(InputFormat::kWGSL, target.second); fuzzer.EnableInspector(); fuzzer.Run(reinterpret_cast(wgsl.data()), wgsl.size()); if (fuzzer.HasErrors()) { auto error = spv_to_wgsl.Diagnostics().str(); util::LogWgslError(error, data, size, wgsl, target.second, context->params.error_dir); } } return 0; } } // namespace } // namespace spvtools_fuzzer } // namespace fuzzers } // namespace tint