Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/arguments-and-configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ Some parameters only have an effect when used with standalone Jazzer binary (mar
- **autofuzz_ignore** [list, separator=`','`, default=""] (*DEPRECATED*)
- Fully qualified names of exception classes to ignore during fuzzing

- **autofuzz_constructor_excludes** [list, separator=`';'`, default=""] (*DEPRECATED*)
- Exact constructor references to exclude during Autofuzz argument construction

- **command_line** [bool, default="false"]
- Whether Jazzer is running a JUnit fuzz test from the command line

Expand Down
11 changes: 11 additions & 0 deletions docs/autofuzz.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ Under the hood, Jazzer tries various ways of creating objects from the fuzzer in
For example, if a parameter is an interface or an abstract class, it will look for all concrete implementing classes on the classpath.
Jazzer can also create objects from classes that follow the [builder design pattern](https://www.baeldung.com/creational-design-patterns#builder) or have a default constructor and use setters to set the fields.

Some constructors have undesired side effects or can take a very long time for fuzzed inputs.
Exact constructor references can be excluded via `--autofuzz_constructor_excludes` as a semicolon-separated list:
```
--autofuzz_constructor_excludes='com.example.Dangerous::new(int,java.util.Random);com.example.Dangerous::new(byte[])'
```
The constructor descriptor is exact and uses the same parameter type format as `--autofuzz` method descriptors.
Each exclude must name an accessible constructor that exists when Autofuzz initializes.
Nested classes must be specified with their binary names, for example `com.example.Outer$Inner::new()`.
Globs, regular expressions, and broad `Class::new` excludes are not supported.
Explicitly targeted constructors, for example via `--autofuzz=com.example.Dangerous::new(int)`, are not affected by this option.

Creating objects from fuzzer input can lead to many reported exceptions.
Jazzer addresses this issue by ignoring exceptions that the target method declares to throw.
In addition to that, you can provide a list of exceptions to be ignored during fuzzing via the `--autofuzz_ignore` flag in the form of a comma-separated list.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ java_library(
"AccessibleObjectLookup.java",
"AutofuzzCodegenVisitor.java",
"AutofuzzError.java",
"ConstructorExclusions.java",
"FuzzTarget.java",
"Meta.java",
"YourAverageJavaClass.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright 2024 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.autofuzz;

import com.code_intelligence.jazzer.utils.Utils;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

final class ConstructorExclusions {
private static final String CONSTRUCTOR_REFERENCE = "::new";
private static final ConstructorExclusions EMPTY =
new ConstructorExclusions(Collections.emptySet());

static ConstructorExclusions empty() {
return EMPTY;
}

static ConstructorExclusions from(
List<String> constructorReferences, AccessibleObjectLookup lookup) {
return constructorReferences.isEmpty()
? EMPTY
: new ConstructorExclusions(
constructorReferences.stream()
.map(String::trim)
.filter(reference -> !reference.isEmpty())
.map(reference -> resolveConstructorReference(reference, lookup))
.collect(
Collectors.collectingAndThen(
Collectors.toCollection(LinkedHashSet::new),
Collections::unmodifiableSet)));
}

private final Set<String> excludedConstructorReferences;

private ConstructorExclusions(Set<String> excludedConstructorReferences) {
this.excludedConstructorReferences = excludedConstructorReferences;
}

boolean isExcluded(Constructor<?> constructor) {
return !excludedConstructorReferences.isEmpty()
&& excludedConstructorReferences.contains(toConstructorReference(constructor));
}

private static String toConstructorReference(Constructor<?> constructor) {
return constructor.getDeclaringClass().getName()
+ CONSTRUCTOR_REFERENCE
+ Utils.getReadableDescriptor(constructor);
}

private static String resolveConstructorReference(
String reference, AccessibleObjectLookup lookup) {
int separator = reference.indexOf(CONSTRUCTOR_REFERENCE);
if (separator <= 0 || separator != reference.lastIndexOf(CONSTRUCTOR_REFERENCE)) {
throw invalidConstructorReference(reference);
}

String className = reference.substring(0, separator);
String descriptor = reference.substring(separator + CONSTRUCTOR_REFERENCE.length());
if (!descriptor.startsWith("(")
|| !descriptor.endsWith(")")
|| descriptor.indexOf(')') != descriptor.length() - 1) {
throw invalidConstructorReference(reference);
}

Class<?> clazz = loadClass(reference, className);
Constructor<?>[] constructors = lookup.getAccessibleConstructors(clazz);
List<Constructor<?>> matchingConstructors =
Arrays.stream(constructors)
.filter(constructor -> Utils.getReadableDescriptor(constructor).equals(descriptor))
.collect(Collectors.toList());

if (matchingConstructors.size() == 1) {
return toConstructorReference(matchingConstructors.get(0));
}
throw noMatchingConstructor(reference, clazz, constructors);
}

private static Class<?> loadClass(String reference, String className) {
try {
return Class.forName(className, false, ClassLoader.getSystemClassLoader());
} catch (ClassNotFoundException e) {
throw classNotFound(reference, className, e);
}
}

private static IllegalArgumentException invalidConstructorReference(String reference) {
return new IllegalArgumentException(
String.format(
"Invalid Autofuzz constructor exclude '%s'; expected exact constructor reference like"
+ " 'com.example.Dangerous::new(int,java.util.Random)'",
reference));
}

private static IllegalArgumentException classNotFound(
String reference, String className, ClassNotFoundException cause) {
return new IllegalArgumentException(
String.format(
"Invalid Autofuzz constructor exclude '%s'; class '%s' could not be found",
reference, className),
cause);
}

private static IllegalArgumentException noMatchingConstructor(
String reference, Class<?> clazz, Constructor<?>[] constructors) {
return new IllegalArgumentException(
String.format(
"Invalid Autofuzz constructor exclude '%s'; no accessible constructor with this"
+ " descriptor found in %s.%nAccessible constructors:%n%s",
reference, clazz.getName(), formatConstructorReferences(constructors)));
}

private static String formatConstructorReferences(Constructor<?>[] constructors) {
if (constructors.length == 0) {
return "<none>";
}
return Arrays.stream(constructors)
.map(ConstructorExclusions::toConstructorReference)
.collect(Collectors.joining(System.lineSeparator()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -51,6 +53,7 @@ public final class FuzzTarget {
+ "}";
private static final long MAX_EXECUTIONS_WITHOUT_INVOCATION = 100;

private static List<String> constructorExcludeReferences = Collections.emptyList();
private static Meta meta;
private static String methodReference;
private static Executable[] targetExecutables;
Expand All @@ -59,6 +62,10 @@ public final class FuzzTarget {
private static Set<SimpleGlobMatcher> ignoredExceptionMatchers;
private static long executionsSinceLastInvocation = 0;

public static void setConstructorExcludeReferences(List<String> references) {
constructorExcludeReferences = Collections.unmodifiableList(new ArrayList<>(references));
}

public static void fuzzerInitialize(String[] args) {
if (args.length == 0 || !args[0].contains("::")) {
Log.error(
Expand Down Expand Up @@ -245,8 +252,22 @@ public static void fuzzerInitialize(String[] args) {
Arrays.stream(method.getExceptionTypes()), alwaysIgnore.stream())
.toArray(Class[]::new)));

ConstructorExclusions constructorExclusions;
try {
constructorExclusions = ConstructorExclusions.from(constructorExcludeReferences, lookup);
} catch (IllegalArgumentException e) {
Log.error(e.getMessage());
System.exit(1);
return;
}

setTarget(
executables, null, methodSignature, ignoredExceptionGlobMatchers, ignoredExceptionClasses);
executables,
null,
methodSignature,
ignoredExceptionGlobMatchers,
ignoredExceptionClasses,
constructorExclusions);
}

/**
Expand All @@ -259,6 +280,22 @@ public static void setTarget(
String methodReference,
Set<SimpleGlobMatcher> ignoredExceptionMatchers,
Map<Executable, Class<?>[]> throwsDeclarations) {
setTarget(
targetExecutables,
targetInstance,
methodReference,
ignoredExceptionMatchers,
throwsDeclarations,
ConstructorExclusions.empty());
}

private static void setTarget(
Executable[] targetExecutables,
Object targetInstance,
String methodReference,
Set<SimpleGlobMatcher> ignoredExceptionMatchers,
Map<Executable, Class<?>[]> throwsDeclarations,
ConstructorExclusions constructorExclusions) {
Class<?> targetClass = null;
for (Executable executable : targetExecutables) {
if (targetClass != null && !targetClass.equals(executable.getDeclaringClass())) {
Expand All @@ -269,7 +306,7 @@ public static void setTarget(
executable.setAccessible(true);
}

FuzzTarget.meta = new Meta(targetClass);
FuzzTarget.meta = new Meta(targetClass, constructorExclusions);
FuzzTarget.targetExecutables = targetExecutables;
FuzzTarget.targetInstance = targetInstance;
FuzzTarget.methodReference = methodReference;
Expand Down
25 changes: 20 additions & 5 deletions src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ public class Meta {
new WeakHashMap<>();

private final AccessibleObjectLookup lookup;
private final ConstructorExclusions constructorExclusions;

public Meta(Class<?> referenceClass) {
this(referenceClass, ConstructorExclusions.empty());
}

Meta(Class<?> referenceClass, ConstructorExclusions constructorExclusions) {
lookup = new AccessibleObjectLookup(referenceClass);
this.constructorExclusions = constructorExclusions;
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -610,7 +616,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
if (visitor != null) {
throw new AutofuzzError("codegen has not been implemented for Constructor.class");
}
return data.pickValue(lookup.getAccessibleConstructors(YourAverageJavaClass.class));
return data.pickValue(getAccessibleConstructors(YourAverageJavaClass.class));
} else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) {
List<Class<?>> implementingClasses = implementingClassesCache.get(type);
if (implementingClasses == null) {
Expand Down Expand Up @@ -665,7 +671,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
}
return result;
}
Constructor<?>[] constructors = lookup.getAccessibleConstructors(type);
Constructor<?>[] constructors = getAccessibleConstructors(type);
if (constructors.length > 0) {
Constructor<?> constructor = data.pickValue(constructors);
boolean applySetters = constructor.getParameterCount() == 0;
Expand Down Expand Up @@ -701,7 +707,10 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor

// First, try to find nested classes with names ending in Builder and call a subset of their
// chaining methods.
List<Class<?>> nestedBuilderClasses = getNestedBuilderClasses(type);
List<Class<?>> nestedBuilderClasses =
getNestedBuilderClasses(type).stream()
.filter(builder -> getAccessibleConstructors(builder).length > 0)
.collect(Collectors.toList());
if (!nestedBuilderClasses.isEmpty()) {
Class<?> pickedBuilder = data.pickValue(nestedBuilderClasses);
List<Method> cascadingBuilderMethods = getCascadingBuilderMethods(pickedBuilder);
Expand All @@ -717,7 +726,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
}
Object builderObj =
autofuzzForConsume(
data, data.pickValue(lookup.getAccessibleConstructors(pickedBuilder)), visitor);
data, data.pickValue(getAccessibleConstructors(pickedBuilder)), visitor);
for (Method method : pickedMethods) {
builderObj = autofuzzForConsume(data, method, builderObj, visitor);
}
Expand All @@ -741,7 +750,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
"Failed to generate instance of %s:%nAccessible constructors: %s%nNested subclasses:"
+ " %s%n",
type.getName(),
Arrays.stream(lookup.getAccessibleConstructors(type))
Arrays.stream(getAccessibleConstructors(type))
.map(Utils::getReadableDescriptor)
.collect(Collectors.joining(", ")),
Arrays.stream(lookup.getAccessibleClasses(type))
Expand All @@ -766,6 +775,12 @@ private List<Class<?>> getNestedBuilderClasses(Class<?> type) {
return nestedBuilderClasses;
}

private Constructor<?>[] getAccessibleConstructors(Class<?> type) {
return Arrays.stream(lookup.getAccessibleConstructors(type))
.filter(constructor -> !constructorExclusions.isExcluded(constructor))
.toArray(Constructor<?>[]::new);
}

private List<Method> getOriginalObjectCreationMethods(Class<?> builder) {
List<Method> originalObjectCreationMethods = originalObjectCreationMethodsCache.get(builder);
if (originalObjectCreationMethods == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,16 @@ public final class FuzzTargetRunner {
Log.error("--autofuzz_ignore requires --autofuzz");
exit(1);
}
if (!Opt.autofuzzConstructorExcludes.get().isEmpty()) {
Log.error("--autofuzz_constructor_excludes requires --autofuzz");
exit(1);
}
} else {
if (!Opt.targetClass.get().isEmpty()) {
Log.error("--target_class and --autofuzz cannot be specified together");
exit(1);
}
FuzzTarget.setConstructorExcludeReferences(Opt.autofuzzConstructorExcludes.get());
if (!Opt.targetArgs.setIfDefault(
unmodifiableList(
concat(Stream.of(Opt.autofuzz.get()), Opt.autofuzzIgnore.get().stream())
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/code_intelligence/jazzer/driver/Opt.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public final class Opt {
"autofuzz_ignore",
',',
"Fully qualified names of exception classes to ignore during fuzzing");
public static final OptItem<List<String>> autofuzzConstructorExcludes =
stringListSetting(
"autofuzz_constructor_excludes",
';',
"Exact constructor references to exclude during Autofuzz argument construction");
public static final OptItem<String> coverageDump =
stringSetting(
"coverage_dump",
Expand Down
13 changes: 13 additions & 0 deletions src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ java_test(
],
)

java_test(
name = "ConstructorExclusionsTest",
size = "small",
srcs = [
"ConstructorExclusionsTest.java",
],
test_class = "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest",
deps = [
"//src/main/java/com/code_intelligence/jazzer/autofuzz",
"@maven//:junit_junit",
],
)

java_test(
name = "InterfaceCreationTest",
size = "small",
Expand Down
Loading