-
Notifications
You must be signed in to change notification settings - Fork 498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix security issue: transaction validity controls must be executed in STF #495
Fix security issue: transaction validity controls must be executed in STF #495
Conversation
Please just keep the changes of the two files in
No. The issuer will always be charged in case of error. It only does not pay fees if the call succeed. |
primitives/self-contained/src/lib.rs
Outdated
fn pre_dispatch_self_contained( | ||
&self, | ||
info: &Self::SignedInfo, | ||
) -> Option<Result<(), TransactionValidityError>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change this to return TransactionValidity
. I don't know why not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type TransactionValidity is only of interest for the pool, the struct ValidTransaction contains data that are only used by the pool, these data have no meaning in the context of the execution of a block.
This is not possible. At least the changes in the ethereum pallet are necessary at the same time.
No, the fees are only applied if you reach at least the withdraw_fee call. In the case of signed substrate extrinsics this is done in the pre_dispatch phase by the SignedExtra of the transaction-payment pallet. |
Here is an attack scenario : A malicious collator produce a block with an Ethereum transaction having an invalid nonce. However failing here don't make the block invalid, and as you can see no fees have been charged yet (withdraw_fee is called just after this check). |
Can you show an actual attack routine with this? Should be fairly straightforward -- spin up a dev node, submit an extrinsic to evm pallet, and see if the balance drops. The fee, in case of error, is supposed to be handled by outer Substrate dispatchable logic. If we're doing that incorrectly then there can be bigger problems. |
Substrate dispatchable logic manages the fees via
In fact this is one more problem, we would have to reimplement in the frontier extrinsic the pre_dispatch/post_dispatch logic, and manage the fees in it. This PR reintroduces pre_dispatch, but does not manage the fees in it, this is a small change I can add in this PR |
I think that means we should have no problem in |
For the record, on the more "Substrate-alike" |
Are you sure? As I recall we do the fee logic inside the evm executor. In case of valid Ethereum transactions, Substrate is hands off in terms of fee handling. |
Conflicts: client/rpc/src/eth.rs
If we do that we'll make the block invalid if the transaction is reverted, which is not compliant.
We don't need to pre-validate all the errors, only those that should make the block invalid. That is, the ones that prevent fees from being charged or ensure that there is no replay (otherwise we can force a user to pay fees against his will).
If we do like the SignedExtension of substrate it would be defined by the runtime and configurable, so I think we can do all the validity checks of a transaction. You will have understood that, by valid transaction we mean: transaction that does not imply to reject the block. The notion of valid transaction is totally different from the notion of success or failure of the execution of the call that this transaction contains.
In fact we return |
I haven't fully read your comment, but just to clear some misunderstandings first -- in case of an Ethereum transaction revert we'd still return |
@librelois Invalid transaction vs transaction revert should actually be like this:
|
The current PR actually looks okay for me now, but you just need to fix https://github.com/paritytech/frontier/pull/495/files#r727997940. It doesn't appear to fix the second vulnerability you mentioned though. Just let me know if you'd just want to proceed with this now -- I'd actually prefer we fix that in a separate PR. |
Exactly no, it is a security flaw to do so. If the transaction is invalid then the block must be rejected. Otherwise it allows a dishonest validator to insert an invalid transaction (e.g. a replay), which will charge the originator of the original transaction against his will. |
For me there are only vulnerabilities left for projects that use the evm pallet directly without going through the ethereum pallet. But this is not our case, so you can merge as is, I'll see if I can spend some time on the remaining vulnerabilities in another PR. |
There's no such thing as "Ethereum transactions" in |
Just to clear this confusion. That second vulnerability I referred to is in |
Haa ok, since we don't use frontier that way, I couldn't possibly know. Ok, so that means no more vulnerability, wonderful :) |
No my PR corrects that, that's what this PR is all about. Returning a TransactionValidityError in the apply method causes the block to be rejected, and that's what it should do. Look at the context of the apply call in the executive pallet ;) |
@librelois I mean we're still leaving the possibility of that. Yes if we can confirm all cases are covered in |
The only way to know if the conditions are not met is to do the validity checks of the transaction, which I do in pre_dispatch. And if these checks are invalid then the executive pallet will panic for us. |
… STF (polkadot-evm#495) * add security tests * add pre_dispatch phase to fiy security issues * Revert frame-evm changes * keeping *_self_contained aligned * remove unused default impl Conflicts: frame/ethereum/src/lib.rs frame/ethereum/src/mock.rs frame/ethereum/src/tests.rs primitives/self-contained/src/checked_extrinsic.rs primitives/self-contained/src/lib.rs template/runtime/src/lib.rs
… STF (polkadot-evm#495) * add security tests * add pre_dispatch phase to fiy security issues * Revert frame-evm changes * keeping *_self_contained aligned * remove unused default impl
… STF (polkadot-evm#495) * add security tests * add pre_dispatch phase to fiy security issues * Revert frame-evm changes * keeping *_self_contained aligned * remove unused default impl
PR #482 introduce some security issues: the validity check of a transaction is no longer performed in the State Transition Function (in block execution context).
Before this PR, the
validate_unsigned
function was called in the STF via the call to thepre_dispatch_unsigned
method of theSignedExtension
feature: https://github.com/paritytech/substrate/blob/master/primitives/runtime/src/traits.rs#L929-L937.Now, the
validate_self_contained
function does this job, but it is not called in the STF.Thus, a malicious validator can submit a valid block with invalid transactions, and for example replay transactions from another ChainId.
So of course, it would be enough to modify the implementation of SelfContainedCall by the Call type of the runtime to call the validation function in
apply_self_contained
.But
apply_self_contained
does not allow to return a TransactionValidityError, the only way to make the block fail is to return None, this is not satisfactory.I chose instead to do as substrate and add a
pre_dispatch_self_contained
function, which is used to check the validity of a transaction in the context of a block execution.This also has the advantage of allowing to treat differently the nonce check depending on whether you are in the pool or in the block, like substrate does (see CheckNonce: https://github.com/paritytech/substrate/blob/master/frame/system/src/extensions/check_nonce.rs#L75-L123).
The controls in StackExecutor have been removed because they must be done upstream.
Indeed, in case of failure of a check in the StackExecutor, the extrinsic is failed but the block remains valid, but the issuer will not be charged because the checks return before. This opens a security hole allowing to spam the blockchain without paying fees.