Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions tracer/src/Datadog.Trace/AppSec/ObjectExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,12 @@ private static bool IsCollectionType(Type type)
return EmptyDictionary;
}

depth++;
if (depth >= WafConstants.MaxContainerDepth)
{
return EmptyDictionary;
}

var gtkvp = typeof(KeyValuePair<,>);
var tkvp = gtkvp.MakeGenericType(dictType.GetGenericArguments());
var keyProp = tkvp.GetProperty("Key");
Expand Down Expand Up @@ -603,7 +609,7 @@ private static bool IsCollectionType(Type type)
}
else
{
var extractedvalue = ExtractType(dictValue.GetType(), dictValue, depth + 1, visited, extractorCache, createExtractors, useSimpleDictionaryFormat);
var extractedvalue = ExtractType(dictValue.GetType(), dictValue, depth, visited, extractorCache, createExtractors, useSimpleDictionaryFormat);
items.Add(dictKey, extractedvalue);
}

Expand Down Expand Up @@ -635,6 +641,14 @@ private static bool IsCollectionType(Type type)
return [];
}

// Mirrors ExtractProperties: once the incremented depth reaches the limit, stop recursing
// into children instead of descending unbounded (which previously risked a stack overflow).
Comment thread
dromanol marked this conversation as resolved.
depth++;
if (depth >= WafConstants.MaxContainerDepth)
{
return [];
}

var gtkvp = typeof(KeyValuePair<,>);
var tkvp = gtkvp.MakeGenericType(dictType.GetGenericArguments());
var keyProp = tkvp.GetProperty("Key");
Expand All @@ -654,8 +668,8 @@ private static bool IsCollectionType(Type type)

var pair = new Dictionary<string, object?>(2)
{
["Key"] = dictKey is null ? null : ExtractType(dictKey.GetType(), dictKey, depth + 1, visited, extractorCache, createExtractors, useSimpleDictionaryFormat),
["Value"] = dictValue is null ? null : ExtractType(dictValue.GetType(), dictValue, depth + 1, visited, extractorCache, createExtractors, useSimpleDictionaryFormat),
["Key"] = dictKey is null ? null : ExtractType(dictKey.GetType(), dictKey, depth, visited, extractorCache, createExtractors, useSimpleDictionaryFormat),
["Value"] = dictValue is null ? null : ExtractType(dictValue.GetType(), dictValue, depth, visited, extractorCache, createExtractors, useSimpleDictionaryFormat),
};
items.Add(pair);

Expand All @@ -682,9 +696,14 @@ private static object ExtractNonGenericDictionary(
{
var capacity = Math.Min(WafConstants.MaxContainerSize, source.Count);

// Mirrors ExtractProperties: once the incremented depth reaches the limit, stop recursing
// into children instead of descending unbounded (which previously risked a stack overflow).
Comment on lines +699 to +700

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Mirrors ExtractProperties: once the incremented depth reaches the limit, stop recursing
// into children instead of descending unbounded (which previously risked a stack overflow).

depth++;
var depthExceeded = depth >= WafConstants.MaxContainerDepth;

if (useSimpleDictionaryFormat)
{
if (!visited.Add(source))
if (!visited.Add(source) || depthExceeded)
{
return EmptyDictionary;
}
Expand All @@ -700,7 +719,7 @@ private static object ExtractNonGenericDictionary(
continue;
}

map[key] = entry.Value is null ? null : ExtractType(entry.Value.GetType(), entry.Value, depth + 1, visited, extractorCache, createExtractors, useSimpleDictionaryFormat);
map[key] = entry.Value is null ? null : ExtractType(entry.Value.GetType(), entry.Value, depth, visited, extractorCache, createExtractors, useSimpleDictionaryFormat);
if (map.Count >= WafConstants.MaxContainerSize)
{
break;
Expand All @@ -710,7 +729,7 @@ private static object ExtractNonGenericDictionary(
return map;
}

if (!visited.Add(source))
if (!visited.Add(source) || depthExceeded)
{
return new List<object?>();
}
Expand All @@ -722,8 +741,8 @@ private static object ExtractNonGenericDictionary(
var entry = enumerator.Entry;
items.Add(new Dictionary<string, object?>(2)
{
["Key"] = entry.Key is null ? null : ExtractType(entry.Key.GetType(), entry.Key, depth + 1, visited, extractorCache, createExtractors, useSimpleDictionaryFormat),
["Value"] = entry.Value is null ? null : ExtractType(entry.Value.GetType(), entry.Value, depth + 1, visited, extractorCache, createExtractors, useSimpleDictionaryFormat),
["Key"] = entry.Key is null ? null : ExtractType(entry.Key.GetType(), entry.Key, depth, visited, extractorCache, createExtractors, useSimpleDictionaryFormat),
["Value"] = entry.Value is null ? null : ExtractType(entry.Value.GetType(), entry.Value, depth, visited, extractorCache, createExtractors, useSimpleDictionaryFormat),
});
if (items.Count >= WafConstants.MaxContainerSize)
{
Expand Down Expand Up @@ -754,6 +773,12 @@ private static object ExtractNonGenericDictionary(
return [];
}

depth++;
if (depth >= WafConstants.MaxContainerDepth)
{
return [];
}

// Use ICollection for pre-sizing when available. Some types (e.g. HashSet<T>) implement
// ICollection<T> but not the non-generic ICollection, so fall back to default capacity.
var items = value is ICollection sourceColl
Expand All @@ -768,7 +793,7 @@ private static object ExtractNonGenericDictionary(
}
else
{
var extractedvalue = ExtractType(item.GetType(), item, depth + 1, visited, extractorCache, createExtractors, useSimpleDictionaryFormat);
var extractedvalue = ExtractType(item.GetType(), item, depth, visited, extractorCache, createExtractors, useSimpleDictionaryFormat);
items.Add(extractedvalue);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,31 @@ public void TestSelfReferentialCollectionDoesNotStackOverflow()
act.Should().NotThrow();
}

[Fact]
public void TestNestedListRespectsMaxContainerDepth()
{
object nested = "leaf";
for (var i = 0; i < WafConstants.MaxContainerDepth + 5; i++)
{
nested = new List<object> { nested };
}

var result = ObjectExtractor.Extract(nested) as List<object>;

var current = result;
for (var i = 0; i < WafConstants.MaxContainerDepth - 1; i++)
{
current.Should().NotBeNull();
current.Should().HaveCount(1);
current = current![0] as List<object>;
}

// The list at the depth limit must be cut off empty, matching ExtractProperties' behavior
// for objects at the same depth (see TestNestedObjectsAboveLimit).
current.Should().NotBeNull();
current.Should().BeEmpty();
}

#if NETFRAMEWORK
[Fact]
public void TestDateTimeOffsetExtractedAsObject_DataContractPath()
Expand Down
Loading