1

Consider the following contracts

contract Victim{
//...

  function donate() public payable 
  {
    credit[msg.sender] += msg.value;
  }


  function withdraw(uint amount) public {

    if (credit[msg.sender] >= amount)
    {
      (bool success, ) = msg.sender.call.value(amount)("");
      require(success);
      credit[msg.sender] -= amount;
    }
  }
//...
}

contract Attack{
  Victim victim;
//...
  function () payable {
     victim.withdraw.value(0)(balance);
  }
}
//...

My question: how do we initiate attack as an end-user?

Scenario 1. First call occurs from an EOA (say Bob), for example using a Javascript library like this attack.withdraw(1). Here, Bob calls the withdraw function and so msg.sender is Bob's EOA address (assuming Bob signs the transaction with his private key). Inside the withdraw function we transfer funds to Bob msg.sender.call.value(amount)("");. But how is Attack's fallback function called in this case if msg.sender is not a contract address?

Scenario 2. We call victim.withdraw.value(0)(balance) from inside Attack contract. This time around msg.sender is the contract's address. Inside the withdraw function there is a check if (credit[msg.sender] >= amount). So, does the Attack contract have to have nonzero balance inside the Victim contract? I cannot transfer ethers to the contract's address from truffle console: 'Error: Returned error: sender account not recognized'.

1 Answer 1

2

Scenario 1: This is not possible. An EOA does not have code, by definition, so there is no fallback function to run. When coding, we generally do not know, in advance, who the caller will be so we assume the worst - "hostile contract."

Scenario2: Bob calls Attacker which is a contract that needs to have a balance, in this instance, to get past the require that checks the balance. Attacker's fallback will be called by the transfer and this will give Attacker a chance to call withdraw() again. Since Attacker's balance will be unchanged, it will, again, give Attacker the requested amount.

Attacker can iterate over this process multiple times. It needs to stop before the transaction runs out of gas or the transaction will revert. If he stops in time, then we will have multiples of the amount, possibly in excess of his balance.

The contract will proceed to subtract the amount, once for each iteration, and if Attacker is overdrawn (likely), then Attacker will have a massive balance - far more than the funds actually on deposit. That will permit Attacker to come back and ask for it as much money as is available with a simple withdrawal.

So, there are two problems. Re-entrance, and underflow.

The situation would be mitigated by moving credit[msg.sender] -= amount; ahead of the transfer to "put the house in order" before interacting with another untrusted entity.

The defensive pattern is known as "checks, effects, interactions."

  • Checks: `require(hasEnoughMoney());
  • Effects: subtract the money, emit event, optimistic accounting
  • Interaction: send the money, call the function, etc.

I don't see how underflow could happen if the instructions were re-ordered, but to be on the safe side, most developers use SafeMath to be sure.

Consider:

c = a - b;
require(c <= a, "there was an underflow");

SafeMath lets you do that and keep it concise:

c = a.sub(b);

Hope it helps.

4
  • Thank you. Actually I understand how reentrancy works inside a contract, how to defense too . What I don't understand (in Scenario 2) is that msg.sender is the contract address, not Bob's address, right? But in this case Bob's contract must have non zero balance in the victims's contract. In truffle I couldn't donate using attack contract.
    – HardFork
    Nov 21, 2019 at 0:30
  • Also you write "Since Bob's balance will be unchanged". But in scenario 2 it is not Bob who calls, it is attack-contract. I guess it is truffle's whims that it does not allow to use accounts except 10 ones setup by Ganache. It is not the case in real Ethereum network, right?
    – HardFork
    Nov 21, 2019 at 0:36
  • I was confused about the scenario. I've changed "Bob" to "Attacker" to correct that. "Victim" doesn't know who it's talking to. If Bob wants the money, then his Attacker will have a way for him to withdraw the loot. There is nothing special about the 10 accounts - just an arbitrary number of funded test accounts to play with, and it can be changed with command-line options. Real-world wallets can have any number of accounts. The contract on the chain potentially faces all EOA and contracts that will ever exist. Nov 21, 2019 at 0:54
  • Thanks for accepting my answer. Nov 21, 2019 at 0:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.