Skip to content

Commit 5512980

Browse files
br-lewisfmeum
andauthored
Add FuzzTest dictionary support (#862)
Add support for dictionaries in fuzz tests This adds dictionary support to JUnit fuzz tests via 2 annotations: WithDictionary and WithDictionaryFile that allow dictionaries to be specified either as static arrays of tokens or by referring to a dictionary file. Multiple instances of both annotations are allowed and all values will be merged in the dictionary given to libfuzzer. --------- Co-authored-by: Fabian Meumertzheim <[email protected]>
1 parent 646b431 commit 5512980

File tree

15 files changed

+413
-5
lines changed

15 files changed

+413
-5
lines changed

examples/junit/src/test/java/com/example/BUILD.bazel

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,33 @@ java_fuzz_target_test(
254254
],
255255
)
256256

257+
[
258+
java_fuzz_target_test(
259+
name = "DictionaryFuzzTests_" + method,
260+
srcs = ["DictionaryFuzzTests.java"],
261+
allowed_findings = ["com.example.TestSuccessfulException"],
262+
env = {"JAZZER_FUZZ": "1"},
263+
target_class = "com.example.DictionaryFuzzTests",
264+
target_method = method,
265+
verify_crash_reproducer = False,
266+
runtime_deps = [
267+
":junit_runtime",
268+
],
269+
deps = [
270+
"//examples/junit/src/test/java/com/example:test_successful_exception",
271+
"//examples/junit/src/test/resources:example_dictionaries",
272+
"//src/main/java/com/code_intelligence/jazzer/junit:fuzz_test",
273+
"@maven//:org_junit_jupiter_junit_jupiter_api",
274+
"@maven//:org_junit_jupiter_junit_jupiter_params",
275+
],
276+
)
277+
for method in [
278+
"inlineTest",
279+
"fileTest",
280+
"mixedTest",
281+
]
282+
]
283+
257284
java_library(
258285
name = "junit_runtime",
259286
runtime_deps = [
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2023 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
20+
import com.code_intelligence.jazzer.junit.DictionaryEntries;
21+
import com.code_intelligence.jazzer.junit.DictionaryFile;
22+
import com.code_intelligence.jazzer.junit.FuzzTest;
23+
import java.nio.charset.StandardCharsets;
24+
import java.security.MessageDigest;
25+
import java.security.NoSuchAlgorithmException;
26+
import java.util.Base64;
27+
28+
public class DictionaryFuzzTests {
29+
// Generated via:
30+
// printf 'a_53Cr3T_fl4G' | openssl dgst -binary -sha256 | openssl base64 -A
31+
// Luckily the fuzzer can't read comments ;-)
32+
private static final byte[] FLAG_SHA256 =
33+
Base64.getDecoder().decode("IT7goSzYg6MXLugHl9H4oCswA+OEb4bGZmKrDzlZjO4=");
34+
35+
@DictionaryEntries(tokens = {"a_", "53Cr3T_", "fl4G"})
36+
@FuzzTest
37+
public void inlineTest(FuzzedDataProvider data)
38+
throws NoSuchAlgorithmException, TestSuccessfulException {
39+
String s = data.consumeRemainingAsString();
40+
byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8));
41+
if (MessageDigest.isEqual(hash, FLAG_SHA256)) {
42+
throw new TestSuccessfulException("error found");
43+
}
44+
}
45+
46+
@DictionaryFile(resourcePath = "com/example/test.dict")
47+
@FuzzTest
48+
public void fileTest(FuzzedDataProvider data)
49+
throws NoSuchAlgorithmException, TestSuccessfulException {
50+
String s = data.consumeRemainingAsString();
51+
byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8));
52+
if (MessageDigest.isEqual(hash, FLAG_SHA256)) {
53+
throw new TestSuccessfulException("error found");
54+
}
55+
}
56+
57+
@DictionaryEntries(tokens = {"a_"})
58+
@DictionaryFile(resourcePath = "com/example/test2.dict")
59+
@DictionaryFile(resourcePath = "com/example/test3.dict")
60+
@FuzzTest
61+
public void mixedTest(FuzzedDataProvider data)
62+
throws NoSuchAlgorithmException, TestSuccessfulException {
63+
String s = data.consumeRemainingAsString();
64+
byte[] hash = MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8));
65+
if (MessageDigest.isEqual(hash, FLAG_SHA256)) {
66+
throw new TestSuccessfulException("error found");
67+
}
68+
}
69+
}

examples/junit/src/test/resources/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ java_library(
44
visibility = ["//examples/junit/src/test/java/com/example:__pkg__"],
55
)
66

7+
java_library(
8+
name = "example_dictionaries",
9+
resources = glob(["**/*.dict"]),
10+
visibility = ["//examples/junit/src/test/java/com/example:__pkg__"],
11+
)
12+
713
filegroup(
814
name = "MutatorFuzzTestInputs",
915
srcs = ["com/example/MutatorFuzzTestInputs"],
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# test dictionary
2+
"a_"
3+
"53Cr3T_"
4+
"fl4G"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"53Cr3T_"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"fl4G"

src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.code_intelligence.jazzer.junit;
1818

19+
import java.nio.file.Path;
20+
import java.util.Optional;
1921
import java.util.stream.Stream;
2022
import org.junit.jupiter.api.extension.ExtensionContext;
2123
import org.junit.jupiter.params.provider.Arguments;
@@ -36,11 +38,13 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext extensionCo
3638
// FIXME(fmeum): Calling this here feels like a hack. There should be a lifecycle hook that runs
3739
// before the argument discovery for a ParameterizedTest is kicked off, but I haven't found
3840
// one.
41+
Optional<Path> dictionaryPath =
42+
FuzzerDictionary.createDictionaryFile(extensionContext.getRequiredTestMethod());
3943
// We need to call this method here in addition to the call in FuzzTestExtensions as our
4044
// ArgumentProviders need the bootstrap jar on the classpath and there may be no user-provided
4145
// ArgumentProviders to trigger the call in FuzzTestExtensions.
4246
FuzzTestExecutor.configureAndInstallAgent(
43-
extensionContext, fuzzTest.maxDuration(), fuzzTest.maxExecutions());
47+
extensionContext, fuzzTest.maxDuration(), fuzzTest.maxExecutions(), dictionaryPath);
4448
return Stream.empty();
4549
}
4650
}

src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,13 @@ java_library(
2424
name = "fuzz_test",
2525
srcs = [
2626
"AgentConfiguringArgumentsProvider.java",
27+
"DictionaryEntries.java",
28+
"DictionaryEntriesList.java",
29+
"DictionaryFile.java",
30+
"DictionaryFiles.java",
2731
"FuzzTest.java",
2832
"FuzzTestExtensions.java",
33+
"FuzzerDictionary.java",
2934
"FuzzingArgumentsProvider.java",
3035
"SeedArgumentsProvider.java",
3136
],
@@ -48,6 +53,7 @@ java_library(
4853
":lifecycle",
4954
":seed_serializer",
5055
":utils",
56+
"//src/main/java/com/code_intelligence/jazzer/utils:log",
5157
"@maven//:org_junit_jupiter_junit_jupiter_api",
5258
"@maven//:org_junit_jupiter_junit_jupiter_params",
5359
"@maven//:org_junit_platform_junit_platform_commons",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2023 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.code_intelligence.jazzer.junit;
18+
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Repeatable;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Defines a reference to a dictionary within the resources directory. These should follow <a
27+
* href="https://llvm.org/docs/LibFuzzer.html#dictionaries">libfuzzer's dictionary syntax</a>.
28+
*/
29+
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
30+
@Retention(RetentionPolicy.RUNTIME)
31+
@Repeatable(DictionaryEntriesList.class)
32+
public @interface DictionaryEntries {
33+
String[] tokens();
34+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2023 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.code_intelligence.jazzer.junit;
18+
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
25+
@Retention(RetentionPolicy.RUNTIME)
26+
public @interface DictionaryEntriesList {
27+
DictionaryEntries[] value();
28+
}

0 commit comments

Comments
 (0)