| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- // 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_pass_obfuscate_constants.h"
- #include <algorithm>
- #include <cmath>
- #include "source/fuzz/fuzzer_util.h"
- #include "source/fuzz/instruction_descriptor.h"
- #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
- #include "source/fuzz/transformation_replace_constant_with_uniform.h"
- #include "source/fuzz/uniform_buffer_element_descriptor.h"
- #include "source/opt/ir_context.h"
- namespace spvtools {
- namespace fuzz {
- FuzzerPassObfuscateConstants::FuzzerPassObfuscateConstants(
- opt::IRContext* ir_context, TransformationContext* transformation_context,
- FuzzerContext* fuzzer_context,
- protobufs::TransformationSequence* transformations,
- bool ignore_inapplicable_transformations)
- : FuzzerPass(ir_context, transformation_context, fuzzer_context,
- transformations, ignore_inapplicable_transformations) {}
- void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaConstantPair(
- uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
- const std::vector<spv::Op>& greater_than_opcodes,
- const std::vector<spv::Op>& less_than_opcodes, uint32_t constant_id_1,
- uint32_t constant_id_2, bool first_constant_is_larger) {
- auto bool_constant_opcode = GetIRContext()
- ->get_def_use_mgr()
- ->GetDef(bool_constant_use.id_of_interest())
- ->opcode();
- assert((bool_constant_opcode == spv::Op::OpConstantFalse ||
- bool_constant_opcode == spv::Op::OpConstantTrue) &&
- "Precondition: this must be a usage of a boolean constant.");
- // Pick an opcode at random. First randomly decide whether to generate
- // a 'greater than' or 'less than' kind of opcode, and then select a
- // random opcode from the resulting subset.
- spv::Op comparison_opcode;
- if (GetFuzzerContext()->ChooseEven()) {
- comparison_opcode = greater_than_opcodes[GetFuzzerContext()->RandomIndex(
- greater_than_opcodes)];
- } else {
- comparison_opcode =
- less_than_opcodes[GetFuzzerContext()->RandomIndex(less_than_opcodes)];
- }
- // We now need to decide how to order constant_id_1 and constant_id_2 such
- // that 'constant_id_1 comparison_opcode constant_id_2' evaluates to the
- // boolean constant.
- const bool is_greater_than_opcode =
- std::find(greater_than_opcodes.begin(), greater_than_opcodes.end(),
- comparison_opcode) != greater_than_opcodes.end();
- uint32_t lhs_id;
- uint32_t rhs_id;
- if ((bool_constant_opcode == spv::Op::OpConstantTrue &&
- first_constant_is_larger == is_greater_than_opcode) ||
- (bool_constant_opcode == spv::Op::OpConstantFalse &&
- first_constant_is_larger != is_greater_than_opcode)) {
- lhs_id = constant_id_1;
- rhs_id = constant_id_2;
- } else {
- lhs_id = constant_id_2;
- rhs_id = constant_id_1;
- }
- // We can now make a transformation that will replace |bool_constant_use|
- // with an expression of the form (written using infix notation):
- // |lhs_id| |comparison_opcode| |rhs_id|
- auto transformation = TransformationReplaceBooleanConstantWithConstantBinary(
- bool_constant_use, lhs_id, rhs_id, comparison_opcode,
- GetFuzzerContext()->GetFreshId());
- // The transformation should be applicable by construction.
- assert(
- transformation.IsApplicable(GetIRContext(), *GetTransformationContext()));
- // Applying this transformation yields a pointer to the new instruction that
- // computes the result of the binary expression.
- auto binary_operator_instruction = transformation.ApplyWithResult(
- GetIRContext(), GetTransformationContext());
- // Add this transformation to the sequence of transformations that have been
- // applied.
- *GetTransformations()->add_transformation() = transformation.ToMessage();
- // Having made a binary expression, there may now be opportunities to further
- // obfuscate the constants used as the LHS and RHS of the expression (e.g. by
- // replacing them with loads from known uniforms).
- //
- // We thus consider operands 0 and 1 (LHS and RHS in turn).
- for (uint32_t index : {0u, 1u}) {
- // We randomly decide, based on the current depth of obfuscation, whether
- // to further obfuscate this operand.
- if (GetFuzzerContext()->GoDeeperInConstantObfuscation(depth)) {
- auto in_operand_use = MakeIdUseDescriptor(
- binary_operator_instruction->GetSingleWordInOperand(index),
- MakeInstructionDescriptor(binary_operator_instruction->result_id(),
- binary_operator_instruction->opcode(), 0),
- index);
- ObfuscateConstant(depth + 1, in_operand_use);
- }
- }
- }
- void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaFloatConstantPair(
- uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
- uint32_t float_constant_id_1, uint32_t float_constant_id_2) {
- auto float_constant_1 = GetIRContext()
- ->get_constant_mgr()
- ->FindDeclaredConstant(float_constant_id_1)
- ->AsFloatConstant();
- auto float_constant_2 = GetIRContext()
- ->get_constant_mgr()
- ->FindDeclaredConstant(float_constant_id_2)
- ->AsFloatConstant();
- assert(float_constant_1->words() != float_constant_2->words() &&
- "The constants should not be identical.");
- assert(std::isfinite(float_constant_1->GetValueAsDouble()) &&
- "The constants must be finite numbers.");
- assert(std::isfinite(float_constant_2->GetValueAsDouble()) &&
- "The constants must be finite numbers.");
- bool first_constant_is_larger;
- assert(float_constant_1->type()->AsFloat()->width() ==
- float_constant_2->type()->AsFloat()->width() &&
- "First and second floating-point constants must have the same width.");
- if (float_constant_1->type()->AsFloat()->width() == 32) {
- first_constant_is_larger =
- float_constant_1->GetFloat() > float_constant_2->GetFloat();
- } else {
- assert(float_constant_1->type()->AsFloat()->width() == 64 &&
- "Supported floating-point widths are 32 and 64.");
- first_constant_is_larger =
- float_constant_1->GetDouble() > float_constant_2->GetDouble();
- }
- std::vector<spv::Op> greater_than_opcodes{
- spv::Op::OpFOrdGreaterThan, spv::Op::OpFOrdGreaterThanEqual,
- spv::Op::OpFUnordGreaterThan, spv::Op::OpFUnordGreaterThanEqual};
- std::vector<spv::Op> less_than_opcodes{
- spv::Op::OpFOrdGreaterThan, spv::Op::OpFOrdGreaterThanEqual,
- spv::Op::OpFUnordGreaterThan, spv::Op::OpFUnordGreaterThanEqual};
- ObfuscateBoolConstantViaConstantPair(
- depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
- float_constant_id_1, float_constant_id_2, first_constant_is_larger);
- }
- void FuzzerPassObfuscateConstants::
- ObfuscateBoolConstantViaSignedIntConstantPair(
- uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
- uint32_t signed_int_constant_id_1, uint32_t signed_int_constant_id_2) {
- auto signed_int_constant_1 =
- GetIRContext()
- ->get_constant_mgr()
- ->FindDeclaredConstant(signed_int_constant_id_1)
- ->AsIntConstant();
- auto signed_int_constant_2 =
- GetIRContext()
- ->get_constant_mgr()
- ->FindDeclaredConstant(signed_int_constant_id_2)
- ->AsIntConstant();
- assert(signed_int_constant_1->words() != signed_int_constant_2->words() &&
- "The constants should not be identical.");
- bool first_constant_is_larger;
- assert(signed_int_constant_1->type()->AsInteger()->width() ==
- signed_int_constant_2->type()->AsInteger()->width() &&
- "First and second floating-point constants must have the same width.");
- assert(signed_int_constant_1->type()->AsInteger()->IsSigned());
- assert(signed_int_constant_2->type()->AsInteger()->IsSigned());
- if (signed_int_constant_1->type()->AsFloat()->width() == 32) {
- first_constant_is_larger =
- signed_int_constant_1->GetS32() > signed_int_constant_2->GetS32();
- } else {
- assert(signed_int_constant_1->type()->AsFloat()->width() == 64 &&
- "Supported integer widths are 32 and 64.");
- first_constant_is_larger =
- signed_int_constant_1->GetS64() > signed_int_constant_2->GetS64();
- }
- std::vector<spv::Op> greater_than_opcodes{spv::Op::OpSGreaterThan,
- spv::Op::OpSGreaterThanEqual};
- std::vector<spv::Op> less_than_opcodes{spv::Op::OpSLessThan,
- spv::Op::OpSLessThanEqual};
- ObfuscateBoolConstantViaConstantPair(
- depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
- signed_int_constant_id_1, signed_int_constant_id_2,
- first_constant_is_larger);
- }
- void FuzzerPassObfuscateConstants::
- ObfuscateBoolConstantViaUnsignedIntConstantPair(
- uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
- uint32_t unsigned_int_constant_id_1,
- uint32_t unsigned_int_constant_id_2) {
- auto unsigned_int_constant_1 =
- GetIRContext()
- ->get_constant_mgr()
- ->FindDeclaredConstant(unsigned_int_constant_id_1)
- ->AsIntConstant();
- auto unsigned_int_constant_2 =
- GetIRContext()
- ->get_constant_mgr()
- ->FindDeclaredConstant(unsigned_int_constant_id_2)
- ->AsIntConstant();
- assert(unsigned_int_constant_1->words() != unsigned_int_constant_2->words() &&
- "The constants should not be identical.");
- bool first_constant_is_larger;
- assert(unsigned_int_constant_1->type()->AsInteger()->width() ==
- unsigned_int_constant_2->type()->AsInteger()->width() &&
- "First and second floating-point constants must have the same width.");
- assert(!unsigned_int_constant_1->type()->AsInteger()->IsSigned());
- assert(!unsigned_int_constant_2->type()->AsInteger()->IsSigned());
- if (unsigned_int_constant_1->type()->AsFloat()->width() == 32) {
- first_constant_is_larger =
- unsigned_int_constant_1->GetU32() > unsigned_int_constant_2->GetU32();
- } else {
- assert(unsigned_int_constant_1->type()->AsFloat()->width() == 64 &&
- "Supported integer widths are 32 and 64.");
- first_constant_is_larger =
- unsigned_int_constant_1->GetU64() > unsigned_int_constant_2->GetU64();
- }
- std::vector<spv::Op> greater_than_opcodes{spv::Op::OpUGreaterThan,
- spv::Op::OpUGreaterThanEqual};
- std::vector<spv::Op> less_than_opcodes{spv::Op::OpULessThan,
- spv::Op::OpULessThanEqual};
- ObfuscateBoolConstantViaConstantPair(
- depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
- unsigned_int_constant_id_1, unsigned_int_constant_id_2,
- first_constant_is_larger);
- }
- std::vector<std::vector<uint32_t>>
- FuzzerPassObfuscateConstants::GetConstantWordsFromUniformsForType(
- uint32_t type_id) {
- assert(type_id && "Type id can't be 0");
- std::vector<std::vector<uint32_t>> result;
- for (const auto& facts_and_types : GetTransformationContext()
- ->GetFactManager()
- ->GetConstantUniformFactsAndTypes()) {
- if (facts_and_types.second != type_id) {
- continue;
- }
- std::vector<uint32_t> words(facts_and_types.first.constant_word().begin(),
- facts_and_types.first.constant_word().end());
- if (std::find(result.begin(), result.end(), words) == result.end()) {
- result.push_back(std::move(words));
- }
- }
- return result;
- }
- void FuzzerPassObfuscateConstants::ObfuscateBoolConstant(
- uint32_t depth, const protobufs::IdUseDescriptor& constant_use) {
- // We want to replace the boolean constant use with a binary expression over
- // scalar constants, but only if we can then potentially replace the constants
- // with uniforms of the same value.
- auto available_types_with_uniforms =
- GetTransformationContext()
- ->GetFactManager()
- ->GetTypesForWhichUniformValuesAreKnown();
- if (available_types_with_uniforms.empty()) {
- // Do not try to obfuscate if we do not have access to any uniform
- // elements with known values.
- return;
- }
- auto chosen_type_id =
- available_types_with_uniforms[GetFuzzerContext()->RandomIndex(
- available_types_with_uniforms)];
- auto available_constant_words =
- GetConstantWordsFromUniformsForType(chosen_type_id);
- if (available_constant_words.size() == 1) {
- // TODO(afd): for now we only obfuscate a boolean if there are at least
- // two constants available from uniforms, so that we can do a
- // comparison between them. It would be good to be able to do the
- // obfuscation even if there is only one such constant, if there is
- // also another regular constant available.
- return;
- }
- assert(!available_constant_words.empty() &&
- "There exists a fact but no constants - impossible");
- // We know we have at least two known-to-be-constant uniforms of the chosen
- // type. Pick one of them at random.
- auto constant_index_1 =
- GetFuzzerContext()->RandomIndex(available_constant_words);
- uint32_t constant_index_2;
- // Now choose another one distinct from the first one.
- do {
- constant_index_2 =
- GetFuzzerContext()->RandomIndex(available_constant_words);
- } while (constant_index_1 == constant_index_2);
- auto constant_id_1 = FindOrCreateConstant(
- available_constant_words[constant_index_1], chosen_type_id, false);
- auto constant_id_2 = FindOrCreateConstant(
- available_constant_words[constant_index_2], chosen_type_id, false);
- assert(constant_id_1 != 0 && constant_id_2 != 0 &&
- "We should not find an available constant with an id of 0.");
- // Now perform the obfuscation, according to whether the type of the constants
- // is float, signed int, or unsigned int.
- auto chosen_type = GetIRContext()->get_type_mgr()->GetType(chosen_type_id);
- if (chosen_type->AsFloat()) {
- ObfuscateBoolConstantViaFloatConstantPair(depth, constant_use,
- constant_id_1, constant_id_2);
- } else {
- assert(chosen_type->AsInteger() &&
- "We should only have uniform facts about ints and floats.");
- if (chosen_type->AsInteger()->IsSigned()) {
- ObfuscateBoolConstantViaSignedIntConstantPair(
- depth, constant_use, constant_id_1, constant_id_2);
- } else {
- ObfuscateBoolConstantViaUnsignedIntConstantPair(
- depth, constant_use, constant_id_1, constant_id_2);
- }
- }
- }
- void FuzzerPassObfuscateConstants::ObfuscateScalarConstant(
- uint32_t /*depth*/, const protobufs::IdUseDescriptor& constant_use) {
- // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2670): consider
- // additional ways to obfuscate scalar constants.
- // Check whether we know that any uniforms are guaranteed to be equal to the
- // scalar constant associated with |constant_use|.
- auto uniform_descriptors =
- GetTransformationContext()
- ->GetFactManager()
- ->GetUniformDescriptorsForConstant(constant_use.id_of_interest());
- if (uniform_descriptors.empty()) {
- // No relevant uniforms, so do not obfuscate.
- return;
- }
- // Choose a random available uniform known to be equal to the constant.
- const auto& uniform_descriptor =
- uniform_descriptors[GetFuzzerContext()->RandomIndex(uniform_descriptors)];
- // Make sure the module has OpConstant instructions for each index used to
- // access a uniform.
- for (auto index : uniform_descriptor.index()) {
- FindOrCreateIntegerConstant({index}, 32, true, false);
- }
- // Make sure the module has OpTypePointer that points to the element type of
- // the uniform.
- const auto* uniform_variable_instr =
- FindUniformVariable(uniform_descriptor, GetIRContext(), true);
- assert(uniform_variable_instr &&
- "Uniform variable does not exist or not unique.");
- const auto* uniform_variable_type_intr =
- GetIRContext()->get_def_use_mgr()->GetDef(
- uniform_variable_instr->type_id());
- assert(uniform_variable_type_intr && "Uniform variable has invalid type");
- auto element_type_id = fuzzerutil::WalkCompositeTypeIndices(
- GetIRContext(), uniform_variable_type_intr->GetSingleWordInOperand(1),
- uniform_descriptor.index());
- assert(element_type_id && "Type of uniform variable is invalid");
- FindOrCreatePointerType(element_type_id, spv::StorageClass::Uniform);
- // Create, apply and record a transformation to replace the constant use with
- // the result of a load from the chosen uniform.
- ApplyTransformation(TransformationReplaceConstantWithUniform(
- constant_use, uniform_descriptor, GetFuzzerContext()->GetFreshId(),
- GetFuzzerContext()->GetFreshId()));
- }
- void FuzzerPassObfuscateConstants::ObfuscateConstant(
- uint32_t depth, const protobufs::IdUseDescriptor& constant_use) {
- switch (GetIRContext()
- ->get_def_use_mgr()
- ->GetDef(constant_use.id_of_interest())
- ->opcode()) {
- case spv::Op::OpConstantTrue:
- case spv::Op::OpConstantFalse:
- ObfuscateBoolConstant(depth, constant_use);
- break;
- case spv::Op::OpConstant:
- ObfuscateScalarConstant(depth, constant_use);
- break;
- default:
- assert(false && "The opcode should be one of the above.");
- break;
- }
- }
- void FuzzerPassObfuscateConstants::MaybeAddConstantIdUse(
- const opt::Instruction& inst, uint32_t in_operand_index,
- uint32_t base_instruction_result_id,
- const std::map<spv::Op, uint32_t>& skipped_opcode_count,
- std::vector<protobufs::IdUseDescriptor>* constant_uses) {
- if (inst.GetInOperand(in_operand_index).type != SPV_OPERAND_TYPE_ID) {
- // The operand is not an id, so it cannot be a constant id.
- return;
- }
- auto operand_id = inst.GetSingleWordInOperand(in_operand_index);
- auto operand_definition =
- GetIRContext()->get_def_use_mgr()->GetDef(operand_id);
- switch (operand_definition->opcode()) {
- case spv::Op::OpConstantFalse:
- case spv::Op::OpConstantTrue:
- case spv::Op::OpConstant: {
- // The operand is a constant id, so make an id use descriptor and record
- // it.
- protobufs::IdUseDescriptor id_use_descriptor;
- id_use_descriptor.set_id_of_interest(operand_id);
- id_use_descriptor.mutable_enclosing_instruction()
- ->set_target_instruction_opcode(uint32_t(inst.opcode()));
- id_use_descriptor.mutable_enclosing_instruction()
- ->set_base_instruction_result_id(base_instruction_result_id);
- id_use_descriptor.mutable_enclosing_instruction()
- ->set_num_opcodes_to_ignore(
- skipped_opcode_count.find(inst.opcode()) ==
- skipped_opcode_count.end()
- ? 0
- : skipped_opcode_count.at(inst.opcode()));
- id_use_descriptor.set_in_operand_index(in_operand_index);
- constant_uses->push_back(id_use_descriptor);
- } break;
- default:
- break;
- }
- }
- void FuzzerPassObfuscateConstants::Apply() {
- // First, gather up all the constant uses available in the module, by going
- // through each block in each function.
- std::vector<protobufs::IdUseDescriptor> constant_uses;
- for (auto& function : *GetIRContext()->module()) {
- for (auto& block : function) {
- // For each constant use we encounter we are going to make an id use
- // descriptor. An id use is described with respect to a base instruction;
- // if there are instructions at the start of the block without result ids,
- // the base instruction will have to be the block's label.
- uint32_t base_instruction_result_id = block.id();
- // An id use descriptor also records how many instructions of a particular
- // opcode need to be skipped in order to find the instruction of interest
- // from the base instruction. We maintain a mapping that records a skip
- // count for each relevant opcode.
- std::map<spv::Op, uint32_t> skipped_opcode_count;
- // Go through each instruction in the block.
- for (auto& inst : block) {
- if (inst.HasResultId()) {
- // The instruction has a result id, so can be used as the base
- // instruction from now on, until another instruction with a result id
- // is encountered.
- base_instruction_result_id = inst.result_id();
- // Opcode skip counts were with respect to the previous base
- // instruction and are now irrelevant.
- skipped_opcode_count.clear();
- }
- // The instruction must not be an OpVariable, the only id that an
- // OpVariable uses is an initializer id, which has to remain
- // constant.
- if (inst.opcode() != spv::Op::OpVariable) {
- // Consider each operand of the instruction, and add a constant id
- // use for the operand if relevant.
- for (uint32_t in_operand_index = 0;
- in_operand_index < inst.NumInOperands(); in_operand_index++) {
- MaybeAddConstantIdUse(inst, in_operand_index,
- base_instruction_result_id,
- skipped_opcode_count, &constant_uses);
- }
- }
- if (!inst.HasResultId()) {
- // The instruction has no result id, so in order to identify future id
- // uses for instructions with this opcode from the existing base
- // instruction, we need to increase the skip count for this opcode.
- skipped_opcode_count[inst.opcode()] =
- skipped_opcode_count.find(inst.opcode()) ==
- skipped_opcode_count.end()
- ? 1
- : skipped_opcode_count[inst.opcode()] + 1;
- }
- }
- }
- }
- // Go through the constant uses in a random order by repeatedly pulling out a
- // constant use at a random index.
- while (!constant_uses.empty()) {
- auto index = GetFuzzerContext()->RandomIndex(constant_uses);
- auto constant_use = std::move(constant_uses[index]);
- constant_uses.erase(constant_uses.begin() + index);
- // Decide probabilistically whether to skip or obfuscate this constant use.
- if (!GetFuzzerContext()->ChoosePercentage(
- GetFuzzerContext()->GetChanceOfObfuscatingConstant())) {
- continue;
- }
- ObfuscateConstant(0, constant_use);
- }
- }
- } // namespace fuzz
- } // namespace spvtools
|