mirror of https://github.com/crytic/slither
Merge pull request #148 from trailofbits/dev-upgradibility
Add slither-check-upgradability commandpull/169/head
commit
e545eaab37
@ -0,0 +1,50 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
### Test slither-check-upgradability |
||||||
|
|
||||||
|
DIR_TESTS="tests/check-upgradability" |
||||||
|
|
||||||
|
slither-check-upgradability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 > test_1.txt 2>&1 |
||||||
|
DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") |
||||||
|
if [ "$DIFF" != "" ] |
||||||
|
then |
||||||
|
echo "slither-check-upgradability failed" |
||||||
|
cat test_1.txt |
||||||
|
cat "$DIR_TESTS/test_1.txt" |
||||||
|
exit -1 |
||||||
|
fi |
||||||
|
|
||||||
|
slither-check-upgradability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-version "$DIR_TESTS/contractV2.sol" --new-contract-name ContractV2 > test_2.txt 2>&1 |
||||||
|
DIFF=$(diff test_2.txt "$DIR_TESTS/test_2.txt") |
||||||
|
if [ "$DIFF" != "" ] |
||||||
|
then |
||||||
|
echo "slither-check-upgradability failed" |
||||||
|
cat test_2.txt |
||||||
|
cat "$DIR_TESTS/test_2.txt" |
||||||
|
exit -1 |
||||||
|
fi |
||||||
|
|
||||||
|
slither-check-upgradability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-version "$DIR_TESTS/contractV2_bug.sol" --new-contract-name ContractV2 > test_3.txt 2>&1 |
||||||
|
DIFF=$(diff test_3.txt "$DIR_TESTS/test_3.txt") |
||||||
|
if [ "$DIFF" != "" ] |
||||||
|
then |
||||||
|
echo "slither-check-upgradability failed" |
||||||
|
cat test_3.txt |
||||||
|
cat "$DIR_TESTS/test_3.txt" |
||||||
|
exit -1 |
||||||
|
fi |
||||||
|
|
||||||
|
slither-check-upgradability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-version "$DIR_TESTS/contractV2_bug2.sol" --new-contract-name ContractV2 > test_4.txt 2>&1 |
||||||
|
DIFF=$(diff test_4.txt "$DIR_TESTS/test_4.txt") |
||||||
|
if [ "$DIFF" != "" ] |
||||||
|
then |
||||||
|
echo "slither-check-upgradability failed" |
||||||
|
cat test_4.txt |
||||||
|
cat "$DIR_TESTS/test_4.txt" |
||||||
|
exit -1 |
||||||
|
fi |
||||||
|
|
||||||
|
rm test_1.txt |
||||||
|
rm test_2.txt |
||||||
|
rm test_3.txt |
||||||
|
rm test_4.txt |
@ -0,0 +1,34 @@ |
|||||||
|
""" |
||||||
|
Module printing summary of the contract |
||||||
|
""" |
||||||
|
import collections |
||||||
|
from prettytable import PrettyTable |
||||||
|
from slither.printers.abstract_printer import AbstractPrinter |
||||||
|
from slither.utils.colors import blue, green, magenta |
||||||
|
from slither.utils.function import get_function_id |
||||||
|
|
||||||
|
class FunctionIds(AbstractPrinter): |
||||||
|
|
||||||
|
ARGUMENT = 'function-id' |
||||||
|
HELP = 'Print the keccack256 signature of the functions' |
||||||
|
|
||||||
|
def output(self, _filename): |
||||||
|
""" |
||||||
|
_filename is not used |
||||||
|
Args: |
||||||
|
_filename(string) |
||||||
|
""" |
||||||
|
|
||||||
|
txt = '' |
||||||
|
for contract in self.slither.contracts_derived: |
||||||
|
txt += '\n{}:\n'.format(contract.name) |
||||||
|
table = PrettyTable(['Name', 'ID']) |
||||||
|
for function in contract.functions: |
||||||
|
if function.visibility in ['public', 'external']: |
||||||
|
table.add_row([function.full_name, hex(get_function_id(function.full_name))]) |
||||||
|
for variable in contract.state_variables: |
||||||
|
if variable.visibility in ['public']: |
||||||
|
table.add_row([variable.name+'()', hex(get_function_id(variable.name+'()'))]) |
||||||
|
txt += str(table) + '\n' |
||||||
|
|
||||||
|
self.info(txt) |
@ -0,0 +1,29 @@ |
|||||||
|
""" |
||||||
|
Module printing summary of the contract |
||||||
|
""" |
||||||
|
|
||||||
|
from prettytable import PrettyTable |
||||||
|
from slither.printers.abstract_printer import AbstractPrinter |
||||||
|
|
||||||
|
class VariablesOrder(AbstractPrinter): |
||||||
|
|
||||||
|
ARGUMENT = 'variables-order' |
||||||
|
HELP = 'Print the storage order of the state variables' |
||||||
|
|
||||||
|
def output(self, _filename): |
||||||
|
""" |
||||||
|
_filename is not used |
||||||
|
Args: |
||||||
|
_filename(string) |
||||||
|
""" |
||||||
|
|
||||||
|
txt = '' |
||||||
|
for contract in self.slither.contracts_derived: |
||||||
|
txt += '\n{}:\n'.format(contract.name) |
||||||
|
table = PrettyTable(['Name', 'Type']) |
||||||
|
for variable in contract.state_variables: |
||||||
|
if not variable.is_constant: |
||||||
|
table.add_row([variable.name, str(variable.type)]) |
||||||
|
txt += str(table) + '\n' |
||||||
|
|
||||||
|
self.info(txt) |
@ -0,0 +1,14 @@ |
|||||||
|
import hashlib |
||||||
|
import sha3 |
||||||
|
|
||||||
|
def get_function_id(sig): |
||||||
|
'''' |
||||||
|
Return the function id of the given signature |
||||||
|
Args: |
||||||
|
sig (str) |
||||||
|
Return: |
||||||
|
(int) |
||||||
|
''' |
||||||
|
s = sha3.keccak_256() |
||||||
|
s.update(sig.encode('utf-8')) |
||||||
|
return int("0x" + s.hexdigest()[:8], 16) |
@ -0,0 +1,3 @@ |
|||||||
|
contract ContractV1{ |
||||||
|
address destination; |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
contract ContractV2{ |
||||||
|
address destination; |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
contract ContractV2{ |
||||||
|
uint destination; |
||||||
|
|
||||||
|
uint public myFunc; |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
contract Base { |
||||||
|
uint val; |
||||||
|
} |
||||||
|
contract ContractV2 is Base{ |
||||||
|
address destination; |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/* |
||||||
|
Fake proxy, do not use |
||||||
|
*/ |
||||||
|
|
||||||
|
pragma solidity ^0.5.0; |
||||||
|
|
||||||
|
contract Proxy{ |
||||||
|
|
||||||
|
address destination; |
||||||
|
|
||||||
|
function myFunc() public{} |
||||||
|
|
||||||
|
function () external{ |
||||||
|
uint size_destination_code; |
||||||
|
assembly{ |
||||||
|
size_destination_code := extcodesize(destination_slot) |
||||||
|
} |
||||||
|
require(size_destination_code>0); |
||||||
|
(bool ret_status,bytes memory ret_values) = destination.delegatecall(msg.data); |
||||||
|
require(ret_status); |
||||||
|
uint length = ret_values.length; |
||||||
|
assembly{ |
||||||
|
return (ret_values, length) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
import logging |
||||||
|
import argparse |
||||||
|
import sys |
||||||
|
|
||||||
|
from slither import Slither |
||||||
|
|
||||||
|
from .compare_variables_order import compare_variables_order_implementation, compare_variables_order_proxy |
||||||
|
from .compare_function_ids import compare_function_ids |
||||||
|
|
||||||
|
logging.basicConfig() |
||||||
|
logger = logging.getLogger("Slither-check-upgradability") |
||||||
|
|
||||||
|
def parse_args(): |
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Slither Upgradability Checks', |
||||||
|
usage="slither-check-upgradability proxy.sol ProxyName implem.sol ContractName") |
||||||
|
|
||||||
|
|
||||||
|
parser.add_argument('proxy.sol', help='Proxy filename') |
||||||
|
parser.add_argument('ProxyName', help='Contract name') |
||||||
|
|
||||||
|
parser.add_argument('implem.sol', help='Implementation filename') |
||||||
|
parser.add_argument('ContractName', help='Contract name') |
||||||
|
|
||||||
|
parser.add_argument('--new-version', help='New implementation filename') |
||||||
|
parser.add_argument('--new-contract-name', help='New contract name (if changed)') |
||||||
|
parser.add_argument('--solc', help='solc path', default='solc') |
||||||
|
|
||||||
|
if len(sys.argv) == 1: |
||||||
|
parser.print_help(sys.stderr) |
||||||
|
sys.exit(1) |
||||||
|
|
||||||
|
return parser.parse_args() |
||||||
|
|
||||||
|
def main(): |
||||||
|
args = parse_args() |
||||||
|
|
||||||
|
proxy = Slither(vars(args)['proxy.sol'], solc=args.solc) |
||||||
|
proxy_name = args.ProxyName |
||||||
|
v1 = Slither(vars(args)['implem.sol'], solc=args.solc) |
||||||
|
v1_name = args.ContractName |
||||||
|
|
||||||
|
last_contract = v1 |
||||||
|
last_name = v1_name |
||||||
|
|
||||||
|
if args.new_version: |
||||||
|
v2 = Slither(args.new_version, solc=args.solc) |
||||||
|
last_contract = v2 |
||||||
|
|
||||||
|
if args.new_contract_name: |
||||||
|
last_name = args.new_contract_name |
||||||
|
|
||||||
|
compare_function_ids(last_contract, proxy) |
||||||
|
compare_variables_order_proxy(last_contract, last_name, proxy, proxy_name) |
||||||
|
|
||||||
|
if args.new_version: |
||||||
|
compare_variables_order_implementation(v1, v1_name, v2, last_name) |
@ -0,0 +1,42 @@ |
|||||||
|
''' |
||||||
|
Check for functions collisions between a proxy and the implementation |
||||||
|
More for information: https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357 |
||||||
|
''' |
||||||
|
|
||||||
|
import logging |
||||||
|
from slither import Slither |
||||||
|
from slither.utils.function import get_function_id |
||||||
|
from slither.utils.colors import red, green |
||||||
|
|
||||||
|
logger = logging.getLogger("CompareFunctions") |
||||||
|
logger.setLevel(logging.INFO) |
||||||
|
|
||||||
|
def get_signatures(s): |
||||||
|
functions = [contract.functions for contract in s.contracts_derived] |
||||||
|
functions = [item for sublist in functions for item in sublist] |
||||||
|
functions = [f.full_name for f in functions if f.visibility in ['public', 'external']] |
||||||
|
|
||||||
|
variables = [contract.state_variables for contract in s.contracts_derived] |
||||||
|
variables = [item for sublist in variables for item in sublist] |
||||||
|
variables = [variable.name+ '()' for variable in variables if variable.visibility in ['public']] |
||||||
|
return list(set(functions+variables)) |
||||||
|
|
||||||
|
|
||||||
|
def compare_function_ids(implem, proxy): |
||||||
|
|
||||||
|
signatures_implem = get_signatures(implem) |
||||||
|
signatures_proxy = get_signatures(proxy) |
||||||
|
|
||||||
|
signatures_ids_implem = {get_function_id(s): s for s in signatures_implem} |
||||||
|
signatures_ids_proxy = {get_function_id(s): s for s in signatures_proxy} |
||||||
|
|
||||||
|
found = False |
||||||
|
for (k, _) in signatures_ids_implem.items(): |
||||||
|
if k in signatures_ids_proxy: |
||||||
|
found = True |
||||||
|
logger.info(red('Function id collision found {} {}'.format(signatures_ids_implem[k], |
||||||
|
signatures_ids_proxy[k]))) |
||||||
|
|
||||||
|
if not found: |
||||||
|
logger.info(green('No function id collision found')) |
||||||
|
|
@ -0,0 +1,96 @@ |
|||||||
|
''' |
||||||
|
Check if the variables respect the same ordering |
||||||
|
''' |
||||||
|
import logging |
||||||
|
from slither import Slither |
||||||
|
from slither.utils.function import get_function_id |
||||||
|
from slither.utils.colors import red, green, yellow |
||||||
|
|
||||||
|
logger = logging.getLogger("VariablesOrder") |
||||||
|
logger.setLevel(logging.INFO) |
||||||
|
|
||||||
|
def compare_variables_order_implementation(v1, contract_name1, v2, contract_name2): |
||||||
|
|
||||||
|
contract_v1 = v1.get_contract_from_name(contract_name1) |
||||||
|
if contract_v1 is None: |
||||||
|
logger.info(red('Contract {} not found in {}'.format(contract_name1, v1.filename))) |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
contract_v2 = v2.get_contract_from_name(contract_name2) |
||||||
|
if contract_v2 is None: |
||||||
|
logger.info(red('Contract {} not found in {}'.format(contract_name2, v2.filename))) |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
|
||||||
|
order_v1 = [(variable.name, variable.type) for variable in contract_v1.state_variables if not variable.is_constant] |
||||||
|
order_v2 = [(variable.name, variable.type) for variable in contract_v2.state_variables if not variable.is_constant] |
||||||
|
|
||||||
|
|
||||||
|
found = False |
||||||
|
for idx in range(0, len(order_v1)): |
||||||
|
(v1_name, v1_type) = order_v1[idx] |
||||||
|
if len(order_v2) < idx: |
||||||
|
logger.info(red('Missing variable in the new version: {} {}'.format(v1_name, v1_type))) |
||||||
|
continue |
||||||
|
(v2_name, v2_type) = order_v2[idx] |
||||||
|
|
||||||
|
if (v1_name != v2_name) or (v1_type != v2_type): |
||||||
|
found = True |
||||||
|
logger.info(red('Different variables between v1 and v2: {} {} -> {} {}'.format(v1_name, |
||||||
|
v1_type, |
||||||
|
v2_name, |
||||||
|
v2_type))) |
||||||
|
|
||||||
|
if len(order_v2) > len(order_v1): |
||||||
|
new_variables = order_v2[len(order_v1):] |
||||||
|
for (name, t) in new_variables: |
||||||
|
logger.info(green('New variable: {} {}'.format(name, t))) |
||||||
|
|
||||||
|
if not found: |
||||||
|
logger.info(green('No error found (variables ordering between implementations)')) |
||||||
|
|
||||||
|
def compare_variables_order_proxy(implem, implem_name, proxy, proxy_name): |
||||||
|
|
||||||
|
contract_implem = implem.get_contract_from_name(implem_name) |
||||||
|
if contract_implem is None: |
||||||
|
logger.info(red('Contract {} not found in {}'.format(implem_name, implem.filename))) |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
contract_proxy = proxy.get_contract_from_name(proxy_name) |
||||||
|
if contract_proxy is None: |
||||||
|
logger.info(red('Contract {} not found in {}'.format(proxy_name, proxy.filename))) |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
|
||||||
|
order_implem = [(variable.name, variable.type) for variable in contract_implem.state_variables if not variable.is_constant] |
||||||
|
order_proxy = [(variable.name, variable.type) for variable in contract_proxy.state_variables if not variable.is_constant] |
||||||
|
|
||||||
|
|
||||||
|
found = False |
||||||
|
for idx in range(0, len(order_proxy)): |
||||||
|
(proxy_name, proxy_type) = order_proxy[idx] |
||||||
|
if len(order_proxy) < idx: |
||||||
|
logger.info(red('Extra variable in the proxy: {} {}'.format(proxy_name, proxy_type))) |
||||||
|
continue |
||||||
|
(implem_name, implem_type) = order_implem[idx] |
||||||
|
|
||||||
|
if (proxy_name != implem_name) or (proxy_type != implem_type): |
||||||
|
found = True |
||||||
|
logger.info(red('Different variables between proxy and implem: {} {} -> {} {}'.format(proxy_name, |
||||||
|
proxy_type, |
||||||
|
implem_name, |
||||||
|
implem_type))) |
||||||
|
else: |
||||||
|
logger.info(yellow('Variable in the proxy: {} {}'.format(proxy_name, |
||||||
|
proxy_type))) |
||||||
|
|
||||||
|
|
||||||
|
#if len(order_implem) > len(order_proxy): |
||||||
|
# new_variables = order_implem[len(order_proxy):] |
||||||
|
# for (name, t) in new_variables: |
||||||
|
# logger.info(green('Variable only in implem: {} {}'.format(name, t))) |
||||||
|
|
||||||
|
if not found: |
||||||
|
logger.info(green('No error found (variables ordering proxy <-> implementation)')) |
||||||
|
|
||||||
|
|
Loading…
Reference in new issue