|
4 | 4 |
|
5 | 5 | #include <test/fuzz/util/descriptor.h>
|
6 | 6 |
|
| 7 | +#include <ranges> |
| 8 | +#include <stack> |
| 9 | + |
7 | 10 | void MockedDescriptorConverter::Init() {
|
8 | 11 | // The data to use as a private key or a seed for an xprv.
|
9 | 12 | std::array<std::byte, 32> key_data{std::byte{1}};
|
@@ -84,3 +87,59 @@ bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth)
|
84 | 87 | }
|
85 | 88 | return false;
|
86 | 89 | }
|
| 90 | + |
| 91 | +bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs, const size_t max_nested_subs) |
| 92 | +{ |
| 93 | + // We use a stack because there may be many nested sub-frags. |
| 94 | + std::stack<int> counts; |
| 95 | + for (const auto& ch: buff) { |
| 96 | + // The fuzzer may generate an input with a ton of parentheses. Rule out pathological cases. |
| 97 | + if (counts.size() > max_nested_subs) return true; |
| 98 | + |
| 99 | + if (ch == '(') { |
| 100 | + // A new fragment was opened, create a new sub-count for it and start as one since any fragment with |
| 101 | + // parentheses has at least one sub. |
| 102 | + counts.push(1); |
| 103 | + } else if (ch == ',' && !counts.empty()) { |
| 104 | + // When encountering a comma, account for an additional sub in the last opened fragment. If it exceeds the |
| 105 | + // limit, bail. |
| 106 | + if (++counts.top() > max_subs) return true; |
| 107 | + } else if (ch == ')' && !counts.empty()) { |
| 108 | + // Fragment closed! Drop its sub count and resume to counting the number of subs for its parent. |
| 109 | + counts.pop(); |
| 110 | + } |
| 111 | + } |
| 112 | + return false; |
| 113 | +} |
| 114 | + |
| 115 | +bool HasTooManyWrappers(const FuzzBufferType& buff, const int max_wrappers) |
| 116 | +{ |
| 117 | + // The number of nested wrappers. Nested wrappers are always characters which follow each other so we don't have to |
| 118 | + // use a stack as we do above when counting the number of sub-fragments. |
| 119 | + std::optional<int> count; |
| 120 | + |
| 121 | + // We want to detect nested wrappers. A wrapper is a character prepended to a fragment, separated by a colon. There |
| 122 | + // may be more than one wrapper, in which case the colon is not repeated. For instance `jjjjj:pk()`. To count |
| 123 | + // wrappers we iterate in reverse and use the colon to detect the end of a wrapper expression and count how many |
| 124 | + // characters there are since the beginning of the expression. We stop counting when we encounter a character |
| 125 | + // indicating the beginning of a new expression. |
| 126 | + for (const auto ch: buff | std::views::reverse) { |
| 127 | + // A colon, start counting. |
| 128 | + if (ch == ':') { |
| 129 | + // The colon itself is not a wrapper so we start at 0. |
| 130 | + count = 0; |
| 131 | + } else if (count) { |
| 132 | + // If we are counting wrappers, stop when we crossed the beginning of the wrapper expression. Otherwise keep |
| 133 | + // counting and bail if we reached the limit. |
| 134 | + // A wrapper may only ever occur as the first sub of a descriptor/miniscript expression ('('), as the |
| 135 | + // first Taproot leaf in a pair ('{') or as the nth sub in each case (','). |
| 136 | + if (ch == ',' || ch == '(' || ch == '{') { |
| 137 | + count.reset(); |
| 138 | + } else if (++*count > max_wrappers) { |
| 139 | + return true; |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + return false; |
| 145 | +} |
0 commit comments