From 2ac0abf51882e6728e29592161ddc8756381c41b Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 1 Mar 2019 12:23:22 +0000 Subject: [PATCH] Improve slither-find-paths + add travis unit tests --- scripts/travis_test_find_paths.sh | 16 +++++ tests/possible_paths/paths.sol | 17 +++++ tests/possible_paths/paths.txt | 14 ++++ utils/possible_paths/possible_paths.py | 95 ++++---------------------- 4 files changed, 62 insertions(+), 80 deletions(-) create mode 100755 scripts/travis_test_find_paths.sh create mode 100644 tests/possible_paths/paths.sol create mode 100644 tests/possible_paths/paths.txt diff --git a/scripts/travis_test_find_paths.sh b/scripts/travis_test_find_paths.sh new file mode 100755 index 000000000..85c49f733 --- /dev/null +++ b/scripts/travis_test_find_paths.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +### Test slither-check-upgradability + +DIR_TESTS="tests/possible_paths" + +slither-find-paths "$DIR_TESTS/paths.sol" A.destination --solc solc-0.5.0 > test_possible_paths.txt 2>&1 +DIFF=$(diff test_possible_paths.txt "$DIR_TESTS/paths.txt") +if [ "$DIFF" != "" ] +then + echo "slither-find-paths failed" + cat test_possible_paths.txt + cat "$DIR_TESTS/paths.txt" + exit -1 +fi +rm test_possible_paths.txt diff --git a/tests/possible_paths/paths.sol b/tests/possible_paths/paths.sol new file mode 100644 index 000000000..15f2b1d8b --- /dev/null +++ b/tests/possible_paths/paths.sol @@ -0,0 +1,17 @@ +contract A{ + function destination() private{ + + } + + function call() public{ + destination(); + } +} + +contract B{ + + + function call2(A a) public{ + a.call(); + } +} diff --git a/tests/possible_paths/paths.txt b/tests/possible_paths/paths.txt new file mode 100644 index 000000000..00d6c0ead --- /dev/null +++ b/tests/possible_paths/paths.txt @@ -0,0 +1,14 @@ +Target functions: +- A.destination() + + +The following functions reach the specified targets: +- A.call() +- B.call2(A) + + +The following paths reach the specified targets: +A.call() -> A.destination() + +B.call2(A) -> A.call() -> A.destination() + diff --git a/utils/possible_paths/possible_paths.py b/utils/possible_paths/possible_paths.py index e65ddb1c8..0d9976e08 100644 --- a/utils/possible_paths/possible_paths.py +++ b/utils/possible_paths/possible_paths.py @@ -1,9 +1,7 @@ -import os -import argparse -from slither import Slither +class ResolveFunctionException(Exception): pass -def resolve_function(contract_name, function_name): +def resolve_function(slither, contract_name, function_name): """ Resolves a function instance, given a contract name and function. :param contract_name: The name of the contract the function is declared in. @@ -15,20 +13,20 @@ def resolve_function(contract_name, function_name): # Verify the contract was resolved successfully if contract is None: - raise ValueError(f"Could not resolve target contract: {contract_name}") + raise ResolveFunctionException(f"Could not resolve target contract: {contract_name}") # Obtain the target function target_function = next((function for function in contract.functions if function.name == function_name), None) # Verify we have resolved the function specified. if target_function is None: - raise ValueError(f"Could not resolve target function: {contract_name}.{function_name}") + raise ResolveFunctionException(f"Could not resolve target function: {contract_name}.{function_name}") # Add the resolved function to the new list. return target_function -def resolve_functions(functions): +def resolve_functions(slither, functions): """ Resolves the provided function descriptors. :param functions: A list of tuples (contract_name, function_name) or str (of form "ContractName.FunctionName") @@ -40,7 +38,7 @@ def resolve_functions(functions): # Verify that the provided argument is a list. if not isinstance(functions, list): - raise ValueError("Provided functions to resolve must be a list type.") + raise ResolveFunctionException("Provided functions to resolve must be a list type.") # Loop for each item in the list. for item in functions: @@ -48,15 +46,15 @@ def resolve_functions(functions): # If the item is a single string, we assume it is of form 'ContractName.FunctionName'. parts = item.split('.') if len(parts) < 2: - raise ValueError("Provided string descriptor must be of form 'ContractName.FunctionName'") - resolved.append(resolve_function(parts[0], parts[1])) + raise ResolveFunctionException("Provided string descriptor must be of form 'ContractName.FunctionName'") + resolved.append(resolve_function(slither, parts[0], parts[1])) elif isinstance(item, tuple): # If the item is a tuple, it should be a 2-tuple providing contract and function names. if len(item) != 2: - raise ValueError("Provided tuple descriptor must provide a contract and function name.") - resolved.append(resolve_function(item[0], item[1])) + raise ResolveFunctionException("Provided tuple descriptor must provide a contract and function name.") + resolved.append(resolve_function(slither, item[0], item[1])) else: - raise ValueError(f"Unexpected function descriptor type to resolve in list: {type(item)}") + raise ResolveFunctionException(f"Unexpected function descriptor type to resolve in list: {type(item)}") # Return the resolved list. return resolved @@ -73,7 +71,7 @@ def all_function_definitions(function): if f.full_name == function.full_name] -def __find_target_paths(target_function, current_path=[]): +def __find_target_paths(slither, target_function, current_path=[]): # Create our results list results = set() @@ -99,7 +97,7 @@ def __find_target_paths(target_function, current_path=[]): # If any of our target functions are reachable from this function, it's a result. if all_target_functions.intersection(called_functions): - path_results = __find_target_paths(function, current_path.copy()) + path_results = __find_target_paths(slither, function, current_path.copy()) if path_results: results = results.union(path_results) @@ -110,7 +108,7 @@ def __find_target_paths(target_function, current_path=[]): return results -def find_target_paths(target_functions): +def find_target_paths(slither, target_functions): """ Obtains all functions which can lead to any of the target functions being called. :param target_functions: The functions we are interested in reaching. @@ -121,72 +119,9 @@ def find_target_paths(target_functions): # Loop for each target function for target_function in target_functions: - results = results.union(__find_target_paths(target_function)) + results = results.union(__find_target_paths(slither, target_function)) return results -def parse_args(): - """ - Parse the underlying arguments for the program. - :return: Returns the arguments for the program. - """ - parser = argparse.ArgumentParser(description='PossiblePaths', - usage='possible_paths.py [--is-truffle] filename [contract.function targets]') - - parser.add_argument('--is-truffle', - help='Indicates the filename refers to a truffle directory path.', - action='store_true', - default=False) - - parser.add_argument('filename', - help='The filename of the contract or truffle directory to analyze.') - - parser.add_argument('targets', nargs='+') - - return parser.parse_args() - - -# ------------------------------ -# PossiblePaths.py -# Usage: python3 possible_paths.py [--is-truffle] filename targets -# Example: python3 possible_paths.py contract.sol contract1.function1 contract2.function2 contract3.function3 -# ------------------------------ -# Parse all arguments -args = parse_args() - -# If this is a truffle project, verify we have a valid build directory. -if args.is_truffle: - cwd = os.path.abspath(args.filename) - build_dir = os.path.join(cwd, "build", "contracts") - if not os.path.exists(build_dir): - raise FileNotFoundError(f"Could not find truffle build directory at '{build_dir}'") - -# Perform slither analysis on the given filename -slither = Slither(args.filename, is_truffle=args.is_truffle) - -targets = resolve_functions(args.targets) - -# Print out all target functions. -print(f"Target functions:") -for target in targets: - print(f"-{target.contract.name}.{target.full_name}") -print("\n") - -# Obtain all paths which reach the target functions. -reaching_paths = find_target_paths(targets) -reaching_functions = set([y for x in reaching_paths for y in x if y not in targets]) - -# Print out all function names which can reach the targets. -print(f"The following functions reach the specified targets:") -for function_desc in sorted([f"{f.contract.name}.{f.full_name}" for f in reaching_functions]): - print(f"-{function_desc}") -print("\n") - -# Format all function paths. -reaching_paths_str = [' -> '.join([f"{f.contract.name}.{f.full_name}" for f in reaching_path]) for reaching_path in reaching_paths] -# Print a sorted list of all function paths which can reach the targets. -print(f"The following paths reach the specified targets:") -for reaching_path in sorted(reaching_paths_str): - print(f"{reaching_path}\n")