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