mirror of https://github.com/crytic/slither
commit
1048c61185
@ -0,0 +1,21 @@ |
|||||||
|
FROM alpine:3.6 |
||||||
|
|
||||||
|
LABEL name slither |
||||||
|
LABEL src "https://github.com/trailofbits/slither" |
||||||
|
LABEL creator trailofbits |
||||||
|
LABEL dockerfile_maintenance trailofbits |
||||||
|
LABEL desc "Static Analyzer for Solidity" |
||||||
|
|
||||||
|
# Mostly stolen from ethereum/solc. |
||||||
|
RUN apk add --no-cache git python3 build-base cmake boost-dev \ |
||||||
|
&& sed -i -E -e 's/include <sys\/poll.h>/include <poll.h>/' /usr/include/boost/asio/detail/socket_types.hpp \ |
||||||
|
&& git clone --depth 1 --recursive -b release https://github.com/ethereum/solidity \ |
||||||
|
&& cd /solidity && cmake -DCMAKE_BUILD_TYPE=Release -DTESTS=0 -DSTATIC_LINKING=1 \ |
||||||
|
&& cd /solidity && make solc && install -s solc/solc /usr/bin \ |
||||||
|
&& cd / && rm -rf solidity \ |
||||||
|
&& rm -rf /var/cache/apk/* \ |
||||||
|
&& git clone https://github.com/trailofbits/slither.git |
||||||
|
WORKDIR slither |
||||||
|
RUN python3 setup.py install |
||||||
|
ENTRYPOINT ["slither"] |
||||||
|
CMD ["tests/uninitialized.sol"] |
@ -1,84 +1,115 @@ |
|||||||
# Slither, the Solidity source analyzer |
# Slither, the Solidity source analyzer |
||||||
[![Build Status](https://travis-ci.com/trailofbits/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/trailofbits/slither) |
[![Build Status](https://travis-ci.com/trailofbits/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/trailofbits/slither) |
||||||
|
[![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) |
||||||
|
|
||||||
Slither is a Solidity static analysis framework written in Python 3. It provides an API to easily manipulate Solidity code. In addition to exposing a Solidity contracts AST, Slither provides many APIs to quickly check local and state variable usage. |
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 comphrehension, and quickly prototype custom analyses. |
||||||
|
|
||||||
With Slither you can: |
## Features |
||||||
- Detect vulnerabilities |
|
||||||
- Speed up your understanding of code |
|
||||||
- Build custom analyses to answer specific questions |
|
||||||
- Quickly prototype a new static analysis techniques |
|
||||||
|
|
||||||
## How to install |
* Detects vulnerable Solidity code with low false positives |
||||||
|
* Identifies where the error condition occurs in the source code |
||||||
Slither uses Python 3.6. |
* Easy integration into continuous integration and Truffle builds |
||||||
|
* Built-in 'printers' quickly report crucial contract information |
||||||
|
* Detector API to write custom analyses in Python |
||||||
|
* Ability to analyze contracts written with Solidity >= 0.4 |
||||||
|
* Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
```bash |
Run Slither on a Truffle application: |
||||||
$ python setup.py install |
|
||||||
``` |
``` |
||||||
|
truffle compile |
||||||
You may also want solc, the Solidity compiler, which can be installed using homebrew: |
slither . |
||||||
|
|
||||||
```bash |
|
||||||
$ brew update |
|
||||||
$ brew upgrade |
|
||||||
$ brew tap ethereum/ethereum |
|
||||||
$ brew install solidity |
|
||||||
$ brew linkapps solidity |
|
||||||
``` |
``` |
||||||
|
|
||||||
or with aptitude: |
Run Slither on a single file: |
||||||
|
|
||||||
```bash |
|
||||||
$ sudo add-apt-repository ppa:ethereum/ethereum |
|
||||||
$ sudo apt-get update |
|
||||||
$ sudo apt-get install solc |
|
||||||
``` |
|
||||||
|
|
||||||
## How to use |
|
||||||
|
|
||||||
``` |
|
||||||
$ slither file.sol |
|
||||||
``` |
``` |
||||||
|
$ slither tests/uninitialized.sol # argument can be file, folder or glob, be sure to quote the argument when using a glob |
||||||
``` |
|
||||||
$ slither examples/uninitialized.sol |
|
||||||
[..] |
[..] |
||||||
INFO:Detectors:Uninitialized state variables in examples/uninitialized.sol, Contract: Uninitialized, Vars: destination, Used in ['transfer'] |
INFO:Detectors:Uninitialized state variables in tests/uninitialized.sol, Contract: Uninitialized, Vars: destination, Used in ['transfer'] |
||||||
[..] |
[..] |
||||||
``` |
``` |
||||||
|
|
||||||
If Slither is applied on a directory, it will run on every `.sol` file of the directory. |
If Slither is run on a directory, it will run on every `.sol` file in the directory. |
||||||
|
|
||||||
## Options |
### Configuration |
||||||
|
|
||||||
### Configuration |
|
||||||
* `--solc SOLC`: Path to `solc` (default 'solc') |
* `--solc SOLC`: Path to `solc` (default 'solc') |
||||||
|
* `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments |
||||||
* `--disable-solc-warnings`: Do not print solc warnings |
* `--disable-solc-warnings`: Do not print solc warnings |
||||||
* `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`) |
* `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`) |
||||||
* `--json FILE`: Export results as JSON |
* `--json FILE`: Export results as JSON |
||||||
* `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments. |
|
||||||
|
|
||||||
### Analyses |
## Detectors |
||||||
* `--high`: Run only medium/high severity checks with high confidence |
|
||||||
* `--medium`: Run only medium/high severity checks with medium confidence |
By default, all the detectors are run. |
||||||
* `--low`: Run only low severity checks |
|
||||||
|
Num | Detector | What it Detects | Impact | Confidence |
||||||
|
--- | --- | --- | --- | --- |
||||||
|
1 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#suicidal) | High | High |
||||||
|
2 | `uninitialized-local` | [Uninitialized local variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-local-variables) | High | High |
||||||
|
3 | `uninitialized-state` | [Uninitialized state variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-state-variables) | High | High |
||||||
|
4 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-storage-variables) | High | High |
||||||
|
5 | `arbitrary-send` | [Functions that send ether to arbitrary destinations](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations) | High | Medium |
||||||
|
6 | `reentrancy` | [Reentrancy vulnerabilities](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#reentrancy-vulnerabilities) | High | Medium |
||||||
|
7 | `locked-ether` | [Contracts that lock ether](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#contracts-that-lock-ether) | Medium | High |
||||||
|
8 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-usage-of-txorigin) | Medium | Medium |
||||||
|
9 | `assembly` | [Assembly usage](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#assembly-usage) | Informational | High |
||||||
|
10 | `constable-states` | [State variables that could be declared constant](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant) | Informational | High |
||||||
|
11 | `external-function` | [Public function that could be declared as external](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#public-function-that-could-be-declared-as-external) | Informational | High |
||||||
|
12 | `low-level-calls` | [Low level calls](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#low-level-calls) | Informational | High |
||||||
|
13 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#conformance-to-solidity-naming-conventions) | Informational | High |
||||||
|
14 | `pragma` | [If different pragma directives are used](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant) | Informational | High |
||||||
|
15 | `solc-version` | [Old versions of Solidity (< 0.4.23)](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#old-versions-of-solidity) | Informational | High |
||||||
|
16 | `unused-state` | [Unused state variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#unused-state-variables) | Informational | High |
||||||
|
|
||||||
|
[Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. |
||||||
|
|
||||||
### Printers |
### Printers |
||||||
* `--print-summary`: Print a summary of the contracts |
|
||||||
* `--print-quick-summary`: Print a quick summary of the contracts |
|
||||||
* `--print-inheritance`: Print the inheritance graph |
|
||||||
|
|
||||||
For more information about printers, see the [Printers documentation](docs/PRINTERS.md) |
To run a printer, use `--print` and a comma-separated list of printers. |
||||||
|
|
||||||
|
Num | Printer | Description |
||||||
|
--- | --- | --- |
||||||
|
1 | `call-graph` | Export the call-graph of the contracts to a dot file |
||||||
|
2 | `contract-summary` | Print a summary of the contracts |
||||||
|
3 | `function-summary` | Print a summary of the functions |
||||||
|
4 | `inheritance` | Print the inheritance relations between contracts |
||||||
|
5 | `inheritance-graph` | Export the inheritance graph of each contract to a dot file |
||||||
|
6 | `slithir` | Print the slithIR representation of the functions |
||||||
|
7 | `vars-and-auth` | Print the state variables written and the authorization of the functions |
||||||
|
|
||||||
|
|
||||||
|
## How to install |
||||||
|
|
||||||
|
Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. |
||||||
|
|
||||||
|
### Using Pip |
||||||
|
|
||||||
|
``` |
||||||
|
$ pip install slither-analyzer |
||||||
|
``` |
||||||
|
|
||||||
|
### Using Git |
||||||
|
|
||||||
|
```bash |
||||||
|
$ git clone https://github.com/trailofbits/slither.git && cd slither |
||||||
|
$ python setup.py install |
||||||
|
``` |
||||||
|
|
||||||
|
## Getting Help |
||||||
|
|
||||||
|
Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (#ethereum) for help using or extending Slither. |
||||||
|
|
||||||
|
* The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract. |
||||||
|
|
||||||
## Checks available |
* The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses. |
||||||
|
|
||||||
Check | Purpose | Severity | Confidence |
* The [API documentation](https://github.com/trailofbits/slither/wiki/API-examples) describes the methods and objects available for custom analyses. |
||||||
--- | --- | --- | --- |
|
||||||
`--uninitialized`| Detect uninitialized variables | High | High |
|
||||||
|
|
||||||
|
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation. |
||||||
|
|
||||||
## License |
## License |
||||||
|
|
||||||
Slither is licensed and distributed under AGPLv3. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms. |
Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms. |
||||||
|
@ -1,96 +0,0 @@ |
|||||||
# Slither Printers |
|
||||||
|
|
||||||
Slither allows printing contracts information through its printers. |
|
||||||
|
|
||||||
## Quick Summary |
|
||||||
`slither.py file.sol --print-quick-summary` |
|
||||||
|
|
||||||
Output a quick summary of the contract. |
|
||||||
Example: |
|
||||||
``` |
|
||||||
$ slither.py vulns/0x01293cd77f68341635814c35299ed30ae212789e.sol --print-quick-summary |
|
||||||
``` |
|
||||||
<img src="imgs/quick-summary.png" width="300"> |
|
||||||
|
|
||||||
## Summary |
|
||||||
`slither.py file.sol --print-summary` |
|
||||||
|
|
||||||
Output a summary of the contract showing for each function: |
|
||||||
- What are the visibility and the modifiers |
|
||||||
- What are the state variables read or written |
|
||||||
- What are the calls |
|
||||||
|
|
||||||
Example: |
|
||||||
``` |
|
||||||
$ slither.py vulns/0x01293cd77f68341635814c35299ed30ae212789e.sol --print-summary |
|
||||||
``` |
|
||||||
``` |
|
||||||
[...] |
|
||||||
|
|
||||||
INFO:Slither:Contract NBACrypto |
|
||||||
Contract vars: [u'ceoAddress', u'cfoAddress', u'teams', u'players', u'teamsAreInitiated', u'playersAreInitiated', u'isPaused'] |
|
||||||
+--------------------+------------+--------------+------------------------+---------------+----------------------------------------------+ |
|
||||||
| Function | Visibility | Modifiers | Read | Write | Calls | |
|
||||||
+--------------------+------------+--------------+------------------------+---------------+----------------------------------------------+ |
|
||||||
| pauseGame | public | [u'onlyCeo'] | [] | [u'isPaused'] | [] | |
|
||||||
| unPauseGame | public | [u'onlyCeo'] | [] | [u'isPaused'] | [] | |
|
||||||
| GetIsPauded | public | [] | [u'isPaused'] | [] | [] | |
|
||||||
| purchaseCountry | public | [] | [u'isPaused'] | [u'teams'] | [u'cfoAddress.transfer', u'mul'] | |
|
||||||
| | | | | | [u'require', u'teams.ownerAddress.transfer'] | |
|
||||||
| purchasePlayer | public | [] | [u'isPaused'] | [u'players'] | [u'cfoAddress.transfer', u'mul'] | |
|
||||||
| | | | | | [u'require', u'teams.ownerAddress.transfer'] | |
|
||||||
| | | | | | [u'players.ownerAddress.transfer'] | |
|
||||||
| modifyPriceCountry | public | [] | [] | [u'teams'] | [u'require'] | |
|
||||||
| getTeam | public | [] | [u'teams'] | [] | [] | |
|
||||||
| getPlayer | public | [] | [u'players'] | [] | [] | |
|
||||||
| getTeamPrice | public | [] | [] | [] | [] | |
|
||||||
| getPlayerPrice | public | [] | [] | [] | [] | |
|
||||||
| getTeamOwner | public | [] | [] | [] | [] | |
|
||||||
| getPlayerOwner | public | [] | [] | [] | [] | |
|
||||||
| mul | internal | [] | [] | [] | [u'assert'] | |
|
||||||
| div | internal | [] | [] | [] | [] | |
|
||||||
| InitiateTeams | public | [u'onlyCeo'] | [u'teamsAreInitiated'] | [] | [u'require', u'teams.push'] | |
|
||||||
| addPlayer | public | [u'onlyCeo'] | [] | [] | [u'players.push'] | |
|
||||||
+--------------------+------------+--------------+------------------------+---------------+----------------------------------------------+ |
|
||||||
``` |
|
||||||
|
|
||||||
## Inheritance Graph |
|
||||||
`slither.py file.sol --print-inheritance` |
|
||||||
|
|
||||||
Output a graph showing the inheritance interaction between the contracts. |
|
||||||
Example: |
|
||||||
``` |
|
||||||
$ slither examples/DAO.sol --print-inheritance |
|
||||||
[...] |
|
||||||
INFO:PrinterInheritance:Inheritance Graph: examples/DAO.sol.dot |
|
||||||
``` |
|
||||||
|
|
||||||
The output format is [dot](https://www.graphviz.org/) and can be converted to svg using: |
|
||||||
``` |
|
||||||
dot examples/DAO.sol.dot -Tsvg -o examples/DAO.svg |
|
||||||
``` |
|
||||||
|
|
||||||
Functions in orange override a parent's functions. If a variable points to another contract, the contract type is written in blue. |
|
||||||
|
|
||||||
<img src="imgs/DAO.svg" width="700"> |
|
||||||
|
|
||||||
|
|
||||||
## Variables written and authorization |
|
||||||
`slither.py file.sol --print-variables-written-and-authorization` |
|
||||||
|
|
||||||
Print the variables written and the check on `msg.sender` of each function. |
|
||||||
``` |
|
||||||
... |
|
||||||
INFO:Printers: |
|
||||||
Contract MyNewBank |
|
||||||
+----------+------------------------+-------------------------+ |
|
||||||
| Function | State variable written | Condition on msg.sender | |
|
||||||
+----------+------------------------+-------------------------+ |
|
||||||
| kill | [] | ['msg.sender != owner'] | |
|
||||||
| withdraw | [] | ['msg.sender != owner'] | |
|
||||||
| init | [u'owner'] | [] | |
|
||||||
| owned | [u'owner'] | [] | |
|
||||||
| fallback | [u'deposits'] | [] | |
|
||||||
+----------+------------------------+-------------------------+ |
|
||||||
``` |
|
||||||
|
|
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 81 KiB |
@ -1,11 +0,0 @@ |
|||||||
contract Uninitialized{ |
|
||||||
|
|
||||||
|
|
||||||
address destination; |
|
||||||
|
|
||||||
function transfer() payable{ |
|
||||||
|
|
||||||
destination.transfer(msg.value); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,25 @@ |
|||||||
|
pragma solidity ^0.4.24; |
||||||
|
contract Owner{ |
||||||
|
|
||||||
|
address owner; |
||||||
|
|
||||||
|
modifier onlyOwner(){ |
||||||
|
require(msg.sender == owner); |
||||||
|
_; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
contract MyContract is Owner{ |
||||||
|
|
||||||
|
mapping(address => uint) balances; |
||||||
|
|
||||||
|
constructor() public{ |
||||||
|
owner = msg.sender; |
||||||
|
} |
||||||
|
|
||||||
|
function mint(uint value) onlyOwner public{ |
||||||
|
balances[msg.sender] += value; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
library Library { |
||||||
|
function library_func() { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract ContractA { |
||||||
|
uint256 public val = 0; |
||||||
|
|
||||||
|
function my_func_a() { |
||||||
|
keccak256(0); |
||||||
|
Library.library_func(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract ContractB { |
||||||
|
ContractA a; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
a = new ContractA(); |
||||||
|
} |
||||||
|
|
||||||
|
function my_func_b() { |
||||||
|
a.my_func_a(); |
||||||
|
my_second_func_b(); |
||||||
|
} |
||||||
|
|
||||||
|
function my_func_a() { |
||||||
|
my_second_func_b(); |
||||||
|
} |
||||||
|
|
||||||
|
function my_second_func_b(){ |
||||||
|
a.val(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
strict digraph { |
||||||
|
subgraph cluster_5_Library { |
||||||
|
label = "Library" |
||||||
|
"5_library_func" [label="library_func"] |
||||||
|
} |
||||||
|
subgraph cluster_22_ContractA { |
||||||
|
label = "ContractA" |
||||||
|
"22_my_func_a" [label="my_func_a"] |
||||||
|
"22_val" [label="val"] |
||||||
|
} |
||||||
|
subgraph cluster_63_ContractB { |
||||||
|
label = "ContractB" |
||||||
|
"63_my_second_func_b" [label="my_second_func_b"] |
||||||
|
"63_my_func_a" [label="my_func_a"] |
||||||
|
"63_constructor" [label="constructor"] |
||||||
|
"63_my_func_b" [label="my_func_b"] |
||||||
|
"63_my_func_b" -> "63_my_second_func_b" |
||||||
|
"63_my_func_a" -> "63_my_second_func_b" |
||||||
|
} |
||||||
|
subgraph cluster_solidity { |
||||||
|
label = "[Solidity]" |
||||||
|
"keccak256()" |
||||||
|
"22_my_func_a" -> "keccak256()" |
||||||
|
} |
||||||
|
"22_my_func_a" -> "5_library_func" |
||||||
|
"63_my_func_b" -> "22_my_func_a" |
||||||
|
"63_my_second_func_b" -> "22_val" |
||||||
|
} |
After Width: | Height: | Size: 35 KiB |
@ -1,21 +1,16 @@ |
|||||||
contract Contract1{ |
pragma solidity ^0.4.24; |
||||||
|
|
||||||
uint myvar; |
contract BaseContract1{ |
||||||
|
|
||||||
function myfunc() public{} |
|
||||||
} |
} |
||||||
|
|
||||||
contract Contract2{ |
contract BaseContract2{ |
||||||
|
|
||||||
uint public myvar2; |
|
||||||
|
|
||||||
function myfunc2() public{} |
|
||||||
|
|
||||||
function privatefunc() private{} |
|
||||||
} |
} |
||||||
|
|
||||||
contract Contract3 is Contract1, Contract2{ |
contract ChildContract1 is BaseContract1{ |
||||||
|
} |
||||||
function myfunc() public{} // override myfunc |
|
||||||
|
|
||||||
|
contract ChildContract2 is BaseContract1, BaseContract2{ |
||||||
} |
} |
||||||
|
|
||||||
|
contract GrandchildContract1 is ChildContract1{ |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
contract Contract1{ |
||||||
|
|
||||||
|
uint myvar; |
||||||
|
|
||||||
|
function myfunc() public{} |
||||||
|
} |
||||||
|
|
||||||
|
contract Contract2{ |
||||||
|
|
||||||
|
uint public myvar2; |
||||||
|
|
||||||
|
function myfunc2() public{} |
||||||
|
|
||||||
|
function privatefunc() private{} |
||||||
|
} |
||||||
|
|
||||||
|
contract Contract3 is Contract1, Contract2{ |
||||||
|
|
||||||
|
function myfunc() public{} // override myfunc |
||||||
|
|
||||||
|
} |
@ -1,7 +1,7 @@ |
|||||||
digraph{ |
digraph{ |
||||||
|
Contract1[shape="box"label=< <TABLE border="0"><TR><TD align="center"><B>Contract1</B></TD></TR><TR><TD align="left"><I>Public Functions:</I></TD></TR><TR><TD align="left"> myfunc()</TD></TR><TR><TD align="left"><I>Private Variables:</I></TD></TR><TR><TD align="left"> myvar</TD></TR></TABLE> >]; |
||||||
|
Contract2[shape="box"label=< <TABLE border="0"><TR><TD align="center"><B>Contract2</B></TD></TR><TR><TD align="left"><I>Public Functions:</I></TD></TR><TR><TD align="left"> myfunc2()</TD></TR><TR><TD align="left"><I>Private Functions:</I></TD></TR><TR><TD align="left"> privatefunc()</TD></TR><TR><TD align="left"><I>Public Variables:</I></TD></TR><TR><TD align="left"> myvar2</TD></TR></TABLE> >]; |
||||||
Contract3 -> Contract2; |
Contract3 -> Contract2; |
||||||
Contract3 -> Contract1; |
Contract3 -> Contract1; |
||||||
Contract3[shape="box"label=< <TABLE border="0"><TR><TD align="center"><B>Contract3</B></TD></TR><TR><TD align="left"><I>Public Functions:</I></TD></TR><TR><TD align="left"><font color="#FFA500"> myfunc()</font></TD></TR><TR><TD align="left"><I>Public Variables:</I></TD></TR><TR><TD align="left"> myvar2</TD></TR><TR><TD align="left"><I>Private Variables:</I></TD></TR><TR><TD align="left"> myvar</TD></TR></TABLE> >]; |
Contract3[shape="box"label=< <TABLE border="0"><TR><TD align="center"><B>Contract3</B></TD></TR><TR><TD align="left"><I>Public Functions:</I></TD></TR><TR><TD align="left"><font color="#FFA500"> myfunc()</font></TD></TR><TR><TD align="left"><I>Public Variables:</I></TD></TR><TR><TD align="left"> myvar2</TD></TR><TR><TD align="left"><I>Private Variables:</I></TD></TR><TR><TD align="left"> myvar</TD></TR></TABLE> >]; |
||||||
Contract2[shape="box"label=< <TABLE border="0"><TR><TD align="center"><B>Contract2</B></TD></TR><TR><TD align="left"><I>Public Functions:</I></TD></TR><TR><TD align="left"> myfunc2()</TD></TR><TR><TD align="left"><I>Private Functions:</I></TD></TR><TR><TD align="left"> privatefunc()</TD></TR><TR><TD align="left"><I>Public Variables:</I></TD></TR><TR><TD align="left"> myvar2</TD></TR></TABLE> >]; |
|
||||||
Contract1[shape="box"label=< <TABLE border="0"><TR><TD align="center"><B>Contract1</B></TD></TR><TR><TD align="left"><I>Public Functions:</I></TD></TR><TR><TD align="left"> myfunc()</TD></TR><TR><TD align="left"><I>Private Variables:</I></TD></TR><TR><TD align="left"> myvar</TD></TR></TABLE> >]; |
|
||||||
} |
} |
After Width: | Height: | Size: 36 KiB |
@ -0,0 +1,13 @@ |
|||||||
|
pragma solidity 0.4.24; |
||||||
|
|
||||||
|
contract MyContract{ |
||||||
|
|
||||||
|
function myfunc() public{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function myPrivateFunc() private{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,27 @@ |
|||||||
|
pragma solidity ^0.4.24; |
||||||
|
|
||||||
|
library UnsafeMath{ |
||||||
|
|
||||||
|
function add(uint a, uint b) public pure returns(uint){ |
||||||
|
return a + b; |
||||||
|
} |
||||||
|
|
||||||
|
function min(uint a, uint b) public pure returns(uint){ |
||||||
|
return a - b; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract MyContract{ |
||||||
|
using UnsafeMath for uint; |
||||||
|
|
||||||
|
mapping(address => uint) balances; |
||||||
|
|
||||||
|
function transfer(address to, uint val) public{ |
||||||
|
|
||||||
|
balances[msg.sender] = balances[msg.sender].min(val); |
||||||
|
balances[to] = balances[to].add(val); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
import sys |
||||||
|
from slither.slither import Slither |
||||||
|
from slither.slithir.convert import convert_expression |
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) != 2: |
||||||
|
print('python function_called.py functions_called.sol') |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
# Init slither |
||||||
|
slither = Slither(sys.argv[1]) |
||||||
|
|
||||||
|
# Get the contract |
||||||
|
contract = slither.get_contract_from_name('Test') |
||||||
|
|
||||||
|
# Get the variable |
||||||
|
test = contract.get_function_from_signature('one()') |
||||||
|
|
||||||
|
nodes = test.nodes |
||||||
|
|
||||||
|
for node in nodes: |
||||||
|
if node.expression: |
||||||
|
print('Expression:\n\t{}'.format(node.expression)) |
||||||
|
irs = convert_expression(node.expression) |
||||||
|
print('IR expressions:') |
||||||
|
for ir in irs: |
||||||
|
print('\t{}'.format(ir)) |
||||||
|
print() |
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@ |
|||||||
|
import sys |
||||||
|
from slither.slither import Slither |
||||||
|
|
||||||
|
|
||||||
|
if len(sys.argv) != 2: |
||||||
|
print('python function_called.py contract.sol') |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
# Init slither |
||||||
|
slither = Slither(sys.argv[1]) |
||||||
|
|
||||||
|
for contract in slither.contracts: |
||||||
|
for function in contract.functions + contract.modifiers: |
||||||
|
filename = "{}-{}-{}.dot".format(sys.argv[1], contract.name, function.full_name) |
||||||
|
print('Export {}'.format(filename)) |
||||||
|
function.slithir_cfg_to_dot(filename) |
||||||
|
|
||||||
|
|
@ -0,0 +1,32 @@ |
|||||||
|
import sys |
||||||
|
from slither import Slither |
||||||
|
|
||||||
|
if len(sys.argv) != 2: |
||||||
|
print('python slithIR.py contract.sol') |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
# Init slither |
||||||
|
slither = Slither(sys.argv[1]) |
||||||
|
|
||||||
|
# Iterate over all the contracts |
||||||
|
for contract in slither.contracts: |
||||||
|
|
||||||
|
# Iterate over all the functions |
||||||
|
for function in contract.functions: |
||||||
|
|
||||||
|
# Dont explore inherited functions |
||||||
|
if function.contract == contract: |
||||||
|
|
||||||
|
print('Function: {}'.format(function.name)) |
||||||
|
|
||||||
|
# Iterate over the nodes of the function |
||||||
|
for node in function.nodes: |
||||||
|
|
||||||
|
# Print the Solidity expression of the nodes |
||||||
|
# And the SlithIR operations |
||||||
|
if node.expression: |
||||||
|
|
||||||
|
print('\tSolidity expression: {}'.format(node.expression)) |
||||||
|
print('\tSlithIR:') |
||||||
|
for ir in node.irs: |
||||||
|
print('\t\t\t{}'.format(ir)) |
@ -0,0 +1,84 @@ |
|||||||
|
import sys |
||||||
|
|
||||||
|
from slither.core.declarations.solidity_variables import \ |
||||||
|
SolidityVariableComposed |
||||||
|
from slither.core.variables.state_variable import StateVariable |
||||||
|
from slither.slither import Slither |
||||||
|
from slither.slithir.operations.high_level_call import HighLevelCall |
||||||
|
from slither.slithir.operations.index import Index |
||||||
|
from slither.slithir.variables.reference import ReferenceVariable |
||||||
|
from slither.slithir.variables.temporary import TemporaryVariable |
||||||
|
|
||||||
|
|
||||||
|
def visit_node(node, visited): |
||||||
|
if node in visited: |
||||||
|
return |
||||||
|
|
||||||
|
visited += [node] |
||||||
|
taints = node.function.slither.context[KEY] |
||||||
|
|
||||||
|
refs = {} |
||||||
|
for ir in node.irs: |
||||||
|
if isinstance(ir, Index): |
||||||
|
refs[ir.lvalue] = ir.variable_left |
||||||
|
|
||||||
|
if isinstance(ir, Index): |
||||||
|
read = [ir.variable_left] |
||||||
|
else: |
||||||
|
read = ir.read |
||||||
|
print(ir) |
||||||
|
print('Refs {}'.format(refs)) |
||||||
|
print('Read {}'.format([str(x) for x in ir.read])) |
||||||
|
print('Before {}'.format([str(x) for x in taints])) |
||||||
|
if any(var_read in taints for var_read in read): |
||||||
|
taints += [ir.lvalue] |
||||||
|
lvalue = ir.lvalue |
||||||
|
while isinstance(lvalue, ReferenceVariable): |
||||||
|
taints += [refs[lvalue]] |
||||||
|
lvalue = refs[lvalue] |
||||||
|
|
||||||
|
print('After {}'.format([str(x) for x in taints])) |
||||||
|
print() |
||||||
|
|
||||||
|
taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] |
||||||
|
|
||||||
|
node.function.slither.context[KEY] = list(set(taints)) |
||||||
|
|
||||||
|
for son in node.sons: |
||||||
|
visit_node(son, visited) |
||||||
|
|
||||||
|
def check_call(func, taints): |
||||||
|
for node in func.nodes: |
||||||
|
for ir in node.irs: |
||||||
|
if isinstance(ir, HighLevelCall): |
||||||
|
if ir.destination in taints: |
||||||
|
print('Call to tainted address found in {}'.format(function.name)) |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
if len(sys.argv) != 2: |
||||||
|
print('python taint_mapping.py taint.sol') |
||||||
|
exit(-1) |
||||||
|
|
||||||
|
# Init slither |
||||||
|
slither = Slither(sys.argv[1]) |
||||||
|
|
||||||
|
initial_taint = [SolidityVariableComposed('msg.sender')] |
||||||
|
initial_taint += [SolidityVariableComposed('msg.value')] |
||||||
|
|
||||||
|
KEY = 'TAINT' |
||||||
|
|
||||||
|
prev_taints = [] |
||||||
|
slither.context[KEY] = initial_taint |
||||||
|
while(set(prev_taints) != set(slither.context[KEY])): |
||||||
|
prev_taints = slither.context[KEY] |
||||||
|
for contract in slither.contracts: |
||||||
|
for function in contract.functions: |
||||||
|
print('Function {}'.format(function.name)) |
||||||
|
slither.context[KEY] = list(set(slither.context[KEY] + function.parameters)) |
||||||
|
visit_node(function.entry_point, []) |
||||||
|
print('All variables tainted : {}'.format([str(v) for v in slither.context[KEY]])) |
||||||
|
|
||||||
|
print('All state variables tainted : {}'.format([str(v) for v in prev_taints if isinstance(v, StateVariable)])) |
||||||
|
|
||||||
|
for function in contract.functions: |
||||||
|
check_call(function, slither.context[KEY]) |
@ -0,0 +1,19 @@ |
|||||||
|
# Slither, Plugin Example |
||||||
|
|
||||||
|
This repo contains an example of plugin for Slither. |
||||||
|
|
||||||
|
See the [detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector). |
||||||
|
|
||||||
|
## Architecture |
||||||
|
|
||||||
|
- `setup.py`: Contain the plugin information |
||||||
|
- `slither_my_plugin/__init__.py`: Contain `make_plugin()`. The function must return the list of new detectors and printers |
||||||
|
- `slither_my_plugin/detectors/example.py`: Detector plugin skeleton. |
||||||
|
|
||||||
|
Once these files are updated with your plugin, you can install it: |
||||||
|
``` |
||||||
|
python setup.py develop |
||||||
|
``` |
||||||
|
|
||||||
|
We recommend to use a Python virtual environment (for example: [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)). |
||||||
|
|
@ -0,0 +1,17 @@ |
|||||||
|
from setuptools import setup, find_packages |
||||||
|
|
||||||
|
setup( |
||||||
|
name='slither-my-plugins', |
||||||
|
description='This is an example of detectors and printers to Slither.', |
||||||
|
url='https://github.com/trailofbits/slither-plugins', |
||||||
|
author='Trail of Bits', |
||||||
|
version='0.0', |
||||||
|
packages=find_packages(), |
||||||
|
python_requires='>=3.6', |
||||||
|
install_requires=[ |
||||||
|
'slither-analyzer==0.1' |
||||||
|
], |
||||||
|
entry_points={ |
||||||
|
'slither_analyzer.plugin': 'slither my-plugin=slither_my_plugin:make_plugin', |
||||||
|
} |
||||||
|
) |
@ -0,0 +1,8 @@ |
|||||||
|
from slither_my_plugin.detectors.example import Example |
||||||
|
|
||||||
|
|
||||||
|
def make_plugin(): |
||||||
|
plugin_detectors = [Example] |
||||||
|
plugin_printers = [] |
||||||
|
|
||||||
|
return plugin_detectors, plugin_printers |
@ -0,0 +1,19 @@ |
|||||||
|
|
||||||
|
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification |
||||||
|
|
||||||
|
|
||||||
|
class Example(AbstractDetector): |
||||||
|
""" |
||||||
|
Documentation |
||||||
|
""" |
||||||
|
|
||||||
|
ARGUMENT = 'mydetector' # slither will launch the detector with slither.py --mydetector |
||||||
|
HELP = 'Help printed by slither' |
||||||
|
IMPACT = DetectorClassification.HIGH |
||||||
|
CONFIDENCE = DetectorClassification.HIGH |
||||||
|
|
||||||
|
def detect(self): |
||||||
|
|
||||||
|
self.logger('Nothing to detect!') |
||||||
|
|
||||||
|
return [] |
@ -0,0 +1,101 @@ |
|||||||
|
#!/usr/bin/python3 |
||||||
|
|
||||||
|
''' |
||||||
|
the purpose of this file is to sort the json output from the detectors such that |
||||||
|
the order is deterministic |
||||||
|
|
||||||
|
- the keys of a json object are sorted |
||||||
|
- json objects in a list will be sorted based on the values of their keys |
||||||
|
- lists of strings/numbers are sorted |
||||||
|
|
||||||
|
''' |
||||||
|
|
||||||
|
import sys |
||||||
|
import json |
||||||
|
|
||||||
|
raw_json_file = sys.argv[1] |
||||||
|
pretty_json_file = sys.argv[2] |
||||||
|
|
||||||
|
from collections import OrderedDict |
||||||
|
|
||||||
|
def create_property_val_tuple(d, props_info): |
||||||
|
p_names = props_info[0] |
||||||
|
p_types = props_info[1] |
||||||
|
result = [] |
||||||
|
for p in p_names: |
||||||
|
if not p in d: # not all objects have the same keys |
||||||
|
if p_types[p] is 'number': |
||||||
|
result.append(0) # to make sorting work |
||||||
|
if p_types[p] is 'string': |
||||||
|
result.append("") # to make sorting work |
||||||
|
else: |
||||||
|
result.append(d[p]) |
||||||
|
return tuple(result) |
||||||
|
|
||||||
|
def get_props_info(list_of_dicts): |
||||||
|
found_props = set() |
||||||
|
prop_types = dict() |
||||||
|
|
||||||
|
# gather all prop names |
||||||
|
for d in list_of_dicts: |
||||||
|
for p in d: |
||||||
|
found_props.add(p) |
||||||
|
|
||||||
|
# create a copy, since we are gonna possibly remove props |
||||||
|
props_whose_value_we_can_sort_on = set(found_props) |
||||||
|
|
||||||
|
# for each object, loop through list of all found property names, |
||||||
|
# if the object contains that property, check that it's of type string or number |
||||||
|
# if it is, save it's type (used later on for sorting with objects that don't have that property) |
||||||
|
# if it's not of type string/number remove it from list of properties to check |
||||||
|
# since we cannot sort on non-string/number values |
||||||
|
for p in list(found_props): |
||||||
|
if p in props_whose_value_we_can_sort_on: # short circuit |
||||||
|
for d in list_of_dicts: |
||||||
|
if p in props_whose_value_we_can_sort_on: # less shorter short circuit |
||||||
|
if p in d: |
||||||
|
# we ae only gonna sort key values if they are of type string or number |
||||||
|
if not isinstance(d[p], str) and not isinstance(d[p], int): |
||||||
|
props_whose_value_we_can_sort_on.remove(p) |
||||||
|
# we need to store the type of the value because not each object |
||||||
|
# in a list of output objects for 1 detector will have the same |
||||||
|
# keys, so if we want to sort based on the values then if a certain object |
||||||
|
# does not have a key which another object does have we are gonna |
||||||
|
# put in 0 for number and "" for string for that key such that sorting on values |
||||||
|
# still works |
||||||
|
elif isinstance(d[p], str): |
||||||
|
prop_types[p] = 'string' |
||||||
|
elif isinstance(d[p], int): |
||||||
|
prop_types[p] = 'number' |
||||||
|
return (sorted(list(props_whose_value_we_can_sort_on)), prop_types) |
||||||
|
|
||||||
|
def order_by_prop_value(list_of_dicts): |
||||||
|
props_info = get_props_info(list_of_dicts) |
||||||
|
return sorted(list_of_dicts, key=lambda d: create_property_val_tuple(d, props_info)) |
||||||
|
|
||||||
|
def order_dict(d): |
||||||
|
result = OrderedDict() # such that we keep the order |
||||||
|
for k, v in sorted(d.items()): |
||||||
|
if isinstance(v, dict): |
||||||
|
result[k] = order_dict(v) |
||||||
|
elif type(v) is list: |
||||||
|
result[k] = order_list(v) |
||||||
|
else: # string/number |
||||||
|
result[k] = v |
||||||
|
return result |
||||||
|
|
||||||
|
def order_list(l): |
||||||
|
if not l: |
||||||
|
return [] |
||||||
|
if isinstance(l[0], str): # it's a list of string |
||||||
|
return sorted(l) |
||||||
|
elif isinstance(l[0], int): # it's a list of numbers |
||||||
|
return sorted(l) |
||||||
|
elif isinstance(l[0], dict): # it's a list of objects |
||||||
|
ordered_by_key = [order_dict(v) for v in l] |
||||||
|
ordered_by_val = order_by_prop_value(ordered_by_key) |
||||||
|
return ordered_by_val |
||||||
|
|
||||||
|
with open(raw_json_file, 'r') as json_data: |
||||||
|
with open(pretty_json_file, 'w') as out_file: |
||||||
|
out_file.write(json.dumps(order_list(json.load(json_data)), sort_keys=False, indent=4, separators=(',',': '))) |
@ -0,0 +1,39 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "$0")" && pwd)" |
||||||
|
|
||||||
|
# generate_expected_json file.sol detectors |
||||||
|
generate_expected_json(){ |
||||||
|
# generate output filename |
||||||
|
# e.g. file: uninitialized.sol detector: uninitialized-state |
||||||
|
# ---> uninitialized.uninitialized-state.json |
||||||
|
output_filename="$(basename $1 .sol).$2.json" |
||||||
|
|
||||||
|
# run slither detector on input file and save output as json |
||||||
|
slither "$1" --disable-solc-warnings --detect "$2" --json "$DIR/tmp-gen.json" |
||||||
|
|
||||||
|
# convert json file to pretty print and write to destination folder |
||||||
|
python "$DIR/pretty_print_and_sort_json.py" "$DIR/tmp-gen.json" "$DIR/../tests/expected_json/$output_filename" |
||||||
|
|
||||||
|
# remove the raw un-prettified json file |
||||||
|
rm "$DIR/tmp-gen.json" |
||||||
|
} |
||||||
|
|
||||||
|
generate_expected_json tests/uninitialized.sol "uninitialized-state" |
||||||
|
generate_expected_json tests/backdoor.sol "backdoor" |
||||||
|
generate_expected_json tests/backdoor.sol "suicidal" |
||||||
|
generate_expected_json tests/pragma.0.4.24.sol "pragma" |
||||||
|
generate_expected_json tests/old_solc.sol.json "solc-version" |
||||||
|
generate_expected_json tests/reentrancy.sol "reentrancy" |
||||||
|
generate_expected_json tests/uninitialized_storage_pointer.sol "uninitialized-storage" |
||||||
|
generate_expected_json tests/tx_origin.sol "tx-origin" |
||||||
|
generate_expected_json tests/unused_state.sol "unused-state" |
||||||
|
generate_expected_json tests/locked_ether.sol "locked-ether" |
||||||
|
generate_expected_json tests/arbitrary_send.sol "arbitrary-send" |
||||||
|
generate_expected_json tests/inline_assembly_contract.sol "assembly" |
||||||
|
generate_expected_json tests/inline_assembly_library.sol "assembly" |
||||||
|
generate_expected_json tests/low_level_calls.sol "low-level-calls" |
||||||
|
generate_expected_json tests/const_state_variables.sol "constable-states" |
||||||
|
generate_expected_json tests/external_function.sol "external-function" |
||||||
|
generate_expected_json tests/naming_convention.sol "naming-convention" |
||||||
|
generate_expected_json tests/uninitialized_local_variable.sol "uninitialized-local" |
@ -1,13 +1,96 @@ |
|||||||
#!/usr/bin/env bash |
#!/usr/bin/env bash |
||||||
|
|
||||||
./slither.py examples/bugs/uninitialized.sol --disable-solc-warnings |
### Test Detectors |
||||||
if [ $? -ne 1 ]; then |
|
||||||
|
DIR="$(cd "$(dirname "$0")" && pwd)" |
||||||
|
|
||||||
|
# test_slither file.sol detectors |
||||||
|
test_slither(){ |
||||||
|
|
||||||
|
expected="$DIR/../tests/expected_json/$(basename $1 .sol).$2.json" |
||||||
|
actual="$DIR/$(basename $1 .sol).$2.json" |
||||||
|
|
||||||
|
# run slither detector on input file and save output as json |
||||||
|
slither "$1" --disable-solc-warnings --detect "$2" --json "$DIR/tmp-test.json" |
||||||
|
|
||||||
|
# convert json file to pretty print and write to destination folder |
||||||
|
python "$DIR/pretty_print_and_sort_json.py" "$DIR/tmp-test.json" "$actual" |
||||||
|
|
||||||
|
# remove the raw un-prettified json file |
||||||
|
rm "$DIR/tmp-test.json" |
||||||
|
|
||||||
|
result=$(diff "$expected" "$actual") |
||||||
|
|
||||||
|
if [ "$result" != "" ]; then |
||||||
|
rm "$actual" |
||||||
|
echo "" |
||||||
|
echo "failed test of file: $1, detector: $2" |
||||||
|
echo "" |
||||||
|
echo "$result" |
||||||
|
echo "" |
||||||
|
exit 1 |
||||||
|
else |
||||||
|
rm "$actual" |
||||||
|
fi |
||||||
|
|
||||||
|
# run slither detector on input file and save output as json |
||||||
|
slither "$1" --disable-solc-warnings --detect "$2" --compact-ast --json "$DIR/tmp-test.json" |
||||||
|
|
||||||
|
# convert json file to pretty print and write to destination folder |
||||||
|
python "$DIR/pretty_print_and_sort_json.py" "$DIR/tmp-test.json" "$actual" |
||||||
|
|
||||||
|
# remove the raw un-prettified json file |
||||||
|
rm "$DIR/tmp-test.json" |
||||||
|
|
||||||
|
result=$(diff "$expected" "$actual") |
||||||
|
|
||||||
|
if [ "$result" != "" ]; then |
||||||
|
rm "$actual" |
||||||
|
echo "" |
||||||
|
echo "failed test of file: $1, detector: $2" |
||||||
|
echo "" |
||||||
|
echo "$result" |
||||||
|
echo "" |
||||||
|
exit 1 |
||||||
|
else |
||||||
|
rm "$actual" |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
test_slither tests/uninitialized.sol "uninitialized-state" |
||||||
|
test_slither tests/backdoor.sol "backdoor" |
||||||
|
test_slither tests/backdoor.sol "suicidal" |
||||||
|
test_slither tests/pragma.0.4.24.sol "pragma" |
||||||
|
test_slither tests/old_solc.sol.json "solc-version" |
||||||
|
test_slither tests/reentrancy.sol "reentrancy" |
||||||
|
test_slither tests/uninitialized_storage_pointer.sol "uninitialized-storage" |
||||||
|
test_slither tests/tx_origin.sol "tx-origin" |
||||||
|
test_slither tests/unused_state.sol "unused-state" |
||||||
|
test_slither tests/locked_ether.sol "locked-ether" |
||||||
|
test_slither tests/arbitrary_send.sol "arbitrary-send" |
||||||
|
test_slither tests/inline_assembly_contract.sol "assembly" |
||||||
|
test_slither tests/inline_assembly_library.sol "assembly" |
||||||
|
test_slither tests/low_level_calls.sol "low-level-calls" |
||||||
|
test_slither tests/const_state_variables.sol "constable-states" |
||||||
|
test_slither tests/external_function.sol "external-function" |
||||||
|
test_slither tests/naming_convention.sol "naming-convention" |
||||||
|
#test_slither tests/complex_func.sol "complex-function" |
||||||
|
|
||||||
|
### Test scripts |
||||||
|
|
||||||
|
python examples/scripts/functions_called.py examples/scripts/functions_called.sol |
||||||
|
if [ $? -ne 0 ]; then |
||||||
exit 1 |
exit 1 |
||||||
fi |
fi |
||||||
|
|
||||||
./slither.py examples/bugs/backdoor.sol --disable-solc-warnings |
python examples/scripts/functions_writing.py examples/scripts/functions_writing.sol |
||||||
if [ $? -ne 1 ]; then |
if [ $? -ne 0 ]; then |
||||||
exit 1 |
exit 1 |
||||||
fi |
fi |
||||||
|
|
||||||
|
python examples/scripts/variable_in_condition.py examples/scripts/variable_in_condition.sol |
||||||
|
if [ $? -ne 0 ]; then |
||||||
|
exit 1 |
||||||
|
fi |
||||||
exit 0 |
exit 0 |
||||||
|
@ -0,0 +1,58 @@ |
|||||||
|
""" |
||||||
|
Compute taint on call |
||||||
|
|
||||||
|
use taint from state_variable |
||||||
|
|
||||||
|
call from slithIR with a taint set to yes means its destination is tainted |
||||||
|
""" |
||||||
|
from slither.analyses.taint.state_variables import get_taint as get_taint_state |
||||||
|
from slither.core.declarations import SolidityVariableComposed |
||||||
|
from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, |
||||||
|
Member, OperationWithLValue, Send, |
||||||
|
Transfer) |
||||||
|
from slither.slithir.variables import ReferenceVariable |
||||||
|
|
||||||
|
from .common import iterate_over_irs |
||||||
|
|
||||||
|
KEY = 'TAINT_CALL_DESTINATION' |
||||||
|
|
||||||
|
def _transfer_func(ir, read, refs, taints): |
||||||
|
if isinstance(ir, OperationWithLValue) and any(var_read in taints for var_read in read): |
||||||
|
taints += [ir.lvalue] |
||||||
|
lvalue = ir.lvalue |
||||||
|
while isinstance(lvalue, ReferenceVariable): |
||||||
|
taints += [refs[lvalue]] |
||||||
|
lvalue = refs[lvalue] |
||||||
|
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): |
||||||
|
if ir.destination in taints: |
||||||
|
ir.context[KEY] = True |
||||||
|
|
||||||
|
return taints |
||||||
|
|
||||||
|
def _visit_node(node, visited, taints): |
||||||
|
if node in visited: |
||||||
|
return |
||||||
|
|
||||||
|
visited += [node] |
||||||
|
|
||||||
|
taints = iterate_over_irs(node.irs, _transfer_func, taints) |
||||||
|
|
||||||
|
for son in node.sons: |
||||||
|
_visit_node(son, visited, taints) |
||||||
|
|
||||||
|
def _run_taint(slither, initial_taint): |
||||||
|
if KEY in slither.context: |
||||||
|
return |
||||||
|
for contract in slither.contracts: |
||||||
|
for function in contract.functions: |
||||||
|
if not function.is_implemented: |
||||||
|
continue |
||||||
|
_visit_node(function.entry_point, [], initial_taint + function.parameters) |
||||||
|
|
||||||
|
def run_taint(slither): |
||||||
|
initial_taint = get_taint_state(slither) |
||||||
|
initial_taint += [SolidityVariableComposed('msg.sender')] |
||||||
|
|
||||||
|
if KEY not in slither.context: |
||||||
|
_run_taint(slither, initial_taint) |
||||||
|
|
@ -0,0 +1,18 @@ |
|||||||
|
from slither.slithir.operations import (Index, Member, Length, Balance) |
||||||
|
|
||||||
|
def iterate_over_irs(irs, transfer_func, taints): |
||||||
|
refs = {} |
||||||
|
for ir in irs: |
||||||
|
if isinstance(ir, (Index, Member)): |
||||||
|
refs[ir.lvalue] = ir.variable_left |
||||||
|
|
||||||
|
if isinstance(ir, (Length, Balance)): |
||||||
|
refs[ir.lvalue] = ir.value |
||||||
|
|
||||||
|
if isinstance(ir, Index): |
||||||
|
read = [ir.variable_left] |
||||||
|
else: |
||||||
|
read = ir.read |
||||||
|
taints = transfer_func(ir, read, refs, taints) |
||||||
|
return taints |
||||||
|
|
@ -0,0 +1,115 @@ |
|||||||
|
""" |
||||||
|
Compute taint from a specific variable |
||||||
|
|
||||||
|
Do not propagate taint on protected function or constructor |
||||||
|
Propage to state variables |
||||||
|
Iterate until it finding a fixpoint |
||||||
|
""" |
||||||
|
from slither.core.declarations.solidity_variables import SolidityVariable |
||||||
|
from slither.core.variables.state_variable import StateVariable |
||||||
|
from slither.core.variables.variable import Variable |
||||||
|
from slither.slithir.operations import Index, Member, OperationWithLValue |
||||||
|
from slither.slithir.variables import ReferenceVariable, TemporaryVariable |
||||||
|
|
||||||
|
from .common import iterate_over_irs |
||||||
|
|
||||||
|
def make_key(variable): |
||||||
|
if isinstance(variable, Variable): |
||||||
|
key = 'TAINT_{}'.format(id(variable)) |
||||||
|
else: |
||||||
|
assert isinstance(variable, SolidityVariable) |
||||||
|
key = 'TAINT_{}{}'.format(variable.name, |
||||||
|
str(type(variable))) |
||||||
|
return key |
||||||
|
|
||||||
|
def _transfer_func_with_key(ir, read, refs, taints, key): |
||||||
|
if isinstance(ir, OperationWithLValue) and ir.lvalue: |
||||||
|
if any(is_tainted_from_key(var_read, key) or var_read in taints for var_read in read): |
||||||
|
taints += [ir.lvalue] |
||||||
|
ir.lvalue.context[key] = True |
||||||
|
lvalue = ir.lvalue |
||||||
|
while isinstance(lvalue, ReferenceVariable): |
||||||
|
taints += [refs[lvalue]] |
||||||
|
lvalue = refs[lvalue] |
||||||
|
lvalue.context[key] = True |
||||||
|
return taints |
||||||
|
|
||||||
|
def _visit_node(node, visited, key): |
||||||
|
if node in visited: |
||||||
|
return |
||||||
|
|
||||||
|
visited = visited + [node] |
||||||
|
taints = node.function.slither.context[key] |
||||||
|
|
||||||
|
# taints only increase |
||||||
|
# if we already see this node with the last taint set |
||||||
|
# we dont need to explore itœ |
||||||
|
if node in node.slither.context['visited_all_paths']: |
||||||
|
if node.slither.context['visited_all_paths'][node] == taints: |
||||||
|
return |
||||||
|
|
||||||
|
node.slither.context['visited_all_paths'][node] = taints |
||||||
|
|
||||||
|
# use of lambda function, as the key is required for this transfer_func |
||||||
|
_transfer_func_ = lambda _ir, _read, _refs, _taints: _transfer_func_with_key(_ir, |
||||||
|
_read, |
||||||
|
_refs, |
||||||
|
_taints, |
||||||
|
key) |
||||||
|
taints = iterate_over_irs(node.irs, _transfer_func_, taints) |
||||||
|
|
||||||
|
node.function.slither.context[key] = list(set(taints)) |
||||||
|
|
||||||
|
for son in node.sons: |
||||||
|
_visit_node(son, visited, key) |
||||||
|
|
||||||
|
def run_taint(slither, taint): |
||||||
|
|
||||||
|
key = make_key(taint) |
||||||
|
|
||||||
|
# if a node was already visited by another path |
||||||
|
# we will only explore it if the traversal brings |
||||||
|
# new variables written |
||||||
|
# This speedup the exploration through a light fixpoint |
||||||
|
# Its particular useful on 'complex' functions with several loops and conditions |
||||||
|
slither.context['visited_all_paths'] = {} |
||||||
|
|
||||||
|
prev_taints = [] |
||||||
|
slither.context[key] = [taint] |
||||||
|
# Loop until reaching a fixpoint |
||||||
|
while(set(prev_taints) != set(slither.context[key])): |
||||||
|
prev_taints = slither.context[key] |
||||||
|
for contract in slither.contracts: |
||||||
|
for function in contract.functions: |
||||||
|
# Dont propagated taint on protected functions |
||||||
|
if function.is_implemented and not function.is_protected(): |
||||||
|
slither.context[key] = list(set(slither.context[key])) |
||||||
|
_visit_node(function.entry_point, [], key) |
||||||
|
|
||||||
|
slither.context[key] = [v for v in prev_taints if isinstance(v, (StateVariable, SolidityVariable))] |
||||||
|
|
||||||
|
def is_tainted(variable, taint): |
||||||
|
""" |
||||||
|
Args: |
||||||
|
variable (Variable) |
||||||
|
taint (Variable): Root of the taint |
||||||
|
""" |
||||||
|
if not isinstance(variable, (Variable, SolidityVariable)): |
||||||
|
return False |
||||||
|
key = make_key(taint) |
||||||
|
return (key in variable.context and variable.context[key]) or variable == taint |
||||||
|
|
||||||
|
def is_tainted_from_key(variable, key): |
||||||
|
""" |
||||||
|
Args: |
||||||
|
variable (Variable) |
||||||
|
key (str): key |
||||||
|
""" |
||||||
|
if not isinstance(variable, (Variable, SolidityVariable)): |
||||||
|
return False |
||||||
|
return key in variable.context and variable.context[key] |
||||||
|
|
||||||
|
|
||||||
|
def get_state_variable_tainted(slither, taint): |
||||||
|
key = make_key(taint) |
||||||
|
return slither.context[key] |
@ -0,0 +1,82 @@ |
|||||||
|
""" |
||||||
|
Compute taint on state variables |
||||||
|
|
||||||
|
Do not propagate taint on protected function |
||||||
|
Compute taint from function parameters, msg.sender and msg.value |
||||||
|
Iterate until it finding a fixpoint |
||||||
|
|
||||||
|
""" |
||||||
|
from slither.core.declarations.solidity_variables import \ |
||||||
|
SolidityVariableComposed |
||||||
|
from slither.core.variables.state_variable import StateVariable |
||||||
|
from slither.slithir.operations import Index, Member, OperationWithLValue |
||||||
|
from slither.slithir.variables import ReferenceVariable, TemporaryVariable |
||||||
|
|
||||||
|
from .common import iterate_over_irs |
||||||
|
KEY = 'TAINT_STATE_VARIABLES' |
||||||
|
|
||||||
|
def _transfer_func(ir, read, refs, taints): |
||||||
|
if isinstance(ir, OperationWithLValue) and any(var_read in taints for var_read in read): |
||||||
|
taints += [ir.lvalue] |
||||||
|
lvalue = ir.lvalue |
||||||
|
while isinstance(lvalue, ReferenceVariable): |
||||||
|
taints += [refs[lvalue]] |
||||||
|
lvalue = refs[lvalue] |
||||||
|
return taints |
||||||
|
|
||||||
|
def _visit_node(node, visited): |
||||||
|
if node in visited: |
||||||
|
return |
||||||
|
|
||||||
|
visited += [node] |
||||||
|
taints = node.function.slither.context[KEY] |
||||||
|
|
||||||
|
taints = iterate_over_irs(node.irs, _transfer_func, taints) |
||||||
|
|
||||||
|
taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] |
||||||
|
|
||||||
|
node.function.slither.context[KEY] = list(set(taints)) |
||||||
|
|
||||||
|
for son in node.sons: |
||||||
|
_visit_node(son, visited) |
||||||
|
|
||||||
|
|
||||||
|
def _run_taint(slither, initial_taint): |
||||||
|
if KEY in slither.context: |
||||||
|
return |
||||||
|
|
||||||
|
prev_taints = [] |
||||||
|
slither.context[KEY] = initial_taint |
||||||
|
# Loop until reaching a fixpoint |
||||||
|
while(set(prev_taints) != set(slither.context[KEY])): |
||||||
|
prev_taints = slither.context[KEY] |
||||||
|
for contract in slither.contracts: |
||||||
|
for function in contract.functions: |
||||||
|
if not function.is_implemented: |
||||||
|
continue |
||||||
|
# Dont propagated taint on protected functions |
||||||
|
if not function.is_protected(): |
||||||
|
slither.context[KEY] = list(set(slither.context[KEY] + function.parameters)) |
||||||
|
_visit_node(function.entry_point, []) |
||||||
|
|
||||||
|
slither.context[KEY] = [v for v in prev_taints if isinstance(v, StateVariable)] |
||||||
|
|
||||||
|
def run_taint(slither, initial_taint=None): |
||||||
|
if initial_taint is None: |
||||||
|
initial_taint = [SolidityVariableComposed('msg.sender')] |
||||||
|
initial_taint += [SolidityVariableComposed('msg.value')] |
||||||
|
|
||||||
|
if KEY not in slither.context: |
||||||
|
_run_taint(slither, initial_taint) |
||||||
|
|
||||||
|
def get_taint(slither, initial_taint=None): |
||||||
|
""" |
||||||
|
Return the state variables tainted |
||||||
|
Args: |
||||||
|
slither: |
||||||
|
initial_taint (List Variable) |
||||||
|
Returns: |
||||||
|
List(StateVariable) |
||||||
|
""" |
||||||
|
run_taint(slither, initial_taint) |
||||||
|
return slither.context[KEY] |
@ -0,0 +1,57 @@ |
|||||||
|
""" |
||||||
|
Detect if all the given variables are written in all the paths of the function |
||||||
|
""" |
||||||
|
from slither.core.cfg.node import NodeType |
||||||
|
from slither.core.declarations import SolidityFunction |
||||||
|
from slither.slithir.operations import (Index, Member, OperationWithLValue, |
||||||
|
SolidityCall, Length, Balance) |
||||||
|
from slither.slithir.variables import ReferenceVariable |
||||||
|
|
||||||
|
|
||||||
|
def _visit(node, visited, variables_written, variables_to_write): |
||||||
|
|
||||||
|
if node in visited: |
||||||
|
return [] |
||||||
|
|
||||||
|
visited = visited + [node] |
||||||
|
|
||||||
|
refs = {} |
||||||
|
for ir in node.irs: |
||||||
|
if isinstance(ir, SolidityCall): |
||||||
|
# TODO convert the revert to a THROW node |
||||||
|
if ir.function in [SolidityFunction('revert(string)'), |
||||||
|
SolidityFunction('revert()')]: |
||||||
|
return [] |
||||||
|
|
||||||
|
if not isinstance(ir, OperationWithLValue): |
||||||
|
continue |
||||||
|
if isinstance(ir, (Index, Member)): |
||||||
|
refs[ir.lvalue] = ir.variable_left |
||||||
|
if isinstance(ir, (Length, Balance)): |
||||||
|
refs[ir.lvalue] = ir.value |
||||||
|
|
||||||
|
variables_written = variables_written + [ir.lvalue] |
||||||
|
lvalue = ir.lvalue |
||||||
|
while isinstance(lvalue, ReferenceVariable): |
||||||
|
variables_written = variables_written + [refs[lvalue]] |
||||||
|
lvalue = refs[lvalue] |
||||||
|
|
||||||
|
ret = [] |
||||||
|
if not node.sons and not node.type in [NodeType.THROW, NodeType.RETURN]: |
||||||
|
ret += [v for v in variables_to_write if not v in variables_written] |
||||||
|
|
||||||
|
for son in node.sons: |
||||||
|
ret += _visit(son, visited, variables_written, variables_to_write) |
||||||
|
return ret |
||||||
|
|
||||||
|
def are_variables_written(function, variables_to_write): |
||||||
|
""" |
||||||
|
Return the list of variable that are not written at the end of the function |
||||||
|
|
||||||
|
Args: |
||||||
|
function (Function) |
||||||
|
variables_to_write (list Variable): variable that must be written |
||||||
|
Returns: |
||||||
|
list(Variable): List of variable that are not written (sublist of variables_to_write) |
||||||
|
""" |
||||||
|
return list(set(_visit(function.entry_point, [], [], variables_to_write))) |
@ -1,63 +0,0 @@ |
|||||||
class NodeType: |
|
||||||
|
|
||||||
ENTRYPOINT = 0x0 # no expression |
|
||||||
|
|
||||||
# Node with expression |
|
||||||
|
|
||||||
EXPRESSION = 0x10 # normal case |
|
||||||
RETURN = 0x11 # RETURN may contain an expression |
|
||||||
IF = 0x12 |
|
||||||
VARIABLE = 0x13 # Declaration of variable |
|
||||||
ASSEMBLY = 0x14 |
|
||||||
IFLOOP = 0x15 |
|
||||||
|
|
||||||
# Below the nodes have no expression |
|
||||||
# But are used to expression CFG structure |
|
||||||
|
|
||||||
# Absorbing node |
|
||||||
THROW = 0x20 |
|
||||||
|
|
||||||
# Loop related nodes |
|
||||||
BREAK = 0x31 |
|
||||||
CONTINUE = 0x32 |
|
||||||
|
|
||||||
# Only modifier node |
|
||||||
PLACEHOLDER = 0x40 |
|
||||||
|
|
||||||
# Merging nodes |
|
||||||
# Unclear if they will be necessary |
|
||||||
ENDIF = 0x50 |
|
||||||
STARTLOOP = 0x51 |
|
||||||
ENDLOOP = 0x52 |
|
||||||
|
|
||||||
@staticmethod |
|
||||||
def str(t): |
|
||||||
if t == 0x0: |
|
||||||
return 'EntryPoint' |
|
||||||
if t == 0x10: |
|
||||||
return 'Expressions' |
|
||||||
if t == 0x11: |
|
||||||
return 'Return' |
|
||||||
if t == 0x12: |
|
||||||
return 'If' |
|
||||||
if t == 0x13: |
|
||||||
return 'New variable' |
|
||||||
if t == 0x14: |
|
||||||
return 'Inline Assembly' |
|
||||||
if t == 0x15: |
|
||||||
return 'IfLoop' |
|
||||||
if t == 0x20: |
|
||||||
return 'Throw' |
|
||||||
if t == 0x31: |
|
||||||
return 'Break' |
|
||||||
if t == 0x32: |
|
||||||
return 'Continue' |
|
||||||
if t == 0x40: |
|
||||||
return '_' |
|
||||||
if t == 0x50: |
|
||||||
return 'EndIf' |
|
||||||
if t == 0x51: |
|
||||||
return 'BeginLoop' |
|
||||||
if t == 0x52: |
|
||||||
return 'EndLoop' |
|
||||||
return 'Unknown type {}'.format(hex(t)) |
|
@ -0,0 +1,20 @@ |
|||||||
|
|
||||||
|
class ChildNode(object): |
||||||
|
def __init__(self): |
||||||
|
super(ChildNode, self).__init__() |
||||||
|
self._node = None |
||||||
|
|
||||||
|
def set_node(self, node): |
||||||
|
self._node = node |
||||||
|
|
||||||
|
@property |
||||||
|
def node(self): |
||||||
|
return self._node |
||||||
|
|
||||||
|
@property |
||||||
|
def function(self): |
||||||
|
return self.node.function |
||||||
|
|
||||||
|
@property |
||||||
|
def contract(self): |
||||||
|
return self.node.function.contract |
@ -0,0 +1,9 @@ |
|||||||
|
from .contract import Contract |
||||||
|
from .enum import Enum |
||||||
|
from .event import Event |
||||||
|
from .function import Function |
||||||
|
from .import_directive import Import |
||||||
|
from .modifier import Modifier |
||||||
|
from .pragma_directive import Pragma |
||||||
|
from .solidity_variables import SolidityVariable, SolidityVariableComposed, SolidityFunction |
||||||
|
from .structure import Structure |
@ -0,0 +1,14 @@ |
|||||||
|
from slither.core.source_mapping.source_mapping import SourceMapping |
||||||
|
|
||||||
|
class Import(SourceMapping): |
||||||
|
|
||||||
|
def __init__(self, filename): |
||||||
|
super(Import, self).__init__() |
||||||
|
self._filename = filename |
||||||
|
|
||||||
|
@property |
||||||
|
def filename(self): |
||||||
|
return self._filename |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return self.filename |
@ -0,0 +1,21 @@ |
|||||||
|
from slither.core.source_mapping.source_mapping import SourceMapping |
||||||
|
|
||||||
|
class Pragma(SourceMapping): |
||||||
|
|
||||||
|
def __init__(self, directive): |
||||||
|
super(Pragma, self).__init__() |
||||||
|
self._directive = directive |
||||||
|
|
||||||
|
@property |
||||||
|
def directive(self): |
||||||
|
''' |
||||||
|
list(str) |
||||||
|
''' |
||||||
|
return self._directive |
||||||
|
|
||||||
|
@property |
||||||
|
def version(self): |
||||||
|
return ''.join(self.directive[1:]) |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return 'pragma '+''.join(self.directive) |
@ -1,48 +0,0 @@ |
|||||||
# https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html |
|
||||||
|
|
||||||
|
|
||||||
SOLIDITY_VARIABLES = ["block", "msg", "now", "tx", "this", "super", 'abi'] |
|
||||||
|
|
||||||
SOLIDITY_VARIABLES_COMPOSED = ["block.coinbase", "block.difficulty", "block.gaslimit", "block.number", "block.timestamp", "msg.data", "msg.gas", "msg.sender", "msg.sig", "msg.value", "tx.gasprice", "tx.origin"] |
|
||||||
|
|
||||||
SOLIDITY_FUNCTIONS = ["gasleft()", "assert(bool)", "require(bool)", "require(bool,string)", "revert()", "revert(string)", "addmod(uint256,uint256,uint256)", "mulmod(uint256,uint256,uint256)", "keccak256()", "sha256()", "sha3()", "ripemd160()", "ecrecover(bytes32,uint8,bytes32,bytes32)", "selfdestruct(address)", "suicide(address)", "log0(bytes32)", "log1(bytes32,bytes32)", "log2(bytes32,bytes32,bytes32)", "log3(bytes32,bytes32,bytes32,bytes32)", "blockhash(uint256)"] |
|
||||||
|
|
||||||
class SolidityVariable: |
|
||||||
|
|
||||||
def __init__(self, name): |
|
||||||
assert name in SOLIDITY_VARIABLES |
|
||||||
self._name = name |
|
||||||
|
|
||||||
@property |
|
||||||
def name(self): |
|
||||||
return self._name |
|
||||||
|
|
||||||
def __str__(self): |
|
||||||
return self._name |
|
||||||
|
|
||||||
class SolidityVariableComposed(SolidityVariable): |
|
||||||
def __init__(self, name): |
|
||||||
assert name in SOLIDITY_VARIABLES_COMPOSED |
|
||||||
self._name = name |
|
||||||
|
|
||||||
@property |
|
||||||
def name(self): |
|
||||||
return self._name |
|
||||||
|
|
||||||
def __str__(self): |
|
||||||
return self._name |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SolidityFunction: |
|
||||||
|
|
||||||
def __init__(self, name): |
|
||||||
assert name in SOLIDITY_FUNCTIONS |
|
||||||
self._name = name |
|
||||||
|
|
||||||
@property |
|
||||||
def name(self): |
|
||||||
return self._name |
|
||||||
|
|
||||||
def __str__(self): |
|
||||||
return self._name |
|
@ -0,0 +1,147 @@ |
|||||||
|
# https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html |
||||||
|
from slither.core.context.context import Context |
||||||
|
from slither.core.solidity_types import ElementaryType |
||||||
|
|
||||||
|
SOLIDITY_VARIABLES = {"now":'uint256', |
||||||
|
"this":'address', |
||||||
|
'abi':'address', # to simplify the conversion, assume that abi return an address |
||||||
|
'msg':'', |
||||||
|
'tx':'', |
||||||
|
'block':'', |
||||||
|
'super':''} |
||||||
|
|
||||||
|
SOLIDITY_VARIABLES_COMPOSED = {"block.coinbase":"address", |
||||||
|
"block.difficulty":"uint256", |
||||||
|
"block.gaslimit":"uint256", |
||||||
|
"block.number":"uint256", |
||||||
|
"block.timestamp":"uint256", |
||||||
|
"block.blockhash":"uint256", # alias for blockhash. It's a call |
||||||
|
"msg.data":"bytes", |
||||||
|
"msg.gas":"uint256", |
||||||
|
"msg.sender":"address", |
||||||
|
"msg.sig":"bytes4", |
||||||
|
"msg.value":"uint256", |
||||||
|
"tx.gasprice":"uint256", |
||||||
|
"tx.origin":"address"} |
||||||
|
|
||||||
|
|
||||||
|
SOLIDITY_FUNCTIONS = {"gasleft()":['uint256'], |
||||||
|
"assert(bool)":[], |
||||||
|
"require(bool)":[], |
||||||
|
"require(bool,string)":[], |
||||||
|
"revert()":[], |
||||||
|
"revert(string)":[], |
||||||
|
"addmod(uint256,uint256,uint256)":['uint256'], |
||||||
|
"mulmod(uint256,uint256,uint256)":['uint256'], |
||||||
|
"keccak256()":['bytes32'], |
||||||
|
"sha256()":['bytes32'], |
||||||
|
"sha3()":['bytes32'], |
||||||
|
"ripemd160()":['bytes32'], |
||||||
|
"ecrecover(bytes32,uint8,bytes32,bytes32)":['address'], |
||||||
|
"selfdestruct(address)":[], |
||||||
|
"suicide(address)":[], |
||||||
|
"log0(bytes32)":[], |
||||||
|
"log1(bytes32,bytes32)":[], |
||||||
|
"log2(bytes32,bytes32,bytes32)":[], |
||||||
|
"log3(bytes32,bytes32,bytes32,bytes32)":[], |
||||||
|
"blockhash(uint256)":['bytes32'], |
||||||
|
# the following need a special handling |
||||||
|
# as they are recognized as a SolidityVariableComposed |
||||||
|
# and converted to a SolidityFunction by SlithIR |
||||||
|
"this.balance()":['uint256'], |
||||||
|
"abi.encode()":['bytes'], |
||||||
|
"abi.encodePacked()":['bytes'], |
||||||
|
"abi.encodeWithSelector()":["bytes"], |
||||||
|
"abi.encodeWithSignature()":["bytes"]} |
||||||
|
|
||||||
|
def solidity_function_signature(name): |
||||||
|
""" |
||||||
|
Return the function signature (containing the return value) |
||||||
|
It is useful if a solidity function is used as a pointer |
||||||
|
(see exoressionParsing.find_variable documentation) |
||||||
|
Args: |
||||||
|
name(str): |
||||||
|
Returns: |
||||||
|
str |
||||||
|
""" |
||||||
|
return name+' returns({})'.format(','.join(SOLIDITY_FUNCTIONS[name])) |
||||||
|
|
||||||
|
class SolidityVariable(Context): |
||||||
|
|
||||||
|
def __init__(self, name): |
||||||
|
super(SolidityVariable, self).__init__() |
||||||
|
self._check_name(name) |
||||||
|
self._name = name |
||||||
|
|
||||||
|
# dev function, will be removed once the code is stable |
||||||
|
def _check_name(self, name): |
||||||
|
assert name in SOLIDITY_VARIABLES |
||||||
|
|
||||||
|
@property |
||||||
|
def name(self): |
||||||
|
return self._name |
||||||
|
|
||||||
|
@property |
||||||
|
def type(self): |
||||||
|
return ElementaryType(SOLIDITY_VARIABLES[self.name]) |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return self._name |
||||||
|
|
||||||
|
def __eq__(self, other): |
||||||
|
return self.__class__ == other.__class__ and self.name == other.name |
||||||
|
|
||||||
|
def __hash__(self): |
||||||
|
return hash(self.name) |
||||||
|
|
||||||
|
class SolidityVariableComposed(SolidityVariable): |
||||||
|
def __init__(self, name): |
||||||
|
super(SolidityVariableComposed, self).__init__(name) |
||||||
|
|
||||||
|
def _check_name(self, name): |
||||||
|
assert name in SOLIDITY_VARIABLES_COMPOSED |
||||||
|
|
||||||
|
@property |
||||||
|
def name(self): |
||||||
|
return self._name |
||||||
|
|
||||||
|
@property |
||||||
|
def type(self): |
||||||
|
return ElementaryType(SOLIDITY_VARIABLES_COMPOSED[self.name]) |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return self._name |
||||||
|
|
||||||
|
def __eq__(self, other): |
||||||
|
return self.__class__ == other.__class__ and self.name == other.name |
||||||
|
|
||||||
|
def __hash__(self): |
||||||
|
return hash(self.name) |
||||||
|
|
||||||
|
|
||||||
|
class SolidityFunction: |
||||||
|
|
||||||
|
def __init__(self, name): |
||||||
|
assert name in SOLIDITY_FUNCTIONS |
||||||
|
self._name = name |
||||||
|
|
||||||
|
@property |
||||||
|
def name(self): |
||||||
|
return self._name |
||||||
|
|
||||||
|
@property |
||||||
|
def full_name(self): |
||||||
|
return self.name |
||||||
|
|
||||||
|
@property |
||||||
|
def return_type(self): |
||||||
|
return [ElementaryType(x) for x in SOLIDITY_FUNCTIONS[self.name]] |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return self._name |
||||||
|
|
||||||
|
def __eq__(self, other): |
||||||
|
return self.__class__ == other.__class__ and self.name == other.name |
||||||
|
|
||||||
|
def __hash__(self): |
||||||
|
return hash(self.name) |
@ -0,0 +1,16 @@ |
|||||||
|
from .assignment_operation import AssignmentOperation, AssignmentOperationType |
||||||
|
from .binary_operation import BinaryOperation, BinaryOperationType |
||||||
|
from .call_expression import CallExpression |
||||||
|
from .conditional_expression import ConditionalExpression |
||||||
|
from .elementary_type_name_expression import ElementaryTypeNameExpression |
||||||
|
from .identifier import Identifier |
||||||
|
from .index_access import IndexAccess |
||||||
|
from .literal import Literal |
||||||
|
from .new_array import NewArray |
||||||
|
from .new_contract import NewContract |
||||||
|
from .new_elementary_type import NewElementaryType |
||||||
|
from .super_call_expression import SuperCallExpression |
||||||
|
from .super_identifier import SuperIdentifier |
||||||
|
from .tuple_expression import TupleExpression |
||||||
|
from .type_conversion import TypeConversion |
||||||
|
from .unary_operation import UnaryOperation, UnaryOperationType |
@ -1,5 +1,5 @@ |
|||||||
from slither.core.expressions.expressionTyped import ExpressionTyped |
from slither.core.expressions.expression_typed import ExpressionTyped |
||||||
from slither.core.solidityTypes.type import Type |
from slither.core.solidity_types.type import Type |
||||||
|
|
||||||
|
|
||||||
class IndexAccess(ExpressionTyped): |
class IndexAccess(ExpressionTyped): |
@ -1,6 +1,6 @@ |
|||||||
from slither.core.expressions.expression import Expression |
from slither.core.expressions.expression import Expression |
||||||
from slither.core.expressions.expressionTyped import ExpressionTyped |
from slither.core.expressions.expression_typed import ExpressionTyped |
||||||
from slither.core.solidityTypes.type import Type |
from slither.core.solidity_types.type import Type |
||||||
|
|
||||||
class MemberAccess(ExpressionTyped): |
class MemberAccess(ExpressionTyped): |
||||||
|
|
@ -1,8 +1,5 @@ |
|||||||
import logging |
from slither.core.expressions.expression import Expression |
||||||
from .expression import Expression |
from slither.core.solidity_types.type import Type |
||||||
from slither.core.solidityTypes.type import Type |
|
||||||
|
|
||||||
logger = logging.getLogger("NewArray") |
|
||||||
|
|
||||||
class NewArray(Expression): |
class NewArray(Expression): |
||||||
|
|
@ -1,5 +1,5 @@ |
|||||||
from slither.core.expressions.expression import Expression |
from slither.core.expressions.expression import Expression |
||||||
from slither.core.solidityTypes.elementaryType import ElementaryType |
from slither.core.solidity_types.elementary_type import ElementaryType |
||||||
|
|
||||||
class NewElementaryType(Expression): |
class NewElementaryType(Expression): |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
from slither.core.expressions.expression import Expression |
from slither.core.expressions.expression import Expression |
||||||
from slither.core.expressions.callExpression import CallExpression |
from slither.core.expressions.call_expression import CallExpression |
||||||
|
|
||||||
class SuperCallExpression(CallExpression): pass |
class SuperCallExpression(CallExpression): pass |
@ -1,4 +1,4 @@ |
|||||||
from slither.core.expressions.expressionTyped import ExpressionTyped |
from slither.core.expressions.expression_typed import ExpressionTyped |
||||||
from slither.core.expressions.identifier import Identifier |
from slither.core.expressions.identifier import Identifier |
||||||
|
|
||||||
class SuperIdentifier(Identifier): |
class SuperIdentifier(Identifier): |
@ -1,6 +1,6 @@ |
|||||||
from slither.core.expressions.expressionTyped import ExpressionTyped |
from slither.core.expressions.expression_typed import ExpressionTyped |
||||||
from slither.core.expressions.expression import Expression |
from slither.core.expressions.expression import Expression |
||||||
from slither.core.solidityTypes.type import Type |
from slither.core.solidity_types.type import Type |
||||||
|
|
||||||
|
|
||||||
class TypeConversion(ExpressionTyped): |
class TypeConversion(ExpressionTyped): |
@ -1,26 +0,0 @@ |
|||||||
from slither.core.solidityTypes.type import Type |
|
||||||
from slither.core.variables.functionTypeVariable import FunctionTypeVariable |
|
||||||
from slither.core.expressions.expression import Expression |
|
||||||
|
|
||||||
class FunctionType(Type): |
|
||||||
|
|
||||||
def __init__(self, params, return_values): |
|
||||||
assert all(isinstance(x, FunctionTypeVariable) for x in params) |
|
||||||
assert all(isinstance(x, FunctionTypeVariable) for x in return_values) |
|
||||||
super(FunctionType, self).__init__() |
|
||||||
self._params = params |
|
||||||
self._return_values = return_values |
|
||||||
|
|
||||||
@property |
|
||||||
def params(self): |
|
||||||
return self._params |
|
||||||
|
|
||||||
@property |
|
||||||
def return_values(self): |
|
||||||
return self._return_values |
|
||||||
|
|
||||||
def __str__(self): |
|
||||||
params = ".".join([str(x) for x in self._params]) |
|
||||||
return_values = ".".join([str(x) for x in self._return_values]) |
|
||||||
return 'function({}) returns ({})'.format(params, return_values) |
|
||||||
|
|
@ -1,3 +0,0 @@ |
|||||||
from slither.core.sourceMapping.sourceMapping import SourceMapping |
|
||||||
|
|
||||||
class Type(SourceMapping): pass |
|
@ -1,23 +0,0 @@ |
|||||||
from slither.core.solidityTypes.type import Type |
|
||||||
|
|
||||||
from slither.core.declarations.structure import Structure |
|
||||||
from slither.core.declarations.enum import Enum |
|
||||||
from slither.core.declarations.contract import Contract |
|
||||||
|
|
||||||
|
|
||||||
class UserDefinedType(Type): |
|
||||||
|
|
||||||
def __init__(self, t): |
|
||||||
assert isinstance(t, (Contract, Enum, Structure)) |
|
||||||
super(UserDefinedType, self).__init__() |
|
||||||
self._type = t |
|
||||||
|
|
||||||
@property |
|
||||||
def type(self): |
|
||||||
return self._type |
|
||||||
|
|
||||||
def __str__(self): |
|
||||||
if isinstance(self.type, (Enum, Structure)): |
|
||||||
return str(self.type.contract)+'.'+str(self.type.name) |
|
||||||
return str(self.type.name) |
|
||||||
|
|
@ -0,0 +1,5 @@ |
|||||||
|
from .array_type import ArrayType |
||||||
|
from .elementary_type import ElementaryType |
||||||
|
from .function_type import FunctionType |
||||||
|
from .mapping_type import MappingType |
||||||
|
from .user_defined_type import UserDefinedType |
@ -0,0 +1,66 @@ |
|||||||
|
from slither.core.solidity_types.type import Type |
||||||
|
from slither.core.variables.function_type_variable import FunctionTypeVariable |
||||||
|
from slither.core.expressions.expression import Expression |
||||||
|
|
||||||
|
class FunctionType(Type): |
||||||
|
|
||||||
|
def __init__(self, params, return_values): |
||||||
|
assert all(isinstance(x, FunctionTypeVariable) for x in params) |
||||||
|
assert all(isinstance(x, FunctionTypeVariable) for x in return_values) |
||||||
|
super(FunctionType, self).__init__() |
||||||
|
self._params = params |
||||||
|
self._return_values = return_values |
||||||
|
|
||||||
|
@property |
||||||
|
def params(self): |
||||||
|
return self._params |
||||||
|
|
||||||
|
@property |
||||||
|
def return_values(self): |
||||||
|
return self._return_values |
||||||
|
|
||||||
|
@property |
||||||
|
def return_type(self): |
||||||
|
return [x.type for x in self.return_values] |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
# Use x.type |
||||||
|
# x.name may be empty |
||||||
|
params = ",".join([str(x.type) for x in self._params]) |
||||||
|
return_values = ",".join([str(x.type) for x in self._return_values]) |
||||||
|
if return_values: |
||||||
|
return 'function({}) returns({})'.format(params, return_values) |
||||||
|
return 'function({})'.format(params) |
||||||
|
|
||||||
|
@property |
||||||
|
def parameters_signature(self): |
||||||
|
''' |
||||||
|
Return the parameters signature(without the return statetement) |
||||||
|
''' |
||||||
|
# Use x.type |
||||||
|
# x.name may be empty |
||||||
|
params = ",".join([str(x.type) for x in self._params]) |
||||||
|
return '({})'.format(params) |
||||||
|
|
||||||
|
@property |
||||||
|
def signature(self): |
||||||
|
''' |
||||||
|
Return the signature(with the return statetement if it exists) |
||||||
|
''' |
||||||
|
# Use x.type |
||||||
|
# x.name may be empty |
||||||
|
params = ",".join([str(x.type) for x in self._params]) |
||||||
|
return_values = ",".join([str(x.type) for x in self._return_values]) |
||||||
|
if return_values: |
||||||
|
return '({}) returns({})'.format(params, return_values) |
||||||
|
return '({})'.format(params) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __eq__(self, other): |
||||||
|
if not isinstance(other, FunctionType): |
||||||
|
return False |
||||||
|
return self.params == other.params and self.return_values == other.return_values |
||||||
|
|
||||||
|
def __hash__(self): |
||||||
|
return hash(str(self)) |
@ -0,0 +1,3 @@ |
|||||||
|
from slither.core.source_mapping.source_mapping import SourceMapping |
||||||
|
|
||||||
|
class Type(SourceMapping): pass |
@ -0,0 +1,35 @@ |
|||||||
|
from slither.core.solidity_types.type import Type |
||||||
|
|
||||||
|
|
||||||
|
class UserDefinedType(Type): |
||||||
|
|
||||||
|
def __init__(self, t): |
||||||
|
from slither.core.declarations.structure import Structure |
||||||
|
from slither.core.declarations.enum import Enum |
||||||
|
from slither.core.declarations.contract import Contract |
||||||
|
|
||||||
|
assert isinstance(t, (Contract, Enum, Structure)) |
||||||
|
super(UserDefinedType, self).__init__() |
||||||
|
self._type = t |
||||||
|
|
||||||
|
@property |
||||||
|
def type(self): |
||||||
|
return self._type |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
from slither.core.declarations.structure import Structure |
||||||
|
from slither.core.declarations.enum import Enum |
||||||
|
|
||||||
|
if isinstance(self.type, (Enum, Structure)): |
||||||
|
return str(self.type.contract)+'.'+str(self.type.name) |
||||||
|
return str(self.type.name) |
||||||
|
|
||||||
|
def __eq__(self, other): |
||||||
|
if not isinstance(other, UserDefinedType): |
||||||
|
return False |
||||||
|
return self.type == other.type |
||||||
|
|
||||||
|
|
||||||
|
def __hash__(self): |
||||||
|
return hash(str(self)) |
||||||
|
|
@ -1,18 +0,0 @@ |
|||||||
from slither.core.context.context import Context |
|
||||||
|
|
||||||
class SourceMapping(Context): |
|
||||||
|
|
||||||
def __init__(self): |
|
||||||
super(SourceMapping, self).__init__() |
|
||||||
self._source_mapping = None |
|
||||||
self._offset = None |
|
||||||
|
|
||||||
def set_source_mapping(self, source_mapping): |
|
||||||
self._source_mapping = source_mapping |
|
||||||
|
|
||||||
@property |
|
||||||
def source_mapping(self): |
|
||||||
return self._source_mapping |
|
||||||
|
|
||||||
def set_offset(self, offset): |
|
||||||
self._offset = offset |
|
@ -0,0 +1,89 @@ |
|||||||
|
import re |
||||||
|
from slither.core.context.context import Context |
||||||
|
|
||||||
|
class SourceMapping(Context): |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
super(SourceMapping, self).__init__() |
||||||
|
self._source_mapping = None |
||||||
|
|
||||||
|
@property |
||||||
|
def source_mapping(self): |
||||||
|
return self._source_mapping |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def _compute_line(source_code, start, length): |
||||||
|
""" |
||||||
|
Compute line(s) number from a start/end offset |
||||||
|
Not done in an efficient way |
||||||
|
""" |
||||||
|
total_length = len(source_code) |
||||||
|
source_code = source_code.split('\n') |
||||||
|
counter = 0 |
||||||
|
i = 0 |
||||||
|
lines = [] |
||||||
|
while counter < total_length: |
||||||
|
counter += len(source_code[i]) +1 |
||||||
|
i = i+1 |
||||||
|
if counter > start: |
||||||
|
lines.append(i) |
||||||
|
if counter > start+length: |
||||||
|
break |
||||||
|
return lines |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def _convert_source_mapping(offset, slither): |
||||||
|
''' |
||||||
|
Convert a text offset to a real offset |
||||||
|
see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings |
||||||
|
Returns: |
||||||
|
(dict): {'start':0, 'length':0, 'filename': 'file.sol'} |
||||||
|
''' |
||||||
|
sourceUnits = slither.source_units |
||||||
|
|
||||||
|
position = re.findall('([0-9]*):([0-9]*):([-]?[0-9]*)', offset) |
||||||
|
if len(position) != 1: |
||||||
|
return {} |
||||||
|
|
||||||
|
s, l, f = position[0] |
||||||
|
s = int(s) |
||||||
|
l = int(l) |
||||||
|
f = int(f) |
||||||
|
|
||||||
|
if f not in sourceUnits: |
||||||
|
return {'start':s, 'length':l} |
||||||
|
filename = sourceUnits[f] |
||||||
|
|
||||||
|
lines = [] |
||||||
|
|
||||||
|
if filename in slither.source_code: |
||||||
|
lines = SourceMapping._compute_line(slither.source_code[filename], s, l) |
||||||
|
|
||||||
|
return {'start':s, 'length':l, 'filename': filename, 'lines' : lines } |
||||||
|
|
||||||
|
def set_offset(self, offset, slither): |
||||||
|
if isinstance(offset, dict): |
||||||
|
self._source_mapping = offset |
||||||
|
else: |
||||||
|
self._source_mapping = self._convert_source_mapping(offset, slither) |
||||||
|
|
||||||
|
|
||||||
|
@property |
||||||
|
def source_mapping_str(self): |
||||||
|
|
||||||
|
def relative_path(path): |
||||||
|
# Remove absolute path for printing |
||||||
|
# Truffle returns absolutePath |
||||||
|
if '/contracts/' in path: |
||||||
|
return path[path.find('/contracts/'):] |
||||||
|
return path |
||||||
|
|
||||||
|
lines = self.source_mapping['lines'] |
||||||
|
if not lines: |
||||||
|
lines = '' |
||||||
|
elif len(lines) == 1: |
||||||
|
lines = '#{}'.format(lines[0]) |
||||||
|
else: |
||||||
|
lines = '#{}-{}'.format(lines[0], lines[-1]) |
||||||
|
return '{}{}'.format(relative_path(self.source_mapping['filename']), lines) |
||||||
|
|
@ -1,5 +1,5 @@ |
|||||||
from .variable import Variable |
from .variable import Variable |
||||||
from slither.core.children.childEvent import ChildEvent |
from slither.core.children.child_event import ChildEvent |
||||||
|
|
||||||
class EventVariable(ChildEvent, Variable): pass |
class EventVariable(ChildEvent, Variable): pass |
||||||
|
|
@ -1,5 +0,0 @@ |
|||||||
from .variable import Variable |
|
||||||
from slither.core.children.childFunction import ChildFunction |
|
||||||
|
|
||||||
class LocalVariable(ChildFunction, Variable): pass |
|
||||||
|
|
@ -0,0 +1,48 @@ |
|||||||
|
from .variable import Variable |
||||||
|
from slither.core.children.child_function import ChildFunction |
||||||
|
from slither.core.solidity_types.user_defined_type import UserDefinedType |
||||||
|
from slither.core.solidity_types.array_type import ArrayType |
||||||
|
|
||||||
|
from slither.core.declarations.structure import Structure |
||||||
|
|
||||||
|
|
||||||
|
class LocalVariable(ChildFunction, Variable): |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
super(LocalVariable, self).__init__() |
||||||
|
self._location = None |
||||||
|
|
||||||
|
|
||||||
|
def set_location(self, loc): |
||||||
|
self._location = loc |
||||||
|
|
||||||
|
@property |
||||||
|
def location(self): |
||||||
|
''' |
||||||
|
Variable Location |
||||||
|
Can be storage/memory or default |
||||||
|
Returns: |
||||||
|
(str) |
||||||
|
''' |
||||||
|
return self._location |
||||||
|
|
||||||
|
@property |
||||||
|
def is_storage(self): |
||||||
|
""" |
||||||
|
Return true if the variable is located in storage |
||||||
|
See https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location |
||||||
|
Returns: |
||||||
|
(bool) |
||||||
|
""" |
||||||
|
if self.location == 'memory': |
||||||
|
return False |
||||||
|
if self.location == 'storage': |
||||||
|
return True |
||||||
|
|
||||||
|
if isinstance(self.type, ArrayType): |
||||||
|
return True |
||||||
|
|
||||||
|
if isinstance(self.type, UserDefinedType): |
||||||
|
return isinstance(self.type.type, Structure) |
||||||
|
|
||||||
|
return False |
@ -1,4 +1,4 @@ |
|||||||
from slither.core.variables.localVariable import LocalVariable |
from slither.core.variables.local_variable import LocalVariable |
||||||
|
|
||||||
class LocalVariableInitFromTuple(LocalVariable): |
class LocalVariableInitFromTuple(LocalVariable): |
||||||
""" |
""" |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue