From 2915a986f51e605c045a5c41fdc6823983ddab17 Mon Sep 17 00:00:00 2001 From: Xu Xing Date: Fri, 14 Aug 2026 10:15:52 +0800 Subject: [PATCH] [Core] Add nesting-depth limit to Loop::validate_and_infer_types and test TI nesting assert (CWE-674) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Details: `Loop::validate_and_infer_types` (loop.cpp:206) calls `m_bodies[0]->validate_nodes_and_infer_types()` which walks every body node and invokes each node's `validate_and_infer_types()`. If a body node is itself a `v5::Loop`, validation re-enters the same descent with no depth counter, producing one native C++ stack frame per nesting level. A sufficiently deep chain overflows the call stack and crashes the process (SIGSEGV / EXCEPTION_STACK_OVERFLOW). This is the same defect shape as the If recursion,at a distinct location (Loop). `TensorIterator` already has a hard assert in `revalidate_and_infer_types_for_body_ops()` (tensor_iterator.cpp:47) that unconditionally blocks any TI nesting: ```cpp OPENVINO_ASSERT(... == nullptr, "No nested TensorIterator"); ``` This is stronger than a depth guard — it forbids TI-in-TI entirely. No code change is needed for TI, but this assert was previously untested. Fix: use the shared `OV_VALIDATION_DEPTH_GUARD` macro (introduced in the companion If PR) in `Loop::validate_and_infer_types` to guard against nesting deeper than `kMaxValidationDepth = 64`: ```cpp OV_VALIDATION_DEPTH_GUARD(this, "Loop"); ``` Tests added: - `type_prop.loop_nested_depth_limit_is_rejected` (ov_core_unit_tests) — validates the Loop depth guard throws `NodeValidationFailure` at depth 1024. - `type_prop.tensor_iterator_nested_is_rejected` (ov_core_unit_tests) — locks TI's existing hard assert: nesting TI-in-TI at depth 2 throws `AssertFailure` with "No nested TensorIterator". ### Tickets: - *CVS-192817* ### AI Assistance: - *AI assistance used: yes* - *Bug root cause was found by AI.* --- src/core/src/op/loop.cpp | 3 ++ src/core/tests/type_prop/loop.cpp | 34 +++++++++++++++ src/core/tests/type_prop/tensor_iterator.cpp | 44 ++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/src/core/src/op/loop.cpp b/src/core/src/op/loop.cpp index 934a2a68436d0a..523387e919d1a4 100644 --- a/src/core/src/op/loop.cpp +++ b/src/core/src/op/loop.cpp @@ -9,6 +9,7 @@ #include "itt.hpp" #include "openvino/core/validation_util.hpp" #include "openvino/op/tensor_iterator.hpp" +#include "openvino/op/util/validation_depth_guard.hpp" #include "openvino/reference/loop.hpp" #include "openvino/runtime/tensor.hpp" @@ -33,6 +34,8 @@ bool Loop::visit_attributes(AttributeVisitor& visitor) { void Loop::validate_and_infer_types() { OV_OP_SCOPE(v5_Loop_validate_and_infer_types); + OV_VALIDATION_DEPTH_GUARD(this, "Loop"); + NODE_VALIDATION_CHECK(this, m_bodies.size() == 1, "Number of bodies for loop is greater than 1"); NODE_VALIDATION_CHECK(this, m_input_descriptions.size() == 1, "Loop contains input descriptions for other bodies"); diff --git a/src/core/tests/type_prop/loop.cpp b/src/core/tests/type_prop/loop.cpp index 1faa460e87baaf..6c7551666bddba 100644 --- a/src/core/tests/type_prop/loop.cpp +++ b/src/core/tests/type_prop/loop.cpp @@ -6,6 +6,7 @@ #include +#include "common_test_utils/test_assertions.hpp" #include "common_test_utils/type_prop.hpp" #include "openvino/core/model.hpp" #include "openvino/op/add.hpp" @@ -19,6 +20,7 @@ using namespace std; using namespace ov; +using testing::HasSubstr; // trip_count = 10 // execution_condition = true @@ -1580,3 +1582,35 @@ TEST(type_prop, loop_merged_static_seed_relaxed_when_body_value_dynamic) { EXPECT_EQ(body_carried->get_partial_shape(), (PartialShape{1, Dimension::dynamic()})); EXPECT_TRUE(loop->get_output_partial_shape(0)[1].is_dynamic()); } + +static shared_ptr make_nested_loop(size_t depth) { + auto trip = op::v0::Constant::create(element::i64, Shape{1}, {1}); + auto cond = op::v0::Constant::create(element::boolean, Shape{1}, {true}); + auto loop = make_shared(trip, cond); + + auto body_data = make_shared(element::f32, PartialShape::dynamic()); + auto body_cond = make_shared(element::boolean, Shape{1}); + shared_ptr body_out; + if (depth > 0) { + body_out = make_nested_loop(depth - 1); + } else { + body_out = body_data; + } + auto body_cond_res = make_shared(body_cond); + auto body_data_res = make_shared(body_out); + auto body = make_shared(OutputVector{body_cond_res, body_data_res}, ParameterVector{body_data, body_cond}); + + loop->set_function(body); + loop->set_special_body_ports({-1, 0}); + loop->set_invariant_input(body_data, loop->input_value(0)); + loop->set_invariant_input(body_cond, cond); + loop->get_iter_value(body_data_res, -1); + return loop; +} + +TEST(type_prop, loop_nested_depth_limit_is_rejected) { + const size_t excessive_depth = 1024; + OV_EXPECT_THROW(std::ignore = make_nested_loop(excessive_depth), + ov::NodeValidationFailure, + HasSubstr("nesting depth exceeds")); +} diff --git a/src/core/tests/type_prop/tensor_iterator.cpp b/src/core/tests/type_prop/tensor_iterator.cpp index 3b1876f1a3d3ae..d33a05eac4c697 100644 --- a/src/core/tests/type_prop/tensor_iterator.cpp +++ b/src/core/tests/type_prop/tensor_iterator.cpp @@ -7,6 +7,7 @@ #include #include "common_test_utils/node_builders/reshape.hpp" +#include "common_test_utils/test_assertions.hpp" #include "common_test_utils/type_prop.hpp" #include "openvino/core/model.hpp" #include "openvino/op/add.hpp" @@ -15,6 +16,7 @@ using namespace std; using namespace ov; +using testing::HasSubstr; TEST(type_prop, tensor_iterator_lstm) { // That which we iterate over @@ -264,3 +266,45 @@ TEST(type_prop, tensor_iterator_dyn_slice) { PartialShape ref_ps = {N, part_size, I}; EXPECT_EQ(X->get_partial_shape(), ref_ps); } + +static shared_ptr make_nested_ti(size_t depth) { + auto ti = make_shared(); + + auto body_param = make_shared(element::f32, PartialShape{1, 1}); + shared_ptr body_out; + if (depth > 0) { + // Nest another TI whose input comes from the outer body parameter + auto inner_ti = make_shared(); + auto inner_param = make_shared(element::f32, PartialShape{1, 1}); + shared_ptr inner_out; + if (depth > 1) { + inner_out = make_nested_ti(depth - 1); + } else { + inner_out = inner_param; + } + auto inner_res = make_shared(inner_out); + auto inner_body = make_shared(OutputVector{inner_res}, ParameterVector{inner_param}); + inner_ti->set_body(inner_body); + inner_ti->set_invariant_input(inner_param, body_param); + inner_ti->get_iter_value(inner_res, -1); + body_out = inner_ti; + } else { + body_out = body_param; + } + auto body_res = make_shared(body_out); + auto body = make_shared(OutputVector{body_res}, ParameterVector{body_param}); + + ti->set_body(body); + auto data = make_shared(element::f32, PartialShape{1, 1}); + ti->set_invariant_input(body_param, data); + ti->get_iter_value(body_res, -1); + return ti; +} + +// TI already blocks nesting via a hard assert in revalidate_and_infer_types_for_body_ops. +TEST(type_prop, tensor_iterator_nested_is_rejected) { + const size_t excessive_depth = 2; + OV_EXPECT_THROW(std::ignore = make_nested_ti(excessive_depth), + ov::AssertFailure, + HasSubstr("No nested TensorIterator")); +}