-
Notifications
You must be signed in to change notification settings - Fork 252
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
Use the current block number as the nonce of the new account #3402
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: linning <[email protected]>
Signed-off-by: linning <[email protected]>
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.
Looks good but lets move the DefaultNonceProvider
to runtime primitives so we dont have to re-define it everywhere
Thinking on this more, the is core substrate problem so shouldn't this be automatically handled in the upstream instead ? |
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.
This seems like a useful fix, but I’m not sure what the exact security constraints are. Let’s document them somewhere?
crates/subspace-runtime/src/lib.rs
Outdated
@@ -191,6 +192,18 @@ parameter_types! { | |||
|
|||
pub type SS58Prefix = ConstU16<6094>; | |||
|
|||
// `DefaultNonceProvider` uses the current block number as the nonce of the new account, | |||
// this is used to prevent the replay attack see https://wiki.polkadot.network/docs/transaction-attacks#replay-attack | |||
// for more detail. |
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.
Is this a probabilistic defence, or a guaranteed defence?
Is it unlikely that an account/transaction could have a nonce greater than or equal to the block number, or is it impossible? Or do we need to add one to the default to make it impossible?
Let’s document the exact security constraints somewhere, because the linked document doesn’t go into that level of detail. Maybe in the protocol specs?
Also, do we need to document the security risks of skipping nonces (or throwing away a lot of draft transactions)?
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.
Is this a probabilistic defence, or a guaranteed defence?
It is a guaranteed defense.
Is it unlikely that an account/transaction could have a nonce greater than or equal to the block number, or is it impossible? Or do we need to add one to the default to make it impossible?
It is possible to have a nonce greater than the block number, this PR only changes the default nonce of newly created account. In main, the nonce of a new account will start with 0
, with this PR, it will start with current_block_number
, and the next nonce is current_block_number + 1
, current_block_number + 2
, ..., etc.
Let’s document the exact security constraints somewhere, because the linked document doesn’t go into that level of detail. Maybe in the protocol specs?
Yeah, I will update the protocol spec 👍
Also, do we need to document the security risks of skipping nonces (or throwing away a lot of draft transactions)?
Actually, I don't have any security risk in mind. This PR doesn't change the nonce of the existing account or add filter to reject transaction with nonce less than the block number, it only change the default nonce value of the new account. When drafting a new transaction, the user/tool/wallet usually should fetch the account's current nonce onchain to fill the transaction, this PR should not break this process.
But if there are tools/programs that internally transfer funds to create a new account and then draft transaction of the new account without querying its nonce onchain but simply assuming its nonce is start with 0, this kind of scenario will be broken...
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.
Is this a probabilistic defence, or a guaranteed defence?
It is a guaranteed defense.
Is it unlikely that an account/transaction could have a nonce greater than or equal to the block number, or is it impossible? Or do we need to add one to the default to make it impossible?
It is possible to have a nonce greater than the block number, this PR only changes the default nonce of newly created account. In main, the nonce of a new account will start with
0
, with this PR, it will start withcurrent_block_number
, and the next nonce iscurrent_block_number + 1
,current_block_number + 2
, ..., etc.
So this defence won't always work?
For example, in this situation:
- the account's nonce
X
is currently greater than the block number - it gets reaped
- it gets re-created with nonce
current_block_number
- all transactions with nonces between
current_block_number
andX
can be replayed
Also, do we need to document the security risks of skipping nonces (or throwing away a lot of draft transactions)?
Actually, I don't have any security risk in mind. This PR doesn't change the nonce of the existing account or add filter to reject transaction with nonce less than the block number, it only change the default nonce value of the new account. When drafting a new transaction, the user/tool/wallet usually should fetch the account's current nonce onchain to fill the transaction, this PR should not break this process.
The situation I described above is more likely if a tool does not query the current nonce, and the tool/user makes a lot of transactions, using up nonces faster than blocks are created.
Does this need user/developer security documentation?
But if there are tools/programs that internally transfer funds to create a new account and then draft transaction of the new account without querying its nonce onchain but simply assuming its nonce is start with 0, this kind of scenario will be broken...
Does this need user/developer documentation?
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.
For example, in this situation
Indeed, this situation can still happen but it is even harder to trigger though.
Does this need user/developer security documentation?
Does this need user/developer documentation?
Yes, if we decided to take this approach, then we definitely need to document out this behavior change.
This comment was marked as resolved.
This comment was marked as resolved.
While upstream has outlined the issue already in their doc, I'm not sure why they did not fix it, but my guess is
|
Signed-off-by: linning <[email protected]>
Can we not replicate the same conditions on our chain as well ?
This is also a main concern of mine since for
Here is an alternative solution I have been thinking if we are unable to replicate same conditions as polkadot Or maybe we can also enforce only mortal transactions though I believe the problem still exists. Very curious what the polkadot conditions are that makes this not a critical issue 🤔 |
9e3b4c9
to
c712af5
Compare
Signed-off-by: linning <[email protected]>
c712af5
to
4110a03
Compare
Emmm... please see https://wiki.polkadot.network/docs/transaction-attacks#replay-attack for what conditions are required to trigger the issue, and I think the issue still exists in polkadot, the reason I guess it is not a critical issue for polkadot is it is a known but won't fix issue.
This essentially means storing permanent state in the runtime and defeat the purpose of ED, the attacker can easily bloat up the state by continuously transferring funds in and out of an account with the cost of some tx fee. The issue and solution were brought by the audit team let's continue the discussion in Slack with them. |
There is a potential replay attack as mentioned in the upstream doc, where after an account is reaped and later recreated, its nonce is reset to 0, and the previously signed transaction can be replayed if it is immortal or not yet expired since the nonce matched.
This PR fixes the issue by using the current block number as the nonce of the new account, so the previously signed transaction will never match the nonce of the new account.
TODO: I added a unit to ensure the encode/decode of
Nonce
andTypeWithDefault
is the same so we don't need to worry about compatibility with Mainnet and Taurus, but I will test it in a local dev network to double check.Code contributor checklist: