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