[Core] Add nesting-depth limit to If::validate_and_infer_types (CWE-674) - #37436
Open
axinging wants to merge 1 commit into
Open
[Core] Add nesting-depth limit to If::validate_and_infer_types (CWE-674)#37436axinging wants to merge 1 commit into
axinging wants to merge 1 commit into
Conversation
axinging
marked this pull request as draft
August 14, 2026 02:22
### Details: The constant-condition branch of `If::validate_and_infer_types` (if.cpp:109-113) calls `validate_and_infer_type_body`, which at `multi_subgraph_base.cpp:164` invokes `body->validate_nodes_and_infer_types()` with no depth counter or recursion limit. If the body contains another constant-condition If, the same path is re-entered, 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 path is distinct from the non-constant branch (:124-128): a model whose If ops all carry constant conditions never enters that branch, yet still recurses unboundedly here. The IR frontend (pugixml) imposes no XML nesting cap, making this fully exploitable via a crafted IR model. Fix: introduce a shared `ValidationDepthGuard` RAII class (`validation_depth_guard.hpp`) with a per-op-type `static thread_local` counter and `OV_VALIDATION_DEPTH_GUARD` macro. In `If::validate_and_infer_types`, a single line guards both the constant and non-constant paths against nesting deeper than `kMaxValidationDepth = 64`. ```cpp // validation_depth_guard.hpp — reusable by Loop and other subgraph ops OV_VALIDATION_DEPTH_GUARD(this, "If"); ``` Tests added: - `type_prop.if_nested_constant_condition_exceeds_max_depth_throws` (ov_core_unit_tests) — directly validates the depth guard throws `NodeValidationFailure` at depth 1024. - `IRFrontendTestsIf.nested_if_at_max_depth_loads` (ov_ir_frontend_tests) — boundary: depth-64 nested If IR loads successfully. - `IRFrontendTestsIf.nested_if_depth_limit_is_rejected` (ov_ir_frontend_tests) — end-to-end: depth-1024 nested If IR is rejected via `core.read_model()`. ### Tickets: - *CVS-192797* ### AI Assistance: - *AI assistance used: yes* - *Bug root cause was found by AI.*
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Details:
The constant-condition branch of
If::validate_and_infer_types(if.cpp:109-113) callsvalidate_and_infer_type_body, which atmulti_subgraph_base.cpp:164invokesbody->validate_nodes_and_infer_types()with no depth counter or recursion limit. If the body contains another constant-condition If, the same path is re-entered, 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 path is distinct from the non-constant branch (:124-128): a model whose If ops all carry constant conditions never enters that branch, yet still recurses unboundedly here. The IR frontend (pugixml) imposes no XML nesting cap, making this fully exploitable via a crafted IR model.
Fix: introduce a shared
ValidationDepthGuardRAII class (validation_depth_guard.hpp) with a per-op-typestatic thread_localcounter andOV_VALIDATION_DEPTH_GUARDmacro. InIf::validate_and_infer_types, a single line guards both the constant and non-constant paths against nesting deeper thankMaxValidationDepth = 64.Tests added:
type_prop.if_nested_constant_condition_exceeds_max_depth_throws(ov_core_unit_tests) — directly validates the depth guard throwsNodeValidationFailureat depth 1024.IRFrontendTestsIf.nested_if_at_max_depth_loads(ov_ir_frontend_tests) — depth=64 equals kMaxIfValidationDepth and must load successfully.IRFrontendTestsIf.nested_if_depth_limit_is_rejected(ov_ir_frontend_tests) — end-to-end: depth-1024 nested If IR is rejected viacore.read_model().Tickets:
AI Assistance: