|  | #!/usr/bin/env python3 | 
|  | # Copyright lowRISC contributors. | 
|  | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | # SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | import argparse | 
|  | import sys | 
|  |  | 
|  | from shared.control_flow import program_control_graph, subroutine_control_graph | 
|  | from shared.decode import decode_elf | 
|  | from shared.information_flow import InformationFlowGraph | 
|  | from shared.information_flow_analysis import (get_program_iflow, | 
|  | get_subroutine_iflow, | 
|  | stringify_control_deps) | 
|  |  | 
|  |  | 
|  | def main() -> int: | 
|  | parser = argparse.ArgumentParser(description=( | 
|  | 'Analyze the control flow and information flow of an OTBN ' | 
|  | 'program or subroutine.')) | 
|  | parser.add_argument('elf', help=('The .elf file to check.')) | 
|  | parser.add_argument( | 
|  | '--verbose', | 
|  | action='store_true', | 
|  | help=('Print full control-flow and information-flow graphs.')) | 
|  | parser.add_argument( | 
|  | '--subroutine', | 
|  | required=False, | 
|  | help=( | 
|  | 'The specific subroutine to check. If not provided, start point is ' | 
|  | '_imem_start (whole program).')) | 
|  | parser.add_argument( | 
|  | '--secrets', | 
|  | nargs='+', | 
|  | type=str, | 
|  | required=False, | 
|  | help=( | 
|  | 'Initially secret information-flow nodes. If provided, the final ' | 
|  | 'secrets will be printed.')) | 
|  | args = parser.parse_args() | 
|  | program = decode_elf(args.elf) | 
|  |  | 
|  | # Compute control-flow graph. | 
|  | if args.subroutine is None: | 
|  | graph = program_control_graph(program) | 
|  | else: | 
|  | graph = subroutine_control_graph(program, args.subroutine) | 
|  |  | 
|  | # Only print the control-flow graph if --verbose is set. | 
|  | if args.verbose: | 
|  | print('Control-flow graph:') | 
|  | print(graph.pretty(program, indent=2)) | 
|  | cycle_pcs = graph.get_cycle_starts() | 
|  | if cycle_pcs: | 
|  | print('Control flow has cycles starting at the following PCs:') | 
|  | for pc in cycle_pcs: | 
|  | symbols = program.get_symbols_for_pc(pc) | 
|  | label_str = ' <{}>'.format( | 
|  | ', '.join(symbols)) if symbols else '' | 
|  | print('{:#x}{}'.format(pc, label_str)) | 
|  |  | 
|  | # Compute information-flow graph(s). | 
|  | if args.subroutine is None: | 
|  | what = 'program' | 
|  | end_iflow, control_deps = get_program_iflow(program, graph) | 
|  | ret_iflow = InformationFlowGraph.nonexistent() | 
|  | else: | 
|  | what = 'subroutine' | 
|  | ret_iflow, end_iflow, control_deps = get_subroutine_iflow( | 
|  | program, graph, args.subroutine) | 
|  |  | 
|  | # If no secrets were given or the --verbose flag is set, then print the | 
|  | # full information-flow graphs. | 
|  | if (args.verbose or args.secrets is None): | 
|  | if ret_iflow.exists: | 
|  | print( | 
|  | 'Information flow for paths ending in a return to the caller:') | 
|  | print(ret_iflow.pretty(indent=2)) | 
|  | if end_iflow.exists: | 
|  | print('--------') | 
|  |  | 
|  | if end_iflow.exists: | 
|  | print('Information flow for paths ending the program:') | 
|  | print(end_iflow.pretty(indent=2)) | 
|  |  | 
|  | if args.secrets is None: | 
|  | # If no initial secrets were provided, we will print all nodes that | 
|  | # could influence control flow. | 
|  | control_what = 'information-flow nodes' | 
|  | else: | 
|  | # If secrets were provided, only show the ways in which those specific | 
|  | # nodes could influence control flow. | 
|  | control_what = 'secrets' | 
|  | control_deps = { | 
|  | name: pcs | 
|  | for name, pcs in control_deps.items() if name in args.secrets | 
|  | } | 
|  |  | 
|  | # Print any (secret) nodes that influence control flow, and the PCs of the | 
|  | # control-flow instructions they influence. | 
|  | if len(control_deps) == 0: | 
|  | print('No {} were found to influence this {}\'s control flow.'.format( | 
|  | control_what, what)) | 
|  | else: | 
|  | print('The following {} may influence control flow in this {}:'.format( | 
|  | control_what, what)) | 
|  | for node in stringify_control_deps(program, control_deps): | 
|  | print(node) | 
|  |  | 
|  | # Print final secrets (if initial secrets were provided). | 
|  | if args.secrets is not None: | 
|  | if ret_iflow.exists: | 
|  | final_secrets = { | 
|  | sink | 
|  | for node in args.secrets for sink in ret_iflow.sinks(node) | 
|  | } | 
|  | print('Final secrets for paths ending in a return to the caller:', | 
|  | ', '.join(sorted(final_secrets))) | 
|  | if end_iflow.exists: | 
|  | final_secrets = { | 
|  | sink | 
|  | for node in args.secrets for sink in end_iflow.sinks(node) | 
|  | } | 
|  | print('Final secrets for paths ending the program:', | 
|  | ', '.join(sorted(final_secrets))) | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | sys.exit(main()) |