Skip to content

Conversation

maflcko
Copy link
Member

@maflcko maflcko commented Nov 1, 2021

A snapshot contains the utxo set, including the out value. To activate the snapshot, the hash needs to be calculated. As a side-effect, the total amount in the snapshot is calculated (as the sum of all out values), but never used. Instead of running into an integer overflow in an unused result, don't calculate the result in the first place.

Other code paths (using the active utxo set) can not run into an integer overflow, since the active utxo set is valid.

Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=39716

@maflcko
Copy link
Member Author

maflcko commented Nov 1, 2021

@sipa
Copy link
Member

sipa commented Nov 1, 2021

How does activating a snapshot trigger an integer overflow in this code in the first place? That seems far more concerning, and this PR just patches it up.

@maflcko
Copy link
Member Author

maflcko commented Nov 1, 2021

@sipa Added three more sentences to OP to explain this refactor.

@jamesob
Copy link
Contributor

jamesob commented Nov 1, 2021

Hm, I'm kind of confused - is the concern here that a user could be fed a bad snapshot that causes an overflow? Because if that's the case, the hash of the UTXO set won't match the one hardcoded in chainparams' m_assumeutxo_data.

If it's not a bad snapshot you're worried about, then as far as I can tell the same wraparound issue would exist with a normal gettxoutsetinfo call at the height of the snapshot.

@maflcko
Copy link
Member Author

maflcko commented Nov 1, 2021

Correct.

If we don't care about integer overflows when activating invalid snapshots, then this pull can be closed.

@sipa
Copy link
Member

sipa commented Nov 1, 2021

Would it make sense to instead compute the total amount as an std::optional, which starts at {0}, and is incremented with the read values, but when an out-of-range value is read, or the sum goes out-of-range, is replaced with std::nullopt (and then stays at std::nullopt)? That would categorically remove this concern, and pushes the responsibility for checking sanity to the caller (where it apparently differs).

@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 1, 2021

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

Conflicts

No conflicts as of last run.

@maflcko
Copy link
Member Author

maflcko commented Nov 2, 2021

Switched to std::optional approach

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

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

Concept ACK

This PR allows preventing integer overflow of coin_stats.total_amount while activating the snapshot.

The following points explain the PR in greater detail:

  1. The function AdditionOverflow has been moved from src/test/fuzz/util.h to a new file src/util/overflow.h. The dependencies of other files are updated accordingly.
  2. Unit tests are added for this function, which tests on various edge cases in the case of both signed and unsigned elements.
  3. The AdditionOverflow function is used in the coinstats::ApplyStats function to prevent the risk of an overflow of CAmount. In case if there might be an integer overflow, the total_amount var is set to nullopt.

Though I have not rigorously tested this PR yet, I think the code is logically coherent.

I have just one doubt I would like to point out.

In src/test/util_tests.cpp file:
From the line BOOST_CHECK(TestAddMatrix<signed>()); I inferred that this function was made to be used with signed integers. If I am correct, then there is one logical error:

assert(TestAdd(mini, mini));

This might not always be true.

For example:

  • mini = 2; mini + mini = 4 > 2 (in range) TestAdd result == true
  • mini = -2; mini + mini = -4 < -2 (out of range) TestAdd result == false

@maflcko
Copy link
Member Author

maflcko commented Nov 2, 2021

This might not always be true.

MINI is a compile time constant (just force pushed to rename it from mini). It allows for the test checking for underflow to be less verbose. Checking for underflow is only needed when adding signed integers. Adding two unsigned integers can only overflow.

@luke-jr
Copy link
Member

luke-jr commented Nov 2, 2021

Seems like the number is useful for human verification. We should probably log it or tell advanced users somewhere.

Maybe instead, abort activation of snapshots that exceed MAX_MONEY?

@maflcko
Copy link
Member Author

maflcko commented Nov 2, 2021

Maybe instead, abort activation of snapshots that exceed MAX_MONEY?

The amounts are already covered by the hash, which is obviously trusted and verified. Thus, it will abort activation.

Starting to check random consensus rules on the snapshot is going to be an open ended issue (why not check that there are no unspendable scriptPubKeys in the snapshot, ...) that doesn't give any benefit.

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

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

Checking for underflow is only needed when adding signed integers. Adding two unsigned integers can only overflow.

Makes sense to me now. Since TestAddMatrix() is being used only for signed integer. The minimum possible value ( = MINI) must be < 0. In that case the assertion condition assert(TestAdd(MINI, MINI)); will never fail.

crACK fa13888

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

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

reACK fa4a86e

Changes since my last review:

  • Added a new function CheckedAdd (in overflow.h), which returns nullopt if there is an addition overflow (or underflow) and otherwise returns the sum.
  • TestAdd function (in util_tests.cpp) has been removed, and its usage is replaced with CheckedAdd.
  • Finally, CheckedAdd is used to simplify added code in the coinstats.cpp file.

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

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

reACK fa72733

Changes since my last review:

  • Rebased the branch on the current master.

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

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

Concept ACK fa72733

@maflcko
Copy link
Member Author

maflcko commented Dec 17, 2021

Addressed test nits in force push. Should be trivial to re-ACK with:

git range-diff bitcoin-core/master --word-diff-regex=. fa72733b12 fa996c58e8

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

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

reACK fa996c5

Changes since my last review:

  • Addressed suggestion under this comment.
  • stats.total_amount -> stats.total_amount.has_value()
  • Replaced assert with BOOST_CHECK in src/test/util_tests.cpp file.

I agree with the rationale behind the changes discussed under the above comment.

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

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

ACK fa996c5

@maflcko maflcko merged commit e31cdb0 into bitcoin:master Jan 5, 2022
@maflcko maflcko deleted the 2111-fuzzIntAmt branch January 5, 2022 09:38
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jan 6, 2022
…hen activating snapshot

fa996c5 refactor: Avoid integer overflow in ApplyStats when activating snapshot (MarcoFalke)
fac0188 Move AdditionOverflow to util, Add CheckedAdd with unit tests (MarcoFalke)
fa526d8 Add dev doc to CCoinsStats::m_hash_type and make it const (MarcoFalke)
faff051 style: Remove unused whitespace (MarcoFalke)

Pull request description:

  A snapshot contains the utxo set, including the out value. To activate the snapshot, the hash needs to be calculated. As a side-effect, the total amount in the snapshot is calculated (as the sum of all out values), but never used. Instead of running into an integer overflow in an unused result, don't calculate the result in the first place.

  Other code paths (using the active utxo set) can not run into an integer overflow, since the active utxo set is valid.

  Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=39716

ACKs for top commit:
  shaavan:
    reACK fa996c5
  vasild:
    ACK fa996c5

Tree-SHA512: 4f207f634841f6f634fd02ae1e5907e343fd767524fd0e8149aa99fa9a1834fe50167d14874834d45236e9c325d567925f28129bacb7d80be29cf22277a16a14
@bitcoin bitcoin locked and limited conversation to collaborators Jan 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants