Skip to content

Commit 57dc20a

Browse files
authored
Fix GHSA-rvv3-g6hj-g44x: apply default MaxDepth of 64 for self-referential types (#6)
1 parent 2c53bf4 commit 57dc20a

File tree

3 files changed

+66
-2
lines changed

3 files changed

+66
-2
lines changed

docs/source/5.0-Upgrade-Guide.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ cfg.CreateMap<Category, CategoryDto>().MaxDepth(3);
8484
cfg.CreateMap<User, UserDto>().PreserveReferences();
8585
```
8686

87-
Starting from 6.1.0 PreserveReferences is set automatically at config time whenever the recursion can be detected statically. If you're still getting `StackOverflowException`, open an issue with a full repro and we'll look into it.
87+
Starting from 6.1.0 PreserveReferences is set automatically at config time whenever the recursion can be detected statically.
88+
Starting from 16.x / MagicMapper patch, a default MaxDepth of 64 is also applied automatically, preventing a StackOverflowException from deeply nested (but non-circular) object graphs (see GHSA-rvv3-g6hj-g44x). If you need deeper mapping, call `.MaxDepth(n)` explicitly. To rely solely on object-identity caching without a depth limit, call `.PreserveReferences()` explicitly.
8889

8990
## UseDestinationValue
9091

docs/source/Configuration.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,20 @@ Compilation times increase with the size of the execution plan and that depends
224224
You can set `MapAtRuntime` per member or `MaxExecutionPlanDepth` globally (the default is one, set it to zero).
225225

226226
These will reduce the size of the execution plan by replacing the execution plan for a child object with a method call. The compilation will be faster, but the mapping itself might be slower. Search the repo for more details and use a profiler to better understand the effect.
227-
Avoiding `PreserveReferences` and `MaxDepth` also helps.
227+
Avoiding `PreserveReferences` and `MaxDepth` also helps.
228+
229+
## Circular and Self-Referential Types
230+
231+
When MagicMapper detects a self-referential type mapping (e.g., `CreateMap<Node, Node>()` where `Node` has a `Node` property), it automatically enables `PreserveReferences` to avoid re-mapping the same object instance. It also applies a default `MaxDepth` of **64** — matching System.Text.Json and Newtonsoft.Json — to prevent a Denial-of-Service condition from deeply nested object graphs (see GHSA-rvv3-g6hj-g44x).
232+
233+
If your object graphs legitimately exceed 64 levels, increase the limit explicitly:
234+
235+
```c#
236+
cfg.CreateMap<Node, Node>().MaxDepth(128);
237+
```
238+
239+
To disable the depth limit entirely and rely solely on object-identity caching, call `.PreserveReferences()` explicitly:
240+
241+
```c#
242+
cfg.CreateMap<Node, Node>().PreserveReferences();
243+
```
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
namespace AutoMapper.UnitTests.Bug;
2+
3+
public class DeepNestingStackOverflow
4+
{
5+
class Circular { public Circular Self { get; set; } }
6+
7+
// Verifies that mapping a deeply nested self-referential object does not
8+
// crash the process with a StackOverflowException (GHSA-rvv3-g6hj-g44x).
9+
// AutoMapper auto-applies a default MaxDepth of 64 (matching System.Text.Json
10+
// and Newtonsoft.Json) when it detects a self-referential type mapping.
11+
[Fact]
12+
public void Mapping_deeply_nested_self_referential_object_should_not_stackoverflow()
13+
{
14+
var config = new MapperConfiguration(cfg => cfg.CreateMap<Circular, Circular>());
15+
var mapper = config.CreateMapper();
16+
17+
var root = new Circular();
18+
var current = root;
19+
for (int i = 0; i < 30_000; i++)
20+
{
21+
current.Self = new Circular();
22+
current = current.Self;
23+
}
24+
25+
// Should complete without crashing; mapping is truncated at default MaxDepth (64)
26+
var result = mapper.Map<Circular>(root);
27+
result.ShouldNotBeNull();
28+
29+
int depth = 0;
30+
current = result;
31+
while (current.Self != null)
32+
{
33+
depth++;
34+
current = current.Self;
35+
}
36+
depth.ShouldBeLessThanOrEqualTo(64);
37+
}
38+
39+
// Verifies that configuration validation does not detect the vulnerability —
40+
// only the runtime mapping is affected, not the configuration itself.
41+
[Fact]
42+
public void AssertConfigurationIsValid_does_not_detect_deep_nesting_vulnerability()
43+
{
44+
var config = new MapperConfiguration(cfg => cfg.CreateMap<Circular, Circular>());
45+
config.AssertConfigurationIsValid();
46+
}
47+
}

0 commit comments

Comments
 (0)