exerciseOptionsReceiver() Lack of Ownership Check for oTAP, Allowing Anyone to Use oTAPTokenID
Summary
In UsdoOptionReceiverModule.exerciseOptionsReceiver():
For this method to execute successfully, the owner of the oTAPTokenID needs to approve it to address(usdo).
Once approved, anyone can front-run execute exerciseOptionsReceiver() and utilize this authorization.
Vulnerability Detail
In USDO.lzCompose(), it is possible to specify _msgType == MSG_TAP_EXERCISE to execute USDO.exerciseOptionsReceiver() across chains.
function exerciseOptionsReceiver(address srcChainSender, bytes memory _data) public payable {
...
ITapiocaOptionBroker(_options.target).exerciseOption(
@> _options.oTAPTokenID,
address(this), //payment token
_options.tapAmount
);
_approve(address(this), address(pearlmit), 0);
uint256 bAfter = balanceOf(address(this));
// Refund if less was used.
if (bBefore > bAfter) {
uint256 diff = bBefore - bAfter;
if (diff < _options.paymentTokenAmount) {
IERC20(address(this)).safeTransfer(_options.from, _options.paymentTokenAmount - diff);
}
}
...
For this method to succeed, USDO must first obtain approve for the oTAPTokenID.
The signature of oTAP.permit is public, allowing anyone to use it.
Note: if alice call approve(oTAPTokenID,usdo) in chain B without signature, but The same result
This opens up the possibility for malicious users to front-run use this signature.
Let's consider an example with Bob:
Bob in Chain A uses Alice's signature (v, r, s):
composeMsg = [oTAP.permit(usdo, oTAPTokenID, v, r, s), exerciseOptionsReceiver(oTAPTokenID, _options.from=bob)]-----> (Note: _options.from should be set to Bob.)
In Chain B, when executing USDO.lzCompose(dstEid = B), the following actions occur:
bin2chen
high
exerciseOptionsReceiver() Lack of Ownership Check for oTAP, Allowing Anyone to Use oTAPTokenID
Summary
In
UsdoOptionReceiverModule.exerciseOptionsReceiver()
: For this method to execute successfully, theowner
of theoTAPTokenID
needs to approve it toaddress(usdo)
. Once approved, anyone can front-run executeexerciseOptionsReceiver()
and utilize this authorization.Vulnerability Detail
In
USDO.lzCompose()
, it is possible to specify_msgType == MSG_TAP_EXERCISE
to executeUSDO.exerciseOptionsReceiver()
across chains.For this method to succeed, USDO must first obtain approve for the
oTAPTokenID
.Example: The owner of
oTAPTokenID
is Alice.The signature of
oTAP.permit
is public, allowing anyone to use it.This opens up the possibility for malicious users to front-run use this signature. Let's consider an example with Bob:
composeMsg = [oTAP.permit(usdo, oTAPTokenID, v, r, s), exerciseOptionsReceiver(oTAPTokenID, _options.from=bob)]
-----> (Note:_options.from
should be set to Bob.)USDO.lzCompose(dstEid = B)
, the following actions occur:oTAP.permit(usdo, oTAPTokenID)
exerciseOptionsReceiver(srcChainSender=bob, _options.from=bob, oTAPTokenID)
As a result, Bob gains unconditional access to this
oTAPTokenID
.It is advisable to check the ownership of
oTAPTokenID
is_options.from
before executingITapiocaOptionBroker(_options.target).exerciseOption()
.Impact
The
exerciseOptionsReceiver()
function lacks ownership checks foroTAP
, allowing anyone to useoTAPTokenID
.Code Snippet
https://github.com/sherlock-audit/2024-02-tapioca/blob/main/Tapioca-bar/contracts/usdo/modules/UsdoOptionReceiverModule.sol#L67
Tool used
Manual Review
Recommendation
add check
_options.from
is owner or be approved