Attractive Latte Cricket
Medium
In the initialize
function, the contract approves the fiat
token for use in Compound V3 without resetting or validating previous approvals. This unchecked approval can lead to potential double-spending attacks if an attacker manipulates approvals externally before the contract re-initializes.
The vulnerability stems from the initialize()
function found at CompoundV3FiatReserve.sol#L33. In this function, the fiat.approve(address(compound))
call is made without resetting or verifying the current approval status. This lack of proper handling for token allowances can lead to reinitialization issues, allowing double approvals or manipulations if the contract state is tampered with.
- Admin (or an authorized user) needs to call
initialize()
function to set thefiat.approve(address(compound))
allowance without resetting the previous allowance to a new value. - The
compound
contract address must remain the same between approvals, with no changes that would cause invalidation of prior allowances. - There needs to be prior allowance set for
fiat
that could potentially be manipulated if not reset before callinginitialize()
. - The contract state must be initialized at least once (i.e.,
initializer(2)
allows only one-time initialization), allowing the call to happen under the assumption the previous state is valid and unchanged.
- The Compound V3 market contract (external protocol) needs to remain the same after the
fiat.approve()
call in theinitialize()
function, meaning no changes occur in the contract address between transactions. - The fiat token (external asset) must retain its previous allowance for the
compound
contract, so the unreset allowance can be utilized. - The Compound V3 contract needs to continue operating without introducing a method or requirement for resetting allowances or providing security against excessive approvals.
- The Compound market or fiat token’s balance conditions must stay unchanged, allowing the existing approval setup to persist without immediate interference.
- The attacker obtains a role that interacts with the contract.
- The attacker calls the
initialize()
function after having manipulated the approval externally. - This allows the attacker to increase their approved token balance and withdraw funds unjustly.
The issue can result in a double-spend scenario where the attacker gains an unfair advantage by modifying approval levels before the contract re-initializes. This creates a risk of unauthorized transfers and loss of funds for the contract and its users. Given that this contract manages fiat tokens through Compound V3, such a vulnerability could lead to significant financial losses.
- The attacker gains access to a contract interaction and manipulates the approval for the
fiat
token externally. - The contract re-initializes by calling the
initialize()
function, which does not validate prior approvals, allowing the attacker to exploit the modified approval. - This leads to an unauthorized increase in approved tokens, enabling the attacker to double spend or withdraw more tokens than intended.
function initialize() public virtual initializer(2) {
__ReserveBase__initialize();
// No check for previous approval values, leading to potential double spend exploit
fiat.approve(address(compound));
}
In this situation, the contract does not clear or verify previous approval settings, leaving room for manipulation.
To prevent this vulnerability, reset previous approvals to zero before re-initializing them. This will ensure that the contract is secure against potential double spend attacks.
function initialize() public virtual initializer(2) {
__ReserveBase__initialize();
// Reset previous approval before setting a new approval value
fiat.approve(address(compound), 0);
fiat.approve(address(compound), type(uint256).max); // Set a specific maximum approval value
}