msg.value in Loop
Detects msg.value used inside loops where the same ETH value is counted multiple times, enabling fund theft or accounting errors.
msg.value in Loop
Overview
The msg.value-in-loop detector identifies contracts that reference msg.value inside a loop body. Since msg.value is constant throughout a transaction, using it inside a loop that processes multiple items means the same ETH amount is credited to each iteration. An attacker sending 1 ETH can have it counted N times if the loop runs N iterations, effectively multiplying their deposit.
Why This Is an Issue
This is a straightforward accounting flaw. If a batch deposit function loops over recipients and credits each with msg.value, the contract believes it received N times the actual ETH sent. The attacker deposits once but receives credit for N deposits, allowing them to withdraw far more than they sent. This pattern has appeared in multi-call contracts, batch minting functions, and aggregator protocols.
How to Resolve
// Before: Vulnerable — msg.value counted per iteration
function batchDeposit(address[] calldata recipients) external payable {
for (uint i = 0; i < recipients.length; i++) {
deposits[recipients[i]] += msg.value; // Same msg.value each iteration
}
}
// After: Fixed — divide msg.value or use explicit amounts
function batchDeposit(address[] calldata recipients, uint256[] calldata amounts) external payable {
uint256 total = 0;
for (uint i = 0; i < recipients.length; i++) {
deposits[recipients[i]] += amounts[i];
total += amounts[i];
}
require(total == msg.value, "Amount mismatch");
}
Detection Methodology
- Loop identification: Detects loop structures (back-edges in the CFG) in the contract bytecode.
- CALLVALUE in loop body: Checks whether the CALLVALUE opcode (which reads
msg.value) appears within loop-body blocks. - Accumulation pattern: Flags cases where the CALLVALUE result feeds into an addition or storage write that executes on each iteration.
- Single-iteration filter: Loops that provably execute exactly once (e.g., length-1 arrays) are filtered out.
Limitations
False positives: Loops that read msg.value for comparison (e.g., require(msg.value >= minPrice)) without accumulating it are safe but may be flagged. False negatives: Indirect references to msg.value through local variables assigned before the loop are not traced into the loop body.
Related Detectors
- Integer Overflow — detects arithmetic overflow broadly
- Business Logic Error — detects logic flaws in token accounting