-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Support eosio.ccode
permission name in authorities which implicitly pins to code version active at the time the authority was updated
#3050
Comments
@arhag I like this approach, but I was thinking that, even though it would require more changes to how the authorizations work, it would be great to be able to authorize specific contract versions using a hash of the contract's code itself. I know my friends at Protocol Labs and IPFS would definitely agree (content addressing). This would have a couple of nice benefits:
The difficulties I see with this approach are:
I would love to hear your comments! |
@arhag @wanderingbort Are these requirements still valid with the upcoming permission changes? |
Where can I find info about these upcoming changes? |
I'm not sure what permission changes you are referring to, but this change will not be included in the first consensus protocol upgrade. I can see the value in pinning a permission to a particular code hash. However, it is also valuable to pin to a particular code sequence number for reasons discussed in the background section of #6168 (specifically the second to last paragraph of the background section discussing how the audit was conditional on certain table invariants holding true). Both the code sequence number and the code hash are already tracked in the native account objects (code hash as the field The tricky part is needing to upgrade the I do now however think that if this feature is still considered important to have, it is worth doing it in a way that makes it straightforward to use, and that means putting in the effort to upgrade the What I am questioning now, however, is whether the very concept of code pinning at the authority level is even worth having. Adding a contract code permission to a user's account permissions is rarely a good practice (this of course excludes adding the
|
@arhag thanks for your answer. You make some very good points. I take back my support for pinning based on hash and instead I think it makes much more sense to pin based on sequence number of the deployment of code to the account, thus protecting you against attacks based on switching the code temporarily to make some unacceptable changes to the data in the tables and then reverting it to the approved hash. Users (and developers using inline actions) should have to approve each new deployment of the contract. I can imagine dapp developers prompting their users to approve the new version of their contract in order to use the dapp, along with a message that explains the changes and a link to the git diff for developers to inspect if they so wish.
Even though I really like the
I think this perception should change once there is more control over what exactly you are approving, like specifying the contract version as we're discussing. For instance, permissions could be set up so that your account is owned and mostly managed by you, but you give someone else permission to update the contract code (this would be a common scenario in dapps, as the management team could give the developers only permission to deploy new code). In this scenario, if you just specify the There are other important reasons for having better support for these authorizations and actually using them and it not being a bad practice:
I'd be happy to hear your thoughts! Thanks for all your great work. |
I think this is exactly the sort of practice with A better pattern specifically for the token transfer example is to first You bring up a good point about Ricardian contracts though. We don't have a good explicit solution in place for dealing with notified contracts and expressing the intent of those contracts' behavior when notified of another contract's actions. And with the model above, there wouldn't be an explicit action of the contract called The example above is maybe okay (notwithstanding the lack of the Ricardian) for depositing tokens. However, a better pattern in general is needed, not only to get an explicit The tricky part is that the proxy contract would not allow the other contract to just send any inline action of a particular code and action name authorized by the user account. It would need to have proof that the user authorized that specific action (with particular action payload data). Even more than that, it would need proof that the user authorized that specific action (with specified payload data) for just one time and within the context of a particular transaction at least (better yet if it is restricted to the context of a particular action). This is technically possible to do with the existing EOSIO functionality (with some caveats to consider), but it isn't a very nice way to do it. For example, an immediately preceding action within the transaction that is authorized by the user account may create a table record tagged with the computed transaction ID of the current transaction which authorizes the specified contract (contract name also stored in table record) to proxy send an inline action that hashes to a specified hash (hash of action also stored in the table record) but only within the current transaction. If the contract sends an inline action to the proxy (with the actual action to send nested in the action payload), the proxy contract could check that table row and validate all the necessary conditions along with deleting the table record and actually sending the nested action. This approach however requires the transaction signer to know what the inline action payload data will be at the time of signing the transaction. But imagine the inline action was a transfer to pay for the cost of buying some asset from a DEX. The price of the asset (and therefore the calculated amount of tokens to transfer) would not be known until the moment of the trade. In this case the payload of the preceding action that authorized the contract to send the inline transfer would need to not include a simple hash, but a specification (effectively a contract) that restricts certain parameters of the inline action payload. For example it may restrict the token symbol and put an upper bound on the amount. But with this approach the proxy contract would need to be aware of these domain-specific rules. So perhaps instead it passes the inline action the contract wants it to send along with user-provided constraint specification as an inline action to another contract (specified by the user) with domain-specific knowledge about the particular constraints to allow it to validate the constraint. So the action receipt hierarchy for this example might looking something like this:
|
I wish people would stop doing that! The design you've proposed is very powerful. It is also very intricate. Some help would be required from the wallets and end-user tools to make the UX more friendly. I believe it's a good opt-in system, in the same way as the opt-in "banking contract" proposal, but I don't see it becoming the general standard because of it complexity. On the other side, pinning a permission to a to a specific contract deployment implicitly provides enough guarantees for the vast majority of cases, as the user would have been able to audit that version of the contract (code or Ricardian) and understand the logic that will produce the inline action, thus knowing what to expect. In the example you gave, the user would know well that the Together with education about good permissions management practices, this would radically improve the security of the network in general.
I agree, it is blunt for this right now, but with permission pinning to contract deployments it wouldn't be blunt anymore. It would be fine grained enough to ensure we know what's going on and there are no surprises. That's why we need it. |
You have almost won me over with that argument. But I think there is a big difference in user expectations between the two solutions. With the With the code pinning solution, a user is expected to first audit the smart contract that they wish to pin to (or more likely delegate that responsibility to an auditor that they trust), before broadcasting the signed transaction that changes one of their permissions to allow the contract to authorize certain actions on their behalf. It is one thing to trust the audit for a handful of major critical contracts like By knowing that the permission system (perhaps with the augmentations to it provided by But with the code pinning solution used in this |
In order to focus our efforts on issues that are currently creating difficulty for the community we are closing tickets that were created prior to the EOSIO 2.0 release. If you believe this issue is still relevant please feel free to reopen it or create a new one. Thank you for your continued support of EOSIO! |
We currently support the special
eosio.code
permission name in authorities which is automatically satisfied by a contract. So, for example, if Alice's active authority is set up such that it can be satisfied by thebob@eosio.code
permission level, that means that the contract deployed on thebob
account can send actions which are authorized byalice@active
.The limitation with the current approach is that the authority can grant permission to any code deployed on a particular account but currently cannot grant permission only to a particular code deployed on an account. Alice may have audited version 1 of the contract deployed on the
bob
account and addedbob@eosio.code
to her active authority based on that audit. But perhaps she does not fully trust Bob and is worried that he may change the contract to version 2 which may do something undesirable using Alice's active authority. (Bob can make the contract immutable, but it can be useful for Bob to reserve the right to update the code in the future. Or perhaps the only entity that can upgrade the code is the code itself, based on some decentralized governance process. Either way Alice may want the ability to explicitly renew her delegation of authorization to a smart contract each time it upgrades rather than have it automatically happen.)Alice would like to be able to pin the
eosio.code
permission references in an authority to a particular version of the code. That way, if Bob were to then update the code, the new version of the code could no longer satisfy that permission level in Alice's authority.We will not be supporting this feature by EOSIO 1.0, but we do want to be able to upgrade via hard fork with minimal disruption to support the new feature.
The simplest way to support this feature is to add a new virtual permission name with special semantics supported in authorities:
eosio.ccode
(short for current code).contract@eosio.code
would continue to be satisfied by any code version deployed on thecontract
account; whereascontract@eosio.ccode
would only be satisfied by the code version that was deployed on thecontract
account at the time of setting/updating the authority that included thecontract@eosio.ccode
permission level. This approach also means that the hard fork does not change the serialization of authorities or actions.The
authority_checker
algorithm has to be updated to support this new feature. We are already tracking important time information for contracts and permissions:last_code_update
in theaccount_object
tracks the last time the code of an account was updated, andlast_updated
of thepermission_object
tracks the last time a permission was updated. This information needs to be made accessible toauthority_checker
. Whenauthority_checker
retrieves an authority from apermission_level
, it should also retrieve thelast_updated
time of the permission. This time can be compared to time threshold passed intoauthority_checker
which is set to thelast_code_update
time of thereceiver
account when the authorizations are being checked in the context of a sending an inline action or a deferred transaction. If the time the permission was last updated is not greater than the time threshold, then theeosio.ccode
permission would be ignored by theauthority_checker
despite the fact that thereceiver@eosio.ccode
permission would be provided viacheck_authorization
.One corner case that needs to be addressed with this approach is when a contract changes around the same time a user is setting or updating a permission. Alice could be setting the
bob@eosio.ccode
permission level in her active authority at the same time that Bob updates the contract on his account to version 2. But Alice only audited version 1 of the contract and expectedbob@eosio.ccode
to be pinned to version 1, not later versions.The solution to this is to take advantage of the TaPoS-referenced block. Alice's
updateauth
was created with the context of the database state as of the end of the block referenced by TaPoS in the header of the transaction including theupdateauth
action. Therefore, if any of the accounts referenced through theeosio.ccode
permission of the new authority updated their code after the timestamp of the TaPoS-referenced block, the transaction containing thatupdateauth
action should fail.The text was updated successfully, but these errors were encountered: