-
Notifications
You must be signed in to change notification settings - Fork 10
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
Tutorial usage of CheckTx for message validation is insecure #249
Comments
Umm also, and this might need to be a separate issue, but it looks to me like the Bank module is not doing any sort of author checking. kepler/hs-abci-sdk/src/Tendermint/SDK/Modules/Bank/Messages.hs Lines 51 to 52 in fe68362
kepler/hs-abci-sdk/src/Tendermint/SDK/Modules/Bank/Router.hs Lines 26 to 33 in fe68362
kepler/hs-abci-sdk/src/Tendermint/SDK/Modules/Bank/Keeper.hs Lines 63 to 82 in fe68362
|
RE I see what you're saying about Nothing comes to mind immediately, but I'm sure this will be easy enough to fix. Do you have a proposal for how it will work? |
I would say the simplest thing to do would be to call validateMessage before passing the data to the keeper handler. This could be done manually on every router function, such as: kepler/hs-abci-docs/nameservice/src/Nameservice/Modules/Nameservice/Router.hs Lines 34 to 41 in fe68362
Or there could be a requirement on all transaction messages that they have a ValidateMessage instance, and call it earlier for all tx. kepler/hs-abci-sdk/src/Tendermint/SDK/BaseApp/Transaction/Router.hs Lines 66 to 80 in aba9914
Or even earlier in parseTx, at the same time that msgAuthor is extracted from the signature. (this would have the side effect of making defaultCheckTx redundant, since ValidateMessage checks would be automatically performed on both paths) kepler/hs-abci-sdk/src/Tendermint/SDK/Types/Transaction.hs Lines 104 to 123 in fe68362
This would work for any static checks, but dynamic checks such as balance checks would still have to be performed in the keeper handler when actually running the transaction. This creates a small issue for my use case though, which has messages (Utxo style transactions) where the expected message author(s) can't be determined statically, and involves a store lookup. For that case I can't really see a clean way to use this, other than extending validateMessage to be in a Sem. But I can just use a simple workaround where I don't use ValidateMessage for author checking, and just pass msgAuthor into the keeper handler and do those checks there. |
This actually brings up another issue though, which is that parseTx requires recoverable signatures, and the upstream library has removed support for them in versions past 0.2.5 Relevant Commit Signature verification via recovering the pubkey from the signature and then checking equality against something in the message feels pretty non-standard to me (and made it harder to see that signature verification was actually happening), so it might be worth changing this to the more usual pattern. Ie extract expected author key from message, and verify directly with signature and message bytes. |
To keep from talking about a bunch of things at once, maybe we can first focus on what the merits of something like a static validation check are because it's not even clear to me and I wrote it 🙃 Assumptions:
There is a philosophy in the haskell world of "Parse, don't validate" (blog post. This makes me thing that actually the proper static validation class is something like class ParseMessage a b where
parseMessage :: Msg a -> V.Validation [MessageSemanticError] (Msg b) And the point of this is to check things like "a bytestring parses as an Address", "a transfer amount is strictly greater than 0", "a string has a certain form", etc. This means that when we write keeper handlers (1) we don't have to remember to check this and (2) even if we do remember, we don't fill it with this kind of noise. They are not meant for checking stuff against a database to make sure that something is possible, like "the user has enough funds". That kind of stuff is definitely supposed to happen in the keeper handler in my opinion. So before moving on to talk about other things, if you agree to this I feel like I should make an issue to change the typeclass to the above. |
Having read the blog post (thanks for that), I agree that parsing into types which constrain the data possibilities is definitely preferable to just "validation". As for the assumptions, I agree that at a base level the message types are proto or json encoded bytestrings, and that the app developer is expected to define them as Haskell data types. I'm a little confused on the third point though, as I thought that the usage of HasCodec (and the Either return type for decode) would already take care of checking for things like making sure that a value parses as an Address. And that you could take advantage of this to make a value >= 0 by using a Natural instead of an Int (for example). |
Tendermint only uses CheckTx as a local filter to decide whether or not a transaction should be included in the mempool for a future proposal. But when another validator proposes a block, we never run CheckTx on those transactions, and only run DeliverTx when the block is committed. This could be abused to sneak in transactions which would fail CheckTx, but which DeliverTx can still handle.
The tutorial as currently written uses the ValidateMessage class to define validity constraints on messages, and these are then run through defaultCheckTx. Since these checks are not run at the start of DeliverTx, it would be possible for a malicious proposer to create a transaction signed by address A, which deletes a name held by address B.
The text was updated successfully, but these errors were encountered: