Platform: Code4rena
Start Date: 08/01/2024
Pot Size: $83,600 USDC
Total HM: 23
Participants: 116
Period: 10 days
Judge: 0xean
Total Solo HM: 1
Id: 317
League: ETH
Rank: 75/116
Findings: 3
Award: $27.29
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: JCN
Also found by: 0xDING99YA, 0xpiken, ABAIKUNANBAEV, AkshaySrivastav, Audinarey, Aymen0909, DanielArmstrong, J4X, Krace, KupiaSec, Qkite, Ward, evmboi32, fnanni, hash, juancito, kaden, krikolkk, ravikiranweb3, rbserver, rvierdiiev, serial-coder, trachev, zach, zzzitron
15.9479 USDC - $15.95
Under Signer, there is an instance of incorrect encoding of hashes, where there is a discrepancy between the string and how the hash is encoded.
The function _deriveRentalOrderHash returns a keccak256
hash of the order hashed with _RENTAL_ORDER_TYPEHASH. However, there is a difference between the _RENTAL_ORDER_TYPEHASH
string and the order that _deriveRentalOrderHash hashes.
return keccak256( abi.encode( _RENTAL_ORDER_TYPEHASH, order.seaportOrderHash, keccak256(abi.encodePacked(itemHashes)), keccak256(abi.encodePacked(hookHashes)), order.orderType, order.lender, order.renter, //@audit `address rentalWallet` missing here order.startTimestamp, order.endTimestamp ) );
_RENTAL_ORDER_TYPEHASH is derived from _deriveRentalTypehashes, where rentalOrderTypeHash combines rentalOrderTypeString with a few other strings. For now, we are interested in rentalOrderTypeString, which contains the following:
bytes memory rentalOrderTypeString = abi.encodePacked( "..., address renter, address rentalWallet, uint256 startTimestamp, uint256 endTimestamp)" );
From the string, we can conclude that there should be address renter
followed by address rentalWallet
and so on. However, in _deriveRentalOrderHash, after order.renter
, there is order.startTimestamp
, and address rentalWallet
is entirely skipped in this case.
This discrepancy can cause issues with generating signatures, as the system is guided by the string, while the contract misses some crucial parts of it.
Manual review.
To address this issue, add the rentalWallet
address to the missing order hash.
return keccak256( abi.encode( _RENTAL_ORDER_TYPEHASH, order.seaportOrderHash, keccak256(abi.encodePacked(itemHashes)), keccak256(abi.encodePacked(hookHashes)), order.orderType, order.lender, order.renter, + order.rentalWallet, order.startTimestamp, order.endTimestamp ) );
Error
#0 - c4-pre-sort
2024-01-21T17:56:12Z
141345 marked the issue as duplicate of #239
#1 - c4-judge
2024-01-28T21:06:00Z
0xean marked the issue as satisfactory
#2 - c4-judge
2024-01-30T11:34:05Z
0xean marked the issue as not a duplicate
#3 - c4-judge
2024-01-30T11:34:14Z
0xean marked the issue as duplicate of #385
#4 - c4-judge
2024-01-30T11:34:18Z
0xean marked the issue as partial-25
#5 - c4-judge
2024-01-30T14:24:44Z
0xean changed the severity to 3 (High Risk)
🌟 Selected for report: JCN
Also found by: 0xDING99YA, 0xpiken, ABAIKUNANBAEV, AkshaySrivastav, Audinarey, Aymen0909, DanielArmstrong, J4X, Krace, KupiaSec, Qkite, Ward, evmboi32, fnanni, hash, juancito, kaden, krikolkk, ravikiranweb3, rbserver, rvierdiiev, serial-coder, trachev, zach, zzzitron
15.9479 USDC - $15.95
_deriveRentalOrderHash fails to encode a crucial parameter - rentalWallet. This allows users to drain some safes while leaving NFTs stuck in others.
When users call stopRent, they enter their desired order. It passes a few checks, and the NFTs are sent to the lender (with _reclaimRentedItems), and the order is deleted (with removeRentals). When removing the order, removeRentals hashes it (using _deriveRentalOrderHash) and checks if it exists in the system.
However, the hash is incomplete, as it's missing one crucial part - rentalWallet
.
return keccak256( abi.encode( _RENTAL_ORDER_TYPEHASH, order.seaportOrderHash, keccak256(abi.encodePacked(itemHashes)), keccak256(abi.encodePacked(hookHashes)), order.orderType, order.lender, order.renter, //@audit missing rentalWallet order.startTimestamp, order.endTimestamp ) );
This means that we can change rentalWallet
(the address of the safe) value and still pass this existence check. This is where the exploit happens.
If there are two safes holding the same ERC1155 token with the same ID, we can swap their rentalWallet
addresses when ending one's order. This will, in turn, drain the rentalWallet
we used and remove the other order from the system, with its safe still full of the ERC1155s. This will cause the second safe to be bricked forever.
Example:
rentalWallet
(safe) address to Alice's rentalWallet
address.Gist - https://gist.github.com/0x3b33/7d928fc232596a2c6bb8d2770e92df5b
Place in - smart-contracts/test/integration/<name>.t.sol
Run with - forge test --match-test test_StopRentWithDifferentRentalWallet
Notice that we change 2 other contracts:
Manual review.
I would suggest including rentalWallet
in _deriveRentalOrderHash's hash.
Error
#0 - c4-pre-sort
2024-01-21T17:56:05Z
141345 marked the issue as duplicate of #239
#1 - c4-judge
2024-01-28T20:50:03Z
0xean changed the severity to 2 (Med Risk)
#2 - c4-judge
2024-01-28T21:05:55Z
0xean marked the issue as satisfactory
#3 - c4-judge
2024-01-30T11:31:50Z
0xean marked the issue as not a duplicate
#4 - c4-judge
2024-01-30T11:32:01Z
0xean marked the issue as duplicate of #385
#5 - c4-judge
2024-01-30T14:24:44Z
0xean changed the severity to 3 (High Risk)
🌟 Selected for report: JCN
Also found by: 0xDING99YA, 0xpiken, ABAIKUNANBAEV, AkshaySrivastav, Audinarey, Aymen0909, DanielArmstrong, J4X, Krace, KupiaSec, Qkite, Ward, evmboi32, fnanni, hash, juancito, kaden, krikolkk, ravikiranweb3, rbserver, rvierdiiev, serial-coder, trachev, zach, zzzitron
15.9479 USDC - $15.95
_deriveOrderMetadataHash is missing uint8 orderType
in its hash.
The orderMetadata
string contains a few parameters, and uint8 orderType
is one of them (orderMetadataTypeString -> orderMetadataTypeHash -> _ORDER_METADATA_TYPEHASH).
bytes memory orderMetadataTypeString = abi.encodePacked("OrderMetadata(uint8 orderType,uint256 rentDuration,Hook[] hooks,bytes emittedExtraData)");
However, when _deriveOrderMetadataHash creates the metadata hash, it misses including orderType
. This is not compliant with EIP721 and results in incorrect or incomplete hashes.
function _deriveOrderMetadataHash(OrderMetadata memory metadata) internal view returns (bytes32) { bytes32[] memory hookHashes = new bytes32[](metadata.hooks.length); for (uint256 i = 0; i < metadata.hooks.length; ++i) { // Hash the hook hookHashes[i] = _deriveHookHash(metadata.hooks[i]); } // Derive and return the metadata hash as specified by EIP-712. //@audit missing `orderType` return keccak256( abi.encode(_ORDER_METADATA_TYPEHASH, metadata.rentDuration, keccak256(abi.encodePacked(hookHashes))) ); }
Manual review.
Include orderType
in the hash.
- return keccak256(abi.encode(_ORDER_METADATA_TYPEHASH, metadata.rentDuration, keccak256(abi.encodePacked(hookHashes))) + return keccak256(abi.encode(_ORDER_METADATA_TYPEHASH, metadata.orderType, metadata.rentDuration, keccak256(abi.encodePacked(hookHashes))) );
Error
#0 - c4-pre-sort
2024-01-21T17:54:36Z
141345 marked the issue as duplicate of #239
#1 - c4-judge
2024-01-28T21:05:09Z
0xean marked the issue as satisfactory
#2 - c4-pre-sort
2024-02-02T09:09:38Z
141345 marked the issue as not a duplicate
#3 - c4-pre-sort
2024-02-02T09:09:53Z
141345 marked the issue as duplicate of #418
#4 - c4-judge
2024-02-02T11:28:28Z
0xean marked the issue as partial-50
#5 - c4-judge
2024-02-02T11:41:28Z
0xean changed the severity to 3 (High Risk)
🌟 Selected for report: LokiThe5th
Also found by: 0xAlix2, BI_security, Coverage, EV_om, Giorgio, KupiaSec, Qkite, SBSecurity, anshujalan, evmboi32, hals, juancito, krikolkk, oakcobalt, rbserver, rokinot, roleengineer, said, sin1st3r__, trachev, yashar
8.618 USDC - $8.62
The guard protects the safe owner from transferring the NFT away, however, it doesn't protect it from burn
.
Some NFTs implement the burn extension or have burn as an external function (even the Mock721 has it). There is nothing stopping the renter from griefing the lender by simply burning the NFT. While this will not yield any profit (unless they are burning to mint another NFT), it allows them to cause damage to the lender.
Example:
Bob cannot stop the order, and he can't even get the ERC20 that he rented the NFT for. This is because the NFT is burned, and safeTransferFrom will revert.
Gist - https://gist.github.com/0x3b33/e805f909c6262f1e60274081be305685
Place in - smart-contracts/test/integration/<name>.sol
Run with - forge test --match-test test_burnNFT -vv
Manual review.
Add burn as another one of the selectors that the guard will check for.
if (selector == shared_set_approval_for_all_selector) { revert Errors.GuardPolicy_UnauthorizedSelector(...); } + if (selector == e721_burn_selector){ + revert Errors.GuardPolicy_UnauthorizedSelector(e721_burn_selector); + } ...
Error
#0 - c4-pre-sort
2024-01-21T17:39:40Z
141345 marked the issue as duplicate of #323
#1 - c4-judge
2024-01-28T20:06:40Z
0xean marked the issue as satisfactory
#2 - c4-judge
2024-01-28T20:48:45Z
0xean changed the severity to 2 (Med Risk)
🌟 Selected for report: stackachu
Also found by: 0xHelium, 0xabhay, 0xc695, 0xpiken, DeFiHackLabs, EV_om, HSP, J4X, Krace, KupiaSec, Qkite, ZanyBonzy, albertwh1te, cccz, evmboi32, hals, hash, holydevoti0n, krikolkk, ladboy233, lanrebayode77, marqymarq10, oakcobalt, peanuts, peter, rbserver, said, serial-coder, sin1st3r__
2.7205 USDC - $2.72
In pay orders, the lender pays the renter ERC20s to use their NFT. However, if the renter gets blacklisted for this ERC20, the NFT will remain stuck in the safe.
The stopRent function is called when an order is finished. It returns the NFTs and sends the payment to the renter (when a pay order is involved). However, if the renter somehow gets blacklisted from the payment token (USDC, USDT, etc.), the payment cannot be made. Since stopRent uses push over pull (via settlePayment -> _settlePayment -> _settlePaymentInFull), the entire transaction will revert. This will cause the NFT to remain in the safe.
Example:
Here, Bob doesn't gain anything, but he causes damage to the lender, leading to the loss of her 3 NFTs.
Manual review.
I recommend using pull over push. In this case, stopRent should, instead of directly sending tokens, enable users to withdraw them from the escrow.
DoS
#0 - c4-pre-sort
2024-01-21T17:36:43Z
141345 marked the issue as duplicate of #64
#1 - c4-judge
2024-01-28T21:01:40Z
0xean marked the issue as satisfactory