From e38e835c59c54348616d3c779914b3628e334d4d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 24 Apr 2023 13:50:16 +0200 Subject: [PATCH 1/3] deploy: Fix MergeJars label after rules_jvm_external update --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7240d5538..fb25268d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,7 +74,7 @@ jobs: - name: Merge jars run: | - bazel run @rules_jvm_external//private/tools/java/rules/jvm/external/jar:MergeJars -- \ + bazel run @rules_jvm_external//private/tools/java/com/github/bazelbuild/rules_jvm_external/jar:MergeJars -- \ --output "$(pwd)"/_tmp/jazzer.jar \ $(find "$(pwd)/_tmp/" -name '*.jar' -printf "--sources %h/%f ") From 559bfac068691f3cc3fb81ce26628dc5402e1fdc Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 25 Apr 2023 11:17:14 +0200 Subject: [PATCH 2/3] mutation: Make `BuilderAdapters#getMapField` fully dynamic The previous implementation would fail on map fields in dynamically instantiated messages. --- .../jazzer/mutation/mutator/proto/BuilderAdapters.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdapters.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdapters.java index bfcc0acaa..12dcd4066 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdapters.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderAdapters.java @@ -19,7 +19,7 @@ import static java.util.Collections.singletonList; import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.MapEntry; +import com.google.protobuf.Message; import com.google.protobuf.Message.Builder; import java.util.AbstractList; import java.util.ArrayList; @@ -163,10 +163,12 @@ static void setFieldWithPresence( static Map getMapField(T builder, FieldDescriptor field) { int size = builder.getRepeatedFieldCount(field); + FieldDescriptor keyField = field.getMessageType().getFields().get(0); + FieldDescriptor valueField = field.getMessageType().getFields().get(1); HashMap map = new HashMap<>(size); for (int i = 0; i < size; i++) { - MapEntry entry = (MapEntry) builder.getRepeatedField(field, i); - map.put(entry.getKey(), entry.getValue()); + Message entry = (Message) builder.getRepeatedField(field, i); + map.put((K) entry.getField(keyField), (V) entry.getField(valueField)); } return map; } From 88ba6dd97f9f4ca0272620b736ecdbef58b123d2 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 25 Apr 2023 11:18:17 +0200 Subject: [PATCH 3/3] mutation: Add support for `DynamicMessage` A new `DescriptorSource` annotation is used to specify a field with the `DynamicMessage`'s `Descriptor`. Since `DynamicMessage` instances are only compatible if their descriptors are reference equal, the mutator interner now checks for this instead of relying on `equals`. --- .../annotation/proto/DescriptorSource.java | 40 ++++++++++++ .../mutator/proto/BuilderMutatorFactory.java | 21 ++++-- .../mutation/mutator/proto/TypeLibrary.java | 61 ++++++++++++++++++ .../jazzer/mutation/support/TypeSupport.java | 6 +- .../jazzer/mutation/mutator/StressTest.java | 13 ++++ tests/BUILD.bazel | 24 +++++++ ...ExperimentalMutatorDynamicProtoFuzzer.java | 64 +++++++++++++++++++ 7 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto/DescriptorSource.java create mode 100644 tests/src/test/java/com/example/ExperimentalMutatorDynamicProtoFuzzer.java diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto/DescriptorSource.java b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto/DescriptorSource.java new file mode 100644 index 000000000..6cf7286c5 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto/DescriptorSource.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.mutation.annotation.proto; + +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.code_intelligence.jazzer.mutation.annotation.AppliesTo; +import com.google.protobuf.DynamicMessage; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Provides a {@link com.google.protobuf.Descriptors.Descriptor} to use as the base for mutations + * of the annotated {@link DynamicMessage} or {@link DynamicMessage.Builder}. + */ +@Target(TYPE_USE) +@Retention(RUNTIME) +@AppliesTo({DynamicMessage.class, DynamicMessage.Builder.class}) +public @interface DescriptorSource { + /** + * The fully qualified name of a static final field (e.g. {@code com.example.MyClass#MY_FIELD} of + * type * {@link com.google.protobuf.Descriptors.Descriptor} that mutations should be based on. + */ + String value(); +} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorFactory.java index 09967ab08..a012f3f4f 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/BuilderMutatorFactory.java @@ -44,6 +44,7 @@ import static java.util.stream.Collectors.toMap; import com.code_intelligence.jazzer.mutation.annotation.proto.AnySource; +import com.code_intelligence.jazzer.mutation.annotation.proto.DescriptorSource; import com.code_intelligence.jazzer.mutation.api.ChainedMutatorFactory; import com.code_intelligence.jazzer.mutation.api.InPlaceMutator; import com.code_intelligence.jazzer.mutation.api.MutatorFactory; @@ -325,8 +326,19 @@ public Optional> tryCreate(AnnotatedType type, MutatorFact return Optional.empty(); } - Message defaultInstance = - getDefaultInstance((Class) builderClass.getEnclosingClass()); + Message defaultInstance; + if (builderClass == DynamicMessage.Builder.class) { + DescriptorSource descriptorSource = type.getAnnotation(DescriptorSource.class); + if (descriptorSource == null) { + throw new IllegalArgumentException( + "To mutate a dynamic message, add a @DescriptorSource annotation specifying the fully qualified method name of a static method returning a Descriptor"); + } + defaultInstance = getDefaultInstance(descriptorSource); + } else { + defaultInstance = + getDefaultInstance((Class) builderClass.getEnclosingClass()); + } + return Optional.of( makeBuilderMutator(factory, defaultInstance, type.getDeclaredAnnotations())); }); @@ -400,13 +412,12 @@ public boolean equals(Object o) { return false; } CacheKey cacheKey = (CacheKey) o; - return Objects.equals(descriptor, cacheKey.descriptor) - && Objects.equals(anySource, cacheKey.anySource); + return descriptor == cacheKey.descriptor && Objects.equals(anySource, cacheKey.anySource); } @Override public int hashCode() { - return Objects.hash(descriptor, anySource); + return 31 * System.identityHashCode(descriptor) + Objects.hashCode(anySource); } } } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/TypeLibrary.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/TypeLibrary.java index d96993fc6..340466a95 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/TypeLibrary.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/proto/TypeLibrary.java @@ -27,14 +27,18 @@ import static java.util.stream.Collectors.toMap; import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.proto.DescriptorSource; import com.code_intelligence.jazzer.mutation.support.TypeHolder; import com.google.protobuf.ByteString; +import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.Message; import com.google.protobuf.Message.Builder; import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -99,4 +103,61 @@ static Message getDefaultInstance(Class messageClass) { format(getDefaultInstance + " isn't accessible or threw an exception"), e); } } + + static DynamicMessage getDefaultInstance(DescriptorSource descriptorSource) { + String[] parts = descriptorSource.value().split("#"); + if (parts.length != 2) { + throw new IllegalArgumentException(format( + "Expected @DescriptorSource(\"%s\") to specify a fully-qualified field name (e.g. com.example.MyClass#MY_FIELD)", + descriptorSource.value())); + } + + Class clazz; + try { + clazz = Class.forName(parts[0]); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException( + format("Failed to find class '%s' specified by @DescriptorSource(\"%s\")", parts[0], + descriptorSource.value()), + e); + } + + Field field; + try { + field = clazz.getDeclaredField(parts[1]); + field.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException( + format("Failed to find field specified by @DescriptorSource(\"%s\")", + descriptorSource.value()), + e); + } + if (!Modifier.isStatic(field.getModifiers())) { + throw new IllegalArgumentException( + format("Expected field specified by @DescriptorSource(\"%s\") to be static", + descriptorSource.value())); + } + if (!Modifier.isFinal(field.getModifiers())) { + throw new IllegalArgumentException( + format("Expected field specified by @DescriptorSource(\"%s\") to be final", + descriptorSource.value())); + } + if (field.getType() != Descriptor.class) { + throw new IllegalArgumentException( + format("Expected field specified by @DescriptorSource(\"%s\") to have type %s, got %s", + descriptorSource.value(), Descriptor.class.getName(), field.getType().getName())); + } + + Descriptor descriptor; + try { + descriptor = (Descriptor) field.get(null); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException( + format("Failed to access field specified by @DescriptorSource(\"%s\")", + descriptorSource.value()), + e); + } + + return DynamicMessage.getDefaultInstance(descriptor); + } } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/support/TypeSupport.java b/src/main/java/com/code_intelligence/jazzer/mutation/support/TypeSupport.java index 07f1f0f4d..9d3671443 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/support/TypeSupport.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/support/TypeSupport.java @@ -64,12 +64,14 @@ public static boolean isInheritable(Annotation annotation) { } /** - * Returns {@code type} as a {@code Class} if it is a subclass of T, otherwise empty. + * Returns {@code type} as a {@code Class} if it is a subclass of T, otherwise + * empty. * *

This function also returns an empty {@link Optional} for more complex (e.g. parameterized) * types. */ - public static Optional> asSubclassOrEmpty(AnnotatedType type, Class superclass) { + public static Optional> asSubclassOrEmpty( + AnnotatedType type, Class superclass) { if (!(type.getType() instanceof Class) ) { return Optional.empty(); } diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java index 25ca16e0e..ccee97c04 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java @@ -38,6 +38,7 @@ import com.code_intelligence.jazzer.mutation.annotation.NotNull; import com.code_intelligence.jazzer.mutation.annotation.WithSize; import com.code_intelligence.jazzer.mutation.annotation.proto.AnySource; +import com.code_intelligence.jazzer.mutation.annotation.proto.DescriptorSource; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.Serializer; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; @@ -63,8 +64,10 @@ import com.code_intelligence.jazzer.protobuf.Proto3.RepeatedRecursiveMessageField3; import com.code_intelligence.jazzer.protobuf.Proto3.StringField3; import com.google.protobuf.Any; +import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.Message; import com.google.protobuf.Message.Builder; import java.io.ByteArrayInputStream; @@ -90,6 +93,9 @@ public class StressTest { private static final int NUM_MUTATE_PER_INIT = 100; private static final double MANY_DISTINCT_ELEMENTS_RATIO = 0.5; + @SuppressWarnings("unused") + static final Descriptor TEST_PROTOBUF_DESCRIPTOR = TestProtobuf.getDescriptor(); + private enum TestEnumTwo { A, B } private enum TestEnumThree { A, B, C } @@ -302,6 +308,13 @@ public static Stream protoStressTestCases() { arguments(new TypeHolder<@NotNull TestProtobuf>() {}.annotatedType(), "{Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable>, Builder.Nullable<{Builder.Nullable, Builder via List} -> Message>, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List>, Builder via List<(cycle) -> Message>, Builder.Map, Builder.Nullable, Builder.Nullable<{} -> Message>, Builder.Nullable | Builder.Nullable | Builder.Nullable} -> Message", manyDistinctElements(), manyDistinctElements()), + arguments( + new TypeHolder<@NotNull @DescriptorSource( + "com.code_intelligence.jazzer.mutation.mutator.StressTest#TEST_PROTOBUF_DESCRIPTOR") + DynamicMessage>() { + }.annotatedType(), + "{Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable, Builder.Nullable>, Builder.Nullable<{Builder.Nullable, Builder via List} -> Message>, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List, Builder via List>, Builder via List<(cycle) -> Message>, Builder.Map, Builder.Nullable, Builder.Nullable<{} -> Message>, Builder.Nullable | Builder.Nullable | Builder.Nullable} -> Message", + manyDistinctElements(), manyDistinctElements()), arguments( new TypeHolder<@NotNull @AnySource( {PrimitiveField3.class, MessageField3.class}) AnyField3>() { diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index a72b524a5..4cbc9a19a 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -472,6 +472,30 @@ java_fuzz_target_test( ], ) +java_fuzz_target_test( + name = "ExperimentalMutatorDynamicProtoFuzzer", + srcs = ["src/test/java/com/example/ExperimentalMutatorDynamicProtoFuzzer.java"], + allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium"], + fuzzer_args = [ + "--experimental_mutator", + "--instrumentation_includes=com.example.**", + "--custom_hook_includes=com.example.**", + ] + select({ + # Limit runs to catch regressions in mutator efficiency and speed up test runs. + "@platforms//os:linux": ["-runs=400000"], + # TODO: Investigate why this test takes far more runs on macOS, with Windows also being + # significantly worse than Linux. + "//conditions:default": ["-runs=1200000"], + }), + target_class = "com.example.ExperimentalMutatorDynamicProtoFuzzer", + verify_crash_reproducer = False, + deps = [ + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation/proto", + "@com_google_protobuf//java/core", + ], +) + sh_test( name = "jazzer_from_path_test", srcs = ["src/test/shell/jazzer_from_path_test.sh"], diff --git a/tests/src/test/java/com/example/ExperimentalMutatorDynamicProtoFuzzer.java b/tests/src/test/java/com/example/ExperimentalMutatorDynamicProtoFuzzer.java new file mode 100644 index 000000000..5df078081 --- /dev/null +++ b/tests/src/test/java/com/example/ExperimentalMutatorDynamicProtoFuzzer.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example; + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium; +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.proto.DescriptorSource; +import com.google.protobuf.DescriptorProtos.DescriptorProto; +import com.google.protobuf.DescriptorProtos.FieldDescriptorProto; +import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type; +import com.google.protobuf.DescriptorProtos.FileDescriptorProto; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.DescriptorValidationException; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.DynamicMessage; + +public class ExperimentalMutatorDynamicProtoFuzzer { + private static final Descriptor DESCRIPTOR = makeDescriptor(); + private static final FieldDescriptor I32 = DESCRIPTOR.findFieldByName("i32"); + private static final FieldDescriptor STR = DESCRIPTOR.findFieldByName("str"); + + public static void fuzzerTestOneInput(@NotNull @DescriptorSource( + "com.example.ExperimentalMutatorDynamicProtoFuzzer#DESCRIPTOR") DynamicMessage proto) { + if (proto.getField(I32).equals(1234) && proto.getField(STR).equals("abcd")) { + throw new FuzzerSecurityIssueMedium("Secret proto is found!"); + } + } + + private static Descriptor makeDescriptor() { + DescriptorProto myMessage = + DescriptorProto.newBuilder() + .setName("my_message") + .addField(FieldDescriptorProto.newBuilder().setNumber(1).setName("i32").setType( + Type.TYPE_INT32)) + .addField(FieldDescriptorProto.newBuilder().setNumber(2).setName("str").setType( + Type.TYPE_STRING)) + .build(); + FileDescriptorProto file = FileDescriptorProto.newBuilder() + .setName("my_protos.proto") + .addMessageType(myMessage) + .build(); + try { + return FileDescriptor.buildFrom(file, new FileDescriptor[0]) + .findMessageTypeByName("my_message"); + } catch (DescriptorValidationException e) { + throw new IllegalStateException(e); + } + } +}