From dbfc9cd9034e7dee109492f37487cb950e491ed3 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Thu, 5 Oct 2023 11:53:34 +0200 Subject: [PATCH 01/19] Add support for dictionaries in fuzz tests --- .../src/test/java/com/example/BUILD.bazel | 22 ++++ .../java/com/example/DictionaryFuzzTests.java | 66 +++++++++++ examples/junit/src/test/resources/BUILD.bazel | 6 + .../src/test/resources/com/example/test.dict | 4 + .../src/test/resources/com/example/test2.dict | 1 + .../src/test/resources/com/example/test3.dict | 1 + .../jazzer/junit/BUILD.bazel | 3 + .../jazzer/junit/FuzzTestExecutor.java | 32 ++++++ .../jazzer/junit/FuzzerDictionary.java | 108 ++++++++++++++++++ .../jazzer/junit/BUILD.bazel | 12 ++ .../jazzer/junit/FuzzerDictionaryTest.java | 19 +++ 11 files changed, 274 insertions(+) create mode 100644 examples/junit/src/test/java/com/example/DictionaryFuzzTests.java create mode 100644 examples/junit/src/test/resources/com/example/test.dict create mode 100644 examples/junit/src/test/resources/com/example/test2.dict create mode 100644 examples/junit/src/test/resources/com/example/test3.dict create mode 100644 src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java create mode 100644 src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java diff --git a/examples/junit/src/test/java/com/example/BUILD.bazel b/examples/junit/src/test/java/com/example/BUILD.bazel index 43b6a88dc..7f585a065 100644 --- a/examples/junit/src/test/java/com/example/BUILD.bazel +++ b/examples/junit/src/test/java/com/example/BUILD.bazel @@ -254,6 +254,28 @@ 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_library( name = "junit_runtime", runtime_deps = [ diff --git a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java new file mode 100644 index 000000000..e66409e3c --- /dev/null +++ b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java @@ -0,0 +1,66 @@ +/* + * 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.FuzzedDataProvider; +import com.code_intelligence.jazzer.junit.FuzzTest; +import com.code_intelligence.jazzer.junit.FuzzerDictionary; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +public class DictionaryFuzzTests { + // Generated via: + // printf 'a_53Cr3T_fl4G' | openssl dgst -binary -sha256 | openssl base64 -A + // Luckily the fuzzer can't read comments ;-) + private static final byte[] FLAG_SHA256 = + Base64.getDecoder().decode("IT7goSzYg6MXLugHl9H4oCswA+OEb4bGZmKrDzlZjO4="); + + @FuzzerDictionary.WithDictionary(tokens = {"a_", "53Cr3T_", "fl4G"}) + @FuzzTest + public void inlineTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { + String s = data.consumeRemainingAsString(); + byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); + if (MessageDigest.isEqual(hash, FLAG_SHA256)) { + throw new Error("error found"); + } + } + + @FuzzerDictionary.WithDictionaryFile(resourcePath = "/com/example/test.dict") + @FuzzTest + public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { + String s = data.consumeRemainingAsString(); + byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); + if (MessageDigest.isEqual(hash, FLAG_SHA256)) { + throw new Error("error found"); + } + } + + @FuzzerDictionary.WithDictionary(tokens = {"a_"}) + @FuzzerDictionary.WithDictionaryFile(resourcePath = "/com/example/test2.dict") + @FuzzerDictionary.WithDictionaryFile(resourcePath = "/com/example/test3.dict") + @FuzzTest + public void mixedTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { + String s = data.consumeRemainingAsString(); + byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); + if (MessageDigest.isEqual(hash, FLAG_SHA256)) { + throw new Error("error found"); + } + throw new RuntimeException("asdf"); + } +} diff --git a/examples/junit/src/test/resources/BUILD.bazel b/examples/junit/src/test/resources/BUILD.bazel index 693430535..7189b7ff3 100644 --- a/examples/junit/src/test/resources/BUILD.bazel +++ b/examples/junit/src/test/resources/BUILD.bazel @@ -4,6 +4,12 @@ java_library( visibility = ["//examples/junit/src/test/java/com/example:__pkg__"], ) +java_library( + name = "example_dictionaries", + resources = glob(["**/*.dict"]), + visibility = ["//examples/junit/src/test/java/com/example:__pkg__"], +) + filegroup( name = "MutatorFuzzTestInputs", srcs = ["com/example/MutatorFuzzTestInputs"], diff --git a/examples/junit/src/test/resources/com/example/test.dict b/examples/junit/src/test/resources/com/example/test.dict new file mode 100644 index 000000000..fb9cb25e7 --- /dev/null +++ b/examples/junit/src/test/resources/com/example/test.dict @@ -0,0 +1,4 @@ +# test dictionary +"a_" +"53Cr3T_" +"fl4G" diff --git a/examples/junit/src/test/resources/com/example/test2.dict b/examples/junit/src/test/resources/com/example/test2.dict new file mode 100644 index 000000000..0470f6698 --- /dev/null +++ b/examples/junit/src/test/resources/com/example/test2.dict @@ -0,0 +1 @@ +"53Cr3T_" diff --git a/examples/junit/src/test/resources/com/example/test3.dict b/examples/junit/src/test/resources/com/example/test3.dict new file mode 100644 index 000000000..b0aa4e06a --- /dev/null +++ b/examples/junit/src/test/resources/com/example/test3.dict @@ -0,0 +1 @@ +"fl4G" \ No newline at end of file diff --git a/src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel index 76b2e801c..29f0471a0 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel @@ -64,10 +64,12 @@ java_jni_library( name = "fuzz_test_executor", srcs = [ "FuzzTestExecutor.java", + "FuzzerDictionary.java", ], native_libs = [ "//src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", ], + visibility = ["//examples/junit/src/test/java/com/example:__pkg__"], deps = [ ":agent_configurator", ":junit_lifecycle_methods_invoker", @@ -83,6 +85,7 @@ java_jni_library( "//src/main/java/com/code_intelligence/jazzer/driver/junit:exit_code_exception", "//src/main/java/com/code_intelligence/jazzer/mutation", "//src/main/java/com/code_intelligence/jazzer/utils", + "//src/main/java/com/code_intelligence/jazzer/utils:log", "@maven//:org_junit_jupiter_junit_jupiter_api", "@maven//:org_junit_jupiter_junit_jupiter_params", "@maven//:org_junit_platform_junit_platform_commons", diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 7537a7fd8..cd25d29ad 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -25,6 +25,9 @@ import com.code_intelligence.jazzer.driver.FuzzTargetRunner; import com.code_intelligence.jazzer.driver.Opt; 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; @@ -120,6 +123,15 @@ public static FuzzTestExecutor prepare(ExtensionContext context, String maxDurat Optional.of(addInputAndSeedDirs(context, libFuzzerArgs, createDefaultGeneratedCorpusDir)); } + Optional dictionary = createDictionaryFile(context); + if (dictionary.isPresent()) { + Log.info("fuzzing with dictionary " + dictionary.get()); + List lines = Files.readAllLines(Paths.get(dictionary.get())); + Log.info(String.join("%n", lines)); + + libFuzzerArgs.add("-dict=" + dictionary.get()); + } + libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(maxDuration)); if (maxRuns > 0) { libFuzzerArgs.add("-runs=" + maxRuns); @@ -261,6 +273,26 @@ private static Path addInputAndSeedDirs( return javaSeedsDir; } + private static Optional createDictionaryFile(ExtensionContext context) { + List inlineDictionaries = + AnnotationSupport.findRepeatableAnnotations( + context.getRequiredTestMethod(), WithDictionary.class); + + List fileDictionaries = + 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); + } + } + /** Returns the list of arguments set on the command line. */ private static List getLibFuzzerArgs(ExtensionContext extensionContext) { List args = new ArrayList<>(); diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java new file mode 100644 index 000000000..b96c5d318 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -0,0 +1,108 @@ +/* + * 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.junit; + +import com.code_intelligence.jazzer.utils.Log; +import java.io.*; +import java.lang.annotation.*; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.platform.commons.util.ClassLoaderUtils; + +public class FuzzerDictionary { + private static final String DICTIONARY_PREFIX = "jazzer-"; + private static final String DICTIONARY_SUFFIX = ".dict"; + + public static String createMergedFile(List inline, List files) + throws IOException { + // https://llvm.org/docs/LibFuzzer.html#dictionaries + Stream inlineTokens = + inline.stream() + .map(WithDictionary::tokens) + .flatMap(Arrays::stream) + .map((token) -> String.format("\"%s\"", token)); + Stream fileTokens = + files.stream() + .map(WithDictionaryFile::resourcePath) + .map(FuzzerDictionary::tokensFromFile) + .flatMap(List::stream); + Stream joined = Stream.concat(inlineTokens, fileTokens); + + 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 { + String line = token.concat("\n"); + out.write(line.getBytes()); + } catch (IOException e) { + throw new RuntimeException("error writing to dictionary file"); + } + }); + } + return f.getPath(); + } + + private static List tokensFromFile(String path) { + try (InputStream resourceFile = ClassLoaderUtils.class.getResourceAsStream(path)) { + if (resourceFile == null) { + 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 + return reader.lines().collect(Collectors.toList()); + } catch (IOException e) { + throw new RuntimeException("error reading dictionary file", e); + } + } + + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Repeatable(Dictionaries.class) + public @interface WithDictionary { + String[] tokens(); + } + + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Repeatable(DictionaryFiles.class) + public @interface WithDictionaryFile { + String resourcePath(); + } + + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Dictionaries { + WithDictionary[] value(); + } + + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface DictionaryFiles { + WithDictionaryFile[] value(); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel index 5da1f0cf6..c4b8cfa45 100644 --- a/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel +++ b/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel @@ -347,3 +347,15 @@ java_test( "_fuzzing", ] ] + +java_test( + name = "FuzzerDictionaryTest", + srcs = ["FuzzerDictionaryTest.java"], + test_class = "com.code_intelligence.jazzer.junit.FuzzerDictionaryTest", + deps = [ + "@maven//:junit_junit", + "@maven//:org_assertj_assertj_core", + "@maven//:org_junit_platform_junit_platform_engine", + "@maven//:org_junit_platform_junit_platform_testkit", + ], +) diff --git a/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java b/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java new file mode 100644 index 000000000..fe0243974 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java @@ -0,0 +1,19 @@ +/* + * 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.junit; + +public class FuzzerDictionaryTest {} From dac4e85e5c90dab2db294253a39b093ae115a64d Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Thu, 5 Oct 2023 11:56:14 +0200 Subject: [PATCH 02/19] Remove unused test --- .../jazzer/junit/BUILD.bazel | 12 ------------ .../jazzer/junit/FuzzerDictionaryTest.java | 19 ------------------- 2 files changed, 31 deletions(-) delete mode 100644 src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java diff --git a/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel index c4b8cfa45..5da1f0cf6 100644 --- a/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel +++ b/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel @@ -347,15 +347,3 @@ java_test( "_fuzzing", ] ] - -java_test( - name = "FuzzerDictionaryTest", - srcs = ["FuzzerDictionaryTest.java"], - test_class = "com.code_intelligence.jazzer.junit.FuzzerDictionaryTest", - deps = [ - "@maven//:junit_junit", - "@maven//:org_assertj_assertj_core", - "@maven//:org_junit_platform_junit_platform_engine", - "@maven//:org_junit_platform_junit_platform_testkit", - ], -) diff --git a/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java b/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java deleted file mode 100644 index fe0243974..000000000 --- a/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.junit; - -public class FuzzerDictionaryTest {} From 22fb4d8494aeb0696ca82f95c5dfbf9948663513 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Thu, 5 Oct 2023 12:00:08 +0200 Subject: [PATCH 03/19] Revert "Remove unused test" This reverts commit 79ce2354afb2fc3a6d1ff8408fcaa47158c511b7. --- .../jazzer/junit/BUILD.bazel | 12 ++++++++++++ .../jazzer/junit/FuzzerDictionaryTest.java | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java diff --git a/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel index 5da1f0cf6..c4b8cfa45 100644 --- a/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel +++ b/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel @@ -347,3 +347,15 @@ java_test( "_fuzzing", ] ] + +java_test( + name = "FuzzerDictionaryTest", + srcs = ["FuzzerDictionaryTest.java"], + test_class = "com.code_intelligence.jazzer.junit.FuzzerDictionaryTest", + deps = [ + "@maven//:junit_junit", + "@maven//:org_assertj_assertj_core", + "@maven//:org_junit_platform_junit_platform_engine", + "@maven//:org_junit_platform_junit_platform_testkit", + ], +) diff --git a/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java b/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java new file mode 100644 index 000000000..fe0243974 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java @@ -0,0 +1,19 @@ +/* + * 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.junit; + +public class FuzzerDictionaryTest {} From ed4b34ea575d5702c55db09ea9c6274545920e3d Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Thu, 5 Oct 2023 14:53:16 +0200 Subject: [PATCH 04/19] Undo revert --- .../jazzer/junit/BUILD.bazel | 12 ------------ .../jazzer/junit/FuzzerDictionaryTest.java | 19 ------------------- 2 files changed, 31 deletions(-) delete mode 100644 src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java diff --git a/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel index c4b8cfa45..5da1f0cf6 100644 --- a/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel +++ b/src/test/java/com/code_intelligence/jazzer/junit/BUILD.bazel @@ -347,15 +347,3 @@ java_test( "_fuzzing", ] ] - -java_test( - name = "FuzzerDictionaryTest", - srcs = ["FuzzerDictionaryTest.java"], - test_class = "com.code_intelligence.jazzer.junit.FuzzerDictionaryTest", - deps = [ - "@maven//:junit_junit", - "@maven//:org_assertj_assertj_core", - "@maven//:org_junit_platform_junit_platform_engine", - "@maven//:org_junit_platform_junit_platform_testkit", - ], -) diff --git a/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java b/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java deleted file mode 100644 index fe0243974..000000000 --- a/src/test/java/com/code_intelligence/jazzer/junit/FuzzerDictionaryTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.junit; - -public class FuzzerDictionaryTest {} From f40ee456d8461600e48b3897e8536975fc98ca09 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Thu, 5 Oct 2023 15:45:31 +0200 Subject: [PATCH 05/19] Cleanup and documentation --- .../src/test/java/com/example/BUILD.bazel | 47 +++++---- .../java/com/example/DictionaryFuzzTests.java | 1 - .../jazzer/junit/FuzzTestExecutor.java | 22 +---- .../jazzer/junit/FuzzerDictionary.java | 95 ++++++++++++++----- 4 files changed, 103 insertions(+), 62 deletions(-) diff --git a/examples/junit/src/test/java/com/example/BUILD.bazel b/examples/junit/src/test/java/com/example/BUILD.bazel index 7f585a065..91db7a702 100644 --- a/examples/junit/src/test/java/com/example/BUILD.bazel +++ b/examples/junit/src/test/java/com/example/BUILD.bazel @@ -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", diff --git a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java index e66409e3c..d383f5e53 100644 --- a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java +++ b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java @@ -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"); } } diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index cd25d29ad..d3fae26c0 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -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; @@ -124,13 +123,7 @@ public static FuzzTestExecutor prepare(ExtensionContext context, String maxDurat } Optional dictionary = createDictionaryFile(context); - if (dictionary.isPresent()) { - Log.info("fuzzing with dictionary " + dictionary.get()); - List 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) { @@ -273,7 +266,8 @@ private static Path addInputAndSeedDirs( return javaSeedsDir; } - private static Optional createDictionaryFile(ExtensionContext context) { + private static Optional createDictionaryFile(ExtensionContext context) + throws IOException { List inlineDictionaries = AnnotationSupport.findRepeatableAnnotations( context.getRequiredTestMethod(), WithDictionary.class); @@ -282,15 +276,7 @@ private static Optional 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. */ diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index b96c5d318..c3f2a78b9 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -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. + * + *

Syntax for dictionaries can be found here. + */ public class FuzzerDictionary { private static final String DICTIONARY_PREFIX = "jazzer-"; private static final String DICTIONARY_SUFFIX = ".dict"; - public static String createMergedFile(List inline, List files) - throws IOException { - // https://llvm.org/docs/LibFuzzer.html#dictionaries - Stream inlineTokens = - inline.stream() - .map(WithDictionary::tokens) - .flatMap(Arrays::stream) - .map((token) -> String.format("\"%s\"", token)); - Stream fileTokens = - files.stream() - .map(WithDictionaryFile::resourcePath) - .map(FuzzerDictionary::tokensFromFile) - .flatMap(List::stream); - Stream 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 createDictionaryFile( + List inline, List files) throws IOException { + int sources = inline.size() + files.size(); + if (sources == 0) { + return Optional.empty(); + } + + Stream 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 getInlineTokens(List 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 getFileTokens(List files) { + return files.stream() + .map(WithDictionaryFile::resourcePath) + .map(FuzzerDictionary::tokensFromFile) + .flatMap(List::stream); } private static List tokensFromFile(String path) { @@ -71,15 +115,18 @@ private static List 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) @@ -87,6 +134,10 @@ private static List tokensFromFile(String path) { String[] tokens(); } + /** + * Defines a reference to a dictionary within the resources directory. These should follow libfuzzer's dictionary syntax. + */ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(DictionaryFiles.class) From 68ae11dfc6554fcf27ee2ea954be589fe8657d44 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Thu, 5 Oct 2023 15:53:45 +0200 Subject: [PATCH 06/19] Try using the direct annotation to appease github's syntax highlighter --- .../test/java/com/example/DictionaryFuzzTests.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java index d383f5e53..ea2bdd9ea 100644 --- a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java +++ b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java @@ -18,7 +18,8 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.junit.FuzzTest; -import com.code_intelligence.jazzer.junit.FuzzerDictionary; +import com.code_intelligence.jazzer.junit.FuzzerDictionary.WithDictionary; +import com.code_intelligence.jazzer.junit.FuzzerDictionary.WithDictionaryFile; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -31,7 +32,7 @@ public class DictionaryFuzzTests { private static final byte[] FLAG_SHA256 = Base64.getDecoder().decode("IT7goSzYg6MXLugHl9H4oCswA+OEb4bGZmKrDzlZjO4="); - @FuzzerDictionary.WithDictionary(tokens = {"a_", "53Cr3T_", "fl4G"}) + @WithDictionary(tokens = {"a_", "53Cr3T_", "fl4G"}) @FuzzTest public void inlineTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { String s = data.consumeRemainingAsString(); @@ -41,7 +42,7 @@ public void inlineTest(FuzzedDataProvider data) throws NoSuchAlgorithmException } } - @FuzzerDictionary.WithDictionaryFile(resourcePath = "/com/example/test.dict") + @WithDictionaryFile(resourcePath = "/com/example/test.dict") @FuzzTest public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { String s = data.consumeRemainingAsString(); @@ -51,9 +52,9 @@ public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { } } - @FuzzerDictionary.WithDictionary(tokens = {"a_"}) - @FuzzerDictionary.WithDictionaryFile(resourcePath = "/com/example/test2.dict") - @FuzzerDictionary.WithDictionaryFile(resourcePath = "/com/example/test3.dict") + @WithDictionary(tokens = {"a_"}) + @WithDictionaryFile(resourcePath = "/com/example/test2.dict") + @WithDictionaryFile(resourcePath = "/com/example/test3.dict") @FuzzTest public void mixedTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { String s = data.consumeRemainingAsString(); From a0a9dee840cc442557cc2204e7d41d585ee872cf Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Fri, 6 Oct 2023 11:55:02 +0200 Subject: [PATCH 07/19] Update src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java Co-authored-by: Fabian Meumertzheim --- .../com/code_intelligence/jazzer/junit/FuzzTestExecutor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index d3fae26c0..0ea6d16c2 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -122,8 +122,7 @@ public static FuzzTestExecutor prepare(ExtensionContext context, String maxDurat Optional.of(addInputAndSeedDirs(context, libFuzzerArgs, createDefaultGeneratedCorpusDir)); } - Optional dictionary = createDictionaryFile(context); - dictionary.ifPresent(s -> libFuzzerArgs.add("-dict=" + s)); + createDictionaryFile(context).ifPresent(s -> libFuzzerArgs.add("-dict=" + s)); libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(maxDuration)); if (maxRuns > 0) { From 15876f836b7b963358987a133d227843af0d7a68 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Fri, 6 Oct 2023 16:15:19 +0200 Subject: [PATCH 08/19] Update src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java Co-authored-by: Fabian Meumertzheim --- .../com/code_intelligence/jazzer/junit/FuzzerDictionary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index c3f2a78b9..659d9bb77 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -93,7 +93,7 @@ private static Stream getInlineTokens(List inline) { return inline.stream() .map(WithDictionary::tokens) .flatMap(Arrays::stream) - .map((token) -> String.format("\"%s\"", token)); + .map(token -> String.format("\"%s\"", token)); } /** From d9d0a8fe18d15c3653af294eb47be8d9e9712796 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Fri, 6 Oct 2023 11:54:37 +0200 Subject: [PATCH 09/19] Switch test to use TestSuccessfulException --- examples/junit/src/test/java/com/example/BUILD.bazel | 3 ++- .../test/java/com/example/DictionaryFuzzTests.java | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/junit/src/test/java/com/example/BUILD.bazel b/examples/junit/src/test/java/com/example/BUILD.bazel index 91db7a702..5a7b62066 100644 --- a/examples/junit/src/test/java/com/example/BUILD.bazel +++ b/examples/junit/src/test/java/com/example/BUILD.bazel @@ -258,7 +258,7 @@ java_fuzz_target_test( java_fuzz_target_test( name = "DictionaryFuzzTests_" + method, srcs = ["DictionaryFuzzTests.java"], - allowed_findings = ["java.lang.Error"], + allowed_findings = ["com.example.TestSuccessfulException"], env = {"JAZZER_FUZZ": "1"}, target_class = "com.example.DictionaryFuzzTests", target_method = method, @@ -267,6 +267,7 @@ java_fuzz_target_test( ":junit_runtime", ], deps = [ + "//examples/junit/src/test/java/com/example:test_successful_exception", "//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", diff --git a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java index ea2bdd9ea..3b6f9875b 100644 --- a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java +++ b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java @@ -34,21 +34,21 @@ public class DictionaryFuzzTests { @WithDictionary(tokens = {"a_", "53Cr3T_", "fl4G"}) @FuzzTest - public void inlineTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { + public void inlineTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { String s = data.consumeRemainingAsString(); byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); if (MessageDigest.isEqual(hash, FLAG_SHA256)) { - throw new Error("error found"); + throw new TestSuccessfulException("error found"); } } @WithDictionaryFile(resourcePath = "/com/example/test.dict") @FuzzTest - public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { + public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { String s = data.consumeRemainingAsString(); byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); if (MessageDigest.isEqual(hash, FLAG_SHA256)) { - throw new Error("error found"); + throw new TestSuccessfulException("error found"); } } @@ -56,11 +56,11 @@ public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { @WithDictionaryFile(resourcePath = "/com/example/test2.dict") @WithDictionaryFile(resourcePath = "/com/example/test3.dict") @FuzzTest - public void mixedTest(FuzzedDataProvider data) throws NoSuchAlgorithmException { + public void mixedTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { String s = data.consumeRemainingAsString(); byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); if (MessageDigest.isEqual(hash, FLAG_SHA256)) { - throw new Error("error found"); + throw new TestSuccessfulException("error found"); } } } From 2db6321da6892a52c8af9e1a69c7f9404d133763 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Fri, 6 Oct 2023 12:03:57 +0200 Subject: [PATCH 10/19] Run format --- .../src/test/java/com/example/DictionaryFuzzTests.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java index 3b6f9875b..6fb580cc2 100644 --- a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java +++ b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java @@ -34,7 +34,8 @@ public class DictionaryFuzzTests { @WithDictionary(tokens = {"a_", "53Cr3T_", "fl4G"}) @FuzzTest - public void inlineTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { + public void inlineTest(FuzzedDataProvider data) + throws NoSuchAlgorithmException, TestSuccessfulException { String s = data.consumeRemainingAsString(); byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); if (MessageDigest.isEqual(hash, FLAG_SHA256)) { @@ -44,7 +45,8 @@ public void inlineTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, @WithDictionaryFile(resourcePath = "/com/example/test.dict") @FuzzTest - public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { + public void fileTest(FuzzedDataProvider data) + throws NoSuchAlgorithmException, TestSuccessfulException { String s = data.consumeRemainingAsString(); byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); if (MessageDigest.isEqual(hash, FLAG_SHA256)) { @@ -56,7 +58,8 @@ public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, T @WithDictionaryFile(resourcePath = "/com/example/test2.dict") @WithDictionaryFile(resourcePath = "/com/example/test3.dict") @FuzzTest - public void mixedTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { + public void mixedTest(FuzzedDataProvider data) + throws NoSuchAlgorithmException, TestSuccessfulException { String s = data.consumeRemainingAsString(); byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); if (MessageDigest.isEqual(hash, FLAG_SHA256)) { From 45a281ae55e284e15e80f6f72b74d17bb3837609 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Fri, 6 Oct 2023 12:04:35 +0200 Subject: [PATCH 11/19] Remove * imports --- .../jazzer/junit/FuzzerDictionary.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index 659d9bb77..15d2c9c1a 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -17,15 +17,27 @@ package com.code_intelligence.jazzer.junit; import com.code_intelligence.jazzer.utils.Log; -import java.io.*; -import java.lang.annotation.*; +import org.junit.platform.commons.util.ClassLoaderUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; 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 From f79b5e96ec98ed520cf7c2f3d09e51137b667e0b Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Fri, 6 Oct 2023 15:39:07 +0200 Subject: [PATCH 12/19] Move interfaces into their own files and move annotations out of FuzzTestExecutor --- .../src/test/java/com/example/BUILD.bazel | 1 - .../java/com/example/DictionaryFuzzTests.java | 15 ++-- .../AgentConfiguringArgumentsProvider.java | 7 +- .../jazzer/junit/BUILD.bazel | 9 ++- .../jazzer/junit/DictionaryEntries.java | 19 +++++ .../jazzer/junit/DictionaryEntriesList.java | 12 +++ .../jazzer/junit/DictionaryFile.java | 19 +++++ .../jazzer/junit/DictionaryFiles.java | 12 +++ .../jazzer/junit/FuzzTestExecutor.java | 56 ++++++------- .../jazzer/junit/FuzzTestExtensions.java | 19 +++-- .../jazzer/junit/FuzzerDictionary.java | 79 +++++++------------ 11 files changed, 145 insertions(+), 103 deletions(-) create mode 100644 src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntries.java create mode 100644 src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntriesList.java create mode 100644 src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java create mode 100644 src/main/java/com/code_intelligence/jazzer/junit/DictionaryFiles.java diff --git a/examples/junit/src/test/java/com/example/BUILD.bazel b/examples/junit/src/test/java/com/example/BUILD.bazel index 5a7b62066..8dcce7cf5 100644 --- a/examples/junit/src/test/java/com/example/BUILD.bazel +++ b/examples/junit/src/test/java/com/example/BUILD.bazel @@ -270,7 +270,6 @@ java_fuzz_target_test( "//examples/junit/src/test/java/com/example:test_successful_exception", "//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", ], diff --git a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java index 6fb580cc2..f02914d51 100644 --- a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java +++ b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java @@ -17,9 +17,10 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.junit.DictionaryEntries; +import com.code_intelligence.jazzer.junit.DictionaryFile; import com.code_intelligence.jazzer.junit.FuzzTest; -import com.code_intelligence.jazzer.junit.FuzzerDictionary.WithDictionary; -import com.code_intelligence.jazzer.junit.FuzzerDictionary.WithDictionaryFile; + import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -32,7 +33,7 @@ public class DictionaryFuzzTests { private static final byte[] FLAG_SHA256 = Base64.getDecoder().decode("IT7goSzYg6MXLugHl9H4oCswA+OEb4bGZmKrDzlZjO4="); - @WithDictionary(tokens = {"a_", "53Cr3T_", "fl4G"}) + @DictionaryEntries(tokens = {"a_", "53Cr3T_", "fl4G"}) @FuzzTest public void inlineTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { @@ -43,7 +44,7 @@ public void inlineTest(FuzzedDataProvider data) } } - @WithDictionaryFile(resourcePath = "/com/example/test.dict") + @DictionaryFile(resourcePath = "/com/example/test.dict") @FuzzTest public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { @@ -54,9 +55,9 @@ public void fileTest(FuzzedDataProvider data) } } - @WithDictionary(tokens = {"a_"}) - @WithDictionaryFile(resourcePath = "/com/example/test2.dict") - @WithDictionaryFile(resourcePath = "/com/example/test3.dict") + @DictionaryEntries(tokens = {"a_"}) + @DictionaryFile(resourcePath = "/com/example/test2.dict") + @DictionaryFile(resourcePath = "/com/example/test3.dict") @FuzzTest public void mixedTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { diff --git a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java index 5fa8d61a9..edadc7d15 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java @@ -16,12 +16,14 @@ package com.code_intelligence.jazzer.junit; -import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.support.AnnotationConsumer; +import java.util.Optional; +import java.util.stream.Stream; + class AgentConfiguringArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { private FuzzTest fuzzTest; @@ -36,11 +38,12 @@ public Stream provideArguments(ExtensionContext extensionCo // FIXME(fmeum): Calling this here feels like a hack. There should be a lifecycle hook that runs // before the argument discovery for a ParameterizedTest is kicked off, but I haven't found // one. + Optional dictionaryPath = FuzzerDictionary.createDictionaryFile(extensionContext); // We need to call this method here in addition to the call in FuzzTestExtensions as our // ArgumentProviders need the bootstrap jar on the classpath and there may be no user-provided // ArgumentProviders to trigger the call in FuzzTestExtensions. FuzzTestExecutor.configureAndInstallAgent( - extensionContext, fuzzTest.maxDuration(), fuzzTest.maxExecutions()); + extensionContext, fuzzTest.maxDuration(), fuzzTest.maxExecutions(), dictionaryPath); return Stream.empty(); } } diff --git a/src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel index 29f0471a0..53305f769 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel @@ -24,8 +24,13 @@ java_library( name = "fuzz_test", srcs = [ "AgentConfiguringArgumentsProvider.java", + "DictionaryEntries.java", + "DictionaryEntriesList.java", + "DictionaryFile.java", + "DictionaryFiles.java", "FuzzTest.java", "FuzzTestExtensions.java", + "FuzzerDictionary.java", "FuzzingArgumentsProvider.java", "SeedArgumentsProvider.java", ], @@ -48,6 +53,7 @@ java_library( ":lifecycle", ":seed_serializer", ":utils", + "//src/main/java/com/code_intelligence/jazzer/utils:log", "@maven//:org_junit_jupiter_junit_jupiter_api", "@maven//:org_junit_jupiter_junit_jupiter_params", "@maven//:org_junit_platform_junit_platform_commons", @@ -64,12 +70,10 @@ java_jni_library( name = "fuzz_test_executor", srcs = [ "FuzzTestExecutor.java", - "FuzzerDictionary.java", ], native_libs = [ "//src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", ], - visibility = ["//examples/junit/src/test/java/com/example:__pkg__"], deps = [ ":agent_configurator", ":junit_lifecycle_methods_invoker", @@ -85,7 +89,6 @@ java_jni_library( "//src/main/java/com/code_intelligence/jazzer/driver/junit:exit_code_exception", "//src/main/java/com/code_intelligence/jazzer/mutation", "//src/main/java/com/code_intelligence/jazzer/utils", - "//src/main/java/com/code_intelligence/jazzer/utils:log", "@maven//:org_junit_jupiter_junit_jupiter_api", "@maven//:org_junit_jupiter_junit_jupiter_params", "@maven//:org_junit_platform_junit_platform_commons", diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntries.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntries.java new file mode 100644 index 000000000..b1b27daeb --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntries.java @@ -0,0 +1,19 @@ +package com.code_intelligence.jazzer.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines a reference to a dictionary within the resources directory. These should follow libfuzzer's dictionary syntax. + */ +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(DictionaryEntriesList.class) +public @interface DictionaryEntries { + String[] tokens(); +} + diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntriesList.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntriesList.java new file mode 100644 index 000000000..c99a95181 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntriesList.java @@ -0,0 +1,12 @@ +package com.code_intelligence.jazzer.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DictionaryEntriesList { + DictionaryEntries[] value(); +} diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java new file mode 100644 index 000000000..5e8fba8c3 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java @@ -0,0 +1,19 @@ +package com.code_intelligence.jazzer.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines a reference to a dictionary within the resources directory. These should follow libfuzzer's dictionary syntax. + */ +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(DictionaryFiles.class) +public @interface DictionaryFile { + String resourcePath(); +} + diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFiles.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFiles.java new file mode 100644 index 000000000..a08686505 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFiles.java @@ -0,0 +1,12 @@ +package com.code_intelligence.jazzer.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DictionaryFiles { + DictionaryFile[] value(); +} diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 0ea6d16c2..252021a36 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -14,19 +14,18 @@ package com.code_intelligence.jazzer.junit; -import static com.code_intelligence.jazzer.junit.Utils.durationStringToSeconds; -import static com.code_intelligence.jazzer.junit.Utils.generatedCorpusPath; -import static com.code_intelligence.jazzer.junit.Utils.inputsDirectoryResourcePath; -import static com.code_intelligence.jazzer.junit.Utils.inputsDirectorySourcePath; -import static java.util.stream.Collectors.toList; - import com.code_intelligence.jazzer.agent.AgentInstaller; import com.code_intelligence.jazzer.driver.FuzzTargetHolder; import com.code_intelligence.jazzer.driver.FuzzTargetRunner; import com.code_intelligence.jazzer.driver.Opt; 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 org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.platform.commons.support.AnnotationSupport; + import java.io.File; import java.io.IOException; import java.lang.reflect.Executable; @@ -40,6 +39,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashMap; @@ -50,12 +50,12 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.stream.Stream; -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.platform.commons.support.AnnotationSupport; + +import static com.code_intelligence.jazzer.junit.Utils.durationStringToSeconds; +import static com.code_intelligence.jazzer.junit.Utils.generatedCorpusPath; +import static com.code_intelligence.jazzer.junit.Utils.inputsDirectoryResourcePath; +import static com.code_intelligence.jazzer.junit.Utils.inputsDirectorySourcePath; +import static java.util.stream.Collectors.toList; class FuzzTestExecutor { private static final AtomicBoolean hasBeenPrepared = new AtomicBoolean(); @@ -72,7 +72,7 @@ private FuzzTestExecutor( this.isRunFromCommandLine = isRunFromCommandLine; } - public static FuzzTestExecutor prepare(ExtensionContext context, String maxDuration, long maxRuns) + public static FuzzTestExecutor prepare(ExtensionContext context, String maxDuration, long maxRuns, Optional dictionaryPath) throws IOException { if (!hasBeenPrepared.compareAndSet(false, true)) { throw new IllegalStateException( @@ -122,7 +122,12 @@ public static FuzzTestExecutor prepare(ExtensionContext context, String maxDurat Optional.of(addInputAndSeedDirs(context, libFuzzerArgs, createDefaultGeneratedCorpusDir)); } - createDictionaryFile(context).ifPresent(s -> libFuzzerArgs.add("-dict=" + s)); + if (dictionaryPath.isPresent()) { + System.out.printf("USING DICTIONARY %s%n", dictionaryPath.get()); + } else { + System.out.println("NO DICTIONARY"); + } + dictionaryPath.ifPresent(s -> libFuzzerArgs.add("-dict=" + s)); libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(maxDuration)); if (maxRuns > 0) { @@ -265,19 +270,6 @@ private static Path addInputAndSeedDirs( return javaSeedsDir; } - private static Optional createDictionaryFile(ExtensionContext context) - throws IOException { - List inlineDictionaries = - AnnotationSupport.findRepeatableAnnotations( - context.getRequiredTestMethod(), WithDictionary.class); - - List fileDictionaries = - AnnotationSupport.findRepeatableAnnotations( - context.getRequiredTestMethod(), WithDictionaryFile.class); - - return FuzzerDictionary.createDictionaryFile(inlineDictionaries, fileDictionaries); - } - /** Returns the list of arguments set on the command line. */ private static List getLibFuzzerArgs(ExtensionContext extensionContext) { List args = new ArrayList<>(); @@ -292,13 +284,13 @@ private static List getLibFuzzerArgs(ExtensionContext extensionContext) } static void configureAndInstallAgent( - ExtensionContext extensionContext, String maxDuration, long maxExecutions) + ExtensionContext extensionContext, String maxDuration, long maxExecutions, Optional dictionaryPath) throws IOException { if (!agentInstalled.compareAndSet(false, true)) { return; } if (Utils.isFuzzing(extensionContext)) { - FuzzTestExecutor executor = prepare(extensionContext, maxDuration, maxExecutions); + FuzzTestExecutor executor = prepare(extensionContext, maxDuration, maxExecutions, dictionaryPath); extensionContext.getRoot().getStore(Namespace.GLOBAL).put(FuzzTestExecutor.class, executor); AgentConfigurator.forFuzzing(extensionContext); } else { @@ -380,6 +372,8 @@ public Optional execute( }); } + System.out.println("STARTING LIBFUZZER"); + System.out.printf("WITH ARGS %s%n", Arrays.toString(libFuzzerArgs.toArray())); int exitCode = FuzzTargetRunner.startLibFuzzer(libFuzzerArgs); javaSeedsDir.ifPresent(FuzzTestExecutor::deleteJavaSeedsDir); Throwable finding = atomicFinding.get(); diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java index 380642c75..389b05c0b 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java @@ -14,12 +14,6 @@ package com.code_intelligence.jazzer.junit; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; @@ -30,6 +24,15 @@ import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.platform.commons.support.AnnotationSupport; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +import static com.code_intelligence.jazzer.junit.FuzzerDictionary.createDictionaryFile; + class FuzzTestExtensions implements ExecutionCondition, InvocationInterceptor, TestExecutionExceptionHandler { private static final String JAZZER_INTERNAL = @@ -46,11 +49,13 @@ public void interceptTestTemplateMethod( throws Throwable { FuzzTest fuzzTest = AnnotationSupport.findAnnotation(invocationContext.getExecutable(), FuzzTest.class).get(); + Optional dictionaryPath = createDictionaryFile(extensionContext); + // We need to call this method here in addition to the call in AgentConfiguringArgumentsProvider // as that provider isn't invoked before fuzz test executions for the arguments provided by // user-provided ArgumentsProviders ("Java seeds"). FuzzTestExecutor.configureAndInstallAgent( - extensionContext, fuzzTest.maxDuration(), fuzzTest.maxExecutions()); + extensionContext, fuzzTest.maxDuration(), fuzzTest.maxExecutions(), dictionaryPath); // Skip the invocation of the test method with the special arguments provided by // FuzzTestArgumentsProvider and start fuzzing instead. if (Utils.isMarkedInvocation(invocationContext)) { diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index 15d2c9c1a..08711cec7 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -17,6 +17,8 @@ package com.code_intelligence.jazzer.junit; import com.code_intelligence.jazzer.utils.Log; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.ClassLoaderUtils; import java.io.BufferedReader; @@ -27,11 +29,6 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UncheckedIOException; -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.nio.file.Files; import java.util.Arrays; import java.util.List; @@ -40,8 +37,8 @@ import java.util.stream.Stream; /** - * Class that manages dictionaries for fuzz tests. The {@link WithDictionary} and {@link - * WithDictionaryFile} annotations are added to {@link FuzzTest}s to indicate that these + * Class that manages dictionaries for fuzz tests. The {@link DictionaryEntries} and {@link + * DictionaryFile} 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. @@ -49,24 +46,36 @@ *

Syntax for dictionaries can be found here. */ -public class FuzzerDictionary { +class FuzzerDictionary { private static final String DICTIONARY_PREFIX = "jazzer-"; private static final String DICTIONARY_SUFFIX = ".dict"; + static Optional createDictionaryFile(ExtensionContext context) throws IOException { + List inlineDictionaries = + AnnotationSupport.findRepeatableAnnotations( + context.getRequiredTestMethod(), DictionaryEntries.class); + + List fileDictionaries = + AnnotationSupport.findRepeatableAnnotations( + context.getRequiredTestMethod(), DictionaryFile.class); + + return FuzzerDictionary.createDictionaryFile(inlineDictionaries, fileDictionaries); + } + /** * 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 + * @param inline List of {@link DictionaryEntries} annotations that directly hold static token values * to use in the dictionary - * @param files List of {@link WithDictionaryFile} annotations that reference dictionary files to + * @param files List of {@link DictionaryFile} 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 createDictionaryFile( - List inline, List files) throws IOException { + private static Optional createDictionaryFile( + List inline, List files) throws IOException { int sources = inline.size() + files.size(); if (sources == 0) { return Optional.empty(); @@ -98,12 +107,12 @@ public static Optional createDictionaryFile( * 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 + * @param inline List of {@link DictionaryEntries} annotations to extract from * @return stream of all the tokens from each of the elements of {@code inline} */ - private static Stream getInlineTokens(List inline) { + private static Stream getInlineTokens(List inline) { return inline.stream() - .map(WithDictionary::tokens) + .map(DictionaryEntries::tokens) .flatMap(Arrays::stream) .map(token -> String.format("\"%s\"", token)); } @@ -111,12 +120,12 @@ private static Stream getInlineTokens(List inline) { /** * Gets the individual lines from each of the specified dictionary files * - * @param files List of {@link WithDictionaryFile} annotations indicating which files to use + * @param files List of {@link DictionaryFile} annotations indicating which files to use * @return stream of all lines from each of the files */ - private static Stream getFileTokens(List files) { + private static Stream getFileTokens(List files) { return files.stream() - .map(WithDictionaryFile::resourcePath) + .map(DictionaryFile::resourcePath) .map(FuzzerDictionary::tokensFromFile) .flatMap(List::stream); } @@ -134,38 +143,4 @@ private static List tokensFromFile(String path) { 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 libfuzzer's dictionary syntax. - */ - @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) - @Retention(RetentionPolicy.RUNTIME) - @Repeatable(DictionaryFiles.class) - public @interface WithDictionaryFile { - String resourcePath(); - } - - @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) - @Retention(RetentionPolicy.RUNTIME) - public @interface Dictionaries { - WithDictionary[] value(); - } - - @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) - @Retention(RetentionPolicy.RUNTIME) - public @interface DictionaryFiles { - WithDictionaryFile[] value(); - } } From d0155cb6f42aabca33a109b8a9d1ee969d1abbd8 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Fri, 6 Oct 2023 15:50:17 +0200 Subject: [PATCH 13/19] Format, documentation and change `createDictionaryFile` to take a Method instead of `extensionContext` --- .../java/com/example/DictionaryFuzzTests.java | 1 - .../AgentConfiguringArgumentsProvider.java | 8 ++-- .../jazzer/junit/DictionaryEntries.java | 19 +++++++- .../jazzer/junit/DictionaryEntriesList.java | 18 +++++++- .../jazzer/junit/DictionaryFile.java | 19 +++++++- .../jazzer/junit/DictionaryFiles.java | 18 +++++++- .../jazzer/junit/FuzzTestExecutor.java | 36 ++++++++------- .../jazzer/junit/FuzzTestExtensions.java | 20 ++++----- .../jazzer/junit/FuzzerDictionary.java | 45 ++++++++++--------- 9 files changed, 126 insertions(+), 58 deletions(-) diff --git a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java index f02914d51..8da9d9931 100644 --- a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java +++ b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java @@ -20,7 +20,6 @@ import com.code_intelligence.jazzer.junit.DictionaryEntries; import com.code_intelligence.jazzer.junit.DictionaryFile; import com.code_intelligence.jazzer.junit.FuzzTest; - import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; diff --git a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java index edadc7d15..78b5221c4 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java @@ -16,14 +16,13 @@ package com.code_intelligence.jazzer.junit; +import java.util.Optional; +import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.support.AnnotationConsumer; -import java.util.Optional; -import java.util.stream.Stream; - class AgentConfiguringArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { private FuzzTest fuzzTest; @@ -38,7 +37,8 @@ public Stream provideArguments(ExtensionContext extensionCo // FIXME(fmeum): Calling this here feels like a hack. There should be a lifecycle hook that runs // before the argument discovery for a ParameterizedTest is kicked off, but I haven't found // one. - Optional dictionaryPath = FuzzerDictionary.createDictionaryFile(extensionContext); + Optional dictionaryPath = + FuzzerDictionary.createDictionaryFile(extensionContext.getRequiredTestMethod()); // We need to call this method here in addition to the call in FuzzTestExtensions as our // ArgumentProviders need the bootstrap jar on the classpath and there may be no user-provided // ArgumentProviders to trigger the call in FuzzTestExtensions. diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntries.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntries.java index b1b27daeb..16695cf94 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntries.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntries.java @@ -1,3 +1,19 @@ +/* + * 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.junit; import java.lang.annotation.ElementType; @@ -14,6 +30,5 @@ @Retention(RetentionPolicy.RUNTIME) @Repeatable(DictionaryEntriesList.class) public @interface DictionaryEntries { - String[] tokens(); + String[] tokens(); } - diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntriesList.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntriesList.java index c99a95181..74e4d3af2 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntriesList.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryEntriesList.java @@ -1,3 +1,19 @@ +/* + * 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.junit; import java.lang.annotation.ElementType; @@ -8,5 +24,5 @@ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DictionaryEntriesList { - DictionaryEntries[] value(); + DictionaryEntries[] value(); } diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java index 5e8fba8c3..e9c602fb3 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java @@ -1,3 +1,19 @@ +/* + * 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.junit; import java.lang.annotation.ElementType; @@ -14,6 +30,5 @@ @Retention(RetentionPolicy.RUNTIME) @Repeatable(DictionaryFiles.class) public @interface DictionaryFile { - String resourcePath(); + String resourcePath(); } - diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFiles.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFiles.java index a08686505..73bad5b5d 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFiles.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFiles.java @@ -1,3 +1,19 @@ +/* + * 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.junit; import java.lang.annotation.ElementType; @@ -8,5 +24,5 @@ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DictionaryFiles { - DictionaryFile[] value(); + DictionaryFile[] value(); } diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 252021a36..06a6f934d 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -14,18 +14,17 @@ package com.code_intelligence.jazzer.junit; +import static com.code_intelligence.jazzer.junit.Utils.durationStringToSeconds; +import static com.code_intelligence.jazzer.junit.Utils.generatedCorpusPath; +import static com.code_intelligence.jazzer.junit.Utils.inputsDirectoryResourcePath; +import static com.code_intelligence.jazzer.junit.Utils.inputsDirectorySourcePath; +import static java.util.stream.Collectors.toList; + import com.code_intelligence.jazzer.agent.AgentInstaller; import com.code_intelligence.jazzer.driver.FuzzTargetHolder; import com.code_intelligence.jazzer.driver.FuzzTargetRunner; import com.code_intelligence.jazzer.driver.Opt; import com.code_intelligence.jazzer.driver.junit.ExitCodeException; -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.platform.commons.support.AnnotationSupport; - import java.io.File; import java.io.IOException; import java.lang.reflect.Executable; @@ -50,12 +49,12 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.stream.Stream; - -import static com.code_intelligence.jazzer.junit.Utils.durationStringToSeconds; -import static com.code_intelligence.jazzer.junit.Utils.generatedCorpusPath; -import static com.code_intelligence.jazzer.junit.Utils.inputsDirectoryResourcePath; -import static com.code_intelligence.jazzer.junit.Utils.inputsDirectorySourcePath; -import static java.util.stream.Collectors.toList; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.platform.commons.support.AnnotationSupport; class FuzzTestExecutor { private static final AtomicBoolean hasBeenPrepared = new AtomicBoolean(); @@ -72,7 +71,8 @@ private FuzzTestExecutor( this.isRunFromCommandLine = isRunFromCommandLine; } - public static FuzzTestExecutor prepare(ExtensionContext context, String maxDuration, long maxRuns, Optional dictionaryPath) + public static FuzzTestExecutor prepare( + ExtensionContext context, String maxDuration, long maxRuns, Optional dictionaryPath) throws IOException { if (!hasBeenPrepared.compareAndSet(false, true)) { throw new IllegalStateException( @@ -284,13 +284,17 @@ private static List getLibFuzzerArgs(ExtensionContext extensionContext) } static void configureAndInstallAgent( - ExtensionContext extensionContext, String maxDuration, long maxExecutions, Optional dictionaryPath) + ExtensionContext extensionContext, + String maxDuration, + long maxExecutions, + Optional dictionaryPath) throws IOException { if (!agentInstalled.compareAndSet(false, true)) { return; } if (Utils.isFuzzing(extensionContext)) { - FuzzTestExecutor executor = prepare(extensionContext, maxDuration, maxExecutions, dictionaryPath); + FuzzTestExecutor executor = + prepare(extensionContext, maxDuration, maxExecutions, dictionaryPath); extensionContext.getRoot().getStore(Namespace.GLOBAL).put(FuzzTestExecutor.class, executor); AgentConfigurator.forFuzzing(extensionContext); } else { diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java index 389b05c0b..bb47859a2 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java @@ -14,6 +14,14 @@ package com.code_intelligence.jazzer.junit; +import static com.code_intelligence.jazzer.junit.FuzzerDictionary.createDictionaryFile; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; @@ -24,15 +32,6 @@ import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.platform.commons.support.AnnotationSupport; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; - -import static com.code_intelligence.jazzer.junit.FuzzerDictionary.createDictionaryFile; - class FuzzTestExtensions implements ExecutionCondition, InvocationInterceptor, TestExecutionExceptionHandler { private static final String JAZZER_INTERNAL = @@ -49,7 +48,8 @@ public void interceptTestTemplateMethod( throws Throwable { FuzzTest fuzzTest = AnnotationSupport.findAnnotation(invocationContext.getExecutable(), FuzzTest.class).get(); - Optional dictionaryPath = createDictionaryFile(extensionContext); + Optional dictionaryPath = + createDictionaryFile(extensionContext.getRequiredTestMethod()); // We need to call this method here in addition to the call in AgentConfiguringArgumentsProvider // as that provider isn't invoked before fuzz test executions for the arguments provided by diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index 08711cec7..8fc16555a 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -17,10 +17,6 @@ package com.code_intelligence.jazzer.junit; import com.code_intelligence.jazzer.utils.Log; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.commons.support.AnnotationSupport; -import org.junit.platform.commons.util.ClassLoaderUtils; - import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; @@ -29,19 +25,21 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UncheckedIOException; +import java.lang.reflect.Method; 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.support.AnnotationSupport; +import org.junit.platform.commons.util.ClassLoaderUtils; /** * Class that manages dictionaries for fuzz tests. The {@link DictionaryEntries} and {@link - * DictionaryFile} 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. + * DictionaryFile} 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. * *

Syntax for dictionaries can be found here. @@ -50,28 +48,33 @@ class FuzzerDictionary { private static final String DICTIONARY_PREFIX = "jazzer-"; private static final String DICTIONARY_SUFFIX = ".dict"; - static Optional createDictionaryFile(ExtensionContext context) throws IOException { + /** + * Create a temporary dictionary file for use during a fuzzing run based on the {@link + * DictionaryEntries} and {@link DictionaryFile} annotations applied to {@code method} + * + * @param method The method which has 0 or more {@link DictionaryEntries} and {@link + * DictionaryFile} annotations applied + * @return Optional containing the path to the created file, or nothing if {@code inline} and + * {@code files} are both empty + * @throws IOException + */ + static Optional createDictionaryFile(Method method) throws IOException { List inlineDictionaries = - AnnotationSupport.findRepeatableAnnotations( - context.getRequiredTestMethod(), DictionaryEntries.class); + AnnotationSupport.findRepeatableAnnotations(method, DictionaryEntries.class); List fileDictionaries = - AnnotationSupport.findRepeatableAnnotations( - context.getRequiredTestMethod(), DictionaryFile.class); + AnnotationSupport.findRepeatableAnnotations(method, DictionaryFile.class); return FuzzerDictionary.createDictionaryFile(inlineDictionaries, fileDictionaries); } /** - * Create a temporary dictionary file for use during a fuzzing run based on the tokens found - * within {@code inline} and {@code files}. + * Takes the lists of {@link DictionaryEntries} and {@link DictionaryFile} and creates the + * temporary dictionary file based on their tokens * - * @param inline List of {@link DictionaryEntries} annotations that directly hold static token values - * to use in the dictionary - * @param files List of {@link DictionaryFile} 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 + * @param inline list of {@link DictionaryEntries} + * @param files list of {@link DictionaryFile} + * @return Optional of dictionaryPath if created * @throws IOException */ private static Optional createDictionaryFile( From 62fd34ae0524047b6c0b0090cdd9dfe89ebe9332 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Fri, 6 Oct 2023 16:16:57 +0200 Subject: [PATCH 14/19] Rename tokensFromFile to tokensFromResource --- .../code_intelligence/jazzer/junit/FuzzerDictionary.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index 8fc16555a..b95427642 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -129,14 +129,15 @@ private static Stream getInlineTokens(List inline) { private static Stream getFileTokens(List files) { return files.stream() .map(DictionaryFile::resourcePath) - .map(FuzzerDictionary::tokensFromFile) + .map(FuzzerDictionary::tokensFromResource) .flatMap(List::stream); } - private static List tokensFromFile(String path) { - try (InputStream resourceFile = ClassLoaderUtils.class.getResourceAsStream(path)) { + private static List tokensFromResource(String absoluteResourcePath) { + try (InputStream resourceFile = + ClassLoaderUtils.class.getResourceAsStream(absoluteResourcePath)) { if (resourceFile == null) { - throw new FileNotFoundException(path); + throw new FileNotFoundException(absoluteResourcePath); } BufferedReader reader = new BufferedReader(new InputStreamReader(resourceFile)); // I think returning just reader.lines() results in the file stream being closed before it's From db537377ddc241fece2666bfbe6a97d81ef7c4eb Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Mon, 9 Oct 2023 11:39:24 +0200 Subject: [PATCH 15/19] Address comments around dictionary creation --- .../AgentConfiguringArgumentsProvider.java | 3 +- .../jazzer/junit/FuzzTestExecutor.java | 13 ++------ .../jazzer/junit/FuzzTestExtensions.java | 12 ++------ .../jazzer/junit/FuzzerDictionary.java | 30 +++++++------------ 4 files changed, 19 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java index 78b5221c4..42a2122bd 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java @@ -16,6 +16,7 @@ package com.code_intelligence.jazzer.junit; +import java.nio.file.Path; import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; @@ -37,7 +38,7 @@ public Stream provideArguments(ExtensionContext extensionCo // FIXME(fmeum): Calling this here feels like a hack. There should be a lifecycle hook that runs // before the argument discovery for a ParameterizedTest is kicked off, but I haven't found // one. - Optional dictionaryPath = + Optional dictionaryPath = FuzzerDictionary.createDictionaryFile(extensionContext.getRequiredTestMethod()); // We need to call this method here in addition to the call in FuzzTestExtensions as our // ArgumentProviders need the bootstrap jar on the classpath and there may be no user-provided diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 06a6f934d..0fe84e7d5 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -37,14 +37,7 @@ import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -72,7 +65,7 @@ private FuzzTestExecutor( } public static FuzzTestExecutor prepare( - ExtensionContext context, String maxDuration, long maxRuns, Optional dictionaryPath) + ExtensionContext context, String maxDuration, long maxRuns, Optional dictionaryPath) throws IOException { if (!hasBeenPrepared.compareAndSet(false, true)) { throw new IllegalStateException( @@ -287,7 +280,7 @@ static void configureAndInstallAgent( ExtensionContext extensionContext, String maxDuration, long maxExecutions, - Optional dictionaryPath) + Optional dictionaryPath) throws IOException { if (!agentInstalled.compareAndSet(false, true)) { return; diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java index bb47859a2..e58ed5940 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java @@ -19,17 +19,12 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.nio.file.Path; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.*; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.InvocationInterceptor; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.platform.commons.support.AnnotationSupport; class FuzzTestExtensions @@ -48,8 +43,7 @@ public void interceptTestTemplateMethod( throws Throwable { FuzzTest fuzzTest = AnnotationSupport.findAnnotation(invocationContext.getExecutable(), FuzzTest.class).get(); - Optional dictionaryPath = - createDictionaryFile(extensionContext.getRequiredTestMethod()); + Optional dictionaryPath = createDictionaryFile(extensionContext.getRequiredTestMethod()); // We need to call this method here in addition to the call in AgentConfiguringArgumentsProvider // as that provider isn't invoked before fuzz test executions for the arguments provided by diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index b95427642..6a151e45a 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -17,16 +17,11 @@ package com.code_intelligence.jazzer.junit; import com.code_intelligence.jazzer.utils.Log; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.UncheckedIOException; +import java.io.*; import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -58,7 +53,7 @@ class FuzzerDictionary { * {@code files} are both empty * @throws IOException */ - static Optional createDictionaryFile(Method method) throws IOException { + static Optional createDictionaryFile(Method method) throws IOException { List inlineDictionaries = AnnotationSupport.findRepeatableAnnotations(method, DictionaryEntries.class); @@ -77,7 +72,7 @@ static Optional createDictionaryFile(Method method) throws IOException { * @return Optional of dictionaryPath if created * @throws IOException */ - private static Optional createDictionaryFile( + private static Optional createDictionaryFile( List inline, List files) throws IOException { int sources = inline.size() + files.size(); if (sources == 0) { @@ -86,24 +81,21 @@ private static Optional createDictionaryFile( Stream joined = Stream.concat(getInlineTokens(inline), getFileTokens(files)); - File f = File.createTempFile(DICTIONARY_PREFIX, DICTIONARY_SUFFIX); - f.deleteOnExit(); + Path p = Files.createTempFile(DICTIONARY_PREFIX, DICTIONARY_SUFFIX); + p.toFile().deleteOnExit(); Log.info(String.format("Creating merged dictionary from %d sources", sources)); - try (OutputStream out = Files.newOutputStream(f.toPath())) { + try (Writer w = Files.newBufferedWriter(p, StandardCharsets.UTF_8)) { joined.forEach( - (token) -> { + 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()); + w.append(token).append('\n'); } catch (IOException e) { throw new UncheckedIOException(e); } }); } - return Optional.of(f.getPath()); + return Optional.of(p); } /** From ee2766d62943822307cf2c7832ef206d3d662959 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Mon, 9 Oct 2023 12:36:15 +0200 Subject: [PATCH 16/19] Switched class to get the loader from, enforce absolute paths in dictionary files --- .../java/com/example/DictionaryFuzzTests.java | 6 +++--- .../jazzer/junit/DictionaryFile.java | 5 +++-- .../jazzer/junit/FuzzerDictionary.java | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java index 8da9d9931..63dfe147d 100644 --- a/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java +++ b/examples/junit/src/test/java/com/example/DictionaryFuzzTests.java @@ -43,7 +43,7 @@ public void inlineTest(FuzzedDataProvider data) } } - @DictionaryFile(resourcePath = "/com/example/test.dict") + @DictionaryFile(resourcePath = "com/example/test.dict") @FuzzTest public void fileTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { @@ -55,8 +55,8 @@ public void fileTest(FuzzedDataProvider data) } @DictionaryEntries(tokens = {"a_"}) - @DictionaryFile(resourcePath = "/com/example/test2.dict") - @DictionaryFile(resourcePath = "/com/example/test3.dict") + @DictionaryFile(resourcePath = "com/example/test2.dict") + @DictionaryFile(resourcePath = "com/example/test3.dict") @FuzzTest public void mixedTest(FuzzedDataProvider data) throws NoSuchAlgorithmException, TestSuccessfulException { diff --git a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java index e9c602fb3..93a3d2555 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/DictionaryFile.java @@ -23,8 +23,9 @@ import java.lang.annotation.Target; /** - * Defines a reference to a dictionary within the resources directory. These should follow libfuzzer's dictionary syntax. + * Defines a reference to a dictionary within the resources directory. The given {@code + * resourcePath} must be an absolute path and must not have a leading {@code /}. These should follow + * libfuzzer's dictionary syntax. */ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index 6a151e45a..57e1c1d62 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -17,7 +17,13 @@ package com.code_intelligence.jazzer.junit; import com.code_intelligence.jazzer.utils.Log; -import java.io.*; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.io.Writer; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -28,7 +34,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.platform.commons.support.AnnotationSupport; -import org.junit.platform.commons.util.ClassLoaderUtils; /** * Class that manages dictionaries for fuzz tests. The {@link DictionaryEntries} and {@link @@ -126,8 +131,13 @@ private static Stream getFileTokens(List files) { } private static List tokensFromResource(String absoluteResourcePath) { + if (absoluteResourcePath.startsWith("/")) { + throw new IllegalArgumentException( + String.format( + "absolute resource path is must not have leading /: %s", absoluteResourcePath)); + } try (InputStream resourceFile = - ClassLoaderUtils.class.getResourceAsStream(absoluteResourcePath)) { + FuzzerDictionary.class.getClassLoader().getResourceAsStream(absoluteResourcePath)) { if (resourceFile == null) { throw new FileNotFoundException(absoluteResourcePath); } From 24440414ad17092017a718f9e3b7e1fb1e4d97dc Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Mon, 9 Oct 2023 13:09:49 +0200 Subject: [PATCH 17/19] Run format --- .../jazzer/junit/FuzzTestExtensions.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java index e58ed5940..85b8fc999 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java @@ -23,8 +23,14 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; import org.junit.platform.commons.support.AnnotationSupport; class FuzzTestExtensions From b85330851130e0b2247c8caeefe5c2abc807bf23 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Mon, 9 Oct 2023 13:11:58 +0200 Subject: [PATCH 18/19] Make resource reader's closure more explicit --- .../code_intelligence/jazzer/junit/FuzzerDictionary.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java index 57e1c1d62..a005b33f7 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzerDictionary.java @@ -141,10 +141,11 @@ private static List tokensFromResource(String absoluteResourcePath) { if (resourceFile == null) { throw new FileNotFoundException(absoluteResourcePath); } - 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 - return reader.lines().collect(Collectors.toList()); + List tokens; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceFile))) { + tokens = reader.lines().collect(Collectors.toList()); + } + return tokens; } catch (IOException e) { throw new UncheckedIOException(e); } From b94cc6393ccc27eeac6232cf79e3db56803ad325 Mon Sep 17 00:00:00 2001 From: Brian Lewis Date: Mon, 16 Oct 2023 15:01:18 +0200 Subject: [PATCH 19/19] Remove deubug prints and unsplit an import --- .../jazzer/junit/FuzzTestExecutor.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 0fe84e7d5..9d3734fb0 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -37,7 +37,13 @@ import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.*; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -115,11 +121,6 @@ public static FuzzTestExecutor prepare( Optional.of(addInputAndSeedDirs(context, libFuzzerArgs, createDefaultGeneratedCorpusDir)); } - if (dictionaryPath.isPresent()) { - System.out.printf("USING DICTIONARY %s%n", dictionaryPath.get()); - } else { - System.out.println("NO DICTIONARY"); - } dictionaryPath.ifPresent(s -> libFuzzerArgs.add("-dict=" + s)); libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(maxDuration)); @@ -369,8 +370,6 @@ public Optional execute( }); } - System.out.println("STARTING LIBFUZZER"); - System.out.printf("WITH ARGS %s%n", Arrays.toString(libFuzzerArgs.toArray())); int exitCode = FuzzTargetRunner.startLibFuzzer(libFuzzerArgs); javaSeedsDir.ifPresent(FuzzTestExecutor::deleteJavaSeedsDir); Throwable finding = atomicFinding.get();