From d6bda09e5d8974a7bbfc97744e2f5d39186e86d7 Mon Sep 17 00:00:00 2001 From: Anirudh Prasad Date: Thu, 30 Jul 2026 20:50:08 +0530 Subject: [PATCH] fix: enforce type depth/size limits on PackClosure function types --- .../runtime/src/runtime_type_checks.rs | 11 +-- .../types/src/loaded_data/runtime_types.rs | 89 +++++++++++++++++++ 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/third_party/move/move-vm/runtime/src/runtime_type_checks.rs b/third_party/move/move-vm/runtime/src/runtime_type_checks.rs index a43c604a582..7d240bea6d3 100644 --- a/third_party/move/move-vm/runtime/src/runtime_type_checks.rs +++ b/third_party/move/move-vm/runtime/src/runtime_type_checks.rs @@ -108,11 +108,12 @@ pub fn verify_pack_closure( .iter() .map(|ret| with_owned_instantiation(ty_builder, func, ret, Ok)) .collect::>>()?; - operand_stack.push_ty(Type::Function { - args, - results, - abilities, - })?; + let function_ty = ty_builder + .create_function_ty(args, results, abilities) + .map_err(|e| { + e.append_message_with_separator('.', "Failed to pack closure: function type exceeds depth/size limits".to_string()) + })?; + operand_stack.push_ty(function_ty)?; Ok(()) } diff --git a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs index 89dba783224..8adf76404f3 100644 --- a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs +++ b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs @@ -1018,6 +1018,48 @@ impl TypeBuilder { }) } + /// Creates a function type from the given argument and result types. + /// The function-type node itself is counted towards type size and depth limits, + /// with arguments and results treated as its children at depth 2. Returns an + /// error if the resulting type would exceed the configured limits. + pub fn create_function_ty( + &self, + args: Vec, + results: Vec, + abilities: AbilitySet, + ) -> PartialVMResult { + // Count 1 for the function-type node itself; children start at depth 2. + let mut count = 1; + let check = |c: &mut u64, d: u64| self.check(c, d); + + let args = args + .iter() + .map(|ty| self.clone_impl(ty, &mut count, 2, check)) + .collect::>>() + .map_err(|e| { + e.append_message_with_separator( + '.', + "Failed to create function type: argument type exceeds limits".to_string(), + ) + })?; + let results = results + .iter() + .map(|ty| self.clone_impl(ty, &mut count, 2, check)) + .collect::>>() + .map_err(|e| { + e.append_message_with_separator( + '.', + "Failed to create function type: result type exceeds limits".to_string(), + ) + })?; + + Ok(Type::Function { + args, + results, + abilities, + }) + } + /// Creates a type for a Move constant. Note that constant types can be /// more restrictive and therefore have their own creation API. pub fn create_constant_ty(&self, const_tok: &SignatureToken) -> PartialVMResult { @@ -1862,4 +1904,51 @@ mod unit_tests { let err = assert_err!(ty_builder.create_ty(&vec_tag, no_op)); assert_eq!(err.major_status(), StatusCode::TOO_MANY_TYPE_NODES); } + + #[test] + fn test_create_function_ty_depth() { + use Type::*; + + let max_ty_depth = 5; + let ty_builder = TypeBuilder::with_limits(100, max_ty_depth); + + // A function type with no args or results succeeds (depth = 1, the node itself). + assert_ok!(ty_builder.create_function_ty(vec![], vec![], AbilitySet::EMPTY)); + + // A result type max_ty_depth-1 levels deep is allowed: function node is at depth 1, + // result root at depth 2, deepest result node at depth max_ty_depth. + let (shallow_result, _, _) = nested_vec_for_test(max_ty_depth - 1); + assert_ok!(ty_builder.create_function_ty(vec![], vec![shallow_result], AbilitySet::EMPTY)); + + // A result type max_ty_depth levels deep is rejected: the function-type node occupies + // depth 1, pushing the deepest result node to depth max_ty_depth + 1. + let (deep_result, _, _) = nested_vec_for_test(max_ty_depth); + let err = assert_err!( + ty_builder.create_function_ty(vec![], vec![deep_result], AbilitySet::EMPTY) + ); + assert_eq!(err.major_status(), StatusCode::VM_MAX_TYPE_DEPTH_REACHED); + + // Confirm that the same deep result type passes create_ty_with_subst (which does not + // account for the enclosing function-type node), showing what the old code permitted. + let (deep_result_again, _, _) = nested_vec_for_test(max_ty_depth); + assert_ok!(ty_builder.create_ty_with_subst(&deep_result_again, &[])); + } + + #[test] + fn test_create_function_ty_size() { + use Type::*; + + let max_ty_size = 5; + let ty_builder = TypeBuilder::with_limits(max_ty_size, 100); + + // max_ty_size - 1 Bool args: 1 (function node) + 4 = 5 total nodes, at the limit. + let at_limit = vec![Bool; (max_ty_size - 1) as usize]; + assert_ok!(ty_builder.create_function_ty(at_limit, vec![], AbilitySet::EMPTY)); + + // max_ty_size Bool args: 1 (function node) + 5 = 6 total nodes, over the limit. + let over_limit = vec![Bool; max_ty_size as usize]; + let err = + assert_err!(ty_builder.create_function_ty(over_limit, vec![], AbilitySet::EMPTY)); + assert_eq!(err.major_status(), StatusCode::TOO_MANY_TYPE_NODES); + } }