Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
kmnls committed Apr 25, 2023
2 parents f77cc66 + 88ba6dd commit 748665e
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ")
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -163,10 +163,12 @@ static <T extends Builder, U> void setFieldWithPresence(

static <T extends Builder, K, V> Map<K, V> 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<K, V> map = new HashMap<>(size);
for (int i = 0; i < size; i++) {
MapEntry<K, V> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -325,8 +326,19 @@ public Optional<SerializingMutator<?>> tryCreate(AnnotatedType type, MutatorFact
return Optional.empty();
}

Message defaultInstance =
getDefaultInstance((Class<? extends Message>) 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<? extends Message>) builderClass.getEnclosingClass());
}

return Optional.of(
makeBuilderMutator(factory, defaultInstance, type.getDeclaredAnnotations()));
});
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -99,4 +103,61 @@ static Message getDefaultInstance(Class<? extends Message> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,14 @@ public static boolean isInheritable(Annotation annotation) {
}

/**
* Returns {@code type} as a {@code Class<T>} if it is a subclass of T, otherwise empty.
* Returns {@code type} as a {@code Class<? extends T>} if it is a subclass of T, otherwise
* empty.
*
* <p>This function also returns an empty {@link Optional} for more complex (e.g. parameterized)
* types.
*/
public static <T> Optional<Class<T>> asSubclassOrEmpty(AnnotatedType type, Class<T> superclass) {
public static <T> Optional<Class<? extends T>> asSubclassOrEmpty(
AnnotatedType type, Class<T> superclass) {
if (!(type.getType() instanceof Class<?>) ) {
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 }
Expand Down Expand Up @@ -302,6 +308,13 @@ public static Stream<Arguments> protoStressTestCases() {
arguments(new TypeHolder<@NotNull TestProtobuf>() {}.annotatedType(),
"{Builder.Nullable<Boolean>, Builder.Nullable<Integer>, Builder.Nullable<Integer>, Builder.Nullable<Long>, Builder.Nullable<Long>, Builder.Nullable<Float>, Builder.Nullable<Double>, Builder.Nullable<String>, Builder.Nullable<Enum<Enum>>, Builder.Nullable<{Builder.Nullable<Integer>, Builder via List<Integer>} -> Message>, Builder via List<Boolean>, Builder via List<Integer>, Builder via List<Integer>, Builder via List<Long>, Builder via List<Long>, Builder via List<Float>, Builder via List<Double>, Builder via List<String>, Builder via List<Enum<Enum>>, Builder via List<(cycle) -> Message>, Builder.Map<Integer,Integer>, Builder.Nullable<FixedValue(OnlyLabel)>, Builder.Nullable<{<empty>} -> Message>, Builder.Nullable<Integer> | Builder.Nullable<Long> | Builder.Nullable<Integer>} -> Message",
manyDistinctElements(), manyDistinctElements()),
arguments(
new TypeHolder<@NotNull @DescriptorSource(
"com.code_intelligence.jazzer.mutation.mutator.StressTest#TEST_PROTOBUF_DESCRIPTOR")
DynamicMessage>() {
}.annotatedType(),
"{Builder.Nullable<Boolean>, Builder.Nullable<Integer>, Builder.Nullable<Integer>, Builder.Nullable<Long>, Builder.Nullable<Long>, Builder.Nullable<Float>, Builder.Nullable<Double>, Builder.Nullable<String>, Builder.Nullable<Enum<Enum>>, Builder.Nullable<{Builder.Nullable<Integer>, Builder via List<Integer>} -> Message>, Builder via List<Boolean>, Builder via List<Integer>, Builder via List<Integer>, Builder via List<Long>, Builder via List<Long>, Builder via List<Float>, Builder via List<Double>, Builder via List<String>, Builder via List<Enum<Enum>>, Builder via List<(cycle) -> Message>, Builder.Map<Integer,Integer>, Builder.Nullable<FixedValue(OnlyLabel)>, Builder.Nullable<{<empty>} -> Message>, Builder.Nullable<Integer> | Builder.Nullable<Long> | Builder.Nullable<Integer>} -> Message",
manyDistinctElements(), manyDistinctElements()),
arguments(
new TypeHolder<@NotNull @AnySource(
{PrimitiveField3.class, MessageField3.class}) AnyField3>() {
Expand Down
24 changes: 24 additions & 0 deletions tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}

0 comments on commit 748665e

Please sign in to comment.