diff options
author | Pieter Wuille <pieter@wuille.net> | 2023-02-25 16:19:12 -0500 |
---|---|---|
committer | Pieter Wuille <pieter@wuille.net> | 2023-02-28 09:42:33 -0500 |
commit | 56e37e71a2538a240cc360678aeb752d17bd8f45 (patch) | |
tree | 741685ae23fda0fbfb1cc7524d3b359eb01213a8 /src/test/fuzz | |
parent | bcec5ab4ff1039c0c309dbbb9953adbd0a4f3e88 (diff) |
Make miniscript fuzzers avoid script size limit
Use the same technique as is using in the FromString miniscript parser to
predict the final script size of the miniscript being generated in the
miniscript_stable and miniscript_smart fuzzers (by counting every unexplored
sub node as 1 script byte, which is possible because every leaf node always
adds at least 1 byte). This allows bailing out early if the script being
generated would exceed the maximum allowed size (before actually constructing
the miniscript, as that may happen only significantly later potentially).
Also add a self-check to make sure this predicted script size matches that
of generated scripts.
Diffstat (limited to 'src/test/fuzz')
-rw-r--r-- | src/test/fuzz/miniscript.cpp | 14 |
1 files changed, 13 insertions, 1 deletions
diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp index 9597c289fa..f3a48daf2c 100644 --- a/src/test/fuzz/miniscript.cpp +++ b/src/test/fuzz/miniscript.cpp @@ -769,6 +769,9 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { std::vector<std::pair<Type, std::optional<NodeInfo>>> todo{{root_type, {}}}; /** Predict the number of (static) script ops. */ uint32_t ops{0}; + /** Predict the total script size (every unexplored subnode is counted as one, as every leaf is + * at least one script byte). */ + uint32_t scriptsize{1}; while (!todo.empty()) { // The expected type we have to construct. @@ -777,7 +780,11 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { // Fragment/children have not been decided yet. Decide them. auto node_info = ConsumeNode(type_needed); if (!node_info) return {}; - // Update predicted resource limits. + // Update predicted resource limits. Since every leaf Miniscript node is at least one + // byte long, we move one byte from each child to their parent. A similar technique is + // used in the miniscript::internal::Parse function to prevent runaway string parsing. + scriptsize += miniscript::internal::ComputeScriptLen(node_info->fragment, ""_mst, node_info->subtypes.size(), node_info->k, node_info->subtypes.size(), node_info->keys.size()) - 1; + if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; switch (node_info->fragment) { case Fragment::JUST_0: case Fragment::JUST_1: @@ -834,6 +841,8 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { ops += 3; break; case Fragment::WRAP_V: + // We don't account for OP_VERIFY here; that will be corrected for when the actual + // node is constructed below. break; case Fragment::WRAP_J: ops += 4; @@ -885,8 +894,10 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { // Update resource predictions. if (node->fragment == Fragment::WRAP_V && node->subs[0]->GetType() << "x"_mst) { ops += 1; + scriptsize += 1; } if (ops > MAX_OPS_PER_SCRIPT) return {}; + if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; // Move it to the stack. stack.push_back(std::move(node)); todo.pop_back(); @@ -894,6 +905,7 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { } assert(stack.size() == 1); assert(stack[0]->GetStaticOps() == ops); + assert(stack[0]->ScriptSize() == scriptsize); stack[0]->DuplicateKeyCheck(KEY_COMP); return std::move(stack[0]); } |