NestedFactory
does not track operators properly
#38
Labels
2 (Med Risk)
Assets not at direct risk, but function/availability of the protocol could be impacted or leak value
bug
Something isn't working
sponsor confirmed
Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
Lines of code
https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/NestedFactory.sol#L99-L108
https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/abstracts/MixinOperatorResolver.sol#L30-L47
https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/NestedFactory.sol#L110-L122
https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/abstracts/MixinOperatorResolver.sol#L49-L55
Vulnerability details
NestedFactory
extends theMixinOperatorResolver
contract which comes from thesynthetix/MixinResolver.sol
code base where the expectation is thatisResolverCached()
returns false untilrebuildCache()
is called and the cache is fully up to date. Due to a medium issue identified in a prior contest, theOperatorResolver.importOperators()
step was made to be atomically combined with theNestedFactory.rebuildCache()
step. However, the atomicity was not applied everywhere and the ability to add/remove operators from theNestedFactory
also had other cache-inconsistency issues. There are four separate instances of operator tracking problems in this submission.Impact
As with the prior issue, many core operations (such as
NestedFactory.create()
andNestedFactory.swapTokenForTokens()
) are dependant on the assumption that theoperatorCache
cache is synced prior to these functions being executed, but this may not necessarily be the case. Unlike the prior issue which was about updates to the resolver not getting reflected in the cache, this issue is about changes to the factory not updating the cache.Proof of Concept
1.
removeOperator()
does not callrebuildCache()
NestedFactory.removeOperator()
is called to remove an operatorNestedFactory(MixinOperatorResolver).create()
using that operator and succeedesNestedFactory.rebuildCache()
is called to rebuild cacheThis flow is not aware that the cache is not in sync
https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/NestedFactory.sol#L99-L108
2. Using both
removeOperator()
andrebuildCache()
does not preventcreate()
from using the operatorEven if
removeOperator()
callsrebuildCache()
the function will still not work becauseresolverOperatorsRequired()
only keeps track of remaining operators, andrebuildCache()
currently has no way of knowing that an entry was removed from that array and that a corresponding entry fromoperatorCache
needs to be removed too.https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/abstracts/MixinOperatorResolver.sol#L30-L47
3.
addOperator()
does not callrebuildCache()
NestedFactory.addOperator()
is called to add an operatorNestedFactory(MixinOperatorResolver).create()
using that operator and fails because the operator wasn't in theresolverOperatorsRequired()
during the last call torebuildCaches()
, so the operator isn't inoperatorCache
NestedFactory.rebuildCache()
is called to rebuild cacheThis flow is not aware that the cache is not in sync
https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/NestedFactory.sol#L110-L122
4.
isResolverCached()
does not reflect the actual updated-or-not stateThis function, like
removeOperator()
is not able to tell that there is an operator that needs to be removed fromresolverCache
, causing the owner not to know a call torebuildCache()
is required to 'remove' the operatorhttps://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/abstracts/MixinOperatorResolver.sol#L49-L55
Tools Used
Code inspection
Recommended Mitigation Steps
Add calls to
rebuildCache()
inaddOperator()
andremoveOperator()
, haveINestedFactory
also track operators that have been removed with a new array, and haveisResolverCached()
also check whether this new array is empty or not.The text was updated successfully, but these errors were encountered: