From 00f03d5aa99034e44dc736b65df5ce946e45f89c Mon Sep 17 00:00:00 2001 From: Simon Resch Date: Wed, 29 Oct 2025 10:59:53 +0100 Subject: [PATCH] fix: assertion failure when fuzzing proto strings When parsing protobuf messages from libfuzzer bytes in the protobuf mutator we don't enforce length limits. This could lead to maxSizeIncrease < 0 situations if the e.g. a String read by the protobuf mutator exceeds the size limit. To guard against similar cases we now enforce the size constraints for the byte array that is being mutated before performing the mutation. --- .../mutation/ArgumentsMutatorFuzzTest.java | 17 ++++++----------- .../selffuzz/mutation/BUILD.bazel | 2 +- .../libfuzzer/LibFuzzerMutatorFactory.java | 3 +++ .../mutator/lang/StringMutatorTest.java | 14 ++++++++++++++ 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java index 41164949e..f5d2a6372 100644 --- a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java @@ -206,15 +206,13 @@ void fuzz_Enums(MyEnum e0, MyEnum e1, MyEnum e2) {} @SelfFuzzTest void fuzz_ProtoBufs( - // com.google.protobuf.StringValue v0, // BUG: makes maxIncreaseSize negative in - // LibProtobufMutator.mutate + com.google.protobuf.StringValue v0, com.google.protobuf.Int32Value v1, com.google.protobuf.BoolValue v2, com.google.protobuf.UInt64Value v3, com.google.protobuf.FloatValue v4, com.google.protobuf.DoubleValue v5, - // com.google.protobuf.BytesValue v6, // BUG: makes maxIncreaseSize negative in - // LibProtobufMutator.mutate + com.google.protobuf.BytesValue v6, com.google.protobuf.Int64Value v7) { if (v7 != null) { assertThat(v7.getValue()).isAtLeast(Long.MIN_VALUE); @@ -224,15 +222,13 @@ void fuzz_ProtoBufs( @SelfFuzzTest void fuzz_ProtoBufsNotNull( - // @NotNull com.google.protobuf.StringValue v0, // BUG: makes maxIncreaseSize negative in - // LibProtobufMutator.mutate + @NotNull com.google.protobuf.StringValue v0, @NotNull com.google.protobuf.Int32Value v1, @NotNull com.google.protobuf.BoolValue v2, @NotNull com.google.protobuf.UInt64Value v3, @NotNull com.google.protobuf.FloatValue v4, @NotNull com.google.protobuf.DoubleValue v5, - // @NotNull com.google.protobuf.BytesValue v6, // BUG: makes maxIncreaseSize negative in - // LibProtobufMutator.mutate + @NotNull com.google.protobuf.BytesValue v6, @NotNull com.google.protobuf.Int64Value v7) { if (v7 != null) { assertThat(v7.getValue()).isAtLeast(Long.MIN_VALUE); @@ -240,9 +236,8 @@ void fuzz_ProtoBufsNotNull( } } - // BUG: makes maxIncreaseSize negative in LibProtobufMutator.mutate - // @SelfFuzzTest - // public static void fuzz_TestProtobuf(TestProtobuf o1) {} + @SelfFuzzTest + public static void fuzz_TestProtobuf(Proto2.TestProtobuf o1) {} @SelfFuzzTest void fuzz_MapField3(Proto3.MapField3 o1) {} diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel index 208dbca40..298a4026e 100644 --- a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/BUILD.bazel @@ -10,7 +10,7 @@ java_fuzz_target_test( ], data = ["//selffuzz/src/test/resources:ArgumentsMutatorFuzzTest-corpus"], env = { - "_JAVA_OPTIONS": "-Xmx1024m", + "_JAVA_OPTIONS": "-Xmx2048m", }, fuzzer_args = [ # Make sure that the fuzzer can run. Longer fuzzing runs will be done in a separate GH action. diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java index d2cc897e0..9b9e323f9 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java @@ -113,6 +113,9 @@ private int maxInitialSize() { @Override public byte[] mutate(byte[] value, PseudoRandom prng) { + // Enforce length constraints on the input to mutate. We do this because some mutators (e.g. + // protobuf mutator) don't enforce length constraints in the read methods. + value = enforceLength(value); int maxLengthIncrease = maxLength - value.length; byte[] mutated = LibFuzzerMutate.mutateDefault(value, maxLengthIncrease); return enforceLength(mutated); diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java index b4e4fba8c..ef4f2bd1b 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorTest.java @@ -226,6 +226,20 @@ void testReadInputsLongerThanMaxLength() throws IOException { assertThat(s).isEqualTo("fooba"); } + @Test + void testMutateWithInputLongerThanMaxLength() { + SerializingMutator mutator = + (SerializingMutator) + factory.createOrThrow( + new TypeHolder<@NotNull @WithUtf8Length(max = 5) String>() {}.annotatedType()); + assertThat(mutator.toString()).isEqualTo("String"); + String s = "foobarbazf"; + try (MockPseudoRandom prng = mockPseudoRandom()) { + s = mutator.mutate(s, prng); + } + assertThat(s.length()).isAtMost(5); + } + @Test void testMaxLengthMutate() { SerializingMutator mutator =