blob: f34fd1f60a077b30f02f4464263fe65db44eeda9 [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 typing import Dict, List
from shared.check import CheckResult
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)
# GPR maximum value.
GPR_MAX = (1 << 32) - 1
def is_gpr_name(name: str):
return name in [f'x{i}' for i in range(32)]
def parse_required_constants(constants: List[str]) -> Dict[str,int]:
'''Parses required initial constants.
Constants are expected to be provided in the form <reg>:<value>, e.g.
x5:0xfffffff or x22:0. The value can be expressed in decimal or integer
form. Only GPRs are accepted as required constants (not wide registers or
special registers).
'''
out = {}
for token in constants:
reg_and_value = token.split(':')
if len(reg_and_value) != 2:
raise ValueError(
f'Could not parse required constant {token}. Please '
'provide required constants in the form <reg>:<value>, '
'e.g. x5:3.')
reg, value = reg_and_value
if not is_gpr_name(reg):
raise ValueError(
f'Cannot parse required constant {token}: {reg} is not a '
'valid GPR name.')
if not value.isnumeric():
raise ValueError(
f'Cannot parse required constant {token}: {value} is not '
'a recognized numeric value.')
value = int(value)
if value < 0 or value > GPR_MAX:
raise ValueError(
f'Cannot parse required constant {token}: {value} is out '
'of range [0, GPR_MAX].')
out[reg] = value
return out
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())