Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
LABS
Glossary

Call Stack Reentrancy

Call stack reentrancy is a denial-of-service attack vector where an attacker recursively calls a vulnerable contract to exceed the EVM's call stack depth, preventing the original function from finishing.
Chainscore © 2026
definition
SECURITY VULNERABILITY

What is Call Stack Reentrancy?

A specific type of reentrancy attack that exploits the depth limit of the Ethereum Virtual Machine's call stack.

Call stack reentrancy is a smart contract vulnerability where an attacker exploits the 1024-frame depth limit of the Ethereum Virtual Machine (EVM) call stack to cause a transaction to fail during a state-changing callback. Unlike classic reentrancy, which manipulates contract state before updates, this attack forces a failure in an external call, often via a revert(), which then propagates up the call stack. The original, vulnerable function may then continue execution with its state changes intact but its external interactions incomplete, leading to inconsistencies. This is also known as call stack depth limit attack or EVM stack overflow attack.

The attack vector relies on the attacker recursively calling into the vulnerable contract until the call stack is nearly full (e.g., depth 1022). A subsequent legitimate call from the victim contract then exceeds the limit, causing an out-of-gas-style REVERT. Crucially, in pre-Constantinople Ethereum clients, a failed CALL did not revert the entire state of the calling contract. This allowed the vulnerable function to proceed past the failed call, often skipping critical logic like balance updates or event emissions, while the initial state changes (like a transfer) had already occurred.

A classic example involves a contract that performs a state update (like recording a withdrawal), then makes an external call to send funds. An attacker's contract, receiving the funds, would recursively call back into the victim. After sufficient recursion, the victim's call to send funds would fail due to stack depth, but the victim's function would continue, leaving its internal ledger in an inconsistent state where the attacker's balance was deducted but the ether was not successfully sent. The EIP-150 hard fork significantly mitigated this by adjusting gas costs, making it practically impossible to reach the depth limit, but the pattern remains a critical lesson in safe state management.

The primary defense against call stack reentrancy is the use of the Checks-Effects-Interactions pattern, which dictates that code should: perform all checks first, update all internal state (effects) second, and only then interact with external contracts (interactions). This ensures state is finalized before any potentially re-entrant calls. Furthermore, using higher-level security constructs like ReentrancyGuard modifiers or adopting the pull-over-push pattern for payments, where users withdraw funds themselves, eliminates the vulnerable external call from the core logic. Understanding this historical vulnerability is essential for auditing older contracts and designing robust state machines.

how-it-works
VULNERABILITY MECHANISM

How Call Stack Reentrancy Works

Call stack reentrancy is a specific type of smart contract vulnerability where a malicious contract exploits the depth of the Ethereum Virtual Machine's call stack to bypass security checks, distinct from the more common state-based reentrancy.

Call stack reentrancy is a smart contract vulnerability where an attacker exploits the EVM's 1024-frame call stack limit to cause a function call to fail and bypass subsequent security logic. Unlike classic reentrancy, which manipulates contract state before a state update, this attack targets the execution flow itself. The attacker initiates a recursive chain of external calls that consumes most of the call stack depth. When the victim contract later attempts a low-level call (e.g., send() or call() without gas) to the attacker, this call fails because it would exceed the stack limit, but the execution continues as if the call succeeded, often skipping critical checks like balance deductions.

The attack's success hinges on the behavior of low-level send() and call() operations when they run out of gas or fail due to the call stack limit. These operations return false on failure but do not revert the entire transaction by default. If a contract does not check this return value—a common oversight—its execution proceeds under the false assumption the external interaction was successful. This can leave the contract in an inconsistent state, for instance, crediting funds without deducting a balance. The vulnerability is often found in functions that perform multiple external transfers or make calls after the presumed "safe" state changes.

A canonical example is a contract that uses send() to refund users after an operation, assuming the send will always succeed. An attacker creates a contract whose fallback() or receive() function calls back into the victim contract recursively 1023 times. When the victim's send() to the attacker is the 1024th call, it fails silently. The victim's code, not checking the return value, continues and may grant a benefit (like releasing collateral) as if the refund had been paid. This flaw was famously exploited in the King of the Ether game and similar early contracts.

Mitigating call stack reentrancy requires a defensive coding paradigm. The primary defense is to always check the return value of low-level call() and send() operations and handle failures appropriately, often by reverting the transaction. The Checks-Effects-Interactions pattern remains crucial, as it minimizes state changes after external calls. However, for this specific attack, using higher-level transfer() (which throws on failure) or explicitly limiting gas in external calls can be effective, as a failed transfer() will revert, preventing the flawed continuation. Modern developers are advised to use the call() function with explicit gas and return value checks for all external interactions.

It is important to distinguish this from the state reentrancy vulnerability made infamous by The DAO hack. State reentrancy occurs when an external call allows a malicious contract to re-enter the calling function before its state (like a balance) is updated, enabling repeated withdrawals. Call stack reentancy, by contrast, does not rely on re-entering the same function; it causes a later external call to fail, tricking the contract into skipping post-call logic. Understanding this distinction is key for auditors and developers in applying the correct mitigation strategies for different attack vectors within smart contract security.

key-features
CALL STACK REENTRANCY

Key Characteristics of the Attack

Call stack reentrancy is a vulnerability where an attacker exploits the order of external calls within a function to manipulate contract state after the initial call but before its effects are finalized.

01

Single-Function Exploitation

Unlike classical reentrancy, this attack occurs within a single transaction and function execution. The attacker does not re-enter the same function via a callback. Instead, they manipulate the order of operations and state changes between multiple external calls made by the vulnerable function.

  • The vulnerable function makes several external calls.
  • The attacker's contract intercepts one call, executes custom logic, and then calls back into the original contract before the vulnerable function's final state update.
02

State Inconsistency Window

The core vulnerability is a temporary state inconsistency between internal accounting and external asset transfers. The contract updates its internal balance after making external calls, creating a window where the recorded state does not reflect reality.

For example, a contract might:

  1. Transfer tokens to a user.
  2. Then decrement its internal balance ledger.

An attacker can exploit the gap between step 1 and step 2.

03

Exploit via Low-Level Calls

The attack is typically enabled by the use of low-level call operations (e.g., addr.call{value: x}("")) instead of higher-level transfer methods. call forwards all remaining gas by default and does not throw an exception on failure unless explicitly checked, allowing the recipient's fallback function to execute complex logic and make further calls back to the sender.

04

Checks-Effects-Interactions Violation

This is a direct violation of the Checks-Effects-Interactions pattern, the primary defense against reentrancy. The secure pattern mandates:

  1. Checks: Validate all conditions (e.g., balances, permissions).
  2. Effects: Update all internal state variables.
  3. Interactions: Perform external calls last.

Call stack reentrancy exploits contracts that perform Interactions before completing all Effects.

05

Cross-Function Implications

The attacker's callback often targets a different function in the same vulnerable contract, one that relies on the inconsistent state. For instance, after receiving funds in step 1, the callback might invoke a withdraw() function that checks the same, not-yet-updated balance ledger, allowing a double spend.

This makes the attack more subtle than direct reentrancy, as the malicious code path differs from the initially called function.

06

Mitigation: The Reentrancy Guard

The standard mitigation is a reentrancy guard modifier, such as OpenZeppelin's ReentrancyGuard. It uses a boolean lock (nonReentrant) that is set on entry and cleared on exit of a function, preventing any function with the modifier from being called recursively within the same transaction.

  • Best Practice: Apply nonReentrant to any function that makes external calls.
  • Fundamental Fix: Strictly adhere to the Checks-Effects-Interactions pattern.
code-example
CALL STACK REENTRANCY

Code Example: Vulnerable Pattern

This section dissects a canonical smart contract vulnerability where an external call allows an attacker to re-enter the function before its state updates are finalized.

A vulnerable pattern for call stack reentrancy occurs when a smart contract function performs an external call (e.g., sending Ether via .call.value()) to an untrusted address before it updates its own internal state. This creates a dangerous execution window where the recipient contract's fallback or receive function can call back into the vulnerable function, exploiting the unchanged state to drain funds or manipulate logic. The classic example is a simple bank contract that deducts a user's balance after transferring their withdrawal.

The core flaw is the violation of the Checks-Effects-Interactions pattern. Secure code should: 1) Check all preconditions (e.g., sufficient balance), 2) Effect all state changes (e.g., deduct the balance), and 3) only then perform Interactions with external contracts. The vulnerable pattern inverts steps 2 and 3. When the external call is made, the contract's state still reflects the old balance, allowing a malicious contract to re-enter the withdraw function repeatedly, passing the initial check each time, as the balance hasn't yet been reduced.

In the provided code example, the function might resemble: function withdraw(uint _amount) public { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(''); balances[msg.sender] -= _amount; }. Here, the .call.value() interaction precedes the crucial state update balances[msg.sender] -= _amount. An attacker's contract would have a fallback function that simply calls vulnerableContract.withdraw() again, initiating a recursive loop until gas limits are reached or funds are exhausted.

Mitigating this vulnerability requires adhering to the Checks-Effects-Interactions pattern as a primary defense. For Ether transfers, using transfer or send (which forward a limited 2300 gas) can prevent complex reentrancy but is not a complete solution. The modern and robust approach is to employ a reentrancy guard, such as OpenZeppelin's ReentrancyGuard modifier, which uses a boolean lock (nonReentrant) to block recursive calls into the protected function for the duration of its execution.

Understanding this pattern is fundamental to smart contract security auditing. It was the exploit vector in the infamous DAO attack of 2016, which resulted in the loss of millions of dollars in Ether and led to the Ethereum network hard fork. Auditors systematically search for state changes that occur after external calls to untrusted addresses, as this sequence is a strong indicator of potential reentrancy vulnerabilities.

security-considerations
CALL STACK REENTRANCY

Security Implications & Risks

Call stack reentrancy is a vulnerability where an attacker exploits the order of external calls within a function to manipulate the contract's state before the initial execution completes, often leading to drained funds or corrupted logic.

01

Core Vulnerability Pattern

The exploit occurs when a contract makes an external call to an untrusted contract before updating its own internal state. The malicious contract's fallback or receive function reenters the calling function, which sees outdated state variables. This allows repeated withdrawals or unauthorized actions, as seen in the infamous DAO hack of 2016.

02

The Checks-Effects-Interactions Pattern

The primary defense is to strictly follow the Checks-Effects-Interactions (CEI) pattern:

  • Checks: Validate all conditions and inputs (e.g., require(balance >= amount)).
  • Effects: Update all internal state variables before any external calls (e.g., balances[msg.sender] -= amount).
  • Interactions: Perform external calls last (e.g., msg.sender.call{value: amount}("")). This ensures state is finalized before interaction, preventing reentrancy.
03

Reentrancy Guard Modifier

A common technical mitigation is a reentrancy guard, a boolean lock that prevents a function from being called recursively. OpenZeppelin's ReentrancyGuard contract provides a nonReentrant modifier. When applied to a function, it sets a lock for the duration of execution, causing any reentrant call to revert. This is a robust solution but adds gas overhead and should not replace proper state management via CEI.

04

Single-Function vs. Cross-Function

Reentrancy attacks manifest in two primary forms:

  • Single-Function Reentrancy: The attacker reenters the same function they initially called (e.g., a withdraw function). This is the classic pattern.
  • Cross-Function Reentrancy: The attacker uses an external call to reenter a different function in the same contract that shares state with the vulnerable function. This can be more subtle and harder to guard against, as it bypasses single-function locks.
05

State Corruption & Logic Errors

Beyond direct fund theft, reentrancy can cause state corruption and business logic failures. An attacker might not drain ETH but could manipulate voting weights, mint unlimited tokens, or bypass access controls by exploiting intermediate state. For example, a function that calculates rewards based on a user's balance before updating it could be tricked into paying out inflated amounts repeatedly.

06

Historical Impact & Modern Context

The 2016 DAO attack, which exploited a reentrancy bug to drain 3.6 million ETH (worth ~$50M at the time), remains the most famous example and led to the Ethereum hard fork. While awareness and tools like CEI and guards have reduced simple attacks, reentrancy remains a top vulnerability in smart contract audits. Complex DeFi protocols with intricate callback mechanisms require diligent review to prevent novel reentrancy vectors.

ATTACK VECTORS

Call Stack vs. Classic Reentrancy

A comparison of two distinct smart contract reentrancy attack patterns, focusing on their mechanisms and the security measures they bypass.

FeatureClassic ReentrancyCall Stack Reentrancy

Primary Attack Vector

State update after external call

Nested call depth limit

Bypasses Checks-Effects-Interactions

Exploits EVM Call Stack Limit

Requires Malicious Fallback/Receive Function

Mitigated by Reentrancy Guard Modifier

Maximum Call Depth (Pre-EIP-150)

1024 frames

1024 frames

Classic Example

TheDAO (2016)

Governance proposal execution

mitigation-strategies
CALL STACK REENTRANCY

Mitigation & Prevention Strategies

Call stack reentrancy is a vulnerability where an external call can re-enter a function before its state changes are finalized, exploiting the order of operations. The primary defense is the Checks-Effects-Interactions pattern.

01

Checks-Effects-Interactions Pattern

The fundamental design pattern for preventing reentrancy. It mandates a strict execution order:

  • Checks: Validate all conditions and inputs (e.g., balances, permissions).
  • Effects: Perform all state updates (e.g., deducting balances, updating counters).
  • Interactions: Make external calls to other contracts or addresses only after all state is finalized. This ensures the contract is in a consistent state before any external interaction that could trigger a reentrant call.
02

Reentrancy Guard Modifier

A mutex (mutual exclusion) mechanism that locks a function during execution. A boolean state variable (e.g., locked) is set to true at the start of the function and false at the end. Any reentrant call will fail the initial check, preventing recursive execution. This is a robust, generalized solution, famously implemented in OpenZeppelin's ReentrancyGuard contract.

03

Pull Over Push Payments

Instead of a contract pushing funds to users (an external call that can be hijacked), users pull funds themselves. The contract updates an internal accounting balance (an effect) and provides a separate withdraw function. This separates the state-changing logic from the fund transfer, eliminating the reentrancy vector in the core logic. This pattern is also more gas-efficient for batch operations.

04

Gas Limit Considerations

Call stack depth limits and gas stipends for legacy send/transfer were historical mitigations. However, with the 63/64 rule for call depth and EIP-1884 increasing opcode costs, these are no longer reliable primary defenses. Relying on transfer() or send() (which forward a 2300 gas stipend) can lead to failures with gas-costly fallback functions. Use the CEI pattern or guards instead.

05

State Variable Sequencing

A specific failure mode occurs when state variables are updated in the wrong order. For example, updating a user's balance after making an external call creates a window for reentrancy. The attacker's fallback function can call back into the contract, and because their balance hasn't been deducted yet, they can drain funds. Always update critical state before any external call.

06

Formal Verification & Static Analysis

Proactive tools to detect potential vulnerabilities:

  • Static Analysis: Tools like Slither or MythX can automatically flag functions that make external calls after state changes or lack reentrancy guards.
  • Formal Verification: Using tools like Certora or modeling in TLA+ to mathematically prove that a contract's state machine cannot enter an invalid state, regardless of call order. These are essential for high-value protocols.
historical-examples
CASE STUDIES

Historical Context & Examples

Call stack reentrancy attacks are defined by their historical exploits, which fundamentally shaped smart contract security practices and led to the development of key defensive patterns.

03

Cross-Function Reentrancy

A more subtle variant where an attacker reenters a different function in the same contract that shares state. This bypasses simple single-function guards. Example: An attacker calls Function A, which makes an external call. The malicious contract's fallback then calls Function B, which relies on state that Function A hasn't finished updating. Defenses require careful application of the Checks-Effects-Interactions pattern across the entire contract's state model.

04

ERC-777 & ERC-20 Callback Reentrancy

Demonstrates how token standards can introduce new reentrancy vectors. ERC-777 tokens have tokensToSend and tokensReceived hooks that are called during transfers. Attack Vector: A contract receiving tokens could reenter the sender's contract via the hook before the sender's balance is updated. This led to incidents like the Uniswap/Lendf.Me hack in 2020, where an attacker used an ERC-777 token's hook to manipulate a lending market's internal accounting.

05

The Checks-Effects-Interactions Pattern

The foundational coding pattern to prevent reentrancy, mandated as a best practice post-DAO.

  1. Checks: Validate all conditions and inputs (e.g., require(balance[msg.sender] >= amount)).
  2. Effects: Update all internal state variables (e.g., balance[msg.sender] -= amount).
  3. Interactions: Perform external calls last (e.g., msg.sender.call{value: amount}("")). By performing interactions last, the contract's state is already in a consistent state, eliminating the reentrancy vulnerability.
06

Modern Tooling & Detection

Today, reentrancy is a primary target for automated security tools.

  • Static Analysis: Slither and MythX can detect common reentrancy patterns by analyzing control flow graphs.
  • Formal Verification: Tools like Certora and SMTChecker can mathematically prove the absence of reentrancy under specified conditions.
  • Fuzzing & Invariant Testing: Foundry and Echidna can simulate complex transaction sequences to uncover cross-function and cross-contract reentrancy paths that static analysis might miss.
CALL STACK REENTRANCY

Common Misconceptions

Clarifying persistent myths and misunderstandings about the mechanics and risks of call stack reentrancy in smart contract security.

Call stack reentrancy is a vulnerability where an external contract call allows the callee to re-enter the calling function before its state changes are finalized, exploiting the single-threaded nature of the EVM's call stack. It differs from classic storage reentrancy (like The DAO hack) in its attack vector: while storage reentancy exploits state changes after external calls (checks-effects-interactions pattern), call stack reentrancy exploits the depth limit of the EVM call stack (1024 frames). An attacker can make recursive calls to overflow the stack and cause the victim's transaction to revert, potentially leaving funds in a vulnerable intermediate state, without necessarily modifying storage variables mid-execution.

CALL STACK REENTRANCY

Frequently Asked Questions

Call stack reentrancy is a specific type of smart contract vulnerability where an attacker exploits the order of operations within a single transaction to drain funds or corrupt state. These questions address its mechanics, risks, and prevention.

Call stack reentrancy is a smart contract vulnerability where an attacker exploits the order of execution within a single transaction's call stack to manipulate contract state before a previous function call has completed its state updates. Unlike the classic reentrancy attack popularized by The DAO hack, which uses recursive external calls, this attack leverages how the Ethereum Virtual Machine (EVM) processes nested internal calls and the msg.sender context. The attacker's contract calls a vulnerable function, which then makes an internal call to another contract. Before the original function's state changes are finalized, this internal call can trigger a callback that re-enters the original contract through a different function, accessing inconsistent or outdated state variables to siphon funds.

ENQUIRY

Get In Touch
today.

Our experts will offer a free quote and a 30min call to discuss your project.

NDA Protected
24h Response
Directly to Engineering Team
10+
Protocols Shipped
$20M+
TVL Overall
NDA Protected direct pipeline