Platform: Code4rena
Start Date: 12/07/2022
Pot Size: $75,000 USDC
Total HM: 16
Participants: 100
Period: 7 days
Judge: LSDan
Total Solo HM: 7
Id: 145
League: ETH
Rank: 2/100
Findings: 6
Award: $11,088.16
🌟 Selected for report: 2
🚀 Solo Findings: 2
🌟 Selected for report: PwnedNoMore
3124.0979 USDC - $3,124.10
https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/wrapper/NameWrapper.sol#L657-L674 https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/wrapper/ERC1155Fuse.sol#L274-L284
HIGH - bypassing PARENT_CANNOT_CONTROL
fuse
As discussed in the discord, bypassing fuse is considered high, thus it is reported as high impact
Conditions for the parent for this exploit:
CANNOT_UNWRAP
fuse on the parent nodeCANNOT_CREATE_SUBDOMAIN
fuse on the parent nodeOr alternatively, the parent node is not wrapped in the first place
The proof of concept demonstrates how to bypass PARENT_CANNOT_CONTROL
fuse and set fuses for child's node.
ENSRegistry::setSubnodeOwner
for the child node and set the owner to zero addressNameWrapper::setSubnodeOwner
or NameWRapper::setSubnodeRecord
to change the fuse to zeroThis is similar to the issue NameWrapper: parent can change owner of subnode by minting
, but this proof of concept combines other issues for an alternative path to exploit. This exploit will work even when the mint of burn functions are checking the existing fuses.
By setting the childnode's owner in the ENS Registry, one can bypass the PARENT_CANNOT_CONTROL
fuse:
// https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/wrapper/NameWrapper.sol#L657-L674 // wrapper/NameWrapper.sol::canCallSetSubnodeOwner // both setSubnodeOwner and setSubnodeRecord are using this modifier // setting the owner in the ENS registry to zero will bypass `PARENT_CANNOT_CONTROL` fuse 657 modifier canCallSetSubnodeOwner(bytes32 node, bytes32 labelhash) { 658 bytes32 subnode = _makeNode(node, labelhash); 659 address owner = ens.owner(subnode); 660 661 if (owner == address(0)) { 662 (, uint32 fuses, ) = getData(uint256(node)); 663 if (fuses & CANNOT_CREATE_SUBDOMAIN != 0) { 664 revert OperationProhibited(node); 665 } 666 } else { 667 (, uint32 subnodeFuses, ) = getData(uint256(subnode)); 668 if (subnodeFuses & PARENT_CANNOT_CONTROL != 0) { 669 revert OperationProhibited(node); 670 } 671 } 672 673 _; 674 }
A bug in the ERC1155Fuse::_transfer
function was used. It will not revert even when the sender does not own the token, if it is sent to the current owner.
In the proof of concept, the parent does not own the ERC-1155 wrapper token of the child node, but the transfer function did not revert, since the token was sent to the current owner.
// https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/wrapper/ERC1155Fuse.sol#L274-L284 // wrapper/ERC1155Fuse.sol::_transfer // line 283 does not revert even when the sender does not own the token // if it is sent to the oldOwner 274 function _transfer( 275 address from, 276 address to, 277 uint256 id, 278 uint256 amount, 279 bytes memory data 280 ) internal { 281 (address oldOwner, uint32 fuses, uint64 expiry) = getData(id); 282 if (oldOwner == to) { 283 return; 284 }
harahat
The NameWrapper assumes when the address zero is the owner, the token was not minted. But in the ENS Registry one can check the owner to zero.
#0 - jefflau
2022-07-20T04:35:22Z
duplicate of #173
🌟 Selected for report: rajatbeladiya
Also found by: 0x29A, 0xNineDec, Amithuddar, Aussie_Battlers, Ch_301, Dravee, GimelSec, IllIllI, Jujic, Limbooo, RedOneN, Ruhum, TomJ, _Adam, __141345__, alan724, asutorufos, berndartmueller, c3phas, cccz, cryptphi, durianSausage, fatherOfBlocks, hake, hyh, pashov, scaraven, zzzitron
5.45 USDC - $5.45
Judge has assessed an item in Issue #182 as Medium risk. The relevant finding follows:
The transfer
function has a fixed gas stipend of 2300. If a contract as well as EOA can call the function, it is advised to use call
function instead of transfer
.
Here are transfer
function used in the ETHRegistrarController.sol
:
#0 - dmvt
2022-09-27T12:19:45Z
Duplicate of #133
🌟 Selected for report: zzzitron
3471.2198 USDC - $3,471.22
MED - the function of the protocol could be impacted
compare will return false answer without reverting when the inputs are not valid.
The compare
function is used for compareNames
. The names are supposed to be DNS wire format. If the strings are malformed, it is possible to give out-of-range offset
, len
, otheroffset
, and otherlen
. When it happens, the compare
will return some false values, without reverting, since the validity of offset
and len
are not checked.
// https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/dnssec-oracle/BytesUtils.sol#L44-L51 // dnssec-oracle/BytesUtils.sol::compare // The length of self and other are not enforced 44 function compare(bytes memory self, uint offset, uint len, bytes memory other, uint otheroffset, uint otherlen) internal pure returns (int) { 45 uint shortest = len; 46 if (otherlen < len) 47 shortest = otherlen; 48 49 uint selfptr; 50 uint otherptr;
none
Check whether the offset
, len
are within the length of self
, as well as for the other
.
#0 - makoto
2022-07-27T11:20:53Z
It's lacking enough test to prove the bug
#1 - Arachnid
2022-07-28T22:52:00Z
compareNames
does not sanity check the lengths passed in, so this is a legitimate bug.
937.2294 USDC - $937.23
https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/dnssec-oracle/RRUtils.sol#L266-L268 https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/dnssec-oracle/DNSSECImpl.sol#L126-L134
MED - the function of the protocol could be impacted
The serialNumberGte
will revert when one of the serial number is greater than the max value of int32.
// https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/dnssec-oracle/DNSSECImpl.sol#L126-L134 // dnssec-oracle/DNSSECImpl.sol::validateSignedSet 126 if(!RRUtils.serialNumberGte(rrset.expiration, uint32(now))) { 127 revert SignatureExpired(rrset.expiration, uint32(now)); 128 } 132 if(!RRUtils.serialNumberGte(uint32(now), rrset.inception)) { 133 revert SignatureNotValidYet(rrset.inception, uint32(now)); 134 }
The validateSignedSet
is using serialNumberGte
with timestamp values.
// https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/dnssec-oracle/RRUtils.sol#L266-L268 // dnssec-oracle/RRUtils.sol::serialNumberGte // serialNumberGte is used in DNSSECImple::validateSignedSet // the i1 and i2 are timestamps 266 function serialNumberGte(uint32 i1, uint32 i2) internal pure returns(bool) { 267 return int32(i1) - int32(i2) >= 0; 268 }
If one of the parameters of serialNumberGte
is greater than the max of the int32 while the other parameter is not, the difference between them are bigger than int32 and it causes overflow or underflow revert.
For example, RRUtils.serialNumberGte(2147483648, 1658130036)
will revert with (Arithmetic operation underflowed or overflowed outside of an unchecked block)
. The first parameter 2147483648
is about 16 years from now (Jan/19/2038), and the second parameter 1658130036
is Jul/18/2022. The timestamp can be an expiration, so if the expiration is set to be greater than roughly 2038, validateSignedSet
will always revert.
none
Use unchecked block
#0 - makoto
2022-07-27T11:00:41Z
apparently this is known as year 2038 problem https://en.wikipedia.org/wiki/Year_2038_problem
#1 - makoto
2022-07-27T11:30:54Z
#2 - dmvt
2022-08-04T23:14:17Z
Duplicate of #211
🌟 Selected for report: zzzitron
3471.2198 USDC - $3,471.22
MED - the function of the protocol could be impacted
The safeTransferFrom
does not comply with the ERC1155 standard when the token is sent to the old owner.
According to the EIP-1155 standard for the safeTransferFrom
:
MUST revert if balance of holder for token
_id
is lower than the_value
sent.
Let's say alice
does not hold any token of tokenId
, and bob
holds one token of tokenId
. Then alice tries to send one token of tokenId
to bob with safeTranferFrom(alice, bob, tokenId, 1, "")
. In this case, even though alice's balance (= 0) is lower than the amount (= 1) sent, the safeTransferFrom
will not revert. Thus, violating the EIP-1155 standard.
It can cause problems for other contracts using this token, since they assume the token was transferred if the safeTransferFrom
does not revert. However, in the example above, no token was actually transferred.
// https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/wrapper/ERC1155Fuse.sol#L274-L284 // wrapper/ERC1155Fuse.sol::_transfer // ERC1155Fuse::safeTransferFrom uses _transfer 274 function _transfer( 275 address from, 276 address to, 277 uint256 id, 278 uint256 amount, 279 bytes memory data 280 ) internal { 281 (address oldOwner, uint32 fuses, uint64 expiry) = getData(id); 282 if (oldOwner == to) { 283 return; 284 }
none
Revert even if the to
address already owns the token.
#0 - jefflau
2022-07-27T09:31:52Z
Recommend severity QA
#1 - dmvt
2022-08-04T23:11:51Z
I'm going to leave this as Medium. This issue could definitely impact other protocols and potentially cause a loss of funds given external factors.
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 8olidity, Aussie_Battlers, Bnke0x0, Ch_301, Critical, Deivitto, Dravee, ElKu, Funen, GimelSec, JC, JohnSmith, Lambda, MiloTruck, PwnedNoMore, ReyAdmirado, Rohan16, Rolezn, Ruhum, RustyRabbit, Sm4rty, TomJ, Waze, _Adam, __141345__, alan724, asutorufos, benbaessler, berndartmueller, bin2chen, brgltd, bulej93, c3phas, cRat1st0s, cryptonue, cryptphi, csanuragjain, delfin454000, dxdv, exd0tpy, fatherOfBlocks, gogo, hake, hyh, joestakey, kyteg, lcfr_eth, minhtrng, p_crypt0, pashov, pedr02b2, philogy, rajatbeladiya, rbserver, rishabh, robee, rokinot, sach1r0, sashik_eth, seyni, simon135, svskaushik, zuhaibmohd, zzzitron
78.9385 USDC - $78.94
Risk | Title |
---|---|
L00 | Usage of transfer to send eth |
L01 | Lack of zero address check |
L02 | Incomplete NatSpec |
The transfer
function has a fixed gas stipend of 2300. If a contract as well as EOA can call the function, it is advised to use call
function instead of transfer
.
Here are transfer
function used in the ETHRegistrarController.sol
:
Functions without checks on the zero address may cause loss of token
file | line | function | note | link |
---|---|---|---|---|
DNSSECImpl.sol | 108 | validateSignedSet | missing return value in NatSpec | https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/dnssec-oracle/DNSSECImpl.sol#L108-L109 |
DNSSECImpl.sol | 145 | validateRRs | missing return value in NatSpec | https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/dnssec-oracle/DNSSECImpl.sol#L145-L146 |
NameWrapper.sol | 114 | uri | missing param in NatSpec | https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/wrapper/NameWrapper.sol#L113-L114 |
ReverseRegistrar.sol | 73 | claimForAddr | missing param in NatSpec | https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/registry/ReverseRegistrar.sol#L72-L73 |