fuzzer_pass_obfuscate_constants.cpp 20 KB

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