interpreter: Use safer casting in IsValidSignatureEncoding(...) #12611

pull practicalswift wants to merge 1 commits into bitcoin:master from practicalswift:safer-casting-in-IsValidSignatureEncoding changing 1 files +1 −1
  1. practicalswift commented at 8:57 AM on March 6, 2018: contributor

    Use safer casting in IsValidSignatureEncoding(...).

    This was found when looking at the c-lightning codebase which contains a copy of this function.

  2. practicalswift force-pushed on Mar 6, 2018
  3. practicalswift renamed this:
    consensus: Use safer casting in IsValidSignatureEncoding(...)
    interpreter: Use safer casting in IsValidSignatureEncoding(...)
    on Mar 6, 2018
  4. interpreter: Use safer casting in IsValidSignatureEncoding(...)
    This was found when looking at the c-lightning codebase which
    contains a copy of this function.
    332ecf9b49
  5. practicalswift force-pushed on Mar 6, 2018
  6. fanquake added the label Refactoring on Mar 6, 2018
  7. fanquake added the label Consensus on Mar 6, 2018
  8. eklitzke commented at 8:07 AM on March 11, 2018: contributor

    Can you explain what makes this safer? The types inside the existing cast are already unsigned, and AFAIK size_t is at least as large as unsigned int so I don't see how this affects overflow semantics.

  9. practicalswift commented at 7:07 PM on March 12, 2018: contributor

    Consider the case:

    unsigned int lenR = 4294967295; // std::numeric_limits<unsigned int>::max()
    unsigned int lenS = 0;
    

    On my system the following then holds true:

    (size_t)lenR + (size_t)lenS + 7 == 4294967302
    (size_t)(lenR + lenS + 7) == 6
    

    The latter case is the result of an unsigned integer overflow since 4294967295 + 7 cannot be represented as an unsigned int.

    (Luckily both lenR and lenS are guaranteed to be in the closed range [0, 255] as the code is currently written.)

  10. eklitzke commented at 7:06 AM on March 13, 2018: contributor

    I see, and now that you point it out it makes sense. This PR is probably not the place for it, but I'm curious what you think about making these kinds of semantics better defined (e.g. by using -fwrapv, or related flags).

  11. practicalswift commented at 11:30 AM on March 14, 2018: contributor

    @eklitzke Is that an utACK? :-)

    Regarding -ftrapv for catching signed arithmetic overflows – see #12686.

  12. eklitzke commented at 1:20 PM on March 15, 2018: contributor

    utACK 332ecf9b497dda7d4faaea50768b104dd2666bf5

  13. practicalswift commented at 12:23 PM on May 2, 2018: contributor

    @sipa @laanwj @theuni Would you mind reviewing? :-)

  14. theuni commented at 10:50 PM on May 2, 2018: member

    (Luckily both lenR and lenS are guaranteed to be in the closed range [0, 255] as the code is currently written.)

    This code is basically one constraint after another, each enabling the safety of the next check. The very first constraints (sig size checks) pretty much rule out conversion issues further down. So IMO it's not so much "luckily" that way, so much as it is necessarily that way.

    Consider the case:

    unsigned int lenR = 4294967295; // std::numeric_limits<unsigned int>::max()
    unsigned int lenS = 0;
    

    In that case, we would've already gone off the rails with an OOB read before the code in question:

    unsigned int lenS = sig[5 + lenR];
    

    I think this change actually obscures the code because it's not clear at all why you'd cast those two values individually.

    The intent would be much more clear if the sig.size() were cast and stored as a uint8_t, then used for the rest of the function for comparison rather than size(). At least then the ranges would be 100% obvious. I'm not actually advocating for that change, though.

    IMO this is fine as-is. -0.

  15. sipa commented at 11:37 PM on May 2, 2018: member

    I have no opinion.

  16. promag commented at 11:59 PM on May 2, 2018: member

    Just change lenRand lenS to unsigner char?

  17. practicalswift commented at 9:47 AM on May 3, 2018: contributor

    @theuni Do you mean like this?

    diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
    index 2cdff7e..afcb6d5 100644
    --- a/src/script/interpreter.cpp
    +++ b/src/script/interpreter.cpp
    @@ -121,24 +121,26 @@ bool static IsValidSignatureEncoding(const std::vector<unsigned char> &sig) {
         if (sig.size() < 9) return false;
         if (sig.size() > 73) return false;
    
    +    uint8_t signatureSize = static_cast<uint8_t>(sig.size());
    +
         // A signature is of type 0x30 (compound).
         if (sig[0] != 0x30) return false;
    
         // Make sure the length covers the entire signature.
    -    if (sig[1] != sig.size() - 3) return false;
    +    if (sig[1] != signatureSize - 3) return false;
    
         // Extract the length of the R element.
         unsigned int lenR = sig[3];
    
         // Make sure the length of the S element is still inside the signature.
    -    if (5 + lenR >= sig.size()) return false;
    +    if (5 + lenR >= signatureSize) return false;
    
         // Extract the length of the S element.
         unsigned int lenS = sig[5 + lenR];
    
         // Verify that the length of the signature matches the sum of the length
         // of the elements.
    -    if ((size_t)(lenR + lenS + 7) != sig.size()) return false;
    +    if ((size_t)(lenR + lenS + 7) != signatureSize) return false;
    
         // Check whether the R element is an integer.
         if (sig[2] != 0x02) return false;
    
  18. practicalswift commented at 6:01 AM on June 29, 2018: contributor

    Ping @theuni :-)

  19. MarcoFalke commented at 9:14 AM on June 29, 2018: member

    There hasn't been any activity in the last month and half of the reviewers consider the current code already 100% safe and easier to read. Could be closed, maybe?

  20. practicalswift commented at 9:24 AM on June 29, 2018: contributor

    @MarcoFalke In a comment above @theuni wrote "The intent would be much more clear if the sig.size() were cast and stored as a uint8_t". I agree with that and that's why I asked for a clarification before updating the PR and asking for a re-review.

  21. theuni commented at 5:06 PM on June 29, 2018: member

    The intent would be much more clear if the sig.size() were cast and stored as a uint8_t, then used for the rest of the function for comparison rather than size(). At least then the ranges would be 100% obvious. I'm not actually advocating for that change, though.

    This code is safe as-is. While your proposed changes looks fine too, I just don't see any benefit in risking the change.

  22. practicalswift commented at 6:19 PM on June 29, 2018: contributor

    @theuni Thanks! Now closing :-)

  23. practicalswift closed this on Jun 29, 2018

  24. practicalswift deleted the branch on Apr 10, 2021
  25. 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: 2026-04-16 15:15 UTC

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