iemejia commented on code in PR #3863:
URL: https://github.com/apache/avro/pull/3863#discussion_r3566776929


##########
lang/php/test/DatumIOTest.php:
##########
@@ -261,6 +262,225 @@ public function 
test_invalid_bytes_logical_type_out_of_range(): void
         $writer->write("10000", $encoder);
     }
 
+    // A bytes/string value declares a length prefix; a malicious or truncated
+    // input can declare far more bytes than actually exist. On a reader that
+    // can report its size, that is rejected before allocating for it.
+    public function test_read_bytes_rejects_length_beyond_stream(): void
+    {
+        $io = new AvroStringIO();
+        (new AvroIOBinaryEncoder($io))->writeLong(100 * 1024 * 1024);
+        $io->seek(0);
+        $decoder = new AvroIOBinaryDecoder($io);
+
+        $this->expectException(AvroException::class);
+        $decoder->readBytes();
+    }
+
+    public function test_read_bytes_within_stream_still_reads(): void
+    {
+        $payload = str_repeat('x', 2 * 1024 * 1024);
+        $io = new AvroStringIO();
+        (new AvroIOBinaryEncoder($io))->writeBytes($payload);
+        $io->seek(0);
+        $decoder = new AvroIOBinaryDecoder($io);
+
+        $this->assertEquals($payload, $decoder->readBytes());
+    }
+
+    public function test_read_array_rejects_count_beyond_stream(): void
+    {
+        $io = new AvroStringIO();
+        (new AvroIOBinaryEncoder($io))->writeLong(1000000); // 1,000,000 
longs, no data
+        $this->expectException(AvroException::class);
+        $this->decodeWith('{"type":"array","items":"long"}', $io);
+    }
+
+    public function test_read_map_rejects_count_beyond_stream(): void
+    {
+        $io = new AvroStringIO();
+        (new AvroIOBinaryEncoder($io))->writeLong(1000000);
+        $this->expectException(AvroException::class);
+        $this->decodeWith('{"type":"map","values":"long"}', $io);
+    }
+
+    public function test_read_array_of_null_not_falsely_rejected(): void
+    {
+        $count = 100000;
+        $io = new AvroStringIO();
+        $encoder = new AvroIOBinaryEncoder($io);
+        $encoder->writeLong($count); // one block of `count` nulls (zero bytes 
each)
+        $encoder->writeLong(0);      // end-of-array marker
+        $result = $this->decodeWith('{"type":"array","items":"null"}', $io);
+        $this->assertCount($count, $result);
+    }
+
+    public function test_read_array_within_stream_still_reads(): void
+    {
+        $schema = AvroSchema::parse('{"type":"array","items":"long"}');
+        $io = new AvroStringIO();
+        (new AvroIODatumWriter($schema))->write([1, 2, 3], new 
AvroIOBinaryEncoder($io));
+        $this->assertEquals([1, 2, 3], 
$this->decodeWith('{"type":"array","items":"long"}', $io));
+    }
+
+    public function test_array_of_null_exceeds_default_limit(): void
+    {
+        // The reported exploit: a ~6 byte payload declaring 200,000,000 nulls.
+        $this->expectException(AvroIOCollectionSizeException::class);
+        $this->decodeWith('{"type":"array","items":"null"}', 
self::zeroByteBlock(200000000));
+    }
+
+    public function test_array_of_null_within_configured_limit_still_reads(): 
void
+    {
+        putenv('AVRO_MAX_COLLECTION_ITEMS=1000');
+
+        try {
+            $result = $this->decodeWith('{"type":"array","items":"null"}', 
self::zeroByteBlock(1000));
+            $this->assertCount(1000, $result);
+        } finally {
+            putenv('AVRO_MAX_COLLECTION_ITEMS');
+        }
+    }
+
+    public function test_array_of_null_exceeds_configured_limit(): void
+    {
+        putenv('AVRO_MAX_COLLECTION_ITEMS=1000');
+
+        try {
+            $this->expectException(AvroException::class);
+            $this->decodeWith('{"type":"array","items":"null"}', 
self::zeroByteBlock(1001));
+        } finally {
+            putenv('AVRO_MAX_COLLECTION_ITEMS');
+        }
+    }
+
+    public function test_array_of_null_cumulative_across_blocks(): void
+    {
+        $io = new AvroStringIO();
+        $encoder = new AvroIOBinaryEncoder($io);
+        $encoder->writeLong(600);
+        $encoder->writeLong(600);
+        $encoder->writeLong(0);
+        putenv('AVRO_MAX_COLLECTION_ITEMS=1000');
+
+        try {
+            $this->expectException(AvroException::class);

Review Comment:
   Fixed in 23f15e04f4: now asserts AvroIOCollectionSizeException.



##########
lang/php/test/DatumIOTest.php:
##########
@@ -261,6 +262,225 @@ public function 
test_invalid_bytes_logical_type_out_of_range(): void
         $writer->write("10000", $encoder);
     }
 
+    // A bytes/string value declares a length prefix; a malicious or truncated
+    // input can declare far more bytes than actually exist. On a reader that
+    // can report its size, that is rejected before allocating for it.
+    public function test_read_bytes_rejects_length_beyond_stream(): void
+    {
+        $io = new AvroStringIO();
+        (new AvroIOBinaryEncoder($io))->writeLong(100 * 1024 * 1024);
+        $io->seek(0);
+        $decoder = new AvroIOBinaryDecoder($io);
+
+        $this->expectException(AvroException::class);
+        $decoder->readBytes();
+    }
+
+    public function test_read_bytes_within_stream_still_reads(): void
+    {
+        $payload = str_repeat('x', 2 * 1024 * 1024);
+        $io = new AvroStringIO();
+        (new AvroIOBinaryEncoder($io))->writeBytes($payload);
+        $io->seek(0);
+        $decoder = new AvroIOBinaryDecoder($io);
+
+        $this->assertEquals($payload, $decoder->readBytes());
+    }
+
+    public function test_read_array_rejects_count_beyond_stream(): void
+    {
+        $io = new AvroStringIO();
+        (new AvroIOBinaryEncoder($io))->writeLong(1000000); // 1,000,000 
longs, no data
+        $this->expectException(AvroException::class);
+        $this->decodeWith('{"type":"array","items":"long"}', $io);
+    }
+
+    public function test_read_map_rejects_count_beyond_stream(): void
+    {
+        $io = new AvroStringIO();
+        (new AvroIOBinaryEncoder($io))->writeLong(1000000);
+        $this->expectException(AvroException::class);
+        $this->decodeWith('{"type":"map","values":"long"}', $io);
+    }
+
+    public function test_read_array_of_null_not_falsely_rejected(): void
+    {
+        $count = 100000;
+        $io = new AvroStringIO();
+        $encoder = new AvroIOBinaryEncoder($io);
+        $encoder->writeLong($count); // one block of `count` nulls (zero bytes 
each)
+        $encoder->writeLong(0);      // end-of-array marker
+        $result = $this->decodeWith('{"type":"array","items":"null"}', $io);
+        $this->assertCount($count, $result);
+    }
+
+    public function test_read_array_within_stream_still_reads(): void
+    {
+        $schema = AvroSchema::parse('{"type":"array","items":"long"}');
+        $io = new AvroStringIO();
+        (new AvroIODatumWriter($schema))->write([1, 2, 3], new 
AvroIOBinaryEncoder($io));
+        $this->assertEquals([1, 2, 3], 
$this->decodeWith('{"type":"array","items":"long"}', $io));
+    }
+
+    public function test_array_of_null_exceeds_default_limit(): void
+    {
+        // The reported exploit: a ~6 byte payload declaring 200,000,000 nulls.
+        $this->expectException(AvroIOCollectionSizeException::class);
+        $this->decodeWith('{"type":"array","items":"null"}', 
self::zeroByteBlock(200000000));
+    }
+
+    public function test_array_of_null_within_configured_limit_still_reads(): 
void
+    {
+        putenv('AVRO_MAX_COLLECTION_ITEMS=1000');
+
+        try {
+            $result = $this->decodeWith('{"type":"array","items":"null"}', 
self::zeroByteBlock(1000));
+            $this->assertCount(1000, $result);
+        } finally {
+            putenv('AVRO_MAX_COLLECTION_ITEMS');
+        }
+    }
+
+    public function test_array_of_null_exceeds_configured_limit(): void
+    {
+        putenv('AVRO_MAX_COLLECTION_ITEMS=1000');
+
+        try {
+            $this->expectException(AvroException::class);
+            $this->decodeWith('{"type":"array","items":"null"}', 
self::zeroByteBlock(1001));
+        } finally {
+            putenv('AVRO_MAX_COLLECTION_ITEMS');
+        }
+    }
+
+    public function test_array_of_null_cumulative_across_blocks(): void
+    {
+        $io = new AvroStringIO();
+        $encoder = new AvroIOBinaryEncoder($io);
+        $encoder->writeLong(600);
+        $encoder->writeLong(600);
+        $encoder->writeLong(0);
+        putenv('AVRO_MAX_COLLECTION_ITEMS=1000');
+
+        try {
+            $this->expectException(AvroException::class);
+            $this->decodeWith('{"type":"array","items":"null"}', $io);
+        } finally {
+            putenv('AVRO_MAX_COLLECTION_ITEMS');
+        }
+    }
+
+    public function test_array_of_null_negative_block_count(): void
+    {
+        putenv('AVRO_MAX_COLLECTION_ITEMS=1000');
+
+        try {
+            $this->expectException(AvroException::class);

Review Comment:
   Fixed in 23f15e04f4: now asserts AvroIOCollectionSizeException.



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