-
Notifications
You must be signed in to change notification settings - Fork 35.6k
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
p2p: Prevent block index fingerprinting by sending additional getheaders messages #24571
Conversation
a2b09e8
to
73b8e05
Compare
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
73b8e05
to
b4d108e
Compare
Concept ACK As I understood it, |
b4d108e
to
4e41506
Compare
Thank you @naumenkogs for the review!
It does not, the header that the attacker uses can be completely invalid. The proof-of-work check happens later on in |
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.
I found the wording of the first sentence of the second paragraph in the commit "[net processing] Prevent block index fingerprinting when receiving he…" to be confusing. It sounds like we now send (in)valid headers to prevent the attack.
This commit prevents malicious peers from performing such an attack by sending (in)valid headers that extend a stale chain on the victims node.
Maybe drop the "performing such an attack"
This commit prevents malicious peers from sending (in)valid headers that extend a stale chain on the victims node.
Maybe relevant for other reviewers: https://bitcoincore.reviews/24571
I'm planning to test this against a node on my reorg-Signet.
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.
Concept ACK.
Do you think such anti-fingerprinting protections should be extended to BIP157 messages request handling ? Even if there are few nodes offering such service now.
Thanks everyone for the review so far! I am working on addressing all your feedback. @ariard Good point bringing up BIP157 requests but luckily we already handle them safely, see: bitcoin/src/net_processing.cpp Line 2437 in b69fd5e
|
Concept ACK |
4e41506
to
2021f7c
Compare
I updated this PR with the new approach (see commits and new PR description) and it’s now ready for review again. Most of the review comments did not apply anymore so i marked them as resolved. The previous approach can be found on this branch. |
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.
Concept ACK. Nice find.
As an alternative approach, did you consider pruning stale tips from the block index once they're more than a certain age (eg a month)? Or perhaps just making them unavailable from outside validation if no peer has informed you about them for some period of time.
This commit extracts the logic from the addr cache id generation into its own function (GetUniqueNetworkID).
5e1308f
to
97c86d4
Compare
Rebased. @naumenkogs Thanks for the review! I have addressed some of your comments and will get to the others as soon as I can. |
This commit introduces the `PeerManagerImpl::m_chain_tip_sets` map which keeps track of seen chain tips per network. No DoS risk is introduced by keeping these sets of chain tips since each set in the map will have at most as many entries as there are chain tips in our global index.
The block index might contain stale blocks that are not part of the main chain. If a malicious peer is able to probe a node's block index for certain stale blocks then it can use this information to fingerprint the node. Before this commit such an attack is possible due to the logic when receiving headers. A malicous peer can probe for a stale block in a node's index by sending a headers message in which the first header extends a stale chain and has invalid proof-of-work. Nodes behave differently when receiving such a headers message, depending on whether or not the stale block exists in its block index. If it exists, the node will disconnect as the proof-of-work on the new header was invalid. If it does not exist, the node will send a getheaders message in an attempt to connect the two chains. In this commit we prevent this attack by making use of the `PeerManagerImpl::m_chain_tip_sets` map which we introduced in the previous commit. We only try to accept new headers if they connect to anything in our global index and they connect to our active chain or to a chain that was previously sent to us by a peer on the same network. We send a getheaders message should these conditions not be met.
This commit prevents the same attack that was described in the previous commit when receiving compact blocks.
97c86d4
to
d700342
Compare
@@ -1167,6 +1167,13 @@ class CConnman | |||
friend struct ConnmanTestMsg; | |||
}; | |||
|
|||
/** | |||
* Get a unique identifier for the network (e.g. Tor, IPv4/6) of a connection. |
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.
nit: From my reading of the first line of the comment, it looks like this is just a network type identifier (without the local socket). e.g., tor=0, ipv4=1, etc.
🐙 This pull request conflicts with the target branch and needs rebase. |
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.
Concept ACK. I've tested this out on a couple of reorg scenarios in regtest. Liked how test_header_leak_via_headers()
checks whether stale header leaks should happen or not.
Summarising this PR in case it's useful to other reviewers - if a node on 2 different networks have same stale block indices, it's highly possible that it's the same node (fingerprinting).
behaviour on master :
- node1 sends sequence of headers built on some stale block to node2.
- if node2 knows stale block, node2 won't send GETHEADERS
- if node2 doesn't know stale block, node2 sends GETHEADERS (of it’s active chain tip)
- this behaviour difference can be used to identify the stale blocks a node has.
behaviour on PR:
- node1 sends sequence of headers built on some stale block to node2.
- if node2 knows stale block, node2 sends GETHEADERS (of it’s active chain tip)
- if node2 doesn't know stale block, node2 sends GETHEADERS (of it’s active chain tip)
- presence/absence of stale blocks can't be identified now.
* replacing an existing tip if it is an ancestor of the new block or by | ||
* adding a new tip if none of the existing chain tips were replaced. | ||
*/ | ||
void UpdateChainTipSet(const CNode& node, const CBlockIndex& potential_new_tip) |
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.
did i understand this correctly? - UpdateChainTipSet
updates chain tip set based on:
1. the whole block/valid headers we received and were told of from other nodes on the same network
2. when we switch networks, chain tip set initially would only contain tip of active chain.
3. inbound and outbound connections on same network would have different chain tip set. is this intentional?
if we mine a block and tell other nodes on the network about it, should we update the chain tip set? (i haven't checked if UpdateChainTipSet
is called in all the locations.) are there other situations in which a block isn’t inserted into the chain tip set?
const CBlockIndex* index{m_chainman.m_blockman.LookupBlockIndex(hash)}; | ||
if (!index) return nullptr; | ||
|
||
if (IsAncestorOfBestHeaderOrTip(index) || |
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.
IsAncestorOfBestHeaderOrTip
would already by contained in IsBlockInChainTipSet
right? do we include IsAncestorOfBestHeaderOrTip
check for performance gain instead of iterating over all the chain tips? or is there some other reason?
@stratospher Thank you for having a look at this! There is an alternative approach to the one in this PR that I have been meaning to implement and I forgot to put this PR in draft mode. We made some changes to the headers sync logic (#25454), which causes us to now only have one |
Closing for now, can be marked up for grabs. Someone should look into implementing the alternative approach I have described here: #24571 (comment). |
The block index might contain stale blocks that are not part of the main chain. If a malicious peer is able to probe a node's block index for certain stale blocks then it can use this information to fingerprint the node.
When receiving headers (either through a
cmpctblock
orheaders
messages) a node will sendgetheaders
if the predecessor of the first header does not exist. This leaks information from the block index if the predecessor of the header is a stale block because nogetheaders
will be sent in that case revealing that the stale block exists in the index.This PR prevents this fingerprinting by sending additional
getheaders
messages in cases where not doing so leaks the existence of stale blocks. To determine when additional messages should be send, we introduce thePeerManagerImpl::m_chain_tips_sets
map which keeps track of seen chain tips per network, effectively creating a per network view of the node's global block index. We only try to accept new headers if they connect to anything in our global index and they connect to our active chain or to a chain that was previously sent to us by a peer on the same network. We send agetheaders
message should these conditions not be met.