Increase robustness against UB in secp256k1_scalar_cadd_bit #647

pull roconnor-blockstream wants to merge 2 commits into bitcoin-core:master from roconnor-blockstream:patch-1 changing 1 files +4 −1
  1. roconnor-blockstream commented at 3:26 PM on July 3, 2019: contributor

    Avoid possible, but unlikely undefined behaviour in scalar_low_impl's secp256k1_scalar_cadd_bit. Thanks to elichai2 who noted that the literal 1 is a signed integer, and that shifting a signed 32-bit integer by 31 bits causes an overflow and yields undefined behaviour.

    Using the unsigned literal 1u addresses the issue.

  2. elichai commented at 3:42 PM on July 3, 2019: contributor

    It seems that casting literals in the library is usually done as explicit cast ((uint32_t)1). although I personally don't think it matters too much.

  3. real-or-random commented at 8:52 PM on July 3, 2019: contributor

    Yeah, (uint32_t) is slightly nicer for readability in the context of the if and because *r has type uint32_t but I'll ACK both variants. :man_shrugging:

    ACK 6499ebe, I read the code

  4. real-or-random approved
  5. practicalswift commented at 2:42 PM on July 4, 2019: contributor

    utACK 6499ebea0ad24e000ca7cee442ed2fc6075bffe9

    FWIW, similar case in Bitcoin Core: https://github.com/bitcoin/bitcoin/pull/14510

  6. roconnor-blockstream commented at 10:48 PM on July 4, 2019: contributor

    (uint32_t)1 is probably better since it is guaranteed to have 32 bits.

  7. gmaxwell commented at 1:44 AM on July 5, 2019: contributor

    Please do not open PRs with alarming sounding titles like "possible UB" when there is actually no potential for UB.

    I was contacted by someone asking if there needs to be a CVE for this. I can't blame them for thinking so from the PR title and description, but I believe the concern is entirely misplaced.

    To be specific: secp256k1_scalar_cadd_bit is only ever called with bit==0 or bit==1. As such, UB here is not possible, not merely unlikely.

    Additionally, scalar_low_impl.h is a mock scalar implemented exclusively for tests_exhaustive.c, so if there were UB it would only be in a special test harness, not the actual production implementation.

    In terms of robustness against future codebase changes (or in out of tree extensions), the secp256k1_scalar_cadd_bit interface is documented in scalar.h to have an implicit domain restriction on it's arguments: "The result is not allowed to overflow".

    Calling that implementation secp256k1_scalar_cadd_bit with bit >= ceil(log2(EXHAUSTIVE_TEST_ORDER)) is outside of the function contract. In the codebase EXHAUSTIVE_TEST_ORDER is at most 199, so the largest 'bit' that function could be called with is 7 (as any larger value would be guaranteed to overflow, which is forbidden by the contract). The codebase does not attempt to avoid UB in cases where functions are called with arguments outside of their specified domains (though the codebase does often contain VERIFY_CHECKs for these conditions).

    If the code were changed so that EXHAUSTIVE_TEST_ORDER were set to some really large value, like 4294967291 such that invocation with 31 could make logical sense and not be an automatic violation of the contract in and of itself, the function's existing VERIFY_CHECK would not work correctly because it would fail to detect overflow in some cases. (This isn't much of a concern since the tests would probably take longer than the heat death of the universe with such a large group as the exhaustive tests take time proportional to the order to the eighth power :))...

    In the unit tests for this interface -- which AFAICT don't currently get run for the _low_impl code-- overflow is detected by use of the add function before secp256k1_scalar_cadd_bit so the incomplete verify_check would go undetected by the tests, even if they were run on it.

    I don't see a problem with adding the (uint32_t) annotation, as that would also be consistent with the real implementation, but scalar_low_impl should also fix the anti-overflow VERIFY_CHECK (e.g. by adding an additional ((1<<bit)+EXHAUSTIVE_TEST_ORDER) < 2^32 test; this is somewhat similar to the real implementation's VERIFY_CHECK on bit<256). Any changes here should probably also fix the bracing (the statement should be either braced or one-line), or eliminating it by casting flag like the real implementations' do, and not branching at all. Alternatively, VERIFY_CHECKing that bit<2 would probably be perfectly fine there: if someone wants to extend the use of cadd_bit beyond 0/1, they could update the test implementation.

    This is a situation where a guard ("bit < 32") implies a domain larger than the code is actually specified for, written for, or (arguably) correct for (at least if you include VERIFY_CHECKs as part of the definition of correctness, which is probably reasonable in this pure-test code). But implied domains are not the actual domains of functions although they can sometimes cause misunderstandings when they're assumed to be. Simply adding the cast here does not actually make the function completely correct for the full bit < 32 domain. If the goal of the change is to avoid potential confusion by making the implied domain match the actual domain, it isn't achieved by this change. If the goal is just to silence some (meat based?) static analysis tool that is reasoning only from the locally implied domain-- then I suppose it does.

    Regardless, please avoid using potentially hyperbolic language for such things, at least without simply grepping to see what the actual risk is. If someone else does so-- after all, if you're not sure or mistaken, that's fine--, reviewers should also be pointing out that they believe there is no potential for misbehaviour if they've reviewed it and determined as much. After all, presumably everyone's first review step was determining if the issue was actually serious or not. Specifically calling out cases where the change makes no functional difference also can help expose miscommunication between participants: E.g. if I screwed up in determining that this is a nothing-burger roconnor now has the opportunity to disagree. If instead I concluded it did nothing but it was harmless and ACKed it anyways, then we'd miss an opportunity to expose that disagreement... and then maybe fail to resolve the behaviour completely as a result (e.g. I think the function can only be called with 0 or 1, but if roconnor knew if could be called with 31 due to something I missed-- the code would still not be completely correct after this change).

    The seriousness of UB means that any possibility of it will sensibly get treated by some as an emergency. This is very much not something that should be treated as an emergency, and IMO it would be perfectly fine to just close this and do nothing as well.

  8. roconnor-blockstream renamed this:
    Possible UB in secp256k1_scalar_cadd_bit
    Increase robustness against UB in secp256k1_scalar_cadd_bit
    on Jul 5, 2019
  9. roconnor-blockstream commented at 1:48 AM on July 5, 2019: contributor

    Sorry.

    Edit: FWIW, the issue is that the bit < 32 guard is clearly there to ostensibly prevent the UB of bitshifting more than the type's width (as it serves no other correctness purpose), however it fails to prevent the UB of overflowing a signed integer value. If we decide we don't want to guard against UB, then we should remove the bit < 32 guard, since it isn't doing its job, otherwise we should fix the code so that it does successfully guard against UB. That said, my title did end up more alarming than I intended, and I'll try to do better in the future.

  10. gmaxwell commented at 1:50 AM on July 5, 2019: contributor

    Thanks for the rename, that's good.

  11. Increase robustness against UB.
    Thanks to elichai2 who noted that the literal '1' is a signed integer, and that shifting a signed 32-bit integer by 31 bits causes an overflow and yields undefined behaviour.
    While 'scalar_low_impl''s 'secp256k1_scalar_cadd_bit' is only used for testing purposes and currently the 'bit' parameter is only 0 or 1, it is better to avoid undefined behaviour in case the used domain of 'secp256k1_scalar_cadd_bit' expands.
    8fe63e5654
  12. roconnor-blockstream force-pushed on Jul 5, 2019
  13. roconnor-blockstream force-pushed on Jul 5, 2019
  14. roconnor-blockstream commented at 2:50 AM on July 5, 2019: contributor

    I'm not sure why there is an #ifdef VERIFY around the VERIFY_CHECK, and I'm tempted to remove the #ifdefs.

  15. gmaxwell commented at 4:30 AM on July 5, 2019: contributor

    FWIW, the issue is that the bit < 32 guard is clearly there to ostensibly prevent the UB of bitshifting more than the type's width (as it serves no other correctness purpose),

    Right, or set it to 31 instead of 32. For consistency with the real functions though I think it should have a verify check on bit which is consistent with the size of the scalar. E.g. the production versions of this function VERIFY_CHECK that bit<256, which is necessary for the normal SECP256k1 and eliminate the runtime branch. I'm only somewhat unsure of this approach because the actual limit to check depends on the configured group size.

    As far as the ifdefs go, there are a few places where ifdefs are needed to prevent compile errors (dereferencing fields that don't exist in non-verify builds) and a few places where ifdefs are needed to prevent unused function warnings. I don't think either apply here, but I see that the secp256k1_scalar_check_overflow is being VERIFY guarded consistently, so maybe I'm missing something.

  16. roconnor-blockstream force-pushed on Jul 5, 2019
  17. roconnor-blockstream commented at 4:33 AM on July 5, 2019: contributor

    I'm totally fine with setting the guard to be bit < 31 as a solution.

  18. real-or-random commented at 12:34 PM on July 5, 2019: contributor

    FWIW, I was aware that this is not possible in current codebase when I acked it. But yes, I should have pointed that out for clarity... I had a similar case just two days ago: https://github.com/bitcoin/bitcoin/pull/16158#issuecomment-508049038. I wasn't aware that people get worried by PR titles, hm.

    ACK 57f25c8, I read the code.

  19. practicalswift commented at 2:35 PM on July 5, 2019: contributor

    utACK 57f25c866f86e0e55c0b8200ad448b803d88f6be

  20. Improve VERIFY_CHECK of overflow in secp256k1_scalar_cadd_bit.
    This added check ensures that any curve order overflow doesn't go undetected due a uint32_t overflow.
    0d82732a9a
  21. in src/scalar_low_impl.h:45 in 57f25c866f outdated
      37 | @@ -38,8 +38,10 @@ static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a,
      38 |  
      39 |  static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag) {
      40 |      if (flag && bit < 32)
      41 | -        *r += (1 << bit);
      42 | +        *r += ((uint32_t)1 << bit);
      43 |  #ifdef VERIFY
      44 | +    VERIFY_CHECK(bit < 32);
      45 | +    VERIFY_CHECK(((uint32_t)1 << bit) - 1 <= UINT32_MAX - EXHAUSTIVE_TEST_ORDER);
    


    sipa commented at 9:58 PM on August 6, 2019:

    I don't understand this line. Shouldn't it be VERIFY_CHECK(((uint32_t)1 << bit) < EXHAUSTIVE_TEST_ORDER);?


    real-or-random commented at 10:52 PM on August 6, 2019:

    Or even better, it should check if overflow in r has happened (not just in (uint32_t)1 << bit.


    roconnor-blockstream commented at 11:44 PM on August 6, 2019:

    My intention here was to check to see there exists any (valid) value of r such that adding 1 << bit would exceed UINT32_MAX, rather than just checking that this value of r doesn't overflow. So the code as written verifies (1 << bit) + (EXHAUSTIVE_TEST_ORDER - 1) <= UINT32_MAX.

    Maybe I shouldn't be the one editing this code. :)


    real-or-random commented at 10:48 AM on August 7, 2019:

    My point was that the documentation of this function say that r is not allowed to overflow (and that means "overflow as a scalar", not "overflow the underlying integer type"). So I think this is what we should check, and this is consistent with the other implementations. BUT yes, this is already checked, it's just one line below, and I haven't seen it, nevermind!

    For your line, we were just not clever enough to figure out what it is supposed to to. In fact Greg has suggested this, now that I read it again...

    So I'm fine with this as it is currently but a comment may be useful

        /* Verify that adding (1 << bit) will not overflow any in-range scalar *r by overflowing the underlying uint32_t. /*
        VERIFY_CHECK(((uint32_t)1 << bit) - 1 <= UINT32_MAX - EXHAUSTIVE_TEST_ORDER);
    

    roconnor-blockstream commented at 5:36 PM on August 7, 2019:

    Done.

  22. roconnor-blockstream force-pushed on Aug 7, 2019
  23. real-or-random commented at 12:39 PM on August 20, 2019: contributor

    ACK 0d82732a9a16cecc445e61c718ce9bdc2d228e76

  24. jonasnick approved
  25. jonasnick commented at 10:51 AM on October 28, 2019: contributor

    ACK 0d82732a9a16cecc445e61c718ce9bdc2d228e76

  26. real-or-random referenced this in commit 137d304a6b on Oct 28, 2019
  27. real-or-random merged this on Oct 28, 2019
  28. real-or-random closed this on Oct 28, 2019

  29. sipa cross-referenced this on Jun 9, 2020 from issue Update libsecp256k1 subtree by sipa
  30. fanquake referenced this in commit 8c97780db8 on Jun 13, 2020
  31. sidhujag referenced this in commit 8a3a072968 on Jun 13, 2020
  32. ComputerCraftr referenced this in commit b98f1c6e6c on Jun 16, 2020
  33. UdjinM6 referenced this in commit 9d36ba6570 on Aug 10, 2021
  34. 5tefan referenced this in commit 8ded2caa74 on Aug 12, 2021
  35. gades referenced this in commit d855cc511d on May 8, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin-core/secp256k1. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-14 18:15 UTC

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