Skip to content

Commit

Permalink
Cleanup and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
br-lewis committed Oct 5, 2023
1 parent a8339ec commit 1e9875d
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 62 deletions.
47 changes: 26 additions & 21 deletions examples/junit/src/test/java/com/example/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -254,27 +254,32 @@ java_fuzz_target_test(
],
)

java_fuzz_target_test(
name = "DictionaryFuzzTests",
srcs = ["DictionaryFuzzTests.java"],
allowed_findings = ["java.lang.Error"],
env = {"JAZZER_FUZZ": "1"},
target_class = "com.example.DictionaryFuzzTests",
# target_method = "inlineTest",
# target_method = "fileTest",
target_method = "mixedTest",
verify_crash_reproducer = False,
runtime_deps = [
":junit_runtime",
],
deps = [
"//examples/junit/src/test/resources:example_dictionaries",
"//src/main/java/com/code_intelligence/jazzer/junit:fuzz_test",
"//src/main/java/com/code_intelligence/jazzer/junit:fuzz_test_executor",
"@maven//:org_junit_jupiter_junit_jupiter_api",
"@maven//:org_junit_jupiter_junit_jupiter_params",
],
)
[
java_fuzz_target_test(
name = "DictionaryFuzzTests_" + method,
srcs = ["DictionaryFuzzTests.java"],
allowed_findings = ["java.lang.Error"],
env = {"JAZZER_FUZZ": "1"},
target_class = "com.example.DictionaryFuzzTests",
target_method = method,
verify_crash_reproducer = False,
runtime_deps = [
":junit_runtime",
],
deps = [
"//examples/junit/src/test/resources:example_dictionaries",
"//src/main/java/com/code_intelligence/jazzer/junit:fuzz_test",
"//src/main/java/com/code_intelligence/jazzer/junit:fuzz_test_executor",
"@maven//:org_junit_jupiter_junit_jupiter_api",
"@maven//:org_junit_jupiter_junit_jupiter_params",
],
)
for method in [
"inlineTest",
"fileTest",
"mixedTest",
]
]

java_library(
name = "junit_runtime",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,5 @@ public void mixedTest(FuzzedDataProvider data) throws NoSuchAlgorithmException {
if (MessageDigest.isEqual(hash, FLAG_SHA256)) {
throw new Error("error found");
}
throw new RuntimeException("asdf");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import com.code_intelligence.jazzer.driver.junit.ExitCodeException;
import com.code_intelligence.jazzer.junit.FuzzerDictionary.WithDictionary;
import com.code_intelligence.jazzer.junit.FuzzerDictionary.WithDictionaryFile;
import com.code_intelligence.jazzer.utils.Log;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Executable;
Expand Down Expand Up @@ -124,13 +123,7 @@ public static FuzzTestExecutor prepare(ExtensionContext context, String maxDurat
}

Optional<String> dictionary = createDictionaryFile(context);
if (dictionary.isPresent()) {
Log.info("fuzzing with dictionary " + dictionary.get());
List<String> lines = Files.readAllLines(Paths.get(dictionary.get()));
Log.info(String.join("%n", lines));

libFuzzerArgs.add("-dict=" + dictionary.get());
}
dictionary.ifPresent(s -> libFuzzerArgs.add("-dict=" + s));

libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(maxDuration));
if (maxRuns > 0) {
Expand Down Expand Up @@ -273,7 +266,8 @@ private static Path addInputAndSeedDirs(
return javaSeedsDir;
}

private static Optional<String> createDictionaryFile(ExtensionContext context) {
private static Optional<String> createDictionaryFile(ExtensionContext context)
throws IOException {
List<WithDictionary> inlineDictionaries =
AnnotationSupport.findRepeatableAnnotations(
context.getRequiredTestMethod(), WithDictionary.class);
Expand All @@ -282,15 +276,7 @@ private static Optional<String> createDictionaryFile(ExtensionContext context) {
AnnotationSupport.findRepeatableAnnotations(
context.getRequiredTestMethod(), WithDictionaryFile.class);

try {
if (!inlineDictionaries.isEmpty() || !fileDictionaries.isEmpty()) {
return Optional.of(FuzzerDictionary.createMergedFile(inlineDictionaries, fileDictionaries));
} else {
return Optional.empty();
}
} catch (IOException e) {
throw new RuntimeException("error creating dictionary file", e);
}
return FuzzerDictionary.createDictionaryFile(inlineDictionaries, fileDictionaries);
}

/** Returns the list of arguments set on the command line. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,47 +22,91 @@
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.platform.commons.util.ClassLoaderUtils;

/**
* Class that manages dictionaries for fuzz tests. The {@link WithDictionary} and {@link
* WithDictionaryFile} annotations are added to {@link FuzzTest}s to indicate that these
* dictionaries should be used for fuzzing this function. All tokens from all the sources will be
* added into a single merged dictionary file as libfuzzer can only accept a single {@code -dict}
* flag.
*
* <p>Syntax for dictionaries can be found <a
* href="https://llvm.org/docs/LibFuzzer.html#dictionaries">here</a>.
*/
public class FuzzerDictionary {
private static final String DICTIONARY_PREFIX = "jazzer-";
private static final String DICTIONARY_SUFFIX = ".dict";

public static String createMergedFile(List<WithDictionary> inline, List<WithDictionaryFile> files)
throws IOException {
// https://llvm.org/docs/LibFuzzer.html#dictionaries
Stream<String> inlineTokens =
inline.stream()
.map(WithDictionary::tokens)
.flatMap(Arrays::stream)
.map((token) -> String.format("\"%s\"", token));
Stream<String> fileTokens =
files.stream()
.map(WithDictionaryFile::resourcePath)
.map(FuzzerDictionary::tokensFromFile)
.flatMap(List::stream);
Stream<String> joined = Stream.concat(inlineTokens, fileTokens);
/**
* Create a temporary dictionary file for use during a fuzzing run based on the tokens found
* within {@code inline} and {@code files}.
*
* @param inline List of {@link WithDictionary} annotations that directly hold static token values
* to use in the dictionary
* @param files List of {@link WithDictionaryFile} annotations that reference dictionary files to
* include
* @return Optional containing the path to the created file, or nothing if {@code inline} and
* {@code files} are both empty
* @throws IOException
*/
public static Optional<String> createDictionaryFile(
List<WithDictionary> inline, List<WithDictionaryFile> files) throws IOException {
int sources = inline.size() + files.size();
if (sources == 0) {
return Optional.empty();
}

Stream<String> joined = Stream.concat(getInlineTokens(inline), getFileTokens(files));

File f = File.createTempFile(DICTIONARY_PREFIX, DICTIONARY_SUFFIX);
f.deleteOnExit();

int sources = inline.size() + files.size();
Log.info(String.format("Creating merged dictionary from %d sources", sources));

try (OutputStream out = Files.newOutputStream(f.toPath())) {
joined.forEach(
(token) -> {
try {
// the tokens will come in without newlines attached, so we append them here before
// writing
String line = token.concat("\n");
out.write(line.getBytes());
} catch (IOException e) {
throw new RuntimeException("error writing to dictionary file");
throw new UncheckedIOException(e);
}
});
}
return f.getPath();
return Optional.of(f.getPath());
}

/**
* Gets the inlined arrays from each annotation, flattens them into a single stream, and wraps the
* elements in double quotes to comply with libfuzzer's dictionary syntax
*
* @param inline List of {@link WithDictionary} annotations to extract from
* @return stream of all the tokens from each of the elements of {@code inline}
*/
private static Stream<String> getInlineTokens(List<WithDictionary> inline) {
return inline.stream()
.map(WithDictionary::tokens)
.flatMap(Arrays::stream)
.map((token) -> String.format("\"%s\"", token));
}

/**
* Gets the individual lines from each of the specified dictionary files
*
* @param files List of {@link WithDictionaryFile} annotations indicating which files to use
* @return stream of all lines from each of the files
*/
private static Stream<String> getFileTokens(List<WithDictionaryFile> files) {
return files.stream()
.map(WithDictionaryFile::resourcePath)
.map(FuzzerDictionary::tokensFromFile)
.flatMap(List::stream);
}

private static List<String> tokensFromFile(String path) {
Expand All @@ -71,22 +115,29 @@ private static List<String> tokensFromFile(String path) {
throw new FileNotFoundException(path);
}
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceFile));
// I think returning just reader.lines results in the file stream being closed before it's
// read so we immediately
// read the file and collect the lines into a list
// I think returning just reader.lines() results in the file stream being closed before it's
// read, so we immediately read the file and collect the lines into a list
return reader.lines().collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException("error reading dictionary file", e);
throw new UncheckedIOException(e);
}
}

/**
* Defines a dictionary where the values are given inline within this annotation. Values given
* here are intended to be bare tokens, so should not be given in the {@code name="value"} form.
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Dictionaries.class)
public @interface WithDictionary {
String[] tokens();
}

/**
* Defines a reference to a dictionary within the resources directory. These should follow <a
* href="https://llvm.org/docs/LibFuzzer.html#dictionaries">libfuzzer's dictionary syntax</a>.
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(DictionaryFiles.class)
Expand Down

0 comments on commit 1e9875d

Please sign in to comment.