From 50f7abc10dbced3964e439eb7553e7482e46a941 Mon Sep 17 00:00:00 2001 From: alinpahontu2912 Date: Tue, 26 Aug 2025 11:56:07 +0200 Subject: [PATCH 1/5] cap memory stream max length and add unit test for max capacity --- .../src/System/IO/MemoryStream.cs | 15 ++++++++------- .../MemoryStream/MemoryStreamTests.cs | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs index fc645292c91d58..e2144aa5f90ba3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; @@ -34,7 +34,8 @@ public class MemoryStream : Stream private CachedCompletedInt32Task _lastReadTask; // The last successful task returned from ReadAsync - private const int MemStreamMaxLength = int.MaxValue; + // Max array length allowed by CLR for byte[] + private const int MemStreamMaxLength = 0x7FFFFFC7; public MemoryStream() : this(0) @@ -536,24 +537,24 @@ private long SeekCore(long offset, int loc) // Sets the length of the stream to a given value. The new // value must be nonnegative and less than the space remaining in - // the array, int.MaxValue - origin + // the array, MemStreamMaxLength - origin // Origin is 0 in all cases other than a MemoryStream created on // top of an existing array and a specific starting offset was passed // into the MemoryStream constructor. The upper bounds prevents any // situations where a stream may be created on top of an array then // the stream is made longer than the maximum possible length of the - // array (int.MaxValue). + // array (MemStreamMaxLength // public override void SetLength(long value) { - if (value < 0 || value > int.MaxValue) + if (value < 0 || value > MemStreamMaxLength) throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_StreamLength); EnsureWriteable(); // Origin wasn't publicly exposed above. - Debug.Assert(MemStreamMaxLength == int.MaxValue); // Check parameter validation logic in this method if this fails. - if (value > (int.MaxValue - _origin)) + Debug.Assert(MemStreamMaxLength == 0x7FFFFFC7); // Check parameter validation logic in this method if this fails. + if (value > (MemStreamMaxLength - _origin)) throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_StreamLength); int newLength = _origin + (int)value; diff --git a/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs b/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs index 23d4b88c3da8d2..3601b701433def 100644 --- a/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs +++ b/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs @@ -146,6 +146,21 @@ public async Task DerivedMemoryStream_ReadWriteAsyncMemoryCalled_ReadWriteAsyncA Assert.True(s.ReadArrayInvoked); } + [Fact] + public void MemoryStream_CapacityBoundaryChecks() + { + const int MaxSupportedLength = 0x7FFFFFC7; + + using (var ms = new MemoryStream()) + { + ms.Capacity = MaxSupportedLength - 1; + + Assert.Equal(MaxSupportedLength - 1, ms.Capacity); + + Assert.ThrowsAny(() => ms.Capacity = MaxSupportedLength + 1); + } + } + private class ReadWriteOverridingMemoryStream : MemoryStream { public bool ReadArrayInvoked, WriteArrayInvoked; From 7d3947a909b66f23b0e6a0af43fe7886c673873d Mon Sep 17 00:00:00 2001 From: Stefan-Alin Pahontu <56953855+alinpahontu2912@users.noreply.github.com> Date: Tue, 26 Aug 2025 12:03:03 +0200 Subject: [PATCH 2/5] Update src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/IO/MemoryStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs index e2144aa5f90ba3..ec71301c0be496 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs @@ -543,7 +543,7 @@ private long SeekCore(long offset, int loc) // into the MemoryStream constructor. The upper bounds prevents any // situations where a stream may be created on top of an array then // the stream is made longer than the maximum possible length of the - // array (MemStreamMaxLength + // array (MemStreamMaxLength). // public override void SetLength(long value) { From 9995980468c6dad4621408f21977810de70694ae Mon Sep 17 00:00:00 2001 From: alinpahontu2912 Date: Tue, 26 Aug 2025 14:37:56 +0200 Subject: [PATCH 3/5] fix failing tests --- .../BinaryWriter/BinaryWriterTests.cs | 3 ++- .../MemoryStream/MemoryStreamTests.cs | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs b/src/libraries/System.Runtime/tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs index 1ed29de3009c22..06562604f0e262 100644 --- a/src/libraries/System.Runtime/tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs +++ b/src/libraries/System.Runtime/tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs @@ -91,7 +91,8 @@ public void BinaryWriter_EncodingCtorAndWriteTests_Negative() [Fact] public void BinaryWriter_SeekTests() { - int[] iArrLargeValues = new int[] { 10000, 100000, int.MaxValue / 200, int.MaxValue / 1000, short.MaxValue, int.MaxValue, int.MaxValue - 1, int.MaxValue / 2, int.MaxValue / 10, int.MaxValue / 100 }; + // Max length allowed by CLR for byte[] is 0x7FFFFFC7 + int[] iArrLargeValues = new int[] { 10000, 100000, int.MaxValue / 200, int.MaxValue / 1000, short.MaxValue, 0x7FFFFFC7, 0x7FFFFFC7 - 1, int.MaxValue / 2, int.MaxValue / 10, int.MaxValue / 100 }; BinaryWriter dw2 = null; MemoryStream mstr = null; diff --git a/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs b/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs index 3601b701433def..dd770768e86de8 100644 --- a/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs +++ b/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs @@ -105,8 +105,8 @@ public void MemoryStream_SeekOverflow_Throws(SeekMode mode, int bufferSize, int byte[] buffer = new byte[bufferSize]; using (MemoryStream ms = new MemoryStream(buffer, origin, buffer.Length - origin, true)) { - Seek(mode, ms, int.MaxValue - origin); - Assert.Throws(() => Seek(mode, ms, (long)int.MaxValue - origin + 1)); + Seek(mode, ms, 0x7FFFFFC7 - origin); + Assert.Throws(() => Seek(mode, ms, (long)0x7FFFFFC7 - origin + 1)); Assert.ThrowsAny(() => Seek(mode, ms, long.MinValue + 1)); Assert.ThrowsAny(() => Seek(mode, ms, long.MaxValue - 1)); } @@ -147,6 +147,7 @@ public async Task DerivedMemoryStream_ReadWriteAsyncMemoryCalled_ReadWriteAsyncA } [Fact] + [SkipOnCI("Skipping on CI due to large memory allocation")] public void MemoryStream_CapacityBoundaryChecks() { const int MaxSupportedLength = 0x7FFFFFC7; @@ -154,10 +155,14 @@ public void MemoryStream_CapacityBoundaryChecks() using (var ms = new MemoryStream()) { ms.Capacity = MaxSupportedLength - 1; - Assert.Equal(MaxSupportedLength - 1, ms.Capacity); - Assert.ThrowsAny(() => ms.Capacity = MaxSupportedLength + 1); + ms.Capacity = MaxSupportedLength; + Assert.Equal(MaxSupportedLength, ms.Capacity); + + Assert.Throws(() => ms.Capacity = MaxSupportedLength + 1); + + Assert.Throws(() => ms.Capacity = int.MaxValue); } } From ac778362fa5b46c16cb9df681235806805f3997b Mon Sep 17 00:00:00 2001 From: Stefan-Alin Pahontu <56953855+alinpahontu2912@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:19:01 +0200 Subject: [PATCH 4/5] Replace hardcoded value Co-authored-by: Theodore Tsirpanis --- .../System.Private.CoreLib/src/System/IO/MemoryStream.cs | 3 +-- .../tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs | 3 +-- .../tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs index ec71301c0be496..05480a61c72b5c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs @@ -34,8 +34,7 @@ public class MemoryStream : Stream private CachedCompletedInt32Task _lastReadTask; // The last successful task returned from ReadAsync - // Max array length allowed by CLR for byte[] - private const int MemStreamMaxLength = 0x7FFFFFC7; + private static int MemStreamMaxLength => Array.MaxLength; public MemoryStream() : this(0) diff --git a/src/libraries/System.Runtime/tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs b/src/libraries/System.Runtime/tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs index 06562604f0e262..1d1ed698b0dbdf 100644 --- a/src/libraries/System.Runtime/tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs +++ b/src/libraries/System.Runtime/tests/System.IO.Tests/BinaryWriter/BinaryWriterTests.cs @@ -91,8 +91,7 @@ public void BinaryWriter_EncodingCtorAndWriteTests_Negative() [Fact] public void BinaryWriter_SeekTests() { - // Max length allowed by CLR for byte[] is 0x7FFFFFC7 - int[] iArrLargeValues = new int[] { 10000, 100000, int.MaxValue / 200, int.MaxValue / 1000, short.MaxValue, 0x7FFFFFC7, 0x7FFFFFC7 - 1, int.MaxValue / 2, int.MaxValue / 10, int.MaxValue / 100 }; + int[] iArrLargeValues = new int[] { 10000, 100000, int.MaxValue / 200, int.MaxValue / 1000, short.MaxValue, Array.MaxLength, Array.MaxLength - 1, int.MaxValue / 2, int.MaxValue / 10, int.MaxValue / 100 }; BinaryWriter dw2 = null; MemoryStream mstr = null; diff --git a/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs b/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs index dd770768e86de8..ee77811f224290 100644 --- a/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs +++ b/src/libraries/System.Runtime/tests/System.IO.Tests/MemoryStream/MemoryStreamTests.cs @@ -105,8 +105,8 @@ public void MemoryStream_SeekOverflow_Throws(SeekMode mode, int bufferSize, int byte[] buffer = new byte[bufferSize]; using (MemoryStream ms = new MemoryStream(buffer, origin, buffer.Length - origin, true)) { - Seek(mode, ms, 0x7FFFFFC7 - origin); - Assert.Throws(() => Seek(mode, ms, (long)0x7FFFFFC7 - origin + 1)); + Seek(mode, ms, Array.MaxLength - origin); + Assert.Throws(() => Seek(mode, ms, (long)Array.MaxLength - origin + 1)); Assert.ThrowsAny(() => Seek(mode, ms, long.MinValue + 1)); Assert.ThrowsAny(() => Seek(mode, ms, long.MaxValue - 1)); } @@ -150,7 +150,7 @@ public async Task DerivedMemoryStream_ReadWriteAsyncMemoryCalled_ReadWriteAsyncA [SkipOnCI("Skipping on CI due to large memory allocation")] public void MemoryStream_CapacityBoundaryChecks() { - const int MaxSupportedLength = 0x7FFFFFC7; + int MaxSupportedLength = Array.MaxLength; using (var ms = new MemoryStream()) { From dff632b3a7a28b8bdb09831ca40c94960766d0bb Mon Sep 17 00:00:00 2001 From: alinpahontu2912 Date: Tue, 26 Aug 2025 17:10:33 +0200 Subject: [PATCH 5/5] update error message --- src/libraries/System.Private.CoreLib/src/Resources/Strings.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 289b1df2b90512..23f8538a1dc16b 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1944,7 +1944,7 @@ startIndex cannot be larger than length of string. - Stream length must be non-negative and less than 2^31 - 1 - origin. + Stream length must be non-negative and less than the maximum array length (0x7FFFFFC7) - origin. The length of the buffer must be less than the maximum UIntPtr value for your platform.