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


##########
lang/py/avro/io.py:
##########
@@ -103,6 +104,53 @@
 STRUCT_SIGNED_INT = struct.Struct(">i")  # big-endian signed int
 STRUCT_SIGNED_LONG = struct.Struct(">q")  # big-endian signed long
 
+# Name of the environment variable used to override the default maximum number
+# of items permitted in a single decoded array or map.
+MAX_COLLECTION_ITEMS_ENV = "AVRO_MAX_COLLECTION_ITEMS"
+
+# Default upper bound on the number of items in a single array or map decoded
+# from a stream. When decoding, the block count of an array or map is read from
+# the (potentially untrusted or truncated) input and drives the allocation of
+# the resulting collection. Reading a collection that declares more items than
+# this limit raises an :class:`avro.errors.AvroCollectionSizeException` instead
+# of attempting a potentially huge allocation. This mirrors the Java SDK's
+# ``org.apache.avro.limits.collectionItems.maxLength`` limit. The default may 
be
+# overridden with the ``AVRO_MAX_COLLECTION_ITEMS`` environment variable.
+DEFAULT_MAX_COLLECTION_ITEMS = (1 << 31) - 8
+
+
+def _default_max_collection_items() -> int:
+    """Return the default collection item limit, honoring the environment 
override."""
+    value = os.environ.get(MAX_COLLECTION_ITEMS_ENV)
+    if value is None:
+        return DEFAULT_MAX_COLLECTION_ITEMS
+    try:
+        parsed = int(value)
+    except ValueError:
+        warnings.warn(f"Ignoring invalid {MAX_COLLECTION_ITEMS_ENV} value: 
{value!r}", avro.errors.AvroWarning)

Review Comment:
   `warnings.warn` is called with a string message + category class here, but 
the rest of the codebase consistently passes an `AvroWarning` instance (e.g., 
`warnings.warn(avro.errors.AvroWarning(...))`). Using the instance form keeps 
warning construction consistent across modules.



##########
lang/py/avro/io.py:
##########
@@ -834,13 +887,19 @@ def read_map(self, writers_schema: avro.schema.MapSchema, 
readers_schema: avro.s
         """
         read_items = {}
         block_count = decoder.read_long()
+        # Track the number of pairs decoded rather than len(read_items): 
repeated
+        # keys collapse in the dict and would otherwise let the cumulative 
check
+        # be bypassed by a stream that keeps rewriting the same key.
+        items_read = 0
         while block_count != 0:
             if block_count < 0:
                 block_count = -block_count
                 decoder.skip_long()
+            check_max_collection_items(items_read, block_count, 
self.max_collection_items)

Review Comment:
   `read_map` now enforces `max_collection_items`, but `skip_map` can still be 
driven by an untrusted `block_count` into an unbounded loop while skipping 
writer-only data (CPU DoS). Mirroring the same cumulative limit check in 
`skip_map` keeps the protection effective even when fields are skipped during 
schema resolution.



##########
lang/py/avro/io.py:
##########
@@ -103,6 +104,53 @@
 STRUCT_SIGNED_INT = struct.Struct(">i")  # big-endian signed int
 STRUCT_SIGNED_LONG = struct.Struct(">q")  # big-endian signed long
 
+# Name of the environment variable used to override the default maximum number
+# of items permitted in a single decoded array or map.
+MAX_COLLECTION_ITEMS_ENV = "AVRO_MAX_COLLECTION_ITEMS"
+
+# Default upper bound on the number of items in a single array or map decoded
+# from a stream. When decoding, the block count of an array or map is read from
+# the (potentially untrusted or truncated) input and drives the allocation of
+# the resulting collection. Reading a collection that declares more items than
+# this limit raises an :class:`avro.errors.AvroCollectionSizeException` instead
+# of attempting a potentially huge allocation. This mirrors the Java SDK's
+# ``org.apache.avro.limits.collectionItems.maxLength`` limit. The default may 
be
+# overridden with the ``AVRO_MAX_COLLECTION_ITEMS`` environment variable.
+DEFAULT_MAX_COLLECTION_ITEMS = (1 << 31) - 8
+
+
+def _default_max_collection_items() -> int:
+    """Return the default collection item limit, honoring the environment 
override."""
+    value = os.environ.get(MAX_COLLECTION_ITEMS_ENV)
+    if value is None:
+        return DEFAULT_MAX_COLLECTION_ITEMS
+    try:
+        parsed = int(value)
+    except ValueError:
+        warnings.warn(f"Ignoring invalid {MAX_COLLECTION_ITEMS_ENV} value: 
{value!r}", avro.errors.AvroWarning)
+        return DEFAULT_MAX_COLLECTION_ITEMS
+    if parsed < 0:
+        warnings.warn(f"Ignoring negative {MAX_COLLECTION_ITEMS_ENV} value: 
{value!r}", avro.errors.AvroWarning)

Review Comment:
   `warnings.warn` is called with a string message + category class here, but 
the codebase consistently passes an `AvroWarning` instance (e.g., 
`warnings.warn(avro.errors.AvroWarning(...))`). This keeps warning construction 
consistent and avoids mixing the two call styles.



##########
lang/py/avro/io.py:
##########
@@ -795,12 +847,13 @@ def read_array(self, writers_schema: 
avro.schema.ArraySchema, readers_schema: av
         The actual count in this case
         is the absolute value of the count written.
         """
-        read_items = []
+        read_items: List[object] = []
         block_count = decoder.read_long()
         while block_count != 0:
             if block_count < 0:
                 block_count = -block_count
                 decoder.skip_long()
+            check_max_collection_items(len(read_items), block_count, 
self.max_collection_items)

Review Comment:
   `read_array` now enforces `max_collection_items`, but `skip_array` can still 
be driven by an untrusted `block_count` into an unbounded per-item loop (CPU 
DoS), especially for schemas where items are cheap to skip. Applying the same 
cumulative limit check in `skip_array` prevents resource exhaustion when the 
reader is skipping writer-only fields.



-- 
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