aboutsummaryrefslogtreecommitdiff
path: root/src/test/fuzz
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2023-02-25 16:19:12 -0500
committerPieter Wuille <pieter@wuille.net>2023-02-28 09:42:33 -0500
commit56e37e71a2538a240cc360678aeb752d17bd8f45 (patch)
tree741685ae23fda0fbfb1cc7524d3b359eb01213a8 /src/test/fuzz
parentbcec5ab4ff1039c0c309dbbb9953adbd0a4f3e88 (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.cpp14
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]);
}