Copilot commented on code in PR #3851:
URL: https://github.com/apache/avro/pull/3851#discussion_r3567488052


##########
lang/ruby/lib/avro/data_file.rb:
##########
@@ -319,10 +340,22 @@ def decompress(compressed)
         # (without the RFC1950 header & checksum). See the docs for
         # inflateInit2 in https://www.zlib.net/manual.html
         zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
-        data = zstream.inflate(compressed)
-        data << zstream.finish
+        limit = DataFile.max_decompress_length
+        data = "".b
+        # Inflate in chunks so an over-large (or malicious) block is rejected
+        # before its full decompressed form is materialized in memory.
+        append = lambda do |chunk|
+          if data.bytesize + chunk.bytesize > limit
+            raise DecompressionSizeError, "Decompressed block size exceeds the 
maximum allowed of #{limit} bytes"
+          end
+          data << chunk
+        end
+        zstream.inflate(compressed, &append)
+        remaining = zstream.finish
+        append.call(remaining) unless remaining.empty?
+        data

Review Comment:
   `DeflateCodec#decompress` calls `Zlib::Inflate#inflate` with a block 
(`zstream.inflate(compressed, &append)`). Ruby only added block/yield support 
for `Inflate#inflate` in Ruby 3.1+, but this gem declares 
`required_ruby_version >= 2.7`, so this will raise `ArgumentError` on Ruby 
2.7–3.0. To keep the size guard while preserving Ruby 2.7 compatibility, fall 
back to feeding the inflater the compressed input in small slices (calling 
`inflate` repeatedly) when running on older Rubies.



##########
lang/ruby/lib/avro/data_file.rb:
##########
@@ -374,11 +439,30 @@ def load_snappy!
     end
 
     class ZstandardCodec
+      # Size of the compressed input fed to the streaming decompressor per 
step,
+      # so the decompressed output can be bounded incrementally.
+      ZSTD_DECOMPRESS_CHUNK_SIZE = 16 * 1024
+
       def codec_name; 'zstandard'; end
 
       def decompress(data)
         load_zstandard!
-        Zstd.decompress(data)
+        limit = DataFile.max_decompress_length
+        stream = Zstd::StreamingDecompress.new
+        uncompressed = +''.b
+        offset = 0
+        size = data.bytesize
+        # Decompress the block in chunks so an over-large (or malicious) block 
is
+        # rejected before its full decompressed form is materialized in memory.
+        while offset < size
+          out = stream.decompress(data.byteslice(offset, 
ZSTD_DECOMPRESS_CHUNK_SIZE))
+          offset += ZSTD_DECOMPRESS_CHUNK_SIZE
+          if uncompressed.bytesize + out.bytesize > limit
+            raise DecompressionSizeError, "Decompressed block size exceeds the 
maximum allowed of #{limit} bytes"
+          end
+          uncompressed << out
+        end
+        uncompressed
       end

Review Comment:
   `ZstandardCodec#decompress` accumulates output from `stream.decompress(...)` 
but never calls `stream.finish`. With streaming decompressors, the final bytes 
can remain buffered until `finish` is called, so callers may see truncated 
output (and the within-limit test may not cover all frames/versions). Consider 
appending `stream.finish` (with the same size check) and closing the stream in 
an `ensure` to avoid native resource leaks.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to