10

Hi was going through solidity documentation. There was some code that I was not able to understand, even after researching a lot I was not able to find some satisfactory output. The code is as under:

contract Mutex {
  bool locked;
modifier noReentrancy() {
    require(!locked);
    locked = true;
    _;
    locked = false;
}

/// This function is protected by a mutex, which means that
/// reentrant calls from within `msg.sender.call` cannot call `f` again.
/// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier.
  function f() public noReentrancy returns (uint) {
    require(msg.sender.call());
   return 7;
  }
}

What msg.sender.call() does? is it calling f() again? If yes then how?

Shane Fontaine
  • 18,036
  • 20
  • 54
  • 82
Thinker
  • 837
  • 2
  • 10
  • 14

2 Answers2

7

It calls the anonymous fallback function on msg.sender.

In a typical reentrancy attack, it would be something like a withdraw function doing msg.sender.call.value(1 ether)(). The caller (a smart contract), would then call the function again, hence the "reentrancy" attack. In this snippet, the call doesn't seem to be doing anything useful, but it's just there to show that the locked variables guards against reentrancy.

user19510
  • 27,999
  • 2
  • 30
  • 48
6

msg.sender.call() calls the on msg.sender.

Here is an example that is extended with a canBeAttacked function.

contract Mutex {
  bool locked;
modifier noReentrancy() {
    require(!locked);
    locked = true;
    _;
    locked = false;
}

  function canBeAttacked() public returns (uint) {
    require(msg.sender.call.value(1 ether)());
   return 7;
  }

/// This function is protected by a mutex
  function f() public noReentrancy returns (uint) {
    require(msg.sender.call());
   return 7;
  }
}

2 things to be aware of from above code.

  1. An attacker can create a contract that has a fallback

of:

// attacker fallback
function() {
  Mutex(msg.sender).canBeAttacked();
}

The contract can have another function that calls canBeAttacked(), and this will keep reentering canBeAttacked() and send the attacker 1 ether each time. (The fallback needs more code to avoid an infinite loop and running out of gas.) This is an example of a reentrant attack.

Now imagine an attacker contract that has a fallback of:

// attacker fallback
function() {
  Mutex(msg.sender).f();
}
  1. When attacker calls f(), the attacker fallback will fail because locked is true, so require(!locked) will generate an exception and revert the entire transaction.

Also note that noReentrancy prevents an attacker calling f() and in the attacker's fallback, calling canBeAttacked. (But they can attack as #1 above.)

eth
  • 85,679
  • 53
  • 285
  • 406
  • 2
    You didn't explain why anyone would want to call the sender's fallback function in the first place. What purpose does this serve? – Luke Hutchison Dec 01 '21 at 08:27
  • 2
    @LukeHutchison You're right that in almost all cases one doesn't want to call the fallback function. You might be asking a similar thing I wondered about why sending ether to a contract, always invokes the fallback function? https://ethereum.stackexchange.com/questions/1505/how-does-a-solidity-fallback-function-work-with-the-raw-call-opcode-of-the-evm – eth Dec 02 '21 at 20:52