Skip to content

Commit c435236

Browse files
bshastryclaude
andauthored
fix: prevent EVM memory size overflow crash for extreme memory requests (#9887)
fix: prevent EVM memory size overflow crash for large memory requests The memory validation in CheckMemoryAccessViolation was checking against long.MaxValue, but .NET arrays are limited to int.MaxValue. When memory requests exceeded int.MaxValue but were less than long.MaxValue, the word-aligned size calculation would overflow when cast to int, causing ArrayPool.Rent() to allocate an incorrectly sized array and subsequent Array.Copy operations to crash. This fix changes the limit from long.MaxValue to (int.MaxValue - WordSize + 1) to ensure that after word alignment, the resulting size still fits in int. Root cause: - Memory size validation checked: totalSize > long.MaxValue - But .NET arrays require: (int)Size to be valid - Word alignment adds up to 31 bytes: Size = totalSize + (32 - totalSize % 32) - When totalSize > int.MaxValue - 31, (int)Size overflows Example crash scenario: 1. Contract requests 4GB (0xffffffff) memory via DELEGATECALL 2. Validation passes (4GB < long.MaxValue) 3. Word-aligned Size = 0x100000000 (exceeds int.MaxValue) 4. (int)Size = 0 (overflow) 5. ArrayPool.Rent(0) returns tiny array 6. Array.Copy crashes with "Destination array was not long enough" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent d7ad74e commit c435236

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public void Div32Ceiling(int input, int expectedResult)
5454
[TestCase(100 * MaxCodeSize, MaxCodeSize)]
5555
[TestCase(1000 * MaxCodeSize, MaxCodeSize)]
5656
[TestCase(0, 1024 * 1024)]
57-
[TestCase(0, Int32.MaxValue)]
57+
// Note: Int32.MaxValue was removed as a test case because after word alignment
58+
// it exceeds the maximum allowed memory size and correctly returns out-of-gas.
5859
public void MemoryCost(int destination, int memoryAllocation)
5960
{
6061
EvmPooledMemory memory = new();
@@ -114,6 +115,50 @@ public void CalculateMemoryCost_TotalSizeExceedsLongMax_ShouldReturnOutOfGas()
114115
Assert.That(result, Is.EqualTo(0L));
115116
}
116117

118+
[Test]
119+
public void CalculateMemoryCost_TotalSizeExceedsIntMaxAfterWordAlignment_ShouldReturnOutOfGas()
120+
{
121+
// Test that memory requests that would overflow int.MaxValue after word alignment
122+
// are properly rejected. This prevents crashes in .NET array operations.
123+
// The limit is int.MaxValue - WordSize + 1 to ensure word-aligned size fits in int.
124+
EvmPooledMemory memory = new();
125+
126+
// Request exactly at the limit should succeed
127+
UInt256 maxAllowedSize = (UInt256)(int.MaxValue - EvmPooledMemory.WordSize + 1);
128+
long result = memory.CalculateMemoryCost(0, in maxAllowedSize, out bool outOfGas);
129+
Assert.That(outOfGas, Is.EqualTo(false), "Size at limit should be allowed");
130+
131+
// Request one byte over the limit should fail
132+
UInt256 overLimitSize = maxAllowedSize + 1;
133+
result = memory.CalculateMemoryCost(0, in overLimitSize, out outOfGas);
134+
Assert.That(outOfGas, Is.EqualTo(true), "Size over limit should return out of gas");
135+
Assert.That(result, Is.EqualTo(0L));
136+
}
137+
138+
[Test]
139+
public void CalculateMemoryCost_4GBMemoryRequest_ShouldReturnOutOfGas()
140+
{
141+
// Regression test: 4GB memory request (0xffffffff) should return out-of-gas
142+
// instead of causing integer overflow crash in array operations.
143+
EvmPooledMemory memory = new();
144+
UInt256 size4GB = 0xffffffffUL;
145+
long result = memory.CalculateMemoryCost(0, in size4GB, out bool outOfGas);
146+
Assert.That(outOfGas, Is.EqualTo(true), "4GB memory request should return out of gas");
147+
Assert.That(result, Is.EqualTo(0L));
148+
}
149+
150+
[Test]
151+
public void CalculateMemoryCost_LargeOffsetPlusLength_ShouldReturnOutOfGas()
152+
{
153+
// Test that location + length exceeding int.MaxValue - WordSize + 1 returns out-of-gas
154+
EvmPooledMemory memory = new();
155+
UInt256 location = (UInt256)(int.MaxValue / 2);
156+
UInt256 length = (UInt256)(int.MaxValue / 2 + 100); // Sum exceeds limit
157+
long result = memory.CalculateMemoryCost(in location, in length, out bool outOfGas);
158+
Assert.That(outOfGas, Is.EqualTo(true), "Location + length exceeding limit should return out of gas");
159+
Assert.That(result, Is.EqualTo(0L));
160+
}
161+
117162
[Test]
118163
public void Save_LocationExceedsULong_ShouldReturnOutOfGas()
119164
{

src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,20 @@ private static void CheckMemoryAccessViolation(in UInt256 location, in UInt256 l
8484

8585
private static void CheckMemoryAccessViolation(in UInt256 location, ulong length, out ulong newLength, out bool outOfGas)
8686
{
87-
if (location.IsLargerThanULong() || length > long.MaxValue)
87+
// Check for overflow and ensure the word-aligned size fits in int.
88+
// Word alignment can add up to 31 bytes, so we use (int.MaxValue - WordSize + 1) as the limit.
89+
// This ensures that after word alignment, the size still fits in int for .NET array operations.
90+
const ulong MaxMemorySize = int.MaxValue - WordSize + 1;
91+
92+
if (location.IsLargerThanULong() || length > MaxMemorySize)
8893
{
8994
outOfGas = true;
9095
newLength = 0;
9196
return;
9297
}
9398

9499
ulong totalSize = location.u0 + length;
95-
if (totalSize < location.u0 || totalSize > long.MaxValue)
100+
if (totalSize < location.u0 || totalSize > MaxMemorySize)
96101
{
97102
outOfGas = true;
98103
newLength = 0;

0 commit comments

Comments
 (0)