Conversation
|
Claude finished @deffrian's task in 6m 45s —— View job PR Review: fix/stack-overflow-trace
SummaryThe core fix — converting FindingsMedium
Low
What's correct
|
| // contributes ~2 JSON levels (object + "calls" array), the EVM allows up to MaxCallDepth=1024 | ||
| // (Yellow Paper / Nethermind.Evm.VirtualMachine.MaxCallDepth), plus a few levels of JSON-RPC | ||
| // envelope. 4096 leaves comfortable headroom. | ||
| public const int DefaultMaxDepth = 4096; |
There was a problem hiding this comment.
Medium — DefaultMaxDepth = 4096 widens the deserialization attack surface
JsonSerializerOptions.MaxDepth applies to both serialization and deserialization. The iterative converter removes the call-stack overflow by eliminating recursion in the converter itself; it writes depth via direct WriteStartObject/WriteStartArray calls, which the writer tracks — not the serializer's recursion depth. Only the writer's MaxDepth (set via JsonWriterOptions) needs to be 4096.
For deserialization of incoming JSON-RPC requests, the old limit of 128 was a reasonable DoS guard. Raising it 32× means a malicious client can send a 4096-level nested JSON body and the deserializer will happily recurse through it (custom converters chain recursively via JsonSerializer.Deserialize), consuming more CPU and stack.
Consider splitting the concern:
// Output (trace serialization) needs deep writer
public const int DefaultMaxDepth = 4096;
// Input (RPC deserialization) can stay conservative
public const int DefaultDeserializationMaxDepth = 128;Or document explicitly why the unified limit increase is acceptable here.
There was a problem hiding this comment.
@claude We have input size limit, can you verify?
| private static void WriteJsonRpcResponse(IBufferWriter<byte> writer, JsonRpcResponse response) | ||
| { | ||
| using Utf8JsonWriter jsonWriter = new(writer, new JsonWriterOptions { SkipValidation = true }); | ||
| using Utf8JsonWriter jsonWriter = new(writer, new JsonWriterOptions { SkipValidation = true, MaxDepth = EthereumJsonSerializer.DefaultMaxDepth }); |
There was a problem hiding this comment.
Low — MaxDepth on a SkipValidation = true writer is a no-op
System.Text.Json's Utf8JsonWriter enforces MaxDepth inside ValidateStart(), which is only called when _options.SkipValidation is false. With SkipValidation = true, depth tracking still advances (the _currentDepth field increments) but the guard check is never reached, so MaxDepth = EthereumJsonSerializer.DefaultMaxDepth has no effect on overflow protection here.
If the intent is defensive documentation (signal the intended limit), add a comment. If the intent was actual enforcement, SkipValidation must be removed — though the actual overflow for the trace case is already fixed by the iterative converter. Fix this →
| Throws.TypeOf<JsonException>() | ||
| .With.InnerException.TypeOf<InvalidOperationException>() | ||
| .And.InnerException.Message.EqualTo( | ||
| $"CurrentDepth ({EthereumJsonSerializer.DefaultMaxDepth}) is equal to or larger than the maximum allowed depth of {EthereumJsonSerializer.DefaultMaxDepth}. Cannot write the next JSON object or array."), |
There was a problem hiding this comment.
Low — hardcoded System.Text.Json internal error message is brittle
This assertion pins the exact error text produced by Utf8JsonWriter when the depth limit is reached. That message is an internal implementation detail — it can change wording between .NET releases and will silently break this test.
Consider asserting only on exception type and optionally a stable substring ("MaxDepth", "depth"), or simply drop the message assertion entirely:
Throws.TypeOf<JsonException>()
.With.InnerException.TypeOf<InvalidOperationException>()
.And.InnerException.Message.Contains("depth", StringComparison.OrdinalIgnoreCase)|
Claude finished @deffrian's task in 3m 5s —— View job PR Review: fix/stack-overflow-trace
SummaryThe core fix — converting The findings below carry over from the initial review and remain unresolved. The Medium issue should be acknowledged or addressed before merge. FindingsMedium
…a malicious client can send 4096-deep nested JSON and the deserializer will happily recurse through it, consuming proportionally more CPU and stack. Consider splitting the concern with separate serialization/deserialization depth constants so the output depth can be 4096 while the input guard stays conservative (e.g., 128–256). Low
What's correct
|
| Throws.TypeOf<JsonException>() | ||
| .With.InnerException.TypeOf<InvalidOperationException>() | ||
| .And.InnerException.Message.EqualTo( | ||
| $"CurrentDepth ({EthereumJsonSerializer.DefaultMaxDepth}) is equal to or larger than the maximum allowed depth of {EthereumJsonSerializer.DefaultMaxDepth}. Cannot write the next JSON object or array."), |
There was a problem hiding this comment.
Low — hardcoded System.Text.Json internal exception message is brittle
This exact wording is an internal implementation detail that can change between .NET releases, silently breaking the test. Consider asserting only on a stable substring:
| $"CurrentDepth ({EthereumJsonSerializer.DefaultMaxDepth}) is equal to or larger than the maximum allowed depth of {EthereumJsonSerializer.DefaultMaxDepth}. Cannot write the next JSON object or array."), | |
| .And.InnerException.Message.Contains("depth", StringComparison.OrdinalIgnoreCase), |
There was a problem hiding this comment.
Better to leave as this. It's not hard to fix
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
| // Must accommodate the deepest possible callTracer output: each NativeCallTracerCallFrame | ||
| // contributes ~2 JSON levels (object + "calls" array), the EVM allows up to MaxCallDepth=1024 | ||
| // (Yellow Paper / Nethermind.Evm.VirtualMachine.MaxCallDepth), plus a few levels of JSON-RPC | ||
| // envelope. 4096 leaves comfortable headroom. | ||
| public const int DefaultMaxDepth = 4096; |
There was a problem hiding this comment.
Is it potential DOS vector though?
| int MaxBatchSize { get; set; } | ||
|
|
||
| [ConfigItem(Description = "The maximum depth of JSON response object tree.", DefaultValue = "128")] | ||
| [ConfigItem(Description = "The maximum depth of JSON response object tree.", DefaultValue = "4096")] |
There was a problem hiding this comment.
Should we have such large default? What about DOS vectors?
Fixes Closes Resolves #
#8380
Changes
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Documentation
Requires documentation update
Requires explanation in Release Notes