Avoid dividing by zero in EstimateMedianVal (policy)/ConnectTip (validation)/CreateTransaction (wallet) #14239

pull practicalswift wants to merge 1 commits into bitcoin:master from practicalswift:undefined-behaviour-division-by-zero changing 4 files +16 −11
  1. practicalswift commented at 1:32 pm on September 17, 2018: contributor

    Avoid diving by zero in:

    • EstimateMedianVal (policy)
    • ConnectTip (validation)
    • CreateTransaction (wallet)

    These are real nontheoretical cases in the sense that they are easily reachable.

    Steps to reproduce:

    0./configure --with-sanitizers=undefined && make check && ./test/functional/test_runner.py
    
  2. fanquake added the label Refactoring on Sep 17, 2018
  3. practicalswift commented at 1:38 pm on September 17, 2018: contributor
    @fanquake Refactoring? :-)
  4. practicalswift renamed this:
    Avoid undefined behaviour in EstimateMedianVal (policy)/ConnectTip (validation)/CreateTransaction (wallet)
    Avoid UB (divide by zero) in EstimateMedianVal (policy)/ConnectTip (validation)/CreateTransaction (wallet)
    on Sep 17, 2018
  5. practicalswift renamed this:
    Avoid UB (divide by zero) in EstimateMedianVal (policy)/ConnectTip (validation)/CreateTransaction (wallet)
    Avoid dividing by zero (undefined behaviour) in EstimateMedianVal (policy)/ConnectTip (validation)/CreateTransaction (wallet)
    on Sep 17, 2018
  6. MarcoFalke commented at 6:56 pm on September 17, 2018: member
    Concept ACK. Could enable the functional tests in travis in this commit?
  7. practicalswift commented at 7:03 pm on September 17, 2018: contributor
    @MarcoFalke Unfortunately it turned out that these were not the only undefined behaviours we’re triggering in the functional tests. I’ll submit a separate PR that enables the functional tests with suppressions for everything we’re triggering :-)
  8. in src/validation.cpp:2445 in 3b1283aa98 outdated
    2441@@ -2442,17 +2442,23 @@ bool CChainState::ConnectTip(CValidationState& state, const CChainParams& chainp
    2442             return error("%s: ConnectBlock %s failed, %s", __func__, pindexNew->GetBlockHash().ToString(), FormatStateMessage(state));
    2443         }
    2444         nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2;
    2445-        LogPrint(BCLog::BENCH, "  - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO, nTimeConnectTotal * MILLI / nBlocksTotal);
    2446+        if (nBlocksTotal != 0) {
    


    promag commented at 9:47 pm on September 17, 2018:
    I don’t think nBlocksTotal can be zero? So at this point we could just assert(nBlocksTotal > 0) and ignore these remaining changes.

    practicalswift commented at 4:28 am on September 18, 2018:

    nBlocksTotal can be zero: this UB is triggered when running the functional tests.

    As said in the PR description:

    UBSAN in Travis running the test suite would have catched all three of these.

    :-)

  9. in src/policy/fees.cpp:377 in 3b1283aa98 outdated
    381-             passBucket.withinTarget, passBucket.totalConfirmed, passBucket.inMempool, passBucket.leftMempool,
    382-             failBucket.start, failBucket.end,
    383-             100 * failBucket.withinTarget / (failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool),
    384-             failBucket.withinTarget, failBucket.totalConfirmed, failBucket.inMempool, failBucket.leftMempool);
    385-
    386+    if (passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool != 0 && failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool != 0) {
    


    promag commented at 10:00 pm on September 17, 2018:
    Can any of this sums be zero? I guess no otherwise this was raised before. Just assert? Same below in wallet.cpp.

    practicalswift commented at 4:29 am on September 18, 2018:

    Yes they can be zero: this UB is triggered when running the functional tests.

    As said in the PR description:

    UBSAN in Travis running the test suite would have catched all three of these.

    :-)


    promag commented at 4:24 pm on November 23, 2018:

    I have 2 suggestions:

    1. could save sum, which also helps understanding the condition?
    2. use > instead of !=?

    So:

    0auto pass_bucket_sum = passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool;
    1if (pass_bucket_sum > 0 && ...) {
    2      ...
    3      100 * passBucket.withinTarget / pass_bucket_sum,
    4      ...
    5}
    
  10. practicalswift force-pushed on Sep 27, 2018
  11. practicalswift commented at 8:05 pm on September 27, 2018: contributor
    Rebased! :-)
  12. practicalswift commented at 7:00 am on October 1, 2018: contributor

    @fanquake Is refactoring really the correct label for this PR?

    Definitions:

    1. Code refactoring is defined as the process of restructuring existing computer code without changing its external behaviour.
    2. Triggering undefined behaviour is defined as executing computer code whose behaviour is not prescribed by the language specification to which the code adheres, for the current state of the program.

    Since undefined behaviour is a superset of external behaviour it follows that a PR fixing UB cannot be considered refactoring, no? :-)

    I suggest labelling all UB fixes with the bug label so that they get the attention they deserve.

  13. practicalswift commented at 1:05 pm on October 9, 2018: contributor
    @fanquake Ping? :-)
  14. practicalswift commented at 9:45 pm on October 18, 2018: contributor

    @fanquake I’m afraid the “refactoring” label is causing the UB:s (this and others) to go under the radar and thus remain unfixed.

    Please consider giving the UB PR:s a more appropriate label :-) Please at least talk to me my fellow contributor – I’m your friend :-)

  15. sipa removed the label Refactoring on Oct 20, 2018
  16. MarcoFalke referenced this in commit 1ba5583646 on Nov 5, 2018
  17. MarcoFalke deleted a comment on Nov 5, 2018
  18. MarcoFalke commented at 8:11 pm on November 5, 2018: member
    Could remove the suppression from the file in contrib?
  19. practicalswift force-pushed on Nov 6, 2018
  20. practicalswift commented at 9:00 am on November 6, 2018: contributor

    Added commit 8ed2af7ca980345995ed4115570e4677ed0b2f47 which remove the UBSan suppressions for the fixed files.

    Please re-review :-)

  21. DrahtBot commented at 9:49 pm on November 8, 2018: member

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

    Conflicts

    No conflicts as of last run.

  22. DrahtBot added the label Needs rebase on Nov 23, 2018
  23. practicalswift force-pushed on Nov 23, 2018
  24. practicalswift commented at 3:58 pm on November 23, 2018: contributor
    Rebased!
  25. DrahtBot removed the label Needs rebase on Nov 23, 2018
  26. practicalswift force-pushed on Nov 24, 2018
  27. practicalswift force-pushed on Nov 24, 2018
  28. practicalswift commented at 11:49 am on November 24, 2018: contributor
    @promag Good points! Now addressed. Please re-review :-)
  29. practicalswift commented at 7:58 pm on January 15, 2019: contributor
    @MarcoFalke Could this one get a release milestone? :-)
  30. MarcoFalke added this to the milestone 0.18.0 on Jan 15, 2019
  31. practicalswift force-pushed on Jan 30, 2019
  32. Empact commented at 10:25 pm on January 30, 2019: member
    Rather than if (pass_bucket_sum > 0 && fail_bucket_sum > 0), how about something like: pass_bucket_sum ? 100 * passBucket.withinTarget / pass_bucket_sum : -1 or similar with a string value result such as “N/A”.?
  33. practicalswift force-pushed on Jan 31, 2019
  34. practicalswift commented at 8:47 am on January 31, 2019: contributor
    @Empact Fixed. Please re-review :-)
  35. practicalswift force-pushed on Jan 31, 2019
  36. promag commented at 3:32 pm on January 31, 2019: member

    utACK 914553d.

    But I wonder if logs with -1 matter at all. If not then it could be avoided by incrementing the denominator, like sum = a + b + 1 and then the ternary can be removed..

  37. laanwj commented at 3:54 pm on February 6, 2019: member

    But I wonder if logs with -1 matter at all. If not then it could be avoided by incrementing the denominator, like sum = a + b + 1 and then the ternary can be removed..

    Agree, I’m not sure logging -1 is so great here. The meaning of this would be unclear to the user.

  38. practicalswift commented at 4:09 pm on February 6, 2019: contributor
    @laanwj @promag Makes sense! I’ll revert to the original version :-)
  39. laanwj added the label Wallet on Feb 6, 2019
  40. Empact commented at 12:39 pm on February 7, 2019: member
    How about keeping the log message in those cases and adapting it to be more clear than -1, e.g. "N/A"?
  41. practicalswift force-pushed on Feb 7, 2019
  42. practicalswift commented at 9:28 pm on February 7, 2019: contributor
    Now reverted to the original version. Please re-review. @Empact That will make the diff unnecessarily large since %d cannot be used for "N/A". I think the current version is simpler and therefore preferred :-)
  43. practicalswift force-pushed on Feb 7, 2019
  44. in src/policy/fees.cpp:377 in c4a88550c5 outdated
    373@@ -374,14 +374,18 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
    374         failBucket.leftMempool = failNum;
    375     }
    376 
    377-    LogPrint(BCLog::ESTIMATEFEE, "FeeEst: %d %s%.0f%% decay %.5f: feerate: %g from (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
    378+    auto pass_bucket_sum = passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool;
    


    promag commented at 9:36 pm on February 7, 2019:
    nit, could specify type instead of auto.
  45. promag commented at 9:36 pm on February 7, 2019: member
    utACK c4a88550, nit, also could be squashed.
  46. practicalswift force-pushed on Feb 7, 2019
  47. practicalswift commented at 9:43 pm on February 7, 2019: contributor
    @promag Feedback addressed. Please re-review.
  48. promag commented at 9:59 pm on February 7, 2019: member
    utACK 2ce49a16, thanks.
  49. Empact commented at 10:27 pm on February 7, 2019: member

    I’m not sure it’s better to not have undefined behavior while also not having a logging statement in those cases, than to propagate the available information, with undefined behavior.

    In fairness I’m also not clear on what situations the undefined behavior is associated with and how valuable the information is in those cases.

  50. DrahtBot added the label Needs rebase on Feb 8, 2019
  51. Avoid dividing by zero e559c73483
  52. practicalswift force-pushed on Feb 8, 2019
  53. DrahtBot removed the label Needs rebase on Feb 8, 2019
  54. in src/validation.cpp:1828 in e559c73483
    1824@@ -1825,6 +1825,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
    1825     uint256 hashPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash();
    1826     assert(hashPrevBlock == view.GetBestBlock());
    1827 
    1828+    nBlocksTotal++;
    


    laanwj commented at 3:25 pm on February 10, 2019:
    uuuuhm, what is this change? I don’t think it’s supposed to touch validation at all! I think you’re doing an accidental reversion here.

    practicalswift commented at 4:07 pm on February 10, 2019:

    See #15283 for a description of the problem:

    During the loading of the genesis block, the bench print lines in ConnectTip divide by zero due to early return in ConnectBlock.

    Do you mean it has been fixed elsewhere since being discussed in #15283?


    gmaxwell commented at 4:23 pm on February 10, 2019:

    nBlocksTotal appears to be only referenced otherwise from bench output. And I assume what this is trying to do is avoid the genesis block early terminate from leaving the bench output with a zero. I believe this is a reasonable change, though it will make bench results slightly incomparable (with small numbers of blocks processed) across versions.

    This change would have been better done– easier to review for correctness, more stable against “regression”– by simply changing the existing initialization constant for nBlocksTotal from 0 to 1.

  55. gmaxwell commented at 4:17 pm on February 10, 2019: contributor
    Floating point division by zero is well defined (by IEEE 754, see the C standard Annex F). The justification given for this change is incorrect.
  56. practicalswift renamed this:
    Avoid dividing by zero (undefined behaviour) in EstimateMedianVal (policy)/ConnectTip (validation)/CreateTransaction (wallet)
    Avoid dividing by zero in EstimateMedianVal (policy)/ConnectTip (validation)/CreateTransaction (wallet)
    on Feb 10, 2019
  57. practicalswift commented at 8:13 pm on February 10, 2019: contributor

    @gmaxwell

    The value representation of floating-point types is implementation-defined.

    For an IEEE 754 implementation (std::numeric_limits<double>::is_iec559 == true), floating point division by zero is well defined.

    Otherwise it is undefined.

    Please correct me if I’m wrong.

  58. moneyball commented at 6:31 am on February 11, 2019: contributor

    I am not familiar with the code base so take this with a grain of salt. I was curious about the change in validation.cpp though, and I thought another pair of eyes cannot hurt. From my observation:

    1. A search of the repo indicates nBlocksTotal is only referenced in validation.cpp.
    2. nBlocksTotal is only used in 2 functions, ConnectBlock and ConnectTip.
    3. nBlocksTotal is initialized to 0, is incremented on another line of code, and the other 11 instances it is a divisor in a LogPrint.
    4. The change made causes the nBlocksTotal counter to include the genesis block, so it will always be one higher than prior to this change. Since it is only used in logging statements, this should not affect functionality at all.
    5. The logging statements in ConnectBlock would not have been vulnerable to this division by zero risk as they would not have been executed for the genesis block due to the return statement for that special case.
    6. The logging statements in ConnectTip might be affected by division by zero as ConnectTip is called by ActivateBestChain, which is called from several places in the code base. I didn’t study it further to see if it would ever be called prior to ConnectBlock() for the second time, but given the several instances of calling it sprinkled throughout the code base, it is a good idea to protect against division by zero. [as an aside, would division by zero in a logging statement terminate the process and kill the node? although in this particular example, it’d happen immediately after validating just the first couple of blocks so not really that critical]

    This change appears worthwhile and doesn’t seem harmful to me.

  59. gmaxwell commented at 8:26 pm on February 11, 2019: contributor
    NAK
  60. practicalswift commented at 8:34 pm on February 11, 2019: contributor
    @gmaxwell Why?
  61. sipa commented at 8:41 pm on February 11, 2019: member

    @gmaxwell In C++11, division by zero is undefined behavior. Of course all architectures we currently support very likely follow IEEE 754 which makes it well-defined, so calling this a fix for actual UB is an exaggeration.

    Still, I think it’s an improvement to the codebase in my opinion.

  62. gmaxwell commented at 8:43 pm on February 11, 2019: contributor
    Because the behaviour isn’t undefined. If you cannot manage to get the basis motivation for the change factually correct what confidence should we have that the change does not break consensus or somehow introduce a vulnerability? Given that the PR doesn’t give a (valid) justification for an improvement to the software, it isn’t clear to me that it’s worth review efforts to determine that its actually free of harmful side-effects and certainly shouldn’t be made without such review, it certainly isn’t worth yet another worthless language wonking debate.
  63. practicalswift commented at 8:46 pm on February 11, 2019: contributor

    @gmaxwell You didn’t see my reply from yesterday? :-)

    The value representation of floating-point types is implementation-defined.

    For an IEEE 754 implementation (std::numeric_limits<double>::is_iec559 == true), floating point division by zero is well defined.

    Otherwise it is undefined.

    Please correct me if I’m wrong.

  64. sipa commented at 8:50 pm on February 11, 2019: member

    @practicalswift Please, don’t turn a discussion about language semantics into an absolute what is UB and what not.

    I am fairly confident that on all platforms we support, this is not UB, as they follow IEEE 754.

    It may be desirable to strictly follow C++11 here, but that’s a code quality discussion, not one about software bugs.

  65. practicalswift commented at 8:52 pm on February 11, 2019: contributor
    @sipa I simply refuted @gmaxwell’s technically incorrect claim. I’m allowed intellectual self-defence, right? :-)
  66. sipa commented at 8:54 pm on February 11, 2019: member
    @practicalswift I don’t see any incorrect claim or refutation of that. I only see a disagreement about what language requirements to assume.
  67. practicalswift commented at 8:55 pm on February 11, 2019: contributor
    @sipa That was the point I made yesterday :-)
  68. gmaxwell commented at 9:00 pm on February 11, 2019: contributor

    @sipa In C++11, while allowing all possible implementations and ignoring language annexes, assuming an “int” can represent a value outside of [-32768, 32767] is undefined behaviour. Do you intend to accept PRs going through the codebase to fix all code relative to that?

    With these change the codebase is still not unconditionally correct or safe on some hypothetical system which has arbitrary non-IEEE-754-like semantics (e.g. go look at the fee estimator calculations). Nor do I think it’s plausible to make it so without just eliminating the floating point entirely. (and, as an aside, if we wanted to limit the codebase to the language without optional appendexes like annex f then we’d have to eliminate floating point regardless)

    This is a silence-a-tool make-work change– of low but non-zero value– being disguised as a fix of a serious and concerning problem. The result of handling it that way is that it both gets inadequate review and diverts attention from more useful activities, causing an increased risk of actual bugs.

  69. sipa commented at 9:14 pm on February 11, 2019: member

    @practicalswift I understand you’re on a crusade against anything that smells like UB under the strictest definitions of the language. I support improvements in that regard, but discussions like this only piss people off.

    We’re writing software for real systems, and even have the luxury of rather tight control over what architectures are supported. There are plenty of assumptions made that restrict correct operation to certain systems. A certain subset of those also mean that things detected by ubsan and others are spurious.

    Sorry, but claiming that a fix is needed “because UB in C++” is crying wolf. Especially when not taking the reality into account, it is overly dramatic, distracts from far more important issues, and won’t get you much goodwill from reviewers.

    I think this is a meaningful code quality improvement, but you just justify it as “this lets us remove the silencing of divide-by-zero in ubsan, making it a more powerful check for future issues, and it’s just a small change” - not as “UB! Must fix at all costs”, and then turn it into an endless semantics discussion.

  70. MarcoFalke removed this from the milestone 0.18.0 on Feb 11, 2019
  71. sipa commented at 9:48 pm on February 11, 2019: member

    Alternative suggestion:

    Add a static_assert(std::numeric_limits<double>::is_iec559 == true) to the code, and remove the explicit enabling of float division by zero in ubsan.

  72. practicalswift closed this on Feb 11, 2019

  73. practicalswift commented at 10:04 pm on February 11, 2019: contributor
    @sipa Yes, that’s a good idea – I thought about taking that route too. As long as we’re verifying the assumptions we’re making I’m happy (obviously).
  74. laanwj referenced this in commit 95801902b9 on Feb 15, 2019
  75. practicalswift deleted the branch on Apr 10, 2021
  76. PastaPastaPasta referenced this in commit 2ed7317a9a on Jun 27, 2021
  77. PastaPastaPasta referenced this in commit 87cd2776e5 on Jun 28, 2021
  78. PastaPastaPasta referenced this in commit 9276c78497 on Jun 29, 2021
  79. PastaPastaPasta referenced this in commit 1197a1f392 on Aug 16, 2021
  80. PastaPastaPasta referenced this in commit 857fae9277 on Aug 16, 2021
  81. PastaPastaPasta referenced this in commit 009f0059b9 on Aug 17, 2021
  82. PastaPastaPasta referenced this in commit b98e643250 on Aug 18, 2021
  83. gades referenced this in commit 7c4d74079c on Apr 20, 2022
  84. DrahtBot locked this on Aug 18, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-18 21:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me