This is an automated email from the ASF dual-hosted git repository. iemejia pushed a commit to branch fuzzer in repository https://gitbox.apache.org/repos/asf/avro.git
commit a850f5d58f4fa4118abc7fcfdeb016297753ddd5 Author: Ismaël Mejía <[email protected]> AuthorDate: Tue Mar 31 23:01:36 2026 +0000 Simplify fuzz targets: merge redundant tests, consolidate exception handling, lean POM Reduce 9 fuzz targets to 5 by merging schemas into compound targets that exercise the same code paths in fewer tests. Replace per-target exception type lists with broad catch(Exception) since any non-Error is expected for invalid input. Remove unused maven-jar-plugin, deduplicate jazzer.instrument config, and disable build cache for the non-deterministic fuzz module. --- lang/java/fuzz/pom.xml | 19 +------- .../org/apache/avro/fuzz/BinaryDecodingFuzzer.java | 51 ++++++---------------- .../org/apache/avro/fuzz/DataFileReaderFuzzer.java | 30 +++++-------- .../org/apache/avro/fuzz/JsonDecodingFuzzer.java | 41 ++++++----------- .../java/org/apache/avro/fuzz/SchemaFuzzer.java | 26 +++-------- 5 files changed, 46 insertions(+), 121 deletions(-) diff --git a/lang/java/fuzz/pom.xml b/lang/java/fuzz/pom.xml index 970b05c523..ec06ca2add 100644 --- a/lang/java/fuzz/pom.xml +++ b/lang/java/fuzz/pom.xml @@ -36,6 +36,8 @@ <properties> <jazzer.version>0.22.1</jazzer.version> <main.basedir>${project.parent.parent.basedir}</main.basedir> + <!-- Fuzz tests are non-deterministic; never serve results from cache --> + <maven.build.cache.enabled>false</maven.build.cache.enabled> </properties> <build> @@ -54,22 +56,6 @@ <forkCount>1</forkCount> <reuseForks>true</reuseForks> <redirectTestOutputToFile>false</redirectTestOutputToFile> - <properties> - <configurationParameters> - jazzer.instrument = org.apache.avro.** - </configurationParameters> - </properties> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.apache.avro.fuzz</Automatic-Module-Name> - </manifestEntries> - </archive> </configuration> </plugin> </plugins> @@ -92,7 +78,6 @@ <configurationParameters> jazzer.internal.command_line = true jazzer.duration = 60s - jazzer.instrument = org.apache.avro.** </configurationParameters> </properties> </configuration> diff --git a/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/BinaryDecodingFuzzer.java b/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/BinaryDecodingFuzzer.java index 4912d89b31..e253c3422e 100644 --- a/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/BinaryDecodingFuzzer.java +++ b/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/BinaryDecodingFuzzer.java @@ -18,7 +18,6 @@ package org.apache.avro.fuzz; import com.code_intelligence.jazzer.junit.FuzzTest; -import org.apache.avro.AvroRuntimeException; import org.apache.avro.Schema; import org.apache.avro.generic.GenericDatumReader; import org.apache.avro.generic.GenericRecord; @@ -32,58 +31,36 @@ import java.io.IOException; * * <p> * Feeds arbitrary bytes through {@link BinaryDecoder} and - * {@link GenericDatumReader} using several representative schemas to exercise - * varint parsing, buffer management, nested record handling, and collection - * size limit enforcement. + * {@link GenericDatumReader} using a compound schema that exercises varint + * parsing, buffer management, nested records, enums, arrays, maps, unions, and + * collection size limit enforcement in a single target. * </p> */ class BinaryDecodingFuzzer { - /** A record schema with multiple field types to exercise diverse code paths. */ - private static final Schema RECORD_SCHEMA = new Schema.Parser() - .parse("{\"type\":\"record\",\"name\":\"TestRecord\",\"fields\":[" + "{\"name\":\"id\",\"type\":\"long\"}," + /** + * A compound schema that nests records, enums, arrays, maps, and unions to + * maximise code coverage from a single fuzz target. + */ + private static final Schema SCHEMA = new Schema.Parser() + .parse("{\"type\":\"record\",\"name\":\"Root\",\"fields\":[" + "{\"name\":\"id\",\"type\":\"long\"}," + "{\"name\":\"name\",\"type\":\"string\"}," + "{\"name\":\"value\",\"type\":\"double\"}," + "{\"name\":\"tags\",\"type\":{\"type\":\"array\",\"items\":\"string\"}}," + "{\"name\":\"metadata\",\"type\":{\"type\":\"map\",\"values\":\"string\"}}," - + "{\"name\":\"optionalField\",\"type\":[\"null\",\"string\"]}" + "]}"); - - /** A schema with nested records to test recursive decoding. */ - private static final Schema NESTED_SCHEMA = new Schema.Parser() - .parse("{\"type\":\"record\",\"name\":\"Outer\",\"fields\":[" + + "{\"name\":\"optionalField\",\"type\":[\"null\",\"string\"]}," + "{\"name\":\"inner\",\"type\":{\"type\":\"record\",\"name\":\"Inner\",\"fields\":[" - + "{\"name\":\"x\",\"type\":\"int\"}," + "{\"name\":\"y\",\"type\":\"int\"}" + "]}}," - + "{\"name\":\"label\",\"type\":\"string\"}" + "]}"); - - /** An enum schema. */ - private static final Schema ENUM_SCHEMA = new Schema.Parser() - .parse("{\"type\":\"record\",\"name\":\"EnumRecord\",\"fields\":[" + + "{\"name\":\"x\",\"type\":\"int\"},{\"name\":\"y\",\"type\":\"int\"}]}}," + "{\"name\":\"status\",\"type\":{\"type\":\"enum\",\"name\":\"Status\"," + "\"symbols\":[\"ACTIVE\",\"INACTIVE\",\"PENDING\"]}}," + "{\"name\":\"count\",\"type\":\"int\"}" + "]}"); @FuzzTest - void fuzzBinaryDecodingRecord(byte[] data) { - decodeWithSchema(data, RECORD_SCHEMA); - } - - @FuzzTest - void fuzzBinaryDecodingNested(byte[] data) { - decodeWithSchema(data, NESTED_SCHEMA); - } - - @FuzzTest - void fuzzBinaryDecodingEnum(byte[] data) { - decodeWithSchema(data, ENUM_SCHEMA); - } - - private static void decodeWithSchema(byte[] data, Schema schema) { + void fuzzBinaryDecoding(byte[] data) { try { BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(data, null); - GenericDatumReader<GenericRecord> reader = new GenericDatumReader<>(schema); + GenericDatumReader<GenericRecord> reader = new GenericDatumReader<>(SCHEMA); reader.read(null, decoder); - } catch (IOException | AvroRuntimeException | ArrayIndexOutOfBoundsException | UnsupportedOperationException e) { + } catch (Exception e) { // Expected for malformed binary data - // UnsupportedOperationException: thrown by SystemLimitException for oversized - // strings/collections } } } diff --git a/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/DataFileReaderFuzzer.java b/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/DataFileReaderFuzzer.java index fd12ec7109..5e274f36a6 100644 --- a/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/DataFileReaderFuzzer.java +++ b/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/DataFileReaderFuzzer.java @@ -18,7 +18,6 @@ package org.apache.avro.fuzz; import com.code_intelligence.jazzer.junit.FuzzTest; -import org.apache.avro.AvroRuntimeException; import org.apache.avro.file.DataFileReader; import org.apache.avro.file.DataFileStream; import org.apache.avro.file.SeekableByteArrayInput; @@ -26,7 +25,6 @@ import org.apache.avro.generic.GenericDatumReader; import org.apache.avro.generic.GenericRecord; import java.io.ByteArrayInputStream; -import java.io.IOException; /** * Fuzz tests for Avro data file (container format) reading. @@ -35,19 +33,15 @@ import java.io.IOException; * Feeds arbitrary bytes through {@link DataFileReader} and * {@link DataFileStream} to test the full Avro container format parsing: magic * bytes, file header, metadata (including the embedded schema), sync markers, - * codec decompression, and datum deserialization. This is the highest-value - * target for finding container format vulnerabilities. + * codec decompression, and datum deserialization. * </p> */ class DataFileReaderFuzzer { /** - * Fuzz the seekable {@link DataFileReader} path. - * - * <p> - * Uses {@link SeekableByteArrayInput} to wrap the fuzz input, which exercises - * the random-access container file reading code path including block seeking. - * </p> + * Fuzz the seekable {@link DataFileReader} path via + * {@link SeekableByteArrayInput}, exercising random-access container file + * reading including block seeking. */ @FuzzTest void fuzzDataFileReader(byte[] data) { @@ -56,19 +50,15 @@ class DataFileReaderFuzzer { while (reader.hasNext()) { reader.next(); } - } catch (IOException | AvroRuntimeException | UnsupportedOperationException e) { - // Expected for malformed container files -- invalid magic, bad headers, - // corrupt sync markers, unsupported codecs, etc. + } catch (Exception e) { + // Expected for malformed container files } } /** - * Fuzz the streaming {@link DataFileStream} path. - * - * <p> - * Uses a plain {@link ByteArrayInputStream}, which exercises the sequential - * (non-seekable) container file reading code path. - * </p> + * Fuzz the streaming {@link DataFileStream} path via + * {@link ByteArrayInputStream}, exercising sequential (non-seekable) container + * file reading. */ @FuzzTest void fuzzDataFileStream(byte[] data) { @@ -77,7 +67,7 @@ class DataFileReaderFuzzer { while (stream.hasNext()) { stream.next(); } - } catch (IOException | AvroRuntimeException | UnsupportedOperationException e) { + } catch (Exception e) { // Expected for malformed container files } } diff --git a/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/JsonDecodingFuzzer.java b/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/JsonDecodingFuzzer.java index bfb858b984..362773c838 100644 --- a/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/JsonDecodingFuzzer.java +++ b/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/JsonDecodingFuzzer.java @@ -18,61 +18,48 @@ package org.apache.avro.fuzz; import com.code_intelligence.jazzer.junit.FuzzTest; -import org.apache.avro.AvroRuntimeException; import org.apache.avro.Schema; import org.apache.avro.generic.GenericDatumReader; import org.apache.avro.generic.GenericRecord; import org.apache.avro.io.DecoderFactory; import org.apache.avro.io.JsonDecoder; -import java.io.IOException; import java.nio.charset.StandardCharsets; /** * Fuzz tests for Avro JSON decoding. * * <p> - * Feeds arbitrary JSON strings through {@link JsonDecoder} with various schemas - * to test the JSON-to-Avro deserialization path, including Jackson JSON + * Feeds arbitrary JSON strings through {@link JsonDecoder} with a compound + * schema to test the JSON-to-Avro deserialization path, including Jackson JSON * parsing, type coercion, union resolution, and error handling for malformed * data. * </p> */ class JsonDecodingFuzzer { - /** A record schema with diverse field types. */ - private static final Schema RECORD_SCHEMA = new Schema.Parser() + /** + * A compound schema with diverse field types: primitives, arrays, maps, and + * unions. This is a superset of the previous separate record and simple + * schemas. + */ + private static final Schema SCHEMA = new Schema.Parser() .parse("{\"type\":\"record\",\"name\":\"TestRecord\",\"fields\":[" + "{\"name\":\"id\",\"type\":\"long\"}," - + "{\"name\":\"name\",\"type\":\"string\"}," + "{\"name\":\"active\",\"type\":\"boolean\"}," + + "{\"name\":\"name\",\"type\":\"string\"}," + "{\"name\":\"x\",\"type\":\"int\"}," + + "{\"name\":\"active\",\"type\":\"boolean\"}," + "{\"name\":\"scores\",\"type\":{\"type\":\"array\",\"items\":\"double\"}}," + "{\"name\":\"props\",\"type\":{\"type\":\"map\",\"values\":\"string\"}}," + "{\"name\":\"extra\",\"type\":[\"null\",\"string\",\"long\"]}" + "]}"); - /** A simpler schema for basic field decoding. */ - private static final Schema SIMPLE_SCHEMA = new Schema.Parser() - .parse("{\"type\":\"record\",\"name\":\"Simple\",\"fields\":[" + "{\"name\":\"x\",\"type\":\"int\"}," - + "{\"name\":\"y\",\"type\":\"string\"}" + "]}"); - - @FuzzTest - void fuzzJsonDecodingRecord(byte[] data) { - decodeJsonWithSchema(data, RECORD_SCHEMA); - } - @FuzzTest - void fuzzJsonDecodingSimple(byte[] data) { - decodeJsonWithSchema(data, SIMPLE_SCHEMA); - } - - private static void decodeJsonWithSchema(byte[] data, Schema schema) { + void fuzzJsonDecoding(byte[] data) { String jsonData = new String(data, StandardCharsets.UTF_8); try { - JsonDecoder decoder = DecoderFactory.get().jsonDecoder(schema, jsonData); - GenericDatumReader<GenericRecord> reader = new GenericDatumReader<>(schema); + JsonDecoder decoder = DecoderFactory.get().jsonDecoder(SCHEMA, jsonData); + GenericDatumReader<GenericRecord> reader = new GenericDatumReader<>(SCHEMA); reader.read(null, decoder); - } catch (IOException | AvroRuntimeException | UnsupportedOperationException e) { + } catch (Exception e) { // Expected for malformed JSON or type mismatches - // UnsupportedOperationException: thrown by SystemLimitException for oversized - // strings/collections } } } diff --git a/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/SchemaFuzzer.java b/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/SchemaFuzzer.java index 20cd38ddc4..f7ef7669cf 100644 --- a/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/SchemaFuzzer.java +++ b/lang/java/fuzz/src/test/java/org/apache/avro/fuzz/SchemaFuzzer.java @@ -18,7 +18,6 @@ package org.apache.avro.fuzz; import com.code_intelligence.jazzer.junit.FuzzTest; -import org.apache.avro.AvroRuntimeException; import org.apache.avro.Schema; import java.nio.charset.StandardCharsets; @@ -27,34 +26,21 @@ import java.nio.charset.StandardCharsets; * Fuzz tests for Avro schema parsing. * * <p> - * Targets {@link Schema#parse(String)} with arbitrary input to find crashes, - * hangs, or unexpected exceptions in the JSON schema parser. Expected - * exceptions like {@link SchemaParseException} are caught and ignored since - * they represent correct rejection of invalid input. + * Targets {@link Schema#parse(String, boolean)} with arbitrary input and name + * validation enabled. Any exception is treated as expected behavior for invalid + * input; only JVM-level errors (e.g. {@link StackOverflowError}) escape and are + * reported as real findings. * </p> */ class SchemaFuzzer { @FuzzTest void fuzzSchemaParse(byte[] data) { - String schemaJson = new String(data, StandardCharsets.UTF_8); - try { - Schema.parse(schemaJson); - } catch (AvroRuntimeException | IllegalArgumentException | NullPointerException e) { - // Expected for invalid schema input -- these indicate correct rejection - // (SchemaParseException is a subclass of AvroRuntimeException) - // NullPointerException: known bug in ParseContext.resolve with unresolved - // schemas - } - } - - @FuzzTest - void fuzzSchemaParseWithValidation(byte[] data) { String schemaJson = new String(data, StandardCharsets.UTF_8); try { Schema.parse(schemaJson, true); - } catch (AvroRuntimeException | IllegalArgumentException | NullPointerException e) { - // Expected for invalid schema input -- these indicate correct rejection + } catch (Exception e) { + // Expected for invalid schema input } } }
