create_seed_file_pair.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. #!/usr/bin/env python
  2. # Copyright 2018 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Create binary protobuf encoding for fuzzer seeds.
  6. This script is used to generate binary encoded protobuf seeds for fuzzers
  7. related to Zucchini-gen and -apply, which take pairs of files are arguments. The
  8. binary protobuf format is faster to parse so it is the preferred method for
  9. encoding the seeds. For gen related fuzzers this should only need to be run
  10. once. For any apply related fuzzers this should be rerun whenever the patch
  11. format is changed.
  12. """
  13. import argparse
  14. import logging
  15. import os
  16. import subprocess
  17. import sys
  18. ABS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__)))
  19. PROTO_DEFINITION_FILE = 'file_pair.proto'
  20. def parse_args():
  21. """Parse commandline args."""
  22. parser = argparse.ArgumentParser()
  23. parser.add_argument('protoc_path', help='Path to protoc.')
  24. parser.add_argument('old_file', help='Old file to generate/apply patch.')
  25. parser.add_argument('new_or_patch_file',
  26. help='New file to generate or patch to apply.')
  27. parser.add_argument('output_file',
  28. help='File to write binary protobuf to.')
  29. parser.add_argument('--imposed_matches',
  30. help='Equivalence matches to impose when generating '
  31. 'the patch.')
  32. return parser.parse_args()
  33. def read_to_proto_escaped_string(filename):
  34. """Reads a file and converts it to hex escape sequences."""
  35. with open(filename, 'rb') as f:
  36. # Note that unicode-escape escapes all non-ASCII printable characters
  37. # excluding ", which needs to be manually escaped.
  38. return f.read().decode('latin1').encode('unicode-escape').replace(
  39. b'"', b'\\"')
  40. def main():
  41. args = parse_args()
  42. # Create an ASCII string representing a protobuf.
  43. content = [b'old_file: "%s"' % read_to_proto_escaped_string(args.old_file),
  44. b'new_or_patch_file: "%s"' % read_to_proto_escaped_string(
  45. args.new_or_patch_file)]
  46. if args.imposed_matches:
  47. content.append(b'imposed_matches: "%s"' %
  48. args.imposed_matches.encode('unicode-escape'))
  49. # Encode the ASCII protobuf as a binary protobuf.
  50. ps = subprocess.Popen([args.protoc_path, '--proto_path=%s' % ABS_PATH,
  51. '--encode=zucchini.fuzzers.FilePair',
  52. os.path.join(ABS_PATH, PROTO_DEFINITION_FILE)],
  53. stdin=subprocess.PIPE,
  54. stdout=subprocess.PIPE)
  55. # Write the string to the subprocess. Single line IO is fine as protoc returns
  56. # a string.
  57. output = ps.communicate(input=b'\n'.join(content))
  58. ps.wait()
  59. if ps.returncode:
  60. logging.error('Binary protobuf encoding failed.')
  61. return ps.returncode
  62. # Write stdout of the subprocess for protoc to the |output_file|.
  63. with open(args.output_file, 'wb') as f:
  64. f.write(output[0])
  65. return 0
  66. if __name__ == '__main__':
  67. sys.exit(main())