Merge branch 'master' into dev

pull/60/head
Josselin 6 years ago
commit a9448f0a13
  1. 52
      README.md
  2. 39
      scripts/travis_test.sh
  3. 233
      slither/__main__.py
  4. 67
      slither/utils/command_line.py

@ -36,33 +36,39 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct
### Printers ### Printers
* `--printer-summary`: Print a summary of the contracts By default, the `contract-summary` printer is used. Use --printers comma-separated list of printers,
* `--printer-quick-summary`: Print a quick summary of the contracts or `none` to disable the default printer.
* `--printer-inheritance`: Print the inheritance relations
* `--printer-inheritance-graph`: Print the inheritance graph in a file
* `--printer-call-graph`: Print the call graph in a file
* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function
## Checks available Num | Printer | Description
--- | --- | ---
1 | `contract-summary` | a summary of the contract
2 | `function-summary` | the summary of the functions
3 | `inheritance` | the inheritance relation between contracts
4 | `inheritance-graph` | the inheritance graph
5 | `slithir` | the slithIR
6 | `vars-and-auth` | the state variables written and the authorization of the functions
By default, all the checks are run. Use --detect-_name-of-check_ to run one check at a time. ## Detectors
Num | Check | What it Detects | Impact | Confidence By default, all the detectors are run. Use --detectors comma-separated list of detectors to run.
Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | --- --- | --- | --- | --- | ---
1 | `suicidal` | Suicidal functions | High | High 1 | `backdoor` | Function named backdoor (detector example) | High | High
2 | `uninitialized-state` | Uninitialized state variables | High | High 2 | `suicidal` | Suicidal functions | High | High
3 | `uninitialized-storage` | Uninitialized storage variables | High | High 3 | `uninitialized-state` | Uninitialized state variables | High | High
4 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium 4 | `uninitialized-storage` | Uninitialized storage variables | High | High
5 | `reentrancy` | Reentrancy vulnerabilities | High | Medium 5 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium
6 | `locked-ether` | Contracts that lock ether | Medium | High 6 | `reentrancy` | Reentrancy vulnerabilities | High | Medium
7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium 7 | `locked-ether` | Contracts that lock ether | Medium | High
8 | `assembly` | Assembly usage | Informational | High 8 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium
9 | `const-candidates-state` | State variables that could be declared constant | Informational | High 9 | `assembly` | Assembly usage | Informational | High
10 | `low-level-calls` | Low level calls | Informational | High 10 | `const-candidates-state` | State variables that could be declared constant | Informational | High
11 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High 11 | `low-level-calls` | Low level calls | Informational | High
12 | `pragma` | If different pragma directives are used | Informational | High 12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High
13 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High 13 | `pragma` | If different pragma directives are used | Informational | High
14 | `unused-state` | Unused state variables | Informational | High 14 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High
15 | `unused-state` | Unused state variables | Informational | High
[Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors.

@ -2,35 +2,36 @@
### Test Detectors ### Test Detectors
# test_slither file.sol --detect-detectors number_results # test_slither file.sol detectors number_results
test_slither(){ test_slither(){
slither "$1" --disable-solc-warnings "$2" slither "$1" --disable-solc-warnings --detectors "$2"
if [ $? -ne "$3" ]; then if [ $? -ne "$3" ]; then
exit 1 exit 1
fi fi
slither "$1" --disable-solc-warnings "$2" --compact-ast slither "$1" --disable-solc-warnings --detectors "$2" --compact-ast
if [ $? -ne "$3" ]; then if [ $? -ne "$3" ]; then
exit 1 exit 1
fi fi
} }
test_slither tests/uninitialized.sol "--detect-uninitialized-state" 1 test_slither tests/uninitialized.sol "uninitialized-state" 1
test_slither tests/backdoor.sol "--detect-backdoor" 1 test_slither tests/backdoor.sol "backdoor" 1
test_slither tests/backdoor.sol "--detect-suicidal" 1 test_slither tests/backdoor.sol "suicidal" 1
test_slither tests/pragma.0.4.24.sol "--detect-pragma" 1 test_slither tests/pragma.0.4.24.sol "pragma" 1
test_slither tests/old_solc.sol.json "--detect-solc-version" 1 test_slither tests/old_solc.sol.json "solc-version" 1
test_slither tests/reentrancy.sol "--detect-reentrancy" 1 test_slither tests/reentrancy.sol "reentrancy" 1
test_slither tests/uninitialized_storage_pointer.sol "--detect-uninitialized-storage" 1 test_slither tests/uninitialized_storage_pointer.sol "uninitialized-storage" 1
test_slither tests/tx_origin.sol "--detect-tx-origin" 2 test_slither tests/tx_origin.sol "tx-origin" 2
test_slither tests/unused_state.sol "--detect-unused-state" 1 test_slither tests/unused_state.sol "unused-state" 1
test_slither tests/locked_ether.sol "--detect-locked-ether" 1 test_slither tests/locked_ether.sol "locked-ether" 1
test_slither tests/arbitrary_send.sol "--detect-arbitrary-send" 2 test_slither tests/arbitrary_send.sol "arbitrary-send" 2
test_slither tests/inline_assembly_contract.sol "--detect-assembly" 1 test_slither tests/inline_assembly_contract.sol "assembly" 1
test_slither tests/inline_assembly_library.sol "--detect-assembly" 2 test_slither tests/inline_assembly_library.sol "assembly" 2
test_slither tests/naming_convention.sol "--detect-naming-convention" 10 test_slither tests/naming_convention.sol "naming-convention" 10
test_slither tests/low_level_calls.sol "--detect-low-level-calls" 1 test_slither tests/low_level_calls.sol "low-level-calls" 1
test_slither tests/const_state_variables.sol "--detect-const-candidates-state" 2 test_slither tests/const_state_variables.sol "const-candidates-state" 2
### Test scripts ### Test scripts

@ -8,43 +8,18 @@ import os
import sys import sys
import traceback import traceback
from pkg_resources import iter_entry_points from pkg_resources import iter_entry_points, require
from slither.detectors.abstract_detector import (AbstractDetector, from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification, DetectorClassification)
classification_txt)
from slither.printers.abstract_printer import AbstractPrinter from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither from slither.slither import Slither
from slither.utils.colors import red from slither.utils.colors import red
from slither.utils.command_line import output_to_markdown, output_detectors, output_printers
logging.basicConfig() logging.basicConfig()
logger = logging.getLogger("Slither") logger = logging.getLogger("Slither")
def output_to_markdown(detector_classes):
"""
Pretty print of the detectors to README.md
"""
detectors_list = []
for detector in detector_classes:
argument = detector.ARGUMENT
# dont show the backdoor example
if argument == 'backdoor':
continue
help_info = detector.HELP
impact = detector.IMPACT
confidence = classification_txt[detector.CONFIDENCE]
detectors_list.append((argument, help_info, impact, confidence))
# Sort by impact, confidence, and name
detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[3], element[0]))
idx = 1
for (argument, help_info, impact, confidence) in detectors_list:
print('{} | `{}` | {} | {} | {}'.format(idx,
argument,
help_info,
classification_txt[impact],
confidence))
idx = idx +1
def process(filename, args, detector_classes, printer_classes): def process(filename, args, detector_classes, printer_classes):
""" """
@ -71,16 +46,14 @@ def _process(slither, detector_classes, printer_classes):
results = [] results = []
if printer_classes:
slither.run_printers() # Currently printers does not return results
elif detector_classes:
detector_results = slither.run_detectors() detector_results = slither.run_detectors()
detector_results = [x for x in detector_results if x] # remove empty results detector_results = [x for x in detector_results if x] # remove empty results
detector_results = [item for sublist in detector_results for item in sublist] # flatten detector_results = [item for sublist in detector_results for item in sublist] # flatten
results.extend(detector_results) results.extend(detector_results)
slither.run_printers() # Currently printers does not return results
return results, analyzed_contracts_count return results, analyzed_contracts_count
def process_truffle(dirname, args, detector_classes, printer_classes): def process_truffle(dirname, args, detector_classes, printer_classes):
@ -117,7 +90,7 @@ def exit(results):
sys.exit(len(results)) sys.exit(len(results))
def main(): def get_detectors_and_printers():
""" """
NOTE: This contains just a few detectors and printers that we made public. NOTE: This contains just a few detectors and printers that we made public.
""" """
@ -185,6 +158,11 @@ def main():
detectors += list(plugin_detectors) detectors += list(plugin_detectors)
printers += list(plugin_printers) printers += list(plugin_printers)
return detectors, printers
def main():
detectors, printers = get_detectors_and_printers()
main_impl(all_detector_classes=detectors, all_printer_classes=printers) main_impl(all_detector_classes=detectors, all_printer_classes=printers)
@ -195,12 +173,11 @@ def main_impl(all_detector_classes, all_printer_classes):
""" """
args = parse_args(all_detector_classes, all_printer_classes) args = parse_args(all_detector_classes, all_printer_classes)
if args.markdown:
output_to_markdown(all_detector_classes)
return
detector_classes = choose_detectors(args, all_detector_classes)
printer_classes = choose_printers(args, all_printer_classes) printer_classes = choose_printers(args, all_printer_classes)
if printer_classes:
detector_classes = []
else:
detector_classes = choose_detectors(args, all_detector_classes)
default_log = logging.INFO if not args.debug else logging.DEBUG default_log = logging.INFO if not args.debug else logging.DEBUG
@ -239,9 +216,6 @@ def main_impl(all_detector_classes, all_printer_classes):
(results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes) (results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes)
number_contracts += number_contracts_tmp number_contracts += number_contracts_tmp
results += results_tmp results += results_tmp
# if args.json:
# output_json(results, args.json)
# exit(results)
else: else:
@ -264,84 +238,103 @@ def main_impl(all_detector_classes, all_printer_classes):
def parse_args(detector_classes, printer_classes): def parse_args(detector_classes, printer_classes):
parser = argparse.ArgumentParser(description='Slither', parser = argparse.ArgumentParser(description='Slither',
usage="slither.py contract.sol [flag]", usage="slither.py contract.sol [flag]")
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=35))
parser.add_argument('filename', parser.add_argument('filename',
help='contract.sol file') help='contract.sol')
parser.add_argument('--solc', parser.add_argument('--version',
help='solc path', help='displays the current version',
version=require('slither-analyzer')[0].version,
action='version')
group_detector = parser.add_argument_group('Detectors')
group_printer = parser.add_argument_group('Printers')
group_solc = parser.add_argument_group('Solc options')
group_misc = parser.add_argument_group('Additional option')
group_detector.add_argument('--detectors',
help='Comma-separated list of detectors, defaults to all, '
'available detectors: {}'.format(
', '.join(d.ARGUMENT for d in detector_classes)),
action='store', action='store',
default='solc') dest='detectors_to_run',
default='all')
parser.add_argument('--solc-args', group_printer.add_argument('--printers',
help='Add custom solc arguments. Example: --solc-args "--allow-path /tmp --evm-version byzantium".', help='Comma-separated list fo contract information printers, '
'available printers: {}'.format(
', '.join(d.ARGUMENT for d in printer_classes)),
action='store', action='store',
default=None) dest='printers_to_run',
default='')
parser.add_argument('--disable-solc-warnings', group_detector.add_argument('--list-detectors',
help='Disable solc warnings', help='List available detectors',
action='store_true', action=ListDetectors,
nargs=0,
default=False) default=False)
parser.add_argument('--solc-ast', group_printer.add_argument('--list-printers',
help='Provide the ast solc file', help='List available printers',
action='store_true', action=ListPrinters,
nargs=0,
default=False) default=False)
parser.add_argument('--json',
help='Export results as JSON', group_detector.add_argument('--exclude-detectors',
help='Comma-separated list of detectors that should be excluded',
action='store', action='store',
default=None) dest='detectors_to_exclude',
default='')
parser.add_argument('--exclude-informational', group_detector.add_argument('--exclude-informational',
help='Exclude informational impact analyses', help='Exclude informational impact analyses',
action='store_true', action='store_true',
default=False) default=False)
parser.add_argument('--exclude-low', group_detector.add_argument('--exclude-low',
help='Exclude low impact analyses', help='Exclude low impact analyses',
action='store_true', action='store_true',
default=False) default=False)
parser.add_argument('--exclude-medium', group_detector.add_argument('--exclude-medium',
help='Exclude medium impact analyses', help='Exclude medium impact analyses',
action='store_true', action='store_true',
default=False) default=False)
parser.add_argument('--exclude-high', group_detector.add_argument('--exclude-high',
help='Exclude high impact analyses', help='Exclude high impact analyses',
action='store_true', action='store_true',
default=False) default=False)
for detector_cls in detector_classes:
detector_arg = '--detect-{}'.format(detector_cls.ARGUMENT)
detector_help = '{}'.format(detector_cls.HELP)
parser.add_argument(detector_arg,
help=detector_help,
action="append_const",
dest="detectors_to_run",
const=detector_cls.ARGUMENT)
# Second loop so that the --exclude are shown after all the detectors
for detector_cls in detector_classes:
exclude_detector_arg = '--exclude-{}'.format(detector_cls.ARGUMENT)
exclude_detector_help = 'Exclude {} detector'.format(detector_cls.ARGUMENT)
parser.add_argument(exclude_detector_arg,
help=exclude_detector_help,
action="append_const",
dest="detectors_to_exclude",
const=detector_cls.ARGUMENT)
for printer_cls in printer_classes: group_solc.add_argument('--solc',
printer_arg = '--printer-{}'.format(printer_cls.ARGUMENT) help='solc path',
printer_help = 'Print {}'.format(printer_cls.HELP) action='store',
parser.add_argument(printer_arg, default='solc')
help=printer_help,
action="append_const", group_solc.add_argument('--solc-args',
dest="printers_to_run", help='Add custom solc arguments. Example: --solc-args "--allow-path /tmp --evm-version byzantium".',
const=printer_cls.ARGUMENT) action='store',
default=None)
group_solc.add_argument('--disable-solc-warnings',
help='Disable solc warnings',
action='store_true',
default=False)
group_solc.add_argument('--solc-ast',
help='Provide the ast solc file',
action='store_true',
default=False)
group_misc.add_argument('--json',
help='Export results as JSON',
action='store',
default=None)
# debugger command # debugger command
parser.add_argument('--debug', parser.add_argument('--debug',
@ -351,7 +344,7 @@ def parse_args(detector_classes, printer_classes):
parser.add_argument('--markdown', parser.add_argument('--markdown',
help=argparse.SUPPRESS, help=argparse.SUPPRESS,
action="store_true", action=OutputMarkdown,
default=False) default=False)
parser.add_argument('--compact-ast', parser.add_argument('--compact-ast',
@ -359,15 +352,52 @@ def parse_args(detector_classes, printer_classes):
action='store_true', action='store_true',
default=False) default=False)
return parser.parse_args() if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)
args = parser.parse_args()
return args
class ListDetectors(argparse.Action):
def __call__(self, parser, *args, **kwargs):
detectors, _ = get_detectors_and_printers()
output_detectors(detectors)
parser.exit()
class ListPrinters(argparse.Action):
def __call__(self, parser, *args, **kwargs):
_, printers = get_detectors_and_printers()
output_printers(printers)
parser.exit()
class OutputMarkdown(argparse.Action):
def __call__(self, parser, *args, **kwargs):
detectors, _ = get_detectors_and_printers()
output_to_markdown(detectors)
parser.exit()
def choose_detectors(args, all_detector_classes): def choose_detectors(args, all_detector_classes):
# If detectors are specified, run only these ones # If detectors are specified, run only these ones
if args.detectors_to_run:
return [d for d in all_detector_classes if d.ARGUMENT in args.detectors_to_run]
detectors_to_run = []
detectors = {d.ARGUMENT: d for d in all_detector_classes}
if args.detectors_to_run == 'all':
detectors_to_run = all_detector_classes detectors_to_run = all_detector_classes
detectors_excluded = args.detectors_to_exclude.split(',')
for d in detectors:
if d in detectors_excluded:
detectors_to_run.remove(detectors[d])
else:
for d in args.detectors_to_run.split(','):
if d in detectors:
detectors_to_run.append(detectors[d])
else:
raise Exception('Error: {} is not a detector'.format(d))
return detectors_to_run
if args.exclude_informational: if args.exclude_informational:
detectors_to_run = [d for d in detectors_to_run if detectors_to_run = [d for d in detectors_to_run if
@ -388,11 +418,18 @@ def choose_detectors(args, all_detector_classes):
def choose_printers(args, all_printer_classes): def choose_printers(args, all_printer_classes):
# by default, dont run any printer
printers_to_run = [] printers_to_run = []
if args.printers_to_run:
printers_to_run = [p for p in all_printer_classes if # disable default printer
p.ARGUMENT in args.printers_to_run] if args.printers_to_run == '':
return []
printers = {p.ARGUMENT: p for p in all_printer_classes}
for p in args.printers_to_run.split(','):
if p in printers:
printers_to_run.append(printers[p])
else:
raise Exception('Error: {} is not a printer'.format(p))
return printers_to_run return printers_to_run

@ -0,0 +1,67 @@
from prettytable import PrettyTable
from slither.detectors.abstract_detector import classification_txt
def output_to_markdown(detector_classes):
detectors_list = []
for detector in detector_classes:
argument = detector.ARGUMENT
# dont show the backdoor example
if argument == 'backdoor':
continue
help_info = detector.HELP
impact = detector.IMPACT
confidence = classification_txt[detector.CONFIDENCE]
detectors_list.append((argument, help_info, impact, confidence))
# Sort by impact, confidence, and name
detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[3], element[0]))
idx = 1
for (argument, help_info, impact, confidence) in detectors_list:
print('{} | `{}` | {} | {} | {}'.format(idx,
argument,
help_info,
classification_txt[impact],
confidence))
idx = idx + 1
def output_detectors(detector_classes):
detectors_list = []
for detector in detector_classes:
argument = detector.ARGUMENT
help_info = detector.HELP
impact = detector.IMPACT
confidence = classification_txt[detector.CONFIDENCE]
detectors_list.append((argument, help_info, impact, confidence))
table = PrettyTable(["Num",
"Check",
"What it Detects",
"Impact",
"Confidence"])
# Sort by impact, confidence, and name
detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[3], element[0]))
idx = 1
for (argument, help_info, impact, confidence) in detectors_list:
table.add_row([idx, argument, help_info, classification_txt[impact], confidence])
idx = idx + 1
print(table)
def output_printers(printer_classes):
printers_list = []
for printer in printer_classes:
argument = printer.ARGUMENT
help_info = printer.HELP
printers_list.append((argument, help_info))
table = PrettyTable(["Num",
"Printer",
"What it Does"])
# Sort by impact, confidence, and name
printers_list = sorted(printers_list, key=lambda element: (element[0]))
idx = 1
for (argument, help_info) in printers_list:
table.add_row([idx, argument, help_info])
idx = idx + 1
print(table)
Loading…
Cancel
Save