Title
Reentrancy
Relationships
CWE-841: Improper Enforcement of Behavioral Workflow
Description
One of the major dangers of calling external contracts is that they can take over the control flow. In the reentrancy attack (a.k.a. recursive call attack), a malicious contract calls back into the calling contract before the first invocation of the function is finished. This may cause the different invocations of the function to interact in undesirable ways.
Remediation
The best practices to avoid Reentrancy weaknesses are:
- Make sure all internal state changes are performed before the call is executed. This is known as the Checks-Effects-Interactions pattern
- Use a reentrancy lock (ie. OpenZeppelin's ReentrancyGuard).
References
Samples
modifier_reentrancy.sol
pragma solidity ^0.5.0;
contract ModifierEntrancy {
mapping (address => uint) public tokenBalance;
string constant name = "Nu Token";
Bank bank;
constructor() public{
bank = new Bank();
}
//If a contract has a zero balance and supports the token give them some token
function airDrop() hasNoBalance supportsToken public{
tokenBalance[msg.sender] += 20;
}
//Checks that the contract responds the way we want
modifier supportsToken() {
require(keccak256(abi.encodePacked("Nu Token")) == bank.supportsToken());
_;
}
//Checks that the caller has a zero balance
modifier hasNoBalance {
require(tokenBalance[msg.sender] == 0);
_;
}
}
contract Bank{
function supportsToken() external returns(bytes32) {
return keccak256(abi.encodePacked("Nu Token"));
}
}
modifier_reentrancy_fixed.sol
pragma solidity ^0.5.0;
contract ModifierEntrancy {
mapping (address => uint) public tokenBalance;
string constant name = "Nu Token";
Bank bank;
constructor() public{
bank = new Bank();
}
//If a contract has a zero balance and supports the token give them some token
function airDrop() supportsToken hasNoBalance public{ // In the fixed version supportsToken comes before hasNoBalance
tokenBalance[msg.sender] += 20;
}
//Checks that the contract responds the way we want
modifier supportsToken() {
require(keccak256(abi.encodePacked("Nu Token")) == bank.supportsToken());
_;
}
//Checks that the caller has a zero balance
modifier hasNoBalance {
require(tokenBalance[msg.sender] == 0);
_;
}
}
contract Bank{
function supportsToken() external returns(bytes32){
return(keccak256(abi.encodePacked("Nu Token")));
}
}
simple_dao.sol
/*
* @source: http://blockchain.unica.it/projects/ethereum-survey/attacks.html#simpledao
* @author: Atzei N., Bartoletti M., Cimoli T
* Modified by Josselin Feist
*/
pragma solidity 0.4.24;
contract SimpleDAO {
mapping (address => uint) public credit;
function donate(address to) payable public{
credit[to] += msg.value;
}
function withdraw(uint amount) public{
if (credit[msg.sender]>= amount) {
require(msg.sender.call.value(amount)());
credit[msg.sender]-=amount;
}
}
function queryCredit(address to) view public returns(uint){
return credit[to];
}
}
simple_dao_fixed.sol
/*
* @source: http://blockchain.unica.it/projects/ethereum-survey/attacks.html#simpledao
* @author: Atzei N., Bartoletti M., Cimoli T
* Modified by Bernhard Mueller, Josselin Feist
*/
pragma solidity 0.4.24;
contract SimpleDAO {
mapping (address => uint) public credit;
function donate(address to) payable public{
credit[to] += msg.value;
}
function withdraw(uint amount) public {
if (credit[msg.sender]>= amount) {
credit[msg.sender]-=amount;
require(msg.sender.call.value(amount)());
}
}
function queryCredit(address to) view public returns (uint){
return credit[to];
}
}