add more ORT_ENFORCE when building the tree structure of TreeEnsemble operators#27677
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens TreeEnsemble initialization by adding ORT_ENFORCE bounds checks around target/weight indexing to prevent out-of-bounds access when models have not been pre-validated, and adjusts a couple of ML operator tests accordingly.
Changes:
- Added additional
ORT_ENFORCEchecks inTreeEnsembleCommon::Initto validate indices and target id ranges before dereferencing. - Updated TreeRegressor/TreeEnsembler CPU tests to use in-range
target_ids/leaf_targetids.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| onnxruntime/core/providers/cpu/ml/tree_ensemble_common.h | Adds extra index/range enforcement when mapping leaf weights to targets/classes. |
| onnxruntime/test/providers/cpu/ml/treeregressor_test.cc | Adjusts test data to keep target_ids within the allowed target range. |
| onnxruntime/test/providers/cpu/ml/tree_ensembler_test.cc | Adjusts test data to keep leaf_targetids within the allowed target range. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
|
There are suggestions to consider and CI to fix. |
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
done |
yuslepukhin
left a comment
There was a problem hiding this comment.
Please, resolve the ICM ticket accordingly.
|
Please, resolve comments for tests |
…xadupre/tree110413
…xadupre/tree110413
…xadupre/tree110413
There was a problem hiding this comment.
Pull request overview
This PR hardens CPU ML TreeEnsemble tree-building against malformed ONNX model attributes by adding additional runtime validations (primarily via ORT_ENFORCE) and adjusting existing tests to comply with the stricter checks.
Changes:
- Add bounds/range validation for
target_ids/weights mapping when building leaf weight structures. - Enforce non-negative
nodes_featureidsfor non-leaf nodes (while normalizing leaf feature ids). - Update/add unit tests to reflect and validate the new failure behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| onnxruntime/test/providers/cpu/ml/treeregressor_test.cc | Fixes an existing test’s invalid target_ids for n_targets=1 and adds new negative tests covering out-of-range target_ids and negative featureids. |
| onnxruntime/test/providers/cpu/ml/tree_ensembler_test.cc | Fixes leaf_targetids in n_targets=1 coverage to conform to new validations. |
| onnxruntime/core/providers/cpu/ml/tree_ensemble_common.h | Adds new ORT_ENFORCE validations during tree construction (target id range, feature id validity) and moves n_targets_or_classes_ initialization earlier. |
| onnxruntime/core/providers/cpu/ml/tree_ensemble_attribute.h | Adds validation of base_values / base_values_as_tensor sizes vs n_targets_or_classes for regressor/classifier attribute parsing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@copilot open a new pull request to apply changes based on the comments in this thread |
…perators (#27678) ### Description Adds informative error messages to the three `ORT_ENFORCE` index-bounds checks in `tree_ensemble_common.h` that validate `target_class_ids`, `target_class_weights`, and `target_class_weights_as_tensor` accesses. Each message now surfaces the failing index `i` and the container size, e.g.: ``` Index i=5 is out of bounds for target_class_ids (size=3) Index i=5 is out of bounds for target_class_weights (size=3) Index i=5 is out of bounds for target_class_weights_as_tensor (size=3) ``` ### Motivation and Context The original bare `ORT_ENFORCE(i < ...)` calls produced opaque failures with no indication of which attribute was malformed or what the actual index/size values were. This made it difficult to diagnose invalid or adversarial models. Addresses feedback from [#27677](#27677 (comment)). <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…owing conversion, fix per-row x_data_end Agent-Logs-Url: https://github.com/microsoft/onnxruntime/sessions/fc1e13a4-ee34-4126-922e-bcb9f4e1e367 Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com>
…xadupre/tree110413
…xadupre/tree110413
…xadupre/tree110413
Multi-reviewer synthesis (readability + code + critical + deep)The hardening direction is right and several individual checks are well-placed. The cross-reviewer team surfaced one backward-compat regression, two structurally-incomplete coverage gaps (other narrowing casts; V5 path bypass), and one placement issue where new checks land after the function that can OOB on the data they validate. No Critical issues — but the Major findings together are substantive. Major1. Models with Fix: change the outer guard from 2. Other narrowing casts in the same init path are still unguarded.
For true/false node ids, the bounds check happens after the narrowing, so 3. Pre-loop Also, the first parallel-array consumer is the loop at line 271 ( Fix: move all four 4. V5 → V3 conversion path bypasses the V3 attribute ctor.
The pre-loop Fix: factor the V3 ctor's validations into a reusable method and run it on converted V5 attributes before common init. Minor
Nits
Verifications (these hold up)
Praise
RecommendationRequest changes on finding #1 (the binary-classifier regression — straightforward fix) and finding #3 (move size checks before line 270 so they're actually defensive). Findings #2 and #4 are larger but high-value — at minimum, file follow-up issues if they won't fit this PR. Minors and nits are nice-to-have. Reviewed by a cross-family reviewer team (Claude readability + GPT code & critical + Claude-high deep). Schema citations grounded in |
tianleiwu
left a comment
There was a problem hiding this comment.
Review Summary
Solid security-hardening PR. The validation additions are correct and close real vulnerabilities:
base_values/base_values_as_tensorsize validation prevents OOB when model specifies wrong counttarget_class_treeidssize check completes the consistency validation setnodes_featureidsrange check before narrowing cast closes a wrap-around vulnerability- Per-row bounds tightening (
x_data + (i+1)*strideinstead of global end) is genuine defense-in-depth - Debug-only bounds checks in
ProcessTreeNodeLeaveare reasonable given the existing release-mode validations (feature_id ∈ [0, INT_MAX]at init,max_feature_id_ < Cat inference) while(1)→while(root->is_not_leaf())in the mixed-mode branch is cleaner- Test coverage is adequate: negative tests for binary/multi-class base_values overflow, negative feature IDs, and out-of-range target IDs
Two minor suggestions below; neither blocks merge.
Co-authored-by: Tianlei Wu <tlwu@microsoft.com>
…alues backward compat for binary classifiers Agent-Logs-Url: https://github.com/microsoft/onnxruntime/sessions/f5c53ed6-6d97-42f9-98f1-60f10de2a0eb Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
tianleiwu
left a comment
There was a problem hiding this comment.
Review Summary
Well-scoped security-hardening PR. Both concerns from my prior review round (macro duplication and comment typo) have been addressed. The validation additions are correct and close real OOB memory-access vulnerabilities.
One remaining inconsistency:
Missing TREE_FIND_VALUE_CHECK in BRANCH_LEQ same_mode else branch — In ProcessTreeNodeLeave, TREE_FIND_VALUE_CHECK() was added to the BRANCH_LEQ has_missing_tracks_ true branch but not the else branch (no missing tracks). Every other branch type has the check in both code paths: TREE_FIND_VALUE macro (LT/GTE/GT/EQ/NEQ), BRANCH_MEMBER (both branches), BRANCH_MEMBER_BIGSET (both branches), and the mixed-mode loop. BRANCH_LEQ without missing tracks is the most common path for gradient-boosted tree ensembles.
Suggested fix (around line 831 in the new file):
} else {
while (root->is_not_leaf()) {
TREE_FIND_VALUE_CHECK(); // <-- add this
val = x_data[root->feature_id];
root = val <= root->value_or_unique_weight ? root->truenode_or_weight.ptr : root + 1;
}
}This is low-severity since the init-time checks (feature_id ∈ [0, INT_MAX] + max_feature_id_ < C) provide the release-build guard, but the debug check should be consistent across all branch modes.
…xadupre/tree110413
Added. |
tianleiwu
left a comment
There was a problem hiding this comment.
Review Summary
All concerns from prior review rounds have been addressed:
- Macro duplication (round 1): Resolved —
TREE_FIND_VALUE_CHECK()is now a separate conditional macro, with a singleTREE_FIND_VALUEdefinition. - Comment typo (round 1): Fixed — "already made sure" with correct attribution.
- Missing check in BRANCH_LEQ else branch (round 2): Fixed —
TREE_FIND_VALUE_CHECK()is now consistently applied to all traversal paths.
The validation additions are correct and comprehensive:
base_valuessize validation prevents OOB when model attributes are malformed.target_class_treeidssize check completes the consistency validation set.nodes_featureidsrange check validatesint64_tbefore narrowing toint, closing the wrap-around vulnerability.- Per-row end pointer (
x_data + (i+1)*stride) tightens debug bounds checking. while(1)→while(root->is_not_leaf())avoids dereferencingx_data[-1]for leaf nodes.
Test coverage is good: negative tests for binary/multi-class base_values, negative feature IDs, out-of-range target IDs, and a backward-compat test for binary classifiers with 1 base value.
Ready to merge.
Description
Hardens
TreeEnsembleinitialization against malformed/unvalidated ONNX models by adding missing bounds checks and fixing existing ones.Validation additions (
tree_ensemble_attribute.h)base_values/base_values_as_tensorsize againstn_targets_or_classes: must be 0 or N (or ≤ 2 for binary classifiers)Validation additions/fixes (
tree_ensemble_common.h)target_class_treeids.size() == limitcheck alongside existingtarget_class_ids,target_class_nodeids, and weights size checksnodes_featureids[i]: validate originalint64_tvalue is in[0, INT_MAX]before narrowing cast toint— prevents large values wrapping to positive ints and bypassing the old post-cast>= 0check; original attribute value included in error messageProcessTreeNodeLeavecalls (sections C/D/E, C2/D2/E2): passx_data + (i+1)*stride(per-row end) instead ofx_data + N*C(global tensor end); removesx_data_endfrom per-row lambda capturestarget_class_idsrange check usestarget_class_ids(not the old hard-codedtarget_ids)Tests
target_ids, negativenodes_featureids, wrong-sizedbase_values(regressor and classifier, binary/multi-class)Motivation and Context
Tree-building code assumed ONNX models had been pre-validated. These changes prevent out-of-bounds memory access and wrap-around bugs when loading unvalidated or adversarially crafted models.