12

From a blogpost on security from Christian Reitwiessner:

Because of the maximal stack depth of 1024 the new bidder can always increase the stack size to 1023 and then call bid() which will cause the send(highestBid) call to silently fail.

So it seems that you can prepare the stack that a send() will fail but the contract will still continue its execution and terminate.

What is the rational behind this behaviour and is it possible to "attack" more than one send() or would a second send() cause an exception if the first already failed "silently".

10

Rationale for depth limit:

Having a 1024 call depth limit - many programming languages break at high stack depths much more quickly than they break at high levels of memory usage or computational load, so the implied limit from the block gas limit may not be sufficient.

The EVM needs to be deterministic. If an Ethereum client is implemented in a language that breaks at high stack depths, then such a client would not be able to implement the Ethereum protocol: it would not be able to produce the results according to consensus.

A recent proposal by Vitalik Buterin is to simply have gas as the limiting factor (though the depth could reach 2091).


function foo() {
  send()
  send()
}

Both send() can be attacked. Invoking the first send() increases the depth by 1, but reduces it again by 1 when it returns. send() doesn't throw, so no exceptions are generated.

To attack foo(), an attacker recursively invokes itself 1023 times, then invokes foo(). (In Solidity, the recursive function should be external so that the call depth is increased [unlike internal functions which are jumps within the code and don't increase the depth].)

(Invokes itself 1023 times because the first invocation before the recursion increases the depth by 1. By the time foo is called, the depth will be at the 1024 limit.)

EDIT: EIP 150 makes it impractical to reach a stack depth of 1024, effectively eliminating call depth attacks. See How does EIP 150 change the call depth attack?

  • What do you mean by "an attacker recursively invokes itself" ? @eth – alper Dec 16 '17 at 20:41
  • 1
    @Alper Something like an attack() function that does if (i==1023) foo() else { i++; attack(); } – eth Dec 30 '17 at 13:20
  • What's the logic behind attacking yourself, i.e., writing a contract with a function that calls itself recursively 1023 times and then calls something else, which never gets executed? There doesn't seem to be any financial gain to this exploit also. – GViz Aug 21 '18 at 2:26
  • @Gviz You're not attacking yourself. The blogpost in the question has an example. Another example is suppose you're buying a non-fungible token from someone, if a call depth attack was possible, it could be used so that you would get the token but end up not paying the other person. – eth Sep 11 '18 at 11:18
  • @eth thanks I see now. One point of clarification: to increase stack depth, an external call is required. Is it sufficient for the attacker contract to have defined a function attack() that is invoked with this.attack() to behave as external? – GViz Sep 18 '18 at 18:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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