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