Skip to content
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

net: limit BIP37 filter lifespan (active between 'filterload'..'filterclear') #18544

Merged

Conversation

theStack
Copy link
Contributor

@theStack theStack commented Apr 6, 2020

This PR fixes #18483. On the master branch, there is currently always a BIP37 filter set for every peer: if not a specific filter is set through a filterload message, a default match-everything filter is instanciated and pointed to via the CBloomFilter default constructor; that happens both initially, when the containing structure TxRelay is constructed:

TxRelay() { pfilter = MakeUnique<CBloomFilter>(); }

and after a loaded filter is removed again through a filterclear message:

pfrom->m_tx_relay->pfilter.reset(new CBloomFilter());

The behaviour was introduced by commit 37c6389 (an intentional covert fix for CVE-2013-5700, according to gmaxwell).

This default match-everything filter leads to some unintended side-effects:

  1. getdata request for filtered blocks (i.e. type MSG_FILTERED_BLOCK) are always responded to with merkleblocks, even if no filter was set by the peer, see issue BIP37: 'getdata' request for filtered blocks is answered with 'merkleblock's even if no filter is set #18483 (strictly speaking, this is a violation of BIP37)

    bitcoin/src/net_processing.cpp

    Lines 1504 to 1507 in c0b389b

    if (pfrom->m_tx_relay->pfilter) {
    sendMerkleBlock = true;
    merkleBlock = CMerkleBlock(*pblock, *pfrom->m_tx_relay->pfilter);
    }
  2. if a peer sends a filteradd message without having loaded a filter via filterload before, the intended increasing of the banscore never happens (triggered if bad is set to true, a few lines below)

    bitcoin/src/net_processing.cpp

    Lines 3182 to 3186 in c0b389b

    if (pfrom->m_tx_relay->pfilter) {
    pfrom->m_tx_relay->pfilter->insert(vData);
    } else {
    bad = true;
    }

This PR basically activates the else-branch code paths for all checks of pfilter again (on the master branch, they are dead code) by limiting the pointer's lifespan: instead of always having a filter set, the pfilter is only pointing to a CBloomFilter-instance after receiving a filterload message and the instance is destroyed again (and the pointer nullified) after receiving a filterclear message.

Here is a before/after comparison in behaviour:

code part / scenario master branch PR branch
getdata processing for MSG_FILTERED_BLOCK always responds with merkleblock only responds if filter was set via filterload
filteradd processing, no filter was loaded nothing peer's banscore increases by 100 (i.e. disconnect)

On the other code parts where pfilter is checked there is no change in the logic behaviour (except that CBloomFilter::IsRelevantAndUpdate() is unnecessarily called and immediately returned in the master branch).
Note that the default constructor of CBloomFilter is only used for deserializing the received filterload message and nowhere else. The PR also contains a functional test checking that sending getdata for filtered blocks is ignored by the node if no bloom filter is set.

@maflcko
Copy link
Member

maflcko commented Apr 6, 2020

Could add a test for the disconnect?

@theStack theStack force-pushed the 20200406-net-limit_bip37_filter_lifetime branch from 82b8976 to 1f3566e Compare April 6, 2020 21:48
@theStack
Copy link
Contributor Author

theStack commented Apr 6, 2020

Could add a test for the disconnect?

Indeed, I added a test checking that sending filteradd is treated as misbehavior by the node through getting the peer's banscore via RPC getpeerinfo before and after (As the node is started with noban on localhost, no disconnect/ban happens).

…lterclear')

Previously, a default match-everything bloom filter was set for every peer,
i.e. even before receiving a 'filterload' message and after receiving a
'filterclear' message code branches checking for the existence of the filter
by testing the pointer "pfilter" were _always_ executed.
@theStack theStack force-pushed the 20200406-net-limit_bip37_filter_lifetime branch from 1f3566e to c4d5ba8 Compare April 9, 2020 09:28
@theStack
Copy link
Contributor Author

theStack commented Apr 9, 2020

Rebased, after #18533 has been merged.

@maflcko maflcko removed the Tests label Apr 13, 2020
@maflcko maflcko added this to the 0.21.0 milestone Apr 13, 2020
Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

ACK c4d5ba8 👍

Show signature and timestamp

Signature:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

ACK c4d5ba83b2ca5781821cc288295da81156a28277 👍
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUjBRAv8DS1SLAG7dVYrnM53l0CpW3L86qsugRb7QX+JR4+fnmqaLkWwg95z80CL
xL4NqnfBkIs3aUzQHtW1Ry0yZA8BuZQf9syg5jVXlFJlSjlJ8l58Obu1VEhPXIhU
pSUNEFAKHGcSZCay6lpuKJ2YESYxf3CWBtObtiC5bA2/NENKt0mMjT3PvH8sbSmA
UvD4qmFdDfU+h/my3hrq8509NP4u2DO029z1i/kw2kRP0BR3ywom4vsBbgyLGsn3
TfUJwA/ZUzHTfcaUJwolkr6fDoS6Xg8b6h6S+FHT5AWDC23D0uK2CSKLoE665P8D
C3CMeVobVgZwwTDUu6ehpm0AJMZ/nuLdtjurO95l8IArronxaY778LZPbqnP14UQ
KC/z1c01wGCzkKqQedA6xI+OaudPBXSAYHtM7OW4O2anlS1OixNX4OHgrYa3lklw
ywUK7s4/7kYp3HasAerziUxx4QFQ1D9c3veimRvkOYtl4MiTSD3I07L+1bat6B6e
4iuKgGpD
=pvkA
-----END PGP SIGNATURE-----

Timestamp of file with hash 94f5012633296cb966cec859d16ca56127a4af35eb21df461ff9a83d033c85d2 -

@theStack theStack force-pushed the 20200406-net-limit_bip37_filter_lifetime branch from c4d5ba8 to 7d6cd75 Compare April 13, 2020 15:56
@theStack
Copy link
Contributor Author

Updated the functional test commit to also check for the inv/getdata exchange via assert_debug_log, as suggested by MarcoFalke above. Also added the check that no tx is received on a request for filtered blocks if no filter is set.

@theStack theStack force-pushed the 20200406-net-limit_bip37_filter_lifetime branch from 7d6cd75 to fb0434a Compare April 13, 2020 16:53
@theStack theStack force-pushed the 20200406-net-limit_bip37_filter_lifetime branch 2 times, most recently from b1fff4f to 3c76e61 Compare April 14, 2020 12:36
@theStack
Copy link
Contributor Author

Updated with @MarcoFalke's suggestion of explicitely waiting for an inv with specified blockhash via wait_for_inv(...), and checking that getdata was received by the node via assert_debug_log(...).

check the following expected behaviors if no filter is set:
-> filtered block requests are ignored by the node
-> sending a 'filteradd' message is treated as misbehavior
   (i.e. the peer's banscore increases by 100)

also fixes a bug in the on_inv() callback method, which
directly modified the type from BLOCK to FILTERED_BLOCK
in the received 'inv' message rather than just for the reply

Co-authored-by: MarcoFalke <falke.marco@gmail.com>
@theStack theStack force-pushed the 20200406-net-limit_bip37_filter_lifetime branch from 3c76e61 to a9ecbdf Compare April 14, 2020 14:44
@theStack
Copy link
Contributor Author

Force-pushed again, now also fixes the bug described in #18544 (comment). Updated the commit message to also describe the bugfix and include MarcoFalke as co-author.

@maflcko
Copy link
Member

maflcko commented Apr 14, 2020

re-ACK a9ecbdf, only change is in test code 🕙

Show signature and timestamp

Signature:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

re-ACK a9ecbdfcaa, only change is in test code 🕙
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUjCSAwAw+tbTLrCHbMLnWn1DS/3VJnGEQ1JQzbznwQP2Cc0B99cHGKv0CSNZe01
AA2cekeyEPQPg+9R/AGkzbOpDYHjAWcJbevmDc7QG3HuUuU4aChXYdnChl7+fQQU
rqvF/EUvN4pcl2cUQC5bZtamqIgk+hCJESMaukNnI+TAJO1LzTgW0w2P2jSVx8U/
9alcOC8+lP4bS9gG50XsYwyWd9H4JUAMHrY4hzmHFimUCL3VcZSc7ujScfmaxiFP
75Q35cGmLAY9xJF6YW0a5/mqJdcJyX7Jdy2QrsjGGTHNJZGF5hBzV+GvkrPRYFvq
MH3QZOX7Sf46fgRjOeSw+7wUEbyZQsUBOCRgTLJVvj+bFxQ6BbZwjpqhZDYfcfRD
BB9mGtXIWkY2Dky4maZbiMg+VF0idRibH3YlOfrMuf0iBcCsYrtTem7/4ruFPp6W
8ermvDj0fmCrGB53OUq1BrurFboyV2I2KTRcYJgk9QvqtqsMphbYnamPOdLsAPO1
NPkGmKtF
=9zHo
-----END PGP SIGNATURE-----

Timestamp of file with hash 7884d646af42356bdba68ff936ba7ce28889d7b45c67195205417d5cf91f47d6 -

@DrahtBot
Copy link
Contributor

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, 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.

@maflcko maflcko merged commit da4cbb7 into bitcoin:master Apr 20, 2020
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Apr 20, 2020
… 'filterload'..'filterclear')

a9ecbdf test: add more inactive filter tests to p2p_filter.py (Sebastian Falbesoner)
5eae034 net: limit BIP37 filter lifespan (active between 'filterload' and 'filterclear') (Sebastian Falbesoner)

Pull request description:

  This PR fixes bitcoin#18483. On the master branch, there is currently _always_ a BIP37 filter set for every peer: if not a specific filter is set through a `filterload` message, a default match-everything filter is instanciated and pointed to via the `CBloomFilter` default constructor; that happens both initially, when the containing structure `TxRelay` is constructed:

  https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net.h#L812

  and after a loaded filter is removed again through a `filterclear` message:

  https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L3201

  The behaviour was introduced by commit bitcoin@37c6389 (an intentional covert fix for [CVE-2013-5700](bitcoin#18515), according to gmaxwell).

  This default match-everything filter leads to some unintended side-effects:
  1. `getdata` request for filtered blocks (i.e. type `MSG_FILTERED_BLOCK`) are always responded to with `merkleblock`s, even if no filter was set by the peer, see issue bitcoin#18483 (strictly speaking, this is a violation of BIP37) https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L1504-L1507
  2. if a peer sends a `filteradd` message without having loaded a filter via `filterload` before, the intended increasing of the banscore never happens (triggered if `bad` is set to true, a few lines below) https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L3182-L3186

  This PR basically activates the `else`-branch code paths for all checks of `pfilter` again (on the master branch, they are dead code) by limiting the pointer's lifespan: instead of always having a filter set, the `pfilter` is only pointing to a `CBloomFilter`-instance after receiving a `filterload` message and the instance is destroyed again (and the pointer nullified) after receiving a `filterclear` message.

  Here is a before/after comparison in behaviour:
  | code part / scenario                          |    master branch                   |   PR branch                                          |
  | --------------------------------------------- | ---------------------------------- | ---------------------------------------------------- |
  | `getdata` processing for `MSG_FILTERED_BLOCK` | always responds with `merkleblock` | only responds if filter was set via `filterload`     |
  | `filteradd` processing, no filter was loaded  | nothing                            | peer's banscore increases by 100 (i.e. disconnect)   |

  On the other code parts where `pfilter` is checked there is no change in the logic behaviour (except that `CBloomFilter::IsRelevantAndUpdate()` is unnecessarily called and immediately returned in the master branch).
  Note that the default constructor of `CBloomFilter` is only used for deserializing the received `filterload` message and nowhere else. The PR also contains a functional test checking that sending `getdata` for filtered blocks is ignored by the node if no bloom filter is set.

ACKs for top commit:
  MarcoFalke:
    re-ACK a9ecbdf, only change is in test code 🕙

Tree-SHA512: 1a656a6d74ccaf628e7fdca063ba63fbab2089e0b6d0a11be9bbd387c2ee6d3230706ff8ffc1a55711481df3d4547137dd7c9d9184d89eaa43ade4927792d0b6
@jonasschnelli
Copy link
Contributor

This merge broke the master build on bitcoinbuilds.org.
p2p_filter.py seems to fail always since this merge.

See: https://bitcoinbuilds.org/index.php?ansilog=b3d4f4bf-5d4b-4957-9bf3-91ffe4a799fb.log#l2935
Or build 2509: https://bitcoinbuilds.org/index.php?build=2509

maflcko pushed a commit that referenced this pull request Apr 21, 2020
c743718 test: add further BIP37 size limit checks to p2p_filter.py (Sebastian Falbesoner)

Pull request description:

  This is a follow-up PR to #18628. In addition to the hash-functions limit test introduced with commit fa4c29b, it adds checks for the following size limits as defined in [BIP37](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki):

  ad message type `filterload`:
  > The filter itself is simply a bit field of arbitrary byte-aligned size. The maximum size is **36,000 bytes**.

  ad message type `filteradd`:
  > The data field must be smaller than or equal to **520 bytes** in size (the maximum size of any potentially matched object).

  Also introduces new constants for the limits (or reuses the max script size constant in case for the `filteradd` limit).

  Also fixes #18711 by changing the misbehaviour check on "filteradd without filterset" (introduced with #18544) below to also use the more commonly used `assert_debug_log` method.

ACKs for top commit:
  MarcoFalke:
    ACK c743718
  robot-visions:
    ACK c743718
  jonasschnelli:
    utACK c743718. Seems to fix it: https://bitcoinbuilds.org/index.php?build=2524

Tree-SHA512: a03e7639263eb36a381922afb4e1d0ed2ae286f2ad2e7bbd922509a043ddf6cfd08747e01d54d29bfb8f54b66908f653974b9c347e4ca4f43332b586778893be
@maflcko
Copy link
Member

maflcko commented Apr 21, 2020

Yes, it was a silent merge conflict on the test script. Should be fixed now.

@hebasto
Copy link
Member

hebasto commented Apr 21, 2020

Yes, it was a silent merge conflict on the test script. Should be fixed now.

See #18721

sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Apr 23, 2020
…_filter.py

c743718 test: add further BIP37 size limit checks to p2p_filter.py (Sebastian Falbesoner)

Pull request description:

  This is a follow-up PR to bitcoin#18628. In addition to the hash-functions limit test introduced with commit bitcoin@fa4c29b, it adds checks for the following size limits as defined in [BIP37](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki):

  ad message type `filterload`:
  > The filter itself is simply a bit field of arbitrary byte-aligned size. The maximum size is **36,000 bytes**.

  ad message type `filteradd`:
  > The data field must be smaller than or equal to **520 bytes** in size (the maximum size of any potentially matched object).

  Also introduces new constants for the limits (or reuses the max script size constant in case for the `filteradd` limit).

  Also fixes bitcoin#18711 by changing the misbehaviour check on "filteradd without filterset" (introduced with bitcoin#18544) below to also use the more commonly used `assert_debug_log` method.

ACKs for top commit:
  MarcoFalke:
    ACK c743718
  robot-visions:
    ACK c743718
  jonasschnelli:
    utACK c743718. Seems to fix it: https://bitcoinbuilds.org/index.php?build=2524

Tree-SHA512: a03e7639263eb36a381922afb4e1d0ed2ae286f2ad2e7bbd922509a043ddf6cfd08747e01d54d29bfb8f54b66908f653974b9c347e4ca4f43332b586778893be
ComputerCraftr pushed a commit to ComputerCraftr/bitcoin that referenced this pull request Jun 10, 2020
… 'filterload'..'filterclear')

a9ecbdf test: add more inactive filter tests to p2p_filter.py (Sebastian Falbesoner)
5eae034 net: limit BIP37 filter lifespan (active between 'filterload' and 'filterclear') (Sebastian Falbesoner)

Pull request description:

  This PR fixes bitcoin#18483. On the master branch, there is currently _always_ a BIP37 filter set for every peer: if not a specific filter is set through a `filterload` message, a default match-everything filter is instanciated and pointed to via the `CBloomFilter` default constructor; that happens both initially, when the containing structure `TxRelay` is constructed:

  https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net.h#L812

  and after a loaded filter is removed again through a `filterclear` message:

  https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L3201

  The behaviour was introduced by commit bitcoin@37c6389 (an intentional covert fix for [CVE-2013-5700](bitcoin#18515), according to gmaxwell).

  This default match-everything filter leads to some unintended side-effects:
  1. `getdata` request for filtered blocks (i.e. type `MSG_FILTERED_BLOCK`) are always responded to with `merkleblock`s, even if no filter was set by the peer, see issue bitcoin#18483 (strictly speaking, this is a violation of BIP37) https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L1504-L1507
  2. if a peer sends a `filteradd` message without having loaded a filter via `filterload` before, the intended increasing of the banscore never happens (triggered if `bad` is set to true, a few lines below) https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L3182-L3186

  This PR basically activates the `else`-branch code paths for all checks of `pfilter` again (on the master branch, they are dead code) by limiting the pointer's lifespan: instead of always having a filter set, the `pfilter` is only pointing to a `CBloomFilter`-instance after receiving a `filterload` message and the instance is destroyed again (and the pointer nullified) after receiving a `filterclear` message.

  Here is a before/after comparison in behaviour:
  | code part / scenario                          |    master branch                   |   PR branch                                          |
  | --------------------------------------------- | ---------------------------------- | ---------------------------------------------------- |
  | `getdata` processing for `MSG_FILTERED_BLOCK` | always responds with `merkleblock` | only responds if filter was set via `filterload`     |
  | `filteradd` processing, no filter was loaded  | nothing                            | peer's banscore increases by 100 (i.e. disconnect)   |

  On the other code parts where `pfilter` is checked there is no change in the logic behaviour (except that `CBloomFilter::IsRelevantAndUpdate()` is unnecessarily called and immediately returned in the master branch).
  Note that the default constructor of `CBloomFilter` is only used for deserializing the received `filterload` message and nowhere else. The PR also contains a functional test checking that sending `getdata` for filtered blocks is ignored by the node if no bloom filter is set.

ACKs for top commit:
  MarcoFalke:
    re-ACK a9ecbdf, only change is in test code 🕙

Tree-SHA512: 1a656a6d74ccaf628e7fdca063ba63fbab2089e0b6d0a11be9bbd387c2ee6d3230706ff8ffc1a55711481df3d4547137dd7c9d9184d89eaa43ade4927792d0b6
@theStack theStack deleted the 20200406-net-limit_bip37_filter_lifetime branch December 1, 2020 09:55
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Jan 19, 2021
…rclear')

Summary:
> net: limit BIP37 filter lifespan (active between 'filterload' and 'filterclear')
> Previously, a default match-everything bloom filter was set for every peer,
> i.e. even before receiving a 'filterload' message and after receiving a
> 'filterclear' message code branches checking for the existence of the filter
> by testing the pointer "pfilter" were _always_ executed.

> test: add more inactive filter tests to p2p_filter.py
> check the following expected behaviors if no filter is set:
> -> filtered block requests are ignored by the node
> -> sending a 'filteradd' message is treated as misbehavior
>
> also fixes a bug in the on_inv() callback method, which
> directly modified the type from BLOCK to FILTERED_BLOCK
> in the received 'inv' message rather than just for the reply
>
> Co-authored-by: MarcoFalke <falke.marco@gmail.com>

This is a backport of Core [[bitcoin/bitcoin#18544 | PR18544]]

I had to change the last test to use a different way to detect misbehavior, because banscore was deprecated in D8798 (this would have been applied in [[bitcoin/bitcoin#18672 | PR18672]], if the backports had been done in sequential order)

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Differential Revision: https://reviews.bitcoinabc.org/D8949
UdjinM6 added a commit to dashpay/dash that referenced this pull request Mar 22, 2021
…n (active between 'filterload'..'filterclear') (#4043)

* partial backport 18544: net: limit BIP37 filter lifespan (active between 'filterload'..'filterclear')

Previously, a default match-everything bloom filter was set for every peer,
i.e. even before receiving a 'filterload' message and after receiving a
'filterclear' message code branches checking for the existence of the filter
by testing the pointer "pfilter" were _always_ executed.

* net: Match the backport PR a bit more

Co-authored-by: xdustinface <xdustinfacex@gmail.com>
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
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.

BIP37: 'getdata' request for filtered blocks is answered with 'merkleblock's even if no filter is set
6 participants