fuzz: bound some miniscript operations to avoid fuzz timeouts #30197

pull darosior wants to merge 2 commits into bitcoin:master from darosior:2405_desc_fuzz_timeouts changing 3 files +91 −1
  1. darosior commented at 11:07 am on May 30, 2024: member

    Some of the logic in the miniscript module is quadratic. It only becomes an issue for very large uninteresting descriptors (like a thresh with 130k sub-fragments or a fragment with more than 60k nested j: wrappers).

    This PR fixes the two types of fuzz timeouts reported by Marco in #28812 by trying to pinpoint the problematic descriptors through a simple analysis of the string, without limiting the size of the string itself. This is the same approach as was adopted for limiting the depth of derivation paths.

  2. DrahtBot commented at 11:07 am on May 30, 2024: contributor

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

    Code Coverage

    For detailed information about the code coverage, see the test coverage report.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK dergoegge, stickies-v, marcofleon

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

  3. DrahtBot added the label Tests on May 30, 2024
  4. DrahtBot added the label CI failed on May 30, 2024
  5. DrahtBot commented at 3:14 pm on May 30, 2024: contributor

    🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the documentation.

    Possibly this is due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    Leave a comment here, if you need help tracking down a confusing failure.

    Debug: https://github.com/bitcoin/bitcoin/runs/25599793054

  6. fanquake requested review from maflcko on May 31, 2024
  7. fanquake requested review from dergoegge on May 31, 2024
  8. in src/test/fuzz/util/descriptor.cpp:110 in 79c2bc9191 outdated
    105+
    106+bool HasTooManyWrappers(const FuzzBufferType& buff, const int max_wrappers)
    107+{
    108+    auto count{0};
    109+    // We iterate in reverse order to start counting when we encounter a colon.
    110+    for (const auto& ch: buff | std::views::reverse) {
    


    maflcko commented at 11:46 am on May 31, 2024:
    This isn’t available yet. You’ll have to use a reverse iterator manually for now.

    maflcko commented at 12:09 pm on June 5, 2024:
    0    for (const auto& ch: reverse_iterate(buff)) {
    

    maflcko commented at 12:12 pm on June 5, 2024:

    Alternative, if it doesn’t compile with Span:

    0    for (const auto& ch: reverse_iterate(std::span{buff}) {
    

    darosior commented at 12:28 pm on June 5, 2024:
    Sorry, i saw your comment earlier, just didn’t have time to address earlier. Thanks, will push soon.

    maflcko commented at 12:45 pm on June 5, 2024:

    darosior commented at 4:34 pm on June 5, 2024:
    Done (through .rbegin() instead as your std::reverse_iterate suggestion didn’t compile).

    maflcko commented at 7:43 am on June 6, 2024:

    reverse_iterate is a helper in this repo, not in std::. You’ll have to include it to use it.

    Also, it looks like taking a std::span from a Span is not possible, at least on clang-15, according to the CI.

    I think if you instead rebase on fabc9b02331ad6d5cbae3d351721e7e5d9585df0, it should fix itself, as that removes Span.


    darosior commented at 12:21 pm on June 6, 2024:
    Changed to use our own reverse_iterator. Opted to instanciate the std::span from an iterator instead of adding a dependency on #30229. Happy to rebase on top of it if it’s merged first.

    stickies-v commented at 4:51 pm on July 8, 2024:

    note: with #30263 merged, you can now use std::reverse::views again (see #28687)

     0diff --git a/src/test/fuzz/util/descriptor.cpp b/src/test/fuzz/util/descriptor.cpp
     1index 1ead54f254..d0ed6ee94c 100644
     2--- a/src/test/fuzz/util/descriptor.cpp
     3+++ b/src/test/fuzz/util/descriptor.cpp
     4@@ -3,8 +3,8 @@
     5 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
     6 
     7 #include <test/fuzz/util/descriptor.h>
     8-#include <reverse_iterator.h>
     9 
    10+#include <ranges>
    11 #include <stack>
    12 
    13 void MockedDescriptorConverter::Init() {
    14@@ -107,7 +107,7 @@ bool HasTooManyWrappers(const FuzzBufferType& buff, const int max_wrappers)
    15 {
    16     auto count{0};
    17     // We iterate in reverse order to start counting when we encounter a colon.
    18-    for (const auto ch: reverse_iterate(buff)) {
    19+    for (const auto ch: buff | std::views::reverse) {
    20         if (ch == ':') {
    21             count++;
    22         } else if (ch == ',' || ch == '(' || ch == '{') {
    

    darosior commented at 5:01 pm on July 11, 2024:
    Cool, will do since i’ll retouch.
  9. darosior force-pushed on Jun 5, 2024
  10. darosior force-pushed on Jun 6, 2024
  11. DrahtBot removed the label CI failed on Jun 6, 2024
  12. dergoegge commented at 8:39 am on June 13, 2024: member
    #30229 was merged if you want to rebase
  13. darosior force-pushed on Jun 27, 2024
  14. darosior commented at 2:42 pm on June 27, 2024: member
    Rebased on master to take advantage of #30229.
  15. dergoegge approved
  16. dergoegge commented at 10:54 am on July 8, 2024: member
    utACK 178a1da9c4da955c3dc78e6b03f110f687e82508
  17. in src/test/fuzz/util/descriptor.cpp:110 in 178a1da9c4 outdated
    105+
    106+bool HasTooManyWrappers(const FuzzBufferType& buff, const int max_wrappers)
    107+{
    108+    auto count{0};
    109+    // We iterate in reverse order to start counting when we encounter a colon.
    110+    for (const auto ch: reverse_iterate(buff)) {
    


    stickies-v commented at 4:52 pm on July 8, 2024:
    It’s not obvious to me why we need/prefer to iterate in reverse, here?

    stickies-v commented at 4:37 pm on July 11, 2024:
    Resolved as per #30197 (review)
  18. in src/test/fuzz/util/descriptor.h:65 in 178a1da9c4 outdated
    60+
    61+/**
    62+ * Whether the buffer, if it represents a valid descriptor, contains a fragment with more
    63+ * sub-fragments than the given maximum.
    64+ */
    65+bool HasLargeFrag(const FuzzBufferType& buff, const int max_subs = MAX_SUBS);
    


    stickies-v commented at 4:56 pm on July 8, 2024:

    nit: HasTooManySubFrags seems like a more accurate name?

    0bool HasTooManySubFrags(const FuzzBufferType& buff, const int max_subs = MAX_SUBS);
    

    stickies-v commented at 4:45 pm on July 11, 2024:
    nit: I think making max_subs and max_wrappers required args (and calling them with the MAX_SUBS and MAX_WRAPPERS consts) makes for a bit more intuitive code imo, since the max number is arbitrary.

    darosior commented at 5:03 pm on July 11, 2024:
    Yeah, i guess a fragment with too many sub-fragments is a too large fragment. I’ll update since i’ll retouch.

    darosior commented at 5:48 pm on July 11, 2024:
    nit stack overflow: @darosior crashed and won’t be able to process any further nit

    darosior commented at 6:37 pm on July 11, 2024:
    More seriously: i considered doing it but it would not match the existing style for HasDeepDerivPath and would require duplicating the MAX_SUBS and MAX_WRAPPERS args (once in descriptor and once in descriptor_parse). So IMO it would be less neat and therefore i didn’t do it.
  19. in src/test/fuzz/util/descriptor.cpp:115 in 178a1da9c4 outdated
    110+    for (const auto ch: reverse_iterate(buff)) {
    111+        if (ch == ':') {
    112+            count++;
    113+        } else if (ch == ',' || ch == '(' || ch == '{') {
    114+            count = 0;
    115+        } else if (count > 0 && ++count > max_wrappers) {
    


    stickies-v commented at 9:48 pm on July 8, 2024:

    Is this correct? I’d expect something like:

     0diff --git a/src/test/fuzz/util/descriptor.cpp b/src/test/fuzz/util/descriptor.cpp
     1index 1ead54f254..434df93994 100644
     2--- a/src/test/fuzz/util/descriptor.cpp
     3+++ b/src/test/fuzz/util/descriptor.cpp
     4@@ -108,12 +108,10 @@ bool HasTooManyWrappers(const FuzzBufferType& buff, const int max_wrappers)
     5     auto count{0};
     6     // We iterate in reverse order to start counting when we encounter a colon.
     7     for (const auto ch: reverse_iterate(buff)) {
     8-        if (ch == ':') {
     9-            count++;
    10+        if (ch == ':' && (++count > max_wrappers)) {
    11+            return true;
    12         } else if (ch == ',' || ch == '(' || ch == '{') {
    13             count = 0;
    14-        } else if (count > 0 && ++count > max_wrappers) {
    15-            return true;
    16         }
    17     }
    18     return false;
    

    marcofleon commented at 3:08 pm on July 11, 2024:

    Based on the two inputs that caused the timeouts: clusterfuzz-testcase-mocked_descriptor_parse-5224675118481408.bin.not.txt clusterfuzz-testcase-minimized-descriptor_parse-5888623437217792.bin.txt

    I think it’s supposed to be counting the characters before the colon, not colons themselves. This is also why it iterates in reverse. I don’t know quite enough to be sure that all timeout inputs would end up in this format, but based on those two, the function as is seems good.


    stickies-v commented at 4:37 pm on July 11, 2024:

    Ooh, right, because each character is a wrapper, and multiple colons in the same fragment are not allowed. That also answers my question about why we iterate in reverse, and why this implementation is correct.

    This is probably fairly obvious to people familiar with Miniscript, but feel free to adopt this docstring anyway if it’s helpful:

     0    auto count{0};
     1    // A wrapper is a single character followed by a colon, e.g. `t:`.
     2    // The colon is dropped between subsequent wrappers; e.g. `dv:` is
     3    // the `d` wrapper applied to the `v:` wrapper. For that reason, we
     4    // iterate in reverse and start counting when a colon is found until
     5    // the start of the fragment is reached.
     6    for (const auto ch: reverse_iterate(buff)) {
     7        if (ch == ':') {
     8            ++count;
     9        } else if (ch == ',' || ch == '(' || ch == '{') {
    10            count = 0;  // start of fragment
    11        } else if (count > 0 && ++count > max_wrappers) {
    12            return true;
    13        }
    14    }
    15    return false;
    

    darosior commented at 5:04 pm on July 11, 2024:
    Will do. Documentation good.

    darosior commented at 5:07 pm on July 11, 2024:

    I don’t know quite enough to be sure that all timeout inputs would end up in this format, but based on those two, the function as is seems good.

    I can’t guarantee it’d avoid all timeouts, but i’m pretty sure it would catch all timeouts which are due to too many nested wrappers.

  20. stickies-v commented at 9:49 pm on July 8, 2024: contributor
    Concept ACK
  21. marcofleon commented at 3:16 pm on July 11, 2024: contributor
    Lightly tested ACK 178a1da9c4da955c3dc78e6b03f110f687e82508. I tested on the three inputs brought up in #28812 and they all executed in 1ms. I can run the target for a while and see if any other timeouts come up. But looks good to me.
  22. DrahtBot requested review from stickies-v on Jul 11, 2024
  23. in src/test/fuzz/util/descriptor.cpp:112 in 178a1da9c4 outdated
    107+{
    108+    auto count{0};
    109+    // We iterate in reverse order to start counting when we encounter a colon.
    110+    for (const auto ch: reverse_iterate(buff)) {
    111+        if (ch == ':') {
    112+            count++;
    


    stickies-v commented at 4:38 pm on July 11, 2024:

    developer-notes-nit:

    0            ++count;
    

    darosior commented at 5:08 pm on July 11, 2024:
    Arg! It slipped through. Will address as i’ll retouch.
  24. in src/test/fuzz/util/descriptor.cpp:113 in 178a1da9c4 outdated
    108+    auto count{0};
    109+    // We iterate in reverse order to start counting when we encounter a colon.
    110+    for (const auto ch: reverse_iterate(buff)) {
    111+        if (ch == ':') {
    112+            count++;
    113+        } else if (ch == ',' || ch == '(' || ch == '{') {
    


    stickies-v commented at 4:41 pm on July 11, 2024:
    I can’t find any reference to fragments starting with ‘{’ in the BIP, is || ch == '{' necessary?

    darosior commented at 5:13 pm on July 11, 2024:

    Taproot leaves. It’s to avoid overcounting if for instance you have tr(d971308d5463ddc82addd897f6ebfa8a88e86de0664a66b9e9cf5e873459c528,{dvdvdvdvdv:1,1)).

    EDIT: hmm it seems you’d always hit the comma anyways so you’d only overcount by the number of nested {. But it’s cleaner to just not overcount.


    stickies-v commented at 5:16 pm on July 11, 2024:
    Okay, thanks. No strong opinion as to which approach, but since I couldn’t find any reference to it perhaps good to add this example as a docstring in this else if path?

    stickies-v commented at 5:19 pm on July 11, 2024:
    Or alternatively, add a separate else if branch for ignored characters (e.g. “{”), just for clarity?

    darosior commented at 6:13 pm on July 11, 2024:
    It’s in doc/descriptors.md but sure i’ll expand a bit in a comment.
  25. stickies-v approved
  26. stickies-v commented at 4:47 pm on July 11, 2024: contributor

    Light ACK 178a1da9c4da955c3dc78e6b03f110f687e82508

    Code LGTM and approach seems reasonable, but there may be fuzzing and miniscript nuances I’m not considering.

  27. darosior force-pushed on Jul 11, 2024
  28. darosior commented at 6:54 pm on July 11, 2024: member

    Thank you both for the review. I addressed your comments and heavily documented the code. I think it can be helpful as it indeed relies on being familiar with the descriptor/miniscript syntax. If only to make the assumptions in the counting logic explicit, or for when we’ll all have forgotten about the details of this syntax.

    I also modified two small things:

    • I introduced a limit to the number of nested sub-fragments a fragment may have. Since we are pushing an element on top of a stack for every occurrence of a (, the fuzzer might just spit a -max_len (about 1M in my experience) string of (((((((... and we’d create a stack of 1 million integers. It’d waste resources and anyways 10k subs is more than enough for anything interesting.
    • I corrected HasTooManySubs to not start counting at 1 since technically : isn’t a wrapper. Instead i use an optional to detect whether we are counting or not. This also made the code slightly clearer and allowed to better document it.
  29. in src/test/fuzz/util/descriptor.cpp:99 in 63f62b50d2 outdated
    94+bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs)
    95+{
    96+    // We use a stack because there may be many nested sub-frags.
    97+    std::stack<int> counts;
    98+    for (const auto& ch: buff) {
    99+        // The fuzzer may generate an input with a ton of parenthesis. Rule out pathological cases.
    


    stickies-v commented at 1:48 pm on July 12, 2024:

    grammar nit

    0        // The fuzzer may generate an input with a ton of parentheses. Rule out pathological cases.
    

    darosior commented at 3:46 pm on July 14, 2024:
    Thanks, done.
  30. in src/test/fuzz/util/descriptor.cpp:92 in 63f62b50d2 outdated
    86@@ -84,3 +87,63 @@ bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth)
    87     }
    88     return false;
    89 }
    90+
    91+//! Maximum number of nested sub-fragments we'll allow in a descriptor.
    92+constexpr int MAX_NESTED_SUBS{10'000};
    


    stickies-v commented at 2:07 pm on July 12, 2024:

    nit: could be more consistent by not using a global for one and a default param for the other:

    (note: these diffs do not include the return true comment made further down)

     0diff --git a/src/test/fuzz/util/descriptor.cpp b/src/test/fuzz/util/descriptor.cpp
     1index c2aa6d9b6a..804fd1cd33 100644
     2--- a/src/test/fuzz/util/descriptor.cpp
     3+++ b/src/test/fuzz/util/descriptor.cpp
     4@@ -87,16 +87,13 @@ bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth)
     5     return false;
     6 }
     7 
     8-//! Maximum number of nested sub-fragments we'll allow in a descriptor.
     9-constexpr int MAX_NESTED_SUBS{10'000};
    10-
    11-bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs)
    12+bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs, const int max_nested_subs)
    13 {
    14     // We use a stack because there may be many nested sub-frags.
    15     std::stack<int> counts;
    16     for (const auto& ch: buff) {
    17         // The fuzzer may generate an input with a ton of parenthesis. Rule out pathological cases.
    18-        if (counts.size() > MAX_NESTED_SUBS) return false;
    19+        if (counts.size() > max_nested_subs) return false;
    20 
    21         if (ch == '(') {
    22             // A new fragment was opened, create a new sub-count for it and start as one since any fragment with
    23diff --git a/src/test/fuzz/util/descriptor.h b/src/test/fuzz/util/descriptor.h
    24index 77ebd11ebb..22205d41ab 100644
    25--- a/src/test/fuzz/util/descriptor.h
    26+++ b/src/test/fuzz/util/descriptor.h
    27@@ -57,11 +57,14 @@ bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth = MAX_DEPT
    28 
    29 //! Default maximum number of sub-fragments.
    30 constexpr int MAX_SUBS{1'000};
    31+//! Maximum number of nested sub-fragments we'll allow in a descriptor.
    32+constexpr int MAX_NESTED_SUBS{10'000};
    33 
    34 /**
    35  * Whether the buffer, if it represents a valid descriptor, contains a fragment with more
    36  * sub-fragments than the given maximum.
    37  */
    38-bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs = MAX_SUBS);
    39+bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs = MAX_SUBS,
    40+                       const int max_nested_subs = MAX_NESTED_SUBS);
    41 
    42 #endif // BITCOIN_TEST_FUZZ_UTIL_DESCRIPTOR_H
    

    Also, this raises a narrowing warning:

    0  CXX      test/fuzz/util/libtest_fuzz_a-descriptor.o
    1test/fuzz/util/descriptor.cpp:96:27: warning: comparison of integers of different signs: 'std::stack<int>::size_type' (aka 'unsigned long') and 'const int' [-Wsign-compare]
    2        if (counts.size() > max_nested_subs) return false;
    3            ~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~
    41 warning generated.
    

    So using size_t for the max values might make sense?

     0diff --git a/src/test/fuzz/util/descriptor.cpp b/src/test/fuzz/util/descriptor.cpp
     1index c2aa6d9b6a..e3b5218e15 100644
     2--- a/src/test/fuzz/util/descriptor.cpp
     3+++ b/src/test/fuzz/util/descriptor.cpp
     4@@ -87,16 +87,14 @@ bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth)
     5     return false;
     6 }
     7 
     8-//! Maximum number of nested sub-fragments we'll allow in a descriptor.
     9-constexpr int MAX_NESTED_SUBS{10'000};
    10-
    11-bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs)
    12+bool HasTooManySubFrag(const FuzzBufferType& buff, const size_t max_subs,
    13+                       const size_t max_nested_subs)
    14 {
    15     // We use a stack because there may be many nested sub-frags.
    16-    std::stack<int> counts;
    17+    std::stack<size_t> counts;
    18     for (const auto& ch: buff) {
    19         // The fuzzer may generate an input with a ton of parenthesis. Rule out pathological cases.
    20-        if (counts.size() > MAX_NESTED_SUBS) return false;
    21+        if (counts.size() > max_nested_subs) return false;
    22 
    23         if (ch == '(') {
    24             // A new fragment was opened, create a new sub-count for it and start as one since any fragment with
    25diff --git a/src/test/fuzz/util/descriptor.h b/src/test/fuzz/util/descriptor.h
    26index 77ebd11ebb..a379883be8 100644
    27--- a/src/test/fuzz/util/descriptor.h
    28+++ b/src/test/fuzz/util/descriptor.h
    29@@ -56,12 +56,15 @@ constexpr int MAX_DEPTH{2};
    30 bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth = MAX_DEPTH);
    31 
    32 //! Default maximum number of sub-fragments.
    33-constexpr int MAX_SUBS{1'000};
    34+constexpr size_t MAX_SUBS{1'000};
    35+//! Maximum number of nested sub-fragments we'll allow in a descriptor.
    36+constexpr size_t MAX_NESTED_SUBS{10'000};
    37 
    38 /**
    39  * Whether the buffer, if it represents a valid descriptor, contains a fragment with more
    40  * sub-fragments than the given maximum.
    41  */
    42-bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs = MAX_SUBS);
    43+bool HasTooManySubFrag(const FuzzBufferType& buff, const size_t max_subs = MAX_SUBS,
    44+                       const size_t max_nested_subs = MAX_NESTED_SUBS);
    45 
    46 #endif // BITCOIN_TEST_FUZZ_UTIL_DESCRIPTOR_H
    

    darosior commented at 3:44 pm on July 14, 2024:
    Done (in part).

    stickies-v commented at 11:12 am on July 15, 2024:
    Thanks, main concerns are addressed in force push. I’m fine with the current code, but is there a problem I’m not seeing with having max_subs be a size_t, too? No need to retouch, I’m just curious.

    darosior commented at 12:23 pm on July 15, 2024:
    I just went for changing only that which was required. I think of int as “default” and chose to only deviate from the default when needed.
  31. in src/test/fuzz/util/descriptor.cpp:100 in 63f62b50d2 outdated
     95+{
     96+    // We use a stack because there may be many nested sub-frags.
     97+    std::stack<int> counts;
     98+    for (const auto& ch: buff) {
     99+        // The fuzzer may generate an input with a ton of parenthesis. Rule out pathological cases.
    100+        if (counts.size() > MAX_NESTED_SUBS) return false;
    


    stickies-v commented at 2:11 pm on July 12, 2024:
    Should this be return true?

    darosior commented at 3:40 pm on July 14, 2024:
    Ugh, yes, thanks.
  32. in src/test/fuzz/util/descriptor.cpp:129 in 63f62b50d2 outdated
    124+    // We want to detect nested wrappers. A wrapper is a character prepanded to a fragment, separated by a column. There
    125+    // may be more than one wrapper, in which case the colon is not repeated. For instance `jjjjj:pk()`. Since the
    126+    // miniscript parsing logic performs a number of copies quadratic in the number of wrappers, we want to limit too
    127+    // large such occurences. To do so we use the colon to detect the end of a wrapper expression and count how many
    128+    // characters there is since the beginning of the expression. We therefore iterate in reverse to start counting when
    129+    // we encounter a colon and stop once we encounter a character indicating the beginning of a new expression.
    


    stickies-v commented at 2:37 pm on July 12, 2024:

    Suggested simplifications & fixes incorporated in the below snippet:

    • typo fixes prepanded->prependedand column->colon, is->are
    • remove “Since the miniscript parsing logic…want to limit too large such occurences” since that’s not a concern of this function (and already documented in the callsites)
    • remove some duplication in the end
    0    // We want to detect nested wrappers. A wrapper is a character prepended to a fragment, separated by a colon. There
    1    // may be more than one wrapper, in which case the colon is not repeated. For instance `jjjjj:pk()`.
    2    // To count wrappers we iterate in reverse and use the colon to detect the end of a wrapper expression and count how many
    3    // characters there are since the beginning of the expression. We stop counting when we encounter a character indicating the beginning of a new expression.
    

    darosior commented at 3:47 pm on July 14, 2024:
    Taken.
  33. stickies-v commented at 3:00 pm on July 12, 2024: contributor
    Changes look good, thanks for adding more documentation. Nits can be ignored but I think HasTooManySubFrag needs some fixes wrt return statement and type narrowing before ACK.
  34. fuzz: limit the number of sub-fragments per fragment for descriptors
    This target may call into logic quadratic over the number of
    sub-fragments. Limit the number of sub-fragments to keep the runtime
    reasonable.
    
    Thanks to Marco Falke for reporting the fuzz timeouts with a minimized
    input.
    8d7340105f
  35. fuzz: limit the number of nested wrappers in descriptors
    The script building logic performs a quadratic number of copies in the
    number of nested wrappers in the miniscript. Limit the number of nested
    wrappers to avoid fuzz timeouts.
    
    Thanks to Marco Falke for reporting the fuzz timeouts and providing a
    minimal input to reproduce.
    bc34bc2888
  36. darosior force-pushed on Jul 14, 2024
  37. dergoegge approved
  38. dergoegge commented at 9:58 am on July 15, 2024: member
    utACK bc34bc288824978ef4b98e8802b47cb863c8a8c2
  39. DrahtBot requested review from marcofleon on Jul 15, 2024
  40. DrahtBot requested review from stickies-v on Jul 15, 2024
  41. stickies-v approved
  42. stickies-v commented at 11:12 am on July 15, 2024: contributor

    Light ACK bc34bc288824978ef4b98e8802b47cb863c8a8c2

    Code LGTM and approach seems reasonable, but there may be fuzzing and miniscript nuances I’m not considering.

  43. marcofleon commented at 1:08 pm on July 15, 2024: contributor
    Code review ACK bc34bc288824978ef4b98e8802b47cb863c8a8c2. The added comments are useful, thanks for those. Tested on the three inputs in #28812 that caused the timeouts.
  44. fanquake merged this on Jul 15, 2024
  45. fanquake closed this on Jul 15, 2024

  46. maflcko commented at 2:00 pm on July 17, 2024: member
    Should the check be applied to miniscript_string, for example the miniscript_string/ae395bdc087e233d7f8e1844d4814b2c00cc9d21 input, as well?
  47. darosior deleted the branch on Jul 18, 2024
  48. darosior commented at 2:25 pm on July 18, 2024: member
    Yes, makes sense.

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-22 03:12 UTC

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