Copilot commented on code in PR #3864:
URL: https://github.com/apache/avro/pull/3864#discussion_r3567572989
##########
lang/perl/t/03_bin_decode.t:
##########
@@ -255,4 +317,357 @@ EOP
is $dec->{one}[0], 1.0, "kind of dumb test";
}
+## A bytes/string value declares a length prefix; a malicious or truncated
+## input can declare far more bytes than actually exist. On a seekable reader
+## that is rejected before allocating for it.
+{
+ my $bytes_schema = Avro::Schema->parse(q({ "type": "bytes" }));
+
+ ## A length prefix declaring 100 MiB, with no payload following it.
+ my $enc = '';
+ Avro::BinaryEncoder->encode(
+ schema => Avro::Schema->parse(q({ "type": "long" })),
+ data => 100 * 1024 * 1024,
+ emit_cb => sub { $enc .= ${ $_[0] } },
+ );
+ open my $reader, '<', \$enc or die "Can't open memory file: $!";
+ throws_ok {
+ Avro::BinaryDecoder->decode(
+ writer_schema => $bytes_schema,
+ reader_schema => $bytes_schema,
+ reader => $reader,
+ );
+ } qr/Cannot read/, "oversized bytes length is rejected before allocating";
+
+ ## A well-formed value above the check threshold whose data is present
+ ## still decodes.
+ my $payload = 'x' x (2 * 1024 * 1024);
+ my $enc2 = '';
+ Avro::BinaryEncoder->encode(
+ schema => $bytes_schema,
+ data => $payload,
+ emit_cb => sub { $enc2 .= ${ $_[0] } },
+ );
+ open my $reader2, '<', \$enc2 or die "Can't open memory file: $!";
+ my $dec = Avro::BinaryDecoder->decode(
+ writer_schema => $bytes_schema,
+ reader_schema => $bytes_schema,
+ reader => $reader2,
+ );
+ is $dec, $payload, "within-limit bytes value still decodes";
+}
+
+## An array/map block declares an element count; a malicious or truncated input
+## can declare far more elements than the remaining bytes could hold. The count
+## is validated against the bytes remaining before iterating, using the minimum
+## on-wire size of the element schema (so 0-byte elements like null are not
+## falsely rejected).
+sub encode_long {
+ my ($value) = @_;
+ my $enc = '';
+ Avro::BinaryEncoder->encode(
+ schema => Avro::Schema->parse(q({ "type": "long" })),
+ data => $value,
+ emit_cb => sub { $enc .= ${ $_[0] } },
+ );
+ return $enc;
+}
+
+{
+ my $array_schema = Avro::Schema->parse(q({ "type": "array", "items":
"long" }));
+ my $enc = encode_long(1_000_000); # 1,000,000 longs, no element data
+ open my $reader, '<', \$enc or die "Can't open memory file: $!";
+ throws_ok {
+ Avro::BinaryDecoder->decode(
+ writer_schema => $array_schema,
+ reader_schema => $array_schema,
+ reader => $reader,
+ );
+ } qr/Collection claims/, "oversized array count is rejected before
iterating";
+}
+
+{
+ my $map_schema = Avro::Schema->parse(q({ "type": "map", "values": "long"
}));
+ my $enc = encode_long(1_000_000);
+ open my $reader, '<', \$enc or die "Can't open memory file: $!";
+ throws_ok {
+ Avro::BinaryDecoder->decode(
+ writer_schema => $map_schema,
+ reader_schema => $map_schema,
+ reader => $reader,
+ );
+ } qr/Collection claims/, "oversized map count is rejected before
iterating";
+}
+
+{
+ ## An array of nulls: null elements occupy zero bytes, so a large count is
+ ## legitimate and must not be rejected.
+ my $array_schema = Avro::Schema->parse(q({ "type": "array", "items":
"null" }));
+ my $count = 100_000;
+ my $enc = encode_long($count) . encode_long(0); # one block + end marker
+ open my $reader, '<', \$enc or die "Can't open memory file: $!";
+ my $dec = Avro::BinaryDecoder->decode(
+ writer_schema => $array_schema,
+ reader_schema => $array_schema,
+ reader => $reader,
+ );
+ is scalar(@$dec), $count, "array of nulls is not falsely rejected";
+}
+
+## Zero-byte elements (null, zero-length fixed, all-null records) consume no
+## input, so the bytes-remaining check cannot bound them. A huge declared block
+## count of such elements is capped instead.
+sub decode_zero_byte_array {
+ my ($items_schema, $enc) = @_;
+ my $schema = Avro::Schema->parse(qq({ "type": "array", "items":
$items_schema }));
+ open my $reader, '<', \$enc or die "Can't open memory file: $!";
+ return Avro::BinaryDecoder->decode(
+ writer_schema => $schema,
+ reader_schema => $schema,
+ reader => $reader,
+ );
+}
+
+{
+ ## The reported exploit: a ~6 byte payload declaring 200,000,000 nulls is
+ ## rejected by the default limit, before allocating.
+ throws_ok {
+ decode_zero_byte_array('"null"', encode_long(200_000_000) .
encode_long(0));
+ } qr/more than \d+ zero-byte/, "array of 200M nulls rejected by default
limit";
+}
Review Comment:
This test assumes the decoder is using the default collection limits. If
AVRO_MAX_COLLECTION_ITEMS is set in the environment when the test suite runs,
BinaryDecoder.pm will initialize both MAX_COLLECTION_ITEMS and
MAX_COLLECTION_STRUCTURAL from it at load time, and this case may stop failing
(e.g., if the env var is larger than 200,000,000). To keep the test
deterministic, explicitly localize the limits to the documented defaults for
this block.
--
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]