This is a javascript implementation of the recent Dao attack.
var baseBalance = 1000;
var reward = 10;
var times = 0;
var actualWithdrewAmount = 0;
function sender() {
if (times < 30) {
times++;
withdrawBalance();
} else {
times = 0;
}
function foo() {
}
foo.value = function(amount) {
return function() {
if (amount > 0) {
actualWithdrewAmount += amount;
console.log('withdrawing ' + amount);
return true;
} else {
console.log('none');
return false;
}
}
}
return foo
}
function withdrawBalance() {
amountToWithdraw = reward
if (!(sender().value(amountToWithdraw)())) { throw Error }
baseBalance -= reward;
reward = 0;
}
withdrawBalance()
console.log('base balance after attack: ' + baseBalance)
console.log('actualWithdrewAmount: ' + actualWithdrewAmount);
The SplitDao function of Dao calls withdrawRewardFor(msg.sender) inside it which actually calls the message sender contract to send reward to it.
The attacker can implement a loop inside the general function of its contract (which becomes msg.sender above) to call split DAO again and again creating a recursive call stack.
And because the msg.sender's balance is only zeroed out after his eth balance is sent to the childDao and withdrawRewardFor(). The recursive call on SplitDao will send the attacker's original amount to the child dao until call stack reaches max limit or the attacker stops paying for gas.
function splitDAO(
uint _proposalID,
address _newCurator
) noEther onlyTokenholders returns (bool _success) {
...
// XXXXX Move ether and assign new Tokens. Notice how this is done first!
uint fundsToBeMoved =
(balances[msg.sender] * p.splitData[0].splitBalance) /
p.splitData[0].totalSupply;
if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false) // XXXXX This is the line the attacker wants to run more than once
throw;
...
// Burn DAO Tokens
Transfer(msg.sender, 0, balances[msg.sender]);
withdrawRewardFor(msg.sender); // be nice, and get his rewards // XXXXX Notice the preceding line is critically before the next few
totalSupply -= balances[msg.sender]; // XXXXX AND THIS IS DONE LAST
balances[msg.sender] = 0; // XXXXX AND THIS IS DONE LAST TOO
paidOut[msg.sender] = 0;
return true;
}
function withdrawRewardFor(address _account) noEther internal returns (bool _success) {
if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account])
throw;
uint reward =
(balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account];
if (!rewardAccount.payOut(_account, reward)) // XXXXX vulnerable
throw;
paidOut[_account] += reward;
return true;
}
function payOut(address _recipient, uint _amount) returns (bool) {
if (msg.sender != owner || msg.value > 0 || (payOwnerOnly && _recipient != owner))
throw;
if (_recipient.call.value(_amount)()) { // XXXXX vulnerable
PayOut(_recipient, _amount);
return true;
} else {
return false;
}
}