• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_pass_obfuscate_constants.h"
16 
17 #include <cmath>
18 
19 #include "source/fuzz/instruction_descriptor.h"
20 #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
21 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
22 #include "source/opt/ir_context.h"
23 
24 namespace spvtools {
25 namespace fuzz {
26 
FuzzerPassObfuscateConstants(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations)27 FuzzerPassObfuscateConstants::FuzzerPassObfuscateConstants(
28     opt::IRContext* ir_context, TransformationContext* transformation_context,
29     FuzzerContext* fuzzer_context,
30     protobufs::TransformationSequence* transformations)
31     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
32                  transformations) {}
33 
34 FuzzerPassObfuscateConstants::~FuzzerPassObfuscateConstants() = default;
35 
ObfuscateBoolConstantViaConstantPair(uint32_t depth,const protobufs::IdUseDescriptor & bool_constant_use,const std::vector<SpvOp> & greater_than_opcodes,const std::vector<SpvOp> & less_than_opcodes,uint32_t constant_id_1,uint32_t constant_id_2,bool first_constant_is_larger)36 void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaConstantPair(
37     uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
38     const std::vector<SpvOp>& greater_than_opcodes,
39     const std::vector<SpvOp>& less_than_opcodes, uint32_t constant_id_1,
40     uint32_t constant_id_2, bool first_constant_is_larger) {
41   auto bool_constant_opcode = GetIRContext()
42                                   ->get_def_use_mgr()
43                                   ->GetDef(bool_constant_use.id_of_interest())
44                                   ->opcode();
45   assert((bool_constant_opcode == SpvOpConstantFalse ||
46           bool_constant_opcode == SpvOpConstantTrue) &&
47          "Precondition: this must be a usage of a boolean constant.");
48 
49   // Pick an opcode at random.  First randomly decide whether to generate
50   // a 'greater than' or 'less than' kind of opcode, and then select a
51   // random opcode from the resulting subset.
52   SpvOp comparison_opcode;
53   if (GetFuzzerContext()->ChooseEven()) {
54     comparison_opcode = greater_than_opcodes[GetFuzzerContext()->RandomIndex(
55         greater_than_opcodes)];
56   } else {
57     comparison_opcode =
58         less_than_opcodes[GetFuzzerContext()->RandomIndex(less_than_opcodes)];
59   }
60 
61   // We now need to decide how to order constant_id_1 and constant_id_2 such
62   // that 'constant_id_1 comparison_opcode constant_id_2' evaluates to the
63   // boolean constant.
64   const bool is_greater_than_opcode =
65       std::find(greater_than_opcodes.begin(), greater_than_opcodes.end(),
66                 comparison_opcode) != greater_than_opcodes.end();
67   uint32_t lhs_id;
68   uint32_t rhs_id;
69   if ((bool_constant_opcode == SpvOpConstantTrue &&
70        first_constant_is_larger == is_greater_than_opcode) ||
71       (bool_constant_opcode == SpvOpConstantFalse &&
72        first_constant_is_larger != is_greater_than_opcode)) {
73     lhs_id = constant_id_1;
74     rhs_id = constant_id_2;
75   } else {
76     lhs_id = constant_id_2;
77     rhs_id = constant_id_1;
78   }
79 
80   // We can now make a transformation that will replace |bool_constant_use|
81   // with an expression of the form (written using infix notation):
82   // |lhs_id| |comparison_opcode| |rhs_id|
83   auto transformation = TransformationReplaceBooleanConstantWithConstantBinary(
84       bool_constant_use, lhs_id, rhs_id, comparison_opcode,
85       GetFuzzerContext()->GetFreshId());
86   // The transformation should be applicable by construction.
87   assert(
88       transformation.IsApplicable(GetIRContext(), *GetTransformationContext()));
89 
90   // Applying this transformation yields a pointer to the new instruction that
91   // computes the result of the binary expression.
92   auto binary_operator_instruction = transformation.ApplyWithResult(
93       GetIRContext(), GetTransformationContext());
94 
95   // Add this transformation to the sequence of transformations that have been
96   // applied.
97   *GetTransformations()->add_transformation() = transformation.ToMessage();
98 
99   // Having made a binary expression, there may now be opportunities to further
100   // obfuscate the constants used as the LHS and RHS of the expression (e.g. by
101   // replacing them with loads from known uniforms).
102   //
103   // We thus consider operands 0 and 1 (LHS and RHS in turn).
104   for (uint32_t index : {0u, 1u}) {
105     // We randomly decide, based on the current depth of obfuscation, whether
106     // to further obfuscate this operand.
107     if (GetFuzzerContext()->GoDeeperInConstantObfuscation(depth)) {
108       auto in_operand_use = MakeIdUseDescriptor(
109           binary_operator_instruction->GetSingleWordInOperand(index),
110           MakeInstructionDescriptor(binary_operator_instruction->result_id(),
111                                     binary_operator_instruction->opcode(), 0),
112           index);
113       ObfuscateConstant(depth + 1, in_operand_use);
114     }
115   }
116 }
117 
ObfuscateBoolConstantViaFloatConstantPair(uint32_t depth,const protobufs::IdUseDescriptor & bool_constant_use,uint32_t float_constant_id_1,uint32_t float_constant_id_2)118 void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaFloatConstantPair(
119     uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
120     uint32_t float_constant_id_1, uint32_t float_constant_id_2) {
121   auto float_constant_1 = GetIRContext()
122                               ->get_constant_mgr()
123                               ->FindDeclaredConstant(float_constant_id_1)
124                               ->AsFloatConstant();
125   auto float_constant_2 = GetIRContext()
126                               ->get_constant_mgr()
127                               ->FindDeclaredConstant(float_constant_id_2)
128                               ->AsFloatConstant();
129   assert(float_constant_1->words() != float_constant_2->words() &&
130          "The constants should not be identical.");
131   assert(std::isfinite(float_constant_1->GetValueAsDouble()) &&
132          "The constants must be finite numbers.");
133   assert(std::isfinite(float_constant_2->GetValueAsDouble()) &&
134          "The constants must be finite numbers.");
135   bool first_constant_is_larger;
136   assert(float_constant_1->type()->AsFloat()->width() ==
137              float_constant_2->type()->AsFloat()->width() &&
138          "First and second floating-point constants must have the same width.");
139   if (float_constant_1->type()->AsFloat()->width() == 32) {
140     first_constant_is_larger =
141         float_constant_1->GetFloat() > float_constant_2->GetFloat();
142   } else {
143     assert(float_constant_1->type()->AsFloat()->width() == 64 &&
144            "Supported floating-point widths are 32 and 64.");
145     first_constant_is_larger =
146         float_constant_1->GetDouble() > float_constant_2->GetDouble();
147   }
148   std::vector<SpvOp> greater_than_opcodes{
149       SpvOpFOrdGreaterThan, SpvOpFOrdGreaterThanEqual, SpvOpFUnordGreaterThan,
150       SpvOpFUnordGreaterThanEqual};
151   std::vector<SpvOp> less_than_opcodes{
152       SpvOpFOrdGreaterThan, SpvOpFOrdGreaterThanEqual, SpvOpFUnordGreaterThan,
153       SpvOpFUnordGreaterThanEqual};
154 
155   ObfuscateBoolConstantViaConstantPair(
156       depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
157       float_constant_id_1, float_constant_id_2, first_constant_is_larger);
158 }
159 
160 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)161     ObfuscateBoolConstantViaSignedIntConstantPair(
162         uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
163         uint32_t signed_int_constant_id_1, uint32_t signed_int_constant_id_2) {
164   auto signed_int_constant_1 =
165       GetIRContext()
166           ->get_constant_mgr()
167           ->FindDeclaredConstant(signed_int_constant_id_1)
168           ->AsIntConstant();
169   auto signed_int_constant_2 =
170       GetIRContext()
171           ->get_constant_mgr()
172           ->FindDeclaredConstant(signed_int_constant_id_2)
173           ->AsIntConstant();
174   assert(signed_int_constant_1->words() != signed_int_constant_2->words() &&
175          "The constants should not be identical.");
176   bool first_constant_is_larger;
177   assert(signed_int_constant_1->type()->AsInteger()->width() ==
178              signed_int_constant_2->type()->AsInteger()->width() &&
179          "First and second floating-point constants must have the same width.");
180   assert(signed_int_constant_1->type()->AsInteger()->IsSigned());
181   assert(signed_int_constant_2->type()->AsInteger()->IsSigned());
182   if (signed_int_constant_1->type()->AsFloat()->width() == 32) {
183     first_constant_is_larger =
184         signed_int_constant_1->GetS32() > signed_int_constant_2->GetS32();
185   } else {
186     assert(signed_int_constant_1->type()->AsFloat()->width() == 64 &&
187            "Supported integer widths are 32 and 64.");
188     first_constant_is_larger =
189         signed_int_constant_1->GetS64() > signed_int_constant_2->GetS64();
190   }
191   std::vector<SpvOp> greater_than_opcodes{SpvOpSGreaterThan,
192                                           SpvOpSGreaterThanEqual};
193   std::vector<SpvOp> less_than_opcodes{SpvOpSLessThan, SpvOpSLessThanEqual};
194 
195   ObfuscateBoolConstantViaConstantPair(
196       depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
197       signed_int_constant_id_1, signed_int_constant_id_2,
198       first_constant_is_larger);
199 }
200 
201 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)202     ObfuscateBoolConstantViaUnsignedIntConstantPair(
203         uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
204         uint32_t unsigned_int_constant_id_1,
205         uint32_t unsigned_int_constant_id_2) {
206   auto unsigned_int_constant_1 =
207       GetIRContext()
208           ->get_constant_mgr()
209           ->FindDeclaredConstant(unsigned_int_constant_id_1)
210           ->AsIntConstant();
211   auto unsigned_int_constant_2 =
212       GetIRContext()
213           ->get_constant_mgr()
214           ->FindDeclaredConstant(unsigned_int_constant_id_2)
215           ->AsIntConstant();
216   assert(unsigned_int_constant_1->words() != unsigned_int_constant_2->words() &&
217          "The constants should not be identical.");
218   bool first_constant_is_larger;
219   assert(unsigned_int_constant_1->type()->AsInteger()->width() ==
220              unsigned_int_constant_2->type()->AsInteger()->width() &&
221          "First and second floating-point constants must have the same width.");
222   assert(!unsigned_int_constant_1->type()->AsInteger()->IsSigned());
223   assert(!unsigned_int_constant_2->type()->AsInteger()->IsSigned());
224   if (unsigned_int_constant_1->type()->AsFloat()->width() == 32) {
225     first_constant_is_larger =
226         unsigned_int_constant_1->GetU32() > unsigned_int_constant_2->GetU32();
227   } else {
228     assert(unsigned_int_constant_1->type()->AsFloat()->width() == 64 &&
229            "Supported integer widths are 32 and 64.");
230     first_constant_is_larger =
231         unsigned_int_constant_1->GetU64() > unsigned_int_constant_2->GetU64();
232   }
233   std::vector<SpvOp> greater_than_opcodes{SpvOpUGreaterThan,
234                                           SpvOpUGreaterThanEqual};
235   std::vector<SpvOp> less_than_opcodes{SpvOpULessThan, SpvOpULessThanEqual};
236 
237   ObfuscateBoolConstantViaConstantPair(
238       depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
239       unsigned_int_constant_id_1, unsigned_int_constant_id_2,
240       first_constant_is_larger);
241 }
242 
ObfuscateBoolConstant(uint32_t depth,const protobufs::IdUseDescriptor & constant_use)243 void FuzzerPassObfuscateConstants::ObfuscateBoolConstant(
244     uint32_t depth, const protobufs::IdUseDescriptor& constant_use) {
245   // We want to replace the boolean constant use with a binary expression over
246   // scalar constants, but only if we can then potentially replace the constants
247   // with uniforms of the same value.
248 
249   auto available_types_with_uniforms =
250       GetTransformationContext()
251           ->GetFactManager()
252           ->GetTypesForWhichUniformValuesAreKnown();
253   if (available_types_with_uniforms.empty()) {
254     // Do not try to obfuscate if we do not have access to any uniform
255     // elements with known values.
256     return;
257   }
258   auto chosen_type_id =
259       available_types_with_uniforms[GetFuzzerContext()->RandomIndex(
260           available_types_with_uniforms)];
261   auto available_constants = GetTransformationContext()
262                                  ->GetFactManager()
263                                  ->GetConstantsAvailableFromUniformsForType(
264                                      GetIRContext(), chosen_type_id);
265   if (available_constants.size() == 1) {
266     // TODO(afd): for now we only obfuscate a boolean if there are at least
267     //  two constants available from uniforms, so that we can do a
268     //  comparison between them. It would be good to be able to do the
269     //  obfuscation even if there is only one such constant, if there is
270     //  also another regular constant available.
271     return;
272   }
273 
274   // We know we have at least two known-to-be-constant uniforms of the chosen
275   // type.  Pick one of them at random.
276   auto constant_index_1 = GetFuzzerContext()->RandomIndex(available_constants);
277   uint32_t constant_index_2;
278 
279   // Now choose another one distinct from the first one.
280   do {
281     constant_index_2 = GetFuzzerContext()->RandomIndex(available_constants);
282   } while (constant_index_1 == constant_index_2);
283 
284   auto constant_id_1 = available_constants[constant_index_1];
285   auto constant_id_2 = available_constants[constant_index_2];
286 
287   assert(constant_id_1 != 0 && constant_id_2 != 0 &&
288          "We should not find an available constant with an id of 0.");
289 
290   // Now perform the obfuscation, according to whether the type of the constants
291   // is float, signed int, or unsigned int.
292   auto chosen_type = GetIRContext()->get_type_mgr()->GetType(chosen_type_id);
293   if (chosen_type->AsFloat()) {
294     ObfuscateBoolConstantViaFloatConstantPair(depth, constant_use,
295                                               constant_id_1, constant_id_2);
296   } else {
297     assert(chosen_type->AsInteger() &&
298            "We should only have uniform facts about ints and floats.");
299     if (chosen_type->AsInteger()->IsSigned()) {
300       ObfuscateBoolConstantViaSignedIntConstantPair(
301           depth, constant_use, constant_id_1, constant_id_2);
302     } else {
303       ObfuscateBoolConstantViaUnsignedIntConstantPair(
304           depth, constant_use, constant_id_1, constant_id_2);
305     }
306   }
307 }
308 
ObfuscateScalarConstant(uint32_t,const protobufs::IdUseDescriptor & constant_use)309 void FuzzerPassObfuscateConstants::ObfuscateScalarConstant(
310     uint32_t /*depth*/, const protobufs::IdUseDescriptor& constant_use) {
311   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2670): consider
312   //  additional ways to obfuscate scalar constants.
313 
314   // Check whether we know that any uniforms are guaranteed to be equal to the
315   // scalar constant associated with |constant_use|.
316   auto uniform_descriptors =
317       GetTransformationContext()
318           ->GetFactManager()
319           ->GetUniformDescriptorsForConstant(GetIRContext(),
320                                              constant_use.id_of_interest());
321   if (uniform_descriptors.empty()) {
322     // No relevant uniforms, so do not obfuscate.
323     return;
324   }
325 
326   // Choose a random available uniform known to be equal to the constant.
327   protobufs::UniformBufferElementDescriptor uniform_descriptor =
328       uniform_descriptors[GetFuzzerContext()->RandomIndex(uniform_descriptors)];
329   // Create, apply and record a transformation to replace the constant use with
330   // the result of a load from the chosen uniform.
331   auto transformation = TransformationReplaceConstantWithUniform(
332       constant_use, uniform_descriptor, GetFuzzerContext()->GetFreshId(),
333       GetFuzzerContext()->GetFreshId());
334   // Transformation should be applicable by construction.
335   assert(
336       transformation.IsApplicable(GetIRContext(), *GetTransformationContext()));
337   transformation.Apply(GetIRContext(), GetTransformationContext());
338   *GetTransformations()->add_transformation() = transformation.ToMessage();
339 }
340 
ObfuscateConstant(uint32_t depth,const protobufs::IdUseDescriptor & constant_use)341 void FuzzerPassObfuscateConstants::ObfuscateConstant(
342     uint32_t depth, const protobufs::IdUseDescriptor& constant_use) {
343   switch (GetIRContext()
344               ->get_def_use_mgr()
345               ->GetDef(constant_use.id_of_interest())
346               ->opcode()) {
347     case SpvOpConstantTrue:
348     case SpvOpConstantFalse:
349       ObfuscateBoolConstant(depth, constant_use);
350       break;
351     case SpvOpConstant:
352       ObfuscateScalarConstant(depth, constant_use);
353       break;
354     default:
355       assert(false && "The opcode should be one of the above.");
356       break;
357   }
358 }
359 
MaybeAddConstantIdUse(const opt::Instruction & inst,uint32_t in_operand_index,uint32_t base_instruction_result_id,const std::map<SpvOp,uint32_t> & skipped_opcode_count,std::vector<protobufs::IdUseDescriptor> * constant_uses)360 void FuzzerPassObfuscateConstants::MaybeAddConstantIdUse(
361     const opt::Instruction& inst, uint32_t in_operand_index,
362     uint32_t base_instruction_result_id,
363     const std::map<SpvOp, uint32_t>& skipped_opcode_count,
364     std::vector<protobufs::IdUseDescriptor>* constant_uses) {
365   if (inst.GetInOperand(in_operand_index).type != SPV_OPERAND_TYPE_ID) {
366     // The operand is not an id, so it cannot be a constant id.
367     return;
368   }
369   auto operand_id = inst.GetSingleWordInOperand(in_operand_index);
370   auto operand_definition =
371       GetIRContext()->get_def_use_mgr()->GetDef(operand_id);
372   switch (operand_definition->opcode()) {
373     case SpvOpConstantFalse:
374     case SpvOpConstantTrue:
375     case SpvOpConstant: {
376       // The operand is a constant id, so make an id use descriptor and record
377       // it.
378       protobufs::IdUseDescriptor id_use_descriptor;
379       id_use_descriptor.set_id_of_interest(operand_id);
380       id_use_descriptor.mutable_enclosing_instruction()
381           ->set_target_instruction_opcode(inst.opcode());
382       id_use_descriptor.mutable_enclosing_instruction()
383           ->set_base_instruction_result_id(base_instruction_result_id);
384       id_use_descriptor.mutable_enclosing_instruction()
385           ->set_num_opcodes_to_ignore(
386               skipped_opcode_count.find(inst.opcode()) ==
387                       skipped_opcode_count.end()
388                   ? 0
389                   : skipped_opcode_count.at(inst.opcode()));
390       id_use_descriptor.set_in_operand_index(in_operand_index);
391       constant_uses->push_back(id_use_descriptor);
392     } break;
393     default:
394       break;
395   }
396 }
397 
Apply()398 void FuzzerPassObfuscateConstants::Apply() {
399   // First, gather up all the constant uses available in the module, by going
400   // through each block in each function.
401   std::vector<protobufs::IdUseDescriptor> constant_uses;
402   for (auto& function : *GetIRContext()->module()) {
403     for (auto& block : function) {
404       // For each constant use we encounter we are going to make an id use
405       // descriptor. An id use is described with respect to a base instruction;
406       // if there are instructions at the start of the block without result ids,
407       // the base instruction will have to be the block's label.
408       uint32_t base_instruction_result_id = block.id();
409 
410       // An id use descriptor also records how many instructions of a particular
411       // opcode need to be skipped in order to find the instruction of interest
412       // from the base instruction. We maintain a mapping that records a skip
413       // count for each relevant opcode.
414       std::map<SpvOp, uint32_t> skipped_opcode_count;
415 
416       // Go through each instruction in the block.
417       for (auto& inst : block) {
418         if (inst.HasResultId()) {
419           // The instruction has a result id, so can be used as the base
420           // instruction from now on, until another instruction with a result id
421           // is encountered.
422           base_instruction_result_id = inst.result_id();
423           // Opcode skip counts were with respect to the previous base
424           // instruction and are now irrelevant.
425           skipped_opcode_count.clear();
426         }
427 
428         switch (inst.opcode()) {
429           case SpvOpPhi:
430             // The instruction must not be an OpPhi, as we cannot insert
431             // instructions before an OpPhi.
432             // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902):
433             //  there is scope for being less conservative.
434             break;
435           case SpvOpVariable:
436             // The instruction must not be an OpVariable, the only id that an
437             // OpVariable uses is an initializer id, which has to remain
438             // constant.
439             break;
440           default:
441             // Consider each operand of the instruction, and add a constant id
442             // use for the operand if relevant.
443             for (uint32_t in_operand_index = 0;
444                  in_operand_index < inst.NumInOperands(); in_operand_index++) {
445               MaybeAddConstantIdUse(inst, in_operand_index,
446                                     base_instruction_result_id,
447                                     skipped_opcode_count, &constant_uses);
448             }
449             break;
450         }
451 
452         if (!inst.HasResultId()) {
453           // The instruction has no result id, so in order to identify future id
454           // uses for instructions with this opcode from the existing base
455           // instruction, we need to increase the skip count for this opcode.
456           skipped_opcode_count[inst.opcode()] =
457               skipped_opcode_count.find(inst.opcode()) ==
458                       skipped_opcode_count.end()
459                   ? 1
460                   : skipped_opcode_count[inst.opcode()] + 1;
461         }
462       }
463     }
464   }
465 
466   // Go through the constant uses in a random order by repeatedly pulling out a
467   // constant use at a random index.
468   while (!constant_uses.empty()) {
469     auto index = GetFuzzerContext()->RandomIndex(constant_uses);
470     auto constant_use = std::move(constant_uses[index]);
471     constant_uses.erase(constant_uses.begin() + index);
472     // Decide probabilistically whether to skip or obfuscate this constant use.
473     if (!GetFuzzerContext()->ChoosePercentage(
474             GetFuzzerContext()->GetChanceOfObfuscatingConstant())) {
475       continue;
476     }
477     ObfuscateConstant(0, constant_use);
478   }
479 }
480 
481 }  // namespace fuzz
482 }  // namespace spvtools
483