#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-2-Clause import os import argparse import itertools # Makefile-fuzz-generated.am is created from this template. MAKEFILE_FUZZ = '''# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2018 Intel Corporation # All rights reserved. if ENABLE_TCTI_FUZZING TESTS_FUZZ = %s %s endif # ENABLE_TCTI_FUZZING ''' # Each fuzz target in Makefile-fuzz-generated.am is created from this template. MAKEFILE_FUZZ_TARGET = ''' noinst_PROGRAMS += test/fuzz/%s.fuzz test_fuzz_%s_fuzz_CPPFLAGS = $(FUZZ_CPPFLAGS) test_fuzz_%s_fuzz_LDADD = $(FUZZLDADD) nodist_test_fuzz_%s_fuzz_SOURCES = test/fuzz/main-sapi.cpp \\ test/fuzz/%s.fuzz.cpp DISTCLEANFILES += test/fuzz/%s.fuzz.cpp''' # Common include definitions needed for fuzzing an SAPI call SAPI_TEMPLATE_HEADER = '''/* SPDX-License-Identifier: BSD-2-Clause */ /*********************************************************************** * Copyright (c) 2018, Intel Corporation * * All rights reserved. ***********************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include extern "C" { #include "tss2_mu.h" #include "tss2_sys.h" #include "tss2_tcti_device.h" #include "tss2-tcti/tcti-common.h" #include "tss2-tcti/tcti-device.h" #define LOGMODULE fuzz #include "tss2_tcti.h" #include "util/log.h" #include "test.h" #include "test-options.h" #include "context-util.h" #include "tss2-sys/sysapi_util.h" #include "tcti/tcti-fuzzing.h" } extern "C" int test_invoke ( TSS2_SYS_CONTEXT *sysContext)''' # Template to call a SAPI _Complete function which takes no arguments SAPI_COMPLETE_TEMPLATE_NO_ARGS = SAPI_TEMPLATE_HEADER + ''' { %s (sysContext); return EXIT_SUCCESS; } ''' # Template to call a SAPI _Complete function which takes arguments SAPI_COMPLETE_TEMPLATE_HAS_ARGS = SAPI_TEMPLATE_HEADER + ''' { %s %s ( sysContext, %s ); return EXIT_SUCCESS; } ''' # Template to call a SAPI _Prepare function SAPI_PREPARE_TEMPLATE_HAS_ARGS = SAPI_TEMPLATE_HEADER + ''' { int ret; %s ret = fuzz_fill ( sysContext, %d, %s ); if (ret) { return ret; } %s ( sysContext, %s ); return EXIT_SUCCESS; } ''' def gen_file(function): ''' Generate a cpp file used as the fuzz target given the function definition from a header file. ''' # Parse the function name from the function definition function_name = function.split('\n')[0]\ .replace('TSS2_RC', '')\ .replace('(', '')\ .strip() # Parse the function arguments into an array. Do not include sysContext. args = [arg.strip() \ for arg in function[function.index('(') + 1:function.index(');')]\ .split(',') \ if not 'TSS2_SYS_CONTEXT' in arg] # Prepare and Complete functions require different methods of generation. # Call the appropriate function to generate a cpp target specific to that # type of function. if '_Complete' in function_name: return gen_complete(function, function_name, args) if '_Prepare' in function_name: return gen_prepare(function, function_name, args) raise NotImplementedError('Unknown function type %r' % (function_name,)) def gen_complete(function, function_name, args): ''' Generate the cpp fuzz target for a SAPI _Complete call ''' if not args: # Fill in the no args template. Simple case. return function_name, SAPI_COMPLETE_TEMPLATE_NO_ARGS % (function_name) # Generate the cpp variable definitions. arg_definitions = (';\n' + ' ' * 4).join([ arg.replace('*', '') for arg in args]) + ';' # Generate the cpp arguments. For arguments that are pointers find replace * # with & so that we pass a pointer to the definition which has been # allocated on the stack. arg_call = (',\n' + ' ' * 8).join([ arg.replace('*', '&').split()[-1] for arg in args]) # Fill in the template return function_name, SAPI_COMPLETE_TEMPLATE_HAS_ARGS % (arg_definitions, function_name, arg_call) def gen_prepare(function, function_name, args): ''' Generate the cpp fuzz target for a SAPI _Prepare call ''' if not args: return function_name, None # Generate the cpp variable definitions. Make sure to initialize to empty # structs (works for initializing anything) or cpp compiler will complain. arg_definitions = (' = {0};\n' + ' ' * 4).join([ arg.replace('*', '').replace('const', '') for arg in args]) + ' = {0};' # Generate the cpp arguments. For arguments that are pointers find replace * # with & so that we pass a pointer to the definition which has been # allocated on the stack. arg_call = (',\n' + ' ' * 8).join([ arg.replace('*', '&').split()[-1] for arg in args]) # Generate the call to fuzz_fill. The call should be the sysContext, double # the number of arguments for the _Prepare call, and then for each _Prepare # argument pass two to fuzz_fill, the sizeof the _Prepare argument, and a # pointer to it. fill_fuzz_args = (',\n' + ' ' * 8).join([ ('sizeof (%s), &%s' % \ tuple([arg.replace('*', '').split()[-1]] * 2)) \ for arg in args]) # Fill in the template return function_name, SAPI_PREPARE_TEMPLATE_HAS_ARGS % (arg_definitions, len(args) * 2, fill_fuzz_args, function_name, arg_call) def functions_from_include(header): ''' Parse out and yield each function definition from a header file. ''' with open(header, 'r') as header_fd: current_function = '' for line in header_fd: # Functions we are interested in start with _Complete or _Prepare if '_Complete' in line or '_Prepare' in line: # Set the current_function to this line current_function = line elif current_function and ');' in line: # When we reach the closing parenthesis yield the function yield current_function + line.rstrip() current_function = '' elif current_function: # Add all the arguments to the function current_function += line def gen_files(header): # Generate a fuzz target cpp file from each function in the header file for current_function in functions_from_include(header): function_name, contents = gen_file(current_function) # Skip the yield if there is no fuzz target that can be generated if contents is None: continue # Yield the function name and the contents of its generated file yield function_name, contents def main(): parser = argparse.ArgumentParser(description='Generate libfuzzer for sapi') parser.add_argument('--header', default='include/tss2/tss2_sys.h', help='Header file to look in (default include/tss2/tss2_sys.h)') args = parser.parse_args() functions = dict(gen_files(args.header)) # Write the generated target to the file for its function name for function_name, contents in functions.items(): filepath = os.path.join('test', 'fuzz', function_name + '.fuzz.cpp') with open(filepath, 'w') as fuzzer_fd: fuzzer_fd.write(contents) # Fill in the Makefile-fuzz-generated.am template using the function names. # Create a list of the compiled fuzz targets files = ' \\\n '.join(['test/fuzz/%s.fuzz' % (function) \ for function in functions]) # Create the Makefile targets for each generated file targets = '\n'.join([MAKEFILE_FUZZ_TARGET % tuple(list(itertools.chain(\ ([function] * 6)))) for function in functions]) # Write out the Makefile-fuzz-generated.am file with open('Makefile-fuzz-generated.am', 'w') as makefile_fd: makefile_fd.write(MAKEFILE_FUZZ % (files, targets)) if __name__ == '__main__': main()