Open sherlock-admin2 opened 4 months ago
Escalate I believe this issue has been wrongly marked as a duplicate of #111 .
The vulnerability detailed in this issue is not related to the issue of passing a wrong parameter as the source chain sender when the _internalRemoteTransferSendPacket()
function is called. The overall root cause for the vulnerability described in #111 is actually different from the issue described in this report.
The problem with the vulnerability reported in this issue is that address(this)
is hardcoded as the source chain sender for the next compose call if the length of the next message appended is > 0:
// TapiocaOmnichainReceiver.sol
...
if (nextMsg_.length > 0) {
_lzCompose(address(this), _guid, nextMsg_); // <---- `address(this)` is wrong
}
This will make the next compose call have address(this)
(the USDO contract address) as the source chain sender for the next call. As seen in this issue comment, the fix proposed for #111 changes the source chain sender from remoteTransferMsg_.owner
to _srcChainSender
.
Although this fix mitigates the possibility of draining any account that is passed as the remoteTransferMsg_.owner
parameter (which is the root cause that allows #111 and all its duplicates to take place), the issue described in this report is still possible because the USDO contract will be passed as the srcChainSender
in the compose call, which enables malicious actors to execute remote transfers as if they were USDO.
As shown in my PoC, an attacker can then burn all USDO fees held in the USDO contract on chain B, and transfer them to an arbitrary address in chain A, effectively stealing all fees sitting in the USDO contract.
Escalate I believe this issue has been wrongly marked as a duplicate of #111 .
The vulnerability detailed in this issue is not related to the issue of passing a wrong parameter as the source chain sender when the
_internalRemoteTransferSendPacket()
function is called. The overall root cause for the vulnerability described in #111 is actually different from the issue described in this report.The problem with the vulnerability reported in this issue is that
address(this)
is hardcoded as the source chain sender for the next compose call if the length of the next message appended is > 0:// TapiocaOmnichainReceiver.sol ... if (nextMsg_.length > 0) { _lzCompose(address(this), _guid, nextMsg_); // <---- `address(this)` is wrong }
This will make the next compose call have
address(this)
(the USDO contract address) as the source chain sender for the next call. As seen in this issue comment, the fix proposed for #111 changes the source chain sender fromremoteTransferMsg_.owner
to_srcChainSender
.Although this fix mitigates the possibility of draining any account that is passed as the
remoteTransferMsg_.owner
parameter (which is the root cause that allows #111 and all its duplicates to take place), the issue described in this report is still possible because the USDO contract will be passed as thesrcChainSender
in the compose call, which enables malicious actors to execute remote transfers as if they were USDO.As shown in my PoC, an attacker can then burn all USDO fees held in the USDO contract on chain B, and transfer them to an arbitrary address in chain A, effectively stealing all fees sitting in the USDO contract.
You've created a valid escalation!
To remove the escalation from consideration: Delete your comment.
You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.
This seems like a duplicate of #135, will need to review further. They are all very similar to each other.
I agree with the escalations and @nevillehuang comment. We can deduplicate from #111 and duplicate with #135.
Planning to accept the escalation and remove the duplication with #111, but duplicate with #135.
Result: High Has Duplicates
0xadrii
high
Recursive _lzCompose() call can be leveraged to steal all generated USDO fees
Summary
It is possible to steal all generated USDO fees by leveraging the recursive _lzCompose() call triggered in compose calls.
Vulnerability Detail
The
USDOFlashloanHelper
contract allows users to take USDO flash loans. When a user takes a flash loan some fees will be enforced and transferred to the USDO contract:Such fees can be later retrieved by the owner of the USDO contract via the
extractFees()
function:However, such fees can be stolen by an attacker by leveraging a wrong parameter set when performing a compose call.
When a compose call is triggered, the internal
_lzCompose()
call will be triggered. This call will check themsgType_
and execute some logic according to the type of message requested. After executing the corresponding logic, it will be checked if there is an additional message by checking thenextMsg_.length
. If the compose call had a next message to be called, a recursive call will be triggered and_lzCompose()
will be called again:As we can see in the code snippet’s last line, if
nextMsg_.length > 0
an additional compose call can be triggered . The problem with this call is that the first parameter in the_lzCompose()
call is hardcoded to beaddress(this)
(address of USDO), making thesrcChainSender_
become the USDO address in the recursive compose call.An attacker can then leverage the remote transfer logic in order to steal all the USDO tokens held in the USDO contract (mainly fees generated by flash loans).
Forcing the recursive call to be a remote transfer,
_remoteTransferReceiver()
will be called. Because the source chain sender in the recursive call is the USDO contract, theowner
parameter in the remote transfer (the address from which the remote transfer tokens are burnt) can also be set to the USDO address, making the allowance check in the_internalTransferWithAllowance()
call be bypassed, and effectively burning a desired amount from USDO.After burning the tokens from USDO, the remote transfer will trigger a call to a destination chain to mint the burnt tokens in the origin chain. The receiver of the tokens can be different from the address whose tokens were burnt, so an attacker can obtain the minted tokens in the destination chain, effectively stealing all USDO balance from the USDO contract.
An example attack path would be:
nextMsg
), which is the actual compose message that will trigger the remote transfer and burn the tokens in chain B, and finally trigger a call back to chain A to mint he tokensProof of concept
The following proof of concept illustrates how the mentioned attack can take place. In order to execute the PoC, the following steps must be performed:
EnpointMock.sol
file inside thetest
folder insideTapioca-bar
and paste the following code (the current tests are too complex, this imitates LZ’s endpoint contracts and reduces the poc’s complexity):Usdo.t.sol
fileUsdo.sol
’s implementation so that the endpoint variable is not immutable and add asetEndpoint()
function so that the endpoint configured insetUp()
can be chainged to the newly deployed endpointsUsdo.t.sol
:Run the poc with the following command:
forge test --mt testVuln_USDOBorrowFeesCanBeDrained
The proof of concept shows how in the end, USDO’s
bUsdo
balance will become 0, while the same amount ofaUsdo
in chain A will be minted to the attacker.Impact
High, all fees generated by the USDO contract can be effectively stolen by the attacker
Code Snippet
https://github.com/sherlock-audit/2024-02-tapioca/blob/main/Tapioca-bar/gitmodule/tapioca-periph/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L182
Tool used
Manual Review, foundry
Recommendation
Ensure that the
_lzCompose()
call triggered when a_nextMsg
exists keeps a consistent source chain sender address, instead of hardcoding it toaddress(this)
: