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