Skip to content

Commit 56e37e7

Browse files
committed
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.
1 parent bcec5ab commit 56e37e7

File tree

1 file changed

+13
-1
lines changed

1 file changed

+13
-1
lines changed

src/test/fuzz/miniscript.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,9 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
769769
std::vector<std::pair<Type, std::optional<NodeInfo>>> todo{{root_type, {}}};
770770
/** Predict the number of (static) script ops. */
771771
uint32_t ops{0};
772+
/** Predict the total script size (every unexplored subnode is counted as one, as every leaf is
773+
* at least one script byte). */
774+
uint32_t scriptsize{1};
772775

773776
while (!todo.empty()) {
774777
// The expected type we have to construct.
@@ -777,7 +780,11 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
777780
// Fragment/children have not been decided yet. Decide them.
778781
auto node_info = ConsumeNode(type_needed);
779782
if (!node_info) return {};
780-
// Update predicted resource limits.
783+
// Update predicted resource limits. Since every leaf Miniscript node is at least one
784+
// byte long, we move one byte from each child to their parent. A similar technique is
785+
// used in the miniscript::internal::Parse function to prevent runaway string parsing.
786+
scriptsize += miniscript::internal::ComputeScriptLen(node_info->fragment, ""_mst, node_info->subtypes.size(), node_info->k, node_info->subtypes.size(), node_info->keys.size()) - 1;
787+
if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
781788
switch (node_info->fragment) {
782789
case Fragment::JUST_0:
783790
case Fragment::JUST_1:
@@ -834,6 +841,8 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
834841
ops += 3;
835842
break;
836843
case Fragment::WRAP_V:
844+
// We don't account for OP_VERIFY here; that will be corrected for when the actual
845+
// node is constructed below.
837846
break;
838847
case Fragment::WRAP_J:
839848
ops += 4;
@@ -885,15 +894,18 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) {
885894
// Update resource predictions.
886895
if (node->fragment == Fragment::WRAP_V && node->subs[0]->GetType() << "x"_mst) {
887896
ops += 1;
897+
scriptsize += 1;
888898
}
889899
if (ops > MAX_OPS_PER_SCRIPT) return {};
900+
if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
890901
// Move it to the stack.
891902
stack.push_back(std::move(node));
892903
todo.pop_back();
893904
}
894905
}
895906
assert(stack.size() == 1);
896907
assert(stack[0]->GetStaticOps() == ops);
908+
assert(stack[0]->ScriptSize() == scriptsize);
897909
stack[0]->DuplicateKeyCheck(KEY_COMP);
898910
return std::move(stack[0]);
899911
}

0 commit comments

Comments
 (0)