Avoid implementation-defined and undefined behavior when dealing with sizes #578

pull real-or-random wants to merge 4 commits into bitcoin-core:master from real-or-random:der-len changing 3 files +43 βˆ’36
  1. real-or-random commented at 1:30 pm on December 14, 2018: contributor

    This is a result of auditing the code for overflow issues at random places. None of this is critical but I think all of it should be fixed.

    I know this touches “red” code. I double-checked and triple-checked this but I can understand if some of the changes are not desirable because they change well-tested code.

    Best reviewed in individual commits.

  2. Fix possible integer overflow in DER parsing
    If we’re in the last loop iteration, then `lenleft == 1` and it could
    be the case that `ret == MAX_SIZE`, and so `ret +  lenleft` will
    overflow to 0 and the sanity check will not catch it. Then we will
    return `(int) MAX_SIZE`, which should be avoided because this value is
    implementation-defined. (However, this is harmless because
    `(int) MAX_SIZE == -1` on all supported platforms.)
    3cb057f842
  3. gmaxwell commented at 7:58 am on December 15, 2018: contributor
    Aside, haven’t reviewed yet… but your first commit messages appear to be exactly the sort of thing I want to see on a PR like that– it gives a trace of the execution it’s concerned with. I want to merge it just so I can point people in the future at what this sort of change should look like.
  4. in src/ecdsa_impl.h:49 in 96a9984de0 outdated
    45@@ -46,68 +46,71 @@ static const secp256k1_fe secp256k1_ecdsa_const_p_minus_order = SECP256K1_FE_CON
    46     0, 0, 0, 1, 0x45512319UL, 0x50B75FC4UL, 0x402DA172UL, 0x2FC9BAEEUL
    47 );
    48 
    49-static int secp256k1_der_read_len(const unsigned char **sigp, const unsigned char *sigend) {
    50-    int lenleft, b1;
    51-    size_t ret = 0;
    52+static int secp256k1_der_read_len(size_t *len, const unsigned char **sigp, const unsigned char *sigend) {
    


    laanwj commented at 1:33 pm on February 21, 2019:
    Does this need a not-NULL check/assertion for len?

    real-or-random commented at 5:17 pm on February 21, 2019:
    sounds like a good idea

    luke-jr commented at 7:54 pm on February 21, 2019:
    n.b. This returns via output pointer instead of return value, to prevent accidentally casting to int (or other wrong types) implicitly.
  5. in src/ecdsa_impl.h:87 in 96a9984de0 outdated
     98-        ret = (ret << 8) | **sigp;
     99-        if (ret + lenleft > (size_t)(sigend - *sigp)) {
    100-            /* Result exceeds the length of the passed array. */
    101-            return -1;
    102-        }
    103+        *len = (*len << 8) | **sigp;
    


    laanwj commented at 1:35 pm on February 21, 2019:
    No initial value is set for *len?

    real-or-random commented at 5:16 pm on February 21, 2019:
    Good catch
  6. in contrib/lax_der_parsing.c:54 in 96a9984de0 outdated
    50@@ -51,7 +51,7 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_
    51     lenbyte = input[pos++];
    52     if (lenbyte & 0x80) {
    53         lenbyte -= 0x80;
    54-        if (pos + lenbyte > inputlen) {
    55+        if (lenbyte > inputlen - pos) {
    


    luke-jr commented at 7:18 pm on February 21, 2019:
    pos could possibly be larger than inputlen here, since it was just post-incremented on line 51 without a check.

    real-or-random commented at 4:18 am on February 22, 2019:
    Right before line 51, we have pos < inputlen since we have pos != input due to the check in line 48. So after the increment in line 51, we have pos <= inputlen
  7. in contrib/lax_der_parsing.c:92 in 96a9984de0 outdated
    88@@ -89,7 +89,7 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_
    89     lenbyte = input[pos++];
    90     if (lenbyte & 0x80) {
    91         lenbyte -= 0x80;
    92-        if (pos + lenbyte > inputlen) {
    93+        if (lenbyte > inputlen - pos) {
    


    luke-jr commented at 7:25 pm on February 21, 2019:
    Same here, pos is incremented without check at line 89.

    real-or-random commented at 4:18 am on February 22, 2019:
    Same here too
  8. in src/ecdsa_impl.h:69 in 3cb057f842 outdated
    65@@ -66,7 +66,7 @@ static int secp256k1_der_read_len(const unsigned char **sigp, const unsigned cha
    66         return -1;
    67     }
    68     /* X.690-207 8.1.3.5 long form length octets */
    69-    lenleft = b1 & 0x7F;
    70+    lenleft = b1 & 0x7F; /* lenleft is at least 1 */
    


    luke-jr commented at 7:38 pm on February 21, 2019:
    ACK, b1 comes from an unsigned char, and the function returns before this point if b1 < 0x80 (ruling out a value of 0) or b1 == 0x80 (the only value that would yield a 0 from this mask)
  9. in src/ecdsa_impl.h:85 in 3cb057f842 outdated
    81@@ -82,13 +82,13 @@ static int secp256k1_der_read_len(const unsigned char **sigp, const unsigned cha
    82     }
    83     while (lenleft > 0) {
    84         ret = (ret << 8) | **sigp;
    85-        if (ret + lenleft > (size_t)(sigend - *sigp)) {
    


    luke-jr commented at 7:42 pm on February 21, 2019:

    This looks like it was wrong. If the remaining digits are all zero, the final size is just ret, not ret + ....

    If my understanding is correct, is there a possibility fixing this bug could create a a consensus incompatibility?


    real-or-random commented at 10:58 pm on February 21, 2019:

    [edit: Deleted what I wrote here because it was wrong. But did you see the commit message? I think that explains the change. If not, I don’t understand your sentence about the remaining digits]

    It should be noted that none of this is code is consensus-critical (for Bitcoin) because Bitcoin relies on lax DER parsing function (as implemented in the contrib directory of secp256k1). The change may affect other projects though. AFAIK Zcash uses the functions in the PR here. But none of my changes are intended to change the behavior (except removing UB and IDB).


    real-or-random commented at 3:12 am on February 22, 2019:

    This looks like it was wrong. If the remaining digits are all zero, the final size is just ret, not ret + ....

    Ah I think now I know what you are saying. It took me also a while to understand that code. The purpose of ret + lenleft instead of ret is a (not really useful) early return: this function needs to read lenleft more bytes (or actually lenleft - 1 bytes but we didn’t increment sigp* yet), and the caller needs to read at least ret more bytes. So if lenleft + ret is more bytes than available, it is indeed correct to return -1 early.

  10. in src/ecdsa_impl.h:135 in 96a9984de0 outdated
    130-        /* Skip leading zero bytes */
    131+    /* There is at most one leading zero byte:
    132+     * if there were two leading zero bytes, we would have failed and returned 0
    133+     * because of excessive 0x00 padding already. */
    134+    if (rlen > 0 && **sig == 0) {
    135+        /* Skip leading zero byte */
    


    luke-jr commented at 7:44 pm on February 21, 2019:
    utACK
  11. in src/ecdsa_impl.h:164 in 5f04e82bc1 outdated
    160-        return 0;
    161-    }
    162-    if (sig + rlen != sigend) {
    163-        /* Garbage after tuple. */
    164+    if (rlen != (size_t)(sigend - sig)) {
    165+        /* Tuple exceeds bounds or garage after tuple. */
    


    luke-jr commented at 7:46 pm on February 21, 2019:
    utACK
  12. in src/hash_impl.h:135 in 5f04e82bc1 outdated
    130@@ -131,7 +131,8 @@ static void secp256k1_sha256_transform(uint32_t* s, const uint32_t* chunk) {
    131 static void secp256k1_sha256_write(secp256k1_sha256 *hash, const unsigned char *data, size_t len) {
    132     size_t bufsize = hash->bytes & 0x3F;
    133     hash->bytes += len;
    134-    while (bufsize + len >= 64) {
    135+    VERIFY_CHECK(hash->bytes >= len);
    136+    while (len >= 64 - bufsize) {
    


    luke-jr commented at 7:49 pm on February 21, 2019:
    utACK
  13. real-or-random commented at 9:06 pm on February 22, 2019: contributor
    Addressed the comments
  14. gmaxwell commented at 10:17 pm on May 22, 2019: contributor
    Can you squash?
  15. Parse DER-enconded length into a size_t instead of an int
    This avoids a possibly implementation-defined signed (int) to unsigned
    (size_t) conversion portably.
    01ee1b3b3c
  16. Avoid out-of-bound pointers and integer overflows in size comparisons
    This changes pointer calculations in size comparions to a form that
    ensures that no out-of-bound pointers are computed, because even their
    computation yields undefined behavior.
    Also, this changes size comparions to a form that ensures that neither
    the left-hand side nor the right-hand side can overflow.
    ec8f20babd
  17. Simplify control flow in DER parsing 14c7dbd444
  18. real-or-random force-pushed on May 23, 2019
  19. real-or-random commented at 1:23 pm on May 23, 2019: contributor
    squashed
  20. gmaxwell merged this on May 29, 2019
  21. gmaxwell closed this on May 29, 2019

  22. gmaxwell referenced this in commit 544435fc90 on May 29, 2019
  23. in src/ecdsa_impl.h:164 in 14c7dbd444
    165         return 0;
    166     }
    167-    if (sig + rlen != sigend) {
    168-        /* Garbage after tuple. */
    169+    if (rlen != (size_t)(sigend - sig)) {
    170+        /* Tuple exceeds bounds or garage after tuple. */
    


    real-or-random commented at 12:16 pm on May 29, 2019:
    I admit that “garage after tuple” seems to be an unrealistic concern. πŸ”§πŸš—
  24. sipa cross-referenced this on Jun 9, 2020 from issue Update libsecp256k1 subtree by sipa
  25. fanquake referenced this in commit 8c97780db8 on Jun 13, 2020
  26. sidhujag referenced this in commit 8a3a072968 on Jun 13, 2020
  27. ComputerCraftr referenced this in commit b98f1c6e6c on Jun 16, 2020
  28. real-or-random cross-referenced this on Jul 29, 2020 from issue Remove lax DER parsing functions from contrib by real-or-random
  29. UdjinM6 referenced this in commit 9d36ba6570 on Aug 10, 2021
  30. 5tefan referenced this in commit 8ded2caa74 on Aug 12, 2021
  31. 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: 2024-11-25 03:15 UTC

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