Modifying Storage Array
id: LS11M
title: Modifying Storage Array
baseSeverity: M
category: storage
language: solidity
blockchain: [ethereum]
impact: State desynchronization, logic failure, or ineffective updates
status: draft
complexity: low
attack_vector: internal
mitigation_difficulty: easy
versions: [">=0.4.0", "<=0.8.25"]
cwe: CWE-710
swc: SWC-109
๐ Description
- In Solidity, accessing a storage array element by value rather than by reference leads to silent bugs where updates do not persist in storage. When developers write:
- The modification applies only to a temporary in-memory copyโnot to the storage-backed array.
- This leads to logic errors, where updates appear successful during execution but vanish once the transaction completes.
- The bug is common when modifying structs within storage arrays.
๐จ Vulnerable Code
pragma solidity ^0.8.0;
contract UserRegistry {
struct User {
uint256 balance;
bool active;
}
User[] public users;
function updateFirstUser(uint256 newBalance) external {
User memory user = users[0]; // โ Copy by value
user.balance = newBalance; // Change lost after function ends
}
function addUser() external {
users.push(User(0, true));
}
}
๐งช Exploit Scenario
- Contract stores a list of User structs in a dynamic array.
- A developer calls users[0] into memory and modifies its fields.
- The state change is expected to persist but does not.
- Other contract logic assumes the user was updated, resulting in incorrect access control, incorrect balances, or broken reward logic.
Assumptions:
- Developers mistakenly believe user = users[0] modifies storage.
- No state update verification or post-condition checks exist.
- Function logic proceeds under false assumptions of success.
โ Fixed Code
pragma solidity ^0.8.0;
contract SafeUserRegistry {
struct User {
uint256 balance;
bool active;
}
User[] public users;
function updateFirstUser(uint256 newBalance) external {
User storage user = users[0]; // โ
Reference storage directly
user.balance = newBalance;
}
function addUser() external {
users.push(User(0, true));
}
}
๐งญ Contextual Severity
- context: "Default"
severity: M
reasoning: "Moderate risk of functional inconsistency or wasted gas."
- context: "Voting or reward logic relying on array indexing"
severity: H
reasoning: "Critical impact on system behavior due to miscounting or skipped users."
- context: "One-off configuration array"
severity: L
reasoning: "Low risk since admin-only functions and known array sizes are involved."
๐ก๏ธ Prevention
Primary Defenses
- Always use the storage keyword when modifying array elements.
- Be cautious when assigning structs or arrays from storage.
Additional Safeguards
- Use comments or naming conventions to distinguish memory vs storage.
- Use internal setter functions to encapsulate safe mutation logic.
- Add events or post-assertions to confirm updates.
Detection Methods
- Check for struct assignments like T memory var = storageArray[i].
- Detect updates to memory variables that are never written back.
- Tools: Slither (uninitialized-storage, useless-assignment), MythX, manual audit
๐ฐ๏ธ Historical Exploits
- Name: Storage Array Mismanagement in Early DeFi Contracts
- Date: 2020
- Loss: Logic errors leading to incorrect state updates
- Post-mortem: Link to post-mortem
๐ Further Reading
- SWC-109: Uninitialized Storage Pointer
- Solidity Docs โ Storage and Memory
- Solidity Documentation: Data Location
โ Vulnerability Report
id: LS11M
title: Modifying Storage Array
severity: M
score:
impact: 3
exploitability: 2
reachability: 4
complexity: 2
detectability: 3
finalScore: 3.0
๐ Justifications & Analysis
- Impact: Leads to state inconsistencies or logic failure; may void critical updates.
- Exploitability: Not directly user-exploitable, but easy to introduce via dev error.
- Reachability: Often occurs in staking, voting, and reward contracts using arrays.
- Complexity: Low; mistake arises from misunderstanding memory vs storage behavior.
- Detectability: Easy to overlook; updates seem correct unless verified with state checks or events.