From 5b63084148da77545e1e0bf9fdd314ff01bdcfdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 14:58:59 +0200 Subject: [PATCH 01/10] AVRO-4286: [csharp] Enforce a maximum decompressed block size When reading a data file, each block is decompressed according to the file's codec. A block with a very high compression ratio (or a malformed block) could expand to far more memory than its compressed size. Enforce a configurable maximum decompressed size, mirroring the Java SDK's decompression limit (AVRO-4247): the built-in deflate codec is inflated in chunks and bounded before the full output is materialized, and DataFileReader additionally checks the decompressed size of every codec's output as a safeguard. The limit defaults to 200 MiB and can be overridden with the AVRO_MAX_DECOMPRESS_LENGTH environment variable; exceeding it throws AvroRuntimeException. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/main/File/Codec.cs | 65 +++++++++++++++++++ .../src/apache/main/File/DataFileReader.cs | 5 ++ .../src/apache/main/File/DeflateCodec.cs | 5 +- lang/csharp/src/apache/test/File/FileTests.cs | 45 +++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/lang/csharp/src/apache/main/File/Codec.cs b/lang/csharp/src/apache/main/File/Codec.cs index 46191997a1d..a32ee616d87 100644 --- a/lang/csharp/src/apache/main/File/Codec.cs +++ b/lang/csharp/src/apache/main/File/Codec.cs @@ -29,6 +29,71 @@ namespace Avro.File /// public abstract class Codec { + /// + /// Default upper bound, in bytes, on the size a single data-file block may + /// decompress to. A block with a very high compression ratio (or a malformed + /// block) can otherwise expand to far more memory than its compressed size. + /// Mirrors the Java SDK's decompression limit (AVRO-4247). Overridable with + /// the AVRO_MAX_DECOMPRESS_LENGTH environment variable. + /// + public const long DefaultMaxDecompressLength = 200L * 1024 * 1024; // 200 MiB + + /// + /// Name of the environment variable used to override the default maximum + /// decompressed size of a single block. + /// + public const string MaxDecompressLengthEnvVar = "AVRO_MAX_DECOMPRESS_LENGTH"; + + /// + /// The maximum number of bytes a single block is allowed to decompress to. + /// + /// The configured limit, honoring the environment override. + public static long GetMaxDecompressLength() + { + var value = Environment.GetEnvironmentVariable(MaxDecompressLengthEnvVar); + if (value != null && long.TryParse(value, out var parsed) && parsed > 0) + { + return parsed; + } + + return DefaultMaxDecompressLength; + } + + /// + /// Throws if the given decompressed length exceeds the maximum allowed. + /// + /// The number of decompressed bytes. + /// The maximum number of decompressed bytes allowed. + public static void CheckDecompressLength(long length, long maxLength) + { + if (length > maxLength) + { + throw new AvroRuntimeException( + $"Decompressed block size exceeds the maximum allowed of {maxLength} bytes"); + } + } + + /// + /// Copies a decompression stream to the destination, rejecting the block as + /// soon as its decompressed size would exceed so + /// an over-large (or malicious) block is not fully materialized in memory. + /// + /// The decompression stream to read from. + /// The stream to write the decompressed data to. + /// The maximum number of decompressed bytes allowed. + public static void CopyBounded(Stream source, Stream destination, long maxLength) + { + byte[] buffer = new byte[81920]; + long total = 0; + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) > 0) + { + total += read; + CheckDecompressLength(total, maxLength); + destination.Write(buffer, 0, read); + } + } + /// /// Compress data using implemented codec. /// diff --git a/lang/csharp/src/apache/main/File/DataFileReader.cs b/lang/csharp/src/apache/main/File/DataFileReader.cs index dff13e05885..9b1e61a0869 100644 --- a/lang/csharp/src/apache/main/File/DataFileReader.cs +++ b/lang/csharp/src/apache/main/File/DataFileReader.cs @@ -335,6 +335,11 @@ public bool HasNext() { _currentBlock = NextRawBlock(_currentBlock); _currentBlock.Data = _codec.Decompress(_currentBlock.Data, (int)_blockSize); + // Guard against a block that decompresses to more than the + // allowed maximum (a decompression bomb). The built-in deflate + // codec is already bounded during decompression; this covers + // any codec that returns a fully decompressed buffer. + Codec.CheckDecompressLength(_currentBlock.Data.Length, Codec.GetMaxDecompressLength()); _datumDecoder = new BinaryDecoder(_currentBlock.GetDataAsStream()); } } diff --git a/lang/csharp/src/apache/main/File/DeflateCodec.cs b/lang/csharp/src/apache/main/File/DeflateCodec.cs index 0ce37adb092..0cedb64fe78 100644 --- a/lang/csharp/src/apache/main/File/DeflateCodec.cs +++ b/lang/csharp/src/apache/main/File/DeflateCodec.cs @@ -63,7 +63,10 @@ public override byte[] Decompress(byte[] compressedData, int length) { using (DeflateStream decompress = new DeflateStream(inStream, CompressionMode.Decompress)) { - decompress.CopyTo(outStream); + // Bound the decompressed size to guard against a block with a very + // high compression ratio expanding to far more memory than its + // compressed size. + CopyBounded(decompress, outStream, GetMaxDecompressLength()); } return outStream.ToArray(); } diff --git a/lang/csharp/src/apache/test/File/FileTests.cs b/lang/csharp/src/apache/test/File/FileTests.cs index abb3f9c6076..d3c572581eb 100644 --- a/lang/csharp/src/apache/test/File/FileTests.cs +++ b/lang/csharp/src/apache/test/File/FileTests.cs @@ -425,6 +425,51 @@ public void OpenAppendWriter_IncorrectOutStream_Throws() Assert.Throws(typeof(AvroRuntimeException), action); } + /// + /// A block with a very high compression ratio can expand to far more memory + /// than its compressed size; decompressing such a block must be rejected once + /// its decompressed size would exceed the configured maximum. + /// + [Test] + public void TestDeflateDecompressionLimit() + { + var codec = new DeflateCodec(); + byte[] big = new byte[4 * 1024 * 1024]; // 4 MiB of zeros, compresses tiny + byte[] compressed = codec.Compress(big); + + var previous = Environment.GetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar); + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "1048576"); // 1 MiB + try + { + Assert.Throws( + () => codec.Decompress(compressed, compressed.Length)); + } + finally + { + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, previous); + } + } + + [Test] + public void TestDeflateWithinDecompressionLimit() + { + var codec = new DeflateCodec(); + byte[] payload = System.Text.Encoding.UTF8.GetBytes("hello world"); + byte[] compressed = codec.Compress(payload); + + var previous = Environment.GetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar); + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "1048576"); // 1 MiB + try + { + byte[] result = codec.Decompress(compressed, compressed.Length); + Assert.AreEqual(payload, result); + } + finally + { + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, previous); + } + } + /// /// This test is a single test case of /// but introduces a From 15737e5942f764289e9a9bc0696f2094ae11536f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 17:39:37 +0200 Subject: [PATCH 02/10] AVRO-4286: [csharp] Address review: actionable message; validate CopyBounded args - The decompression-limit exception now reports the observed decompressed size and names the AVRO_MAX_DECOMPRESS_LENGTH environment variable to raise it. - CopyBounded (public API) validates its arguments, throwing ArgumentNullException for null streams and ArgumentOutOfRangeException for a negative maxLength instead of a NullReferenceException / unexpected behavior. Add a test. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/main/File/Codec.cs | 18 +++++++++++++++++- lang/csharp/src/apache/test/File/FileTests.cs | 11 +++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lang/csharp/src/apache/main/File/Codec.cs b/lang/csharp/src/apache/main/File/Codec.cs index a32ee616d87..c8872f4918c 100644 --- a/lang/csharp/src/apache/main/File/Codec.cs +++ b/lang/csharp/src/apache/main/File/Codec.cs @@ -69,7 +69,8 @@ public static void CheckDecompressLength(long length, long maxLength) if (length > maxLength) { throw new AvroRuntimeException( - $"Decompressed block size exceeds the maximum allowed of {maxLength} bytes"); + $"Decompressed block size {length} exceeds the maximum allowed of {maxLength} bytes. " + + $"Set the {MaxDecompressLengthEnvVar} environment variable to raise the limit."); } } @@ -83,6 +84,21 @@ public static void CheckDecompressLength(long length, long maxLength) /// The maximum number of decompressed bytes allowed. public static void CopyBounded(Stream source, Stream destination, long maxLength) { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (destination == null) + { + throw new ArgumentNullException(nameof(destination)); + } + + if (maxLength < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxLength), "maxLength must not be negative."); + } + byte[] buffer = new byte[81920]; long total = 0; int read; diff --git a/lang/csharp/src/apache/test/File/FileTests.cs b/lang/csharp/src/apache/test/File/FileTests.cs index d3c572581eb..4dd180a7a95 100644 --- a/lang/csharp/src/apache/test/File/FileTests.cs +++ b/lang/csharp/src/apache/test/File/FileTests.cs @@ -470,6 +470,17 @@ public void TestDeflateWithinDecompressionLimit() } } + [Test] + public void TestCopyBoundedValidatesArguments() + { + using (var stream = new MemoryStream()) + { + Assert.Throws(() => Codec.CopyBounded(null, stream, 10)); + Assert.Throws(() => Codec.CopyBounded(stream, null, 10)); + Assert.Throws(() => Codec.CopyBounded(stream, stream, -1)); + } + } + /// /// This test is a single test case of /// but introduces a From f8c8e1d630d3aad572a21b2d16cec1fbf8e005dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 19:06:37 +0200 Subject: [PATCH 03/10] AVRO-4286: [csharp] Preserve inner exception when wrapping block errors HasNext() wrapped any block-fetch failure in an AvroRuntimeException but only folded the original into the message string, dropping the stack trace and exception chain. Pass the original as the inner exception and use its Message in the text so callers can inspect and log the underlying cause. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/main/File/DataFileReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/csharp/src/apache/main/File/DataFileReader.cs b/lang/csharp/src/apache/main/File/DataFileReader.cs index 9b1e61a0869..7bd8c6d27b3 100644 --- a/lang/csharp/src/apache/main/File/DataFileReader.cs +++ b/lang/csharp/src/apache/main/File/DataFileReader.cs @@ -233,7 +233,7 @@ public string GetMetaString(string key) catch (Exception e) { throw new AvroRuntimeException(string.Format(CultureInfo.InvariantCulture, - "Error fetching meta data for key: {0}", key), e); + "Error fetching next object from block: {0}", e.Message), e); } } From 7a05ac129d172cad9255095c65c17e919540a7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 19:22:48 +0200 Subject: [PATCH 04/10] AVRO-4286: [csharp] Fix HasNext wrap; restore GetMetaString message; test reader path - The previous change accidentally edited GetMetaString's catch instead of HasNext. Restore GetMetaString's metadata-specific message (with the key) and apply the inner-exception preservation to HasNext, where a block-fetch failure is wrapped: it now passes the original exception as InnerException and uses its Message in the text. - Add TestReaderRejectsOversizedBlock, which writes a Null-codec block larger than the limit and asserts the DataFileReader read path (not just a direct codec call) rejects it, covering CheckDecompressLength for codecs that return a fully materialized buffer. Assisted-by: GitHub Copilot:claude-opus-4.8 --- .../src/apache/main/File/DataFileReader.cs | 4 +- lang/csharp/src/apache/test/File/FileTests.cs | 47 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lang/csharp/src/apache/main/File/DataFileReader.cs b/lang/csharp/src/apache/main/File/DataFileReader.cs index 7bd8c6d27b3..69e1deb3af8 100644 --- a/lang/csharp/src/apache/main/File/DataFileReader.cs +++ b/lang/csharp/src/apache/main/File/DataFileReader.cs @@ -233,7 +233,7 @@ public string GetMetaString(string key) catch (Exception e) { throw new AvroRuntimeException(string.Format(CultureInfo.InvariantCulture, - "Error fetching next object from block: {0}", e.Message), e); + "Error fetching meta data for key: {0}", key), e); } } @@ -348,7 +348,7 @@ public bool HasNext() catch (Exception e) { throw new AvroRuntimeException(string.Format(CultureInfo.InvariantCulture, - "Error fetching next object from block: {0}", e)); + "Error fetching next object from block: {0}", e.Message), e); } } diff --git a/lang/csharp/src/apache/test/File/FileTests.cs b/lang/csharp/src/apache/test/File/FileTests.cs index 4dd180a7a95..d866184f093 100644 --- a/lang/csharp/src/apache/test/File/FileTests.cs +++ b/lang/csharp/src/apache/test/File/FileTests.cs @@ -481,6 +481,53 @@ public void TestCopyBoundedValidatesArguments() } } + /// + /// The DataFileReader itself must reject a block whose decompressed size + /// exceeds the configured maximum. This covers the safeguard applied to + /// every codec that returns a fully materialized buffer (here the Null + /// codec, which performs no internally bounded decompression), not just a + /// direct call to a codec's Decompress method. + /// + [Test] + public void TestReaderRejectsOversizedBlock() + { + const string schemaStr = + "{\"type\":\"record\",\"name\":\"n\",\"fields\":[{\"name\":\"f1\",\"type\":\"string\"}]}"; + Schema schema = Schema.Parse(schemaStr); + var recordSchema = schema as RecordSchema; + + // A single record whose string field is larger than the limit below. + string big = new string('a', 2 * 1024 * 1024); // 2 MiB + + MemoryStream outStream = new MemoryStream(); + using (var writer = DataFileWriter.OpenWriter( + new GenericWriter(schema), outStream, Codec.CreateCodec(Codec.Type.Null))) + { + writer.Append(mkRecord(new object[] { "f1", big }, recordSchema)); + } + + MemoryStream inStream = new MemoryStream(outStream.ToArray()); + + var previous = Environment.GetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar); + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "1048576"); // 1 MiB + try + { + Assert.Throws(() => + { + using (var reader = DataFileReader.OpenReader(inStream, schema)) + { + foreach (var rec in reader.NextEntries) + { + } + } + }); + } + finally + { + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, previous); + } + } + /// /// This test is a single test case of /// but introduces a From f17a90e45f689dbd28c209f4130de92cb1cf215e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Sat, 11 Jul 2026 20:08:21 +0200 Subject: [PATCH 05/10] AVRO-4286: [csharp] Assert on enumerated record in reader-limit test The oversized-block test had an empty foreach body with an unused loop variable, which CodeQL flagged as a useless assignment and an empty loop body. Assert.NotNull(rec) inside the loop reads the variable and gives the body a meaningful statement while still forcing the block to be read and decompressed. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/test/File/FileTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lang/csharp/src/apache/test/File/FileTests.cs b/lang/csharp/src/apache/test/File/FileTests.cs index d866184f093..d14f7545453 100644 --- a/lang/csharp/src/apache/test/File/FileTests.cs +++ b/lang/csharp/src/apache/test/File/FileTests.cs @@ -516,8 +516,11 @@ public void TestReaderRejectsOversizedBlock() { using (var reader = DataFileReader.OpenReader(inStream, schema)) { + // Enumerating forces the block to be read and + // decompressed, which is where the limit is enforced. foreach (var rec in reader.NextEntries) { + Assert.NotNull(rec); } } }); From 51bf9f85c0fb406b0002bfae94c5fedc6a8ac51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 00:54:06 +0200 Subject: [PATCH 06/10] AVRO-4286: [csharp] Address review: static readonly default, invariant parse - Make DefaultMaxDecompressLength `static readonly` instead of `const` so changing the default later does not require recompiling assemblies that reference this one (and the value is not inlined into them). - Parse the AVRO_MAX_DECOMPRESS_LENGTH override with CultureInfo.InvariantCulture so the environment variable is interpreted consistently regardless of the current culture. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/main/File/Codec.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lang/csharp/src/apache/main/File/Codec.cs b/lang/csharp/src/apache/main/File/Codec.cs index c8872f4918c..302c634bb3a 100644 --- a/lang/csharp/src/apache/main/File/Codec.cs +++ b/lang/csharp/src/apache/main/File/Codec.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Reflection; @@ -36,7 +37,7 @@ public abstract class Codec /// Mirrors the Java SDK's decompression limit (AVRO-4247). Overridable with /// the AVRO_MAX_DECOMPRESS_LENGTH environment variable. /// - public const long DefaultMaxDecompressLength = 200L * 1024 * 1024; // 200 MiB + public static readonly long DefaultMaxDecompressLength = 200L * 1024 * 1024; // 200 MiB /// /// Name of the environment variable used to override the default maximum @@ -51,7 +52,7 @@ public abstract class Codec public static long GetMaxDecompressLength() { var value = Environment.GetEnvironmentVariable(MaxDecompressLengthEnvVar); - if (value != null && long.TryParse(value, out var parsed) && parsed > 0) + if (value != null && long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed) && parsed > 0) { return parsed; } From 76e74599da8bcbc043e2be70c235b53fb63c43e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 02:00:29 +0200 Subject: [PATCH 07/10] AVRO-4286: [csharp] Guard CopyBounded against total overflow CopyBounded did `total += read` before checking the limit; with a very large maxLength the running total could overflow and wrap negative, bypassing the check and allowing unbounded copying. Reject before adding (`read > maxLength - total`); total is always <= maxLength here and read > 0, so the subtraction cannot underflow/overflow. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/main/File/Codec.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lang/csharp/src/apache/main/File/Codec.cs b/lang/csharp/src/apache/main/File/Codec.cs index 302c634bb3a..b8e6af3391f 100644 --- a/lang/csharp/src/apache/main/File/Codec.cs +++ b/lang/csharp/src/apache/main/File/Codec.cs @@ -105,8 +105,18 @@ public static void CopyBounded(Stream source, Stream destination, long maxLength int read; while ((read = source.Read(buffer, 0, buffer.Length)) > 0) { + // Pre-add bound check: total is always <= maxLength here and + // read > 0, so maxLength - total >= 0 and this cannot overflow. + // Rejecting before adding stops total from overflowing and + // wrapping past the limit for a very large maxLength. + if (read > maxLength - total) + { + throw new AvroRuntimeException( + $"Decompressed block size exceeds the maximum allowed of {maxLength} bytes. " + + $"Set the {MaxDecompressLengthEnvVar} environment variable to raise the limit."); + } + total += read; - CheckDecompressLength(total, maxLength); destination.Write(buffer, 0, read); } } From 55d0e4f75398207b4d43e1689b825ca24167f7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 02:20:03 +0200 Subject: [PATCH 08/10] AVRO-4286: [csharp] Make the decompression-limit message generic CheckDecompressLength takes an explicit maxLength (also used by CopyBounded with a caller-supplied max), but the message always told users to set the env var. Reword so it states the env var raises the limit for data-file reads, rather than implying it is the only knob. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/main/File/Codec.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/csharp/src/apache/main/File/Codec.cs b/lang/csharp/src/apache/main/File/Codec.cs index b8e6af3391f..7645c36f6ed 100644 --- a/lang/csharp/src/apache/main/File/Codec.cs +++ b/lang/csharp/src/apache/main/File/Codec.cs @@ -71,7 +71,7 @@ public static void CheckDecompressLength(long length, long maxLength) { throw new AvroRuntimeException( $"Decompressed block size {length} exceeds the maximum allowed of {maxLength} bytes. " + - $"Set the {MaxDecompressLengthEnvVar} environment variable to raise the limit."); + $"For data-file reads, the {MaxDecompressLengthEnvVar} environment variable raises the limit."); } } From 968ac0c05e2b274d0739735643b7b61121f141e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 02:44:26 +0200 Subject: [PATCH 09/10] AVRO-4286: [csharp] Report decompressed-so-far in CopyBounded; lighten tests - CopyBounded's over-limit exception now reports the amount already decompressed ("at least {total} bytes already decompressed"), matching the detail of the non-streaming CheckDecompressLength message. - The decompression-limit tests allocated 4 MiB / 2 MiB payloads against a 1 MiB limit; use a 64 KiB limit with 128 KiB payloads instead, which exercises the same behavior with far less memory/time. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/main/File/Codec.cs | 5 +++-- lang/csharp/src/apache/test/File/FileTests.cs | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lang/csharp/src/apache/main/File/Codec.cs b/lang/csharp/src/apache/main/File/Codec.cs index 7645c36f6ed..06f1b626658 100644 --- a/lang/csharp/src/apache/main/File/Codec.cs +++ b/lang/csharp/src/apache/main/File/Codec.cs @@ -112,8 +112,9 @@ public static void CopyBounded(Stream source, Stream destination, long maxLength if (read > maxLength - total) { throw new AvroRuntimeException( - $"Decompressed block size exceeds the maximum allowed of {maxLength} bytes. " + - $"Set the {MaxDecompressLengthEnvVar} environment variable to raise the limit."); + $"Decompressed block size exceeds the maximum allowed of {maxLength} bytes " + + $"(at least {total} bytes already decompressed). " + + $"For data-file reads, the {MaxDecompressLengthEnvVar} environment variable raises the limit."); } total += read; diff --git a/lang/csharp/src/apache/test/File/FileTests.cs b/lang/csharp/src/apache/test/File/FileTests.cs index d14f7545453..be4ef203c31 100644 --- a/lang/csharp/src/apache/test/File/FileTests.cs +++ b/lang/csharp/src/apache/test/File/FileTests.cs @@ -434,11 +434,11 @@ public void OpenAppendWriter_IncorrectOutStream_Throws() public void TestDeflateDecompressionLimit() { var codec = new DeflateCodec(); - byte[] big = new byte[4 * 1024 * 1024]; // 4 MiB of zeros, compresses tiny + byte[] big = new byte[128 * 1024]; // 128 KiB of zeros, compresses tiny byte[] compressed = codec.Compress(big); var previous = Environment.GetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar); - Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "1048576"); // 1 MiB + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "65536"); // 64 KiB try { Assert.Throws( @@ -458,7 +458,7 @@ public void TestDeflateWithinDecompressionLimit() byte[] compressed = codec.Compress(payload); var previous = Environment.GetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar); - Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "1048576"); // 1 MiB + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "65536"); // 64 KiB try { byte[] result = codec.Decompress(compressed, compressed.Length); @@ -497,7 +497,7 @@ public void TestReaderRejectsOversizedBlock() var recordSchema = schema as RecordSchema; // A single record whose string field is larger than the limit below. - string big = new string('a', 2 * 1024 * 1024); // 2 MiB + string big = new string('a', 128 * 1024); // 128 KiB MemoryStream outStream = new MemoryStream(); using (var writer = DataFileWriter.OpenWriter( @@ -509,7 +509,7 @@ public void TestReaderRejectsOversizedBlock() MemoryStream inStream = new MemoryStream(outStream.ToArray()); var previous = Environment.GetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar); - Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "1048576"); // 1 MiB + Environment.SetEnvironmentVariable(Codec.MaxDecompressLengthEnvVar, "65536"); // 64 KiB try { Assert.Throws(() => From 6bea53e54f3d57ffabf408eda877c06e1f7de9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Mej=C3=ADa?= Date: Mon, 13 Jul 2026 03:40:49 +0200 Subject: [PATCH 10/10] AVRO-4286: [csharp] Use CollectionAssert for the decompressed byte-array check Compare the decompressed byte[] with CollectionAssert.AreEqual, which does an element-wise comparison with clearer failure output than Assert.AreEqual. Assisted-by: GitHub Copilot:claude-opus-4.8 --- lang/csharp/src/apache/test/File/FileTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/csharp/src/apache/test/File/FileTests.cs b/lang/csharp/src/apache/test/File/FileTests.cs index be4ef203c31..3ec74cb1a70 100644 --- a/lang/csharp/src/apache/test/File/FileTests.cs +++ b/lang/csharp/src/apache/test/File/FileTests.cs @@ -462,7 +462,7 @@ public void TestDeflateWithinDecompressionLimit() try { byte[] result = codec.Decompress(compressed, compressed.Length); - Assert.AreEqual(payload, result); + CollectionAssert.AreEqual(payload, result); } finally {