- Contract makes a low-level `.call()` to an untrusted or user-specified address
.call() to an untrusted or user-specified addressreturndatacopy sizefunction 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");
}
.call(, .delegatecall(, .staticcall( to addresses that could be attacker-controlledbytes memory or discarded but still copied)returndatacopy to a bounded size (e.g., max 32 bytes)ExcessivelySafeCall or similar library is used for bounded return dataExcessivelySafeCall library for calls to untrusted addressesreturndatacopy to only the bytes you need (typically 0 or 32)// 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)
}
}
}