Skip to content
Back to skills

2597 Unbounded Return Data 7da19017

ASecurity

- Contract makes a low-level `.call()` to an untrusted or user-specified address - Solidity's automatic return data copying is used (default behavior up to at least v0.8.26) - No assembly-level restriction on `returndatacopy` size - The function is on a critical path where revert would lock funds (withdrawals, undelegation)

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmentrust

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2597-unbounded-return-data_7da19017 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2597 Unbounded Return Data 7da19017?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2597 Unbounded Return Data 7da19017
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2597-unbounded-return-data-7da19017/badge)](https://www.skillsdirectory.com/skills/tools-only-2597-unbounded-return-data-7da19017)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Unbounded Return Data

## Preconditions
- Contract makes a low-level `.call()` to an untrusted or user-specified address
- Solidity's automatic return data copying is used (default behavior up to at least v0.8.26)
- No assembly-level restriction on `returndatacopy` size
- The function is on a critical path where revert would lock funds (withdrawals, undelegation)

## Vulnerable Pattern
```solidity
function unstake(address callback) external {
    uint256 amount = stakes[msg.sender];
    stakes[msg.sender] = 0;

    // Solidity automatically copies ALL return data into memory
    // Attacker's callback contract returns megabytes of data
    // Memory expansion cost grows quadratically — causes out-of-gas
    (bool success,) = callback.call(
        abi.encodeWithSignature("onUnstake(uint256)", amount)
    );
    // Even with limited gas stipend, the return data copy
    // happens in the CALLER's gas context
    require(success, "callback failed");
}
```

## Detection Heuristics
1. Search for `.call(`, `.delegatecall(`, `.staticcall(` to addresses that could be attacker-controlled
2. Check if the return data is handled by Solidity's default (captured as `bytes memory` or discarded but still copied)
3. If the call target is untrusted and no assembly-level return data size limit is used, flag it
4. Check if the function is on a critical path — can a revert here lock user funds?
5. Look for callback patterns (delegation hooks, unstaking callbacks, flash loan callbacks) where the callee is attacker-controlled

## False Positives
- The call target is a trusted, known contract (not user-controlled)
- Assembly is used to limit `returndatacopy` to a bounded size (e.g., max 32 bytes)
- `ExcessivelySafeCall` or similar library is used for bounded return data
- The function doesn't revert on call failure (uses try/catch or ignores success)

## Remediation
- Use assembly to limit return data size instead of Solidity's automatic copy
- Use Nomad's `ExcessivelySafeCall` library for calls to untrusted addresses
- Bound `returndatacopy` to only the bytes you need (typically 0 or 32)
```solidity
// Assembly-bounded return data copy
function safeCall(address target, bytes memory data) internal returns (bool success) {
    assembly {
        success := call(gas(), target, 0, add(data, 0x20), mload(data), 0, 0)
        // Only copy up to 32 bytes of return data
        let rdsize := returndatasize()
        if gt(rdsize, 0) {
            if gt(rdsize, 32) { rdsize := 32 }
            returndatacopy(0, 0, rdsize)
        }
    }
}
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.