Mystery Box

First Flight #25
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

Incomplete Reward Removal in transferReward() Function Can Cause Array Gaps

Summary

The transferReward() function in the MysteryBox contract contains a bug that leaves gaps in the rewardsOwned array after transferring a reward. This is due to the use of the delete keyword, which only sets the array element to its default value instead of removing it. This behavior results in an array with default values at certain indexes, potentially causing issues during iteration and other operations on the array.

Vulnerability Details

The transferReward() function currently performs the following steps:

rewardsOwned[_to].push(rewardsOwned[msg.sender][_index]);
delete rewardsOwned[msg.sender][_index]; // Leaves a gap

Key Issue:

  • The delete operation sets the reward at _index to its default value, but it does not remove the element from the array. This creates a gap in the array where the deleted element still exists, but its values are set to defaults ("" for strings, 0 for numbers).

  • Arrays in Solidity remain the same size after the delete operation, which can lead to unexpected behavior when interacting with the array or iterating over it.

Example of an Exploit

Gaps in the Array Leading to Misuse:

Initial Setup: Alice has three rewards: Reward1, Reward2, and Reward3. She transfers Reward2 (at index 1) to Bob.

Execution: The contract uses the delete keyword to "remove" Reward2

Outcome: Alice's rewardsOwned array now looks like:

[
Reward1,
{ name: "", value: 0 }, // Default values left by delete
Reward3
]

NB: This array still contains three elements, but one of them is a default value, creating a gap. This may cause issues when other functions operate on the array, as they may assume that all elements are valid rewards.

Impact

  • Array integrity is compromised: Leaving default values in the array can cause issues with iteration, indexing, and general array handling, potentially leading to incorrect behavior in other parts of the contract.

  • Operational inefficiencies: The presence of "empty" elements in the array may result in wasted gas costs when iterating over the array, or bugs if the code assumes all array elements are valid rewards.

Tools Used

Manual Review

Recommendations

Replace the element at _index with the last element in the array, then remove the last element using .pop().

function transferReward(address _to, uint256 _index) public {
require(_index < rewardsOwned[msg.sender].length, "Invalid index");
rewardsOwned[_to].push(rewardsOwned[msg.sender][_index]);
// Replace the reward at _index with the last element and pop the last one
rewardsOwned[msg.sender][_index] = rewardsOwned[msg.sender][rewardsOwned[msg.sender].length - 1];
rewardsOwned[msg.sender].pop();
}
Updates

Appeal created

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

A user can poison the `rewardsOwned` of another user via `transferReward` of an empty reward index

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.