Reject non-control-flow nodes that carry subgraphs during session state finalization - #32641
Open
Bin Miao (miaobin) wants to merge 2 commits into
Open
Bin Miao (miaobin) wants to merge 2 commits into
Bin Miao (miaobin) wants to merge 2 commits into
Conversation
…alization FinalizeSessionStateImpl downcasts every node that carries a subgraph to controlflow::IControlFlowKernel and calls SetupSubgraphExecutionInfo on it. The "only control flow nodes have subgraphs" invariant it relies on is not enforced anywhere: Node::Init materializes a subgraph for any GRAPH-typed attribute with no schema gate, so a plain kernel can reach the downcast and read an out-of-bounds vtable slot. Add a virtual OpKernel::IsControlFlowKernel(), overridden to true by IControlFlowKernel (If/Loop/Scan and their CUDA derivatives), and check it before the cast, returning a clear error otherwise. A virtual predicate is used instead of dynamic_cast because onnxruntime_DISABLE_RTTI is on by default.
A SimplifiedLayerNormalization node carrying a forged GRAPH attribute must be rejected gracefully at session initialization instead of triggering the IControlFlowKernel downcast.
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The guard prevents the unsafe cast while preserving valid control-flow behavior and includes targeted regression coverage.
Pull request overview
Hardens session finalization against invalid control-flow kernel casts.
Changes:
- Adds a virtual control-flow kernel predicate.
- Rejects non-control-flow kernels carrying subgraphs.
- Adds a regression test for crafted models.
No actionable issues identified.
File summaries
| File | Description |
|---|---|
include/onnxruntime/core/framework/op_kernel.h |
Adds the kernel predicate. |
onnxruntime/core/providers/cpu/controlflow/utils.h |
Marks control-flow kernels. |
onnxruntime/core/framework/session_state.cc |
Guards the downcast. |
onnxruntime/test/framework/inference_session_test.cc |
Tests graceful rejection. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The guard prevents the invalid cast while preserving valid control-flow and plugin EP paths.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
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.
Description
SessionState::FinalizeSessionStateImpliterates over the subgraphs attached to each node and unconditionally downcasts the node's kernel tocontrolflow::IControlFlowKernel, then callsSetupSubgraphExecutionInfoon it:The "only control flow nodes have subgraphs" invariant is not enforced anywhere. Node::Init materializes a subgraph for any attribute of type GRAPH, with no schema gate, so an ordinary (non-control-flow) node can carry a subgraph. When it does, the downcast above is invalid: IControlFlowKernel adds a vtable slot that a plain OpKernel does not have, so the call reads an out-of-bounds vtable slot — an undefined-behavior type confusion that crashes (observed as an access violation inside FinalizeSessionStateImpl).
This is reachable by loading a crafted/malformed model whose non-control-flow node has a GRAPH-typed attribute (for example, an op that permits unchecked attributes). ORT should reject such a model with a clear error instead of executing the bad cast.
Fix
Gate the downcast on a virtual predicate:
OpKernel::IsControlFlowKernel()returningfalseby default.trueonIControlFlowKernel, which covers If/Loop/Scan and their CUDA derivatives.FinalizeSessionStateImplbefore the cast and return an error otherwise.A virtual predicate is used rather than
dynamic_castbecauseonnxruntime_DISABLE_RTTIis on by default. There is no behavior change for valid models: only the control-flow kernels inheritIControlFlowKernel, and they always returntrue.Adds
InferenceSessionTests.SubgraphAttributeOnNonControlFlowNodeIsRejectedtest case, which builds a model whose non-control-flow node carries aGRAPHattribute and asserts that session initialization fails gracefully instead of triggering the downcast. Existing If/Loop/Scan subgraph tests cover the no-regression path.