Unlock Protocol contest - kenzo's results

Protocol for memberships built on a blockchain, where fans can support their favourite creators by purchasing their NFTs!

General Information

Platform: Code4rena

Start Date: 18/11/2021

Pot Size: $50,000 USDC

Total HM: 18

Participants: 26

Period: 7 days

Judge: leastwood

Total Solo HM: 12

Id: 54

League: ETH

Unlock Protocol

Findings Distribution

Researcher Performance

Rank: 4/26

Findings: 8

Award: $4,980.49

🌟 Selected for report: 6

🚀 Solo Findings: 2

Findings Information

🌟 Selected for report: pauliax

Also found by: 0x0x0x, GiveMeTestEther, Reigada, Ruhum, WatchPug, cmichel, kenzo

Labels

bug
duplicate
1 (Low Risk)

Awards

71.4681 USDC - $71.47

External Links

Handle

kenzo

Vulnerability details

Not all ERC20 tokens revert on failed transfer. This is why using SafeERC20 is recommended. purchase does not use it.

Impact

If a key is using as payment an ERC20 token that does not throw on failed transfer, a user will be able to buy a key without paying, and even later claiming a refund, thereby stealing other users funds.

Proof of Concept

purchase method uses regular transferFrom: token.transferFrom(msg.sender, address(this), pricePaid); https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinPurchase.sol#L97 (Although IERC20Upgradeable is already using SafeERC20Upgradeable)

Change the line to: token.safeTransferFrom(msg.sender, address(this), pricePaid);

#0 - julien51

2021-11-23T03:36:18Z

#1 - 0xleastwood

2022-01-16T05:26:26Z

Duplicate of #162

Findings Information

🌟 Selected for report: kenzo

Labels

bug
2 (Med Risk)

Awards

1195.3761 USDC - $1,195.38

External Links

Handle

kenzo

Vulnerability details

Unlock contains a feature in which a key buyer can ask for a refund. The refund is sent from the lock - where the purchase funds were sent. The lock manager can withdraw all funds from the lock. Therefore, if the lock manager withdraws enough profits from the lock, the user would not be able to cancel his key and request refund. Even if a lock manager is not malicious, if he would want to enable users to cancel their key, he would have to keep track of how much tokens need to be kept in the contract in order to enable this - not a trivial calculation. A naive lock manager might accidently disable refunds for his clients.

Impact

Refunds are not guaranteed. A user might buy a key expecting to cancel it within some time, only to discover he can not cancel it. (This loss of user funds if why I consider this a high risk finding.) An unaware lock manager who just wants to withdraw all his profits might accidently discover that he removed his users' ability to cancel their key.

Notes

It seems the Unlock team is aware to some extent that withdrawing breaks refunds, as they state in the withdraw function:

* TODO: consider allowing anybody to trigger this as long as it goes to owner anyway? * -- however be wary of draining funds as it breaks the `cancelAndRefund` and `expireAndRefundFor` * use cases.

However, even if just the owner is allowed to call it, he may break the refund functionality - on purpose or accidently. Looking on Unlock documentation I don't see a warning to creators about withdrawing their funds.

Proof of Concept

withdraw function has no limit on the amount withdrawn, therefore the owner can withdraw all funds: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinLockCore.sol#L133:#L162 cancelAndRefund transfers the funds from the same lock contract: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinRefunds.sol#L118 Therefore if there are not enough funds, the transfer will fail.

Perhaps a sort of MasterChef-like shares system can be implemented in order to make sure the owner leaves enough funds in the lock to process refunds.

#0 - julien51

2021-11-23T03:34:05Z

As noted, this is actually documented. You are right though that we should make this more obvious on the UI. I would not classify this as High Risk.

#1 - 0xleastwood

2022-01-16T08:27:03Z

Nice find! I think this can be downgraded to medium as the availability of the protocol is impacted by this issue.

#2 - julien51

2022-03-16T06:45:31Z

This would not affect the whole protocol but only the "malicious" lock and it is impractice not only documented but also how these things work in the real world. If Netflix went out of business tomorrow, I could not get a refund on this month's membership fee...

#3 - 0xleastwood

2022-03-22T21:30:06Z

While I mostly agree with the sponsor, this may be intended behaviour as user's should not be entitled to a refund in this case. However, based on what was known at the time, it seemed like this broke the functionality of cancelAndRefund and expireAndRefundFor functions, hence why it was marked as medium severity.

Findings Information

🌟 Selected for report: kenzo

Also found by: 0x0x0x, WatchPug

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

322.7515 USDC - $322.75

External Links

Handle

kenzo

Vulnerability details

Lock manager can change key pricing. The refund mechanism calculates refund according to current key price, not price actually paid.

Impact

A user refunding can get less (or more) funds than deserved.

Proof of Concept

Refund only takes the current price into account: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinRefunds.sol#L144:#L152 Lock manager can update key price at any point, and the old price is not saved anywhere: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinLockCore.sol#L183 So if for example a key price has gone down, a user who tried to refund will get less funds than deserved.

Consider saving the amount the user paid, and refund according to that. Or having a kind of a price snapshot/version mechanism.

#0 - julien51

2021-11-23T03:35:42Z

This is a known issue... but indeed we should show things in the UI to indicate things to users.

#1 - 0xleastwood

2022-01-16T08:29:21Z

Agree this sounds like an issue! However, I don't think this can be justified as a high risk issue. But it does seem that the protocol could leak value and impact users, so marking this as medium.

#2 - julien51

2022-03-16T06:46:24Z

We actually are adding a new mechanism to keep track of the last price paid by any user which means we could use it in the next version to solve this issue!

Findings Information

🌟 Selected for report: kenzo

Also found by: GiveMeTestEther, cmichel

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

322.7515 USDC - $322.75

External Links

Handle

kenzo

Vulnerability details

If calling transferFrom with _from == _recipient, the key will get destroyed (meaning the key will be set as expired and set the owner's key to be 0).

Impact

A key manager or approved might accidently destroy user's token.

Note: this requires user error and so I'm not sure if this is a valid finding. However, few things make me think that it is valid:

  • Unlock protocol checks for transfer to 0-address, so some input validation is there
  • Since other entities other than the owner can be allowed to transfer owner's token, it might be best to make sure such accidental mistake could not happen.
  • This scenario manifests a unique and probably unintended behavior

Proof of Concept

By following transferFrom's execution: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinTransfer.sol#L109:#L166 One can see that in the case where _from == _recipient with a valid key:

  • The function will deduct transfer fee from the key
  • The function will incorrectly add more time to the key's expiration (L151)
  • The function will expire and reset the key (L155) Therefore, the user will lose his key without getting a refund.

Add a require statement in the beginning of transferFrom: require(_from != _recipient, 'TRANSFER_TO_SELF');

#0 - julien51

2022-03-16T06:46:36Z

Fixed since then :)

Findings Information

🌟 Selected for report: GiveMeTestEther

Also found by: kenzo

Labels

bug
duplicate
2 (Med Risk)
sponsor acknowledged

Awards

537.9192 USDC - $537.92

External Links

Handle

kenzo

Vulnerability details

A malicious user can call shareKey repeatedly, transferring miniscule amounts of his key to different accounts, thereby minting new keys until maxNumberOfKeys is reached.

Impact

Malicious user can grief and make lock purchasing become disabled.

Proof of Concept

In shareKey, any user can mint a new token without paying anything extra: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinTransfer.sol#L83:#L84 And there's no minimum on _timeShared. Once _assignNewTokenId is called, _totalSupply is incremented. https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinKeys.sol#L311:#L322 Therefore a user can call shareKey repeatedely until a lock has been "sold out". Note: maxNumberOfKeys cannot be updated after lock deployment.

I think it depends design/ux wise how you want to mitigate this. One option for example would be to not count these shared keys towards the maxNumberOfKeys. Note: a similar situation exists with grantKey, where the lock manager can grant keys which are free but still counted towards maxNumberOfKeys. But there I find it less of an issue as only the lock manager can issue these keys, so it's perhaps more acceptible.

#0 - julien51

2022-01-03T12:49:09Z

Thanks! We have actually added the ability to increase the supply by the lock manager recently. Additionnaly the lock manager can cancel the abuser's keys.

#1 - 0xleastwood

2022-01-16T11:30:02Z

Duplicate of #242

Findings Information

🌟 Selected for report: elprofesor

Also found by: kenzo

Labels

bug
duplicate
2 (Med Risk)

Awards

537.9192 USDC - $537.92

External Links

Handle

kenzo

Vulnerability details

Unlock can add a new lock version, which lock owners can upgrade to, even before the implementation has been initialized. So anybody could initialize it before Unlock does.

Impact

If somebody else has initialized the new lock version before Unlock has called setLockTemplate - Unlock couldn't set this version as the default template as it has already been initialized. (Unlock would have to deploy another implementation.) If the new lock version has some mechanism of delegatecall or selfdestruct, the malicious actor could call it and brick the locks which have upgraded to that version.

Proof of Concept

When Unlock is adding a new lock version, there is no verification on whether it is initialized or not. https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/Unlock.sol#L187:#L193 When upgrading a lock, there is no verification on whether it is initialized or not. https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/Unlock.sol#L237:#L255 Only if the lock is being set as the default lock template, would it be initialized: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/Unlock.sol#L423:#L438 But at that time it might have already been initialized by somebody else.

I might be missing something, but I see no reason to not move the initialization of the lock to addLockTemplate. This will ensure that any lock implementation that is used by the protocol/users belongs to Unlock and not to another actor.

#0 - 0xleastwood

2022-01-16T09:22:21Z

Duplicate of #132

Findings Information

🌟 Selected for report: kenzo

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

1195.3761 USDC - $1,195.38

External Links

Handle

kenzo

Vulnerability details

More keys can be minted than maxNumberOfKeys since shareKey and grantKey do not check if the lock is sold out.

Impact

More keys can be minted than intended.

Proof of Concept

In both shareKey and grantKey, if minting a new token, a new token is simply minted (and _totalSupply increased) without checking it against maxNumberOfKeys. This is unlike purchase, which has the notSoldOut modifier. grantKey: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinGrantKeys.sol#L41:#L42 shareKey: https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinTransfer.sol#L83:#L84 Both functions call _assignNewTokenId which does not check maxNumberOfKeys. https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinKeys.sol#L311:#L322 So you can say that _assignNewTokenId is actually the root of the error, and this is why I am submitting this as 1 finding and not 2 (for grantKey/shareKey).

Add a check to _assignNewTokenId that will revert if we need to record a new key and maxNumberOfKeys has been reached.

#0 - julien51

2022-01-03T12:42:26Z

This is actually intentional. We want the lock manager to be able to grant keys even if the lock is sold out. Note that the lock manager could also increase the supply if they needed anyway.

#1 - julien51

2022-01-03T12:43:58Z

However, we should take that into account in the shareKey flow so I'll mark as confirmed for that flow.

Findings Information

🌟 Selected for report: kenzo

Labels

bug
1 (Low Risk)
sponsor acknowledged

Awards

398.4587 USDC - $398.46

External Links

Handle

kenzo

Vulnerability details

A user calling setKeyManagerOf with address 0 will succeed, thereby locking himself out of managing his key. This can only be remedied by the lock manager himself updating the key manager. (But as I mentioned in a previous finding, I believe that capability is problematic in itself and should be considered for removal.)

Impact

The user will retrain ownership but lose control of his key.

Proof of Concept

There is no zero address check on setKeyManagerOf : https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinKeys.sol#L207:#L231

Add a zero address check for _keyManager.

#0 - julien51

2022-01-03T12:40:52Z

As you noted this can easily be recovered by the lock manager (and no removing the role of lock manager does not make sense!)

#1 - 0xleastwood

2022-01-16T11:27:36Z

As this does not directly impact protocol availability and rather affects how the function state is handled, I'll mark this as low severity.

Findings Information

🌟 Selected for report: kenzo

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

398.4587 USDC - $398.46

External Links

Handle

kenzo

Vulnerability details

_setKeyManagerOf always emits address(0) as the new key manager.

Impact

Wrong event emitted.

Proof of Concept

The code is: emit KeyManagerChanged(_tokenId, address(0)); https://github.com/code-423n4/2021-11-unlock/blob/main/smart-contracts/contracts/mixins/MixinKeys.sol#L229

Change line to emit KeyManagerChanged(_tokenId, _keyManager);

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter