blob: 7f23008f3d3cdd384dcbf6fee0568ea83541adcd [file] [log] [blame]
#!/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.check import CheckResult
from shared.constants import parse_required_constants
from shared.control_flow import program_control_graph, subroutine_control_graph
from shared.decode import decode_elf
from shared.information_flow_analysis import (get_program_iflow,
get_subroutine_iflow,
stringify_control_deps)
def main() -> int:
parser = argparse.ArgumentParser(
description='Analyze whether secret data affects the control flow of '
'an OTBN program or subroutine.')
parser.add_argument('elf', help=('The .elf file to check.'))
parser.add_argument('--verbose', action='store_true')
parser.add_argument(
'--subroutine',
required=False,
help=('The specific subroutine to check. If not provided, the start '
'point is _imem_start (whole program).'))
parser.add_argument(
'--constants',
nargs='+',
type=str,
required=False,
help=('Registers which are required to be constant at the start of the '
'subroutine. Only valid if `--subroutine` is passed. Write '
'in the form "reg:value", e.g. x3:5. Only GPRs are accepted as '
'required constants.'))
parser.add_argument(
'--secrets',
nargs='+',
type=str,
required=False,
help=('Initial secret information-flow nodes. If not provided, '
'assume everything is secret; check that the subroutine or '
'program has only one possible control-flow path regardless '
'of input.'))
args = parser.parse_args()
# Parse initial constants.
if args.constants is None:
constants = {}
else:
if args.subroutine is None:
raise ValueError('Cannot require initial constants for a whole '
'program; use --subroutine to analyze a specific '
'subroutine.')
constants = parse_required_constants(args.constants)
# Compute control graph and get all nodes that influence control flow.
program = decode_elf(args.elf)
if args.subroutine is None:
graph = program_control_graph(program)
to_analyze = 'entire program'
_, control_deps = get_program_iflow(program, graph)
else:
graph = subroutine_control_graph(program, args.subroutine)
to_analyze = 'subroutine {}'.format(args.subroutine)
_, _, control_deps = get_subroutine_iflow(program, graph,
args.subroutine, constants)
if args.secrets is None:
if args.verbose:
print(
'No specific secrets provided; checking that {} has only one '
'control-flow path'.format(to_analyze))
secret_control_deps = control_deps
else:
if args.verbose:
print('Analyzing {} with initial secrets {} and initial constants {}'.format(
to_analyze, args.secrets, constants))
# If secrets were provided, only show the ways in which those specific
# nodes could influence control flow.
secret_control_deps = {
node: pcs
for node, pcs in control_deps.items() if node in args.secrets
}
out = CheckResult()
if len(secret_control_deps) != 0:
msg = 'The following secrets may influence control flow:\n '
msg += '\n '.join(stringify_control_deps(program,
secret_control_deps))
out.err(msg)
if args.verbose or out.has_errors() or out.has_warnings():
print(out.report())
if out.has_errors() or out.has_warnings():
return 1
return 0
if __name__ == "__main__":
sys.exit(main())