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
3 changes: 2 additions & 1 deletion docs/source/5.0-Upgrade-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ cfg.CreateMap<Category, CategoryDto>().MaxDepth(3);
cfg.CreateMap<User, UserDto>().PreserveReferences();
```

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.
Starting from 6.1.0 PreserveReferences is set automatically at config time whenever the recursion can be detected statically.
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.

## UseDestinationValue

Expand Down
18 changes: 17 additions & 1 deletion docs/source/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,20 @@ Compilation times increase with the size of the execution plan and that depends
You can set `MapAtRuntime` per member or `MaxExecutionPlanDepth` globally (the default is one, set it to zero).

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.
Avoiding `PreserveReferences` and `MaxDepth` also helps.
Avoiding `PreserveReferences` and `MaxDepth` also helps.

## Circular and Self-Referential Types

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).

If your object graphs legitimately exceed 64 levels, increase the limit explicitly:

```c#
cfg.CreateMap<Node, Node>().MaxDepth(128);
```

To disable the depth limit entirely and rely solely on object-identity caching, call `.PreserveReferences()` explicitly:

```c#
cfg.CreateMap<Node, Node>().PreserveReferences();
```
47 changes: 47 additions & 0 deletions src/UnitTests/Bug/DeepNestingStackOverflow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace AutoMapper.UnitTests.Bug;

public class DeepNestingStackOverflow
{
class Circular { public Circular Self { get; set; } }

// Verifies that mapping a deeply nested self-referential object does not
// crash the process with a StackOverflowException (GHSA-rvv3-g6hj-g44x).
// AutoMapper auto-applies a default MaxDepth of 64 (matching System.Text.Json
// and Newtonsoft.Json) when it detects a self-referential type mapping.
[Fact]
public void Mapping_deeply_nested_self_referential_object_should_not_stackoverflow()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Circular, Circular>());
var mapper = config.CreateMapper();

var root = new Circular();
var current = root;
for (int i = 0; i < 30_000; i++)
{
current.Self = new Circular();
current = current.Self;
}

// Should complete without crashing; mapping is truncated at default MaxDepth (64)
var result = mapper.Map<Circular>(root);
result.ShouldNotBeNull();

int depth = 0;
current = result;
while (current.Self != null)
{
depth++;
current = current.Self;
}
depth.ShouldBeLessThanOrEqualTo(64);
}

// Verifies that configuration validation does not detect the vulnerability —
// only the runtime mapping is affected, not the configuration itself.
[Fact]
public void AssertConfigurationIsValid_does_not_detect_deep_nesting_vulnerability()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Circular, Circular>());
config.AssertConfigurationIsValid();
}
}