Below is quoted from solidity docs:
pragma solidity ^0.4.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
/// Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() {
if (msg.sender.send(shares[msg.sender]))
shares[msg.sender] = 0;
}
}
The problem is: Ether transfer always includes code execution, so the recipient could be a contract that calls back into withdraw. This would let it get multiple refunds and basically retrieve all the Ether in the contract.
To avoid re-entrancy, you can use the Checks-Effects-Interactions pattern as outlined further below:
pragma solidity ^0.4.11;
contract Fund {
/// Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() {
var share = shares[msg.sender];
shares[msg.sender] = 0;
msg.sender.transfer(share);
}
}
The highlighted part is where I have question: I do understand that in theory reentrancy attack exists, but given the fact that fallback function has 2300 gas limitation (link), which is not enough to make a message call back into current contract. So in reality, how could the reentrancy attack work? Can some expert give an live example? Thanks!
Best Answer
Update
Before Sep 2016
send
andtransfer
pass all available gas to CALL op by default. This was changed in the Solidity compiler in this commit https://github.com/ethereum/solidity/commit/9ca7472089a9f4d8bfec20e9e55c4f7ed2fb502e.The above comments mean that if you send 0 ether the compiler will explicitly set the gas to 2300 for the CALL op. Otherwise the compiler sets 0 gas for the CALL op in which case the EVM will add 2300 gas stipend as explained on the Subtleties page.
The code that adds all available gas to the external function call is in ExpressionCompiler::appendExternalFunctionCall():
The documentation was written in June 2016 https://github.com/ethereum/solidity/commit/2df142c49618138ba7f38f32a76022caecc68abb. Here is the pull request to fix it https://github.com/ethereum/solidity/pull/3197
Previous answer
Here is the transaction trace that demonstrates that it's possible to make a reentrant call: https://rinkeby.etherscan.io/vmtrace?txhash=0x2e77009bda0fc9c07a01a4589d9b426382521c6e04b84008ffda637a4268824f
On step 183 CALL operation is performed which costs 700 gas:
The reason it costs only 700 gas is because no value is passed with the call.
As explained on the Subtleties page in Ethereum's wiki:
The corresponding contracts code:
Note that while the original contract in Solidity docs is vulnerable to reentrancy attack it can't be exploited to send value more than once because the 2nd nested
send
call would deplete the 2300 gas stipend. That's why in my example I had to use the mutex.Note also that in the DAO hack
call
method was used which passes all available gas to the nested call unlikesend
which only passes the 2300 stipend by default.