The xRenzoBridge::sendPrice() function has two instances where the return values of external function calls are ignored:
1- linkToken.approve(address(linkRouterClient), fees): This call approves the linkRouterClient contract to spend LINK tokens on behalf of the xRenzoBridge contract. Ignoring the return value of this call could lead to a situation where the approval fails, but the function continues to execute, potentially leading to issues with the subsequent CCIP message sending.
2- connext.xcall{ value: _connextDestinationParam[i].relayerFee }(...): This call sends a message to the Connext contract, which could potentially fail. Ignoring the return value means that the function will continue executing even if the message sending fails, potentially leading to inconsistent or unexpected behavior.
The main impact of ignoring these return values is the potential for inconsistent or unexpected behavior in the protocol. If the token approval or the Connext message sending fails, the function will continue executing, which could lead to various issues, such as:
Inability to send the price feed update to the intended destination, resulting in stale price data being used by the protocol.
Potential loss of funds if the Connext message sending fails, as the relayer fee would still be deducted from the contract's balance.
Proof of Concept
Bob observes the protocol's price feed update process, where the xRenzoBridge::sendPrice() function is called to send the current ezETH/ETH exchange rate to the Connext and CCIP receivers.
Bob identifies that the function ignores the return values of the linkToken.approve() and connext.xcall() calls.
Bob decides to take advantage of this vulnerability by performing the following attack:
Bob approves the linkRouterClient contract to spend a small amount of LINK tokens on the xRenzoBridge contract's behalf, but he intentionally reverts the transaction.
Bob then calls the xRenzoBridge::sendPrice() function, which will ignore the failed linkToken.approve() call and proceed to send the price feed update.
However, since the approval failed, the subsequent call to linkRouterClient.ccipSend() will also fail, causing the price feed update to be delivered incompletely or not at all.
As a result, the protocol will start using stale price data, potentially leading to incorrect token valuations and impacting user interactions.
Tools Used
Manual review
Recommended Mitigation Steps
xRenzoBridge::sendPrice() function should properly handle the return values of these external function calls. This can be done by either:
Storing the return values in local variables and adding appropriate error handling logic.
Using the OpenZeppelin's SafeERC20 library to make the token approval call, which will revert on failure.
Lines of code
https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/Bridge/L1/xRenzoBridge.sol?plain=1#L241 https://github.com/code-423n4/2024-04-renzo/blob/main/contracts/Bridge/L1/xRenzoBridge.sol?plain=1#L265
Vulnerability details
Impact
The
xRenzoBridge::sendPrice()
function has two instances where the return values of external function calls are ignored:1-
linkToken.approve(address(linkRouterClient), fees)
: This call approves thelinkRouterClient
contract to spend LINK tokens on behalf of thexRenzoBridge
contract. Ignoring the return value of this call could lead to a situation where the approval fails, but the function continues to execute, potentially leading to issues with the subsequent CCIP message sending.2-
connext.xcall{ value: _connextDestinationParam[i].relayerFee }(...)
: This call sends a message to theConnext
contract, which could potentially fail. Ignoring the return value means that the function will continue executing even if the message sending fails, potentially leading to inconsistent or unexpected behavior.The main impact of ignoring these return values is the potential for inconsistent or unexpected behavior in the protocol. If the token approval or the Connext message sending fails, the function will continue executing, which could lead to various issues, such as:
Inability to send the price feed update to the intended destination, resulting in stale price data being used by the protocol.
Potential loss of funds if the Connext message sending fails, as the relayer fee would still be deducted from the contract's balance.
Proof of Concept
Bob observes the protocol's price feed update process, where the
xRenzoBridge::sendPrice()
function is called to send the current ezETH/ETH exchange rate to the Connext and CCIP receivers.Bob identifies that the function ignores the return values of the
linkToken.approve()
andconnext.xcall()
calls.Bob decides to take advantage of this vulnerability by performing the following attack:
Bob approves the
linkRouterClient
contract to spend a small amount of LINK tokens on thexRenzoBridge
contract's behalf, but he intentionally reverts the transaction. Bob then calls thexRenzoBridge::sendPrice()
function, which will ignore the failedlinkToken.approve()
call and proceed to send the price feed update.However, since the approval failed, the subsequent call to linkRouterClient.ccipSend() will also fail, causing the price feed update to be delivered incompletely or not at all.
As a result, the protocol will start using stale price data, potentially leading to incorrect token valuations and impacting user interactions.
Tools Used
Manual review
Recommended Mitigation Steps
xRenzoBridge::sendPrice()
function should properly handle the return values of these external function calls. This can be done by either:Storing the return values in local variables and adding appropriate error handling logic. Using the
OpenZeppelin's SafeERC20
library to make the token approval call, which will revert on failure.Assessed type
Other