Skip to content

Conversation

practicalswift
Copy link
Contributor

Avoid invalid integer negation in FormatMoney and ValueFromAmount.

Fixes #20402.

Before this patch:

$ CC=clang CXX=clang++ ./configure --with-sanitizers=undefined
$ make -C src/ test/test_bitcoin
$ src/test/test_bitcoin -t rpc_tests/rpc_format_monetary_values -t util_tests/util_FormatMoney
core_write.cpp:21:29: runtime error: negation of -9223372036854775808 cannot be represented in type 'CAmount'
  (aka 'long'); cast to an unsigned type to negate this value to itself
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior core_write.cpp:21:29 in 
test/rpc_tests.cpp(186): error: in "rpc_tests/rpc_format_monetary_values": 
  check ValueFromAmount(std::numeric_limits<CAmount>::min()).write() == "-92233720368.54775808" has failed 
  [--92233720368.-54775808 != -92233720368.54775808]
util/moneystr.cpp:16:34: runtime error: negation of -9223372036854775808 cannot be represented in type 'CAmount'
  (aka 'long'); cast to an unsigned type to negate this value to itself
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior util/moneystr.cpp:16:34 in 
test/util_tests.cpp(1188): error: in "util_tests/util_FormatMoney":
  check FormatMoney(std::numeric_limits<CAmount>::min()) == "-92233720368.54775808" has failed 
  [--92233720368.-54775808 != -92233720368.54775808]

After this patch:

$ CC=clang CXX=clang++ ./configure --with-sanitizers=undefined
$ make -C src/ test/test_bitcoin
$ src/test/test_bitcoin -t rpc_tests/rpc_format_monetary_values -t util_tests/util_FormatMoney

@laanwj
Copy link
Member

laanwj commented Nov 19, 2020

I think something is wrong if such a large value is passed in the first place. But the code change looks correct to me anyhow.

@practicalswift
Copy link
Contributor Author

practicalswift commented Nov 19, 2020

@laanwj I agree it should be rare, but there is at least one real world scenario where such an extreme value can be passed without it being a bug at the call site. In PrioritiseTransaction we call FormatMoney(nFeeDelta) on fee deltas. Fee deltas are allowed to be outside of the normal MoneyRange(…) as pointed out by luke-jr in another PR.

AFAICT we don't put a lower or upper bound on fee deltas (besides being CAmount of course), so I thought it was safest to define FormatMoney and ValueFromAmount for the entire CAmount including the std::numeric_limits<CAmount>::min() corner case :)

Some additional context: #20383 (comment) :)

@RandyMcMillan
Copy link
Contributor

I wasn't able to find a test that covered the negative COIN value scenarios.
Wouldn't It would make sense to test those scenarios since they show up on a basic truth table?

n % COIN

 n %  COIN -->   1  
 n % -COIN -->   1  
-n %  COIN -->  -1  
-n % -COIN -->  -1  

@practicalswift practicalswift force-pushed the avoid-invalid-integer-negation-in-FormatMoney branch from 61e6c03 to abe78e8 Compare November 20, 2020 21:31
@practicalswift
Copy link
Contributor Author

@RandyMcMillan I'm not sure I can think of a scenario when the constant COIN would be <= 0 but I've added a static_assert to make the assumption of COIN > 0 crystal clear for readers of the code :)

@RandyMcMillan
Copy link
Contributor

Thanks - I was just thinking thru different scenarios for testing. Negative moduli aren't something that comes up very often. :)

@practicalswift practicalswift force-pushed the avoid-invalid-integer-negation-in-FormatMoney branch 2 times, most recently from f710c50 to 681e92c Compare November 20, 2020 23:05
@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 21, 2020

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.

@practicalswift practicalswift force-pushed the avoid-invalid-integer-negation-in-FormatMoney branch from 681e92c to 8d9979e Compare November 23, 2020 10:48
@laanwj
Copy link
Member

laanwj commented Nov 23, 2020

Thanks. Code review ACK 8d9979e

Copy link
Member

@luke-jr luke-jr left a comment

Choose a reason for hiding this comment

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

utACK, but... is there a reason we don't just leave the sign in quotient?

@practicalswift
Copy link
Contributor Author

@luke-jr Do you mean in FormatMoney? Leaving the sign in quotient would change the output compared to how it works today, no? This PR is meant as a pure refactoring (aside from the avoidance of the invalid integer negation): I don't want to introduce any change to the output format in this PR.

@luke-jr
Copy link
Member

luke-jr commented Nov 25, 2020

Right now, we're checking amount < 0 explicitly and prefixing -. If we simply don't negate quotient, strprintf will do the - for us...

@practicalswift
Copy link
Contributor Author

@luke-jr I'll let others chime in, and I'll happily adjust to the consensus opinion regarding the suggested extra refactoring :)

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.

review ack

@practicalswift practicalswift force-pushed the avoid-invalid-integer-negation-in-FormatMoney branch from 8d9979e to 6043ae1 Compare November 30, 2020 16:04
@practicalswift
Copy link
Contributor Author

practicalswift commented Nov 30, 2020

Thanks for reviewing! Nits addressed (+ rebase: sorry!). Please re-review :)

@practicalswift
Copy link
Contributor Author

practicalswift commented Dec 2, 2020

When fuzzing the RPC interface I stumbled upon this case again: decoderawtransaction ff0000000001000000000000008004ff0400fffe001f00 will trigger the problematic code path :)

If anyone wants to test this PR vs master:

$ git checkout master
$ CC=clang CXX=clang++ ./configure --with-sanitizers=address,undefined
$ make
$ src/bitcoind &
$ src/bitcoin-cli decoderawtransaction ff0000000001000000000000008004ff0400fffe001f00
core_write.cpp:21:29: runtime error: negation of -9223372036854775808 cannot be represented in type 'CAmount' (aka 'long'); cast to an unsigned type to negate this value to itself
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior core_write.cpp:21:29 in
error: couldn't parse reply from server

The malformed JSON response can be found here.

@practicalswift practicalswift force-pushed the avoid-invalid-integer-negation-in-FormatMoney branch 2 times, most recently from 2662109 to bd8926d Compare December 27, 2020 19:48
@practicalswift
Copy link
Contributor Author

This PR has three stale ACKs (@laanwj, @MarcoFalke, @luke-jr) and I believe all feedback has been addressed.

Anything left to do here? :)

Would be nice to have this addressed to be able to fuzz direct and indirect callers of FormatMoney and ValueFromAmount with UBSan enabled without hitting these invalid integer negations over and over from different call sites :)

@practicalswift
Copy link
Contributor Author

This PR has three stale ACKs and I believe all feedback has been addressed.

Ready for merge after re-review?

…(const CAmount& n) well-defined also when n is std::numeric_limits<CAmount>::min()
…omAmount(const CAmount& n) well-defined also when n is std::numeric_limits<CAmount>::min()
@practicalswift practicalswift force-pushed the avoid-invalid-integer-negation-in-FormatMoney branch from bd8926d to 1f05dbd Compare March 2, 2021 16:07
@laanwj
Copy link
Member

laanwj commented Mar 3, 2021

re-ACK 1f05dbd

@laanwj laanwj merged commit 47b99ab into bitcoin:master Mar 3, 2021
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Mar 3, 2021
@practicalswift practicalswift deleted the avoid-invalid-integer-negation-in-FormatMoney branch April 10, 2021 19:44
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 16, 2022
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.

Invalid integer negation in FormatMoney(CAmount n) and ValueFromAmount (CAmount n) when n = std::numeric_limits<CAmount>::min()
7 participants