fuzzer_pass_obfuscate_constants.cpp 20 KB

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