Skip to content

Conversation

@sdaftuar
Copy link
Member

@sdaftuar sdaftuar commented Oct 26, 2017

Alternate to #11446.

Disconnect outbound (non-manual) peers that serve us block headers that are already known to be invalid, but exempt compact block announcements from such disconnects.

We restrict disconnection to outbound peers that are using up an outbound connection slot, because we rely on those peers to give us connectivity to the honest network (our inbound peers are not chosen by us and hence could all be from an attacker/sybil). Maintaining connectivity to peers that serve us invalid headers is sometimes desirable, eg after a soft-fork, to protect unupgraded software from being partitioned off the honest network, so we prefer to only disconnect when necessary.

Compact block announcements are exempted from this logic to comply with BIP 152, which explicitly permits nodes to relay compact blocks before fully validating them.

@laanwj laanwj added this to the 0.15.0.2 milestone Oct 26, 2017
ProcessMessages will now return earlier when processing headers
messages, rather than continuing on (and do nothing).
@sdaftuar sdaftuar force-pushed the 2017-10-disconnect-outbound-peers-on-invalid-chains branch from 94742b1 to 8ca0ffa Compare October 26, 2017 20:40
@sdaftuar
Copy link
Member Author

Needed rebase

@TheBlueMatt
Copy link
Contributor

utACK 8ca0ffa

if (nDoS > 0) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), nDoS);
} else if (!pfrom->fInbound && !pfrom->m_manual_connection && punish_invalid) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carrying forward my cranky-old-man-rant from: #11446 (comment)

This is crazy-confusing to read. If the header was bad enough to earn them some ban points, put the points on the score-board and carry on. But if it wasn't bad enough for points, kick them immediately.

If we're punishing for invalid, I think the disconnect needs to always happen if (state.IsInvalid). Using points as a proxy for some certain invalid condition(s) is really non-obvious.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @theuni. Think this should be if ... not else if ... so that we always disconnect on invalid blocks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously this is very, very much a stop-gap for 0.15.0.2. Next up is refactoring the CValidationState logic to have a concept of what went wrong (bad-by-soft-fork, bad-by-prev-block, bad-by-....) and then just move all of the DoS logic into net_processing. I believe a switch to if instead of else if may break the "prev block not found" handling, so I'm happy to commit to pushing for a DoS overhaul by 0.16 in the coming months.

Copy link
Member Author

@sdaftuar sdaftuar Oct 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After further analysis, I think the logic I've presented here is undesirable, because there is at least one situation where we should not disconnect an outbound peer for an invalid block header, which is when the block timestamp is too far in the future. This is a problem whether or not we break this else if out into its own if clause.

I'm going to add a commit that reworks this to explicitly look for the case where the first invalid block header processed is one for which we have the entry in our mapBlockIndex, and only disconnect in that case. I think the review burden will be somewhat (much?) lower for such an approach, and it still accomplishes the overall goal of disconnecting peers on (some, but not all) known-invalid headers chains.

LOCK(cs_main);
Misbehaving(pfrom->GetId(), nDoS);
} else if (!pfrom->fInbound && !pfrom->m_manual_connection && punish_invalid) {
// Don't keep outbound peers that are on an invalid chain
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird to have a punish_invalid param that only punishes based on its own criteria. That means that both sites have to consider all constraints.

Instead, from the ::HEADERS handler, how about something like:

bool punish_invalid = !pfrom->fInbound && !pfrom->m_manual_connection;
ProcessHeadersMessage(..., should_punish);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

@theuni
Copy link
Member

theuni commented Oct 27, 2017

Concept ACK. No hostility intended, I'm just still having a hard time understanding the intended ban/disconnect behavior.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 8ca0ffa. Confirmed moveonly commit and that the change does seem to "Only disconnect outbound (non-manual) peers that serve us invalid blocks, and exempt compact block announcements from such disconnects." I do think it would be nice if the reasoning was explained more or the logic simplified as other reviewers suggested. It would also be nice to have a unit test to make sure this doesn't break in the future, especially if there's going to be a refactoring like Matt mentioned.


if (fRevertToHeaderProcessing)
return ProcessMessage(pfrom, NetMsgType::HEADERS, vHeadersMsg, nTimeReceived, chainparams, connman, interruptMsgProc);
return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*punish_invalid=*/false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment saying why this avoids disconnecting on compact block messages with invalid headers? It's unclear whether it's important not to disconnect or whether it's an arbitrary decision and you're just being conservative. You could also consider renaming punish_invalid to disconnect_invalid or similar to be more specific.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the hacky vHeadersMsg serialization stuff can be removed now that ProcessHeadersMessage is factored out.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I had been looking forward to nuking that, and then forgot! Done.

LOCK(cs_main);
Misbehaving(pfrom->GetId(), nDoS);
} else if (!pfrom->fInbound && !pfrom->m_manual_connection && punish_invalid) {
// Don't keep outbound peers that are on an invalid chain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion doesn't apply if you make the changes Cory and Andrew are asking, but otherwise it would be nice if this comment said something about why it avoids disconnecting when nDoS > 0.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked this and added an explanatory comment.

@sdaftuar
Copy link
Member Author

I reworked the disconnect logic to (hopefully) be much clearer, and narrowly tailored to the situation that we're interested in.

@achow101
Copy link
Member

utACK 4cde638

@jnewbery
Copy link
Contributor

Somewhat tested ACK 4cde638.

I have a test case that proves a peer gets kicked for sending an invalid header here: https://github.com/jnewbery/bitcoin/tree/pr11568.2 so I can verify that this PR works in the positive case (ie it disconnects when it's supposed to). I don't think that there are corner cases where this PR might cause other changes in existing behaviour, but I haven't been able to convince myself of that.

The test case in https://github.com/jnewbery/bitcoin/tree/pr11568.2 should be reviewed and merged separately and shouldn't hold up merging or backporting this PR. It builds on #10160 which isn't yet merged, and the test itself is potentially racey so could be improved.

@theuni
Copy link
Member

theuni commented Oct 27, 2017

utACK other than #11568 (comment)

@TheBlueMatt
Copy link
Contributor

ewww-ewww-ewww-ewww-ewww-I've-already-started-working-on-rewriting-DoS-interface utACK bf5494b

@theuni
Copy link
Member

theuni commented Oct 27, 2017

@sdaftuar Thanks!

@TheBlueMatt Please consider working on top of #11457 (will rebase) which allows for much more straightforward banning.

@sdaftuar sdaftuar force-pushed the 2017-10-disconnect-outbound-peers-on-invalid-chains branch from bf5494b to 37886d5 Compare October 27, 2017 20:30
@sdaftuar
Copy link
Member Author

@theuni
Copy link
Member

theuni commented Oct 27, 2017

Verified same as unsquashed. utACK 37886d5.

Copy link
Member

@sipa sipa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 37886d5

Verified move-only first commit.

@laanwj
Copy link
Member

laanwj commented Oct 28, 2017

utACK 37886d5

// the peer if the header turns out to be for an invalid block.
// Note that if a peer tries to build on an invalid chain, that
// will be detected and the peer will be banned.
return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*punish_duplicate_invalid=*/false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding a comment before the argument, that's a good way to avoid boolean arguments turning into an unreadable blob of arguments.

@sipa sipa merged commit 37886d5 into bitcoin:master Oct 28, 2017
sipa added a commit that referenced this pull request Oct 28, 2017
37886d5 Disconnect outbound peers relaying invalid headers (Suhas Daftuar)
4637f18 moveonly: factor out headers processing into separate function (Suhas Daftuar)

Pull request description:

  Alternate to #11446.

  Disconnect outbound (non-manual) peers that serve us block headers that are already known to be invalid, but exempt compact block announcements from such disconnects.

  We restrict disconnection to outbound peers that are using up an outbound connection slot, because we rely on those peers to give us connectivity to the honest network (our inbound peers are not chosen by us and hence could all be from an attacker/sybil).  Maintaining connectivity to peers that serve us invalid headers is sometimes desirable, eg after a soft-fork, to protect unupgraded software from being partitioned off the honest network, so we prefer to only disconnect when necessary.

  Compact block announcements are exempted from this logic to comply with BIP 152, which explicitly permits nodes to relay compact blocks before fully validating them.

Tree-SHA512: 3ea88e4ccc1184f292a85b17f800d401d2c3806fefc7ad5429d05d6872c53acfa5751e3df83ce6b9c0060ab289511ed70ae1323d140ccc5b12e3c8da6de49936
@meshcollider
Copy link
Contributor

Posthumous utACK 37886d5

practicalswift added a commit to practicalswift/bitcoin that referenced this pull request Oct 30, 2017
Reading the variable mapBlockIndex requires holding the mutex cs_main.

The new "Disconnect outbound peers relaying invalid headers" code
added in commit 37886d5 and merged
as part of bitcoin#11568 two days ago did not lock cs_main prior to accessing
mapBlockIndex.
laanwj added a commit that referenced this pull request Oct 31, 2017
2530bf2 net: Add missing lock in ProcessHeadersMessage(...) (practicalswift)

Pull request description:

  Add missing lock in `ProcessHeadersMessage(...)`.

  Reading the variable `mapBlockIndex` requires holding the mutex `cs_main`.

  The new "Disconnect outbound peers relaying invalid headers" code added in commit 37886d5 and merged as part of #11568 two days ago did not lock `cs_main` prior to accessing `mapBlockIndex`.

Tree-SHA512: b799c234be8043d036183a00bc7867bbf3bd7ffe3baa94c88529da3b3cd0571c31ed11dadfaf29c5b8498341d6d0a3c928029a43b69f3267ef263682c91563a3
@morcos morcos mentioned this pull request Nov 2, 2017
maflcko pushed a commit to maflcko/bitcoin-core that referenced this pull request Nov 2, 2017
ProcessMessages will now return earlier when processing headers
messages, rather than continuing on (and do nothing).

Github-Pull: bitcoin#11568
Rebased-From: 4637f18
maflcko pushed a commit to maflcko/bitcoin-core that referenced this pull request Nov 2, 2017
maflcko pushed a commit to maflcko/bitcoin-core that referenced this pull request Nov 2, 2017
Reading the variable mapBlockIndex requires holding the mutex cs_main.

The new "Disconnect outbound peers relaying invalid headers" code
added in commit 37886d5 and merged
as part of bitcoin#11568 two days ago did not lock cs_main prior to accessing
mapBlockIndex.

Github-Pull: bitcoin#11578
Rebased-From: 2530bf2
HashUnlimited pushed a commit to chaincoin/chaincoin that referenced this pull request Mar 12, 2018
Reading the variable mapBlockIndex requires holding the mutex cs_main.

The new "Disconnect outbound peers relaying invalid headers" code
added in commit 37886d5 and merged
as part of bitcoin#11568 two days ago did not lock cs_main prior to accessing
mapBlockIndex.
codablock pushed a commit to codablock/dash that referenced this pull request Sep 29, 2019
37886d5 Disconnect outbound peers relaying invalid headers (Suhas Daftuar)
4637f18 moveonly: factor out headers processing into separate function (Suhas Daftuar)

Pull request description:

  Alternate to bitcoin#11446.

  Disconnect outbound (non-manual) peers that serve us block headers that are already known to be invalid, but exempt compact block announcements from such disconnects.

  We restrict disconnection to outbound peers that are using up an outbound connection slot, because we rely on those peers to give us connectivity to the honest network (our inbound peers are not chosen by us and hence could all be from an attacker/sybil).  Maintaining connectivity to peers that serve us invalid headers is sometimes desirable, eg after a soft-fork, to protect unupgraded software from being partitioned off the honest network, so we prefer to only disconnect when necessary.

  Compact block announcements are exempted from this logic to comply with BIP 152, which explicitly permits nodes to relay compact blocks before fully validating them.

Tree-SHA512: 3ea88e4ccc1184f292a85b17f800d401d2c3806fefc7ad5429d05d6872c53acfa5751e3df83ce6b9c0060ab289511ed70ae1323d140ccc5b12e3c8da6de49936
codablock pushed a commit to codablock/dash that referenced this pull request Sep 29, 2019
2530bf2 net: Add missing lock in ProcessHeadersMessage(...) (practicalswift)

Pull request description:

  Add missing lock in `ProcessHeadersMessage(...)`.

  Reading the variable `mapBlockIndex` requires holding the mutex `cs_main`.

  The new "Disconnect outbound peers relaying invalid headers" code added in commit 37886d5 and merged as part of bitcoin#11568 two days ago did not lock `cs_main` prior to accessing `mapBlockIndex`.

Tree-SHA512: b799c234be8043d036183a00bc7867bbf3bd7ffe3baa94c88529da3b3cd0571c31ed11dadfaf29c5b8498341d6d0a3c928029a43b69f3267ef263682c91563a3
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants