Merge branch 'dev' into dev-echidna-usertype

pull/1690/head
Feist Josselin 2 years ago
commit 528e2d2629
  1. 2
      .github/workflows/ci.yml
  2. 46
      .github/workflows/docs.yml
  3. 2
      .github/workflows/linter.yml
  4. 3
      .gitignore
  5. 93
      README.md
  6. 7
      scripts/ci_test_etherscan.sh
  7. 5
      setup.py
  8. 2
      slither/__main__.py
  9. 4
      slither/core/declarations/function_contract.py
  10. 2
      slither/detectors/functions/cyclomatic_complexity.py
  11. 4
      slither/detectors/variables/uninitialized_storage_variables.py
  12. 17
      slither/printers/summary/function.py
  13. 2
      slither/slithir/convert.py
  14. 8
      slither/utils/type_helpers.py
  15. 17
      tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol
  16. 90
      tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol.0.8.19.UninitializedStorageVars.json
  17. 5
      tests/test_detectors.py

@ -67,7 +67,7 @@ jobs:
- name: Set up nix - name: Set up nix
if: matrix.type == 'dapp' if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v16 uses: cachix/install-nix-action@v20
- name: Set up cachix - name: Set up cachix
if: matrix.type == 'dapp' if: matrix.type == 'dapp'

@ -0,0 +1,46 @@
name: docs
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
build:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v3
- uses: actions/setup-python@v4
with:
python-version: '3.8'
- run: pip install -e ".[dev]"
- run: pdoc -o docs/ slither '!slither.tools' #TODO fix import errors on pdoc run
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
# Upload the doc
path: 'docs/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

@ -64,5 +64,7 @@ jobs:
VALIDATE_EDITORCONFIG: false VALIDATE_EDITORCONFIG: false
VALIDATE_JSCPD: false VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false VALIDATE_PYTHON_MYPY: false
# Until we upgrade the super linter for actionlintÒ
VALIDATE_GITHUB_ACTIONS: false
SHELLCHECK_OPTS: "-e SC1090" SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol) FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

3
.gitignore vendored

@ -111,3 +111,6 @@ test_artifacts/
# crytic export # crytic export
crytic-export/ crytic-export/
# Auto-generated Github pages docs
docs/

@ -5,13 +5,15 @@
[![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com) [![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com)
[![PyPI version](https://badge.fury.io/py/slither-analyzer.svg)](https://badge.fury.io/py/slither-analyzer) [![PyPI version](https://badge.fury.io/py/slither-analyzer.svg)](https://badge.fury.io/py/slither-analyzer)
Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses. Slither is a Solidity static analysis framework written in Python3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.
- [Features](#features) - [Features](#features)
- [Bugs and Optimizations Detection](#bugs-and-optimizations-detection) - [Usage](#usage)
- [How to Install](#how-to-install)
- [Detectors](#detectors)
- [Printers](#printers) - [Printers](#printers)
- [Tools](#tools) - [Tools](#tools)
- [How to Install](#how-to-install) - [API Documentation](#api-documentation)
- [Getting Help](#getting-help) - [Getting Help](#getting-help)
- [FAQ](#faq) - [FAQ](#faq)
- [Publications](#publications) - [Publications](#publications)
@ -20,35 +22,68 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s
* Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md)) * Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md))
* Identifies where the error condition occurs in the source code * Identifies where the error condition occurs in the source code
* Easily integrates into continuous integration and Truffle builds * Easily integrates into continuous integration and Hardhat/Foundry builds
* Built-in 'printers' quickly report crucial contract information * Built-in 'printers' quickly report crucial contract information
* Detector API to write custom analyses in Python * Detector API to write custom analyses in Python
* Ability to analyze contracts written with Solidity >= 0.4 * Ability to analyze contracts written with Solidity >= 0.4
* Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses * Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses
* Correctly parses 99.9% of all public Solidity code * Correctly parses 99.9% of all public Solidity code
* Average execution time of less than 1 second per contract * Average execution time of less than 1 second per contract
* Integrates with Github's code scanning in [CI](https://github.com/marketplace/actions/slither-action)
## Usage
## Bugs and Optimizations Detection Run Slither on a Hardhat/Foundry/Dapp/Brownie application:
Run Slither on a Truffle/Embark/Dapp/Etherlime/Hardhat application:
```bash ```bash
slither . slither .
``` ```
This is the preferred option if your project has dependencies as Slither relies on the underlying compilation framework to compile source code.
Run Slither on a single file: However, you can run Slither on a single file that does not import dependencies:
```bash ```bash
slither tests/uninitialized.sol slither tests/uninitialized.sol
``` ```
## How to install
Slither requires Python 3.8+.
If you're **not** going to use one of the [supported compilation frameworks](https://github.com/crytic/crytic-compile), you need [solc](https://github.com/ethereum/solidity/), the Solidity compiler; we recommend using [solc-select](https://github.com/crytic/solc-select) to conveniently switch between solc versions.
### Using Pip
```bash
pip3 install slither-analyzer
```
### Using Git
```bash
git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
```
We recommend using a Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
```
To share a directory in the container:
```bash
docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
```
### Integration ### Integration
- For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action). - For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action).
- To generate a Markdown report, use `slither [target] --checklist`. - To generate a Markdown report, use `slither [target] --checklist`.
- To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`) - To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`)
Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc. For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation. ## Detectors
### Detectors
Num | Detector | What it Detects | Impact | Confidence Num | Detector | What it Detects | Impact | Confidence
@ -173,40 +208,8 @@ See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documen
[Contact us](https://www.trailofbits.com/contact/) to get help on building custom tools. [Contact us](https://www.trailofbits.com/contact/) to get help on building custom tools.
## How to install ## API Documentation
Documentation on Slither's internals is available [here](https://crytic.github.io/slither/slither.html).
Slither requires Python 3.8+.
If you're **not** going to use one of the [supported compilation frameworks](https://github.com/crytic/crytic-compile),
you need to have on your machine the right version of [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
### Using Pip
```bash
pip3 install slither-analyzer
```
### Using Git
```bash
git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
```
We recommend using a Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
```
To share a directory in the container:
```bash
docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
```
## Getting Help ## Getting Help

@ -17,12 +17,5 @@ if [ "$GITHUB_ETHERSCAN" = "" ]; then
sleep $(( ( RANDOM % 5 ) + 1 ))s sleep $(( ( RANDOM % 5 ) + 1 ))s
fi fi
echo "::group::Etherscan rinkeby"
if ! slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN" --no-fail-pedantic; then
echo "Etherscan rinkeby test failed"
exit 1
fi
echo "::endgroup::"
exit 0 exit 0

@ -15,8 +15,8 @@ setup(
"packaging", "packaging",
"prettytable>=0.7.2", "prettytable>=0.7.2",
"pycryptodome>=3.4.6", "pycryptodome>=3.4.6",
"crytic-compile>=0.3.0", # "crytic-compile>=0.3.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile", "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
], ],
extras_require={ extras_require={
"dev": [ "dev": [
@ -29,6 +29,7 @@ setup(
"numpy", "numpy",
"solc-select>=v1.0.0b1", "solc-select>=v1.0.0b1",
"openai", "openai",
"pdoc",
] ]
}, },
license="AGPL-3.0", license="AGPL-3.0",

@ -760,7 +760,7 @@ def main_impl(
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout # If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output. # output.
if outputting_json or output_to_sarif: if outputting_json or outputting_sarif:
StandardOutputCapture.enable(outputting_json_stdout or outputting_sarif_stdout) StandardOutputCapture.enable(outputting_json_stdout or outputting_sarif_stdout)
printer_classes = choose_printers(args, all_printer_classes) printer_classes = choose_printers(args, all_printer_classes)

@ -6,6 +6,7 @@ from typing import Dict, TYPE_CHECKING, List, Tuple
from slither.core.children.child_contract import ChildContract from slither.core.children.child_contract import ChildContract
from slither.core.children.child_inheritance import ChildInheritance from slither.core.children.child_inheritance import ChildInheritance
from slither.core.declarations import Function from slither.core.declarations import Function
from slither.utils.code_complexity import compute_cyclomatic_complexity
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines # pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
@ -73,7 +74,7 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
def get_summary( def get_summary(
self, self,
) -> Tuple[str, str, str, List[str], List[str], List[str], List[str], List[str]]: ) -> Tuple[str, str, str, List[str], List[str], List[str], List[str], List[str], int]:
""" """
Return the function summary Return the function summary
Returns: Returns:
@ -89,6 +90,7 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
[str(x) for x in self.state_variables_written], [str(x) for x in self.state_variables_written],
[str(x) for x in self.internal_calls], [str(x) for x in self.internal_calls],
[str(x) for x in self.external_calls_as_expressions], [str(x) for x in self.external_calls_as_expressions],
compute_cyclomatic_complexity(self),
) )
# endregion # endregion

@ -22,7 +22,7 @@ class CyclomaticComplexity(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity"' WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity"
WIKI_TITLE = "Cyclomatic complexity" WIKI_TITLE = "Cyclomatic complexity"
WIKI_DESCRIPTION = "Detects functions with high (> 11) cyclomatic complexity." WIKI_DESCRIPTION = "Detects functions with high (> 11) cyclomatic complexity."

@ -103,9 +103,11 @@ Bob calls `func`. As a result, `owner` is overridden to `0`.
for contract in self.compilation_unit.contracts: for contract in self.compilation_unit.contracts:
for function in contract.functions: for function in contract.functions:
if function.is_implemented and function.entry_point: if function.is_implemented and function.entry_point:
locals_except_params = set(function.variables) - set(function.parameters)
uninitialized_storage_variables = [ uninitialized_storage_variables = [
v for v in function.local_variables if v.is_storage and v.uninitialized v for v in locals_except_params if v.is_storage and v.uninitialized
] ]
function.entry_point.context[self.key] = uninitialized_storage_variables function.entry_point.context[self.key] = uninitialized_storage_variables
self._detect_uninitialized(function, function.entry_point, []) self._detect_uninitialized(function, function.entry_point, [])

@ -48,6 +48,7 @@ class FunctionSummary(AbstractPrinter):
"Write", "Write",
"Internal Calls", "Internal Calls",
"External Calls", "External Calls",
"Cyclomatic Complexity",
] ]
) )
for ( for (
@ -59,6 +60,7 @@ class FunctionSummary(AbstractPrinter):
write, write,
internal_calls, internal_calls,
external_calls, external_calls,
cyclomatic_complexity,
) in func_summaries: ) in func_summaries:
read = self._convert(sorted(read)) read = self._convert(sorted(read))
write = self._convert(sorted(write)) write = self._convert(sorted(write))
@ -73,6 +75,7 @@ class FunctionSummary(AbstractPrinter):
write, write,
internal_calls, internal_calls,
external_calls, external_calls,
cyclomatic_complexity,
] ]
) )
txt += "\n \n" + str(table) txt += "\n \n" + str(table)
@ -84,6 +87,7 @@ class FunctionSummary(AbstractPrinter):
"Write", "Write",
"Internal Calls", "Internal Calls",
"External Calls", "External Calls",
"Cyclomatic Complexity",
] ]
) )
for ( for (
@ -95,12 +99,23 @@ class FunctionSummary(AbstractPrinter):
write, write,
internal_calls, internal_calls,
external_calls, external_calls,
cyclomatic_complexity,
) in modif_summaries: ) in modif_summaries:
read = self._convert(sorted(read)) read = self._convert(sorted(read))
write = self._convert(sorted(write)) write = self._convert(sorted(write))
internal_calls = self._convert(sorted(internal_calls)) internal_calls = self._convert(sorted(internal_calls))
external_calls = self._convert(sorted(external_calls)) external_calls = self._convert(sorted(external_calls))
table.add_row([f_name, visi, read, write, internal_calls, external_calls]) table.add_row(
[
f_name,
visi,
read,
write,
internal_calls,
external_calls,
cyclomatic_complexity,
]
)
txt += "\n\n" + str(table) txt += "\n\n" + str(table)
txt += "\n" txt += "\n"
self.info(txt) self.info(txt)

@ -577,9 +577,9 @@ def propagate_types(
if (isinstance(t, ElementaryType) and t.name == "address") or ( if (isinstance(t, ElementaryType) and t.name == "address") or (
isinstance(t, TypeAlias) and t.underlying_type.name == "address" isinstance(t, TypeAlias) and t.underlying_type.name == "address"
): ):
if ir.destination.name == "this":
# Cannot be a top level function with this. # Cannot be a top level function with this.
assert isinstance(node_function, FunctionContract) assert isinstance(node_function, FunctionContract)
if ir.destination.name == "this":
# the target contract is the contract itself # the target contract is the contract itself
return convert_type_of_high_and_internal_level_call( return convert_type_of_high_and_internal_level_call(
ir, node_function.contract ir, node_function.contract

@ -11,7 +11,7 @@ if TYPE_CHECKING:
### core.declaration ### core.declaration
# pylint: disable=used-before-assignment # pylint: disable=used-before-assignment
InternalCallType = Union[Function, SolidityFunction] InternalCallType = Union["Function", "SolidityFunction"]
HighLevelCallType = Tuple[Contract, Union[Function, Variable]] HighLevelCallType = Tuple["Contract", Union["Function", "Variable"]]
LibraryCallType = Tuple[Contract, Function] LibraryCallType = Tuple["Contract", "Function"]
LowLevelCallType = Tuple[Union[Variable, SolidityVariable], str] LowLevelCallType = Tuple[Union["Variable", "SolidityVariable"], str]

@ -0,0 +1,17 @@
contract Uninitialized{
struct St{
uint a;
}
function bad() internal returns (St storage ret){
ret = ret;
ret.a += 1;
}
function ok(St storage ret) internal {
ret = ret;
ret.a += 1;
}
}

@ -0,0 +1,90 @@
[
[
{
"elements": [
{
"type": "variable",
"name": "ret",
"source_mapping": {
"start": 100,
"length": 14,
"filename_relative": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol",
"filename_absolute": "/GENERIC_PATH",
"filename_short": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol",
"is_dependency": false,
"lines": [
7
],
"starting_column": 38,
"ending_column": 52
},
"type_specific_fields": {
"parent": {
"type": "function",
"name": "bad",
"source_mapping": {
"start": 67,
"length": 95,
"filename_relative": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol",
"filename_absolute": "/GENERIC_PATH",
"filename_short": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol",
"is_dependency": false,
"lines": [
7,
8,
9,
10
],
"starting_column": 5,
"ending_column": 6
},
"type_specific_fields": {
"parent": {
"type": "contract",
"name": "Uninitialized",
"source_mapping": {
"start": 0,
"length": 262,
"filename_relative": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol",
"filename_absolute": "/GENERIC_PATH",
"filename_short": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol",
"is_dependency": false,
"lines": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17
],
"starting_column": 1,
"ending_column": 2
}
},
"signature": "bad()"
}
}
}
}
],
"description": "Uninitialized.bad().ret (tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol#7) is a storage variable never initialized\n",
"markdown": "[Uninitialized.bad().ret](tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol#L7) is a storage variable never initialized\n",
"first_markdown_element": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol#L7",
"id": "979d28e501693ed7ece0d429e7c30266f8e9d6a2e2eedc87006c4bad63e78706",
"check": "uninitialized-storage",
"impact": "High",
"confidence": "High"
}
]
]

@ -371,6 +371,11 @@ ALL_TEST_OBJECTS = [
"uninitialized_storage_pointer.sol", "uninitialized_storage_pointer.sol",
"0.4.25", "0.4.25",
), ),
Test(
all_detectors.UninitializedStorageVars,
"uninitialized_storage_pointer.sol",
"0.8.19",
),
Test(all_detectors.TxOrigin, "tx_origin.sol", "0.4.25"), Test(all_detectors.TxOrigin, "tx_origin.sol", "0.4.25"),
Test(all_detectors.TxOrigin, "tx_origin.sol", "0.5.16"), Test(all_detectors.TxOrigin, "tx_origin.sol", "0.5.16"),
Test(all_detectors.TxOrigin, "tx_origin.sol", "0.6.11"), Test(all_detectors.TxOrigin, "tx_origin.sol", "0.6.11"),

Loading…
Cancel
Save