[AAP] Cap recursion depth in ObjectExtractor's list/dictionary paths - #8958
Conversation
ExtractProperties enforces WafConstants.MaxContainerDepth, but ExtractListOrArray, ExtractDictionary, ExtractDictionaryAsKeyValuePairs, and ExtractNonGenericDictionary incremented depth on recursion without checking it, so a deeply nested (acyclic) enumerable/dictionary graph could recurse until StackOverflowException. This became more reachable after #8706 broadened extraction to any IEnumerable, not just arrays/List<T>. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8958) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8958) - mean (74ms) : 70, 78
master - mean (75ms) : 70, 80
section Bailout
This PR (8958) - mean (77ms) : 73, 81
master - mean (80ms) : 76, 85
section CallTarget+Inlining+NGEN
This PR (8958) - mean (1,096ms) : 1028, 1163
master - mean (1,090ms) : 1049, 1132
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8958) - mean (112ms) : 105, 119
master - mean (114ms) : 109, 119
section Bailout
This PR (8958) - mean (110ms) : 107, 114
master - mean (112ms) : 109, 114
section CallTarget+Inlining+NGEN
This PR (8958) - mean (777ms) : 754, 800
master - mean (782ms) : 760, 803
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8958) - mean (100ms) : 95, 106
master - mean (101ms) : 95, 106
section Bailout
This PR (8958) - mean (98ms) : 96, 100
master - mean (103ms) : 98, 108
section CallTarget+Inlining+NGEN
This PR (8958) - mean (944ms) : 904, 983
master - mean (943ms) : 903, 984
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8958) - mean (96ms) : 93, 99
master - mean (96ms) : 93, 99
section Bailout
This PR (8958) - mean (101ms) : 96, 105
master - mean (100ms) : 95, 105
section CallTarget+Inlining+NGEN
This PR (8958) - mean (819ms) : 784, 853
master - mean (822ms) : 786, 859
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8958) - mean (192ms) : 188, 196
master - mean (192ms) : 188, 195
section Bailout
This PR (8958) - mean (195ms) : 193, 197
master - mean (195ms) : 193, 197
section CallTarget+Inlining+NGEN
This PR (8958) - mean (1,172ms) : 1129, 1214
master - mean (1,170ms) : 1129, 1211
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8958) - mean (273ms) : 269, 277
master - mean (272ms) : 267, 278
section Bailout
This PR (8958) - mean (273ms) : 271, 276
master - mean (272ms) : 269, 275
section CallTarget+Inlining+NGEN
This PR (8958) - mean (940ms) : 926, 954
master - mean (938ms) : 919, 956
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8958) - mean (267ms) : 265, 270
master - mean (268ms) : 263, 272
section Bailout
This PR (8958) - mean (267ms) : 264, 270
master - mean (267ms) : 265, 270
section CallTarget+Inlining+NGEN
This PR (8958) - mean (1,141ms) : 1081, 1202
master - mean (1,143ms) : 1096, 1191
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8958) - mean (265ms) : 261, 270
master - mean (267ms) : 264, 270
section Bailout
This PR (8958) - mean (266ms) : 264, 268
master - mean (267ms) : 265, 270
section CallTarget+Inlining+NGEN
This PR (8958) - mean (1,024ms) : 982, 1065
master - mean (1,025ms) : 972, 1079
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BenchmarksBenchmark execution time: 2026-07-30 09:17:41 Comparing candidate commit 30f4aba in PR branch Found 0 performance improvements and 1 performance regressions! Performance is the same for 71 metrics, 0 unstable metrics, 63 known flaky benchmarks, 63 flaky benchmarks without significant changes.
|
| // Mirrors ExtractProperties: once the incremented depth reaches the limit, stop recursing | ||
| // into children instead of descending unbounded (which previously risked a stack overflow). |
There was a problem hiding this comment.
| // Mirrors ExtractProperties: once the incremented depth reaches the limit, stop recursing | |
| // into children instead of descending unbounded (which previously risked a stack overflow). |
Co-authored-by: Andrew Lock <andrew.lock@datadoghq.com>
Co-authored-by: Andrew Lock <andrew.lock@datadoghq.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Summary
An internal codex security review found that
ObjectExtractor's object-property extraction (ExtractProperties) enforcesWafConstants.MaxContainerDepth, but the list/dictionary recursion paths (ExtractListOrArray,ExtractDictionary,ExtractDictionaryAsKeyValuePairs,ExtractNonGenericDictionary) increment depth on recursion without ever checking it against the limit. A deeply nested (acyclic) enumerable or dictionary graph can therefore recurse untilStackOverflowException, which is process-fatal in .NET.This became more reachable after #8706 broadened the
IEnumerablerouting to any enumerable type (not just arrays/List<T>), widening what data can reach the unbounded recursion (e.g.HashSet<T>, customIEnumerablewrappers, JSON-like collection types) via AAP request/response extraction.ExtractPropertiesto all four container-recursion paths.Test plan
TestDeeplyNestedAcyclicListDoesNotStackOverflow— 100k nested lists, asserts no crash (crashes the process without the fix).TestNestedListRespectsMaxContainerDepth— asserts the list is truncated to empty exactly atMaxContainerDepth, mirroring the existingTestNestedObjectsAboveLimitobject-property test.ObjectExtractorTestssuite across all target frameworks — all passing.🤖 Generated with Claude Code